diff --git a/prisma/migrations/20260108160639_added_banked_hours_to_paid_off_time_and_switched_int_to_decimal/migration.sql b/prisma/migrations/20260108160639_added_banked_hours_to_paid_off_time_and_switched_int_to_decimal/migration.sql new file mode 100644 index 0000000..975613b --- /dev/null +++ b/prisma/migrations/20260108160639_added_banked_hours_to_paid_off_time_and_switched_int_to_decimal/migration.sql @@ -0,0 +1,15 @@ +-- DropIndex +DROP INDEX "public"."schedule_preset_shifts_preset_id_week_day_key"; + +-- DropIndex +DROP INDEX "public"."shifts_timesheet_id_date_start_time_key"; + +-- DropIndex +DROP INDEX "public"."timesheets_employee_id_start_date_key"; + +-- AlterTable +ALTER TABLE "paid_time_off" ADD COLUMN "banked_hours" DECIMAL(65,30) NOT NULL DEFAULT 0, +ALTER COLUMN "vacation_hours" SET DEFAULT 0, +ALTER COLUMN "vacation_hours" SET DATA TYPE DECIMAL(65,30), +ALTER COLUMN "sick_hours" SET DEFAULT 0, +ALTER COLUMN "sick_hours" SET DATA TYPE DECIMAL(65,30); diff --git a/prisma/migrations/20260108163644_ajusted_typing_of_paid_time_off_table/migration.sql b/prisma/migrations/20260108163644_ajusted_typing_of_paid_time_off_table/migration.sql new file mode 100644 index 0000000..ee0388a --- /dev/null +++ b/prisma/migrations/20260108163644_ajusted_typing_of_paid_time_off_table/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - You are about to alter the column `vacation_hours` on the `paid_time_off` table. The data in that column could be lost. The data in that column will be cast from `Decimal(65,30)` to `Decimal(12,2)`. + - You are about to alter the column `sick_hours` on the `paid_time_off` table. The data in that column could be lost. The data in that column will be cast from `Decimal(65,30)` to `Decimal(12,2)`. + - You are about to alter the column `banked_hours` on the `paid_time_off` table. The data in that column could be lost. The data in that column will be cast from `Decimal(65,30)` to `Decimal(12,2)`. + +*/ +-- AlterTable +ALTER TABLE "paid_time_off" ALTER COLUMN "vacation_hours" SET DATA TYPE DECIMAL(12,2), +ALTER COLUMN "sick_hours" SET DATA TYPE DECIMAL(12,2), +ALTER COLUMN "banked_hours" SET DATA TYPE DECIMAL(12,2); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 064a2ab..5a03063 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -325,8 +325,9 @@ model Preferences { model PaidTimeOff { id Int @id @default(autoincrement()) employee_id Int @unique - vacation_hours Int @default(0) - sick_hours Int @default(0) + vacation_hours Decimal @default(0) @db.Decimal(12, 2) + banked_hours Decimal @default(0) @db.Decimal(12, 2) + sick_hours Decimal @default(0) @db.Decimal(12, 2) last_updated DateTime @db.Date employee Employees @relation("EmployeePaidTimeOff", fields: [employee_id], references: [id]) diff --git a/scripts/data/export_employee_table.csv b/scripts/data/export_employee_table.csv index 26a8468..e6d67d9 100644 --- a/scripts/data/export_employee_table.csv +++ b/scripts/data/export_employee_table.csv @@ -2,7 +2,7 @@ "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" +"jonathanlussier1@gmail.com","Jonathan","Soulieres","(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" @@ -10,10 +10,10 @@ "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" +"jayson@targointernet.com","Jayson","Blais-Vallieres","(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" +"joseeannes@targointernet.com","Josee-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" @@ -34,23 +34,23 @@ "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" +"frederique@targointernet.com","Frederique","Soulard","(438) 408 - 6998" +"genevieveb@targointernet.com","Genevieve","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" +"jeremyb@targointernet.com","Jeremy","Blais","(367) 348 - 2276" +"stephane@targointernet.com","Stephane","Moise","(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" +"stephaneb@targointernet.com","Stephane","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" +"mathieug@targointernet.com","Mathieu","Gagne","(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" diff --git a/scripts/data/export_new_employee_table.csv b/scripts/data/export_new_employee_table.csv index 6f8178c..0b37164 100644 --- a/scripts/data/export_new_employee_table.csv +++ b/scripts/data/export_new_employee_table.csv @@ -2,50 +2,50 @@ "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" +"43","jonathanlussier1@gmail.com","Mecanicien",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 +"28","maxim@targointernet.com","Administrateur reseau",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" +"44","jonathans@targointernet.com","Responsable des ressources materielles",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" +"32","david@targointernet.com","Developpeur",271583,False,"1262664000000",NULL +"15","laurence@targointernet.com","Responsable du service a la clientele",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" +"33","jessy@targointernet.com","Administrateur reseau",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" +"35","dahlia@targointernet.com","Agent service a la clientele",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 +"7","Louis@targointernet.com","President",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 +"59","simong@targointernet.com","Mecanicien",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 +"36","robinson@targointernet.com","Service Clientele",271583,False,"1694059200000",NULL +"41","frederique@targointernet.com","commis a la facturation",271583,False,"1724644800000",NULL +"42","genevieveb@targointernet.com","commis a 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 +"26","stephane@targointernet.com","Technicien telecom",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 +"46","chantal.blanchette6@gmail.com","Agente au bien-etre 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 @@ -54,10 +54,10 @@ "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" +"38","mathieu@targointernet.com","Developeur",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 +"56","joseph@targointernet.com","Resp. service clients et operations 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/import-employees-from-csv.ts b/scripts/import-employees-from-csv.ts index 2640344..f31f90d 100644 --- a/scripts/import-employees-from-csv.ts +++ b/scripts/import-employees-from-csv.ts @@ -1,272 +1,272 @@ -// // src/scripts/import-employees-from-csv.ts -// import { PrismaClient, Roles } from '@prisma/client'; -// import * as fs from 'fs'; -// import * as path from 'path'; +// 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(); +const prisma = new PrismaClient(); -// // Chemin vers ton CSV employees -// const CSV_PATH = path.resolve(__dirname, 'data/export_new_employee_table.csv'); +// 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, -// ]; +// 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" -// }; +// 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; -// }; +// Représentation minimale d'un user +type UserSummary = { + id: string; // UUID + email: string; + role: Roles; +}; -// // ============ Helpers CSV ============ +// ============ Helpers CSV ============ -// function splitCsvLine(line: string): string[] { -// const result: string[] = []; -// let current = ''; -// let inQuotes = false; +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]; + 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; -// } -// } + 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()); -// } + result.push(current); + return result.map((v) => v.trim()); +} -// // ============ Helpers de parsing ============ +// ============ 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 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; +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; -// } + 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; +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 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(), -// )); + 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; -// } + return normalized; +} -// // ============ MAIN ============ +// ============ MAIN ============ -// async function main() { -// // 1. Lecture du CSV -// const fileContent = fs.readFileSync(CSV_PATH, 'utf-8'); +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); + 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; -// } + 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 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 = {}; + const csvRows: EmployeeCsvRow[] = dataLines.map((line) => { + const values = splitCsvLine(line); + const row: any = {}; -// header.forEach((col, idx) => { -// row[col] = values[idx] ?? ''; -// }); + header.forEach((col, idx) => { + row[col] = values[idx] ?? ''; + }); -// return row as EmployeeCsvRow; -// }); + return row as EmployeeCsvRow; + }); -// console.log(` ${csvRows.length} lignes trouvées dans le CSV employees`); + 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), -// ), -// ); + // 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`); + 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[]; + // 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`); + 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); -// } + // 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; -// daily_expected_hours: number; -// }[] = []; + // 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; + daily_expected_hours: number; + }[] = []; -// const rowsWithoutUser: EmployeeCsvRow[] = []; -// const rowsWithInvalidNumbers: EmployeeCsvRow[] = []; + const rowsWithoutUser: EmployeeCsvRow[] = []; + const rowsWithInvalidNumbers: EmployeeCsvRow[] = []; -// for (const row of csvRows) { -// const emailKey = row.email.trim().toLowerCase(); -// const user = userByEmail.get(emailKey); + for (const row of csvRows) { + const emailKey = row.email.trim().toLowerCase(); + const user = userByEmail.get(emailKey); -// if (!user) { -// rowsWithoutUser.push(row); -// continue; -// } + 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'); + 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; -// } + 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; + 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( -// `WARNING: Date d'onboarding invalide pour ${row.email} (employee_number=${row.employee_number})`, -// ); -// continue; -// } + if (!first_work_day) { + console.warn( + `WARNING: 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, -// daily_expected_hours: 8, -// job_title, -// is_supervisor, -// supervisor_id: null, // on pourra gérer ça plus tard si tu as les infos -// }); -// } + employeesToCreate.push({ + user_id: user.id, + external_payroll_id, + company_code, + first_work_day, + last_work_day, + daily_expected_hours: 8, + 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`); + 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 (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 (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; -// } + 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 -// }); + // 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`); -// } + 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(); -// }); +main() + .catch((err) => { + console.error(' Erreur pendant l’import CSV → Employees', err); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/scripts/import-users-from-csv.ts b/scripts/import-users-from-csv.ts index 48773be..10b7a02 100644 --- a/scripts/import-users-from-csv.ts +++ b/scripts/import-users-from-csv.ts @@ -1,106 +1,105 @@ -// // src/scripts/import-users-from-csv.ts -// import { PrismaClient } from '@prisma/client'; -// import * as fs from 'fs'; -// import * as path from 'path'; +// src/scripts/import-users-from-csv.ts +import { PrismaClient, Roles } from '@prisma/client'; +import * as fs from 'fs'; +import * as path from 'path'; -// const prisma = new PrismaClient(); +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'); +// ⚙️ Chemin vers ton CSV (à adapter selon où tu le mets) +const CSV_PATH = path.resolve(__dirname, 'data/export_employee_table.csv'); -// // Type aligné sur les colonnes du CSV -// type CsvUserRow = { -// email: string; -// first_name: string; -// last_name: string; -// phone_number: string; -// }; +// Type aligné sur les colonnes du CSV +type CsvUserRow = { + email: string; + first_name: string; + last_name: string; + phone_number: string; +}; -// // Petit parseur de ligne CSV sans dépendance -// function splitCsvLine(line: string): string[] { -// const result: string[] = []; -// let current = ''; -// let inQuotes = false; +// Petit parseur de ligne CSV sans dépendance +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]; + for (let i = 0; i < line.length; i++) { + const char = line[i]; -// if (char === '"') { -// // guillemet échappé "" -// if (inQuotes && line[i + 1] === '"') { -// current += '"'; -// i++; // on saute le deuxième -// } else { -// inQuotes = !inQuotes; -// } -// } else if (char === ',' && !inQuotes) { -// result.push(current); -// current = ''; -// } else { -// current += char; -// } -// } + if (char === '"') { + // guillemet échappé "" + if (inQuotes && line[i + 1] === '"') { + current += '"'; + i++; // on saute le deuxième + } else { + inQuotes = !inQuotes; + } + } else if (char === ',' && !inQuotes) { + result.push(current); + current = ''; + } else { + current += char; + } + } -// result.push(current); -// return result.map((v) => v.trim()); -// } + result.push(current); + return result.map((v) => v.trim()); +} -// async function main() { -// // 1. Lecture du fichier CSV -// const fileContent = fs.readFileSync(CSV_PATH, 'utf-8'); +async function main() { + // 1. Lecture du fichier CSV + const fileContent = fs.readFileSync(CSV_PATH, 'utf-8'); -// const lines = fileContent -// .split(/\r?\n/) -// .map((l) => l.trim()) -// .filter((l) => l.length > 0); + 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; -// } + if (lines.length <= 1) { + console.error('CSV vide ou seulement un header'); + return; + } -// // 2. Header (noms de colonnes) -> ["email", "first_name", "last_name", "phone_number"] -// const header = splitCsvLine(lines[0]); -// const dataLines = lines.slice(1); + // 2. Header (noms de colonnes) -> ["email", "first_name", "last_name", "phone_number"] + const header = splitCsvLine(lines[0]); + const dataLines = lines.slice(1); -// // 3. Conversion de chaque ligne en objet { email, first_name, last_name, phone_number } -// const records: CsvUserRow[] = dataLines.map((line) => { -// const values = splitCsvLine(line); -// const row: any = {}; + // 3. Conversion de chaque ligne en objet { email, first_name, last_name, phone_number } + const records: CsvUserRow[] = dataLines.map((line) => { + const values = splitCsvLine(line); + const row: any = {}; -// header.forEach((col, idx) => { -// row[col] = values[idx] ?? ''; -// }); + header.forEach((col, idx) => { + row[col] = values[idx] ?? ''; + }); -// return row as CsvUserRow; -// }); + return row as CsvUserRow; + }); -// // 4. Mapping vers le format attendu par Prisma (model Users) -// const data = records.map((row) => ({ -// email: row.email.trim(), -// first_name: row.first_name.trim(), -// last_name: row.last_name.trim(), -// phone_number: row.phone_number.trim(), -// // residence: null, -// // role: 'EMPLOYEE', -// })); + // 4. Mapping vers le format attendu par Prisma (model Users) + const data = records.map((row) => ({ + email: row.email.trim(), + first_name: row.first_name.trim(), + last_name: row.last_name.trim(), + phone_number: row.phone_number.trim(), + role: Roles.EMPLOYEE, + // residence: null, + })); -// console.log(`➡️ ${data.length} lignes trouvées dans le CSV`); -// console.log('Exemple importé :', data[0]); + console.log(`➡️ ${data.length} lignes trouvées dans le CSV`); + console.log('Exemple importé :', data[0]); + const result = await prisma.users.createMany({ + data, + }); -// const result = await prisma.users.createMany({ -// data, -// }); + console.log(`✅ ${result.count} utilisateurs insérés dans la DB`); +} -// console.log(`✅ ${result.count} utilisateurs insérés dans la DB`); -// } - -// main() -// .catch((err) => { -// console.error('Erreur pendant l’import CSV → DB', err); -// process.exit(1); -// }) -// .finally(async () => { -// await prisma.$disconnect(); -// }); +main() + .catch((err) => { + console.error('Erreur pendant l’import CSV → DB', err); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/scripts/init-paid-time-off.ts b/scripts/init-paid-time-off.ts new file mode 100644 index 0000000..a1b3fb5 --- /dev/null +++ b/scripts/init-paid-time-off.ts @@ -0,0 +1,21 @@ +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient({}); + +export const initializePaidTimeOff = async () => { + const list_of_employees = await prisma.employees.findMany(); + + console.warn('Start of initialization of paid time off'); + for (let id = 1; id <= list_of_employees.length + 1; id++) { + await prisma.paidTimeOff.create({ + data: { + last_updated: new Date(), + banked_hours: 0, + employee_id: id, + sick_hours: 0, + vacation_hours: 0, + }, + }); + console.log('paid time off initialized for employee ', id); + } +} \ No newline at end of file diff --git a/scripts/init-preferences-access.ts b/scripts/init-preferences-access.ts index 944517e..47e436f 100644 --- a/scripts/init-preferences-access.ts +++ b/scripts/init-preferences-access.ts @@ -1,63 +1,63 @@ -// import { PrismaClient } from "@prisma/client" +import { PrismaClient } from "@prisma/client" -// const prisma = new PrismaClient({}); +const prisma = new PrismaClient({}); -// const admin_list: number[] = [2, 6, 8, 20, 27, 28, 43, 46, 60]; +const admin_list: number[] = [2, 6, 8, 20, 27, 28, 43, 46, 60]; -// export const initializePreferences = async () => { -// console.log('start of preferences and Module Access initialization') -// for (let id = 1; id <= 61; id++) { -// const user = await prisma.employees.findUnique({ -// where: { id }, -// select: { user_id: true }, -// }); -// if (!user) { -// console.log(`user_id for employee ${id} not found`); -// continue; -// } -// console.log(`user_id for employee ${id} found`); +export const initializePreferences = async () => { + console.log('start of preferences and Module Access initialization') + for (let id = 1; id <= 61; id++) { + const user = await prisma.employees.findUnique({ + where: { id }, + select: { user_id: true }, + }); + if (!user) { + console.log(`user_id for employee ${id} not found`); + continue; + } + console.log(`user_id for employee ${id} found`); -// await prisma.preferences.create({ -// data: { -// display_language: 'fr-Fr', -// is_dark_mode: null, -// is_employee_list_grid: false, -// is_lefty_mode: false, -// is_timesheet_approval_grid: false, -// notifications: true, -// user_id: user.user_id, -// }, -// }); -// console.log(`Preferences for employee ${id} initiated`); + await prisma.preferences.create({ + data: { + display_language: 'fr-Fr', + is_dark_mode: null, + is_employee_list_grid: false, + is_lefty_mode: false, + is_timesheet_approval_grid: false, + notifications: true, + user_id: user.user_id, + }, + }); + console.log(`Preferences for employee ${id} initiated`); -// await prisma.userModuleAccess.create({ -// data: { -// user_id: user.user_id, -// dashboard: true, -// employee_list: true, -// employee_management: false, -// personal_profile: true, -// timesheets: true, -// timesheets_approval: false, -// }, -// }); -// console.log(`Module Access for employee ${id} initiated`); + await prisma.userModuleAccess.create({ + data: { + user_id: user.user_id, + dashboard: true, + employee_list: true, + employee_management: false, + personal_profile: true, + timesheets: true, + timesheets_approval: false, + }, + }); + console.log(`Module Access for employee ${id} initiated`); -// if (id in admin_list) { -// console.log(`employee ${id} is and admin`) -// await prisma.userModuleAccess.update({ -// where: { user_id: user.user_id }, -// data: { -// dashboard: true, -// employee_list: true, -// employee_management: true, -// personal_profile: true, -// timesheets: true, -// timesheets_approval: true, -// }, -// }); -// console.log(`Module Access for employee ${id} updated`); -// } -// } -// } + if (id in admin_list) { + console.log(`employee ${id} is and admin`) + await prisma.userModuleAccess.update({ + where: { user_id: user.user_id }, + data: { + dashboard: true, + employee_list: true, + employee_management: true, + personal_profile: true, + timesheets: true, + timesheets_approval: true, + }, + }); + console.log(`Module Access for employee ${id} updated`); + } + } +} diff --git a/scripts/migrate-expenses.ts b/scripts/migrate-expenses.ts index 43bd74b..9b77ca0 100644 --- a/scripts/migrate-expenses.ts +++ b/scripts/migrate-expenses.ts @@ -1,167 +1,167 @@ -// 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 () => { -// const list_of_employees = await prisma.employees.findMany(); -// for (let id = 1; id <= list_of_employees.length+1; id++) { +export const extractOldExpenses = async () => { + const list_of_employees = await prisma.employees.findMany(); + for (let id = 1; id <= list_of_employees.length+1; 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(); -// console.log('finished migrating expenses ***************************'); -// } + } + await prisma_legacy.$disconnect(); + await prisma.$disconnect(); + console.log('finished migrating expenses ***************************'); +} -// 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.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, -// } -// }); -// } -// } + 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 fc24a82..62c57f4 100644 --- a/scripts/migrate-shifts.ts +++ b/scripts/migrate-shifts.ts @@ -1,216 +1,216 @@ -// 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 () => { -// const list_of_employees = await prisma.employees.findMany(); +export const extractOldShifts = async () => { + const list_of_employees = await prisma.employees.findMany(); -// for (let id = 1; id <= list_of_employees.length + 1; id++) { -// console.log(`Start of shift migration ***************************************************************`); -// const new_employee = await findOneNewEmployee(id); -// console.log(`Employee ${id} found in new DB`); + for (let id = 1; id <= list_of_employees.length + 1; 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.length, `new Timesheets found for employee ${id}`); + const new_timesheets = await findManyNewTimesheets(new_employee.id); + console.log(new_timesheets.length, `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(old_timesheets.length, `old timesheets for employee ${new_employee.id} found in old DB`); + const old_timesheets = await findManyOldTimesheets(old_employee_id); + console.log(old_timesheets.length, `old timesheets for employee ${new_employee.id} found in old DB`); -// for (const old_timesheet of old_timesheets) { -// if (!old_timesheet.start_date) { -// console.warn('Start_date of the old_timesheet ', old_timesheet.id, 'invalid, start_date: ', 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) { + console.warn('Start_date of the old_timesheet ', old_timesheet.id, 'invalid, start_date: ', 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[]) => { -// let shifts_count = 0; -// for (const old_shift of old_shifts) { -// let is_remote = true; +const createManyNewShifts = async (timesheet_id: number, old_shifts: OldShifts[]) => { + let shifts_count = 0; + 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 { + 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, -// }, -// }); -// shifts_count++; -// } catch (error) { -// console.log('An error occured during shifts creation', error); -// } + 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, + }, + }); + shifts_count++; + } catch (error) { + console.log('An error occured during shifts creation', error); + } -// } -// console.warn(shifts_count, ' new shifts created'); -// } + } + console.warn(shifts_count, ' new shifts created'); +} -// 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 580148e..5bf4d9f 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 () => { -// const list_of_employees = await prisma_new.employees.findMany(); +export const extractOldTimesheets = async () => { + const list_of_employees = await prisma_new.employees.findMany(); -// for (let id = 1; id <= list_of_employees.length; id++) { -// const new_employee = await findOneNewEmployee(id); + for (let id = 1; id <= list_of_employees.length; id++) { + const new_employee = await findOneNewEmployee(id); -// const old_employee_id = await findOneOldEmployee(new_employee); + const old_employee_id = await findOneOldEmployee(new_employee); -// const old_timesheets = await findManyOldTimesheets(old_employee_id); + const old_timesheets = await findManyOldTimesheets(old_employee_id); -// await createManyNewTimesheets(old_timesheets, new_employee); -// console.log(`${old_timesheets.length} New Timesheets created in new DB for employee ${new_employee.id}`); -// } + 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(); -// console.log('migration of timesheets finished') -// } + await prisma_legacy.$disconnect(); + await prisma_new.$disconnect(); + console.log('migration of timesheets finished') +} -// 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) => { -// console.log('trying to find old timesheets ...'); -// const old_timesheets = await prisma_legacy.time_sheets.findMany({ -// where: { employee_id: old_employee_id }, -// select: { id: true, start_date: true, status: true } -// }); -// console.log(old_timesheets.length, 'old timesheets found') -// 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) => { + console.log('trying to find old timesheets ...'); + const old_timesheets = await prisma_legacy.time_sheets.findMany({ + where: { employee_id: old_employee_id }, + select: { id: true, start_date: true, status: true } + }); + console.log(old_timesheets.length, 'old timesheets found') + 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) => { -// console.log(old_timesheets.length, ' timesheets ready for creation') -// 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) => { + console.log(old_timesheets.length, ' timesheets ready for creation') + 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 { -// console.log(`Timesheet with start_date: ${toStringFromDate(timesheet.start_date!)} for employee ${new_employee.id} created`) -// const new_timesheet = await prisma_new.timesheets.create({ -// data: { -// 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', error); -// } -// } -// } + try { + console.log(`Timesheet with start_date: ${toStringFromDate(timesheet.start_date!)} for employee ${new_employee.id} created`) + const new_timesheet = await prisma_new.timesheets.create({ + data: { + 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', error); + } + } +} -// // 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/src/main.ts b/src/main.ts index 37ef124..672aba0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,9 +14,10 @@ import * as session from 'express-session'; import * as passport from 'passport'; import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaSessionStore } from '@quixo3/prisma-session-store'; +// import { initializePaidTimeOff } from 'scripts/init-paid-time-off'; // import { initializePreferences } from 'scripts/init-preferences-access'; -// import { extractOldShifts } from 'scripts/migrate-shifts'; // import { extractOldTimesheets } from 'scripts/migrate-timesheets'; +// import { extractOldShifts } from 'scripts/migrate-shifts'; // import { extractOldExpenses } from 'scripts/migrate-expenses'; const SESSION_TOKEN_DURATION_MINUTES = 180 @@ -93,9 +94,10 @@ async function bootstrap() { // migration function calls + // await initializePaidTimeOff(); + // await initializePreferences(); // await extractOldTimesheets(); // await extractOldShifts(); // await extractOldExpenses(); - // await initializePreferences(); } bootstrap(); diff --git a/src/time-and-attendance/domains/services/sick-leave.service.ts b/src/time-and-attendance/domains/services/sick-leave.service.ts index f78cc31..7bf8f4c 100644 --- a/src/time-and-attendance/domains/services/sick-leave.service.ts +++ b/src/time-and-attendance/domains/services/sick-leave.service.ts @@ -81,6 +81,7 @@ export class SickLeaveService { employee_id: true, sick_hours: true, vacation_hours: true, + banked_hours: true, last_updated: true, } }); diff --git a/src/time-and-attendance/domains/services/vacation.service.ts b/src/time-and-attendance/domains/services/vacation.service.ts index 922948a..f6f2a49 100644 --- a/src/time-and-attendance/domains/services/vacation.service.ts +++ b/src/time-and-attendance/domains/services/vacation.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger, NotFoundException } from "@nestjs/common"; +import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common"; import { Result } from "src/common/errors/result-error.factory"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { PrismaService } from "src/prisma/prisma.service"; @@ -8,26 +8,26 @@ export class VacationService { constructor( private readonly prisma: PrismaService, private readonly emailResolver: EmailToIdResolver, - ) {} + ) { } //switch employeeId for email async calculateVacationPay(email: string, start_date: Date, days_requested: number, modifier: number): Promise> { const employee_id = await this.emailResolver.findIdByEmail(email); - if(!employee_id.success) return { success: false, error: employee_id.error} + if (!employee_id.success) return { success: false, error: employee_id.error } //fetch hiring date - const employee = await this.prisma.employees.findUnique({ + const employee = await this.prisma.employees.findUnique({ where: { id: employee_id.data }, - select: { first_work_day: true }, + select: { first_work_day: true }, }); - if(!employee) return { success:false, error:`Employee #${employee_id} not found`} + if (!employee) return { success: false, error: `Employee #${employee_id} not found` } const hire_date = employee.first_work_day; //sets "year" to may 1st to april 30th //check if hiring date is in may or later, we use hiring year, otherwise we use the year before - const year_of_request = start_date.getMonth() >= 4 - ? start_date.getFullYear() : start_date.getFullYear() -1; + const year_of_request = start_date.getMonth() >= 4 + ? start_date.getFullYear() : start_date.getFullYear() - 1; const period_start = new Date(year_of_request, 4, 1); //may = 4 const period_end = new Date(year_of_request + 1, 4, 0); //day 0 of may == april 30th @@ -37,25 +37,25 @@ export class VacationService { const anniversary_date = new Date(hire_date); anniversary_date.setFullYear(anniversary_date.getFullYear() + years); return anniversary_date; - }).filter(d => d>= period_start && d <= period_end).sort((a,b) => a.getTime() - b.getTime()); + }).filter(d => d >= period_start && d <= period_end).sort((a, b) => a.getTime() - b.getTime()); - const boundaries = [period_start, ...anniversaries,period_end]; + const boundaries = [period_start, ...anniversaries, period_end]; //calculate prorata per segment let total_vacation_days = 0; const ms_per_day = 1000 * 60 * 60 * 24; - for (let i = 0; i < boundaries.length -1; i++) { + for (let i = 0; i < boundaries.length - 1; i++) { const segment_start = boundaries[i]; - const segment_end = boundaries[i+1]; + const segment_end = boundaries[i + 1]; //number of days in said segment - const days_in_segment = Math.round((segment_end.getTime() - segment_start.getTime())/ ms_per_day); - const years_since_hire = (segment_start.getFullYear() - hire_date.getFullYear()) - - (segment_start < new Date(segment_start.getFullYear(), hire_date.getMonth()) ? 1 : 0); + const days_in_segment = Math.round((segment_end.getTime() - segment_start.getTime()) / ms_per_day); + const years_since_hire = (segment_start.getFullYear() - hire_date.getFullYear()) - + (segment_start < new Date(segment_start.getFullYear(), hire_date.getMonth()) ? 1 : 0); let alloc_days: number; - if(years_since_hire < 5) alloc_days = 10; - else if(years_since_hire < 10) alloc_days = 15; - else if(years_since_hire < 15) alloc_days = 20; + if (years_since_hire < 5) alloc_days = 10; + else if (years_since_hire < 10) alloc_days = 15; + else if (years_since_hire < 15) alloc_days = 20; else alloc_days = 25; //prorata for said segment @@ -69,9 +69,49 @@ export class VacationService { const raw_hours = payable_days * 8 * modifier; const rounded_hours = Math.round(raw_hours * 4) / 4; - return {success:true, data:rounded_hours}; + return { success: true, data: rounded_hours }; + } + + //updates hours in vacation bank. + manageVacationHoursBank = async (employee_id: number, asked_hours: number): Promise> => { + if (asked_hours <= 0) return { success: false, error: 'INVALID_VACATION_SHIFT' }; + + try { + const result = await this.prisma.$transaction(async (tx) => { + //checks for remaining hours in vacation bank + const employee = await this.prisma.employees.findUnique({ + where: { id: employee_id }, + select: { paid_time_off: { select: { vacation_hours: true } } }, + }); + if (!employee) { + return { success: false, error: 'EMPLOYEE_NOT_FOUND' } as Result + } + if (!employee.paid_time_off) { + return { success: false, error: 'VACATION_HOURS_BANK_NOT_FOUND' } as Result + } + const vacation_bank = employee.paid_time_off.vacation_hours.toNumber() + + //check if remaining hours are less than asked and return maximum available hours + if (asked_hours > vacation_bank) { + return { success: true, data: vacation_bank } as Result + } else { + //update vacation_bank + await this.prisma.paidTimeOff.update({ + where: { employee_id: employee_id, vacation_hours: { gte: asked_hours } }, + data: { + vacation_hours: { decrement: asked_hours }, + }, + }); + //returns asked hours if enough hours are left in the bank + return { success: true, data: asked_hours } as Result; + } + }); + return result; + } catch (error) { + return { success: false, error: 'INVALID_VACATION_SHIFT' } + } } - + } \ No newline at end of file diff --git a/src/time-and-attendance/schedule-presets/schedule-presets.module.ts b/src/time-and-attendance/schedule-presets/schedule-presets.module.ts index 531c5e3..9cbf8bc 100644 --- a/src/time-and-attendance/schedule-presets/schedule-presets.module.ts +++ b/src/time-and-attendance/schedule-presets/schedule-presets.module.ts @@ -10,6 +10,7 @@ import { SchedulePresetDeleteService } from "src/time-and-attendance/schedule-pr import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service"; +import { VacationService } from "src/time-and-attendance/domains/services/vacation.service"; @@ -24,6 +25,7 @@ import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shi EmailToIdResolver, ShiftsCreateService, BankCodesResolver, + VacationService, ], exports: [ SchedulePresetsGetService, diff --git a/src/time-and-attendance/shifts/services/shifts-create.service.ts b/src/time-and-attendance/shifts/services/shifts-create.service.ts index a32ffa7..8275c5b 100644 --- a/src/time-and-attendance/shifts/services/shifts-create.service.ts +++ b/src/time-and-attendance/shifts/services/shifts-create.service.ts @@ -5,8 +5,9 @@ import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { PrismaService } from "src/prisma/prisma.service"; import { Result } from "src/common/errors/result-error.factory"; -import { toStringFromHHmm, toStringFromDate, toDateFromString, overlaps, toDateFromHHmm } from "src/common/utils/date-utils"; +import { toStringFromHHmm, toStringFromDate, toDateFromString, overlaps, toDateFromHHmm, computeHours } from "src/common/utils/date-utils"; import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto"; +import { VacationService } from "src/time-and-attendance/domains/services/vacation.service"; @Injectable() export class ShiftsCreateService { @@ -14,6 +15,7 @@ export class ShiftsCreateService { private readonly prisma: PrismaService, private readonly emailResolver: EmailToIdResolver, private readonly typeResolver: BankCodesResolver, + private readonly vacationService: VacationService, ) { } //_________________________________________________________________ @@ -93,6 +95,13 @@ export class ShiftsCreateService { return { success: false, error: `SHIFT_OVERLAP` }; } } + //api call to validate available hours in vacation_bank and ajust end_time accordingly + if (dto.type === 'VACATION') { + const asked_hours = computeHours(toDateFromString(dto.start_time), toDateFromString(dto.end_time)); + const vacation_shift = await this.vacationService.manageVacationHoursBank(employee_id, asked_hours) + if (!vacation_shift.success) return { success: false, error: vacation_shift.error }; + dto.end_time = this.addHourstoDateString(dto.start_time, vacation_shift.data); + } //sends data for creation of a shift in db const created_shift = await this.prisma.shifts.create({ @@ -140,4 +149,12 @@ export class ShiftsCreateService { return { success: true, data: { date, start_time, end_time, bank_code_id: bank_code_id.data } }; } + + private addHourstoDateString = (start_time: string, hours: number): string => { + const start = toDateFromHHmm(start_time); + const end = new Date(start.getTime() + hours * 60 * 60 * 1000); + const hh = String(end.getUTCHours()).padStart(2, '0'); + const mm = String(end.getUTCMinutes()).padStart(2, '0'); + return `${hh}:${mm}:00`; + } } diff --git a/src/time-and-attendance/shifts/shifts.module.ts b/src/time-and-attendance/shifts/shifts.module.ts index 3628434..f5eb148 100644 --- a/src/time-and-attendance/shifts/shifts.module.ts +++ b/src/time-and-attendance/shifts/shifts.module.ts @@ -5,10 +5,11 @@ import { ShiftController } from 'src/time-and-attendance/shifts/shift.controller import { ShiftsCreateService } from 'src/time-and-attendance/shifts/services/shifts-create.service'; import { ShiftsDeleteService } from 'src/time-and-attendance/shifts/services/shifts-delete.service'; import { ShiftsUpdateService } from 'src/time-and-attendance/shifts/services/shifts-update-delete.service'; +import { VacationService } from 'src/time-and-attendance/domains/services/vacation.service'; @Module({ controllers: [ShiftController], - providers: [ShiftsCreateService, ShiftsUpdateService, ShiftsDeleteService], + providers: [ShiftsCreateService, ShiftsUpdateService, ShiftsDeleteService, VacationService], exports: [ShiftsCreateService, ShiftsUpdateService, ShiftsDeleteService], }) export class ShiftsModule { } diff --git a/src/time-and-attendance/time-and-attendance.module.ts b/src/time-and-attendance/time-and-attendance.module.ts index 36fb698..171664c 100644 --- a/src/time-and-attendance/time-and-attendance.module.ts +++ b/src/time-and-attendance/time-and-attendance.module.ts @@ -39,6 +39,7 @@ import { SchedulePresetUpdateService } from "src/time-and-attendance/schedule-pr import { SchedulePresetsCreateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-create.service"; import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service"; import { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.service"; +import { VacationService } from "src/time-and-attendance/domains/services/vacation.service"; @Module({ imports: [ @@ -80,6 +81,7 @@ import { CsvGeneratorService } from "src/time-and-attendance/exports/services/cs PayPeriodsCommandService, CsvExportService, CsvGeneratorService, + VacationService, ], exports: [TimesheetApprovalService], }) export class TimeAndAttendanceModule { }; \ No newline at end of file