diff --git a/prisma/migrations/20260106124750_added_paid_time_off/migration.sql b/prisma/migrations/20260106124750_added_paid_time_off/migration.sql new file mode 100644 index 0000000..0a060e7 --- /dev/null +++ b/prisma/migrations/20260106124750_added_paid_time_off/migration.sql @@ -0,0 +1,36 @@ +/* + Warnings: + + - You are about to drop the column `sort_order` on the `schedule_preset_shifts` table. All the data in the column will be lost. + - A unique constraint covering the columns `[preset_id,week_day]` on the table `schedule_preset_shifts` will be added. If there are existing duplicate values, this will fail. + - Added the required column `daily_expected_hours` to the `employees` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropIndex +DROP INDEX "public"."schedule_preset_shifts_preset_id_week_day_sort_order_key"; + +-- AlterTable +ALTER TABLE "employees" ADD COLUMN "daily_expected_hours" INTEGER NOT NULL; + +-- AlterTable +ALTER TABLE "schedule_preset_shifts" DROP COLUMN "sort_order"; + +-- CreateTable +CREATE TABLE "paid_time_off" ( + "id" SERIAL NOT NULL, + "employee_id" INTEGER NOT NULL, + "vacation_hours" INTEGER NOT NULL DEFAULT 0, + "sick_hours" INTEGER NOT NULL DEFAULT 0, + "last_updated" DATE NOT NULL, + + CONSTRAINT "paid_time_off_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "paid_time_off_employee_id_key" ON "paid_time_off"("employee_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "schedule_preset_shifts_preset_id_week_day_key" ON "schedule_preset_shifts"("preset_id", "week_day"); + +-- AddForeignKey +ALTER TABLE "paid_time_off" ADD CONSTRAINT "paid_time_off_employee_id_fkey" FOREIGN KEY ("employee_id") REFERENCES "employees"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b810226..0073904 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -39,23 +39,24 @@ model userModuleAccess { } model Employees { - id Int @id @default(autoincrement()) - user_id String @unique @db.Uuid - external_payroll_id Int - company_code Int - first_work_day DateTime @db.Date - last_work_day DateTime? @db.Date - supervisor_id Int? - job_title String? - is_supervisor Boolean @default(false) - schedule_preset_id Int? - schedule_preset SchedulePresets? @relation("EmployeesSchedulePreset", fields: [schedule_preset_id], references: [id]) - supervisor Employees? @relation("EmployeeSupervisor", fields: [supervisor_id], references: [id]) - crew Employees[] @relation("EmployeeSupervisor") - user Users @relation("UserEmployee", fields: [user_id], references: [id]) - leave_request LeaveRequests[] @relation("LeaveRequestEmployee") - timesheet Timesheets[] @relation("TimesheetEmployee") - paid_time_off PaidTimeOff? @relation("EmployeePaidTimeOff") + id Int @id @default(autoincrement()) + user_id String @unique @db.Uuid + external_payroll_id Int + company_code Int + daily_expected_hours Int + first_work_day DateTime @db.Date + last_work_day DateTime? @db.Date + supervisor_id Int? + job_title String? + is_supervisor Boolean @default(false) + schedule_preset_id Int? + schedule_preset SchedulePresets? @relation("EmployeesSchedulePreset", fields: [schedule_preset_id], references: [id]) + supervisor Employees? @relation("EmployeeSupervisor", fields: [supervisor_id], references: [id]) + crew Employees[] @relation("EmployeeSupervisor") + user Users @relation("UserEmployee", fields: [user_id], references: [id]) + leave_request LeaveRequests[] @relation("LeaveRequestEmployee") + timesheet Timesheets[] @relation("TimesheetEmployee") + paid_time_off PaidTimeOff? @relation("EmployeePaidTimeOff") @@map("employees") } @@ -132,16 +133,16 @@ model SchedulePresets { } model SchedulePresetShifts { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) preset_id Int bank_code_id Int - start_time DateTime @db.Time(0) - end_time DateTime @db.Time(0) - is_remote Boolean @default(false) - week_day Weekday - bank_code BankCodes @relation("SchedulePresetShiftsBankCodes", fields: [bank_code_id], references: [id]) - preset SchedulePresets @relation("SchedulePresetShiftsSchedulePreset", fields: [preset_id], references: [id]) + start_time DateTime @db.Time(0) + end_time DateTime @db.Time(0) + is_remote Boolean @default(false) + week_day Weekday + bank_code BankCodes @relation("SchedulePresetShiftsBankCodes", fields: [bank_code_id], references: [id]) + preset SchedulePresets @relation("SchedulePresetShiftsSchedulePreset", fields: [preset_id], references: [id]) @@unique([preset_id, week_day]) @@index([preset_id, week_day]) @@ -323,15 +324,15 @@ model Preferences { } model PaidTimeOff { - id Int @id @default(autoincrement()) - employee_id Int @unique - vacation_hours Int @default(0) - sick_hours Int @default(0) - last_updated DateTime @db.Date + id Int @id @default(autoincrement()) + employee_id Int @unique + vacation_hours Int @default(0) + sick_hours Int @default(0) + last_updated DateTime @db.Date - employee Employees @relation("EmployeePaidTimeOff", fields: [employee_id], references: [id]) + employee Employees @relation("EmployeePaidTimeOff", fields: [employee_id], references: [id]) - @@map("paid_time_off") + @@map("paid_time_off") } view PayPeriods { diff --git a/scripts/import-employees-from-csv.ts b/scripts/import-employees-from-csv.ts index c24e313..f31f90d 100644 --- a/scripts/import-employees-from-csv.ts +++ b/scripts/import-employees-from-csv.ts @@ -5,7 +5,7 @@ import * as path from 'path'; 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'); // Rôles éligibles pour la table Employees @@ -78,7 +78,7 @@ function parseIntSafe(value: string, fieldName: string): number | null { const n = Number.parseInt(trimmed, 10); 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 n; @@ -90,7 +90,7 @@ function millisToDate(value: string): Date | null { const ms = Number(trimmed); 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; } @@ -135,7 +135,7 @@ async function main() { return row as EmployeeCsvRow; }); - console.log(`➡️ ${csvRows.length} lignes trouvées dans le CSV employees`); + console.log(` ${csvRows.length} lignes trouvées dans le CSV employees`); // 2. Récupérer tous les emails du CSV const emails = Array.from( @@ -146,7 +146,7 @@ async function main() { ), ); - console.log(`➡️ ${emails.length} emails uniques trouvés dans le CSV`); + console.log(` ${emails.length} emails uniques trouvés dans le CSV`); // 3. Charger les users correspondants avec les bons rôles const users = (await prisma.users.findMany({ @@ -161,7 +161,7 @@ async function main() { }, })) as UserSummary[]; - console.log(`➡️ ${users.length} users éligibles trouvés dans la DB`); + console.log(` ${users.length} users éligibles trouvés dans la DB`); // Map email → user const userByEmail = new Map(); @@ -180,6 +180,7 @@ async function main() { job_title: string | null; is_supervisor: boolean; supervisor_id?: number | null; + daily_expected_hours: number; }[] = []; const rowsWithoutUser: EmployeeCsvRow[] = []; @@ -209,7 +210,7 @@ async function main() { if (!first_work_day) { console.warn( - `⚠️ 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; } @@ -220,16 +221,17 @@ async function main() { company_code, first_work_day, last_work_day, + daily_expected_hours: 8, job_title, is_supervisor, supervisor_id: null, // on pourra gérer ça plus tard si tu as les infos }); } - console.log(`➡️ ${employeesToCreate.length} entrées Employees prêtes à être insérées`); + console.log(` ${employeesToCreate.length} entrées Employees prêtes à être insérées`); if (rowsWithoutUser.length > 0) { - console.warn(`⚠️ ${rowsWithoutUser.length} lignes CSV sans user correspondant (email / rôle) :`); + console.warn(` ${rowsWithoutUser.length} lignes CSV sans user correspondant (email / rôle) :`); for (const row of rowsWithoutUser) { console.warn( ` - email=${row.email}, employee_number=${row.employee_number}, company=${row.company}`, @@ -238,7 +240,7 @@ async function main() { } 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) { console.warn( ` - email=${row.email}, employee_number="${row.employee_number}", company="${row.company}"`, @@ -247,7 +249,7 @@ async function main() { } if (employeesToCreate.length === 0) { - console.warn('⚠️ Aucun Employees à créer, arrêt.'); + console.warn(' Aucun Employees à créer, arrêt.'); return; } @@ -257,12 +259,12 @@ async function main() { skipDuplicates: true, // évite les erreurs si tu relances le script }); - console.log(`✅ ${result.count} employees insérés dans la DB`); + console.log(` ${result.count} employees insérés dans la DB`); } main() .catch((err) => { - console.error('❌ Erreur pendant l’import CSV → Employees', err); + console.error(' Erreur pendant l’import CSV → Employees', err); process.exit(1); }) .finally(async () => { diff --git a/scripts/import-users-from-csv.ts b/scripts/import-users-from-csv.ts index 66480e8..70d2c39 100644 --- a/scripts/import-users-from-csv.ts +++ b/scripts/import-users-from-csv.ts @@ -1,106 +1,106 @@ -// // src/scripts/import-users-from-csv.ts -// import { PrismaClient } from '@prisma/client'; -// import * as fs from 'fs'; -// import * as path from 'path'; +// src/scripts/import-users-from-csv.ts +import { PrismaClient } from '@prisma/client'; +import * as fs from 'fs'; +import * as path from 'path'; -// const prisma = new PrismaClient(); +const prisma = new PrismaClient(); -// // ⚙️ Chemin vers ton CSV (à adapter selon où tu le mets) -// const CSV_PATH = path.resolve(__dirname, 'data/export_employee_table.csv'); +// ⚙️ Chemin vers ton CSV (à adapter selon où tu le mets) +const CSV_PATH = path.resolve(__dirname, 'data/export_employee_table.csv'); -// // Type aligné sur les colonnes du CSV -// type CsvUserRow = { -// email: string; -// first_name: string; -// last_name: string; -// phone_number: string; -// }; +// Type aligné sur les colonnes du CSV +type CsvUserRow = { + email: string; + first_name: string; + last_name: string; + phone_number: string; +}; -// // Petit parseur de ligne CSV sans dépendance -// function splitCsvLine(line: string): string[] { -// const result: string[] = []; -// let current = ''; -// let inQuotes = false; +// Petit parseur de ligne CSV sans dépendance +function splitCsvLine(line: string): string[] { + const result: string[] = []; + let current = ''; + let inQuotes = false; -// for (let i = 0; i < line.length; i++) { -// const char = line[i]; + for (let i = 0; i < line.length; i++) { + const char = line[i]; -// if (char === '"') { -// // guillemet échappé "" -// if (inQuotes && line[i + 1] === '"') { -// current += '"'; -// i++; // on saute le deuxième -// } else { -// inQuotes = !inQuotes; -// } -// } else if (char === ',' && !inQuotes) { -// result.push(current); -// current = ''; -// } else { -// current += char; -// } -// } + if (char === '"') { + // guillemet échappé "" + if (inQuotes && line[i + 1] === '"') { + current += '"'; + i++; // on saute le deuxième + } else { + inQuotes = !inQuotes; + } + } else if (char === ',' && !inQuotes) { + result.push(current); + current = ''; + } else { + current += char; + } + } -// result.push(current); -// return result.map((v) => v.trim()); -// } + result.push(current); + return result.map((v) => v.trim()); +} -// async function main() { -// // 1. Lecture du fichier CSV -// const fileContent = fs.readFileSync(CSV_PATH, 'utf-8'); +async function main() { + // 1. Lecture du fichier CSV + const fileContent = fs.readFileSync(CSV_PATH, 'utf-8'); -// const lines = fileContent -// .split(/\r?\n/) -// .map((l) => l.trim()) -// .filter((l) => l.length > 0); + const lines = fileContent + .split(/\r?\n/) + .map((l) => l.trim()) + .filter((l) => l.length > 0); -// if (lines.length <= 1) { -// console.error('CSV vide ou seulement un header'); -// return; -// } + if (lines.length <= 1) { + console.error('CSV vide ou seulement un header'); + return; + } -// // 2. Header (noms de colonnes) -> ["email", "first_name", "last_name", "phone_number"] -// const header = splitCsvLine(lines[0]); -// const dataLines = lines.slice(1); + // 2. Header (noms de colonnes) -> ["email", "first_name", "last_name", "phone_number"] + const header = splitCsvLine(lines[0]); + const dataLines = lines.slice(1); -// // 3. Conversion de chaque ligne en objet { email, first_name, last_name, phone_number } -// const records: CsvUserRow[] = dataLines.map((line) => { -// const values = splitCsvLine(line); -// const row: any = {}; + // 3. Conversion de chaque ligne en objet { email, first_name, last_name, phone_number } + const records: CsvUserRow[] = dataLines.map((line) => { + const values = splitCsvLine(line); + const row: any = {}; -// header.forEach((col, idx) => { -// row[col] = values[idx] ?? ''; -// }); + header.forEach((col, idx) => { + row[col] = values[idx] ?? ''; + }); -// return row as CsvUserRow; -// }); + return row as CsvUserRow; + }); -// // 4. Mapping vers le format attendu par Prisma (model Users) -// const data = records.map((row) => ({ -// email: row.email.trim(), -// first_name: row.first_name.trim(), -// last_name: row.last_name.trim(), -// phone_number: row.phone_number.trim(), -// // residence: null, // si tu veux la forcer à null -// // role: 'GUEST', // sinon Prisma va appliquer la valeur par défaut -// })); + // 4. Mapping vers le format attendu par Prisma (model Users) + const data = records.map((row) => ({ + email: row.email.trim(), + first_name: row.first_name.trim(), + last_name: row.last_name.trim(), + phone_number: row.phone_number.trim(), + // residence: null, + // role: 'EMPLOYEE', + })); -// console.log(`➡️ ${data.length} lignes trouvées dans le CSV`); -// console.log('Exemple importé :', data[0]); + console.log(`➡️ ${data.length} lignes trouvées dans le CSV`); + console.log('Exemple importé :', data[0]); -// const result = await prisma.users.createMany({ -// data, -// }); + const result = await prisma.users.createMany({ + data, + }); -// console.log(`✅ ${result.count} utilisateurs insérés dans la DB`); -// } + console.log(`✅ ${result.count} utilisateurs insérés dans la DB`); +} -// main() -// .catch((err) => { -// console.error('❌ Erreur pendant l’import CSV → DB', err); -// process.exit(1); -// }) -// .finally(async () => { -// await prisma.$disconnect(); -// }); +main() + .catch((err) => { + console.error('Erreur pendant l’import CSV → DB', err); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/scripts/migrate-expenses.ts b/scripts/migrate-expenses.ts index 3f111d7..e552d5d 100644 --- a/scripts/migrate-expenses.ts +++ b/scripts/migrate-expenses.ts @@ -1,165 +1,165 @@ -// import { PrismaClient as Prisma } from "@prisma/client"; -// import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" -// import { toDateFromString, toHHmmFromDate, toStringFromDate } from "src/common/utils/date-utils"; +import { PrismaClient as Prisma } from "@prisma/client"; +import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" +import { toDateFromString, toHHmmFromDate, toStringFromDate } from "src/common/utils/date-utils"; -// const prisma_legacy = new PrismaLegacy({}); -// const prisma = new Prisma({}); +const prisma_legacy = new PrismaLegacy({}); +const prisma = new Prisma({}); -// type NewEmployee = { -// id: number; -// company_code: number; -// external_payroll_id: number; -// } +type NewEmployee = { + id: number; + company_code: number; + external_payroll_id: number; +} -// type OldExpense = { -// time_sheet_id: string | null; -// date: string | null; -// code: string | null; -// description: string | null; -// value: number | null; -// status: boolean | null; -// } +type OldExpense = { + time_sheet_id: string | null; + date: string | null; + code: string | null; + description: string | null; + value: number | null; + status: boolean | null; +} -// export const extractOldExpenses = async () => { -// for (let id = 1; id <= 61; id++) { +export const extractOldExpenses = async () => { + for (let id = 1; id <= 61; id++) { -// console.log(`Start of Expense migration ***************************************************************`); + console.log(`Start of Expense migration ***************************************************************`); -// const new_employee = await findOneNewEmployee(id); -// console.log(`Employee ${id} found in new DB`); + const new_employee = await findOneNewEmployee(id); + console.log(`Employee ${id} found in new DB`); -// const new_timesheets = await findManyNewTimesheets(new_employee.id); -// console.log(`New Timesheets found for employee ${id}`); + const new_timesheets = await findManyNewTimesheets(new_employee.id); + console.log(`New Timesheets found for employee ${id}`); -// const old_employee_id = await findOneOldEmployee(new_employee); -// console.log(`Employee ${new_employee.id} found in old DB`); + const old_employee_id = await findOneOldEmployee(new_employee); + console.log(`Employee ${new_employee.id} found in old DB`); -// const old_timesheets = await findManyOldTimesheets(old_employee_id); -// console.log(`Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`); + const old_timesheets = await findManyOldTimesheets(old_employee_id); + console.log(`Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`); -// console.log('Start of Expense creation*****************************************************************'); -// for (const old_timesheet of old_timesheets) { -// if (!old_timesheet.start_date) continue; -// const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime()); -// if (!new_timesheet) { -// console.warn(`No new timesheet matching legacy timesheet ${old_timesheet.id}`); -// continue; -// } -// const old_expenses = await prisma_legacy.expenses.findMany({ -// where: { time_sheet_id: old_timesheet.id }, -// select: { -// time_sheet_id: true, -// date: true, -// code: true, -// description: true, -// value: true, -// status: true, + console.log('Start of Expense creation*****************************************************************'); + for (const old_timesheet of old_timesheets) { + if (!old_timesheet.start_date) continue; + const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime()); + if (!new_timesheet) { + console.warn(`No new timesheet matching legacy timesheet ${old_timesheet.id}`); + continue; + } + const old_expenses = await prisma_legacy.expenses.findMany({ + where: { time_sheet_id: old_timesheet.id }, + select: { + time_sheet_id: true, + date: true, + code: true, + description: true, + value: true, + status: true, -// }, -// }); -// await createManyNewExpenses(new_timesheet.id, old_expenses); -// } + }, + }); + await createManyNewExpenses(new_timesheet.id, old_expenses); + } -// } -// await prisma_legacy.$disconnect(); -// await prisma.$disconnect(); -// } + } + await prisma_legacy.$disconnect(); + await prisma.$disconnect(); +} -// const findOneNewEmployee = async (id: number): Promise => { -// const new_employee = await prisma.employees.findUnique({ -// where: { id: id }, -// select: { -// id: true, -// company_code: true, -// external_payroll_id: true, -// }, -// }); -// if (!new_employee) throw new Error(`New Employee with id ${id} not found`) -// return new_employee; -// } +const findOneNewEmployee = async (id: number): Promise => { + const new_employee = await prisma.employees.findUnique({ + where: { id: id }, + select: { + id: true, + company_code: true, + external_payroll_id: true, + }, + }); + if (!new_employee) throw new Error(`New Employee with id ${id} not found`) + return new_employee; +} -// const findOneOldEmployee = async (new_employee: NewEmployee): Promise => { -// const old_employee = await prisma_legacy.employees.findFirst({ -// where: { -// company: new_employee.company_code, -// employee_number: new_employee.external_payroll_id.toString(), -// }, -// select: { -// id: true, -// }, -// }); -// if (!old_employee) throw new Error(`Old Employee not found`); -// return old_employee.id; -// } +const findOneOldEmployee = async (new_employee: NewEmployee): Promise => { + const old_employee = await prisma_legacy.employees.findFirst({ + where: { + company: new_employee.company_code, + employee_number: new_employee.external_payroll_id.toString(), + }, + select: { + id: true, + }, + }); + if (!old_employee) throw new Error(`Old Employee not found`); + return old_employee.id; +} -// const findManyOldTimesheets = async (old_employee_id: string) => { -// const old_timesheets = await prisma_legacy.time_sheets.findMany({ -// where: { employee_id: old_employee_id }, -// select: { id: true, start_date: true, status: true } -// }); -// return old_timesheets; -// } +const findManyOldTimesheets = async (old_employee_id: string) => { + const old_timesheets = await prisma_legacy.time_sheets.findMany({ + where: { employee_id: old_employee_id }, + select: { id: true, start_date: true, status: true } + }); + return old_timesheets; +} -// const findManyNewTimesheets = async (employee_id: number) => { -// const timesheets = await prisma.timesheets.findMany({ -// where: { employee_id: employee_id }, -// select: { id: true, start_date: true } -// }) -// return timesheets; -// } +const findManyNewTimesheets = async (employee_id: number) => { + const timesheets = await prisma.timesheets.findMany({ + where: { employee_id: employee_id }, + select: { id: true, start_date: true } + }) + return timesheets; +} -// const createManyNewExpenses = async (timesheet_id: number, old_expenses: OldExpense[]) => { -// for (const old_expense of old_expenses) { -// let mileage: number = 0; -// let amount: number = old_expense.value ?? 0; -// if (old_expense.code === 'G503') { -// mileage = old_expense.value!; -// amount = mileage * 0.72; -// } -// if (mileage < 0 || amount < 0) { -// console.warn(`expense of value less than '0' found`) -// continue; -// } +const createManyNewExpenses = async (timesheet_id: number, old_expenses: OldExpense[]) => { + for (const old_expense of old_expenses) { + let mileage: number = 0; + let amount: number = old_expense.value ?? 0; + if (old_expense.code === 'G503') { + mileage = old_expense.value!; + amount = mileage * 0.72; + } + if (mileage < 0 || amount < 0) { + console.warn(`expense of value less than '0' found`) + continue; + } -// if (old_expense.date == null) { -// console.warn(`Expense date invalid ${old_expense.date}`); -// continue; -// } -// const date = toDateFromString(old_expense.date); + if (old_expense.date == null) { + console.warn(`Expense date invalid ${old_expense.date}`); + continue; + } + const date = toDateFromString(old_expense.date); -// if (old_expense.status == null) { -// console.warn(`status null for legacy expense ${old_expense}`); -// continue; -// } + if (old_expense.status == null) { + console.warn(`status null for legacy expense ${old_expense}`); + continue; + } -// if (old_expense.code == null) { -// console.warn(`Code null for legacy expense ${old_expense.code}`); -// continue; -// } -// const bank_code_id = await findBankCodeIdUsingOldCode(old_expense.code); + if (old_expense.code == null) { + console.warn(`Code null for legacy expense ${old_expense.code}`); + continue; + } + const bank_code_id = await findBankCodeIdUsingOldCode(old_expense.code); -// await prisma.expenses.upsert({ -// where: { unique_ts_id_date_amount_mileage: { timesheet_id: timesheet_id, date, amount, mileage } }, -// update: { -// is_approved: old_expense.status, -// }, -// create: { -// date: date, -// comment: old_expense.description ?? '', -// timesheet_id: timesheet_id, -// bank_code_id: bank_code_id, -// amount: amount, -// mileage: mileage, -// } -// }); -// } -// } + await prisma.expenses.upsert({ + where: { unique_ts_id_date_amount_mileage: { timesheet_id: timesheet_id, date, amount, mileage } }, + update: { + is_approved: old_expense.status, + }, + create: { + date: date, + comment: old_expense.description ?? '', + timesheet_id: timesheet_id, + bank_code_id: bank_code_id, + amount: amount, + mileage: mileage, + } + }); + } +} -// const findBankCodeIdUsingOldCode = async (code: string): Promise => { -// const bank_code = await prisma.bankCodes.findFirst({ -// where: { bank_code: code }, -// select: { id: true }, -// }); -// if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`) -// return bank_code.id; -// } \ No newline at end of file +const findBankCodeIdUsingOldCode = async (code: string): Promise => { + const bank_code = await prisma.bankCodes.findFirst({ + where: { bank_code: code }, + select: { id: true }, + }); + if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`) + return bank_code.id; +} \ No newline at end of file diff --git a/scripts/migrate-shifts.ts b/scripts/migrate-shifts.ts index 2a9f68e..5b966c4 100644 --- a/scripts/migrate-shifts.ts +++ b/scripts/migrate-shifts.ts @@ -1,210 +1,210 @@ -// import { PrismaClient as PrismaNew } from "@prisma/client"; -// import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" +import { PrismaClient as PrismaNew } from "@prisma/client"; +import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" -// const prisma_legacy = new PrismaLegacy({}); -// const prisma = new PrismaNew({}); +const prisma_legacy = new PrismaLegacy({}); +const prisma = new PrismaNew({}); -// type NewEmployee = { -// id: number; -// company_code: number; -// external_payroll_id: number; -// } +type NewEmployee = { + id: number; + company_code: number; + external_payroll_id: number; +} -// type OldShifts = { -// time_sheet_id: string | null; -// code: string | null; -// type: string | null; -// date: Date | null; -// start_time: bigint | null; -// end_time: bigint | null; -// comment: string | null; -// status: boolean | null; -// } +type OldShifts = { + time_sheet_id: string | null; + code: string | null; + type: string | null; + date: Date | null; + start_time: bigint | null; + end_time: bigint | null; + comment: string | null; + status: boolean | null; +} -// export const extractOldShifts = async () => { -// for (let id = 1; id <= 61; id++) { -// console.log(`Start of shift migration ***************************************************************`); -// const new_employee = await findOneNewEmployee(id); -// console.log(`Employee ${id} found in new DB`); +export const extractOldShifts = async () => { + for (let id = 1; id <= 61; id++) { + console.log(`Start of shift migration ***************************************************************`); + const new_employee = await findOneNewEmployee(id); + console.log(`Employee ${id} found in new DB`); -// const new_timesheets = await findManyNewTimesheets(new_employee.id); -// console.log(`New Timesheets found for employee ${id}`); -// for (const ts of new_timesheets) { -// console.log(`start_date = ${ts.start_date} timesheet_id = ${ts.id}`) + const new_timesheets = await findManyNewTimesheets(new_employee.id); + console.log(`New Timesheets found for employee ${id}`); + for (const ts of new_timesheets) { + console.log(`start_date = ${ts.start_date} timesheet_id = ${ts.id}`) -// } -// console.log('***************************************************************'); -// const old_employee_id = await findOneOldEmployee(new_employee); -// console.log(`Employee ${new_employee.id} found in old DB`); + } + console.log('***************************************************************'); + const old_employee_id = await findOneOldEmployee(new_employee); + console.log(`Employee ${new_employee.id} found in old DB`); -// const old_timesheets = await findManyOldTimesheets(old_employee_id); -// console.log(`Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`); + const old_timesheets = await findManyOldTimesheets(old_employee_id); + console.log(`Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`); -// for (const old_timesheet of old_timesheets) { -// if (!old_timesheet.start_date) continue; -// const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime()); -// if (!new_timesheet) { -// console.warn(`No new timesheet ${new_timesheet} matching legacy timesheet ${old_timesheet.id}`); -// continue; -// } -// const old_shifts = await prisma_legacy.shifts.findMany({ -// where: { time_sheet_id: old_timesheet.id }, -// select: { -// time_sheet_id: true, -// code: true, -// type: true, -// date: true, -// start_time: true, -// end_time: true, -// comment: true, -// status: true, -// }, -// }); -// await createManyNewShifts(new_timesheet.id, old_shifts); -// } -// } -// await prisma_legacy.$disconnect(); -// await prisma.$disconnect(); -// } + for (const old_timesheet of old_timesheets) { + if (!old_timesheet.start_date) continue; + const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime()); + if (!new_timesheet) { + console.warn(`No new timesheet ${new_timesheet} matching legacy timesheet ${old_timesheet.id}`); + continue; + } + const old_shifts = await prisma_legacy.shifts.findMany({ + where: { time_sheet_id: old_timesheet.id }, + select: { + time_sheet_id: true, + code: true, + type: true, + date: true, + start_time: true, + end_time: true, + comment: true, + status: true, + }, + }); + await createManyNewShifts(new_timesheet.id, old_shifts); + } + } + await prisma_legacy.$disconnect(); + await prisma.$disconnect(); +} -// const findOneNewEmployee = async (id: number): Promise => { -// const new_employee = await prisma.employees.findUnique({ -// where: { id: id }, -// select: { -// id: true, -// company_code: true, -// external_payroll_id: true, -// }, -// }); -// if (!new_employee) throw new Error(`New Employee with id ${id} not found`) -// return new_employee; -// } +const findOneNewEmployee = async (id: number): Promise => { + const new_employee = await prisma.employees.findUnique({ + where: { id: id }, + select: { + id: true, + company_code: true, + external_payroll_id: true, + }, + }); + if (!new_employee) throw new Error(`New Employee with id ${id} not found`) + return new_employee; +} -// const findOneOldEmployee = async (new_employee: NewEmployee): Promise => { -// const old_employee = await prisma_legacy.employees.findFirst({ -// where: { -// company: new_employee.company_code, -// employee_number: new_employee.external_payroll_id.toString(), -// }, -// select: { -// id: true, -// }, -// }); -// if (!old_employee) throw new Error(`Old Employee not found`); -// return old_employee.id; -// } +const findOneOldEmployee = async (new_employee: NewEmployee): Promise => { + const old_employee = await prisma_legacy.employees.findFirst({ + where: { + company: new_employee.company_code, + employee_number: new_employee.external_payroll_id.toString(), + }, + select: { + id: true, + }, + }); + if (!old_employee) throw new Error(`Old Employee not found`); + return old_employee.id; +} -// const findManyOldTimesheets = async (old_employee_id: string) => { -// const old_timesheets = await prisma_legacy.time_sheets.findMany({ -// where: { employee_id: old_employee_id }, -// select: { id: true, start_date: true, status: true } -// }); -// return old_timesheets; -// } +const findManyOldTimesheets = async (old_employee_id: string) => { + const old_timesheets = await prisma_legacy.time_sheets.findMany({ + where: { employee_id: old_employee_id }, + select: { id: true, start_date: true, status: true } + }); + return old_timesheets; +} -// const findManyNewTimesheets = async (employee_id: number) => { -// const timesheets = await prisma.timesheets.findMany({ -// where: { employee_id: employee_id }, -// select: { id: true, start_date: true } -// }) -// return timesheets; -// } +const findManyNewTimesheets = async (employee_id: number) => { + const timesheets = await prisma.timesheets.findMany({ + where: { employee_id: employee_id }, + select: { id: true, start_date: true } + }) + return timesheets; +} -// const createManyNewShifts = async (timesheet_id: number, old_shifts: OldShifts[]) => { -// for (const old_shift of old_shifts) { -// let is_remote = true; +const createManyNewShifts = async (timesheet_id: number, old_shifts: OldShifts[]) => { + for (const old_shift of old_shifts) { + let is_remote = true; -// const start = toHHmmfromLegacyTimestamp(old_shift.start_time); -// if (old_shift.start_time == null || !start) { -// console.warn(`Shift start invalid ${old_shift.start_time}`); -// continue; -// } + const start = toHHmmfromLegacyTimestamp(old_shift.start_time); + if (old_shift.start_time == null || !start) { + console.warn(`Shift start invalid ${old_shift.start_time}`); + continue; + } -// const end = toHHmmfromLegacyTimestamp(old_shift.end_time); -// if (old_shift.end_time == null || !end) { -// console.warn(`Shift end invalid ${old_shift.end_time}`); -// continue; -// } + const end = toHHmmfromLegacyTimestamp(old_shift.end_time); + if (old_shift.end_time == null || !end) { + console.warn(`Shift end invalid ${old_shift.end_time}`); + continue; + } -// if (old_shift.date == null) { -// console.warn(`Shift date invalid ${old_shift.date}`); -// continue; -// } + if (old_shift.date == null) { + console.warn(`Shift date invalid ${old_shift.date}`); + continue; + } -// if (old_shift.status == null) { -// console.warn(`status null for legacy shift ${old_shift}`); -// continue; -// } -// if (old_shift.type == null) { -// console.warn(`type null for legacy shift ${old_shift.type}`); -// continue; -// } + if (old_shift.status == null) { + console.warn(`status null for legacy shift ${old_shift}`); + continue; + } + if (old_shift.type == null) { + console.warn(`type null for legacy shift ${old_shift.type}`); + continue; + } -// if (old_shift.type === 'office') { -// is_remote = false; -// } + if (old_shift.type === 'office') { + is_remote = false; + } -// if (old_shift.code == null) { -// console.warn(`Code null for legacy shift ${old_shift.code}`); -// continue; -// } -// const bank_code_id = await findBankCodeIdUsingOldCode(old_shift.code); -// try { -// await prisma.shifts.create({ -// // where: { unique_ts_id_date_start_time: { -// // timesheet_id, -// // date: old_shift.date, -// // start_time: toDateFromHHmm(start) }}, -// // update: { -// // start_time: toDateFromHHmm(start), -// // end_time: toDateFromHHmm(end), -// // comment: old_shift.comment, -// // is_approved: old_shift.status, -// // is_remote: is_remote, -// // bank_code_id: bank_code_id, -// // }, -// data: { -// date: old_shift.date, -// start_time: toDateFromHHmm(start), -// end_time: toDateFromHHmm(end), -// comment: old_shift.comment, -// is_approved: old_shift.status, -// is_remote: is_remote, -// timesheet_id: timesheet_id, -// bank_code_id: bank_code_id, -// }, -// }); -// } catch (error) { -// console.log('An error occured during shifts creation'); -// } -// } -// } + if (old_shift.code == null) { + console.warn(`Code null for legacy shift ${old_shift.code}`); + continue; + } + const bank_code_id = await findBankCodeIdUsingOldCode(old_shift.code); + try { + await prisma.shifts.create({ + // where: { unique_ts_id_date_start_time: { + // timesheet_id, + // date: old_shift.date, + // start_time: toDateFromHHmm(start) }}, + // update: { + // start_time: toDateFromHHmm(start), + // end_time: toDateFromHHmm(end), + // comment: old_shift.comment, + // is_approved: old_shift.status, + // is_remote: is_remote, + // bank_code_id: bank_code_id, + // }, + data: { + date: old_shift.date, + start_time: toDateFromHHmm(start), + end_time: toDateFromHHmm(end), + comment: old_shift.comment, + is_approved: old_shift.status, + is_remote: is_remote, + timesheet_id: timesheet_id, + bank_code_id: bank_code_id, + }, + }); + } catch (error) { + console.log('An error occured during shifts creation'); + } + } +} -// const toHHmmfromLegacyTimestamp = (value: bigint | null): string | null => { -// if (value == null) return null; -// const date = new Date(Number(value)); -// const hh = String(date.getHours()).padStart(2, '0'); -// const mm = String(date.getMinutes()).padStart(2, '0'); -// return `${hh}:${mm}`; -// } +const toHHmmfromLegacyTimestamp = (value: bigint | null): string | null => { + if (value == null) return null; + const date = new Date(Number(value)); + const hh = String(date.getHours()).padStart(2, '0'); + const mm = String(date.getMinutes()).padStart(2, '0'); + return `${hh}:${mm}`; +} -// const toDateFromHHmm = (hhmm: string): Date => { -// const [hh, mm] = hhmm.split(':'); -// const hours = Number(hh); -// const minutes = Number(mm); -// return new Date(Date.UTC(1970, 0, 1, hours, minutes, 0, 0)); -// } +const toDateFromHHmm = (hhmm: string): Date => { + const [hh, mm] = hhmm.split(':'); + const hours = Number(hh); + const minutes = Number(mm); + return new Date(Date.UTC(1970, 0, 1, hours, minutes, 0, 0)); +} -// const findBankCodeIdUsingOldCode = async (code: string): Promise => { -// if (code === 'G700') { -// code = 'G104'; -// } else if (code === 'G140') { -// code = 'G56' -// } -// const bank_code = await prisma.bankCodes.findFirst({ -// where: { bank_code: code }, -// select: { id: true, bank_code: true }, -// }); -// if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`) -// return bank_code.id; -// } +const findBankCodeIdUsingOldCode = async (code: string): Promise => { + if (code === 'G700') { + code = 'G104'; + } else if (code === 'G140') { + code = 'G56' + } + const bank_code = await prisma.bankCodes.findFirst({ + where: { bank_code: code }, + select: { id: true, bank_code: true }, + }); + if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`) + return bank_code.id; +} diff --git a/scripts/migrate-timesheets.ts b/scripts/migrate-timesheets.ts index dd65564..b072058 100644 --- a/scripts/migrate-timesheets.ts +++ b/scripts/migrate-timesheets.ts @@ -1,118 +1,117 @@ -// import { PrismaClient as Prisma } from "@prisma/client"; -// import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" -// import { toStringFromDate } from "src/common/utils/date-utils"; +import { PrismaClient as Prisma } from "@prisma/client"; +import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy" +import { toStringFromDate } from "src/common/utils/date-utils"; +type NewEmployee = { + id: number; + company_code: number; + external_payroll_id: number; +} -// type NewEmployee = { -// id: number; -// company_code: number; -// external_payroll_id: number; -// } +type OldTimesheets = { + id: string; + start_date: Date | null; + status: boolean | null; +} -// type OldTimesheets = { -// id: string; -// start_date: Date | null; -// status: boolean | null; -// } +const prisma_legacy = new PrismaLegacy({}); +const prisma_new = new Prisma({}); -// const prisma_legacy = new PrismaLegacy({}); -// const prisma_new = new Prisma({}); +export const extractOldTimesheets = async () => { + for (let id = 1; id <= 61; id++) { + const new_employee = await findOneNewEmployee(id); + console.log(`Employee ${id} found in new DB ${new_employee.external_payroll_id}`); -// export const extractOldTimesheets = async () => { -// for (let id = 1; id <= 61; id++) { -// const new_employee = await findOneNewEmployee(id); -// console.log(`Employee ${id} found in new DB ${new_employee.external_payroll_id}`); + const old_employee_id = await findOneOldEmployee(new_employee); + console.log(`Employee ${new_employee.id} found in old DB`); -// const old_employee_id = await findOneOldEmployee(new_employee); -// console.log(`Employee ${new_employee.id} found in old DB`); + const old_timesheets = await findManyOldTimesheets(old_employee_id); + console.log(` ${old_timesheets.length} Timesheets for employee ${old_employee_id} / ${new_employee.id} found in old DB`); -// const old_timesheets = await findManyOldTimesheets(old_employee_id); -// console.log(` ${old_timesheets.length} Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`); + await createManyNewTimesheets(old_timesheets, new_employee); + console.log(`${old_timesheets.length} New Timesheets created in new DB for employee ${new_employee.id}`); + } + await prisma_legacy.$disconnect(); + await prisma_new.$disconnect(); +} -// await createManyNewTimesheets(old_timesheets, new_employee); -// console.log(`${old_timesheets.length} New Timesheets created in new DB for employee ${new_employee.id}`); -// } -// await prisma_legacy.$disconnect(); -// await prisma_new.$disconnect(); -// } +const findOneNewEmployee = async (id: number): Promise => { + const new_employee = await prisma_new.employees.findUnique({ + where: { id: id }, + select: { + id: true, + company_code: true, + external_payroll_id: true, + }, + }); + if (!new_employee) throw new Error(`New Employee with id ${id} not found`) + return new_employee; +} -// const findOneNewEmployee = async (id: number): Promise => { -// const new_employee = await prisma_new.employees.findUnique({ -// where: { id: id }, -// select: { -// id: true, -// company_code: true, -// external_payroll_id: true, -// }, -// }); -// if (!new_employee) throw new Error(`New Employee with id ${id} not found`) -// return new_employee; -// } +const findOneOldEmployee = async (new_employee: NewEmployee): Promise => { + const employee_number = new_employee.external_payroll_id.toString() + const old_employee = await prisma_legacy.employees.findFirst({ + where: { + company: new_employee.company_code, + employee_number: employee_number, + }, + select: { + id: true, + }, + }); + if (!old_employee) throw new Error(`Old Employee not found`); + return old_employee.id; +} -// const findOneOldEmployee = async (new_employee: NewEmployee): Promise => { -// const employee_number = new_employee.external_payroll_id.toString() -// const old_employee = await prisma_legacy.employees.findFirst({ -// where: { -// company: new_employee.company_code, -// employee_number: employee_number, -// }, -// select: { -// id: true, -// }, -// }); -// if (!old_employee) throw new Error(`Old Employee not found`); -// return old_employee.id; -// } +const findManyOldTimesheets = async (old_employee_id: string) => { + const old_timesheets = await prisma_legacy.time_sheets.findMany({ + where: { employee_id: old_employee_id }, + select: { id: true, start_date: true, status: true } + }); + if (!old_timesheets) throw new Error(`old Timesheets not found for employee_id ${old_employee_id}`) + return old_timesheets; +} -// const findManyOldTimesheets = async (old_employee_id: string) => { -// const old_timesheets = await prisma_legacy.time_sheets.findMany({ -// where: { employee_id: old_employee_id }, -// select: { id: true, start_date: true, status: true } -// }); -// if (!old_timesheets) throw new Error(`old Timesheets not found for employee_id ${old_employee_id}`) -// return old_timesheets; -// } +const createManyNewTimesheets = async (old_timesheets: OldTimesheets[], new_employee: NewEmployee) => { + for (const timesheet of old_timesheets) { + if (timesheet.start_date == null) { + console.warn(`start_date invalid for legacy timesheet ${timesheet.id}`); + continue; + } + if (timesheet.status == null) { + console.warn(`status null for legacy timesheet ${timesheet.id}`); + continue; + } -// const createManyNewTimesheets = async (old_timesheets: OldTimesheets[], new_employee: NewEmployee) => { -// for (const timesheet of old_timesheets) { -// if (timesheet.start_date == null) { -// console.warn(`start_date invalid for legacy timesheet ${timesheet.id}`); -// continue; -// } -// if (timesheet.status == null) { -// console.warn(`status null for legacy timesheet ${timesheet.id}`); -// continue; -// } + try { + const new_timesheet = await prisma_new.timesheets.upsert({ + where: { employee_id_start_date: { employee_id: new_employee.id, start_date: timesheet.start_date } }, + update: { + is_approved: timesheet.status, + }, + create: { + employee_id: new_employee.id, + start_date: timesheet.start_date, + is_approved: timesheet.status, + }, + }); + if (!new_timesheet) throw new Error( + `Timesheet with start_date: ${toStringFromDate(timesheet.start_date!)} for employee ${new_employee.id} not created` + ); + } catch (error) { + throw new Error('An error occured during timesheets creation'); + } + } +} -// try { -// const new_timesheet = await prisma_new.timesheets.upsert({ -// where: { employee_id_start_date: { employee_id: new_employee.id, start_date: timesheet.start_date } }, -// update: { -// is_approved: timesheet.status, -// }, -// create: { -// employee_id: new_employee.id, -// start_date: timesheet.start_date, -// is_approved: timesheet.status, -// }, -// }); -// if (!new_timesheet) throw new Error( -// `Timesheet with start_date: ${toStringFromDate(timesheet.start_date!)} for employee ${new_employee.id} not created` -// ); -// } catch (error) { -// throw new Error('An error occured during timesheets creation'); -// } -// } -// } - -// extractOldTimesheets() -// .then(() => { -// console.log("Migration completed"); -// }) -// .catch((error) => { -// console.error("Migration failed:", error); -// }) -// .finally(async () => { -// await prisma_legacy.$disconnect(); -// await prisma_new.$disconnect(); -// }); \ No newline at end of file +extractOldTimesheets() + .then(() => { + console.log("Migration completed"); + }) + .catch((error) => { + console.error("Migration failed:", error); + }) + .finally(async () => { + await prisma_legacy.$disconnect(); + await prisma_new.$disconnect(); + }); \ No newline at end of file diff --git a/src/identity-and-account/employees/services/employees-create.service.ts b/src/identity-and-account/employees/services/employees-create.service.ts index ca4c256..2d84de5 100644 --- a/src/identity-and-account/employees/services/employees-create.service.ts +++ b/src/identity-and-account/employees/services/employees-create.service.ts @@ -43,6 +43,7 @@ export class EmployeesCreateService { external_payroll_id: dto.external_payroll_id, company_code: company_code, job_title: dto.job_title, + daily_expected_hours: 8, first_work_day: first_work_day, is_supervisor: dto.is_supervisor, supervisor_id: supervisor_id, diff --git a/src/main.ts b/src/main.ts index b5341a2..37ef124 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,6 +14,7 @@ import * as session from 'express-session'; import * as passport from 'passport'; import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaSessionStore } from '@quixo3/prisma-session-store'; +// import { initializePreferences } from 'scripts/init-preferences-access'; // import { extractOldShifts } from 'scripts/migrate-shifts'; // import { extractOldTimesheets } from 'scripts/migrate-timesheets'; // import { extractOldExpenses } from 'scripts/migrate-expenses'; @@ -95,5 +96,6 @@ async function bootstrap() { // await extractOldTimesheets(); // await extractOldShifts(); // await extractOldExpenses(); + // await initializePreferences(); } bootstrap();