targo-backend/scripts/done/import-employees-from-csv.ts

271 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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<string, UserSummary>();
for (const user of users) {
const key = user.email.trim().toLowerCase();
userByEmail.set(key, user);
}
// 4. Construire les données pour employees.createMany
const employeesToCreate: {
user_id: string;
external_payroll_id: number;
company_code: number;
first_work_day: Date;
last_work_day: Date | null;
job_title: string | null;
is_supervisor: boolean;
supervisor_id?: number | null;
}[] = [];
const rowsWithoutUser: EmployeeCsvRow[] = [];
const rowsWithInvalidNumbers: EmployeeCsvRow[] = [];
for (const row of csvRows) {
const emailKey = row.email.trim().toLowerCase();
const user = userByEmail.get(emailKey);
if (!user) {
rowsWithoutUser.push(row);
continue;
}
const external_payroll_id = parseIntSafe(row.employee_number, 'external_payroll_id');
const company_code = parseIntSafe(String(row.company), 'company_code');
if (external_payroll_id === null || company_code === null) {
rowsWithInvalidNumbers.push(row);
continue;
}
const first_work_day = millisToDate(row.onboarding);
const last_work_day = millisToDate(row.offboarding);
const is_supervisor = parseBoolean(row.is_supervisor);
const job_title = row.job_title?.trim() || null;
if (!first_work_day) {
console.warn(
`⚠️ Date d'onboarding invalide pour ${row.email} (employee_number=${row.employee_number})`,
);
continue;
}
employeesToCreate.push({
user_id: user.id,
external_payroll_id,
company_code,
first_work_day,
last_work_day,
job_title,
is_supervisor,
supervisor_id: null, // on pourra gérer ça plus tard si tu as les infos
});
}
console.log(`➡️ ${employeesToCreate.length} entrées Employees prêtes à être insérées`);
if (rowsWithoutUser.length > 0) {
console.warn(`⚠️ ${rowsWithoutUser.length} lignes CSV sans user correspondant (email / rôle) :`);
for (const row of rowsWithoutUser) {
console.warn(
` - email=${row.email}, employee_number=${row.employee_number}, company=${row.company}`,
);
}
}
if (rowsWithInvalidNumbers.length > 0) {
console.warn(`⚠️ ${rowsWithInvalidNumbers.length} lignes CSV avec ids/compagnies invalides :`);
for (const row of rowsWithInvalidNumbers) {
console.warn(
` - email=${row.email}, employee_number="${row.employee_number}", company="${row.company}"`,
);
}
}
if (employeesToCreate.length === 0) {
console.warn('⚠️ Aucun Employees à créer, arrêt.');
return;
}
// 5. Insert en batch
const result = await prisma.employees.createMany({
data: employeesToCreate,
skipDuplicates: true, // évite les erreurs si tu relances le script
});
console.log(`${result.count} employees insérés dans la DB`);
}
main()
.catch((err) => {
console.error('❌ Erreur pendant limport CSV → Employees', err);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});