From b714a460f0d029eaacaf6682b496e653f931a91c Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Thu, 27 Nov 2025 12:24:47 -0500 Subject: [PATCH] feat(migration): reset DB to match new schema --- .../migration.sql | 46 +++ prisma/schema.prisma | 3 +- scripts/data/export_employee_table.csv | 63 +++ scripts/data/export_new_employee_table.csv | 63 +++ scripts/done/import-employees-from-csv.ts | 270 ------------- scripts/done/init-preferences.ts | 67 ---- scripts/import-employees-from-csv.ts | 270 +++++++++++++ scripts/{done => }/import-users-from-csv.ts | 2 +- scripts/init-preferences.ts | 67 ++++ scripts/migrate-expenses.ts | 286 ++++++------- scripts/migrate-shifts.ts | 376 +++++++++--------- scripts/migrate-timesheets.ts | 208 +++++----- scripts/migration.service.ts | 34 +- .../preferences/dtos/preferences.dto.ts | 4 +- .../dtos/module-acces.dto.ts | 2 +- .../services/module-access-get.service.ts | 8 +- .../services/module-access-update.service.ts | 10 +- 17 files changed, 977 insertions(+), 802 deletions(-) create mode 100644 prisma/migrations/20251127150919_small_preference_fix/migration.sql create mode 100644 scripts/data/export_employee_table.csv create mode 100644 scripts/data/export_new_employee_table.csv delete mode 100644 scripts/done/import-employees-from-csv.ts delete mode 100644 scripts/done/init-preferences.ts create mode 100644 scripts/import-employees-from-csv.ts rename scripts/{done => }/import-users-from-csv.ts (97%) create mode 100644 scripts/init-preferences.ts diff --git a/prisma/migrations/20251127150919_small_preference_fix/migration.sql b/prisma/migrations/20251127150919_small_preference_fix/migration.sql new file mode 100644 index 0000000..245d43e --- /dev/null +++ b/prisma/migrations/20251127150919_small_preference_fix/migration.sql @@ -0,0 +1,46 @@ +/* + Warnings: + + - You are about to drop the column `dark_mode` on the `preferences` table. All the data in the column will be lost. + - You are about to drop the column `employee_list_display` on the `preferences` table. All the data in the column will be lost. + - You are about to drop the column `lang_switch` on the `preferences` table. All the data in the column will be lost. + - You are about to drop the column `lefty_mode` on the `preferences` table. All the data in the column will be lost. + - You are about to drop the column `timesheet_display` on the `preferences` table. All the data in the column will be lost. + - You are about to drop the column `validation_display` on the `preferences` table. All the data in the column will be lost. + - The `notifications` column on the `preferences` table would be dropped and recreated. This will lead to data loss if there is data in the column. + +*/ +-- AlterTable +ALTER TABLE "preferences" DROP COLUMN "dark_mode", +DROP COLUMN "employee_list_display", +DROP COLUMN "lang_switch", +DROP COLUMN "lefty_mode", +DROP COLUMN "timesheet_display", +DROP COLUMN "validation_display", +ADD COLUMN "display_language" TEXT NOT NULL DEFAULT 'fr-FR', +ADD COLUMN "is_dark_mode" BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN "is_employee_list_grid" BOOLEAN NOT NULL DEFAULT true, +ADD COLUMN "is_lefty_mode" BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN "is_timesheet_approval_grid" BOOLEAN NOT NULL DEFAULT true, +DROP COLUMN "notifications", +ADD COLUMN "notifications" BOOLEAN NOT NULL DEFAULT true; + +-- CreateTable +CREATE TABLE "user_module_access" ( + "id" SERIAL NOT NULL, + "user_id" UUID NOT NULL, + "timesheets" BOOLEAN NOT NULL DEFAULT false, + "timesheets_approval" BOOLEAN NOT NULL DEFAULT false, + "employee_list" BOOLEAN NOT NULL DEFAULT false, + "employee_management" BOOLEAN NOT NULL DEFAULT false, + "personal_profile" BOOLEAN NOT NULL DEFAULT false, + "dashboard" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "user_module_access_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "user_module_access_user_id_key" ON "user_module_access"("user_id"); + +-- AddForeignKey +ALTER TABLE "user_module_access" ADD CONSTRAINT "user_module_access_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1abb05f..db75d2b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -249,6 +249,7 @@ model Expenses { archive ExpensesArchive[] @relation("ExpensesToArchive") + @@unique([timesheet_id, date, amount, mileage], name: "unique_ts_id_date_amount_mileage") @@map("expenses") } @@ -346,7 +347,7 @@ model Preferences { user Users @relation("UserPreferences", fields: [user_id], references: [id]) user_id String @unique @db.Uuid - notifications Int @default(0) + notifications Boolean @default(true) is_dark_mode Boolean @default(false) display_language String @default("fr-FR") //'fr-FR' | 'en-CA'; is_lefty_mode Boolean @default(false) diff --git a/scripts/data/export_employee_table.csv b/scripts/data/export_employee_table.csv new file mode 100644 index 0000000..26a8468 --- /dev/null +++ b/scripts/data/export_employee_table.csv @@ -0,0 +1,63 @@ +"email","first_name","last_name","phone_number" +"mickaelp@targointernet.com","Mickael","Poulin","(514) 825 - 9730" +"simon@targointernet.com","Simon","Clot-Gagnon","(514) 894 - 3632" +"eric@targointernet.com","Eric","Wilson","(438) 229 - 4086" +"jonathanlussier1@gmail.com","Jonathan","Soulières","(514) 461 - 3716" +"Carmen@targointernet.com","Carmen","Ouellet","(514) 448 - 0773" +"patrickm@targointernet.com","Patrick","Moise","(514) 949 - 3082" +"malika@targointernet.com","Malika","Charest","(819) 609 - 3894" +"marcandre@targointernet.com","Marc-Andre","Henrico","(514) 448 - 0773" +"maxim@targointernet.com","Maxim","Murray Gendron","(514) 705 - 8208" +"philips@targointernet.com","Philip","St-Amour","(450) 601 - 8112" +"jeanpierre@targointernet.com","Jean-Pierre","Bourdon","(514) 863 - 7414" +"jayson@targointernet.com","Jayson","Blais-Vallières","(438) 390 - 3679" +"jonathans@targointernet.com","Jonathan","Soulard","(514) 714 - 2647" +"louism@targointernet.com","Louis","Morneau","(514) 715 - 1182" +"joseeannes@targointernet.com","Josée-Anne","Soulard","(438) 410 - 1779" +"louisgs@targointernet.com","Louis Gabriel","Soulard","(438) 528 - 4486" +"david@targointernet.com","David","Richer","(514) 448 - 0773" +"laurence@targointernet.com","Laurence","Gobeil","(438) 884 - 7125" +"antoinewg@targointernet.com","Antoine","Wilson-Guay","(438) 341 - 9266" +"michel@targointernet.com","Michel","Blais","(514) 448 - 0773" +"gilles@targointernet.com","Gilles","Drolet","(514) 296 - 2773" +"sofiane@targointernet.com","Sofiane","Ameziane","(514) 224 - 3329" +"jessy@targointernet.com","Jessy","Sharock","(514) 922 - 7880" +"anthonyd@targointernet.com","Anthony","Dion","(438) 872 - 2432" +"dahlia@targointernet.com","Dahlia","Tremblay","(438) 867 - 9115" +"mathys.renaud00@gmail.com","Mathys","Renaud","(514) 894 - 3632" +"pierre@targointernet.com","Pierre","Brodeur","(514) 608 - 4758" +"Louis@targointernet.com","Louis-Paul","Bourdon","(514) 448 - 0773" +"paulr@targointernet.com","Paul Rommel","Douanla Zebaze","(450) 801 - 2013" +"benjamin@targointernet.com","Benjamin","Djanpou","(514) 663 - 9890" +"nathanb@targointernet.com","Nathan","Boucher","(514) 929 - 3145" +"sylvain@targointernet.com","Sylvain","Delaunais","(438) 408 - 0197" +"simong@targointernet.com","Simon","Goyette","(873) 662 - 4408" +"patrick@targointernet.com","Patrick","Doucet","(514) 919 - 7102" +"robinson@targointernet.com","Robinson","Viaud","(514) 649 - 3063" +"frederique@targointernet.com","Frédérique","Soulard","(438) 408 - 6998" +"genevieveb@targointernet.com","Geneviève","Bourdon","(514) 607 - 7084" +"philippe@targointernet.com","Philippe","Bourdon","(438) 529 - 1940" +"aurelieb@targointernet.com","Aurelie","Bourdon","(438) 357 - 5392" +"tommy@targointernet.com","Tommy","Larouche Dionne","(514) 949 - 0762" +"jeremyb@targointernet.com","Jérémy","Blais","(367) 348 - 2276" +"stephane@targointernet.com","Stéphane","Moïse","(514) 949 - 0587" +"thaiz@targointernet.com","Thaiz ","Menezes Costa","(514) 915 - 0959" +"frederick@targointernet.com","Frederick","Pruneau","(514) 949 - 0340" +"stephaneb@targointernet.com","Stéphane","Beaudoin","(438) 524 - 2556" +"kadi@targointernet.com","Kadi","Nongtodbo","(514) 621 - 2762" +"chantal.blanchette6@gmail.com","Chantal","Blanchette","(514) 889 - 8004" +"dominiquel@targointernet.com","Dominique","Liaud","(514) 949 - 0230" +"brandonf@targointernet.com","Brandon","Fortin","(438) 828 - 4732" +"nicolasd@targointernet.com","Nicolas","Drolet","(514) 951 - 4750" +"matthieuh@targointernet.com","Matthieu","Haineault Gervais","(514) 708 - 2974" +"mathieug@targointernet.com","Mathieu","Gagné","(514) 448 - 0773" +"marcantoineg@targointernet.com","Marc-Antoine","Girard","(514) 641 - 7676" +"djanpoub@targointernet.com","Benjamin (a suprimmer)","Djanpou","(514) 663 - 9890" +"samuel@targointernet.com","Samuel","Rolo","(514) 578 - 0324" +"mathieu@targointernet.com","Mathieu","Lussier","(438) 889 - 4324" +"alexandreg@targointernet.com","Alexandre","Germain","(438) 502 - 1801" +"fanny@targointernet.com","Fanny","Laroche","(514) 771 - 3341" +"samuelr@targointernet.com","Samuel","Rolo","(514) 578 - 0324" +"joseph@targointernet.com","Joseph","Muzowindanga","(514)604-3734" +"nathanm@targointernet.com","Nathan","Morrisseau","(514) 452 - 0543" +"lion@targointernet.com","Lion","Arar","(438) 877 - 9783" diff --git a/scripts/data/export_new_employee_table.csv b/scripts/data/export_new_employee_table.csv new file mode 100644 index 0000000..6f8178c --- /dev/null +++ b/scripts/data/export_new_employee_table.csv @@ -0,0 +1,63 @@ +"employee_number","email","job_title","company","is_supervisor","onboarding","offboarding" +"49","mickaelp@targointernet.com","Fusionneur",271585,False,"1656907200000","1724990400000" +"54","simon@targointernet.com","Fusionneur",271585,True,"1566446400000",NULL +"39","eric@targointernet.com","Fusionneur",271585,False,"1531886400000","1724990400000" +"43","jonathanlussier1@gmail.com","Mécanicien",271585,False,"1679284800000","1728014400000" +"29","Carmen@targointernet.com","Responsable aux comptes payables",271583,False,"1181880000000",NULL +"25","patrickm@targointernet.com","Technicien",271583,True,"1249358400000",NULL +"10","malika@targointernet.com","Compte Recevable",271583,False,"1664856000000","1723176000000" +"18","marcandre@targointernet.com","Support technique -senior",271583,True,"1497326400000",NULL +"28","maxim@targointernet.com","Administrateur réseau",271583,False,"1659931200000",NULL +"51","philips@targointernet.com","Fusionneur",271585,False,"1656907200000",NULL +"58","jeanpierre@targointernet.com","Technicien",271585,False,"1548216000000",NULL +"4","jayson@targointernet.com","Monteur",271583,False,"1564977600000","1720756800000" +"44","jonathans@targointernet.com","Responsable des ressources matérielles",271585,False,"1664164800000","1717473600000" +"27","louism@targointernet.com","Sysadmin",271583,False,"1392091200000",NULL +"45","joseeannes@targointernet.com","Technicien",271585,False,"1665979200000",NULL +"46","louisgs@targointernet.com","Technicien",271585,False,"1661745600000","1723176000000" +"32","david@targointernet.com","Développeur",271583,False,"1262664000000",NULL +"15","laurence@targointernet.com","Responsable du service à la clientèle",271583,True,"1635739200000","1725595200000" +"37","antoinewg@targointernet.com","Concepteur",271585,False,"1643688000000",NULL +"5","michel@targointernet.com","netadmin / sysadmin",271583,True,"1203393600000",NULL +"42","gilles@targointernet.com","Fusionneur",271585,False,"1357531200000",NULL +"2","sofiane@targointernet.com","developpeur web",271583,False,"1696219200000","1728014400000" +"33","jessy@targointernet.com","Administrateur réseau",271583,False,"1679889600000","1755230400000" +"60","anthonyd@targointernet.com","Fusionneur",271585,False,"1713844800000",NULL +"35","dahlia@targointernet.com","Agent service à la clientèle",271583,False,"1684900800000","1734667200000" +"31","mathys.renaud00@gmail.com","Manoeuvre",271583,False,"1688529600000","1724472000000" +"53","pierre@targointernet.com","gestionnaire de projet",271585,True,"1557374400000",NULL +"7","Louis@targointernet.com","Président",271583,False,"1104638400000",NULL +"49","paulr@targointernet.com","Netadmin",271583,False,"1747022400000","1747800000000" +"50","benjamin@targointernet.com","Technicien",271583,False,"1747022400000",NULL +"50","nathanb@targointernet.com","Technicien",271585,False,"1653278400000",NULL +"55","sylvain@targointernet.com","Fusionneur",271585,False,"1661140800000","1724385600000" +"59","simong@targointernet.com","Mécanicien",271585,False,"1712203200000",NULL +"13","patrick@targointernet.com","Support Technique",271583,False,"1527652800000",NULL +"36","robinson@targointernet.com","Service Clientèle",271583,False,"1694059200000",NULL +"41","frederique@targointernet.com","commis à la facturation",271583,False,"1724644800000",NULL +"42","genevieveb@targointernet.com","commis à la facturation",271583,False,"1726459200000",NULL +"52","philippe@targointernet.com","Fusionneur",271585,False,"1637640000000",NULL +"6","aurelieb@targointernet.com","Technicien telecom",271583,False,"1652068800000",NULL +"20","tommy@targointernet.com","Technicien",271583,False,"1462766400000",NULL +"39","jeremyb@targointernet.com","Tech/Installateur ",271583,False,"1721016000000","1724990400000" +"26","stephane@targointernet.com","Technicien télécom",271583,False,"1260936000000",NULL +"24","thaiz@targointernet.com","Agente organisationnel",271583,False,"1539316800000",NULL +"30","frederick@targointernet.com","Net/Sys/VoIP admin",271583,False,"1295323200000",NULL +"37","stephaneb@targointernet.com","Magasinier",271583,False,"1716955200000",NULL +"43","kadi@targointernet.com","RH et vente",271583,True,"1726459200000",NULL +"46","chantal.blanchette6@gmail.com","Agente au bien-être et service interne",271583,False,"1732507200000",NULL +"48","dominiquel@targointernet.com","Vendeur",271583,False,"1746417600000",NULL +"45","brandonf@targointernet.com","Technicien",271583,False,"1731556800000","1744948800000" +"52","nicolasd@targointernet.com","Programmeur",271583,False,"1748923200000",NULL +"51","matthieuh@targointernet.com","Programmeur",271583,False,"1748836800000",NULL +"44","mathieug@targointernet.com","Support technique",271583,False,"1728446400000","1734494400000" +"47","marcantoineg@targointernet.com","Vendeur",271583,False,"1737950400000","1743048000000" +"50","djanpoub@targointernet.com","Technicien",271583,False,"1747713600000","1747800000000" +"53","samuel@targointernet.com","Technicien",271583,False,"1749614400000","1749614400000" +"38","mathieu@targointernet.com","Dévelopeur",271583,False,"1719288000000","1750305600000" +"54","alexandreg@targointernet.com","support",271583,False,"1750651200000","1757649600000" +"19","fanny@targointernet.com","Comptable",271583,False,"1751860800000",NULL +"53","samuelr@targointernet.com","Technicien",271583,False,"1749614400000","1749614400000" +"56","joseph@targointernet.com","Resp. service clients et opérations commerciales",271583,True,"1754395200000",NULL +"55","nathanm@targointernet.com","Technicien",271583,False,"1751860800000",NULL +"58","lion@targointernet.com","Programmeur",271583,False,"1758513600000",NULL diff --git a/scripts/done/import-employees-from-csv.ts b/scripts/done/import-employees-from-csv.ts deleted file mode 100644 index 3b4c638..0000000 --- a/scripts/done/import-employees-from-csv.ts +++ /dev/null @@ -1,270 +0,0 @@ -// // src/scripts/import-employees-from-csv.ts -// import { PrismaClient, Roles } from '@prisma/client'; -// import * as fs from 'fs'; -// import * as path from 'path'; - -// const prisma = new PrismaClient(); - -// // ⚙️ Chemin vers ton CSV employees -// const CSV_PATH = path.resolve(__dirname, '../../data/export_new_employee_table.csv'); - -// // Rôles éligibles pour la table Employees -// const ELIGIBLE_ROLES: Roles[] = [ -// Roles.EMPLOYEE, -// Roles.SUPERVISOR, -// Roles.HR, -// Roles.ACCOUNTING, -// Roles.ADMIN, -// ]; - -// // Type correspondant EXACT aux colonnes de ton CSV -// type EmployeeCsvRow = { -// employee_number: string; -// email: string; -// job_title: string; -// company: string; // sera converti en number -// is_supervisor: string; // "True"/"False" (ou variantes) -// onboarding: string; // millis -// offboarding: string; // millis ou "NULL" -// }; - -// // Représentation minimale d'un user -// type UserSummary = { -// id: string; // UUID -// email: string; -// role: Roles; -// }; - -// // ============ Helpers CSV ============ - -// function splitCsvLine(line: string): string[] { -// const result: string[] = []; -// let current = ''; -// let inQuotes = false; - -// for (let i = 0; i < line.length; i++) { -// const char = line[i]; - -// if (char === '"') { -// // guillemet échappé "" -// if (inQuotes && line[i + 1] === '"') { -// current += '"'; -// i++; -// } else { -// inQuotes = !inQuotes; -// } -// } else if (char === ',' && !inQuotes) { -// result.push(current); -// current = ''; -// } else { -// current += char; -// } -// } - -// result.push(current); -// return result.map((v) => v.trim()); -// } - -// // ============ Helpers de parsing ============ - -// function parseBoolean(value: string): boolean { -// const v = value.trim().toLowerCase(); -// return v === 'true' || v === '1' || v === 'yes' || v === 'y' || v === 'oui'; -// } - -// function parseIntSafe(value: string, fieldName: string): number | null { -// const trimmed = value.trim(); -// if (!trimmed || trimmed.toUpperCase() === 'NULL') return null; - -// const n = Number.parseInt(trimmed, 10); -// if (Number.isNaN(n)) { -// console.warn(`⚠️ Impossible de parser "${value}" en entier pour le champ ${fieldName}`); -// return null; -// } -// return n; -// } - -// function millisToDate(value: string): Date | null { -// const trimmed = value.trim().toUpperCase(); -// if (!trimmed || trimmed === 'NULL') return null; - -// const ms = Number(trimmed); -// if (!Number.isFinite(ms)) { -// console.warn(`⚠️ Impossible de parser "${value}" en millis pour une Date`); -// return null; -// } - -// const d = new Date(ms); -// // On normalise au jour (minuit UTC) -// const normalized = new Date(Date.UTC( -// d.getUTCFullYear(), -// d.getUTCMonth(), -// d.getUTCDate(), -// )); - -// return normalized; -// } - -// // ============ MAIN ============ - -// async function main() { -// // 1. Lecture du CSV -// const fileContent = fs.readFileSync(CSV_PATH, 'utf-8'); - -// const lines = fileContent -// .split(/\r?\n/) -// .map((l) => l.trim()) -// .filter((l) => l.length > 0); - -// if (lines.length <= 1) { -// console.error('CSV vide ou seulement un header'); -// return; -// } - -// const header = splitCsvLine(lines[0]); // ["employee_number","email",...] -// const dataLines = lines.slice(1); - -// const csvRows: EmployeeCsvRow[] = dataLines.map((line) => { -// const values = splitCsvLine(line); -// const row: any = {}; - -// header.forEach((col, idx) => { -// row[col] = values[idx] ?? ''; -// }); - -// return row as EmployeeCsvRow; -// }); - -// console.log(`➡️ ${csvRows.length} lignes trouvées dans le CSV employees`); - -// // 2. Récupérer tous les emails du CSV -// const emails = Array.from( -// new Set( -// csvRows -// .map((r) => r.email.trim()) -// .filter((e) => e.length > 0), -// ), -// ); - -// console.log(`➡️ ${emails.length} emails uniques trouvés dans le CSV`); - -// // 3. Charger les users correspondants avec les bons rôles -// const users = (await prisma.users.findMany({ -// where: { -// email: { in: emails }, -// role: { in: ELIGIBLE_ROLES }, -// }, -// select: { -// id: true, -// email: true, -// role: true, -// }, -// })) as UserSummary[]; - -// console.log(`➡️ ${users.length} users éligibles trouvés dans la DB`); - -// // Map email → user -// const userByEmail = new Map(); -// for (const user of users) { -// const key = user.email.trim().toLowerCase(); -// userByEmail.set(key, user); -// } - -// // 4. Construire les données pour employees.createMany -// const employeesToCreate: { -// user_id: string; -// external_payroll_id: number; -// company_code: number; -// first_work_day: Date; -// last_work_day: Date | null; -// job_title: string | null; -// is_supervisor: boolean; -// supervisor_id?: number | null; -// }[] = []; - -// const rowsWithoutUser: EmployeeCsvRow[] = []; -// const rowsWithInvalidNumbers: EmployeeCsvRow[] = []; - -// for (const row of csvRows) { -// const emailKey = row.email.trim().toLowerCase(); -// const user = userByEmail.get(emailKey); - -// if (!user) { -// rowsWithoutUser.push(row); -// continue; -// } - -// const external_payroll_id = parseIntSafe(row.employee_number, 'external_payroll_id'); -// const company_code = parseIntSafe(String(row.company), 'company_code'); - -// if (external_payroll_id === null || company_code === null) { -// rowsWithInvalidNumbers.push(row); -// continue; -// } - -// const first_work_day = millisToDate(row.onboarding); -// const last_work_day = millisToDate(row.offboarding); -// const is_supervisor = parseBoolean(row.is_supervisor); -// const job_title = row.job_title?.trim() || null; - -// if (!first_work_day) { -// console.warn( -// `⚠️ Date d'onboarding invalide pour ${row.email} (employee_number=${row.employee_number})`, -// ); -// continue; -// } - -// employeesToCreate.push({ -// user_id: user.id, -// external_payroll_id, -// company_code, -// first_work_day, -// last_work_day, -// job_title, -// is_supervisor, -// supervisor_id: null, // on pourra gérer ça plus tard si tu as les infos -// }); -// } - -// console.log(`➡️ ${employeesToCreate.length} entrées Employees prêtes à être insérées`); - -// if (rowsWithoutUser.length > 0) { -// console.warn(`⚠️ ${rowsWithoutUser.length} lignes CSV sans user correspondant (email / rôle) :`); -// for (const row of rowsWithoutUser) { -// console.warn( -// ` - email=${row.email}, employee_number=${row.employee_number}, company=${row.company}`, -// ); -// } -// } - -// if (rowsWithInvalidNumbers.length > 0) { -// console.warn(`⚠️ ${rowsWithInvalidNumbers.length} lignes CSV avec ids/compagnies invalides :`); -// for (const row of rowsWithInvalidNumbers) { -// console.warn( -// ` - email=${row.email}, employee_number="${row.employee_number}", company="${row.company}"`, -// ); -// } -// } - -// if (employeesToCreate.length === 0) { -// console.warn('⚠️ Aucun Employees à créer, arrêt.'); -// return; -// } - -// // 5. Insert en batch -// const result = await prisma.employees.createMany({ -// data: employeesToCreate, -// skipDuplicates: true, // évite les erreurs si tu relances le script -// }); - -// console.log(`✅ ${result.count} employees insérés dans la DB`); -// } - -// main() -// .catch((err) => { -// console.error('❌ Erreur pendant l’import CSV → Employees', err); -// process.exit(1); -// }) -// .finally(async () => { -// await prisma.$disconnect(); -// }); diff --git a/scripts/done/init-preferences.ts b/scripts/done/init-preferences.ts deleted file mode 100644 index d2c5421..0000000 --- a/scripts/done/init-preferences.ts +++ /dev/null @@ -1,67 +0,0 @@ -// // src/scripts/init-preferences.ts -// import { PrismaClient } from '@prisma/client'; - -// const prisma = new PrismaClient(); - -// type UserSummary = { -// id: string; // UUID -// email: string; -// }; - -// async function main() { -// console.log('➡️ Initialisation des préférences utilisateurs…'); - -// // 1. Récupérer tous les users -// const users = (await prisma.users.findMany({ -// select: { -// id: true, -// email: true, -// }, -// })) as UserSummary[]; - -// console.log(`➡️ ${users.length} users trouvés dans la DB`); - -// // 2. Récupérer toutes les préférences existantes -// const existingPrefs = await prisma.preferences.findMany({ -// select: { -// user_id: true, -// }, -// }); - -// const userIdsWithPrefs = new Set(existingPrefs.map((p) => p.user_id)); - -// console.log(`➡️ ${existingPrefs.length} users ont déjà des préférences`); - -// // 3. Filtrer les users qui n'ont pas encore de preferences -// const usersWithoutPrefs = users.filter((u) => !userIdsWithPrefs.has(u.id)); - -// console.log(`➡️ ${usersWithoutPrefs.length} users n'ont pas encore de préférences`); - -// if (usersWithoutPrefs.length === 0) { -// console.log('✅ Rien à faire, toutes les préférences sont déjà créées.'); -// return; -// } - -// // 4. Préparer les entrées pour createMany -// const prefsToCreate = usersWithoutPrefs.map((u) => ({ -// user_id: u.id, -// // tous les autres champs prendront leurs valeurs par défaut (0) -// })); - -// // 5. Insertion en batch -// const result = await prisma.preferences.createMany({ -// data: prefsToCreate, -// skipDuplicates: true, // sécurité si jamais le script est relancé -// }); - -// console.log(`✅ ${result.count} préférences créées dans la DB`); -// } - -// main() -// .catch((err) => { -// console.error('❌ Erreur pendant l’initialisation des préférences', err); -// process.exit(1); -// }) -// .finally(async () => { -// await prisma.$disconnect(); -// }); diff --git a/scripts/import-employees-from-csv.ts b/scripts/import-employees-from-csv.ts new file mode 100644 index 0000000..c24e313 --- /dev/null +++ b/scripts/import-employees-from-csv.ts @@ -0,0 +1,270 @@ +// src/scripts/import-employees-from-csv.ts +import { PrismaClient, Roles } from '@prisma/client'; +import * as fs from 'fs'; +import * as path from 'path'; + +const prisma = new PrismaClient(); + +// ⚙️ Chemin vers ton CSV employees +const CSV_PATH = path.resolve(__dirname, 'data/export_new_employee_table.csv'); + +// Rôles éligibles pour la table Employees +const ELIGIBLE_ROLES: Roles[] = [ + Roles.EMPLOYEE, + Roles.SUPERVISOR, + Roles.HR, + Roles.ACCOUNTING, + Roles.ADMIN, +]; + +// Type correspondant EXACT aux colonnes de ton CSV +type EmployeeCsvRow = { + employee_number: string; + email: string; + job_title: string; + company: string; // sera converti en number + is_supervisor: string; // "True"/"False" (ou variantes) + onboarding: string; // millis + offboarding: string; // millis ou "NULL" +}; + +// Représentation minimale d'un user +type UserSummary = { + id: string; // UUID + email: string; + role: Roles; +}; + +// ============ Helpers CSV ============ + +function splitCsvLine(line: string): string[] { + const result: string[] = []; + let current = ''; + let inQuotes = false; + + for (let i = 0; i < line.length; i++) { + const char = line[i]; + + if (char === '"') { + // guillemet échappé "" + if (inQuotes && line[i + 1] === '"') { + current += '"'; + i++; + } else { + inQuotes = !inQuotes; + } + } else if (char === ',' && !inQuotes) { + result.push(current); + current = ''; + } else { + current += char; + } + } + + result.push(current); + return result.map((v) => v.trim()); +} + +// ============ Helpers de parsing ============ + +function parseBoolean(value: string): boolean { + const v = value.trim().toLowerCase(); + return v === 'true' || v === '1' || v === 'yes' || v === 'y' || v === 'oui'; +} + +function parseIntSafe(value: string, fieldName: string): number | null { + const trimmed = value.trim(); + if (!trimmed || trimmed.toUpperCase() === 'NULL') return null; + + const n = Number.parseInt(trimmed, 10); + if (Number.isNaN(n)) { + console.warn(`⚠️ Impossible de parser "${value}" en entier pour le champ ${fieldName}`); + return null; + } + return n; +} + +function millisToDate(value: string): Date | null { + const trimmed = value.trim().toUpperCase(); + if (!trimmed || trimmed === 'NULL') return null; + + const ms = Number(trimmed); + if (!Number.isFinite(ms)) { + console.warn(`⚠️ Impossible de parser "${value}" en millis pour une Date`); + return null; + } + + const d = new Date(ms); + // On normalise au jour (minuit UTC) + const normalized = new Date(Date.UTC( + d.getUTCFullYear(), + d.getUTCMonth(), + d.getUTCDate(), + )); + + return normalized; +} + +// ============ MAIN ============ + +async function main() { + // 1. Lecture du CSV + const fileContent = fs.readFileSync(CSV_PATH, 'utf-8'); + + const lines = fileContent + .split(/\r?\n/) + .map((l) => l.trim()) + .filter((l) => l.length > 0); + + if (lines.length <= 1) { + console.error('CSV vide ou seulement un header'); + return; + } + + const header = splitCsvLine(lines[0]); // ["employee_number","email",...] + const dataLines = lines.slice(1); + + const csvRows: EmployeeCsvRow[] = dataLines.map((line) => { + const values = splitCsvLine(line); + const row: any = {}; + + header.forEach((col, idx) => { + row[col] = values[idx] ?? ''; + }); + + return row as EmployeeCsvRow; + }); + + console.log(`➡️ ${csvRows.length} lignes trouvées dans le CSV employees`); + + // 2. Récupérer tous les emails du CSV + const emails = Array.from( + new Set( + csvRows + .map((r) => r.email.trim()) + .filter((e) => e.length > 0), + ), + ); + + console.log(`➡️ ${emails.length} emails uniques trouvés dans le CSV`); + + // 3. Charger les users correspondants avec les bons rôles + const users = (await prisma.users.findMany({ + where: { + email: { in: emails }, + role: { in: ELIGIBLE_ROLES }, + }, + select: { + id: true, + email: true, + role: true, + }, + })) as UserSummary[]; + + console.log(`➡️ ${users.length} users éligibles trouvés dans la DB`); + + // Map email → user + const userByEmail = new Map(); + for (const user of users) { + const key = user.email.trim().toLowerCase(); + userByEmail.set(key, user); + } + + // 4. Construire les données pour employees.createMany + const employeesToCreate: { + user_id: string; + external_payroll_id: number; + company_code: number; + first_work_day: Date; + last_work_day: Date | null; + job_title: string | null; + is_supervisor: boolean; + supervisor_id?: number | null; + }[] = []; + + const rowsWithoutUser: EmployeeCsvRow[] = []; + const rowsWithInvalidNumbers: EmployeeCsvRow[] = []; + + for (const row of csvRows) { + const emailKey = row.email.trim().toLowerCase(); + const user = userByEmail.get(emailKey); + + if (!user) { + rowsWithoutUser.push(row); + continue; + } + + const external_payroll_id = parseIntSafe(row.employee_number, 'external_payroll_id'); + const company_code = parseIntSafe(String(row.company), 'company_code'); + + if (external_payroll_id === null || company_code === null) { + rowsWithInvalidNumbers.push(row); + continue; + } + + const first_work_day = millisToDate(row.onboarding); + const last_work_day = millisToDate(row.offboarding); + const is_supervisor = parseBoolean(row.is_supervisor); + const job_title = row.job_title?.trim() || null; + + if (!first_work_day) { + console.warn( + `⚠️ Date d'onboarding invalide pour ${row.email} (employee_number=${row.employee_number})`, + ); + continue; + } + + employeesToCreate.push({ + user_id: user.id, + external_payroll_id, + company_code, + first_work_day, + last_work_day, + job_title, + is_supervisor, + supervisor_id: null, // on pourra gérer ça plus tard si tu as les infos + }); + } + + console.log(`➡️ ${employeesToCreate.length} entrées Employees prêtes à être insérées`); + + if (rowsWithoutUser.length > 0) { + console.warn(`⚠️ ${rowsWithoutUser.length} lignes CSV sans user correspondant (email / rôle) :`); + for (const row of rowsWithoutUser) { + console.warn( + ` - email=${row.email}, employee_number=${row.employee_number}, company=${row.company}`, + ); + } + } + + if (rowsWithInvalidNumbers.length > 0) { + console.warn(`⚠️ ${rowsWithInvalidNumbers.length} lignes CSV avec ids/compagnies invalides :`); + for (const row of rowsWithInvalidNumbers) { + console.warn( + ` - email=${row.email}, employee_number="${row.employee_number}", company="${row.company}"`, + ); + } + } + + if (employeesToCreate.length === 0) { + console.warn('⚠️ Aucun Employees à créer, arrêt.'); + return; + } + + // 5. Insert en batch + const result = await prisma.employees.createMany({ + data: employeesToCreate, + skipDuplicates: true, // évite les erreurs si tu relances le script + }); + + console.log(`✅ ${result.count} employees insérés dans la DB`); +} + +main() + .catch((err) => { + console.error('❌ Erreur pendant l’import CSV → Employees', err); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/scripts/done/import-users-from-csv.ts b/scripts/import-users-from-csv.ts similarity index 97% rename from scripts/done/import-users-from-csv.ts rename to scripts/import-users-from-csv.ts index 0bcf5a9..66480e8 100644 --- a/scripts/done/import-users-from-csv.ts +++ b/scripts/import-users-from-csv.ts @@ -6,7 +6,7 @@ // const prisma = new PrismaClient(); // // ⚙️ Chemin vers ton CSV (à adapter selon où tu le mets) -// const CSV_PATH = path.resolve(__dirname, '../../data/export_employee_table.csv'); +// const CSV_PATH = path.resolve(__dirname, 'data/export_employee_table.csv'); // // Type aligné sur les colonnes du CSV // type CsvUserRow = { diff --git a/scripts/init-preferences.ts b/scripts/init-preferences.ts new file mode 100644 index 0000000..f520857 --- /dev/null +++ b/scripts/init-preferences.ts @@ -0,0 +1,67 @@ +// src/scripts/init-preferences.ts +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +type UserSummary = { + id: string; // UUID + email: string; +}; + +async function main() { + console.log('➡️ Initialisation des préférences utilisateurs…'); + + // 1. Récupérer tous les users + const users = (await prisma.users.findMany({ + select: { + id: true, + email: true, + }, + })) as UserSummary[]; + + console.log(`➡️ ${users.length} users trouvés dans la DB`); + + // 2. Récupérer toutes les préférences existantes + const existingPrefs = await prisma.preferences.findMany({ + select: { + user_id: true, + }, + }); + + const userIdsWithPrefs = new Set(existingPrefs.map((p) => p.user_id)); + + console.log(`➡️ ${existingPrefs.length} users ont déjà des préférences`); + + // 3. Filtrer les users qui n'ont pas encore de preferences + const usersWithoutPrefs = users.filter((u) => !userIdsWithPrefs.has(u.id)); + + console.log(`➡️ ${usersWithoutPrefs.length} users n'ont pas encore de préférences`); + + if (usersWithoutPrefs.length === 0) { + console.log('✅ Rien à faire, toutes les préférences sont déjà créées.'); + return; + } + + // 4. Préparer les entrées pour createMany + const prefsToCreate = usersWithoutPrefs.map((u) => ({ + user_id: u.id, + // tous les autres champs prendront leurs valeurs par défaut (0) + })); + + // 5. Insertion en batch + const result = await prisma.preferences.createMany({ + data: prefsToCreate, + skipDuplicates: true, // sécurité si jamais le script est relancé + }); + + console.log(`✅ ${result.count} préférences créées dans la DB`); +} + +main() + .catch((err) => { + console.error('❌ Erreur pendant l’initialisation des préférences', err); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/scripts/migrate-expenses.ts b/scripts/migrate-expenses.ts index 364a49d..e552d5d 100644 --- a/scripts/migrate-expenses.ts +++ b/scripts/migrate-expenses.ts @@ -1,165 +1,165 @@ -// import { PrismaClient as Prisma } from "@prisma/client"; -// import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" -// import { toDateFromString, toHHmmFromDate, toStringFromDate } from "src/common/utils/date-utils"; +import { PrismaClient as Prisma } from "@prisma/client"; +import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" +import { toDateFromString, toHHmmFromDate, toStringFromDate } from "src/common/utils/date-utils"; -// const prisma_legacy = new PrismaLegacy({}); -// const prisma = new Prisma({}); +const prisma_legacy = new PrismaLegacy({}); +const prisma = new Prisma({}); -// type NewEmployee = { -// id: number; -// company_code: number; -// external_payroll_id: number; -// } +type NewEmployee = { + id: number; + company_code: number; + external_payroll_id: number; +} -// type OldExpense = { -// time_sheet_id: string | null; -// date: string | null; -// code: string | null; -// description: string | null; -// value: number | null; -// status: boolean | null; -// } +type OldExpense = { + time_sheet_id: string | null; + date: string | null; + code: string | null; + description: string | null; + value: number | null; + status: boolean | null; +} -// export const extractOldExpenses = async () => { -// for (let id = 1; id <= 61; id++) { +export const extractOldExpenses = async () => { + for (let id = 1; id <= 61; id++) { -// console.log(`Start of Expense migration ***************************************************************`); + console.log(`Start of Expense migration ***************************************************************`); -// const new_employee = await findOneNewEmployee(id); -// console.log(`Employee ${id} found in new DB`); + const new_employee = await findOneNewEmployee(id); + console.log(`Employee ${id} found in new DB`); -// const new_timesheets = await findManyNewTimesheets(new_employee.id); -// console.log(`New Timesheets found for employee ${id}`); + const new_timesheets = await findManyNewTimesheets(new_employee.id); + console.log(`New Timesheets found for employee ${id}`); -// const old_employee_id = await findOneOldEmployee(new_employee); -// console.log(`Employee ${new_employee.id} found in old DB`); + const old_employee_id = await findOneOldEmployee(new_employee); + console.log(`Employee ${new_employee.id} found in old DB`); -// const old_timesheets = await findManyOldTimesheets(old_employee_id); -// console.log(`Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`); + const old_timesheets = await findManyOldTimesheets(old_employee_id); + console.log(`Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`); -// console.log('Start of Expense creation*****************************************************************'); -// for (const old_timesheet of old_timesheets) { -// if (!old_timesheet.start_date) continue; -// const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime()); -// if (!new_timesheet) { -// console.warn(`No new timesheet matching legacy timesheet ${old_timesheet.id}`); -// continue; -// } -// const old_expenses = await prisma_legacy.expenses.findMany({ -// where: { time_sheet_id: old_timesheet.id }, -// select: { -// time_sheet_id: true, -// date: true, -// code: true, -// description: true, -// value: true, -// status: true, + console.log('Start of Expense creation*****************************************************************'); + for (const old_timesheet of old_timesheets) { + if (!old_timesheet.start_date) continue; + const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime()); + if (!new_timesheet) { + console.warn(`No new timesheet matching legacy timesheet ${old_timesheet.id}`); + continue; + } + const old_expenses = await prisma_legacy.expenses.findMany({ + where: { time_sheet_id: old_timesheet.id }, + select: { + time_sheet_id: true, + date: true, + code: true, + description: true, + value: true, + status: true, -// }, -// }); -// await createManyNewExpenses(new_timesheet.id, old_expenses); -// } + }, + }); + await createManyNewExpenses(new_timesheet.id, old_expenses); + } -// } -// await prisma_legacy.$disconnect(); -// await prisma.$disconnect(); -// } + } + await prisma_legacy.$disconnect(); + await prisma.$disconnect(); +} -// const findOneNewEmployee = async (id: number): Promise => { -// const new_employee = await prisma.employees.findUnique({ -// where: { id: id }, -// select: { -// id: true, -// company_code: true, -// external_payroll_id: true, -// }, -// }); -// if (!new_employee) throw new Error(`New Employee with id ${id} not found`) -// return new_employee; -// } +const findOneNewEmployee = async (id: number): Promise => { + const new_employee = await prisma.employees.findUnique({ + where: { id: id }, + select: { + id: true, + company_code: true, + external_payroll_id: true, + }, + }); + if (!new_employee) throw new Error(`New Employee with id ${id} not found`) + return new_employee; +} -// const findOneOldEmployee = async (new_employee: NewEmployee): Promise => { -// const old_employee = await prisma_legacy.employees.findFirst({ -// where: { -// company: new_employee.company_code, -// employee_number: new_employee.external_payroll_id.toString(), -// }, -// select: { -// id: true, -// }, -// }); -// if (!old_employee) throw new Error(`Old Employee not found`); -// return old_employee.id; -// } +const findOneOldEmployee = async (new_employee: NewEmployee): Promise => { + const old_employee = await prisma_legacy.employees.findFirst({ + where: { + company: new_employee.company_code, + employee_number: new_employee.external_payroll_id.toString(), + }, + select: { + id: true, + }, + }); + if (!old_employee) throw new Error(`Old Employee not found`); + return old_employee.id; +} -// const findManyOldTimesheets = async (old_employee_id: string) => { -// const old_timesheets = await prisma_legacy.time_sheets.findMany({ -// where: { employee_id: old_employee_id }, -// select: { id: true, start_date: true, status: true } -// }); -// return old_timesheets; -// } +const findManyOldTimesheets = async (old_employee_id: string) => { + const old_timesheets = await prisma_legacy.time_sheets.findMany({ + where: { employee_id: old_employee_id }, + select: { id: true, start_date: true, status: true } + }); + return old_timesheets; +} -// const findManyNewTimesheets = async (employee_id: number) => { -// const timesheets = await prisma.timesheets.findMany({ -// where: { employee_id: employee_id }, -// select: { id: true, start_date: true } -// }) -// return timesheets; -// } +const findManyNewTimesheets = async (employee_id: number) => { + const timesheets = await prisma.timesheets.findMany({ + where: { employee_id: employee_id }, + select: { id: true, start_date: true } + }) + return timesheets; +} -// const createManyNewExpenses = async (timesheet_id: number, old_expenses: OldExpense[]) => { -// for (const old_expense of old_expenses) { -// let mileage: number = 0; -// let amount: number = old_expense.value ?? 0; -// if (old_expense.code === 'G503') { -// mileage = old_expense.value!; -// amount = mileage * 0.72; -// } -// if (mileage < 0 || amount < 0) { -// console.warn(`expense of value less than '0' found`) -// continue; -// } +const createManyNewExpenses = async (timesheet_id: number, old_expenses: OldExpense[]) => { + for (const old_expense of old_expenses) { + let mileage: number = 0; + let amount: number = old_expense.value ?? 0; + if (old_expense.code === 'G503') { + mileage = old_expense.value!; + amount = mileage * 0.72; + } + if (mileage < 0 || amount < 0) { + console.warn(`expense of value less than '0' found`) + continue; + } -// if (old_expense.date == null) { -// console.warn(`Expense date invalid ${old_expense.date}`); -// continue; -// } -// const date = toDateFromString(old_expense.date); + if (old_expense.date == null) { + console.warn(`Expense date invalid ${old_expense.date}`); + continue; + } + const date = toDateFromString(old_expense.date); -// if (old_expense.status == null) { -// console.warn(`status null for legacy expense ${old_expense}`); -// continue; -// } + if (old_expense.status == null) { + console.warn(`status null for legacy expense ${old_expense}`); + continue; + } -// if (old_expense.code == null) { -// console.warn(`Code null for legacy expense ${old_expense.code}`); -// continue; -// } -// const bank_code_id = await findBankCodeIdUsingOldCode(old_expense.code); + if (old_expense.code == null) { + console.warn(`Code null for legacy expense ${old_expense.code}`); + continue; + } + const bank_code_id = await findBankCodeIdUsingOldCode(old_expense.code); -// await prisma.expenses.create({ -// // where: { unique_ts_id_date_amount_mileage: { timesheet_id: timesheet_id, date, amount, mileage } }, -// // update: { -// // is_approved: old_expense.status, -// // }, -// data: { -// date: date, -// comment: old_expense.description ?? '', -// timesheet_id: timesheet_id, -// bank_code_id: bank_code_id, -// amount: amount, -// mileage: mileage, -// } -// }); -// } -// } + await prisma.expenses.upsert({ + where: { unique_ts_id_date_amount_mileage: { timesheet_id: timesheet_id, date, amount, mileage } }, + update: { + is_approved: old_expense.status, + }, + create: { + date: date, + comment: old_expense.description ?? '', + timesheet_id: timesheet_id, + bank_code_id: bank_code_id, + amount: amount, + mileage: mileage, + } + }); + } +} -// const findBankCodeIdUsingOldCode = async (code: string): Promise => { -// const bank_code = await prisma.bankCodes.findFirst({ -// where: { bank_code: code }, -// select: { id: true }, -// }); -// if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`) -// return bank_code.id; -// } \ No newline at end of file +const findBankCodeIdUsingOldCode = async (code: string): Promise => { + const bank_code = await prisma.bankCodes.findFirst({ + where: { bank_code: code }, + select: { id: true }, + }); + if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`) + return bank_code.id; +} \ No newline at end of file diff --git a/scripts/migrate-shifts.ts b/scripts/migrate-shifts.ts index c9fc36d..5b966c4 100644 --- a/scripts/migrate-shifts.ts +++ b/scripts/migrate-shifts.ts @@ -1,210 +1,210 @@ -// import { PrismaClient as PrismaNew } from "@prisma/client"; -// import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" +import { PrismaClient as PrismaNew } from "@prisma/client"; +import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" -// const prisma_legacy = new PrismaLegacy({}); -// const prisma = new PrismaNew({}); +const prisma_legacy = new PrismaLegacy({}); +const prisma = new PrismaNew({}); -// type NewEmployee = { -// id: number; -// company_code: number; -// external_payroll_id: number; -// } +type NewEmployee = { + id: number; + company_code: number; + external_payroll_id: number; +} -// type OldShifts = { -// time_sheet_id: string | null; -// code: string | null; -// type: string | null; -// date: Date | null; -// start_time: bigint | null; -// end_time: bigint | null; -// comment: string | null; -// status: boolean | null; -// } +type OldShifts = { + time_sheet_id: string | null; + code: string | null; + type: string | null; + date: Date | null; + start_time: bigint | null; + end_time: bigint | null; + comment: string | null; + status: boolean | null; +} -// export const extractOldShifts = async () => { -// // for (let id = 1; id <= 61; id++) { -// console.log(`Start of shift migration ***************************************************************`); -// const new_employee = await findOneNewEmployee(50); -// console.log(`Employee ${50} found in new DB`); +export const extractOldShifts = async () => { + for (let id = 1; id <= 61; id++) { + console.log(`Start of shift migration ***************************************************************`); + const new_employee = await findOneNewEmployee(id); + console.log(`Employee ${id} found in new DB`); -// const new_timesheets = await findManyNewTimesheets(new_employee.id); -// console.log(`New Timesheets found for employee ${50}`); -// for (const ts of new_timesheets) { -// console.log(`start_date = ${ts.start_date} timesheet_id = ${ts.id}`) + const new_timesheets = await findManyNewTimesheets(new_employee.id); + console.log(`New Timesheets found for employee ${id}`); + for (const ts of new_timesheets) { + console.log(`start_date = ${ts.start_date} timesheet_id = ${ts.id}`) -// } -// console.log('***************************************************************'); -// const old_employee_id = await findOneOldEmployee(new_employee); -// console.log(`Employee ${new_employee.id} found in old DB`); + } + console.log('***************************************************************'); + const old_employee_id = await findOneOldEmployee(new_employee); + console.log(`Employee ${new_employee.id} found in old DB`); -// const old_timesheets = await findManyOldTimesheets(old_employee_id); -// console.log(`Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`); + const old_timesheets = await findManyOldTimesheets(old_employee_id); + console.log(`Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`); -// for (const old_timesheet of old_timesheets) { -// if (!old_timesheet.start_date) continue; -// const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime()); -// if (!new_timesheet) { -// console.warn(`No new timesheet ${new_timesheet} matching legacy timesheet ${old_timesheet.id}`); -// continue; -// } -// const old_shifts = await prisma_legacy.shifts.findMany({ -// where: { time_sheet_id: old_timesheet.id }, -// select: { -// time_sheet_id: true, -// code: true, -// type: true, -// date: true, -// start_time: true, -// end_time: true, -// comment: true, -// status: true, -// }, -// }); -// await createManyNewShifts(new_timesheet.id, old_shifts); -// } -// // } -// await prisma_legacy.$disconnect(); -// await prisma.$disconnect(); -// } + for (const old_timesheet of old_timesheets) { + if (!old_timesheet.start_date) continue; + const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime()); + if (!new_timesheet) { + console.warn(`No new timesheet ${new_timesheet} matching legacy timesheet ${old_timesheet.id}`); + continue; + } + const old_shifts = await prisma_legacy.shifts.findMany({ + where: { time_sheet_id: old_timesheet.id }, + select: { + time_sheet_id: true, + code: true, + type: true, + date: true, + start_time: true, + end_time: true, + comment: true, + status: true, + }, + }); + await createManyNewShifts(new_timesheet.id, old_shifts); + } + } + await prisma_legacy.$disconnect(); + await prisma.$disconnect(); +} -// const findOneNewEmployee = async (id: number): Promise => { -// const new_employee = await prisma.employees.findUnique({ -// where: { id: id }, -// select: { -// id: true, -// company_code: true, -// external_payroll_id: true, -// }, -// }); -// if (!new_employee) throw new Error(`New Employee with id ${id} not found`) -// return new_employee; -// } +const findOneNewEmployee = async (id: number): Promise => { + const new_employee = await prisma.employees.findUnique({ + where: { id: id }, + select: { + id: true, + company_code: true, + external_payroll_id: true, + }, + }); + if (!new_employee) throw new Error(`New Employee with id ${id} not found`) + return new_employee; +} -// const findOneOldEmployee = async (new_employee: NewEmployee): Promise => { -// const old_employee = await prisma_legacy.employees.findFirst({ -// where: { -// company: new_employee.company_code, -// employee_number: new_employee.external_payroll_id.toString(), -// }, -// select: { -// id: true, -// }, -// }); -// if (!old_employee) throw new Error(`Old Employee not found`); -// return old_employee.id; -// } +const findOneOldEmployee = async (new_employee: NewEmployee): Promise => { + const old_employee = await prisma_legacy.employees.findFirst({ + where: { + company: new_employee.company_code, + employee_number: new_employee.external_payroll_id.toString(), + }, + select: { + id: true, + }, + }); + if (!old_employee) throw new Error(`Old Employee not found`); + return old_employee.id; +} -// const findManyOldTimesheets = async (old_employee_id: string) => { -// const old_timesheets = await prisma_legacy.time_sheets.findMany({ -// where: { employee_id: old_employee_id }, -// select: { id: true, start_date: true, status: true } -// }); -// return old_timesheets; -// } +const findManyOldTimesheets = async (old_employee_id: string) => { + const old_timesheets = await prisma_legacy.time_sheets.findMany({ + where: { employee_id: old_employee_id }, + select: { id: true, start_date: true, status: true } + }); + return old_timesheets; +} -// const findManyNewTimesheets = async (employee_id: number) => { -// const timesheets = await prisma.timesheets.findMany({ -// where: { employee_id: employee_id }, -// select: { id: true, start_date: true } -// }) -// return timesheets; -// } +const findManyNewTimesheets = async (employee_id: number) => { + const timesheets = await prisma.timesheets.findMany({ + where: { employee_id: employee_id }, + select: { id: true, start_date: true } + }) + return timesheets; +} -// const createManyNewShifts = async (timesheet_id: number, old_shifts: OldShifts[]) => { -// for (const old_shift of old_shifts) { -// let is_remote = true; +const createManyNewShifts = async (timesheet_id: number, old_shifts: OldShifts[]) => { + for (const old_shift of old_shifts) { + let is_remote = true; -// const start = toHHmmfromLegacyTimestamp(old_shift.start_time); -// if (old_shift.start_time == null || !start) { -// console.warn(`Shift start invalid ${old_shift.start_time}`); -// continue; -// } + const start = toHHmmfromLegacyTimestamp(old_shift.start_time); + if (old_shift.start_time == null || !start) { + console.warn(`Shift start invalid ${old_shift.start_time}`); + continue; + } -// const end = toHHmmfromLegacyTimestamp(old_shift.end_time); -// if (old_shift.end_time == null || !end) { -// console.warn(`Shift end invalid ${old_shift.end_time}`); -// continue; -// } + const end = toHHmmfromLegacyTimestamp(old_shift.end_time); + if (old_shift.end_time == null || !end) { + console.warn(`Shift end invalid ${old_shift.end_time}`); + continue; + } -// if (old_shift.date == null) { -// console.warn(`Shift date invalid ${old_shift.date}`); -// continue; -// } + if (old_shift.date == null) { + console.warn(`Shift date invalid ${old_shift.date}`); + continue; + } -// if (old_shift.status == null) { -// console.warn(`status null for legacy shift ${old_shift}`); -// continue; -// } -// if (old_shift.type == null) { -// console.warn(`type null for legacy shift ${old_shift.type}`); -// continue; -// } + if (old_shift.status == null) { + console.warn(`status null for legacy shift ${old_shift}`); + continue; + } + if (old_shift.type == null) { + console.warn(`type null for legacy shift ${old_shift.type}`); + continue; + } -// if (old_shift.type === 'office') { -// is_remote = false; -// } + if (old_shift.type === 'office') { + is_remote = false; + } -// if (old_shift.code == null) { -// console.warn(`Code null for legacy shift ${old_shift.code}`); -// continue; -// } -// const bank_code_id = await findBankCodeIdUsingOldCode(old_shift.code); -// try { -// await prisma.shifts.create({ -// // where: { unique_ts_id_date_start_time: { -// // timesheet_id, -// // date: old_shift.date, -// // start_time: toDateFromHHmm(start) }}, -// // update: { -// // start_time: toDateFromHHmm(start), -// // end_time: toDateFromHHmm(end), -// // comment: old_shift.comment, -// // is_approved: old_shift.status, -// // is_remote: is_remote, -// // bank_code_id: bank_code_id, -// // }, -// data: { -// date: old_shift.date, -// start_time: toDateFromHHmm(start), -// end_time: toDateFromHHmm(end), -// comment: old_shift.comment, -// is_approved: old_shift.status, -// is_remote: is_remote, -// timesheet_id: timesheet_id, -// bank_code_id: bank_code_id, -// }, -// }); -// } catch (error) { -// console.log('An error occured during shifts creation'); -// } -// } -// } + if (old_shift.code == null) { + console.warn(`Code null for legacy shift ${old_shift.code}`); + continue; + } + const bank_code_id = await findBankCodeIdUsingOldCode(old_shift.code); + try { + await prisma.shifts.create({ + // where: { unique_ts_id_date_start_time: { + // timesheet_id, + // date: old_shift.date, + // start_time: toDateFromHHmm(start) }}, + // update: { + // start_time: toDateFromHHmm(start), + // end_time: toDateFromHHmm(end), + // comment: old_shift.comment, + // is_approved: old_shift.status, + // is_remote: is_remote, + // bank_code_id: bank_code_id, + // }, + data: { + date: old_shift.date, + start_time: toDateFromHHmm(start), + end_time: toDateFromHHmm(end), + comment: old_shift.comment, + is_approved: old_shift.status, + is_remote: is_remote, + timesheet_id: timesheet_id, + bank_code_id: bank_code_id, + }, + }); + } catch (error) { + console.log('An error occured during shifts creation'); + } + } +} -// const toHHmmfromLegacyTimestamp = (value: bigint | null): string | null => { -// if (value == null) return null; -// const date = new Date(Number(value)); -// const hh = String(date.getHours()).padStart(2, '0'); -// const mm = String(date.getMinutes()).padStart(2, '0'); -// return `${hh}:${mm}`; -// } +const toHHmmfromLegacyTimestamp = (value: bigint | null): string | null => { + if (value == null) return null; + const date = new Date(Number(value)); + const hh = String(date.getHours()).padStart(2, '0'); + const mm = String(date.getMinutes()).padStart(2, '0'); + return `${hh}:${mm}`; +} -// const toDateFromHHmm = (hhmm: string): Date => { -// const [hh, mm] = hhmm.split(':'); -// const hours = Number(hh); -// const minutes = Number(mm); -// return new Date(Date.UTC(1970, 0, 1, hours, minutes, 0, 0)); -// } +const toDateFromHHmm = (hhmm: string): Date => { + const [hh, mm] = hhmm.split(':'); + const hours = Number(hh); + const minutes = Number(mm); + return new Date(Date.UTC(1970, 0, 1, hours, minutes, 0, 0)); +} -// const findBankCodeIdUsingOldCode = async (code: string): Promise => { -// if (code === 'G700') { -// code = 'G104'; -// } else if (code === 'G140') { -// code = 'G56' -// } -// const bank_code = await prisma.bankCodes.findFirst({ -// where: { bank_code: code }, -// select: { id: true, bank_code: true }, -// }); -// if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`) -// return bank_code.id; -// } +const findBankCodeIdUsingOldCode = async (code: string): Promise => { + if (code === 'G700') { + code = 'G104'; + } else if (code === 'G140') { + code = 'G56' + } + const bank_code = await prisma.bankCodes.findFirst({ + where: { bank_code: code }, + select: { id: true, bank_code: true }, + }); + if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`) + return bank_code.id; +} diff --git a/scripts/migrate-timesheets.ts b/scripts/migrate-timesheets.ts index dd65564..da25839 100644 --- a/scripts/migrate-timesheets.ts +++ b/scripts/migrate-timesheets.ts @@ -1,118 +1,118 @@ -// import { PrismaClient as Prisma } from "@prisma/client"; -// import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" -// import { toStringFromDate } from "src/common/utils/date-utils"; +import { PrismaClient as Prisma } from "@prisma/client"; +import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" +import { toStringFromDate } from "src/common/utils/date-utils"; -// type NewEmployee = { -// id: number; -// company_code: number; -// external_payroll_id: number; -// } +type NewEmployee = { + id: number; + company_code: number; + external_payroll_id: number; +} -// type OldTimesheets = { -// id: string; -// start_date: Date | null; -// status: boolean | null; -// } +type OldTimesheets = { + id: string; + start_date: Date | null; + status: boolean | null; +} -// const prisma_legacy = new PrismaLegacy({}); -// const prisma_new = new Prisma({}); +const prisma_legacy = new PrismaLegacy({}); +const prisma_new = new Prisma({}); -// export const extractOldTimesheets = async () => { -// for (let id = 1; id <= 61; id++) { -// const new_employee = await findOneNewEmployee(id); -// console.log(`Employee ${id} found in new DB ${new_employee.external_payroll_id}`); +export const extractOldTimesheets = async () => { + for (let id = 1; id <= 61; id++) { + const new_employee = await findOneNewEmployee(id); + console.log(`Employee ${id} found in new DB ${new_employee.external_payroll_id}`); -// const old_employee_id = await findOneOldEmployee(new_employee); -// console.log(`Employee ${new_employee.id} found in old DB`); + const old_employee_id = await findOneOldEmployee(new_employee); + console.log(`Employee ${new_employee.id} found in old DB`); -// const old_timesheets = await findManyOldTimesheets(old_employee_id); -// console.log(` ${old_timesheets.length} Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`); + const old_timesheets = await findManyOldTimesheets(old_employee_id); + console.log(` ${old_timesheets.length} Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`); -// await createManyNewTimesheets(old_timesheets, new_employee); -// console.log(`${old_timesheets.length} New Timesheets created in new DB for employee ${new_employee.id}`); -// } -// await prisma_legacy.$disconnect(); -// await prisma_new.$disconnect(); -// } + await createManyNewTimesheets(old_timesheets, new_employee); + console.log(`${old_timesheets.length} New Timesheets created in new DB for employee ${new_employee.id}`); + } + await prisma_legacy.$disconnect(); + await prisma_new.$disconnect(); +} -// const findOneNewEmployee = async (id: number): Promise => { -// const new_employee = await prisma_new.employees.findUnique({ -// where: { id: id }, -// select: { -// id: true, -// company_code: true, -// external_payroll_id: true, -// }, -// }); -// if (!new_employee) throw new Error(`New Employee with id ${id} not found`) -// return new_employee; -// } +const findOneNewEmployee = async (id: number): Promise => { + const new_employee = await prisma_new.employees.findUnique({ + where: { id: id }, + select: { + id: true, + company_code: true, + external_payroll_id: true, + }, + }); + if (!new_employee) throw new Error(`New Employee with id ${id} not found`) + return new_employee; +} -// const findOneOldEmployee = async (new_employee: NewEmployee): Promise => { -// const employee_number = new_employee.external_payroll_id.toString() -// const old_employee = await prisma_legacy.employees.findFirst({ -// where: { -// company: new_employee.company_code, -// employee_number: employee_number, -// }, -// select: { -// id: true, -// }, -// }); -// if (!old_employee) throw new Error(`Old Employee not found`); -// return old_employee.id; -// } +const findOneOldEmployee = async (new_employee: NewEmployee): Promise => { + const employee_number = new_employee.external_payroll_id.toString() + const old_employee = await prisma_legacy.employees.findFirst({ + where: { + company: new_employee.company_code, + employee_number: employee_number, + }, + select: { + id: true, + }, + }); + if (!old_employee) throw new Error(`Old Employee not found`); + return old_employee.id; +} -// const findManyOldTimesheets = async (old_employee_id: string) => { -// const old_timesheets = await prisma_legacy.time_sheets.findMany({ -// where: { employee_id: old_employee_id }, -// select: { id: true, start_date: true, status: true } -// }); -// if (!old_timesheets) throw new Error(`old Timesheets not found for employee_id ${old_employee_id}`) -// return old_timesheets; -// } +const findManyOldTimesheets = async (old_employee_id: string) => { + const old_timesheets = await prisma_legacy.time_sheets.findMany({ + where: { employee_id: old_employee_id }, + select: { id: true, start_date: true, status: true } + }); + if (!old_timesheets) throw new Error(`old Timesheets not found for employee_id ${old_employee_id}`) + return old_timesheets; +} -// const createManyNewTimesheets = async (old_timesheets: OldTimesheets[], new_employee: NewEmployee) => { -// for (const timesheet of old_timesheets) { -// if (timesheet.start_date == null) { -// console.warn(`start_date invalid for legacy timesheet ${timesheet.id}`); -// continue; -// } -// if (timesheet.status == null) { -// console.warn(`status null for legacy timesheet ${timesheet.id}`); -// continue; -// } +const createManyNewTimesheets = async (old_timesheets: OldTimesheets[], new_employee: NewEmployee) => { + for (const timesheet of old_timesheets) { + if (timesheet.start_date == null) { + console.warn(`start_date invalid for legacy timesheet ${timesheet.id}`); + continue; + } + if (timesheet.status == null) { + console.warn(`status null for legacy timesheet ${timesheet.id}`); + continue; + } -// try { -// const new_timesheet = await prisma_new.timesheets.upsert({ -// where: { employee_id_start_date: { employee_id: new_employee.id, start_date: timesheet.start_date } }, -// update: { -// is_approved: timesheet.status, -// }, -// create: { -// employee_id: new_employee.id, -// start_date: timesheet.start_date, -// is_approved: timesheet.status, -// }, -// }); -// if (!new_timesheet) throw new Error( -// `Timesheet with start_date: ${toStringFromDate(timesheet.start_date!)} for employee ${new_employee.id} not created` -// ); -// } catch (error) { -// throw new Error('An error occured during timesheets creation'); -// } -// } -// } + try { + const new_timesheet = await prisma_new.timesheets.upsert({ + where: { employee_id_start_date: { employee_id: new_employee.id, start_date: timesheet.start_date } }, + update: { + is_approved: timesheet.status, + }, + create: { + employee_id: new_employee.id, + start_date: timesheet.start_date, + is_approved: timesheet.status, + }, + }); + if (!new_timesheet) throw new Error( + `Timesheet with start_date: ${toStringFromDate(timesheet.start_date!)} for employee ${new_employee.id} not created` + ); + } catch (error) { + throw new Error('An error occured during timesheets creation'); + } + } +} -// extractOldTimesheets() -// .then(() => { -// console.log("Migration completed"); -// }) -// .catch((error) => { -// console.error("Migration failed:", error); -// }) -// .finally(async () => { -// await prisma_legacy.$disconnect(); -// await prisma_new.$disconnect(); -// }); \ No newline at end of file +extractOldTimesheets() + .then(() => { + console.log("Migration completed"); + }) + .catch((error) => { + console.error("Migration failed:", error); + }) + .finally(async () => { + await prisma_legacy.$disconnect(); + await prisma_new.$disconnect(); + }); \ No newline at end of file diff --git a/scripts/migration.service.ts b/scripts/migration.service.ts index 3a9b3ba..2bb37d6 100644 --- a/scripts/migration.service.ts +++ b/scripts/migration.service.ts @@ -1,21 +1,21 @@ -// // import { extractOldTimesheets } from "scripts/migrate-timesheets"; -// // import { extractOldExpenses } from "scripts/migrate-expenses"; -// // import { extractOldShifts } from "scripts/migrate-shifts"; -// import { Injectable } from "@nestjs/common"; +// import { extractOldTimesheets } from "scripts/migrate-timesheets"; +// import { extractOldExpenses } from "scripts/migrate-expenses"; +// import { extractOldShifts } from "scripts/migrate-shifts"; +import { Injectable } from "@nestjs/common"; -// @Injectable() -// export class MigrationService { -// constructor() {} +@Injectable() +export class MigrationService { + constructor() {} -// async migrateTimesheets() { -// // extractOldTimesheets(); -// }; + async migrateTimesheets() { + // extractOldTimesheets(); + }; -// async migrateShifts() { -// // extractOldShifts(); -// } + async migrateShifts() { + // extractOldShifts(); + } -// async migrateExpenses() { -// // extractOldExpenses(); -// } -// } \ No newline at end of file + async migrateExpenses() { + // extractOldExpenses(); + } +} \ No newline at end of file diff --git a/src/identity-and-account/preferences/dtos/preferences.dto.ts b/src/identity-and-account/preferences/dtos/preferences.dto.ts index 1c7530f..40de477 100644 --- a/src/identity-and-account/preferences/dtos/preferences.dto.ts +++ b/src/identity-and-account/preferences/dtos/preferences.dto.ts @@ -1,4 +1,4 @@ -import { IsBoolean, IsEnum, IsInt, IsOptional } from "class-validator"; +import { IsBoolean, IsEnum, IsOptional } from "class-validator"; export enum DisplayLanguage { FR = 'fr-FR', @@ -6,7 +6,7 @@ export enum DisplayLanguage { } export class PreferencesDto { - @IsInt() notifications: number; + @IsBoolean() notifications: boolean; @IsOptional() @IsBoolean() is_dark_mode?: boolean; @IsEnum(DisplayLanguage) display_language: string; @IsBoolean() is_lefty_mode: boolean; diff --git a/src/identity-and-account/user-module-access/dtos/module-acces.dto.ts b/src/identity-and-account/user-module-access/dtos/module-acces.dto.ts index a4d5a6f..5de04d9 100644 --- a/src/identity-and-account/user-module-access/dtos/module-acces.dto.ts +++ b/src/identity-and-account/user-module-access/dtos/module-acces.dto.ts @@ -6,5 +6,5 @@ export class ModuleAccess { @IsBoolean() employee_list!: boolean; @IsBoolean() employee_management!: boolean; @IsBoolean() personnal_profile!: boolean; - @IsBoolean() blocked!: boolean; + @IsBoolean() dashboard!: boolean; } \ No newline at end of file diff --git a/src/identity-and-account/user-module-access/services/module-access-get.service.ts b/src/identity-and-account/user-module-access/services/module-access-get.service.ts index 2d74e82..5aaf07d 100644 --- a/src/identity-and-account/user-module-access/services/module-access-get.service.ts +++ b/src/identity-and-account/user-module-access/services/module-access-get.service.ts @@ -23,8 +23,8 @@ export class AccessGetService { timesheets_approval: true, employee_list: true, employee_management: true, - personnal_profile: true, - blocked: true, + personal_profile: true, + dashboard: true, }, }); if (!access) return { success: false, error: 'MODULE_ACCESS_NOT_FOUND' }; @@ -34,8 +34,8 @@ export class AccessGetService { timesheets_approval: access.timesheets_approval, employee_list: access.employee_list, employee_management: access.employee_management, - personnal_profile: access.personnal_profile, - blocked: access.blocked, + personnal_profile: access.personal_profile, + dashboard: access.dashboard, }; return { success: true, data: granted_access } } diff --git a/src/identity-and-account/user-module-access/services/module-access-update.service.ts b/src/identity-and-account/user-module-access/services/module-access-update.service.ts index f721b04..051a1a5 100644 --- a/src/identity-and-account/user-module-access/services/module-access-update.service.ts +++ b/src/identity-and-account/user-module-access/services/module-access-update.service.ts @@ -24,7 +24,8 @@ export class AccessUpdateService { timesheets_approval: true, employee_list: true, employee_management: true, - personnal_profile: true, + personal_profile: true, + dashboard: true, }, }); if (!orignal_access) return { success: false, error: 'MODULE_ACCESS_NOT_FOUND' }; @@ -36,7 +37,8 @@ export class AccessUpdateService { timesheets_approval: dto.timesheets_approval, employee_list: dto.employee_list, employee_management: dto.employee_management, - personnal_profile: dto.personnal_profile, + personal_profile: dto.personnal_profile, + dashboard: dto.dashboard, } }) return { success: true, data: true }; @@ -60,8 +62,8 @@ export class AccessUpdateService { timesheets_approval: false, employee_list: false, employee_management: false, - personnal_profile: false, - blocked: true, + personal_profile: false, + dashboard: true, }, });