From 826fd467bc7e36cc95d0d0f72dc06c1cac28c5bf Mon Sep 17 00:00:00 2001 From: richardtekula Date: Wed, 21 Jan 2026 14:27:03 +0100 Subject: [PATCH] feat: Add farba field and company details to AI Kurzy module - Add farba (color) field to kurzy schema and Zod validation - Add company detail fields (firma_ico, firma_dic, firma_ic_dph, firma_sidlo) to ucastnici - Remove console logs from ai-kurzy service - Add SQL migration scripts for schema updates and data Co-Authored-By: Claude Opus 4.5 --- sql/02_ai_kurzy_company_details.sql | 19 ++++ sql/03_ai_kurzy_full_data.sql | 135 ++++++++++++++++++++++++++++ src/db/schema.js | 5 ++ src/routes/ai-kurzy.routes.js | 5 ++ src/services/ai-kurzy.service.js | 35 +++++++- 5 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 sql/02_ai_kurzy_company_details.sql create mode 100644 sql/03_ai_kurzy_full_data.sql diff --git a/sql/02_ai_kurzy_company_details.sql b/sql/02_ai_kurzy_company_details.sql new file mode 100644 index 0000000..58176b4 --- /dev/null +++ b/sql/02_ai_kurzy_company_details.sql @@ -0,0 +1,19 @@ +-- ============================================================ +-- AI KURZY - Company Details & Course Colors Migration +-- Run this FIRST to add new columns +-- ============================================================ + +-- Add color field to kurzy table +ALTER TABLE "kurzy" ADD COLUMN IF NOT EXISTS "farba" varchar(20); + +-- Add company details fields to ucastnici table +ALTER TABLE "ucastnici" ADD COLUMN IF NOT EXISTS "firma_ico" varchar(20); +ALTER TABLE "ucastnici" ADD COLUMN IF NOT EXISTS "firma_dic" varchar(20); +ALTER TABLE "ucastnici" ADD COLUMN IF NOT EXISTS "firma_ic_dph" varchar(25); +ALTER TABLE "ucastnici" ADD COLUMN IF NOT EXISTS "firma_sidlo" text; + +-- ============================================================ +-- DONE - Now run 03_ai_kurzy_full_data.sql for complete data +-- ============================================================ +SELECT 'AI Kurzy schema migration completed!' as status; +SELECT 'Now run 03_ai_kurzy_full_data.sql for complete data' as next_step; diff --git a/sql/03_ai_kurzy_full_data.sql b/sql/03_ai_kurzy_full_data.sql new file mode 100644 index 0000000..7567aba --- /dev/null +++ b/sql/03_ai_kurzy_full_data.sql @@ -0,0 +1,135 @@ +-- ============================================================ +-- AI KURZY - Complete Data Reset and Import +-- Run this to start fresh with proper data +-- ============================================================ + +-- First, clear existing data +DELETE FROM prilohy; +DELETE FROM registracie; +DELETE FROM ucastnici; +DELETE FROM kurzy; + +-- Reset sequences +ALTER SEQUENCE kurzy_id_seq RESTART WITH 1; +ALTER SEQUENCE ucastnici_id_seq RESTART WITH 1; +ALTER SEQUENCE registracie_id_seq RESTART WITH 1; + +-- ============================================================ +-- INSERT COURSES (without dates in names, with colors) +-- ============================================================ +INSERT INTO kurzy (nazov, typ_kurzu, cena, aktivny, farba) VALUES + ('AI 1 (1 deň)', 'AI', 150.00, true, 'blue'), + ('AI 2 (1 deň)', 'AI', 150.00, true, 'emerald'), + ('AI 1+2 (2 dni)', 'AI', 290.00, true, 'violet'), + ('AI v SEO', 'SEO', 150.00, true, 'amber'); + +-- ============================================================ +-- INSERT PARTICIPANTS with company details +-- ============================================================ + +-- 1. Martin Sovák - energium sro +INSERT INTO ucastnici (titul, meno, priezvisko, email, telefon, firma, firma_ico, firma_dic, firma_ic_dph, firma_sidlo, mesto, ulica, psc) +VALUES (NULL, 'Martin', 'Sovák', 'info@energium.sk', '0918986172', 'energium sro', '47613033', '2024004433', 'SK2024004433', 'Topolcianska 5, 85105 Bratislava', 'Bratislava', 'Topolcianska 5', '85105'); + +-- 2. Michal Farkaš - SLOVWELD +INSERT INTO ucastnici (titul, meno, priezvisko, email, telefon, firma, firma_ico, firma_dic, firma_ic_dph, firma_sidlo, mesto, ulica, psc) +VALUES (NULL, 'Michal', 'Farkaš', 'michal.farkas83@gmail.com', '0911209122', 'SLOVWELD', NULL, NULL, NULL, NULL, 'Dunajska Lužná', 'Mandlova 30', '90042'); + +-- 3. Alena Šranková - bez firmy +INSERT INTO ucastnici (titul, meno, priezvisko, email, telefon, firma, firma_ico, firma_dic, firma_ic_dph, firma_sidlo, mesto, ulica, psc) +VALUES (NULL, 'Alena', 'Šranková', 'alena.srankova@gmail.com', '0917352580', NULL, NULL, NULL, NULL, NULL, 'Bratislava', 'Šándorova 1', '82103'); + +-- 4. Katarina Tomaníková - Classica Shipping Limited +INSERT INTO ucastnici (titul, meno, priezvisko, email, telefon, firma, firma_ico, firma_dic, firma_ic_dph, firma_sidlo, mesto, ulica, psc) +VALUES (NULL, 'Katarina', 'Tomaníková', 'k.tomanikova@riseday.net', '0948070611', 'Classica Shipping Limited', NULL, NULL, NULL, NULL, 'Bratislava', 'Keltska 104', '85110'); + +-- 5. Róbert Brišák - Spojená škola +INSERT INTO ucastnici (titul, meno, priezvisko, email, telefon, firma, firma_ico, firma_dic, firma_ic_dph, firma_sidlo, mesto, ulica, psc) +VALUES (NULL, 'Róbert', 'Brišák', 'robert.brisak@ss-nizna.sk', '0910583883', 'Spojená škola Nižná', NULL, NULL, NULL, 'Hattalova 471, 02743 Nižná', 'Nižná', 'Hattalova 471', '02743'); + +-- 6. Marián Bača - bez firmy +INSERT INTO ucastnici (titul, meno, priezvisko, email, telefon, firma, firma_ico, firma_dic, firma_ic_dph, firma_sidlo, mesto, ulica, psc) +VALUES (NULL, 'Marián', 'Bača', 'baca.marian@gmail.com', '0907994126', NULL, NULL, NULL, NULL, NULL, 'Petrovany', '8', '08253'); + +-- 7. Nikola Horáčková - bez firmy +INSERT INTO ucastnici (titul, meno, priezvisko, email, telefon, firma, firma_ico, firma_dic, firma_ic_dph, firma_sidlo, mesto, ulica, psc) +VALUES ('Mgr. MBA', 'Nikola', 'Horáčková', 'nikolahorackova11@gmail.com', '0918482184', NULL, NULL, NULL, NULL, NULL, 'Zákopčie', 'Zákopčie stred 12', '02311'); + +-- 8. Tomáš Kupec - Jamajka s.r.o. +INSERT INTO ucastnici (titul, meno, priezvisko, email, telefon, firma, firma_ico, firma_dic, firma_ic_dph, firma_sidlo, mesto, ulica, psc) +VALUES (NULL, 'Tomáš', 'Kupec', 'kupec.tom@gmail.com', '0911030190', 'JAMAJKA, s.r.o.', '36411833', '2020128539', 'SK2020128539', 'Hotel Koliba Gréta 270 +032 23 Liptovská Sielnica', 'Liptovská Sielnica', NULL, '03223'); + +-- 9. Anton Považský - bez firmy (testovací) +INSERT INTO ucastnici (titul, meno, priezvisko, email, telefon, firma, firma_ico, firma_dic, firma_ic_dph, firma_sidlo, mesto, ulica, psc) +VALUES (NULL, 'Anton', 'Považský', 'anton.povazsky@example.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + +-- ============================================================ +-- INSERT REGISTRATIONS +-- ============================================================ + +-- Martin Sovák -> AI 1+2 (2 dni) - prezenčne, faktúra vystavená, nezaplatené +INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka) +SELECT k.id, u.id, '2026-02-02', '2026-02-03', 'prezencne', 1, true, false, 'registrovany', 'FA 2026020' +FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 1+2 (2 dni)' AND u.email = 'info@energium.sk'; + +-- Michal Farkaš -> AI 1 (1 deň) - online, zaplatené +INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka) +SELECT k.id, u.id, '2026-02-02', '2026-02-02', 'online', 1, true, true, 'registrovany', 'Fa 2025 338, Súhlasil so zmeneným termínom' +FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 1 (1 deň)' AND u.email = 'michal.farkas83@gmail.com'; + +-- Alena Šranková -> AI 1+2 (2 dni) - online, zaplatené +INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka) +SELECT k.id, u.id, '2026-02-02', '2026-02-03', 'online', 1, true, true, 'registrovany', NULL +FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 1+2 (2 dni)' AND u.email = 'alena.srankova@gmail.com'; + +-- Katarina Tomaníková -> AI 1+2 (2 dni) - prezenčne, zaplatené +INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka) +SELECT k.id, u.id, '2026-02-02', '2026-02-03', 'prezencne', 1, true, true, 'registrovany', 'Presunuta z októbra, chcela až január' +FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 1+2 (2 dni)' AND u.email = 'k.tomanikova@riseday.net'; + +-- Róbert Brišák -> AI 1+2 (2 dni) - prezenčne, nezaplatené +INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka) +SELECT k.id, u.id, '2026-02-02', '2026-02-03', 'prezencne', 1, true, false, 'registrovany', 'FA 2026019' +FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 1+2 (2 dni)' AND u.email = 'robert.brisak@ss-nizna.sk'; + +-- Marián Bača -> AI 2 (1 deň) - prezenčne, nezaplatené +INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka) +SELECT k.id, u.id, '2026-02-03', '2026-02-03', 'prezencne', 1, true, false, 'registrovany', 'Fa Gablasova' +FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 2 (1 deň)' AND u.email = 'baca.marian@gmail.com'; + +-- Nikola Horáčková -> AI 1+2 (2 dni) - potenciálny (vzdelávací poukaz) +INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka) +SELECT k.id, u.id, '2026-02-02', '2026-02-03', 'prezencne', 1, false, false, 'potencialny', 'Vzdelávací poukaz' +FROM kurzy k, ucastnici u WHERE k.nazov = 'AI 1+2 (2 dni)' AND u.email = 'nikolahorackova11@gmail.com'; + +-- Tomáš Kupec -> AI v SEO - prezenčne, nezaplatené +INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka) +SELECT k.id, u.id, '2026-02-13', '2026-02-13', 'prezencne', 1, true, false, 'registrovany', 'FA 2026021' +FROM kurzy k, ucastnici u WHERE k.nazov = 'AI v SEO' AND u.email = 'kupec.tom@gmail.com'; + +-- Anton Považský -> AI v SEO - prezenčne, nezaplatené +INSERT INTO registracie (kurz_id, ucastnik_id, datum_od, datum_do, forma_kurzu, pocet_ucastnikov, faktura_vystavena, zaplatene, stav, poznamka) +SELECT k.id, u.id, '2026-02-13', '2026-02-13', 'prezencne', 1, true, false, 'registrovany', NULL +FROM kurzy k, ucastnici u WHERE k.nazov = 'AI v SEO' AND u.email = 'anton.povazsky@example.com'; + +-- ============================================================ +-- VERIFICATION +-- ============================================================ +SELECT 'Kurzy:' as info, COUNT(*) as pocet FROM kurzy; +SELECT 'Účastníci:' as info, COUNT(*) as pocet FROM ucastnici; +SELECT 'Registrácie:' as info, COUNT(*) as pocet FROM registracie; + +-- Show sample data with company details +SELECT + u.meno || ' ' || u.priezvisko as ucastnik, + u.firma, + u.firma_ico as ico, + u.firma_dic as dic, + k.nazov as kurz, + k.farba, + r.zaplatene +FROM registracie r +JOIN ucastnici u ON r.ucastnik_id = u.id +JOIN kurzy k ON r.kurz_id = k.id +ORDER BY r.datum_od; diff --git a/src/db/schema.js b/src/db/schema.js index c1fc039..7df0cc0 100644 --- a/src/db/schema.js +++ b/src/db/schema.js @@ -435,6 +435,7 @@ export const kurzy = pgTable('kurzy', { cena: numeric('cena', { precision: 10, scale: 2 }).notNull(), maxKapacita: integer('max_kapacita'), aktivny: boolean('aktivny').default(true).notNull(), + farba: varchar('farba', { length: 20 }), // Color for visual distinction (e.g., 'primary', 'info', 'warning') createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), }); @@ -448,6 +449,10 @@ export const ucastnici = pgTable('ucastnici', { email: varchar('email', { length: 255 }).notNull().unique(), telefon: varchar('telefon', { length: 50 }), firma: varchar('firma', { length: 255 }), + firmaIco: varchar('firma_ico', { length: 20 }), // IČO spoločnosti + firmaDic: varchar('firma_dic', { length: 20 }), // DIČ spoločnosti + firmaIcDph: varchar('firma_ic_dph', { length: 25 }), // IČ DPH spoločnosti + firmaSidlo: text('firma_sidlo'), // Sídlo firmy (full address) mesto: varchar('mesto', { length: 100 }), ulica: varchar('ulica', { length: 255 }), psc: varchar('psc', { length: 10 }), diff --git a/src/routes/ai-kurzy.routes.js b/src/routes/ai-kurzy.routes.js index 2fb719a..dfa1da3 100644 --- a/src/routes/ai-kurzy.routes.js +++ b/src/routes/ai-kurzy.routes.js @@ -51,6 +51,7 @@ const createKurzSchema = z.object({ cena: z.string().or(z.number()), maxKapacita: z.number().int().positive().optional().nullable(), aktivny: z.boolean().optional(), + farba: z.string().max(20).optional().nullable(), }); const updateKurzSchema = createKurzSchema.partial(); @@ -62,6 +63,10 @@ const createUcastnikSchema = z.object({ email: z.string().email().max(255), telefon: z.string().max(50).optional().nullable(), firma: z.string().max(255).optional().nullable(), + firmaIco: z.string().max(20).optional().nullable(), + firmaDic: z.string().max(20).optional().nullable(), + firmaIcDph: z.string().max(25).optional().nullable(), + firmaSidlo: z.string().optional().nullable(), mesto: z.string().max(100).optional().nullable(), ulica: z.string().max(255).optional().nullable(), psc: z.string().max(10).optional().nullable(), diff --git a/src/services/ai-kurzy.service.js b/src/services/ai-kurzy.service.js index 57e92ae..0f95f4b 100644 --- a/src/services/ai-kurzy.service.js +++ b/src/services/ai-kurzy.service.js @@ -15,6 +15,7 @@ export const getAllKurzy = async () => { cena: kurzy.cena, maxKapacita: kurzy.maxKapacita, aktivny: kurzy.aktivny, + farba: kurzy.farba, createdAt: kurzy.createdAt, registraciiCount: sql`(SELECT COUNT(*) FROM registracie WHERE kurz_id = ${kurzy.id})::int`, }) @@ -48,6 +49,7 @@ export const createKurz = async (data) => { cena: data.cena, maxKapacita: data.maxKapacita || null, aktivny: data.aktivny !== undefined ? data.aktivny : true, + farba: data.farba || null, }) .returning(); @@ -57,7 +59,23 @@ export const createKurz = async (data) => { export const updateKurz = async (id, data) => { await getKurzById(id); - const updateData = { ...data, updatedAt: new Date() }; + const updateData = { + nazov: data.nazov, + typKurzu: data.typKurzu, + popis: data.popis !== undefined ? data.popis : undefined, + cena: data.cena, + maxKapacita: data.maxKapacita !== undefined ? data.maxKapacita : undefined, + aktivny: data.aktivny !== undefined ? data.aktivny : undefined, + farba: data.farba !== undefined ? data.farba : undefined, + updatedAt: new Date(), + }; + + // Remove undefined values + Object.keys(updateData).forEach(key => { + if (updateData[key] === undefined) { + delete updateData[key]; + } + }); const [updated] = await db .update(kurzy) @@ -86,6 +104,10 @@ export const getAllUcastnici = async () => { email: ucastnici.email, telefon: ucastnici.telefon, firma: ucastnici.firma, + firmaIco: ucastnici.firmaIco, + firmaDic: ucastnici.firmaDic, + firmaIcDph: ucastnici.firmaIcDph, + firmaSidlo: ucastnici.firmaSidlo, mesto: ucastnici.mesto, ulica: ucastnici.ulica, psc: ucastnici.psc, @@ -122,6 +144,10 @@ export const createUcastnik = async (data) => { email: data.email, telefon: data.telefon || null, firma: data.firma || null, + firmaIco: data.firmaIco || null, + firmaDic: data.firmaDic || null, + firmaIcDph: data.firmaIcDph || null, + firmaSidlo: data.firmaSidlo || null, mesto: data.mesto || null, ulica: data.ulica || null, psc: data.psc || null, @@ -280,6 +306,10 @@ export const getCombinedTableData = async () => { email: ucastnici.email, telefon: ucastnici.telefon, firma: ucastnici.firma, + firmaIco: ucastnici.firmaIco, + firmaDic: ucastnici.firmaDic, + firmaIcDph: ucastnici.firmaIcDph, + firmaSidlo: ucastnici.firmaSidlo, mesto: ucastnici.mesto, ulica: ucastnici.ulica, psc: ucastnici.psc, @@ -287,6 +317,7 @@ export const getCombinedTableData = async () => { kurzId: kurzy.id, kurzNazov: kurzy.nazov, kurzTyp: kurzy.typKurzu, + kurzFarba: kurzy.farba, // Registration fields (dates are now here) datumOd: registracie.datumOd, datumDo: registracie.datumDo, @@ -312,7 +343,7 @@ export const getCombinedTableData = async () => { // Update a single field (for inline editing) export const updateField = async (registrationId, field, value) => { // Determine which table to update based on the field - const ucastnikFields = ['titul', 'meno', 'priezvisko', 'email', 'telefon', 'firma', 'mesto', 'ulica', 'psc']; + const ucastnikFields = ['titul', 'meno', 'priezvisko', 'email', 'telefon', 'firma', 'firmaIco', 'firmaDic', 'firmaIcDph', 'firmaSidlo', 'mesto', 'ulica', 'psc']; const registraciaFields = ['datumOd', 'datumDo', 'formaKurzu', 'pocetUcastnikov', 'fakturaCislo', 'fakturaVystavena', 'zaplatene', 'stav', 'poznamka', 'kurzId']; const dateFields = ['datumOd', 'datumDo'];