diff --git a/src/db/migrations/0011_refactor_ai_kurzy_dates.sql b/src/db/migrations/0011_refactor_ai_kurzy_dates.sql new file mode 100644 index 0000000..4bb5835 --- /dev/null +++ b/src/db/migrations/0011_refactor_ai_kurzy_dates.sql @@ -0,0 +1,21 @@ +-- Migration: Move dates from registracie to kurzy table + +-- Step 1: Add new columns to kurzy table +ALTER TABLE kurzy ADD COLUMN IF NOT EXISTS datum_od DATE; +ALTER TABLE kurzy ADD COLUMN IF NOT EXISTS datum_do DATE; + +-- Step 2: Migrate existing data - copy most recent registration dates to each course +UPDATE kurzy k +SET datum_od = r.datum_od, datum_do = r.datum_do +FROM ( + SELECT DISTINCT ON (kurz_id) kurz_id, datum_od, datum_do + FROM registracie + WHERE datum_od IS NOT NULL OR datum_do IS NOT NULL + ORDER BY kurz_id, created_at DESC +) r +WHERE k.id = r.kurz_id; + +-- Step 3: Drop columns from registracie table +ALTER TABLE registracie DROP COLUMN IF EXISTS datum_od; +ALTER TABLE registracie DROP COLUMN IF EXISTS datum_do; +ALTER TABLE registracie DROP COLUMN IF EXISTS pocet_ucastnikov; diff --git a/src/db/schema.js b/src/db/schema.js index b6bbd53..11cdf10 100644 --- a/src/db/schema.js +++ b/src/db/schema.js @@ -441,6 +441,8 @@ export const kurzy = pgTable('kurzy', { maxKapacita: integer('max_kapacita'), aktivny: boolean('aktivny').default(true).notNull(), farba: varchar('farba', { length: 20 }), // Color for visual distinction (e.g., 'primary', 'info', 'warning') + datumOd: date('datum_od', { mode: 'date' }), + datumDo: date('datum_do', { mode: 'date' }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), }); @@ -473,10 +475,7 @@ export const registracie = pgTable('registracie', { id: serial('id').primaryKey(), kurzId: integer('kurz_id').notNull().references(() => kurzy.id, { onDelete: 'cascade' }), ucastnikId: integer('ucastnik_id').notNull().references(() => ucastnici.id, { onDelete: 'cascade' }), - datumOd: date('datum_od', { mode: 'date' }), // dátum začiatku pre túto registráciu - datumDo: date('datum_do', { mode: 'date' }), // dátum konca pre túto registráciu formaKurzu: formaKurzuEnum('forma_kurzu').default('prezencne').notNull(), - pocetUcastnikov: integer('pocet_ucastnikov').default(1).notNull(), fakturaCislo: varchar('faktura_cislo', { length: 100 }), fakturaVystavena: boolean('faktura_vystavena').default(false).notNull(), zaplatene: boolean('zaplatene').default(false).notNull(), diff --git a/src/services/ai-kurzy/certificate.service.js b/src/services/ai-kurzy/certificate.service.js index 17d98df..24d7bec 100644 --- a/src/services/ai-kurzy/certificate.service.js +++ b/src/services/ai-kurzy/certificate.service.js @@ -151,8 +151,6 @@ export const generateCertificate = async (registraciaId, templateName = 'AIcerti const [registration] = await db .select({ id: registracie.id, - datumOd: registracie.datumOd, - datumDo: registracie.datumDo, ucastnikId: registracie.ucastnikId, kurzId: registracie.kurzId, }) @@ -184,6 +182,8 @@ export const generateCertificate = async (registraciaId, templateName = 'AIcerti .select({ nazov: kurzy.nazov, popis: kurzy.popis, + datumOd: kurzy.datumOd, + datumDo: kurzy.datumDo, }) .from(kurzy) .where(eq(kurzy.id, registration.kurzId)) @@ -205,16 +205,16 @@ export const generateCertificate = async (registraciaId, templateName = 'AIcerti .filter(Boolean) .join(' '); - const issueDate = registration.datumDo || registration.datumOd || new Date(); + const issueDate = course.datumDo || course.datumOd || new Date(); // Format date range let dateRange = ''; - if (registration.datumOd && registration.datumDo) { - dateRange = `${formatDate(registration.datumOd)} - ${formatDate(registration.datumDo)}`; - } else if (registration.datumDo) { - dateRange = formatDate(registration.datumDo); - } else if (registration.datumOd) { - dateRange = formatDate(registration.datumOd); + if (course.datumOd && course.datumDo) { + dateRange = `${formatDate(course.datumOd)} - ${formatDate(course.datumDo)}`; + } else if (course.datumDo) { + dateRange = formatDate(course.datumDo); + } else if (course.datumOd) { + dateRange = formatDate(course.datumOd); } // Prepare template data diff --git a/src/services/ai-kurzy/kurzy.service.js b/src/services/ai-kurzy/kurzy.service.js index c470016..4f52310 100644 --- a/src/services/ai-kurzy/kurzy.service.js +++ b/src/services/ai-kurzy/kurzy.service.js @@ -14,6 +14,8 @@ export const getAllKurzy = async () => { maxKapacita: kurzy.maxKapacita, aktivny: kurzy.aktivny, farba: kurzy.farba, + datumOd: kurzy.datumOd, + datumDo: kurzy.datumDo, createdAt: kurzy.createdAt, registraciiCount: sql`(SELECT COUNT(*) FROM registracie WHERE kurz_id = ${kurzy.id})::int`, }) @@ -48,6 +50,8 @@ export const createKurz = async (data) => { maxKapacita: data.maxKapacita || null, aktivny: data.aktivny !== undefined ? data.aktivny : true, farba: data.farba || null, + datumOd: data.datumOd ? new Date(data.datumOd) : null, + datumDo: data.datumDo ? new Date(data.datumDo) : null, }) .returning(); @@ -65,6 +69,8 @@ export const updateKurz = async (id, data) => { maxKapacita: data.maxKapacita !== undefined ? data.maxKapacita : undefined, aktivny: data.aktivny !== undefined ? data.aktivny : undefined, farba: data.farba !== undefined ? data.farba : undefined, + datumOd: data.datumOd !== undefined ? (data.datumOd ? new Date(data.datumOd) : null) : undefined, + datumDo: data.datumDo !== undefined ? (data.datumDo ? new Date(data.datumDo) : null) : undefined, updatedAt: new Date(), }; diff --git a/src/services/ai-kurzy/registracie.service.js b/src/services/ai-kurzy/registracie.service.js index e57485a..f14ef9f 100644 --- a/src/services/ai-kurzy/registracie.service.js +++ b/src/services/ai-kurzy/registracie.service.js @@ -11,10 +11,7 @@ export const getAllRegistracie = async (kurzId = null) => { id: registracie.id, kurzId: registracie.kurzId, ucastnikId: registracie.ucastnikId, - datumOd: registracie.datumOd, - datumDo: registracie.datumDo, formaKurzu: registracie.formaKurzu, - pocetUcastnikov: registracie.pocetUcastnikov, fakturaCislo: registracie.fakturaCislo, fakturaVystavena: registracie.fakturaVystavena, zaplatene: registracie.zaplatene, @@ -23,6 +20,8 @@ export const getAllRegistracie = async (kurzId = null) => { createdAt: registracie.createdAt, kurzNazov: kurzy.nazov, kurzTyp: kurzy.typKurzu, + kurzDatumOd: kurzy.datumOd, + kurzDatumDo: kurzy.datumDo, ucastnikMeno: ucastnici.meno, ucastnikPriezvisko: ucastnici.priezvisko, ucastnikEmail: ucastnici.email, @@ -32,7 +31,7 @@ export const getAllRegistracie = async (kurzId = null) => { .leftJoin(kurzy, eq(registracie.kurzId, kurzy.id)) .leftJoin(ucastnici, eq(registracie.ucastnikId, ucastnici.id)) .where(conditions.length > 0 ? and(...conditions) : undefined) - .orderBy(desc(registracie.datumOd), desc(registracie.createdAt)); + .orderBy(desc(kurzy.datumOd), desc(registracie.createdAt)); return result; }; @@ -44,7 +43,6 @@ export const getRegistraciaById = async (id) => { kurzId: registracie.kurzId, ucastnikId: registracie.ucastnikId, formaKurzu: registracie.formaKurzu, - pocetUcastnikov: registracie.pocetUcastnikov, fakturaCislo: registracie.fakturaCislo, fakturaVystavena: registracie.fakturaVystavena, zaplatene: registracie.zaplatene, @@ -76,10 +74,7 @@ export const createRegistracia = async (data) => { .values({ kurzId: data.kurzId, ucastnikId: data.ucastnikId, - datumOd: data.datumOd ? new Date(data.datumOd) : null, - datumDo: data.datumDo ? new Date(data.datumDo) : null, formaKurzu: data.formaKurzu || 'prezencne', - pocetUcastnikov: data.pocetUcastnikov || 1, fakturaCislo: data.fakturaCislo || null, fakturaVystavena: data.fakturaVystavena || false, zaplatene: data.zaplatene || false, @@ -135,10 +130,9 @@ export const getCombinedTableData = async () => { kurzNazov: kurzy.nazov, kurzTyp: kurzy.typKurzu, kurzFarba: kurzy.farba, - datumOd: registracie.datumOd, - datumDo: registracie.datumDo, + datumOd: kurzy.datumOd, + datumDo: kurzy.datumDo, formaKurzu: registracie.formaKurzu, - pocetUcastnikov: registracie.pocetUcastnikov, fakturaCislo: registracie.fakturaCislo, fakturaVystavena: registracie.fakturaVystavena, zaplatene: registracie.zaplatene, @@ -151,15 +145,14 @@ export const getCombinedTableData = async () => { .from(registracie) .innerJoin(ucastnici, eq(registracie.ucastnikId, ucastnici.id)) .innerJoin(kurzy, eq(registracie.kurzId, kurzy.id)) - .orderBy(desc(registracie.datumOd), desc(registracie.createdAt)); + .orderBy(desc(kurzy.datumOd), desc(registracie.createdAt)); return result; }; export const updateField = async (registrationId, field, value) => { const ucastnikFields = ['titul', 'meno', 'priezvisko', 'email', 'telefon', 'firma', 'firmaIco', 'firmaDic', 'firmaIcDph', 'firmaSidlo', 'mesto', 'ulica', 'psc', 'needsFollowup']; - const registraciaFields = ['datumOd', 'datumDo', 'formaKurzu', 'pocetUcastnikov', 'fakturaCislo', 'fakturaVystavena', 'zaplatene', 'stav', 'poznamka', 'kurzId']; - const dateFields = ['datumOd', 'datumDo']; + const registraciaFields = ['formaKurzu', 'fakturaCislo', 'fakturaVystavena', 'zaplatene', 'stav', 'poznamka', 'kurzId']; const [reg] = await db .select({ ucastnikId: registracie.ucastnikId }) @@ -171,20 +164,15 @@ export const updateField = async (registrationId, field, value) => { throw new NotFoundError('Registrácia nenájdená'); } - let processedValue = value; - if (dateFields.includes(field)) { - processedValue = value ? new Date(value) : null; - } - if (ucastnikFields.includes(field)) { await db .update(ucastnici) - .set({ [field]: processedValue, updatedAt: new Date() }) + .set({ [field]: value, updatedAt: new Date() }) .where(eq(ucastnici.id, reg.ucastnikId)); } else if (registraciaFields.includes(field)) { await db .update(registracie) - .set({ [field]: processedValue, updatedAt: new Date() }) + .set({ [field]: value, updatedAt: new Date() }) .where(eq(registracie.id, registrationId)); } else { throw new Error(`Unknown field: ${field}`); diff --git a/src/validators/ai-kurzy.validators.js b/src/validators/ai-kurzy.validators.js index 209cd05..c98c396 100644 --- a/src/validators/ai-kurzy.validators.js +++ b/src/validators/ai-kurzy.validators.js @@ -20,6 +20,8 @@ export const createKurzSchema = z.object({ maxKapacita: z.number().int().positive().optional().nullable(), aktivny: z.boolean().optional(), farba: z.string().max(20).optional().nullable(), + datumOd: z.string().optional().nullable(), + datumDo: z.string().optional().nullable(), }); export const updateKurzSchema = createKurzSchema.partial(); @@ -45,10 +47,7 @@ export const updateUcastnikSchema = createUcastnikSchema.partial(); export const createRegistraciaSchema = z.object({ kurzId: z.number().int().positive(), ucastnikId: z.number().int().positive(), - datumOd: z.string().optional().nullable(), - datumDo: z.string().optional().nullable(), formaKurzu: z.enum(['prezencne', 'online', 'hybridne']).optional(), - pocetUcastnikov: z.number().int().positive().optional(), fakturaCislo: z.string().max(100).optional().nullable(), fakturaVystavena: z.boolean().optional(), zaplatene: z.boolean().optional(),