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