import { NotFoundException } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); // ====== Config ====== const WEEKS_BACK = 4; // 4 semaines avant + semaine courante const INCLUDE_CURRENT = true; // inclure la semaine courante const STEP_CENTS = 25; // montants en quarts de dollar (.00/.25/.50/.75) // ====== Helpers dates (ancre DIMANCHE UTC) ====== function sundayOfThisWeekUTC(now = new Date()) { const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())); const day = d.getUTCDay(); // 0=Dim, 1=Lun, ... d.setUTCDate(d.getUTCDate() - day); // recule jusqu'au dimanche d.setUTCHours(0, 0, 0, 0); return d; } function sundayNWeeksBefore(sunday: Date, n: number) { const d = new Date(sunday); d.setUTCDate(d.getUTCDate() - n * 7); return d; } // Génère L→V à partir du dimanche (Lundi = dimanche + 1) function weekDatesMonToFriFromSunday(sunday: Date) { return Array.from({ length: 5 }, (_, i) => { const d = new Date(sunday); d.setUTCDate(sunday.getUTCDate() + (i + 1)); // +1..+5 return d; }); } // ====== Helpers random / amount ====== function rndInt(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1)) + min; } // String "xx.yy" à partir de cents entiers (pas de float binaire en DB) function centsToAmountString(cents: number): string { const sign = cents < 0 ? '-' : ''; const abs = Math.abs(cents); const dollars = Math.floor(abs / 100); const c = abs % 100; return `${sign}${dollars}.${c.toString().padStart(2, '0')}`; } function to2(value: string): string { return (Math.round(parseFloat(value) * 100) / 100).toFixed(2); } // Tire un multiple de STEP_CENTS entre minCents et maxCents (inclus) function rndQuantizedCents(minCents: number, maxCents: number, step = STEP_CENTS): number { const qmin = Math.ceil(minCents / step); const qmax = Math.floor(maxCents / step); const q = rndInt(qmin, qmax); return q * step; } function rndAmount(minCents: number, maxCents: number): string { return centsToAmountString(rndQuantizedCents(minCents, maxCents)); } // ====== Lookup timesheet (AUCUNE création ici) ====== async function findTimesheet(employee_id: number, start_date: Date) { return prisma.timesheets.findUnique({ where: { employee_id_start_date: { employee_id, start_date } }, select: { id: true }, }); } async function main() { // Codes d'EXPENSES (exemples) const BANKS = ['G517', 'G503', 'G502', 'G202'] as const; // Précharger les bank codes const bcRows = await prisma.bankCodes.findMany({ where: { bank_code: { in: BANKS as unknown as string[] } }, select: { id: true, bank_code: true }, }); const bcMap = new Map(bcRows.map(c => [c.bank_code, c.id])); for (const c of BANKS) { if (!bcMap.has(c)) throw new Error(`Bank code manquant: ${c}`); } // Employés const employees = await prisma.employees.findMany({ select: { id: true } }); if (!employees.length) { console.warn('Aucun employé — rien à insérer.'); return; } // Fenêtre de semaines ancrées au DIMANCHE const sundayThisWeek = sundayOfThisWeekUTC(); const sundays: Date[] = []; if (INCLUDE_CURRENT) sundays.push(sundayThisWeek); for (let n = 1; n <= WEEKS_BACK; n++) sundays.push(sundayNWeeksBefore(sundayThisWeek, n)); let created = 0; for (const sunday of sundays) { const weekDays = weekDatesMonToFriFromSunday(sunday); // L→V const monday = weekDays[0]; const friday = weekDays[4]; for (const e of employees) { // Utiliser le timesheet EXISTANT (ancré au DIMANCHE) const ts = await findTimesheet(e.id, sunday); if (!ts) throw new NotFoundException(`Timesheet manquant pour emp ${e.id} @ ${sunday.toISOString().slice(0,10)}`); // Idempotence: si déjà au moins une expense L→V, on skip la semaine const already = await prisma.expenses.findFirst({ where: { timesheet_id: ts.id, date: { gte: monday, lte: friday } }, select: { id: true }, }); if (already) continue; // 1 à 3 expenses (jours distincts) const count = rndInt(1, 3); const dayIndexes = [0, 1, 2, 3, 4].sort(() => Math.random() - 0.5).slice(0, count); for (const idx of dayIndexes) { const date = weekDays[idx]; const code = BANKS[rndInt(0, BANKS.length - 1)]; const bank_code_id = bcMap.get(code)!; // Montants (cents) quantisés à 25¢ => aucun flottant binaire en DB let amount: string = '0.00'; let mileage: string = '0.00'; switch (code) { case 'G503': // kilométrage mileage = to2(rndAmount(1000, 7500)); // 10.00 à 75.00 break; case 'G502': // per_diem amount = to2(rndAmount(1500, 3000)); // 15.00 à 30.00 break; case 'G202': // on_call / prime de garde amount = to2(rndAmount(2000, 15000)); // 20.00 à 150.00 break; case 'G517': // expenses default: amount = to2(rndAmount(500, 5000)); // 5.00 à 50.00 break; } await prisma.expenses.create({ data: { timesheet_id: ts.id, bank_code_id, date, amount, mileage, attachment: null, comment: `Expense ${code} (emp ${e.id})`, is_approved: Math.random() < 0.65, supervisor_comment: Math.random() < 0.25 ? 'OK' : null, }, }); created++; } } } const total = await prisma.expenses.count(); console.log(`✓ Expenses: ${created} nouvelles lignes, ${total} total rows (ancre dimanche, L→V, sem courante ${INCLUDE_CURRENT ? 'incluse' : 'exclue'} + ${WEEKS_BACK} précédentes)`); } main().finally(() => prisma.$disconnect());