fix(seeds): added larger scope of data to print

This commit is contained in:
Matthieu Haineault 2025-08-29 09:34:30 -04:00
parent 0516736fa2
commit 4bb42ec3ed
4 changed files with 127 additions and 121 deletions

View File

@ -24,15 +24,19 @@ async function main() {
const pick = <T>(arr: T[]) => arr[Math.floor(Math.random() * arr.length)]; const pick = <T>(arr: T[]) => arr[Math.floor(Math.random() * arr.length)];
// 40 employees, avec une distribution initiale
const rolesForEmployees: Roles[] = [ const rolesForEmployees: Roles[] = [
Roles.ADMIN, Roles.ADMIN,
...Array(4).fill(Roles.SUPERVISOR), ...Array(4).fill(Roles.SUPERVISOR), // 4 superviseurs
Roles.HR, Roles.HR,
Roles.ACCOUNTING, Roles.ACCOUNTING,
...Array(33).fill(Roles.EMPLOYEE), ...Array(33).fill(Roles.EMPLOYEE),
]; ];
// 40 employees // --- Normalisation : forcer user5@example.test à SUPERVISOR ---
// user5 => index 4 (i = 4)
rolesForEmployees[4] = Roles.SUPERVISOR;
for (let i = 0; i < 40; i++) { for (let i = 0; i < 40; i++) {
const fn = pick(firstNames); const fn = pick(firstNames);
const ln = pick(lastNames); const ln = pick(lastNames);
@ -40,7 +44,6 @@ async function main() {
first_name: fn, first_name: fn,
last_name: ln, last_name: ln,
email: emailFor(i), email: emailFor(i),
// on concatène proprement en string
phone_number: BASE_PHONE + i.toString(), phone_number: BASE_PHONE + i.toString(),
residence: Math.random() < 0.5 ? 'QC' : 'ON', residence: Math.random() < 0.5 ? 'QC' : 'ON',
role: rolesForEmployees[i], role: rolesForEmployees[i],
@ -61,8 +64,29 @@ async function main() {
}); });
} }
// 1) Insert (sans doublons)
await prisma.users.createMany({ data: usersData, skipDuplicates: true }); await prisma.users.createMany({ data: usersData, skipDuplicates: true });
console.log('✓ Users: 50 rows (40 employees, 10 customers)');
// 2) Validation/Correction post-insert :
// - garantir que user5@example.test est SUPERVISOR
// - si jamais le projet avait un user avec la typo, on tente aussi de le corriger (fallback)
const targetEmails = ['user5@example.test', 'user5@examplte.tset'];
for (const email of targetEmails) {
try {
await prisma.users.update({
where: { email },
data: { role: Roles.SUPERVISOR },
});
console.log(`✓ Validation: ${email} est SUPERVISOR`);
break; // on s'arrête dès qu'on a corrigé l'un des deux
} catch {
// ignore si non trouvé, on tente l'autre
}
}
// 3) Petite vérif : compter les superviseurs pour sanity check
const supCount = await prisma.users.count({ where: { role: Roles.SUPERVISOR } });
console.log(`✓ Users: 50 rows (40 employees, 10 customers) — SUPERVISORS: ${supCount}`);
} }
main().finally(() => prisma.$disconnect()); main().finally(() => prisma.$disconnect());

View File

@ -12,7 +12,7 @@ function randomPastDate(yearsBack = 3) {
past.setFullYear(now.getFullYear() - yearsBack); past.setFullYear(now.getFullYear() - yearsBack);
const t = randInt(past.getTime(), now.getTime()); const t = randInt(past.getTime(), now.getTime());
const d = new Date(t); const d = new Date(t);
d.setHours(0,0,0,0); d.setHours(0, 0, 0, 0);
return d; return d;
} }
@ -29,7 +29,12 @@ const jobTitles = [
]; ];
function randomTitle() { function randomTitle() {
return jobTitles[randInt(0, jobTitles.length -1)]; return jobTitles[randInt(0, jobTitles.length - 1)];
}
// Sélection aléatoire entre 271583 et 271585
function randomCompanyCode() {
return Math.random() < 0.5 ? 271583 : 271585;
} }
async function main() { async function main() {
@ -38,40 +43,41 @@ async function main() {
orderBy: { email: 'asc' }, orderBy: { email: 'asc' },
}); });
// Create supervisors first // 1) Trouver le user qui sera le superviseur fixe
const supervisorUsers = employeeUsers.filter(u => u.role === Roles.SUPERVISOR); const supervisorUser = await prisma.users.findUnique({
const supervisorEmployeeIds: number[] = []; where: { email: 'user5@examplte.test' },
});
for (const u of supervisorUsers) { if (!supervisorUser) {
const emp = await prisma.employees.upsert({ throw new Error("Le user 'user5@examplte.test' n'existe pas !");
where: { user_id: u.id },
update: {},
create: {
user_id: u.id,
external_payroll_id: randInt(10000, 99999),
company_code: randInt(1, 5),
first_work_day: randomPastDate(3),
last_work_day: null,
job_title: randomTitle(),
is_supervisor: true,
},
});
supervisorEmployeeIds.push(emp.id);
} }
// Create remaining employees, assign a random supervisor (admin can have none) // 2) Créer ou récupérer son employee avec is_supervisor = true
const supervisorEmp = await prisma.employees.upsert({
where: { user_id: supervisorUser.id },
update: { is_supervisor: true },
create: {
user_id: supervisorUser.id,
external_payroll_id: randInt(10000, 99999),
company_code: randomCompanyCode(),
first_work_day: randomPastDate(3),
last_work_day: null,
job_title: randomTitle(),
is_supervisor: true,
},
});
// 3) Créer tous les autres employés avec ce superviseur (sauf ADMIN qui na pas de superviseur)
for (const u of employeeUsers) { for (const u of employeeUsers) {
const already = await prisma.employees.findUnique({ where: { user_id: u.id } }); const already = await prisma.employees.findUnique({ where: { user_id: u.id } });
if (already) continue; if (already) continue;
const supervisor_id = const supervisor_id = u.role === Roles.ADMIN ? null : supervisorEmp.id;
u.role === Roles.ADMIN ? null : supervisorEmployeeIds[randInt(0, supervisorEmployeeIds.length - 1)];
await prisma.employees.create({ await prisma.employees.create({
data: { data: {
user_id: u.id, user_id: u.id,
external_payroll_id: randInt(10000, 99999), external_payroll_id: randInt(10000, 99999),
company_code: randInt(1, 5), company_code: randomCompanyCode(),
first_work_day: randomPastDate(3), first_work_day: randomPastDate(3),
last_work_day: null, last_work_day: null,
supervisor_id, supervisor_id,
@ -81,7 +87,7 @@ async function main() {
} }
const total = await prisma.employees.count(); const total = await prisma.employees.count();
console.log(`✓ Employees: ${total} rows (with supervisors linked)`); console.log(`✓ Employees: ${total} rows (supervisor = ${supervisorUser.email})`);
} }
main().finally(() => prisma.$disconnect()); main().finally(() => prisma.$disconnect());

View File

@ -3,25 +3,22 @@ import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient(); const prisma = new PrismaClient();
// ====== Config ====== // ====== Config ======
const PREVIOUS_WEEKS = 5; // nombre de semaines à générer avant la semaine actuelle const PREVIOUS_WEEKS = 5;
const INCLUDE_CURRENT = false; // passe à true si tu veux aussi générer la semaine actuelle const INCLUDE_CURRENT = false;
// Stocker une heure (Postgres TIME) via Date (UTC 1970-01-01)
function timeAt(hour: number, minute: number) { function timeAt(hour: number, minute: number) {
return new Date(Date.UTC(1970, 0, 1, hour, minute, 0)); return new Date(Date.UTC(1970, 0, 1, hour, minute, 0));
} }
// Lundi de la semaine (en UTC) pour la date courante
function mondayOfThisWeekUTC(now = new Date()) { function mondayOfThisWeekUTC(now = new Date()) {
const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())); const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
const day = d.getUTCDay(); // 0=Dim, 1=Lun, ... const day = d.getUTCDay();
const diffToMonday = (day + 6) % 7; // 0 si lundi const diffToMonday = (day + 6) % 7;
d.setUTCDate(d.getUTCDate() - diffToMonday); d.setUTCDate(d.getUTCDate() - diffToMonday);
d.setUTCHours(0, 0, 0, 0); d.setUTCHours(0, 0, 0, 0);
return d; return d;
} }
// Retourne les 5 dates Lundi→Vendredi (UTC, à minuit) à partir dun lundi donné
function weekDatesFromMonday(monday: Date) { function weekDatesFromMonday(monday: Date) {
return Array.from({ length: 5 }, (_, i) => { return Array.from({ length: 5 }, (_, i) => {
const d = new Date(monday); const d = new Date(monday);
@ -30,21 +27,19 @@ function weekDatesFromMonday(monday: Date) {
}); });
} }
// Lundi n semaines avant un lundi donné
function mondayNWeeksBefore(monday: Date, n: number) { function mondayNWeeksBefore(monday: Date, n: number) {
const d = new Date(monday); const d = new Date(monday);
d.setUTCDate(d.getUTCDate() - n * 7); d.setUTCDate(d.getUTCDate() - n * 7);
return d; return d;
} }
// Random int inclusif
function rndInt(min: number, max: number) { function rndInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min; return Math.floor(Math.random() * (max - min + 1)) + min;
} }
async function main() { async function main() {
// Bank codes utilisés // Bank codes utilisés
const BANKS = ['G1', 'G305', 'G105'] as const; const BANKS = ['G1', 'G56', 'G48', 'G700', 'G105', 'G305', 'G43'] as const;
const bcRows = await prisma.bankCodes.findMany({ const bcRows = await prisma.bankCodes.findMany({
where: { bank_code: { in: BANKS as unknown as string[] } }, where: { bank_code: { in: BANKS as unknown as string[] } },
select: { id: true, bank_code: true }, select: { id: true, bank_code: true },
@ -54,12 +49,12 @@ async function main() {
if (!bcMap.has(c)) throw new Error(`Bank code manquant: ${c}`); if (!bcMap.has(c)) throw new Error(`Bank code manquant: ${c}`);
} }
// Employés + cache des timesheets par employé (évite un findMany dans les boucles)
const employees = await prisma.employees.findMany({ select: { id: true } }); const employees = await prisma.employees.findMany({ select: { id: true } });
if (!employees.length) { if (!employees.length) {
console.log('Aucun employé — rien à insérer.'); console.log('Aucun employé — rien à insérer.');
return; return;
} }
const tsByEmp = new Map<number, { id: number }[]>(); const tsByEmp = new Map<number, { id: number }[]>();
{ {
const allTs = await prisma.timesheets.findMany({ const allTs = await prisma.timesheets.findMany({
@ -72,7 +67,6 @@ async function main() {
} }
} }
// Construit la liste des semaines à insérer
const mondayThisWeek = mondayOfThisWeekUTC(); const mondayThisWeek = mondayOfThisWeekUTC();
const mondays: Date[] = []; const mondays: Date[] = [];
@ -83,7 +77,6 @@ async function main() {
let created = 0; let created = 0;
// Pour chaque semaine à générer
for (let wi = 0; wi < mondays.length; wi++) { for (let wi = 0; wi < mondays.length; wi++) {
const monday = mondays[wi]; const monday = mondays[wi];
const weekDays = weekDatesFromMonday(monday); const weekDays = weekDatesFromMonday(monday);
@ -93,34 +86,22 @@ async function main() {
const tss = tsByEmp.get(e.id) ?? []; const tss = tsByEmp.get(e.id) ?? [];
if (!tss.length) continue; if (!tss.length) continue;
// Base horaire spécifique à lemployé (garantit la diversité) const baseStartHour = 6 + (ei % 5);
// Heures: 6..10 (selon l'index employé) const baseStartMinute = (ei * 15) % 60;
const baseStartHour = 6 + (ei % 5); // 6,7,8,9,10
// Minutes: 0, 15, 30, 45 (selon l'index employé)
const baseStartMinute = (ei * 15) % 60; // 0,15,30,45 (répète)
// 1 employé sur 5 a un jour spécial (G305/G105) par semaine
const isSpecial = (ei % 5) === 0;
const specialDayIdx = isSpecial ? ((ei + wi) % 5) : -1;
const specialCode = isSpecial ? ((ei + wi) % 2 === 0 ? 'G305' : 'G105') : 'G1';
// 5 jours (lun→ven)
for (let di = 0; di < weekDays.length; di++) { for (let di = 0; di < weekDays.length; di++) {
const date = weekDays[di]; const date = weekDays[di];
// Bank code du jour // Tirage aléatoire du bank_code
const codeToday = (di === specialDayIdx) ? specialCode : 'G1'; const randomCode = BANKS[Math.floor(Math.random() * BANKS.length)];
const bank_code_id = bcMap.get(codeToday)!; const bank_code_id = bcMap.get(randomCode)!;
// Durée aléatoire entre 4 et 10 heures
const duration = rndInt(4, 10); const duration = rndInt(4, 10);
// Variation jour+semaine pour casser les patterns (décalage 0..2h) const dayWeekOffset = (di + wi + (ei % 3)) % 3;
const dayWeekOffset = (di + wi + (ei % 3)) % 3; // 0,1,2 const startH = Math.min(12, baseStartHour + dayWeekOffset);
const startH = Math.min(12, baseStartHour + dayWeekOffset); // borne supérieure prudente
const startM = baseStartMinute; const startM = baseStartMinute;
const endH = startH + duration;
const endH = startH + duration; // <= 22 en pratique
const endM = startM; const endM = startM;
const ts = tss[(di + wi) % tss.length]; const ts = tss[(di + wi) % tss.length];
@ -129,8 +110,8 @@ async function main() {
data: { data: {
timesheet_id: ts.id, timesheet_id: ts.id,
bank_code_id, bank_code_id,
description: `Shift ${di + 1} (semaine du ${monday.toISOString().slice(0,10)}) emp ${e.id}${codeToday}`, description: `Shift ${di + 1} (semaine du ${monday.toISOString().slice(0, 10)}) emp ${e.id}${randomCode}`,
date, // Date du jour (UTC minuit) date,
start_time: timeAt(startH, startM), start_time: timeAt(startH, startM),
end_time: timeAt(endH, endM), end_time: timeAt(endH, endM),
is_approved: Math.random() < 0.5, is_approved: Math.random() < 0.5,

View File

@ -2,7 +2,7 @@ import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient(); const prisma = new PrismaClient();
// Lundi de la semaine (en UTC) pour la date courante // Lundi (UTC) de la semaine courante
function mondayOfThisWeekUTC(now = new Date()) { function mondayOfThisWeekUTC(now = new Date()) {
const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())); const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
const day = d.getUTCDay(); // 0=Dim, 1=Lun, ... const day = d.getUTCDay(); // 0=Dim, 1=Lun, ...
@ -12,7 +12,7 @@ function mondayOfThisWeekUTC(now = new Date()) {
return d; return d;
} }
// Retourne les 5 dates Lundi→Vendredi (UTC, à minuit) // Dates Lundi→Vendredi (UTC minuit)
function currentWeekDates() { function currentWeekDates() {
const monday = mondayOfThisWeekUTC(); const monday = mondayOfThisWeekUTC();
return Array.from({ length: 5 }, (_, i) => { return Array.from({ length: 5 }, (_, i) => {
@ -27,19 +27,19 @@ function rndInt(min: number, max: number) {
} }
function rndAmount(minCents: number, maxCents: number) { function rndAmount(minCents: number, maxCents: number) {
const cents = rndInt(minCents, maxCents); const cents = rndInt(minCents, maxCents);
return (cents / 100).toFixed(2); // string (ex: "123.45") return (cents / 100).toFixed(2); // string "123.45"
} }
async function main() { async function main() {
// On veut explicitement G503 (mileage) et G517 (remboursement) // Codes autorisés (aléatoires à chaque dépense)
const wanted = ['G57', 'G517'] as const; const BANKS = ['G517', 'G57', 'G502', 'G202', 'G234'] as const;
const codes = await prisma.bankCodes.findMany({ const bcRows = await prisma.bankCodes.findMany({
where: { bank_code: { in: wanted as unknown as string[] } }, where: { bank_code: { in: BANKS as unknown as string[] } },
select: { id: true, bank_code: true }, select: { id: true, bank_code: true },
}); });
const map = new Map(codes.map(c => [c.bank_code, c.id])); const bcMap = new Map(bcRows.map(c => [c.bank_code, c.id]));
for (const c of wanted) { for (const c of BANKS) {
if (!map.has(c)) throw new Error(`Bank code manquant: ${c}`); if (!bcMap.has(c)) throw new Error(`Bank code manquant: ${c}`);
} }
const employees = await prisma.employees.findMany({ select: { id: true } }); const employees = await prisma.employees.findMany({ select: { id: true } });
@ -49,64 +49,59 @@ async function main() {
} }
const weekDays = currentWeekDates(); const weekDays = currentWeekDates();
const monday = weekDays[0];
const friday = weekDays[4];
// Règles:
// - (index % 5) === 0 -> mileage G503 (km)
// - (index % 5) === 1 -> remboursement G517 ($)
// Les autres: pas de dépense
// On met la dépense un des jours de la semaine (déterministe mais varié).
let created = 0; let created = 0;
for (let ei = 0; ei < employees.length; ei++) { for (const e of employees) {
const e = employees[ei]; // Choisir un timesheet (le plus ancien, ou change 'asc'→'desc' si tu préfères le plus récent)
const ts = await prisma.timesheets.findFirst({ const ts = await prisma.timesheets.findFirst({
where: { employee_id: e.id }, where: { employee_id: e.id },
select: { id: true }, select: { id: true },
orderBy: { id: 'asc' }, // ajuste si tu préfères par date orderBy: { id: 'asc' },
}); });
if (!ts) continue; if (!ts) continue;
const dayIdx = ei % 5; // 0..4 -> répartit sur la semaine // Si lemployé a déjà une dépense cette semaine, on nen recrée pas (≥1 garanti)
const date = weekDays[dayIdx]; const already = await prisma.expenses.findFirst({
where: {
timesheet_id: ts.id,
date: { gte: monday, lte: friday },
},
select: { id: true },
});
if (already) continue;
if (ei % 5 === 0) { // Choix aléatoire du code + jour
// Mileage (G503) — amount = km const randomCode = BANKS[Math.floor(Math.random() * BANKS.length)];
const km = rndInt(10, 180); // 10..180 km const bank_code_id = bcMap.get(randomCode)!;
await prisma.expenses.create({ const date = weekDays[Math.floor(Math.random() * weekDays.length)];
data: {
timesheet_id: ts.id, // Montant aléatoire (ranges par défaut en $ — ajuste au besoin)
bank_code_id: map.get('G503')!, // (ex.: G57 plus petit, G517 remboursement plus large)
date, const amount =
amount: km.toString(), // on stocke le nombre de km dans amount (si tu as un champ "quantity_km", remplace ici) randomCode === 'G57'
attachement: null, ? rndAmount(1000, 7500) // 10.00..75.00
description: `Mileage ${km} km (emp ${e.id})`, : rndAmount(2000, 25000); // 20.00..250.00 pour les autres
is_approved: Math.random() < 0.6,
supervisor_comment: Math.random() < 0.2 ? 'OK' : null, await prisma.expenses.create({
}, data: {
}); timesheet_id: ts.id,
created++; bank_code_id,
} else if (ei % 5 === 1) { date,
// Remboursement (G517) — amount = $ amount, // stocké en string
const dollars = rndAmount(2000, 25000); // 20.00$..250.00$ attachement: null, // garde le champ tel quel si typo volontaire
await prisma.expenses.create({ description: `Expense ${randomCode} ${amount}$ (emp ${e.id})`,
data: { is_approved: Math.random() < 0.6,
timesheet_id: ts.id, supervisor_comment: Math.random() < 0.2 ? 'OK' : null,
bank_code_id: map.get('G517')!, },
date, });
amount: dollars, created++;
attachement: null,
description: `Remboursement ${dollars}$ (emp ${e.id})`,
is_approved: Math.random() < 0.6,
supervisor_comment: Math.random() < 0.2 ? 'OK' : null,
},
});
created++;
}
} }
const total = await prisma.expenses.count(); const total = await prisma.expenses.count();
console.log(`✓ Expenses: ${created} nouvelles lignes, ${total} total rows (semaine courante)`); console.log(`✓ Expenses: ${created} nouvelles lignes, ${total} total rows (≥1 expense/employee pour la semaine courante)`);
} }
main().finally(() => prisma.$disconnect()); main().finally(() => prisma.$disconnect());