feat(paid_time-off): worked on vacation service, implemented vacation business logic inside shifts creation and did a migration

This commit is contained in:
Matthieu Haineault 2026-01-08 12:13:14 -05:00
parent bae8a037f7
commit 5b778943a1
19 changed files with 970 additions and 857 deletions

View File

@ -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);

View File

@ -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);

View File

@ -325,8 +325,9 @@ model Preferences {
model PaidTimeOff { model PaidTimeOff {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
employee_id Int @unique employee_id Int @unique
vacation_hours Int @default(0) vacation_hours Decimal @default(0) @db.Decimal(12, 2)
sick_hours Int @default(0) banked_hours Decimal @default(0) @db.Decimal(12, 2)
sick_hours Decimal @default(0) @db.Decimal(12, 2)
last_updated DateTime @db.Date last_updated DateTime @db.Date
employee Employees @relation("EmployeePaidTimeOff", fields: [employee_id], references: [id]) employee Employees @relation("EmployeePaidTimeOff", fields: [employee_id], references: [id])

View File

@ -2,7 +2,7 @@
"mickaelp@targointernet.com","Mickael","Poulin","(514) 825 - 9730" "mickaelp@targointernet.com","Mickael","Poulin","(514) 825 - 9730"
"simon@targointernet.com","Simon","Clot-Gagnon","(514) 894 - 3632" "simon@targointernet.com","Simon","Clot-Gagnon","(514) 894 - 3632"
"eric@targointernet.com","Eric","Wilson","(438) 229 - 4086" "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" "Carmen@targointernet.com","Carmen","Ouellet","(514) 448 - 0773"
"patrickm@targointernet.com","Patrick","Moise","(514) 949 - 3082" "patrickm@targointernet.com","Patrick","Moise","(514) 949 - 3082"
"malika@targointernet.com","Malika","Charest","(819) 609 - 3894" "malika@targointernet.com","Malika","Charest","(819) 609 - 3894"
@ -10,10 +10,10 @@
"maxim@targointernet.com","Maxim","Murray Gendron","(514) 705 - 8208" "maxim@targointernet.com","Maxim","Murray Gendron","(514) 705 - 8208"
"philips@targointernet.com","Philip","St-Amour","(450) 601 - 8112" "philips@targointernet.com","Philip","St-Amour","(450) 601 - 8112"
"jeanpierre@targointernet.com","Jean-Pierre","Bourdon","(514) 863 - 7414" "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" "jonathans@targointernet.com","Jonathan","Soulard","(514) 714 - 2647"
"louism@targointernet.com","Louis","Morneau","(514) 715 - 1182" "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" "louisgs@targointernet.com","Louis Gabriel","Soulard","(438) 528 - 4486"
"david@targointernet.com","David","Richer","(514) 448 - 0773" "david@targointernet.com","David","Richer","(514) 448 - 0773"
"laurence@targointernet.com","Laurence","Gobeil","(438) 884 - 7125" "laurence@targointernet.com","Laurence","Gobeil","(438) 884 - 7125"
@ -34,23 +34,23 @@
"simong@targointernet.com","Simon","Goyette","(873) 662 - 4408" "simong@targointernet.com","Simon","Goyette","(873) 662 - 4408"
"patrick@targointernet.com","Patrick","Doucet","(514) 919 - 7102" "patrick@targointernet.com","Patrick","Doucet","(514) 919 - 7102"
"robinson@targointernet.com","Robinson","Viaud","(514) 649 - 3063" "robinson@targointernet.com","Robinson","Viaud","(514) 649 - 3063"
"frederique@targointernet.com","Frédérique","Soulard","(438) 408 - 6998" "frederique@targointernet.com","Frederique","Soulard","(438) 408 - 6998"
"genevieveb@targointernet.com","Geneviève","Bourdon","(514) 607 - 7084" "genevieveb@targointernet.com","Genevieve","Bourdon","(514) 607 - 7084"
"philippe@targointernet.com","Philippe","Bourdon","(438) 529 - 1940" "philippe@targointernet.com","Philippe","Bourdon","(438) 529 - 1940"
"aurelieb@targointernet.com","Aurelie","Bourdon","(438) 357 - 5392" "aurelieb@targointernet.com","Aurelie","Bourdon","(438) 357 - 5392"
"tommy@targointernet.com","Tommy","Larouche Dionne","(514) 949 - 0762" "tommy@targointernet.com","Tommy","Larouche Dionne","(514) 949 - 0762"
"jeremyb@targointernet.com","Jérémy","Blais","(367) 348 - 2276" "jeremyb@targointernet.com","Jeremy","Blais","(367) 348 - 2276"
"stephane@targointernet.com","Stéphane","Moïse","(514) 949 - 0587" "stephane@targointernet.com","Stephane","Moise","(514) 949 - 0587"
"thaiz@targointernet.com","Thaiz ","Menezes Costa","(514) 915 - 0959" "thaiz@targointernet.com","Thaiz ","Menezes Costa","(514) 915 - 0959"
"frederick@targointernet.com","Frederick","Pruneau","(514) 949 - 0340" "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" "kadi@targointernet.com","Kadi","Nongtodbo","(514) 621 - 2762"
"chantal.blanchette6@gmail.com","Chantal","Blanchette","(514) 889 - 8004" "chantal.blanchette6@gmail.com","Chantal","Blanchette","(514) 889 - 8004"
"dominiquel@targointernet.com","Dominique","Liaud","(514) 949 - 0230" "dominiquel@targointernet.com","Dominique","Liaud","(514) 949 - 0230"
"brandonf@targointernet.com","Brandon","Fortin","(438) 828 - 4732" "brandonf@targointernet.com","Brandon","Fortin","(438) 828 - 4732"
"nicolasd@targointernet.com","Nicolas","Drolet","(514) 951 - 4750" "nicolasd@targointernet.com","Nicolas","Drolet","(514) 951 - 4750"
"matthieuh@targointernet.com","Matthieu","Haineault Gervais","(514) 708 - 2974" "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" "marcantoineg@targointernet.com","Marc-Antoine","Girard","(514) 641 - 7676"
"djanpoub@targointernet.com","Benjamin (a suprimmer)","Djanpou","(514) 663 - 9890" "djanpoub@targointernet.com","Benjamin (a suprimmer)","Djanpou","(514) 663 - 9890"
"samuel@targointernet.com","Samuel","Rolo","(514) 578 - 0324" "samuel@targointernet.com","Samuel","Rolo","(514) 578 - 0324"

1 email first_name last_name phone_number
2 mickaelp@targointernet.com Mickael Poulin (514) 825 - 9730
3 simon@targointernet.com Simon Clot-Gagnon (514) 894 - 3632
4 eric@targointernet.com Eric Wilson (438) 229 - 4086
5 jonathanlussier1@gmail.com Jonathan Soulières Soulieres (514) 461 - 3716
6 Carmen@targointernet.com Carmen Ouellet (514) 448 - 0773
7 patrickm@targointernet.com Patrick Moise (514) 949 - 3082
8 malika@targointernet.com Malika Charest (819) 609 - 3894
10 maxim@targointernet.com Maxim Murray Gendron (514) 705 - 8208
11 philips@targointernet.com Philip St-Amour (450) 601 - 8112
12 jeanpierre@targointernet.com Jean-Pierre Bourdon (514) 863 - 7414
13 jayson@targointernet.com Jayson Blais-Vallières Blais-Vallieres (438) 390 - 3679
14 jonathans@targointernet.com Jonathan Soulard (514) 714 - 2647
15 louism@targointernet.com Louis Morneau (514) 715 - 1182
16 joseeannes@targointernet.com Josée-Anne Josee-Anne Soulard (438) 410 - 1779
17 louisgs@targointernet.com Louis Gabriel Soulard (438) 528 - 4486
18 david@targointernet.com David Richer (514) 448 - 0773
19 laurence@targointernet.com Laurence Gobeil (438) 884 - 7125
34 simong@targointernet.com Simon Goyette (873) 662 - 4408
35 patrick@targointernet.com Patrick Doucet (514) 919 - 7102
36 robinson@targointernet.com Robinson Viaud (514) 649 - 3063
37 frederique@targointernet.com Frédérique Frederique Soulard (438) 408 - 6998
38 genevieveb@targointernet.com Geneviève Genevieve Bourdon (514) 607 - 7084
39 philippe@targointernet.com Philippe Bourdon (438) 529 - 1940
40 aurelieb@targointernet.com Aurelie Bourdon (438) 357 - 5392
41 tommy@targointernet.com Tommy Larouche Dionne (514) 949 - 0762
42 jeremyb@targointernet.com Jérémy Jeremy Blais (367) 348 - 2276
43 stephane@targointernet.com Stéphane Stephane Moïse Moise (514) 949 - 0587
44 thaiz@targointernet.com Thaiz Menezes Costa (514) 915 - 0959
45 frederick@targointernet.com Frederick Pruneau (514) 949 - 0340
46 stephaneb@targointernet.com Stéphane Stephane Beaudoin (438) 524 - 2556
47 kadi@targointernet.com Kadi Nongtodbo (514) 621 - 2762
48 chantal.blanchette6@gmail.com Chantal Blanchette (514) 889 - 8004
49 dominiquel@targointernet.com Dominique Liaud (514) 949 - 0230
50 brandonf@targointernet.com Brandon Fortin (438) 828 - 4732
51 nicolasd@targointernet.com Nicolas Drolet (514) 951 - 4750
52 matthieuh@targointernet.com Matthieu Haineault Gervais (514) 708 - 2974
53 mathieug@targointernet.com Mathieu Gagné Gagne (514) 448 - 0773
54 marcantoineg@targointernet.com Marc-Antoine Girard (514) 641 - 7676
55 djanpoub@targointernet.com Benjamin (a suprimmer) Djanpou (514) 663 - 9890
56 samuel@targointernet.com Samuel Rolo (514) 578 - 0324

View File

@ -2,50 +2,50 @@
"49","mickaelp@targointernet.com","Fusionneur",271585,False,"1656907200000","1724990400000" "49","mickaelp@targointernet.com","Fusionneur",271585,False,"1656907200000","1724990400000"
"54","simon@targointernet.com","Fusionneur",271585,True,"1566446400000",NULL "54","simon@targointernet.com","Fusionneur",271585,True,"1566446400000",NULL
"39","eric@targointernet.com","Fusionneur",271585,False,"1531886400000","1724990400000" "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 "29","Carmen@targointernet.com","Responsable aux comptes payables",271583,False,"1181880000000",NULL
"25","patrickm@targointernet.com","Technicien",271583,True,"1249358400000",NULL "25","patrickm@targointernet.com","Technicien",271583,True,"1249358400000",NULL
"10","malika@targointernet.com","Compte Recevable",271583,False,"1664856000000","1723176000000" "10","malika@targointernet.com","Compte Recevable",271583,False,"1664856000000","1723176000000"
"18","marcandre@targointernet.com","Support technique -senior",271583,True,"1497326400000",NULL "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 "51","philips@targointernet.com","Fusionneur",271585,False,"1656907200000",NULL
"58","jeanpierre@targointernet.com","Technicien",271585,False,"1548216000000",NULL "58","jeanpierre@targointernet.com","Technicien",271585,False,"1548216000000",NULL
"4","jayson@targointernet.com","Monteur",271583,False,"1564977600000","1720756800000" "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 "27","louism@targointernet.com","Sysadmin",271583,False,"1392091200000",NULL
"45","joseeannes@targointernet.com","Technicien",271585,False,"1665979200000",NULL "45","joseeannes@targointernet.com","Technicien",271585,False,"1665979200000",NULL
"46","louisgs@targointernet.com","Technicien",271585,False,"1661745600000","1723176000000" "46","louisgs@targointernet.com","Technicien",271585,False,"1661745600000","1723176000000"
"32","david@targointernet.com","Développeur",271583,False,"1262664000000",NULL "32","david@targointernet.com","Developpeur",271583,False,"1262664000000",NULL
"15","laurence@targointernet.com","Responsable du service à la clientèle",271583,True,"1635739200000","1725595200000" "15","laurence@targointernet.com","Responsable du service a la clientele",271583,True,"1635739200000","1725595200000"
"37","antoinewg@targointernet.com","Concepteur",271585,False,"1643688000000",NULL "37","antoinewg@targointernet.com","Concepteur",271585,False,"1643688000000",NULL
"5","michel@targointernet.com","netadmin / sysadmin",271583,True,"1203393600000",NULL "5","michel@targointernet.com","netadmin / sysadmin",271583,True,"1203393600000",NULL
"42","gilles@targointernet.com","Fusionneur",271585,False,"1357531200000",NULL "42","gilles@targointernet.com","Fusionneur",271585,False,"1357531200000",NULL
"2","sofiane@targointernet.com","developpeur web",271583,False,"1696219200000","1728014400000" "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 "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" "31","mathys.renaud00@gmail.com","Manoeuvre",271583,False,"1688529600000","1724472000000"
"53","pierre@targointernet.com","gestionnaire de projet",271585,True,"1557374400000",NULL "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" "49","paulr@targointernet.com","Netadmin",271583,False,"1747022400000","1747800000000"
"50","benjamin@targointernet.com","Technicien",271583,False,"1747022400000",NULL "50","benjamin@targointernet.com","Technicien",271583,False,"1747022400000",NULL
"50","nathanb@targointernet.com","Technicien",271585,False,"1653278400000",NULL "50","nathanb@targointernet.com","Technicien",271585,False,"1653278400000",NULL
"55","sylvain@targointernet.com","Fusionneur",271585,False,"1661140800000","1724385600000" "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 "13","patrick@targointernet.com","Support Technique",271583,False,"1527652800000",NULL
"36","robinson@targointernet.com","Service Clientèle",271583,False,"1694059200000",NULL "36","robinson@targointernet.com","Service Clientele",271583,False,"1694059200000",NULL
"41","frederique@targointernet.com","commis à la facturation",271583,False,"1724644800000",NULL "41","frederique@targointernet.com","commis a la facturation",271583,False,"1724644800000",NULL
"42","genevieveb@targointernet.com","commis à la facturation",271583,False,"1726459200000",NULL "42","genevieveb@targointernet.com","commis a la facturation",271583,False,"1726459200000",NULL
"52","philippe@targointernet.com","Fusionneur",271585,False,"1637640000000",NULL "52","philippe@targointernet.com","Fusionneur",271585,False,"1637640000000",NULL
"6","aurelieb@targointernet.com","Technicien telecom",271583,False,"1652068800000",NULL "6","aurelieb@targointernet.com","Technicien telecom",271583,False,"1652068800000",NULL
"20","tommy@targointernet.com","Technicien",271583,False,"1462766400000",NULL "20","tommy@targointernet.com","Technicien",271583,False,"1462766400000",NULL
"39","jeremyb@targointernet.com","Tech/Installateur ",271583,False,"1721016000000","1724990400000" "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 "24","thaiz@targointernet.com","Agente organisationnel",271583,False,"1539316800000",NULL
"30","frederick@targointernet.com","Net/Sys/VoIP admin",271583,False,"1295323200000",NULL "30","frederick@targointernet.com","Net/Sys/VoIP admin",271583,False,"1295323200000",NULL
"37","stephaneb@targointernet.com","Magasinier",271583,False,"1716955200000",NULL "37","stephaneb@targointernet.com","Magasinier",271583,False,"1716955200000",NULL
"43","kadi@targointernet.com","RH et vente",271583,True,"1726459200000",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 "48","dominiquel@targointernet.com","Vendeur",271583,False,"1746417600000",NULL
"45","brandonf@targointernet.com","Technicien",271583,False,"1731556800000","1744948800000" "45","brandonf@targointernet.com","Technicien",271583,False,"1731556800000","1744948800000"
"52","nicolasd@targointernet.com","Programmeur",271583,False,"1748923200000",NULL "52","nicolasd@targointernet.com","Programmeur",271583,False,"1748923200000",NULL
@ -54,10 +54,10 @@
"47","marcantoineg@targointernet.com","Vendeur",271583,False,"1737950400000","1743048000000" "47","marcantoineg@targointernet.com","Vendeur",271583,False,"1737950400000","1743048000000"
"50","djanpoub@targointernet.com","Technicien",271583,False,"1747713600000","1747800000000" "50","djanpoub@targointernet.com","Technicien",271583,False,"1747713600000","1747800000000"
"53","samuel@targointernet.com","Technicien",271583,False,"1749614400000","1749614400000" "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" "54","alexandreg@targointernet.com","support",271583,False,"1750651200000","1757649600000"
"19","fanny@targointernet.com","Comptable",271583,False,"1751860800000",NULL "19","fanny@targointernet.com","Comptable",271583,False,"1751860800000",NULL
"53","samuelr@targointernet.com","Technicien",271583,False,"1749614400000","1749614400000" "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 "55","nathanm@targointernet.com","Technicien",271583,False,"1751860800000",NULL
"58","lion@targointernet.com","Programmeur",271583,False,"1758513600000",NULL "58","lion@targointernet.com","Programmeur",271583,False,"1758513600000",NULL

1 employee_number email job_title company is_supervisor onboarding offboarding
2 49 mickaelp@targointernet.com Fusionneur 271585 False 1656907200000 1724990400000
3 54 simon@targointernet.com Fusionneur 271585 True 1566446400000 NULL
4 39 eric@targointernet.com Fusionneur 271585 False 1531886400000 1724990400000
5 43 jonathanlussier1@gmail.com Mécanicien Mecanicien 271585 False 1679284800000 1728014400000
6 29 Carmen@targointernet.com Responsable aux comptes payables 271583 False 1181880000000 NULL
7 25 patrickm@targointernet.com Technicien 271583 True 1249358400000 NULL
8 10 malika@targointernet.com Compte Recevable 271583 False 1664856000000 1723176000000
9 18 marcandre@targointernet.com Support technique -senior 271583 True 1497326400000 NULL
10 28 maxim@targointernet.com Administrateur réseau Administrateur reseau 271583 False 1659931200000 NULL
11 51 philips@targointernet.com Fusionneur 271585 False 1656907200000 NULL
12 58 jeanpierre@targointernet.com Technicien 271585 False 1548216000000 NULL
13 4 jayson@targointernet.com Monteur 271583 False 1564977600000 1720756800000
14 44 jonathans@targointernet.com Responsable des ressources matérielles Responsable des ressources materielles 271585 False 1664164800000 1717473600000
15 27 louism@targointernet.com Sysadmin 271583 False 1392091200000 NULL
16 45 joseeannes@targointernet.com Technicien 271585 False 1665979200000 NULL
17 46 louisgs@targointernet.com Technicien 271585 False 1661745600000 1723176000000
18 32 david@targointernet.com Développeur Developpeur 271583 False 1262664000000 NULL
19 15 laurence@targointernet.com Responsable du service à la clientèle Responsable du service a la clientele 271583 True 1635739200000 1725595200000
20 37 antoinewg@targointernet.com Concepteur 271585 False 1643688000000 NULL
21 5 michel@targointernet.com netadmin / sysadmin 271583 True 1203393600000 NULL
22 42 gilles@targointernet.com Fusionneur 271585 False 1357531200000 NULL
23 2 sofiane@targointernet.com developpeur web 271583 False 1696219200000 1728014400000
24 33 jessy@targointernet.com Administrateur réseau Administrateur reseau 271583 False 1679889600000 1755230400000
25 60 anthonyd@targointernet.com Fusionneur 271585 False 1713844800000 NULL
26 35 dahlia@targointernet.com Agent service à la clientèle Agent service a la clientele 271583 False 1684900800000 1734667200000
27 31 mathys.renaud00@gmail.com Manoeuvre 271583 False 1688529600000 1724472000000
28 53 pierre@targointernet.com gestionnaire de projet 271585 True 1557374400000 NULL
29 7 Louis@targointernet.com Président President 271583 False 1104638400000 NULL
30 49 paulr@targointernet.com Netadmin 271583 False 1747022400000 1747800000000
31 50 benjamin@targointernet.com Technicien 271583 False 1747022400000 NULL
32 50 nathanb@targointernet.com Technicien 271585 False 1653278400000 NULL
33 55 sylvain@targointernet.com Fusionneur 271585 False 1661140800000 1724385600000
34 59 simong@targointernet.com Mécanicien Mecanicien 271585 False 1712203200000 NULL
35 13 patrick@targointernet.com Support Technique 271583 False 1527652800000 NULL
36 36 robinson@targointernet.com Service Clientèle Service Clientele 271583 False 1694059200000 NULL
37 41 frederique@targointernet.com commis à la facturation commis a la facturation 271583 False 1724644800000 NULL
38 42 genevieveb@targointernet.com commis à la facturation commis a la facturation 271583 False 1726459200000 NULL
39 52 philippe@targointernet.com Fusionneur 271585 False 1637640000000 NULL
40 6 aurelieb@targointernet.com Technicien telecom 271583 False 1652068800000 NULL
41 20 tommy@targointernet.com Technicien 271583 False 1462766400000 NULL
42 39 jeremyb@targointernet.com Tech/Installateur 271583 False 1721016000000 1724990400000
43 26 stephane@targointernet.com Technicien télécom Technicien telecom 271583 False 1260936000000 NULL
44 24 thaiz@targointernet.com Agente organisationnel 271583 False 1539316800000 NULL
45 30 frederick@targointernet.com Net/Sys/VoIP admin 271583 False 1295323200000 NULL
46 37 stephaneb@targointernet.com Magasinier 271583 False 1716955200000 NULL
47 43 kadi@targointernet.com RH et vente 271583 True 1726459200000 NULL
48 46 chantal.blanchette6@gmail.com Agente au bien-être et service interne Agente au bien-etre et service interne 271583 False 1732507200000 NULL
49 48 dominiquel@targointernet.com Vendeur 271583 False 1746417600000 NULL
50 45 brandonf@targointernet.com Technicien 271583 False 1731556800000 1744948800000
51 52 nicolasd@targointernet.com Programmeur 271583 False 1748923200000 NULL
54 47 marcantoineg@targointernet.com Vendeur 271583 False 1737950400000 1743048000000
55 50 djanpoub@targointernet.com Technicien 271583 False 1747713600000 1747800000000
56 53 samuel@targointernet.com Technicien 271583 False 1749614400000 1749614400000
57 38 mathieu@targointernet.com Dévelopeur Developeur 271583 False 1719288000000 1750305600000
58 54 alexandreg@targointernet.com support 271583 False 1750651200000 1757649600000
59 19 fanny@targointernet.com Comptable 271583 False 1751860800000 NULL
60 53 samuelr@targointernet.com Technicien 271583 False 1749614400000 1749614400000
61 56 joseph@targointernet.com Resp. service clients et opérations commerciales Resp. service clients et operations commerciales 271583 True 1754395200000 NULL
62 55 nathanm@targointernet.com Technicien 271583 False 1751860800000 NULL
63 58 lion@targointernet.com Programmeur 271583 False 1758513600000 NULL

View File

@ -1,272 +1,272 @@
// // src/scripts/import-employees-from-csv.ts // src/scripts/import-employees-from-csv.ts
// import { PrismaClient, Roles } from '@prisma/client'; import { PrismaClient, Roles } from '@prisma/client';
// import * as fs from 'fs'; import * as fs from 'fs';
// import * as path from 'path'; import * as path from 'path';
// const prisma = new PrismaClient(); const prisma = new PrismaClient();
// // Chemin vers ton CSV employees // Chemin vers ton CSV employees
// const CSV_PATH = path.resolve(__dirname, 'data/export_new_employee_table.csv'); const CSV_PATH = path.resolve(__dirname, 'data/export_new_employee_table.csv');
// // Rôles éligibles pour la table Employees // Rôles éligibles pour la table Employees
// const ELIGIBLE_ROLES: Roles[] = [ const ELIGIBLE_ROLES: Roles[] = [
// Roles.EMPLOYEE, Roles.EMPLOYEE,
// Roles.SUPERVISOR, Roles.SUPERVISOR,
// Roles.HR, Roles.HR,
// Roles.ACCOUNTING, Roles.ACCOUNTING,
// Roles.ADMIN, Roles.ADMIN,
// ]; ];
// // Type correspondant EXACT aux colonnes de ton CSV // Type correspondant EXACT aux colonnes de ton CSV
// type EmployeeCsvRow = { type EmployeeCsvRow = {
// employee_number: string; employee_number: string;
// email: string; email: string;
// job_title: string; job_title: string;
// company: string; // sera converti en number company: string; // sera converti en number
// is_supervisor: string; // "True"/"False" (ou variantes) is_supervisor: string; // "True"/"False" (ou variantes)
// onboarding: string; // millis onboarding: string; // millis
// offboarding: string; // millis ou "NULL" offboarding: string; // millis ou "NULL"
// }; };
// // Représentation minimale d'un user // Représentation minimale d'un user
// type UserSummary = { type UserSummary = {
// id: string; // UUID id: string; // UUID
// email: string; email: string;
// role: Roles; role: Roles;
// }; };
// // ============ Helpers CSV ============ // ============ Helpers CSV ============
// function splitCsvLine(line: string): string[] { function splitCsvLine(line: string): string[] {
// const result: string[] = []; const result: string[] = [];
// let current = ''; let current = '';
// let inQuotes = false; let inQuotes = false;
// for (let i = 0; i < line.length; i++) { for (let i = 0; i < line.length; i++) {
// const char = line[i]; const char = line[i];
// if (char === '"') { if (char === '"') {
// // guillemet échappé "" // guillemet échappé ""
// if (inQuotes && line[i + 1] === '"') { if (inQuotes && line[i + 1] === '"') {
// current += '"'; current += '"';
// i++; i++;
// } else { } else {
// inQuotes = !inQuotes; inQuotes = !inQuotes;
// } }
// } else if (char === ',' && !inQuotes) { } else if (char === ',' && !inQuotes) {
// result.push(current); result.push(current);
// current = ''; current = '';
// } else { } else {
// current += char; current += char;
// } }
// } }
// result.push(current); result.push(current);
// return result.map((v) => v.trim()); return result.map((v) => v.trim());
// } }
// // ============ Helpers de parsing ============ // ============ Helpers de parsing ============
// function parseBoolean(value: string): boolean { function parseBoolean(value: string): boolean {
// const v = value.trim().toLowerCase(); const v = value.trim().toLowerCase();
// return v === 'true' || v === '1' || v === 'yes' || v === 'y' || v === 'oui'; return v === 'true' || v === '1' || v === 'yes' || v === 'y' || v === 'oui';
// } }
// function parseIntSafe(value: string, fieldName: string): number | null { function parseIntSafe(value: string, fieldName: string): number | null {
// const trimmed = value.trim(); const trimmed = value.trim();
// if (!trimmed || trimmed.toUpperCase() === 'NULL') return null; if (!trimmed || trimmed.toUpperCase() === 'NULL') return null;
// const n = Number.parseInt(trimmed, 10); const n = Number.parseInt(trimmed, 10);
// if (Number.isNaN(n)) { if (Number.isNaN(n)) {
// console.warn(` Impossible de parser "${value}" en entier pour le champ ${fieldName}`); console.warn(` Impossible de parser "${value}" en entier pour le champ ${fieldName}`);
// return null; return null;
// } }
// return n; return n;
// } }
// function millisToDate(value: string): Date | null { function millisToDate(value: string): Date | null {
// const trimmed = value.trim().toUpperCase(); const trimmed = value.trim().toUpperCase();
// if (!trimmed || trimmed === 'NULL') return null; if (!trimmed || trimmed === 'NULL') return null;
// const ms = Number(trimmed); const ms = Number(trimmed);
// if (!Number.isFinite(ms)) { if (!Number.isFinite(ms)) {
// console.warn(` Impossible de parser "${value}" en millis pour une Date`); console.warn(` Impossible de parser "${value}" en millis pour une Date`);
// return null; return null;
// } }
// const d = new Date(ms); const d = new Date(ms);
// // On normalise au jour (minuit UTC) // On normalise au jour (minuit UTC)
// const normalized = new Date(Date.UTC( const normalized = new Date(Date.UTC(
// d.getUTCFullYear(), d.getUTCFullYear(),
// d.getUTCMonth(), d.getUTCMonth(),
// d.getUTCDate(), d.getUTCDate(),
// )); ));
// return normalized; return normalized;
// } }
// // ============ MAIN ============ // ============ MAIN ============
// async function main() { async function main() {
// // 1. Lecture du CSV // 1. Lecture du CSV
// const fileContent = fs.readFileSync(CSV_PATH, 'utf-8'); const fileContent = fs.readFileSync(CSV_PATH, 'utf-8');
// const lines = fileContent const lines = fileContent
// .split(/\r?\n/) .split(/\r?\n/)
// .map((l) => l.trim()) .map((l) => l.trim())
// .filter((l) => l.length > 0); .filter((l) => l.length > 0);
// if (lines.length <= 1) { if (lines.length <= 1) {
// console.error('CSV vide ou seulement un header'); console.error('CSV vide ou seulement un header');
// return; return;
// } }
// const header = splitCsvLine(lines[0]); // ["employee_number","email",...] const header = splitCsvLine(lines[0]); // ["employee_number","email",...]
// const dataLines = lines.slice(1); const dataLines = lines.slice(1);
// const csvRows: EmployeeCsvRow[] = dataLines.map((line) => { const csvRows: EmployeeCsvRow[] = dataLines.map((line) => {
// const values = splitCsvLine(line); const values = splitCsvLine(line);
// const row: any = {}; const row: any = {};
// header.forEach((col, idx) => { header.forEach((col, idx) => {
// row[col] = values[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 // 2. Récupérer tous les emails du CSV
// const emails = Array.from( const emails = Array.from(
// new Set( new Set(
// csvRows csvRows
// .map((r) => r.email.trim()) .map((r) => r.email.trim())
// .filter((e) => e.length > 0), .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 // 3. Charger les users correspondants avec les bons rôles
// const users = (await prisma.users.findMany({ const users = (await prisma.users.findMany({
// where: { where: {
// email: { in: emails }, email: { in: emails },
// role: { in: ELIGIBLE_ROLES }, role: { in: ELIGIBLE_ROLES },
// }, },
// select: { select: {
// id: true, id: true,
// email: true, email: true,
// role: true, role: true,
// }, },
// })) as UserSummary[]; })) 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 // Map email → user
// const userByEmail = new Map<string, UserSummary>(); const userByEmail = new Map<string, UserSummary>();
// for (const user of users) { for (const user of users) {
// const key = user.email.trim().toLowerCase(); const key = user.email.trim().toLowerCase();
// userByEmail.set(key, user); userByEmail.set(key, user);
// } }
// // 4. Construire les données pour employees.createMany // 4. Construire les données pour employees.createMany
// const employeesToCreate: { const employeesToCreate: {
// user_id: string; user_id: string;
// external_payroll_id: number; external_payroll_id: number;
// company_code: number; company_code: number;
// first_work_day: Date; first_work_day: Date;
// last_work_day: Date | null; last_work_day: Date | null;
// job_title: string | null; job_title: string | null;
// is_supervisor: boolean; is_supervisor: boolean;
// supervisor_id?: number | null; supervisor_id?: number | null;
// daily_expected_hours: number; daily_expected_hours: number;
// }[] = []; }[] = [];
// const rowsWithoutUser: EmployeeCsvRow[] = []; const rowsWithoutUser: EmployeeCsvRow[] = [];
// const rowsWithInvalidNumbers: EmployeeCsvRow[] = []; const rowsWithInvalidNumbers: EmployeeCsvRow[] = [];
// for (const row of csvRows) { for (const row of csvRows) {
// const emailKey = row.email.trim().toLowerCase(); const emailKey = row.email.trim().toLowerCase();
// const user = userByEmail.get(emailKey); const user = userByEmail.get(emailKey);
// if (!user) { if (!user) {
// rowsWithoutUser.push(row); rowsWithoutUser.push(row);
// continue; continue;
// } }
// const external_payroll_id = parseIntSafe(row.employee_number, 'external_payroll_id'); const external_payroll_id = parseIntSafe(row.employee_number, 'external_payroll_id');
// const company_code = parseIntSafe(String(row.company), 'company_code'); const company_code = parseIntSafe(String(row.company), 'company_code');
// if (external_payroll_id === null || company_code === null) { if (external_payroll_id === null || company_code === null) {
// rowsWithInvalidNumbers.push(row); rowsWithInvalidNumbers.push(row);
// continue; continue;
// } }
// const first_work_day = millisToDate(row.onboarding); const first_work_day = millisToDate(row.onboarding);
// const last_work_day = millisToDate(row.offboarding); const last_work_day = millisToDate(row.offboarding);
// const is_supervisor = parseBoolean(row.is_supervisor); const is_supervisor = parseBoolean(row.is_supervisor);
// const job_title = row.job_title?.trim() || null; const job_title = row.job_title?.trim() || null;
// if (!first_work_day) { if (!first_work_day) {
// console.warn( console.warn(
// `WARNING: Date d'onboarding invalide pour ${row.email} (employee_number=${row.employee_number})`, `WARNING: Date d'onboarding invalide pour ${row.email} (employee_number=${row.employee_number})`,
// ); );
// continue; continue;
// } }
// employeesToCreate.push({ employeesToCreate.push({
// user_id: user.id, user_id: user.id,
// external_payroll_id, external_payroll_id,
// company_code, company_code,
// first_work_day, first_work_day,
// last_work_day, last_work_day,
// daily_expected_hours: 8, daily_expected_hours: 8,
// job_title, job_title,
// is_supervisor, is_supervisor,
// supervisor_id: null, // on pourra gérer ça plus tard si tu as les infos 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) { if (rowsWithoutUser.length > 0) {
// console.warn(` ${rowsWithoutUser.length} lignes CSV sans user correspondant (email / rôle) :`); console.warn(` ${rowsWithoutUser.length} lignes CSV sans user correspondant (email / rôle) :`);
// for (const row of rowsWithoutUser) { for (const row of rowsWithoutUser) {
// console.warn( console.warn(
// ` - email=${row.email}, employee_number=${row.employee_number}, company=${row.company}`, ` - email=${row.email}, employee_number=${row.employee_number}, company=${row.company}`,
// ); );
// } }
// } }
// if (rowsWithInvalidNumbers.length > 0) { if (rowsWithInvalidNumbers.length > 0) {
// console.warn(` ${rowsWithInvalidNumbers.length} lignes CSV avec ids/compagnies invalides :`); console.warn(` ${rowsWithInvalidNumbers.length} lignes CSV avec ids/compagnies invalides :`);
// for (const row of rowsWithInvalidNumbers) { for (const row of rowsWithInvalidNumbers) {
// console.warn( console.warn(
// ` - email=${row.email}, employee_number="${row.employee_number}", company="${row.company}"`, ` - email=${row.email}, employee_number="${row.employee_number}", company="${row.company}"`,
// ); );
// } }
// } }
// if (employeesToCreate.length === 0) { if (employeesToCreate.length === 0) {
// console.warn(' Aucun Employees à créer, arrêt.'); console.warn(' Aucun Employees à créer, arrêt.');
// return; return;
// } }
// // 5. Insert en batch // 5. Insert en batch
// const result = await prisma.employees.createMany({ const result = await prisma.employees.createMany({
// data: employeesToCreate, data: employeesToCreate,
// skipDuplicates: true, // évite les erreurs si tu relances le script 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() main()
// .catch((err) => { .catch((err) => {
// console.error(' Erreur pendant limport CSV → Employees', err); console.error(' Erreur pendant limport CSV → Employees', err);
// process.exit(1); process.exit(1);
// }) })
// .finally(async () => { .finally(async () => {
// await prisma.$disconnect(); await prisma.$disconnect();
// }); });

View File

@ -1,106 +1,105 @@
// // src/scripts/import-users-from-csv.ts // src/scripts/import-users-from-csv.ts
// import { PrismaClient } from '@prisma/client'; import { PrismaClient, Roles } from '@prisma/client';
// import * as fs from 'fs'; import * as fs from 'fs';
// import * as path from 'path'; import * as path from 'path';
// const prisma = new PrismaClient(); const prisma = new PrismaClient();
// // ⚙️ Chemin vers ton CSV (à adapter selon où tu le mets) // ⚙️ 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 aligné sur les colonnes du CSV
// type CsvUserRow = { type CsvUserRow = {
// email: string; email: string;
// first_name: string; first_name: string;
// last_name: string; last_name: string;
// phone_number: string; phone_number: string;
// }; };
// // Petit parseur de ligne CSV sans dépendance // Petit parseur de ligne CSV sans dépendance
// function splitCsvLine(line: string): string[] { function splitCsvLine(line: string): string[] {
// const result: string[] = []; const result: string[] = [];
// let current = ''; let current = '';
// let inQuotes = false; let inQuotes = false;
// for (let i = 0; i < line.length; i++) { for (let i = 0; i < line.length; i++) {
// const char = line[i]; const char = line[i];
// if (char === '"') { if (char === '"') {
// // guillemet échappé "" // guillemet échappé ""
// if (inQuotes && line[i + 1] === '"') { if (inQuotes && line[i + 1] === '"') {
// current += '"'; current += '"';
// i++; // on saute le deuxième i++; // on saute le deuxième
// } else { } else {
// inQuotes = !inQuotes; inQuotes = !inQuotes;
// } }
// } else if (char === ',' && !inQuotes) { } else if (char === ',' && !inQuotes) {
// result.push(current); result.push(current);
// current = ''; current = '';
// } else { } else {
// current += char; current += char;
// } }
// } }
// result.push(current); result.push(current);
// return result.map((v) => v.trim()); return result.map((v) => v.trim());
// } }
// async function main() { async function main() {
// // 1. Lecture du fichier CSV // 1. Lecture du fichier CSV
// const fileContent = fs.readFileSync(CSV_PATH, 'utf-8'); const fileContent = fs.readFileSync(CSV_PATH, 'utf-8');
// const lines = fileContent const lines = fileContent
// .split(/\r?\n/) .split(/\r?\n/)
// .map((l) => l.trim()) .map((l) => l.trim())
// .filter((l) => l.length > 0); .filter((l) => l.length > 0);
// if (lines.length <= 1) { if (lines.length <= 1) {
// console.error('CSV vide ou seulement un header'); console.error('CSV vide ou seulement un header');
// return; return;
// } }
// // 2. Header (noms de colonnes) -> ["email", "first_name", "last_name", "phone_number"] // 2. Header (noms de colonnes) -> ["email", "first_name", "last_name", "phone_number"]
// const header = splitCsvLine(lines[0]); const header = splitCsvLine(lines[0]);
// const dataLines = lines.slice(1); const dataLines = lines.slice(1);
// // 3. Conversion de chaque ligne en objet { email, first_name, last_name, phone_number } // 3. Conversion de chaque ligne en objet { email, first_name, last_name, phone_number }
// const records: CsvUserRow[] = dataLines.map((line) => { const records: CsvUserRow[] = dataLines.map((line) => {
// const values = splitCsvLine(line); const values = splitCsvLine(line);
// const row: any = {}; const row: any = {};
// header.forEach((col, idx) => { header.forEach((col, idx) => {
// row[col] = values[idx] ?? ''; row[col] = values[idx] ?? '';
// }); });
// return row as CsvUserRow; return row as CsvUserRow;
// }); });
// // 4. Mapping vers le format attendu par Prisma (model Users) // 4. Mapping vers le format attendu par Prisma (model Users)
// const data = records.map((row) => ({ const data = records.map((row) => ({
// email: row.email.trim(), email: row.email.trim(),
// first_name: row.first_name.trim(), first_name: row.first_name.trim(),
// last_name: row.last_name.trim(), last_name: row.last_name.trim(),
// phone_number: row.phone_number.trim(), phone_number: row.phone_number.trim(),
// // residence: null, role: Roles.EMPLOYEE,
// // role: 'EMPLOYEE', // residence: null,
// })); }));
// console.log(`➡️ ${data.length} lignes trouvées dans le CSV`); console.log(`➡️ ${data.length} lignes trouvées dans le CSV`);
// console.log('Exemple importé :', data[0]); console.log('Exemple importé :', data[0]);
const result = await prisma.users.createMany({
data,
});
// const result = await prisma.users.createMany({ console.log(`${result.count} utilisateurs insérés dans la DB`);
// data, }
// });
// console.log(`✅ ${result.count} utilisateurs insérés dans la DB`); main()
// } .catch((err) => {
console.error('Erreur pendant limport CSV → DB', err);
// main() process.exit(1);
// .catch((err) => { })
// console.error('Erreur pendant limport CSV → DB', err); .finally(async () => {
// process.exit(1); await prisma.$disconnect();
// }) });
// .finally(async () => {
// await prisma.$disconnect();
// });

View File

@ -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);
}
}

View File

@ -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 () => { export const initializePreferences = async () => {
// console.log('start of preferences and Module Access initialization') console.log('start of preferences and Module Access initialization')
// for (let id = 1; id <= 61; id++) { for (let id = 1; id <= 61; id++) {
// const user = await prisma.employees.findUnique({ const user = await prisma.employees.findUnique({
// where: { id }, where: { id },
// select: { user_id: true }, select: { user_id: true },
// }); });
// if (!user) { if (!user) {
// console.log(`user_id for employee ${id} not found`); console.log(`user_id for employee ${id} not found`);
// continue; continue;
// } }
// console.log(`user_id for employee ${id} found`); console.log(`user_id for employee ${id} found`);
// await prisma.preferences.create({ await prisma.preferences.create({
// data: { data: {
// display_language: 'fr-Fr', display_language: 'fr-Fr',
// is_dark_mode: null, is_dark_mode: null,
// is_employee_list_grid: false, is_employee_list_grid: false,
// is_lefty_mode: false, is_lefty_mode: false,
// is_timesheet_approval_grid: false, is_timesheet_approval_grid: false,
// notifications: true, notifications: true,
// user_id: user.user_id, user_id: user.user_id,
// }, },
// }); });
// console.log(`Preferences for employee ${id} initiated`); console.log(`Preferences for employee ${id} initiated`);
// await prisma.userModuleAccess.create({ await prisma.userModuleAccess.create({
// data: { data: {
// user_id: user.user_id, user_id: user.user_id,
// dashboard: true, dashboard: true,
// employee_list: true, employee_list: true,
// employee_management: false, employee_management: false,
// personal_profile: true, personal_profile: true,
// timesheets: true, timesheets: true,
// timesheets_approval: false, timesheets_approval: false,
// }, },
// }); });
// console.log(`Module Access for employee ${id} initiated`); console.log(`Module Access for employee ${id} initiated`);
// if (id in admin_list) { if (id in admin_list) {
// console.log(`employee ${id} is and admin`) console.log(`employee ${id} is and admin`)
// await prisma.userModuleAccess.update({ await prisma.userModuleAccess.update({
// where: { user_id: user.user_id }, where: { user_id: user.user_id },
// data: { data: {
// dashboard: true, dashboard: true,
// employee_list: true, employee_list: true,
// employee_management: true, employee_management: true,
// personal_profile: true, personal_profile: true,
// timesheets: true, timesheets: true,
// timesheets_approval: true, timesheets_approval: true,
// }, },
// }); });
// console.log(`Module Access for employee ${id} updated`); console.log(`Module Access for employee ${id} updated`);
// } }
// } }
// } }

View File

@ -1,167 +1,167 @@
// import { PrismaClient as Prisma } from "@prisma/client"; import { PrismaClient as Prisma } from "@prisma/client";
// import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy"
// import { toDateFromString, toHHmmFromDate, toStringFromDate } from "src/common/utils/date-utils"; import { toDateFromString, toHHmmFromDate, toStringFromDate } from "src/common/utils/date-utils";
// const prisma_legacy = new PrismaLegacy({}); const prisma_legacy = new PrismaLegacy({});
// const prisma = new Prisma({}); const prisma = new Prisma({});
// type NewEmployee = { type NewEmployee = {
// id: number; id: number;
// company_code: number; company_code: number;
// external_payroll_id: number; external_payroll_id: number;
// } }
// type OldExpense = { type OldExpense = {
// time_sheet_id: string | null; time_sheet_id: string | null;
// date: string | null; date: string | null;
// code: string | null; code: string | null;
// description: string | null; description: string | null;
// value: number | null; value: number | null;
// status: boolean | null; status: boolean | null;
// } }
// export const extractOldExpenses = async () => { export const extractOldExpenses = async () => {
// const list_of_employees = await prisma.employees.findMany(); const list_of_employees = await prisma.employees.findMany();
// for (let id = 1; id <= list_of_employees.length+1; id++) { 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); const new_employee = await findOneNewEmployee(id);
// console.log(`Employee ${id} found in new DB`); console.log(`Employee ${id} found in new DB`);
// const new_timesheets = await findManyNewTimesheets(new_employee.id); const new_timesheets = await findManyNewTimesheets(new_employee.id);
// console.log(`New Timesheets found for employee ${id}`); console.log(`New Timesheets found for employee ${id}`);
// const old_employee_id = await findOneOldEmployee(new_employee); const old_employee_id = await findOneOldEmployee(new_employee);
// console.log(`Employee ${new_employee.id} found in old DB`); console.log(`Employee ${new_employee.id} found in old DB`);
// const old_timesheets = await findManyOldTimesheets(old_employee_id); 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(`Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`);
// console.log('Start of Expense creation*****************************************************************'); console.log('Start of Expense creation*****************************************************************');
// for (const old_timesheet of old_timesheets) { for (const old_timesheet of old_timesheets) {
// if (!old_timesheet.start_date) continue; if (!old_timesheet.start_date) continue;
// const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime()); const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime());
// if (!new_timesheet) { if (!new_timesheet) {
// console.warn(`No new timesheet matching legacy timesheet ${old_timesheet.id}`); console.warn(`No new timesheet matching legacy timesheet ${old_timesheet.id}`);
// continue; continue;
// } }
// const old_expenses = await prisma_legacy.expenses.findMany({ const old_expenses = await prisma_legacy.expenses.findMany({
// where: { time_sheet_id: old_timesheet.id }, where: { time_sheet_id: old_timesheet.id },
// select: { select: {
// time_sheet_id: true, time_sheet_id: true,
// date: true, date: true,
// code: true, code: true,
// description: true, description: true,
// value: true, value: true,
// status: true, status: true,
// }, },
// }); });
// await createManyNewExpenses(new_timesheet.id, old_expenses); await createManyNewExpenses(new_timesheet.id, old_expenses);
// } }
// } }
// await prisma_legacy.$disconnect(); await prisma_legacy.$disconnect();
// await prisma.$disconnect(); await prisma.$disconnect();
// console.log('finished migrating expenses ***************************'); console.log('finished migrating expenses ***************************');
// } }
// const findOneNewEmployee = async (id: number): Promise<NewEmployee> => { const findOneNewEmployee = async (id: number): Promise<NewEmployee> => {
// const new_employee = await prisma.employees.findUnique({ const new_employee = await prisma.employees.findUnique({
// where: { id: id }, where: { id: id },
// select: { select: {
// id: true, id: true,
// company_code: true, company_code: true,
// external_payroll_id: true, external_payroll_id: true,
// }, },
// }); });
// if (!new_employee) throw new Error(`New Employee with id ${id} not found`) if (!new_employee) throw new Error(`New Employee with id ${id} not found`)
// return new_employee; return new_employee;
// } }
// const findOneOldEmployee = async (new_employee: NewEmployee): Promise<string> => { const findOneOldEmployee = async (new_employee: NewEmployee): Promise<string> => {
// const old_employee = await prisma_legacy.employees.findFirst({ const old_employee = await prisma_legacy.employees.findFirst({
// where: { where: {
// company: new_employee.company_code, company: new_employee.company_code,
// employee_number: new_employee.external_payroll_id.toString(), employee_number: new_employee.external_payroll_id.toString(),
// }, },
// select: { select: {
// id: true, id: true,
// }, },
// }); });
// if (!old_employee) throw new Error(`Old Employee not found`); if (!old_employee) throw new Error(`Old Employee not found`);
// return old_employee.id; return old_employee.id;
// } }
// const findManyOldTimesheets = async (old_employee_id: string) => { const findManyOldTimesheets = async (old_employee_id: string) => {
// const old_timesheets = await prisma_legacy.time_sheets.findMany({ const old_timesheets = await prisma_legacy.time_sheets.findMany({
// where: { employee_id: old_employee_id }, where: { employee_id: old_employee_id },
// select: { id: true, start_date: true, status: true } select: { id: true, start_date: true, status: true }
// }); });
// return old_timesheets; return old_timesheets;
// } }
// const findManyNewTimesheets = async (employee_id: number) => { const findManyNewTimesheets = async (employee_id: number) => {
// const timesheets = await prisma.timesheets.findMany({ const timesheets = await prisma.timesheets.findMany({
// where: { employee_id: employee_id }, where: { employee_id: employee_id },
// select: { id: true, start_date: true } select: { id: true, start_date: true }
// }) })
// return timesheets; return timesheets;
// } }
// const createManyNewExpenses = async (timesheet_id: number, old_expenses: OldExpense[]) => { const createManyNewExpenses = async (timesheet_id: number, old_expenses: OldExpense[]) => {
// for (const old_expense of old_expenses) { for (const old_expense of old_expenses) {
// let mileage: number = 0; let mileage: number = 0;
// let amount: number = old_expense.value ?? 0; let amount: number = old_expense.value ?? 0;
// if (old_expense.code === 'G503') { if (old_expense.code === 'G503') {
// mileage = old_expense.value!; mileage = old_expense.value!;
// amount = mileage * 0.72; amount = mileage * 0.72;
// } }
// if (mileage < 0 || amount < 0) { if (mileage < 0 || amount < 0) {
// console.warn(`expense of value less than '0' found`) console.warn(`expense of value less than '0' found`)
// continue; continue;
// } }
// if (old_expense.date == null) { if (old_expense.date == null) {
// console.warn(`Expense date invalid ${old_expense.date}`); console.warn(`Expense date invalid ${old_expense.date}`);
// continue; continue;
// } }
// const date = toDateFromString(old_expense.date); const date = toDateFromString(old_expense.date);
// if (old_expense.status == null) { if (old_expense.status == null) {
// console.warn(`status null for legacy expense ${old_expense}`); console.warn(`status null for legacy expense ${old_expense}`);
// continue; continue;
// } }
// if (old_expense.code == null) { if (old_expense.code == null) {
// console.warn(`Code null for legacy expense ${old_expense.code}`); console.warn(`Code null for legacy expense ${old_expense.code}`);
// continue; continue;
// } }
// const bank_code_id = await findBankCodeIdUsingOldCode(old_expense.code); const bank_code_id = await findBankCodeIdUsingOldCode(old_expense.code);
// await prisma.expenses.upsert({ await prisma.expenses.upsert({
// where: { unique_ts_id_date_amount_mileage: { timesheet_id: timesheet_id, date, amount, mileage } }, where: { unique_ts_id_date_amount_mileage: { timesheet_id: timesheet_id, date, amount, mileage } },
// update: { update: {
// is_approved: old_expense.status, is_approved: old_expense.status,
// }, },
// create: { create: {
// date: date, date: date,
// comment: old_expense.description ?? '', comment: old_expense.description ?? '',
// timesheet_id: timesheet_id, timesheet_id: timesheet_id,
// bank_code_id: bank_code_id, bank_code_id: bank_code_id,
// amount: amount, amount: amount,
// mileage: mileage, mileage: mileage,
// } }
// }); });
// } }
// } }
// const findBankCodeIdUsingOldCode = async (code: string): Promise<number> => { const findBankCodeIdUsingOldCode = async (code: string): Promise<number> => {
// const bank_code = await prisma.bankCodes.findFirst({ const bank_code = await prisma.bankCodes.findFirst({
// where: { bank_code: code }, where: { bank_code: code },
// select: { id: true }, select: { id: true },
// }); });
// if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`) if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`)
// return bank_code.id; return bank_code.id;
// } }

View File

@ -1,216 +1,216 @@
// import { PrismaClient as PrismaNew } from "@prisma/client"; import { PrismaClient as PrismaNew } from "@prisma/client";
// import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy"
// const prisma_legacy = new PrismaLegacy({}); const prisma_legacy = new PrismaLegacy({});
// const prisma = new PrismaNew({}); const prisma = new PrismaNew({});
// type NewEmployee = { type NewEmployee = {
// id: number; id: number;
// company_code: number; company_code: number;
// external_payroll_id: number; external_payroll_id: number;
// } }
// type OldShifts = { type OldShifts = {
// time_sheet_id: string | null; time_sheet_id: string | null;
// code: string | null; code: string | null;
// type: string | null; type: string | null;
// date: Date | null; date: Date | null;
// start_time: bigint | null; start_time: bigint | null;
// end_time: bigint | null; end_time: bigint | null;
// comment: string | null; comment: string | null;
// status: boolean | null; status: boolean | null;
// } }
// export const extractOldShifts = async () => { export const extractOldShifts = async () => {
// const list_of_employees = await prisma.employees.findMany(); const list_of_employees = await prisma.employees.findMany();
// for (let id = 1; id <= list_of_employees.length + 1; id++) { for (let id = 1; id <= list_of_employees.length + 1; id++) {
// console.log(`Start of shift migration ***************************************************************`); console.log(`Start of shift migration ***************************************************************`);
// const new_employee = await findOneNewEmployee(id); const new_employee = await findOneNewEmployee(id);
// console.log(`Employee ${id} found in new DB`); console.log(`Employee ${id} found in new DB`);
// const new_timesheets = await findManyNewTimesheets(new_employee.id); const new_timesheets = await findManyNewTimesheets(new_employee.id);
// console.log(new_timesheets.length, `new Timesheets found for employee ${id}`); console.log(new_timesheets.length, `new Timesheets found for employee ${id}`);
// const old_employee_id = await findOneOldEmployee(new_employee); const old_employee_id = await findOneOldEmployee(new_employee);
// console.log(`Employee ${new_employee.id} found in old DB`); console.log(`Employee ${new_employee.id} found in old DB`);
// const old_timesheets = await findManyOldTimesheets(old_employee_id); const old_timesheets = await findManyOldTimesheets(old_employee_id);
// console.log(old_timesheets.length, `old timesheets for employee ${new_employee.id} found in old DB`); console.log(old_timesheets.length, `old timesheets for employee ${new_employee.id} found in old DB`);
// for (const old_timesheet of old_timesheets) { for (const old_timesheet of old_timesheets) {
// if (!old_timesheet.start_date) { if (!old_timesheet.start_date) {
// console.warn('Start_date of the old_timesheet ', old_timesheet.id, 'invalid, start_date: ', old_timesheet.start_date); console.warn('Start_date of the old_timesheet ', old_timesheet.id, 'invalid, start_date: ', old_timesheet.start_date);
// continue; continue;
// } }
// const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime()); const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime());
// if (!new_timesheet) { if (!new_timesheet) {
// console.warn(`No new timesheet ${new_timesheet} matching legacy timesheet ${old_timesheet.id}`); console.warn(`No new timesheet ${new_timesheet} matching legacy timesheet ${old_timesheet.id}`);
// continue; continue;
// } }
// const old_shifts = await prisma_legacy.shifts.findMany({ const old_shifts = await prisma_legacy.shifts.findMany({
// where: { time_sheet_id: old_timesheet.id }, where: { time_sheet_id: old_timesheet.id },
// select: { select: {
// time_sheet_id: true, time_sheet_id: true,
// code: true, code: true,
// type: true, type: true,
// date: true, date: true,
// start_time: true, start_time: true,
// end_time: true, end_time: true,
// comment: true, comment: true,
// status: true, status: true,
// }, },
// }); });
// await createManyNewShifts(new_timesheet.id, old_shifts); await createManyNewShifts(new_timesheet.id, old_shifts);
// } }
// } }
// await prisma_legacy.$disconnect(); await prisma_legacy.$disconnect();
// await prisma.$disconnect(); await prisma.$disconnect();
// } }
// const findOneNewEmployee = async (id: number): Promise<NewEmployee> => { const findOneNewEmployee = async (id: number): Promise<NewEmployee> => {
// const new_employee = await prisma.employees.findUnique({ const new_employee = await prisma.employees.findUnique({
// where: { id: id }, where: { id: id },
// select: { select: {
// id: true, id: true,
// company_code: true, company_code: true,
// external_payroll_id: true, external_payroll_id: true,
// }, },
// }); });
// if (!new_employee) throw new Error(`New Employee with id ${id} not found`) if (!new_employee) throw new Error(`New Employee with id ${id} not found`)
// return new_employee; return new_employee;
// } }
// const findOneOldEmployee = async (new_employee: NewEmployee): Promise<string> => { const findOneOldEmployee = async (new_employee: NewEmployee): Promise<string> => {
// const old_employee = await prisma_legacy.employees.findFirst({ const old_employee = await prisma_legacy.employees.findFirst({
// where: { where: {
// company: new_employee.company_code, company: new_employee.company_code,
// employee_number: new_employee.external_payroll_id.toString(), employee_number: new_employee.external_payroll_id.toString(),
// }, },
// select: { select: {
// id: true, id: true,
// }, },
// }); });
// if (!old_employee) throw new Error(`Old Employee not found`); if (!old_employee) throw new Error(`Old Employee not found`);
// return old_employee.id; return old_employee.id;
// } }
// const findManyOldTimesheets = async (old_employee_id: string) => { const findManyOldTimesheets = async (old_employee_id: string) => {
// const old_timesheets = await prisma_legacy.time_sheets.findMany({ const old_timesheets = await prisma_legacy.time_sheets.findMany({
// where: { employee_id: old_employee_id }, where: { employee_id: old_employee_id },
// select: { id: true, start_date: true, status: true } select: { id: true, start_date: true, status: true }
// }); });
// return old_timesheets; return old_timesheets;
// } }
// const findManyNewTimesheets = async (employee_id: number) => { const findManyNewTimesheets = async (employee_id: number) => {
// const timesheets = await prisma.timesheets.findMany({ const timesheets = await prisma.timesheets.findMany({
// where: { employee_id: employee_id }, where: { employee_id: employee_id },
// select: { id: true, start_date: true } select: { id: true, start_date: true }
// }) })
// return timesheets; return timesheets;
// } }
// const createManyNewShifts = async (timesheet_id: number, old_shifts: OldShifts[]) => { const createManyNewShifts = async (timesheet_id: number, old_shifts: OldShifts[]) => {
// let shifts_count = 0; let shifts_count = 0;
// for (const old_shift of old_shifts) { for (const old_shift of old_shifts) {
// let is_remote = true; let is_remote = true;
// const start = toHHmmfromLegacyTimestamp(old_shift.start_time); const start = toHHmmfromLegacyTimestamp(old_shift.start_time);
// if (old_shift.start_time == null || !start) { if (old_shift.start_time == null || !start) {
// console.warn(`Shift start invalid ${old_shift.start_time}`); console.warn(`Shift start invalid ${old_shift.start_time}`);
// continue; continue;
// } }
// const end = toHHmmfromLegacyTimestamp(old_shift.end_time); const end = toHHmmfromLegacyTimestamp(old_shift.end_time);
// if (old_shift.end_time == null || !end) { if (old_shift.end_time == null || !end) {
// console.warn(`Shift end invalid ${old_shift.end_time}`); console.warn(`Shift end invalid ${old_shift.end_time}`);
// continue; continue;
// } }
// if (old_shift.date == null) { if (old_shift.date == null) {
// console.warn(`Shift date invalid ${old_shift.date}`); console.warn(`Shift date invalid ${old_shift.date}`);
// continue; continue;
// } }
// if (old_shift.status == null) { if (old_shift.status == null) {
// console.warn(`status null for legacy shift ${old_shift}`); console.warn(`status null for legacy shift ${old_shift}`);
// continue; continue;
// } }
// if (old_shift.type == null) { if (old_shift.type == null) {
// console.warn(`type null for legacy shift ${old_shift.type}`); console.warn(`type null for legacy shift ${old_shift.type}`);
// continue; continue;
// } }
// if (old_shift.type === 'office') { if (old_shift.type === 'office') {
// is_remote = false; is_remote = false;
// } }
// if (old_shift.code == null) { if (old_shift.code == null) {
// console.warn(`Code null for legacy shift ${old_shift.code}`); console.warn(`Code null for legacy shift ${old_shift.code}`);
// continue; continue;
// } }
// const bank_code_id = await findBankCodeIdUsingOldCode(old_shift.code); const bank_code_id = await findBankCodeIdUsingOldCode(old_shift.code);
// try { try {
// await prisma.shifts.create({ await prisma.shifts.create({
// // where: { unique_ts_id_date_start_time: { // where: { unique_ts_id_date_start_time: {
// // timesheet_id, // timesheet_id,
// // date: old_shift.date, // date: old_shift.date,
// // start_time: toDateFromHHmm(start) }}, // start_time: toDateFromHHmm(start) }},
// // update: { // update: {
// // start_time: toDateFromHHmm(start), // start_time: toDateFromHHmm(start),
// // end_time: toDateFromHHmm(end), // end_time: toDateFromHHmm(end),
// // comment: old_shift.comment, // comment: old_shift.comment,
// // is_approved: old_shift.status, // is_approved: old_shift.status,
// // is_remote: is_remote, // is_remote: is_remote,
// // bank_code_id: bank_code_id, // bank_code_id: bank_code_id,
// // }, // },
// data: { data: {
// date: old_shift.date, date: old_shift.date,
// start_time: toDateFromHHmm(start), start_time: toDateFromHHmm(start),
// end_time: toDateFromHHmm(end), end_time: toDateFromHHmm(end),
// comment: old_shift.comment, comment: old_shift.comment,
// is_approved: old_shift.status, is_approved: old_shift.status,
// is_remote: is_remote, is_remote: is_remote,
// timesheet_id: timesheet_id, timesheet_id: timesheet_id,
// bank_code_id: bank_code_id, bank_code_id: bank_code_id,
// }, },
// }); });
// shifts_count++; shifts_count++;
// } catch (error) { } catch (error) {
// console.log('An error occured during shifts creation', 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 => { const toHHmmfromLegacyTimestamp = (value: bigint | null): string | null => {
// if (value == null) return null; if (value == null) return null;
// const date = new Date(Number(value)); const date = new Date(Number(value));
// const hh = String(date.getHours()).padStart(2, '0'); const hh = String(date.getHours()).padStart(2, '0');
// const mm = String(date.getMinutes()).padStart(2, '0'); const mm = String(date.getMinutes()).padStart(2, '0');
// return `${hh}:${mm}`; return `${hh}:${mm}`;
// } }
// const toDateFromHHmm = (hhmm: string): Date => { const toDateFromHHmm = (hhmm: string): Date => {
// const [hh, mm] = hhmm.split(':'); const [hh, mm] = hhmm.split(':');
// const hours = Number(hh); const hours = Number(hh);
// const minutes = Number(mm); const minutes = Number(mm);
// return new Date(Date.UTC(1970, 0, 1, hours, minutes, 0, 0)); return new Date(Date.UTC(1970, 0, 1, hours, minutes, 0, 0));
// } }
// const findBankCodeIdUsingOldCode = async (code: string): Promise<number> => { const findBankCodeIdUsingOldCode = async (code: string): Promise<number> => {
// if (code === 'G700') { if (code === 'G700') {
// code = 'G104'; code = 'G104';
// } else if (code === 'G140') { } else if (code === 'G140') {
// code = 'G56' code = 'G56'
// } }
// const bank_code = await prisma.bankCodes.findFirst({ const bank_code = await prisma.bankCodes.findFirst({
// where: { bank_code: code }, where: { bank_code: code },
// select: { id: true, bank_code: true }, select: { id: true, bank_code: true },
// }); });
// if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`) if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`)
// return bank_code.id; return bank_code.id;
// } }

View File

@ -1,118 +1,118 @@
// import { PrismaClient as Prisma } from "@prisma/client"; import { PrismaClient as Prisma } from "@prisma/client";
// import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy"
// import { toStringFromDate } from "src/common/utils/date-utils"; import { toStringFromDate } from "src/common/utils/date-utils";
// type NewEmployee = { type NewEmployee = {
// id: number; id: number;
// company_code: number; company_code: number;
// external_payroll_id: number; external_payroll_id: number;
// } }
// type OldTimesheets = { type OldTimesheets = {
// id: string; id: string;
// start_date: Date | null; start_date: Date | null;
// status: boolean | null; status: boolean | null;
// } }
// const prisma_legacy = new PrismaLegacy({}); const prisma_legacy = new PrismaLegacy({});
// const prisma_new = new Prisma({}); const prisma_new = new Prisma({});
// export const extractOldTimesheets = async () => { export const extractOldTimesheets = async () => {
// const list_of_employees = await prisma_new.employees.findMany(); const list_of_employees = await prisma_new.employees.findMany();
// for (let id = 1; id <= list_of_employees.length; id++) { for (let id = 1; id <= list_of_employees.length; id++) {
// const new_employee = await findOneNewEmployee(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); await createManyNewTimesheets(old_timesheets, new_employee);
// console.log(`${old_timesheets.length} New Timesheets created in new DB for employee ${new_employee.id}`); console.log(`${old_timesheets.length} New Timesheets created in new DB for employee ${new_employee.id}`);
// } }
// await prisma_legacy.$disconnect(); await prisma_legacy.$disconnect();
// await prisma_new.$disconnect(); await prisma_new.$disconnect();
// console.log('migration of timesheets finished') console.log('migration of timesheets finished')
// } }
// const findOneNewEmployee = async (id: number): Promise<NewEmployee> => { const findOneNewEmployee = async (id: number): Promise<NewEmployee> => {
// const new_employee = await prisma_new.employees.findUnique({ const new_employee = await prisma_new.employees.findUnique({
// where: { id: id }, where: { id: id },
// select: { select: {
// id: true, id: true,
// company_code: true, company_code: true,
// external_payroll_id: 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<string> => {
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 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);
}
}
}
// extractOldTimesheets()
// .then(() => {
// console.log("Migration completed");
// })
// .catch((error) => {
// console.error("Migration failed:", error);
// })
// .finally(async () => {
// await prisma_legacy.$disconnect();
// await prisma_new.$disconnect();
// }); // });
// if (!new_employee) throw new Error(`New Employee with id ${id} not found`)
// return new_employee;
// }
// const findOneOldEmployee = async (new_employee: NewEmployee): Promise<string> => {
// 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 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);
// }
// }
// }
// // extractOldTimesheets()
// // .then(() => {
// // console.log("Migration completed");
// // })
// // .catch((error) => {
// // console.error("Migration failed:", error);
// // })
// // .finally(async () => {
// // await prisma_legacy.$disconnect();
// // await prisma_new.$disconnect();
// // });

View File

@ -14,9 +14,10 @@ import * as session from 'express-session';
import * as passport from 'passport'; import * as passport from 'passport';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';
import { PrismaSessionStore } from '@quixo3/prisma-session-store'; import { PrismaSessionStore } from '@quixo3/prisma-session-store';
// import { initializePaidTimeOff } from 'scripts/init-paid-time-off';
// import { initializePreferences } from 'scripts/init-preferences-access'; // import { initializePreferences } from 'scripts/init-preferences-access';
// import { extractOldShifts } from 'scripts/migrate-shifts';
// import { extractOldTimesheets } from 'scripts/migrate-timesheets'; // import { extractOldTimesheets } from 'scripts/migrate-timesheets';
// import { extractOldShifts } from 'scripts/migrate-shifts';
// import { extractOldExpenses } from 'scripts/migrate-expenses'; // import { extractOldExpenses } from 'scripts/migrate-expenses';
const SESSION_TOKEN_DURATION_MINUTES = 180 const SESSION_TOKEN_DURATION_MINUTES = 180
@ -93,9 +94,10 @@ async function bootstrap() {
// migration function calls // migration function calls
// await initializePaidTimeOff();
// await initializePreferences();
// await extractOldTimesheets(); // await extractOldTimesheets();
// await extractOldShifts(); // await extractOldShifts();
// await extractOldExpenses(); // await extractOldExpenses();
// await initializePreferences();
} }
bootstrap(); bootstrap();

View File

@ -81,6 +81,7 @@ export class SickLeaveService {
employee_id: true, employee_id: true,
sick_hours: true, sick_hours: true,
vacation_hours: true, vacation_hours: true,
banked_hours: true,
last_updated: true, last_updated: true,
} }
}); });

View File

@ -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 { Result } from "src/common/errors/result-error.factory";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
@ -8,26 +8,26 @@ export class VacationService {
constructor( constructor(
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver, private readonly emailResolver: EmailToIdResolver,
) {} ) { }
//switch employeeId for email //switch employeeId for email
async calculateVacationPay(email: string, start_date: Date, days_requested: number, modifier: number): Promise<Result<number, string>> { async calculateVacationPay(email: string, start_date: Date, days_requested: number, modifier: number): Promise<Result<number, string>> {
const employee_id = await this.emailResolver.findIdByEmail(email); 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 //fetch hiring date
const employee = await this.prisma.employees.findUnique({ const employee = await this.prisma.employees.findUnique({
where: { id: employee_id.data }, 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; const hire_date = employee.first_work_day;
//sets "year" to may 1st to april 30th //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 //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 const year_of_request = start_date.getMonth() >= 4
? start_date.getFullYear() : start_date.getFullYear() -1; ? start_date.getFullYear() : start_date.getFullYear() - 1;
const period_start = new Date(year_of_request, 4, 1); //may = 4 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 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); const anniversary_date = new Date(hire_date);
anniversary_date.setFullYear(anniversary_date.getFullYear() + years); anniversary_date.setFullYear(anniversary_date.getFullYear() + years);
return anniversary_date; 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 //calculate prorata per segment
let total_vacation_days = 0; let total_vacation_days = 0;
const ms_per_day = 1000 * 60 * 60 * 24; 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_start = boundaries[i];
const segment_end = boundaries[i+1]; const segment_end = boundaries[i + 1];
//number of days in said segment //number of days in said segment
const days_in_segment = Math.round((segment_end.getTime() - segment_start.getTime())/ ms_per_day); 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()) - const years_since_hire = (segment_start.getFullYear() - hire_date.getFullYear()) -
(segment_start < new Date(segment_start.getFullYear(), hire_date.getMonth()) ? 1 : 0); (segment_start < new Date(segment_start.getFullYear(), hire_date.getMonth()) ? 1 : 0);
let alloc_days: number; let alloc_days: number;
if(years_since_hire < 5) alloc_days = 10; if (years_since_hire < 5) alloc_days = 10;
else if(years_since_hire < 10) alloc_days = 15; else if (years_since_hire < 10) alloc_days = 15;
else if(years_since_hire < 15) alloc_days = 20; else if (years_since_hire < 15) alloc_days = 20;
else alloc_days = 25; else alloc_days = 25;
//prorata for said segment //prorata for said segment
@ -69,7 +69,47 @@ export class VacationService {
const raw_hours = payable_days * 8 * modifier; const raw_hours = payable_days * 8 * modifier;
const rounded_hours = Math.round(raw_hours * 4) / 4; 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<Result<number, string>> => {
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<number, string>
}
if (!employee.paid_time_off) {
return { success: false, error: 'VACATION_HOURS_BANK_NOT_FOUND' } as Result<number, string>
}
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<number, string>
} 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<number, string>;
}
});
return result;
} catch (error) {
return { success: false, error: 'INVALID_VACATION_SHIFT' }
}
} }

View File

@ -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 { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service"; 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, EmailToIdResolver,
ShiftsCreateService, ShiftsCreateService,
BankCodesResolver, BankCodesResolver,
VacationService,
], ],
exports: [ exports: [
SchedulePresetsGetService, SchedulePresetsGetService,

View File

@ -5,8 +5,9 @@ import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { Result } from "src/common/errors/result-error.factory"; 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 { ShiftDto } from "src/time-and-attendance/shifts/shift.dto";
import { VacationService } from "src/time-and-attendance/domains/services/vacation.service";
@Injectable() @Injectable()
export class ShiftsCreateService { export class ShiftsCreateService {
@ -14,6 +15,7 @@ export class ShiftsCreateService {
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver, private readonly emailResolver: EmailToIdResolver,
private readonly typeResolver: BankCodesResolver, private readonly typeResolver: BankCodesResolver,
private readonly vacationService: VacationService,
) { } ) { }
//_________________________________________________________________ //_________________________________________________________________
@ -93,6 +95,13 @@ export class ShiftsCreateService {
return { success: false, error: `SHIFT_OVERLAP` }; 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 //sends data for creation of a shift in db
const created_shift = await this.prisma.shifts.create({ 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 } }; 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`;
}
} }

View File

@ -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 { ShiftsCreateService } from 'src/time-and-attendance/shifts/services/shifts-create.service';
import { ShiftsDeleteService } from 'src/time-and-attendance/shifts/services/shifts-delete.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 { ShiftsUpdateService } from 'src/time-and-attendance/shifts/services/shifts-update-delete.service';
import { VacationService } from 'src/time-and-attendance/domains/services/vacation.service';
@Module({ @Module({
controllers: [ShiftController], controllers: [ShiftController],
providers: [ShiftsCreateService, ShiftsUpdateService, ShiftsDeleteService], providers: [ShiftsCreateService, ShiftsUpdateService, ShiftsDeleteService, VacationService],
exports: [ShiftsCreateService, ShiftsUpdateService, ShiftsDeleteService], exports: [ShiftsCreateService, ShiftsUpdateService, ShiftsDeleteService],
}) })
export class ShiftsModule { } export class ShiftsModule { }

View File

@ -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 { 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 { 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 { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.service";
import { VacationService } from "src/time-and-attendance/domains/services/vacation.service";
@Module({ @Module({
imports: [ imports: [
@ -80,6 +81,7 @@ import { CsvGeneratorService } from "src/time-and-attendance/exports/services/cs
PayPeriodsCommandService, PayPeriodsCommandService,
CsvExportService, CsvExportService,
CsvGeneratorService, CsvGeneratorService,
VacationService,
], ],
exports: [TimesheetApprovalService], exports: [TimesheetApprovalService],
}) export class TimeAndAttendanceModule { }; }) export class TimeAndAttendanceModule { };