162 lines
5.5 KiB
TypeScript
162 lines
5.5 KiB
TypeScript
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 ======
|
|
function mondayOfThisWeekUTC(now = new Date()) {
|
|
const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
|
const day = d.getUTCDay();
|
|
const diffToMonday = (day + 6) % 7;
|
|
d.setUTCDate(d.getUTCDate() - diffToMonday);
|
|
d.setUTCHours(0, 0, 0, 0);
|
|
return d;
|
|
}
|
|
function mondayNWeeksBefore(monday: Date, n: number) {
|
|
const d = new Date(monday);
|
|
d.setUTCDate(monday.getUTCDate() - n * 7);
|
|
return d;
|
|
}
|
|
// L→V (UTC minuit)
|
|
function weekDatesMonToFri(monday: Date) {
|
|
return Array.from({ length: 5 }, (_, i) => {
|
|
const d = new Date(monday);
|
|
d.setUTCDate(monday.getUTCDate() + i);
|
|
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 (jamais de float)
|
|
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')}`;
|
|
}
|
|
// 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));
|
|
}
|
|
|
|
// ====== Timesheet upsert ======
|
|
async function getOrCreateTimesheet(employee_id: number, start_date: Date) {
|
|
return prisma.timesheets.upsert({
|
|
where: { employee_id_start_date: { employee_id, start_date } },
|
|
update: {},
|
|
create: { employee_id, start_date, is_approved: Math.random() < 0.3 },
|
|
select: { id: true },
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
// Codes d'EXPENSES (exemples)
|
|
const BANKS = ['G517', 'G503', 'G502', 'G202', 'G234'] 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;
|
|
}
|
|
|
|
// Liste des lundis (courant + 4 précédents)
|
|
const mondayThisWeek = mondayOfThisWeekUTC();
|
|
const mondays: Date[] = [];
|
|
if (INCLUDE_CURRENT) mondays.push(mondayThisWeek);
|
|
for (let n = 1; n <= WEEKS_BACK; n++) mondays.push(mondayNWeeksBefore(mondayThisWeek, n));
|
|
|
|
let created = 0;
|
|
|
|
for (const monday of mondays) {
|
|
const weekDays = weekDatesMonToFri(monday);
|
|
const friday = weekDays[4];
|
|
|
|
for (const e of employees) {
|
|
// Upsert timesheet pour CETTE semaine/employee
|
|
const ts = await getOrCreateTimesheet(e.id, monday);
|
|
|
|
// 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 plus tard
|
|
let amount: string;
|
|
switch (code) {
|
|
case 'G503': // petites fournitures
|
|
amount = rndAmount(1000, 7500); // 10.00 à 75.00
|
|
break;
|
|
case 'G502': // repas
|
|
amount = rndAmount(1500, 3000); // 15.00 à 30.00
|
|
break;
|
|
case 'G202': // essence
|
|
amount = rndAmount(2000, 15000); // 20.00 à 150.00
|
|
break;
|
|
case 'G234': // hébergement
|
|
amount = rndAmount(6000, 25000); // 60.00 à 250.00
|
|
break;
|
|
case 'G517': // péages / divers
|
|
default:
|
|
amount = rndAmount(500, 5000); // 5.00 à 50.00
|
|
break;
|
|
}
|
|
|
|
await prisma.expenses.create({
|
|
data: {
|
|
timesheet_id: ts.id,
|
|
bank_code_id,
|
|
date,
|
|
amount, // string "xx.yy" (2 décimales exactes)
|
|
attachement: null,
|
|
comment: `Expense ${code} ${amount}$ (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 (sem courante + ${WEEKS_BACK} précédentes, L→V uniquement, montants en quarts de dollar)`);
|
|
}
|
|
|
|
main().finally(() => prisma.$disconnect());
|