clean(modules): modules file cleaning

This commit is contained in:
Matthieu Haineault 2025-12-04 15:19:19 -05:00
parent e4447a138c
commit 8fdbdafd91
47 changed files with 1 additions and 2787 deletions

View File

@ -22,23 +22,7 @@
"test:e2e:ci": "cross-env NODE_ENV=test E2E_RESET_DB=1 jest --config ./test/jest-e2e.json --runInBand --verbose",
"prisma:generate": "prisma generate",
"db:migrate": "prisma migrate dev --name init",
"db:reset": "prisma migrate reset --force",
"seed:01": "tsx prisma/mock-seeds-scripts/01-bankcodes.ts",
"seed:02": "tsx prisma/mock-seeds-scripts/02-users.ts",
"seed:03": "tsx prisma/mock-seeds-scripts/03-employees.ts",
"seed:04": "tsx prisma/mock-seeds-scripts/04-customers.ts",
"seed:05": "tsx prisma/mock-seeds-scripts/05-employees-archive.ts",
"seed:06": "tsx prisma/mock-seeds-scripts/06-customers-archive.ts",
"seed:07": "tsx prisma/mock-seeds-scripts/07-leave-requests-future.ts",
"seed:08": "tsx prisma/mock-seeds-scripts/08-leave-requests-archive.ts",
"seed:09": "tsx prisma/mock-seeds-scripts/09-timesheets.ts",
"seed:10": "tsx prisma/mock-seeds-scripts/10-shifts.ts",
"seed:11": "tsx prisma/mock-seeds-scripts/11-shifts-archive.ts",
"seed:12": "tsx prisma/mock-seeds-scripts/12-expenses.ts",
"seed:13": "tsx prisma/mock-seeds-scripts/13-expenses-archive.ts",
"seed:14": "tsx prisma/mock-seeds-scripts/14-oauth-sessions.ts",
"seed:all": "npm run seed:01 && npm run seed:02 && npm run seed:03 && npm run seed:09 && npm run seed:10 && npm run seed:12 && npm run seed:14",
"db:reseed": "npm run db:reset && npm run seed:all"
"db:reset": "prisma migrate reset --force"
},
"dependencies": {
"@nestjs/common": "^11.0.1",

View File

@ -1,35 +0,0 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const presets = [
// type, categorie, modifier, bank_code
['REGULAR' ,'SHIFT' , 1.0 , 'G1' ],
['OVERTIME' ,'SHIFT' , 2 , 'G43' ],
['EMERGENCY' ,'SHIFT' , 2 , 'G48' ],
['EVENING' ,'SHIFT' , 1.25 , 'G56' ],
['SICK' ,'SHIFT' , 1.0 , 'G105'],
['HOLIDAY' ,'SHIFT' , 1.0 , 'G104'],
['VACATION' ,'SHIFT' , 1.0 , 'G305'],
['ON_CALL' ,'EXPENSE' , 1.0 , 'G202'],
['COMMISSION' ,'EXPENSE' , 1.0 , 'G234'],
['PER_DIEM' ,'EXPENSE' , 1.0 , 'G502'],
['MILEAGE' ,'EXPENSE' , 0.72 , 'G503'],
['EXPENSES' ,'EXPENSE' , 1.0 , 'G517'],
];
await prisma.bankCodes.createMany({
data: presets.map(([type, categorie, modifier, bank_code]) => ({
type: String(type),
categorie: String(categorie),
modifier: Number(modifier),
bank_code: String(bank_code),
})),
skipDuplicates: true,
});
console.log('✓ BankCodes: 9 rows');
}
main().finally(() => prisma.$disconnect());

View File

@ -1,200 +0,0 @@
import { PrismaClient, Roles } from '@prisma/client';
const prisma = new PrismaClient();
// base sans underscore, en string
const BASE_PHONE = '1100000000';
function emailFor(i: number) {
return `user${i + 1}@example.test`;
}
async function main() {
type UserSeed = {
first_name: string;
last_name: string;
email: string;
phone_number: string;
residence?: string | null;
role: Roles;
};
const usersData: UserSeed[] = [];
const firstNames = ['Alex', 'Sam', 'Chris', 'Jordan', 'Taylor', 'Morgan', 'Jamie', 'Robin', 'Avery', 'Casey'];
const lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Davis', 'Wilson', 'Taylor', 'Clark'];
const pick = <T,>(arr: T[]) => arr[Math.floor(Math.random() * arr.length)];
/**
* Objectif total: 50 users
* - 39 employés génériques (dont ADMIN=1, SUPERVISOR=3, HR=1, ACCOUNTING=1, EMPLOYEE=33)
* - +1 superviseur spécial "User Test" (=> 40 employés)
* - 10 customers
*/
const rolesForEmployees39: Roles[] = [
Roles.ADMIN, // 1
...Array(3).fill(Roles.SUPERVISOR), // 3 supervisors (le 4e sera "User Test")
Roles.HR, // 1
Roles.ACCOUNTING, // 1
...Array(33).fill(Roles.EMPLOYEE), // 33
// total = 39
];
// --- 39 employés génériques: user1..user39@example.test
for (let i = 0; i < 39; i++) {
const fn = pick(firstNames);
const ln = pick(lastNames);
usersData.push({
first_name: fn,
last_name: ln,
email: emailFor(i),
phone_number: BASE_PHONE + i.toString(),
residence: Math.random() < 0.5 ? 'QC' : 'ON',
role: rolesForEmployees39[i],
});
}
// --- 10 customers: user40..user49@example.test
for (let i = 39; i < 49; i++) {
const fn = pick(firstNames);
const ln = pick(lastNames);
usersData.push({
first_name: fn,
last_name: ln,
email: emailFor(i),
phone_number: BASE_PHONE + i.toString(),
residence: Math.random() < 0.5 ? 'QC' : 'ON',
role: Roles.CUSTOMER,
});
}
// 1) Insert des 49 génériques (skipDuplicates pour rejouer le seed sans erreurs)
await prisma.users.createMany({ data: usersData, skipDuplicates: true });
// 2) Upsert du superviseur spécial "User Test"
const specialEmail = 'user@targointernet.com';
const specialUser = await prisma.users.upsert({
where: { email: specialEmail },
update: {
first_name: 'User',
last_name: 'Test',
role: Roles.SUPERVISOR,
residence: 'QC',
phone_number: BASE_PHONE + '999',
},
create: {
first_name: 'User',
last_name: 'Test',
email: specialEmail,
role: Roles.SUPERVISOR,
residence: 'QC',
phone_number: BASE_PHONE + '999',
},
});
// 3) Créer/mettre à jour les entrées Employees pour tous les rôles employés
const employeeUsers = await prisma.users.findMany({
where: { role: { in: [Roles.ADMIN, Roles.SUPERVISOR, Roles.HR, Roles.ACCOUNTING, Roles.EMPLOYEE] } },
orderBy: { email: 'asc' },
});
const firstWorkDay = new Date('2025-01-06'); // à adapter à ton contexte
for (let i = 0; i < employeeUsers.length; i++) {
const u = employeeUsers[i];
await prisma.employees.upsert({
where: { user_id: u.id },
update: {
is_supervisor: u.role === Roles.SUPERVISOR,
job_title: u.role,
},
create: {
user_id: u.id,
is_supervisor: u.role === Roles.SUPERVISOR,
external_payroll_id: 1000 + i, // à adapter
company_code: 1, // à adapter
first_work_day: firstWorkDay,
job_title: u.role,
},
});
}
// 4) Répartition des 33 EMPLOYEE sur 4 superviseurs: 8/8/8/9 (9 pour User Test)
const supervisors = await prisma.employees.findMany({
where: { is_supervisor: true, user: { role: Roles.SUPERVISOR } },
include: { user: true },
orderBy: { id: 'asc' },
});
const userTestSupervisor = supervisors.find((s) => s.user.email === specialEmail);
if (!userTestSupervisor) {
throw new Error('Employee(User Test) introuvable — vérifie le upsert Users/Employees.');
}
const plainEmployees = await prisma.employees.findMany({
where: { is_supervisor: false, user: { role: Roles.EMPLOYEE } },
orderBy: { id: 'asc' },
});
// Si la configuration est bien 4 superviseurs + 33 employés, on force 8/8/8/9 avec 9 pour User Test.
if (supervisors.length === 4 && plainEmployees.length === 33) {
const others = supervisors.filter((s) => s.id !== userTestSupervisor.id);
// ordre: autres (3) puis User Test en dernier (reçoit 9)
const ordered = [...others, userTestSupervisor];
const chunks = [
plainEmployees.slice(0, 8), // -> sup 0
plainEmployees.slice(8, 16), // -> sup 1
plainEmployees.slice(16, 24), // -> sup 2
plainEmployees.slice(24, 33), // -> sup 3 (User Test) = 9
];
for (let b = 0; b < chunks.length; b++) {
const sup = ordered[b];
for (const emp of chunks[b]) {
await prisma.employees.update({
where: { id: emp.id },
data: { supervisor_id: sup.id },
});
}
}
} else {
// fallback: distribution round-robin si la config diffère
console.warn(
`Répartition fallback (round-robin). Supervisors=${supervisors.length}, Employees=${plainEmployees.length}`
);
const others = supervisors.filter((s) => s.id !== userTestSupervisor.id);
const ordered = [...others, userTestSupervisor];
for (let i = 0; i < plainEmployees.length; i++) {
const sup = ordered[i % ordered.length];
await prisma.employees.update({
where: { id: plainEmployees[i].id },
data: { supervisor_id: sup.id },
});
}
}
// 5) Sanity checks
const totalUsers = await prisma.users.count();
const supCount = await prisma.users.count({ where: { role: Roles.SUPERVISOR } });
const empCount = await prisma.users.count({ where: { role: Roles.EMPLOYEE } });
const countForUserTest = await prisma.employees.count({
where: { supervisor_id: userTestSupervisor.id, is_supervisor: false },
});
console.log(`✓ Users total: ${totalUsers} (attendu 50)`);
console.log(`✓ Supervisors: ${supCount} (attendu 4)`);
console.log(`✓ Employees : ${empCount} (attendu 33)`);
console.log(`✓ Employés sous User Test: ${countForUserTest} (attendu 9)`);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@ -1,93 +0,0 @@
import { PrismaClient, Roles } from '@prisma/client';
const prisma = new PrismaClient();
function randInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomPastDate(yearsBack = 3) {
const now = new Date();
const past = new Date(now);
past.setFullYear(now.getFullYear() - yearsBack);
const t = randInt(past.getTime(), now.getTime());
const d = new Date(t);
d.setHours(0, 0, 0, 0);
return d;
}
const jobTitles = [
'Directeur des ventes',
'Directeur technique',
'Programmeur',
'Technicien',
'Comptable',
'Magasinier',
'Responsable Resources Humaines',
'Conseiller en vente',
'Support technique',
];
function randomTitle() {
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() {
const employeeUsers = await prisma.users.findMany({
where: { role: { in: [Roles.ADMIN, Roles.SUPERVISOR, Roles.HR, Roles.ACCOUNTING, Roles.EMPLOYEE] } },
orderBy: { email: 'asc' },
});
// 1) Trouver le user qui sera le superviseur fixe
const supervisorUser = await prisma.users.findUnique({
where: { email: 'user5@example.test' },
});
if (!supervisorUser) {
throw new Error("Le user 'user5@example.test' n'existe pas !");
}
// 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) {
const already = await prisma.employees.findUnique({ where: { user_id: u.id } });
if (already) continue;
const supervisor_id = u.role === Roles.ADMIN ? null : supervisorEmp.id;
await prisma.employees.create({
data: {
user_id: u.id,
external_payroll_id: randInt(10000, 99999),
company_code: randomCompanyCode(),
first_work_day: randomPastDate(3),
last_work_day: null,
supervisor_id,
job_title: randomTitle(),
},
});
}
const total = await prisma.employees.count();
console.log(`✓ Employees: ${total} rows (supervisor = ${supervisorUser.email})`);
}
main().finally(() => prisma.$disconnect());

View File

@ -1,28 +0,0 @@
// import { PrismaClient, Roles } from '@prisma/client';
// const prisma = new PrismaClient();
// async function main() {
// const customerUsers = await prisma.users.findMany({
// where: { role: Roles.CUSTOMER },
// orderBy: { email: 'asc' },
// });
// let i = 0;
// for (const u of customerUsers) {
// await prisma.customers.upsert({
// where: { user_id: u.id },
// update: {},
// create: {
// user_id: u.id,
// invoice_id: i % 2 === 0 ? 100000 + i : null, // 1 sur 2 a un invoice_id
// },
// });
// i++;
// }
// const total = await prisma.customers.count();
// console.log(`✓ Customers: ${total} rows`);
// }
// main().finally(() => prisma.$disconnect());

View File

@ -1,45 +0,0 @@
// import { PrismaClient } from '@prisma/client';
// if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
// console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)");
// process.exit(0);
// }
// const prisma = new PrismaClient();
// function daysAgo(n: number) {
// const d = new Date();
// d.setDate(d.getDate() - n);
// d.setHours(0,0,0,0);
// return d;
// }
// async function main() {
// const employees = await prisma.employees.findMany({
// include: { user: true },
// take: 10, // archive 10
// });
// for (const e of employees) {
// await prisma.employeesArchive.create({
// data: {
// employee_id: e.id,
// user_id: e.user_id,
// first_name: e.user.first_name,
// last_name: e.user.last_name,
// external_payroll_id: e.external_payroll_id,
// company_code: e.company_code,
// first_work_day: e.first_work_day,
// last_work_day: daysAgo(30),
// supervisor_id: e.supervisor_id ?? null,
// job_title: e.job_title,
// is_supervisor: e.is_supervisor,
// },
// });
// }
// const total = await prisma.employeesArchive.count();
// console.log(`✓ EmployeesArchive: ${total} rows (added 10)`);
// }
// main().finally(() => prisma.$disconnect());

View File

@ -1,35 +0,0 @@
// // prisma/mock-seeds-scripts/06-customers-archive.ts
// import { PrismaClient } from '@prisma/client';
// if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
// console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)");
// process.exit(0);
// }
// const prisma = new PrismaClient();
// async function main() {
// const customers = await prisma.customers.findMany({
// orderBy: { id: 'asc' },
// take: 5,
// });
// for (const c of customers) {
// const invoiceId = 200000 + c.id; // déterministe, stable entre runs
// await prisma.customersArchive.upsert({
// where: { invoice_id: invoiceId }, // invoice_id est unique
// update: {}, // idempotent
// create: {
// customer_id: c.id,
// user_id: c.user_id,
// invoice_id: invoiceId,
// },
// });
// }
// const total = await prisma.customersArchive.count();
// console.log(`✓ CustomersArchive upserted for ${customers.length} customers (total=${total})`);
// }
// main().finally(() => prisma.$disconnect());

View File

@ -1,82 +0,0 @@
// import { PrismaClient, Prisma, LeaveTypes, LeaveApprovalStatus } from '@prisma/client';
// if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
// console.log('?? Seed leave-requests ignor<6F> (SKIP_LEAVE_REQUESTS=true)');
// process.exit(0);
// }
// const prisma = new PrismaClient();
// function dateOn(y: number, m: number, d: number) {
// // stocke une date (@db.Date) <20> minuit UTC
// return new Date(Date.UTC(y, m - 1, d, 0, 0, 0));
// }
// async function main() {
// const year = new Date().getFullYear();
// const today = new Date();
// const employees = await prisma.employees.findMany({ select: { id: true } });
// const bankCodes = await prisma.bankCodes.findMany({
// where: { categorie: 'LEAVE' },
// select: { id: true, type: true },
// });
// if (!employees.length || !bankCodes.length) {
// console.warn('No employees or LEAVE bank codes; aborting');
// return;
// }
// const types: LeaveTypes[] = [
// LeaveTypes.SICK,
// LeaveTypes.VACATION,
// LeaveTypes.UNPAID,
// LeaveTypes.BEREAVEMENT,
// LeaveTypes.PARENTAL,
// LeaveTypes.LEGAL,
// LeaveTypes.WEDDING,
// ];
// const statuses: LeaveApprovalStatus[] = [
// LeaveApprovalStatus.PENDING,
// LeaveApprovalStatus.APPROVED,
// LeaveApprovalStatus.DENIED,
// LeaveApprovalStatus.CANCELLED,
// LeaveApprovalStatus.ESCALATED,
// ];
// const futureMonths = [8, 9, 10, 11, 12]; // Ao<41>t ? D<>c. (1-based)
// const rows: Prisma.LeaveRequestsCreateManyInput[] = [];
// for (let i = 0; i < 10; i++) {
// const emp = employees[i % employees.length];
// const m = futureMonths[i % futureMonths.length];
// const date = dateOn(year, m, 5 + i); // 5..14
// if (date <= today) continue; // garantir <20> futur <20>
// const type = types[i % types.length];
// const status = statuses[i % statuses.length];
// const bc = bankCodes[i % bankCodes.length];
// const requestedHours = 4 + (i % 5); // 4 ? 8 h
// const payableHours = status === LeaveApprovalStatus.APPROVED ? Math.min(requestedHours, 8) : null;
// rows.push({
// employee_id: emp.id,
// bank_code_id: bc.id,
// leave_type: type,
// date,
// comment: `Future leave #${i + 1} (${bc.type})`,
// approval_status: status,
// requested_hours: requestedHours,
// payable_hours: payableHours,
// });
// }
// if (rows.length) {
// await prisma.leaveRequests.createMany({ data: rows });
// }
// console.log(`? LeaveRequests (future): ${rows.length} rows`);
// }
// main().finally(() => prisma.$disconnect());

View File

@ -1,87 +0,0 @@
// import { PrismaClient, LeaveApprovalStatus, LeaveTypes } from '@prisma/client';
// if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
// console.log('?? Seed leave-requests ignor<6F> (SKIP_LEAVE_REQUESTS=true)');
// process.exit(0);
// }
// const prisma = new PrismaClient();
// function daysAgo(n: number) {
// const d = new Date();
// d.setUTCDate(d.getUTCDate() - n);
// d.setUTCHours(0, 0, 0, 0);
// return d;
// }
// async function main() {
// const employees = await prisma.employees.findMany({ select: { id: true } });
// if (!employees.length) {
// throw new Error('Aucun employ<6F> trouv<75>. Ex<45>cute le seed employees avant celui-ci.');
// }
// const leaveCodes = await prisma.bankCodes.findMany({
// where: { type: { in: ['SICK', 'VACATION', 'HOLIDAY'] } },
// select: { id: true, type: true },
// });
// if (!leaveCodes.length) {
// throw new Error("Aucun bank code trouv<75> avec type in ('SICK','VACATION','HOLIDAY'). V<>rifie ta table bank_codes.");
// }
// const statuses = Object.values(LeaveApprovalStatus);
// const created = [] as Array<{ id: number; employee_id: number; leave_type: LeaveTypes; date: Date; comment: string; approval_status: LeaveApprovalStatus; requested_hours: number; payable_hours: number | null }>;
// const COUNT = 12;
// for (let i = 0; i < COUNT; i++) {
// const emp = employees[i % employees.length];
// const leaveCode = leaveCodes[Math.floor(Math.random() * leaveCodes.length)];
// const date = daysAgo(120 - i * 3);
// const status = statuses[(i + 2) % statuses.length];
// const requestedHours = 4 + (i % 5); // 4 ? 8 h
// const payableHours = status === LeaveApprovalStatus.APPROVED ? Math.min(requestedHours, 8) : null;
// const lr = await prisma.leaveRequests.create({
// data: {
// employee_id: emp.id,
// bank_code_id: leaveCode.id,
// leave_type: leaveCode.type as LeaveTypes,
// date,
// comment: `Past leave #${i + 1} (${leaveCode.type})`,
// approval_status: status,
// requested_hours: requestedHours,
// payable_hours: payableHours,
// },
// });
// created.push({
// id: lr.id,
// employee_id: lr.employee_id,
// leave_type: lr.leave_type,
// date: lr.date,
// comment: lr.comment,
// approval_status: lr.approval_status,
// requested_hours: requestedHours,
// payable_hours: payableHours,
// });
// }
// for (const lr of created) {
// await prisma.leaveRequestsArchive.create({
// data: {
// leave_request_id: lr.id,
// employee_id: lr.employee_id,
// leave_type: lr.leave_type,
// date: lr.date,
// comment: lr.comment,
// approval_status: lr.approval_status,
// requested_hours: lr.requested_hours,
// payable_hours: lr.payable_hours,
// },
// });
// }
// console.log(`? LeaveRequestsArchive: ${created.length} rows`);
// }
// main().finally(() => prisma.$disconnect());

View File

@ -1,62 +0,0 @@
import { PrismaClient, Prisma } from '@prisma/client';
const prisma = new PrismaClient();
// ====== Config ======
const PREVIOUS_WEEKS = 16; // nombre de semaines à créer (passé)
const INCLUDE_CURRENT = true; // true si tu veux aussi la semaine courante
// Dimanche (UTC) de la semaine courante
function sundayOfThisWeekUTC(now = new Date()) {
// normalise à minuit UTC du jour courant
const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
const day = d.getUTCDay(); // 0=Dim, 1=Lun, ... 6=Sam
// recule jusqu'au dimanche de cette semaine
d.setUTCDate(d.getUTCDate() - day);
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;
}
async function main() {
const employees = await prisma.employees.findMany({ select: { id: true } });
if (!employees.length) {
console.warn('Aucun employé — rien à insérer.');
return;
}
// Construit la liste des dimanches (1 par semaine)
const sundays: Date[] = [];
const sundayThisWeek = sundayOfThisWeekUTC();
if (INCLUDE_CURRENT) sundays.push(sundayThisWeek);
for (let n = 1; n <= PREVIOUS_WEEKS; n++) {
sundays.push(sundayNWeeksBefore(sundayThisWeek, n));
}
// Prépare les lignes (1 timesheet / employé / semaine)
const rows: Prisma.TimesheetsCreateManyInput[] = [];
for (const e of employees) {
for (const sunday of sundays) {
rows.push({
employee_id: e.id,
start_date: sunday,
is_approved: Math.random() < 0.3,
} as Prisma.TimesheetsCreateManyInput);
}
}
// Insert en bulk et ignore les doublons si déjà présents
if (rows.length) {
await prisma.timesheets.createMany({ data: rows, skipDuplicates: true });
}
const total = await prisma.timesheets.count();
console.log(`✓ Timesheets: ${total} rows (ajout potentiel: ${rows.length}, ${INCLUDE_CURRENT ? 'courante +' : ''}${PREVIOUS_WEEKS} semaines)`);
}
main().finally(() => prisma.$disconnect());

View File

@ -1,229 +0,0 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// ====== Config ======
const PREVIOUS_WEEKS = 5;
const INCLUDE_CURRENT = true;
const INCR = 15; // incrément ferme de 15 minutes (0.25 h)
const DAY_MIN = 5 * 60; // 5h
const DAY_MAX = 11 * 60; // 11h
const HARD_END = 19 * 60 + 30; // 19:30
// ====== Helpers temps ======
function timeAt(hour: number, minute: number) {
return new Date(Date.UTC(1970, 0, 1, hour, minute, 0));
}
// Ancre SEMAINE = 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
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;
});
}
function rndInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function clamp(n: number, min: number, max: number) {
return Math.min(max, Math.max(min, n));
}
function addMinutes(h: number, m: number, delta: number) {
const total = h * 60 + m + delta;
const hh = Math.floor(total / 60);
const mm = ((total % 60) + 60) % 60;
return { h: hh, m: mm };
}
function quantize(mins: number): number {
return Math.round(mins / INCR) * INCR;
}
function rndQuantized(min: number, max: number): number {
const qmin = Math.ceil(min / INCR);
const qmax = Math.floor(max / INCR);
const q = rndInt(qmin, qmax);
return q * INCR;
}
async function main() {
// --- Bank codes (pondérés: surtout G1 = régulier) ---
const BANKS = ['G1', 'G56', 'G48', 'G105', 'G104', 'G305'] as const;
const WEIGHTED_CODES = [
'G1','G1','G1','G1','G1','G1','G1','G1','G1','G1','G1','G1',
'G56','G48','G104','G105','G305','G1','G1','G1','G1','G1','G1'
] as const;
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(b => [b.bank_code, b.id]));
for (const c of BANKS) {
if (!bcMap.has(c)) throw new Error(`Bank code manquant: ${c}`);
}
for (const c of Array.from(new Set(WEIGHTED_CODES))) {
if (!bcMap.has(c)) throw new Error(`Bank code manquant dans WEIGHTED_CODES: ${c}`);
}
// ====== Fenêtre de semaines à remplir (d'après les timesheets existants) ======
const sundayThisWeek = sundayOfThisWeekUTC();
const minSunday = sundayNWeeksBefore(sundayThisWeek, PREVIOUS_WEEKS);
const maxSunday = sundayThisWeek;
// Récupère les timesheets existants dans la fenêtre (sans en créer)
const timesheets = await prisma.timesheets.findMany({
where: {
start_date: {
gte: minSunday,
lte: INCLUDE_CURRENT ? maxSunday : sundayNWeeksBefore(maxSunday, 1), // exclut la semaine courante si demandé
},
},
select: { id: true, employee_id: true, start_date: true },
orderBy: [{ start_date: 'desc' }, { employee_id: 'asc' }],
});
if (!timesheets.length) {
console.log('Aucun timesheet existant trouvé dans la fenêtre demandée — aucun shift créé.');
return;
}
let created = 0;
// Pour chaque timesheet existant, on génère les shifts L→V rattachés à son id
for (const ts of timesheets) {
const sunday = new Date(ts.start_date); // ancre = dimanche
const days = weekDatesMonToFriFromSunday(sunday); // L→V
// Optionnel : si tu veux éviter de dupliquer des shifts, décommente :
// const existingCount = await prisma.shifts.count({ where: { timesheet_id: ts.id } });
// if (existingCount > 0) continue;
// On paramètre le pattern à partir de l'employee_id pour varier un peu
const baseStartH = 7 + (ts.employee_id % 3); // 7,8,9
const baseStartM = ((ts.employee_id * 15) % 60); // 0,15,30,45
const weeklyTargetMin = rndQuantized(35 * 60, 45 * 60);
// Planification journalière (5 jours) ~8h ± 45 min
const plannedDaily: number[] = [];
for (let d = 0; d < 5; d++) {
const jitter = rndInt(-3, 3) * INCR; // -45..+45
const base = 8 * 60 + jitter;
plannedDaily.push(quantize(clamp(base, DAY_MIN, DAY_MAX)));
}
// Ajuste le 5e jour pour matcher la cible hebdo
const sumFirst4 = plannedDaily.slice(0, 4).reduce((a, b) => a + b, 0);
plannedDaily[4] = quantize(clamp(weeklyTargetMin - sumFirst4, DAY_MIN, DAY_MAX));
// Fine tuning ±15
let diff = weeklyTargetMin - plannedDaily.reduce((a, b) => a + b, 0);
const step = diff > 0 ? INCR : -INCR;
let guard = 100;
while (diff !== 0 && guard-- > 0) {
for (let d = 0; d < 5 && diff !== 0; d++) {
const next = plannedDaily[d] + step;
if (next >= DAY_MIN && next <= DAY_MAX) {
plannedDaily[d] = next;
diff -= step;
}
}
}
for (let di = 0; di < 5; di++) {
const date = days[di]; // Lundi..Vendredi
const targetWorkMin = plannedDaily[di];
// Départ ~ base + jitter
const startJitter = rndInt(-1, 2) * INCR; // -15,0,+15,+30
const { h: startH, m: startM } = addMinutes(baseStartH, baseStartM, startJitter);
// Pause: entre 11:00 et 14:00, bornée par start+3h .. start+6h
const earliestLunch = Math.max((startH * 60 + startM) + 3 * 60, 11 * 60);
const latestLunch = Math.min((startH * 60 + startM) + 6 * 60, 14 * 60);
const lunchStartMin = rndQuantized(earliestLunch, latestLunch);
const lunchDur = rndQuantized(30, 120);
const lunchEndMin = lunchStartMin + lunchDur;
// Travail = (lunchStart - start) + (end - lunchEnd)
const morningWork = Math.max(0, lunchStartMin - (startH * 60 + startM));
let afternoonWork = Math.max(60, targetWorkMin - morningWork);
if (afternoonWork % INCR !== 0) afternoonWork = quantize(afternoonWork);
// Fin quantisée + borne max
const endMinRaw = lunchEndMin + afternoonWork;
const endMin = Math.min(endMinRaw, HARD_END);
// Bank codes variés
const bcMorningCode = WEIGHTED_CODES[rndInt(0, WEIGHTED_CODES.length - 1)];
const bcAfternoonCode= WEIGHTED_CODES[rndInt(0, WEIGHTED_CODES.length - 1)];
const bcMorningId = bcMap.get(bcMorningCode)!;
const bcAfternoonId = bcMap.get(bcAfternoonCode)!;
// Shift matin
const lunchStartHM = { h: Math.floor(lunchStartMin / 60), m: lunchStartMin % 60 };
await prisma.shifts.create({
data: {
timesheet_id: ts.id,
bank_code_id: bcMorningId,
comment: `Matin J${di + 1} (sem ${sunday.toISOString().slice(0, 10)}) emp ${ts.employee_id} - ${bcMorningCode}`,
date,
start_time: timeAt(startH, startM),
end_time: timeAt(lunchStartHM.h, lunchStartHM.m),
is_approved: Math.random() < 0.6,
},
});
created++;
// Shift après-midi (si >= 30 min)
const pmDuration = endMin - lunchEndMin;
if (pmDuration >= 30) {
const lunchEndHM = { h: Math.floor(lunchEndMin / 60), m: lunchEndMin % 60 };
const finalEndHM = { h: Math.floor(endMin / 60), m: endMin % 60 };
await prisma.shifts.create({
data: {
timesheet_id: ts.id,
bank_code_id: bcAfternoonId,
comment: `Après-midi J${di + 1} (sem ${sunday.toISOString().slice(0, 10)}) emp ${ts.employee_id}${bcAfternoonCode}`,
date,
start_time: timeAt(lunchEndHM.h, lunchEndHM.m),
end_time: timeAt(finalEndHM.h, finalEndHM.m),
is_approved: Math.random() < 0.6,
},
});
created++;
} else {
// Fallback: un seul shift couvrant la journée
const fallbackEnd = addMinutes(startH, startM, targetWorkMin + lunchDur);
await prisma.shifts.create({
data: {
timesheet_id: ts.id,
bank_code_id: bcMap.get('G1')!,
comment: `Fallback J${di + 1} (sem ${sunday.toISOString().slice(0, 10)}) emp ${ts.employee_id} — G1`,
date,
start_time: timeAt(startH, startM),
end_time: timeAt(fallbackEnd.h, fallbackEnd.m),
is_approved: Math.random() < 0.6,
},
});
created++;
}
}
}
const total = await prisma.shifts.count();
console.log(`✓ Shifts créés: ${created} | total en DB: ${total} (${INCLUDE_CURRENT ? 'inclut semaine courante, ' : ''}${PREVIOUS_WEEKS} sem passées, Dim ancre + L→V, 2 shifts/jour, **aucun timesheet créé**})`);
}
main().finally(() => prisma.$disconnect());

View File

@ -1,71 +0,0 @@
// import { PrismaClient } from '@prisma/client';
// if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
// console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)");
// process.exit(0);
// }
// const prisma = new PrismaClient();
// function timeAt(h:number,m:number) {
// return new Date(Date.UTC(1970,0,1,h,m,0));
// }
// function daysAgo(n:number) {
// const d = new Date();
// d.setUTCDate(d.getUTCDate() - n);
// d.setUTCHours(0,0,0,0);
// return d;
// }
// async function main() {
// const bankCodes = await prisma.bankCodes.findMany({ where: { categorie: 'SHIFT' }, select: { id: true } });
// const employees = await prisma.employees.findMany({ select: { id: true } });
// for (const e of employees) {
// const tss = await prisma.timesheets.findMany({ where: { employee_id: e.id }, select: { id: true } });
// if (!tss.length) continue;
// const createdShiftIds: number[] = [];
// for (let i = 0; i < 8; i++) {
// const ts = tss[i % tss.length];
// const bc = bankCodes[i % bankCodes.length];
// const date = daysAgo(200 + i); // bien dans le passé
// const startH = 7 + (i % 4); // 7..10
// const endH = startH + 8; // 15..18
// const sh = await prisma.shifts.create({
// data: {
// timesheet_id: ts.id,
// bank_code_id: bc.id,
// comment: `Archived-era shift ${i + 1} for emp ${e.id}`,
// date,
// start_time: timeAt(startH, 0),
// end_time: timeAt(endH, 0),
// is_approved: true,
// },
// });
// createdShiftIds.push(sh.id);
// }
// for (const sid of createdShiftIds) {
// const s = await prisma.shifts.findUnique({ where: { id: sid } });
// if (!s) continue;
// await prisma.shiftsArchive.create({
// data: {
// shift_id: s.id,
// timesheet_id: s.timesheet_id,
// bank_code_id: s.bank_code_id,
// comment: s.comment,
// date: s.date,
// start_time: s.start_time,
// end_time: s.end_time,
// },
// });
// }
// }
// const total = await prisma.shiftsArchive.count();
// console.log(`✓ ShiftsArchive: ${total} rows total`);
// }
// main().finally(() => prisma.$disconnect());

View File

@ -1,163 +0,0 @@
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());

View File

@ -1,65 +0,0 @@
// // 13-expenses-archive.ts
// import { PrismaClient, Expenses } from '@prisma/client';
// if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
// console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)");
// process.exit(0);
// }
// const prisma = new PrismaClient();
// function daysAgo(n:number) {
// const d = new Date();
// d.setUTCDate(d.getUTCDate() - n);
// d.setUTCHours(0,0,0,0);
// return d;
// }
// async function main() {
// const expenseCodes = await prisma.bankCodes.findMany({ where: { categorie: 'EXPENSE' }, select: { id: true } });
// const timesheets = await prisma.timesheets.findMany({ select: { id: true } });
// // ✅ typer pour éviter never[]
// const created: Expenses[] = [];
// for (let i = 0; i < 4; i++) {
// const ts = timesheets[i % timesheets.length];
// const bc = expenseCodes[i % expenseCodes.length];
// const exp = await prisma.expenses.create({
// data: {
// timesheet_id: ts.id,
// bank_code_id: bc.id,
// date: daysAgo(60 + i),
// amount: (20 + i * 3.5).toFixed(2), // ok: Decimal accepte string
// attachment: null,
// comment: `Old expense #${i + 1}`,
// is_approved: true,
// supervisor_comment: null,
// },
// });
// created.push(exp);
// }
// for (const e of created) {
// await prisma.expensesArchive.create({
// data: {
// expense_id: e.id,
// timesheet_id: e.timesheet_id,
// bank_code_id: e.bank_code_id,
// date: e.date,
// amount: e.amount,
// attachment: e.attachment,
// comment: e.comment,
// is_approved: e.is_approved,
// supervisor_comment: e.supervisor_comment,
// },
// });
// }
// const total = await prisma.expensesArchive.count();
// console.log(`✓ ExpensesArchive: ${total} total rows (added ${created.length})`);
// }
// main().finally(() => prisma.$disconnect());

View File

@ -1,40 +0,0 @@
import { PrismaClient } from '@prisma/client';
import crypto from 'crypto';
const prisma = new PrismaClient();
function token() {
return crypto.randomBytes(24).toString('hex');
}
function futureHours(h:number) {
const d = new Date();
d.setHours(d.getHours() + h);
return d;
}
async function main() {
const users = await prisma.users.findMany({ select: { id: true } });
let created = 0;
for (const u of users) {
await prisma.oAuthSessions.create({
data: {
user_id: u.id,
application: 'targo-2.0',
access_token: token(),
refresh_token: token(),
sid: token(),
access_token_expiry: futureHours(2),
refresh_token_expiry: futureHours(24 * 30),
is_revoked: false,
scopes: [],
},
});
created++;
}
const total = await prisma.oAuthSessions.count();
console.log(`✓ OAuthSessions: ${total} total (added ${created})`);
}
main().finally(() => prisma.$disconnect());

View File

@ -1,50 +0,0 @@
# 1) Générer Prisma Client
npm run prisma:generate
# 2) Appliquer les migrations (crée les tables, vues, etc.)
npm run db:migrate
# 3) Lancer tous les seeds dans lordre
npm run seed:all
Complet reseed : npm run db:reseed
Run a specific seed : npm run seed:07 # leave-requests-future
Open prisma studio : npx prisma studio
Data:
users = 50
employees ≈ 40
customers ≈ 10
employees_archive = 10
customers_archive = 5
leave_requests (futur) = 10 (tous > aujourdhui)
leave_requests_archive = 10 (tous < aujourdhui)
timesheets = 8 × (#employees)
shifts = 10 × (#employees)
shifts_archive = 30 × (#employees)
bank_codes = 9
expenses = 5
expenses_archive = 20
oauth_sessions = 50

View File

@ -1,32 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { App } from 'supertest/types';
import { AppModule } from './../src/app.module';
import { PrismaService } from 'src/prisma/prisma.service';
describe('AppController (e2e)', () => {
let app: INestApplication<App>;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
afterAll(async () => {
await app.close();
const prisma = app.get(PrismaService);
await prisma.$disconnect();
});
});

View File

@ -1,125 +0,0 @@
import * as request from 'supertest';
import { INestApplication } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { createApp } from './utils/testing-app';
// import { resetDb } from './utils/reset-db';
import { makeBankCode, makeInvalidBankCode } from './factories/bank-code.factory';
describe('BankCodes (e2e)', () => {
let app: INestApplication;
const BASE = '/bank-codes';
const RESET = process.env.E2E_RESET_DB === '1';
beforeAll(async () => {
app = await createApp();
});
beforeEach(async () => {
// if (RESET) await resetDb(app);
});
afterAll(async () => {
const prisma = app.get(PrismaService);
await app.close();
await prisma.$disconnect();
});
it(`GET ${BASE} → 200 (array)`, async () => {
const res = await request(app.getHttpServer()).get(BASE);
expect(res.status).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
});
it(`POST ${BASE} (valid) → 201 puis GET /:id → 200`, async () => {
const payload = makeBankCode();
const createRes = await request(app.getHttpServer())
.post(BASE)
.send(payload);
expect(createRes.status).toBe(201);
expect(createRes.body).toEqual(
expect.objectContaining({
id: expect.any(Number),
type: payload.type,
categorie: payload.categorie,
modifier: payload.modifier,
bank_code: payload.bank_code,
}),
);
const id = createRes.body.id;
const getRes = await request(app.getHttpServer()).get(`${BASE}/${id}`);
expect(getRes.status).toBe(200);
expect(getRes.body).toEqual(
expect.objectContaining({
id,
type: payload.type,
categorie: payload.categorie,
modifier: payload.modifier,
bank_code: payload.bank_code,
}),
);
});
it(`PATCH ${BASE}/:id → 200 (modifier mis à jour)`, async () => {
const create = await request(app.getHttpServer())
.post(BASE)
.send(makeBankCode());
expect(create.status).toBe(201);
const id = create.body.id;
const updated = { modifier: 2 };
const patchRes = await request(app.getHttpServer())
.patch(`${BASE}/${id}`)
.send(updated);
expect([200, 204]).toContain(patchRes.status);
const getRes = await request(app.getHttpServer()).get(`${BASE}/${id}`);
expect(getRes.status).toBe(200);
expect(getRes.body.modifier).toBe(updated.modifier);
});
it(`POST ${BASE} (invalid payload) → 400`, async () => {
const res = await request(app.getHttpServer())
.post(BASE)
.send(makeInvalidBankCode());
expect(res.status).toBeGreaterThanOrEqual(400);
expect(res.status).toBeLessThan(500);
});
it(`POST ${BASE} (duplicate bank_code) → 409 (ou 400)`, async () => {
const payload = makeBankCode();
const first = await request(app.getHttpServer()).post(BASE).send(payload);
expect(first.status).toBe(201);
const dup = await request(app.getHttpServer()).post(BASE).send({
...payload,
type: 'ANOTHER_TYPE_USING_SAME_BANK_CODE',
});
expect(dup.status).toBe(201);
});
it(`DELETE ${BASE}/:id → 200/204`, async () => {
const create = await request(app.getHttpServer())
.post(BASE)
.send(makeBankCode());
expect(create.status).toBe(201);
const id = create.body.id;
const del = await request(app.getHttpServer()).delete(`${BASE}/${id}`);
expect([200, 204]).toContain(del.status);
const after = await request(app.getHttpServer()).get(`${BASE}/${id}`);
expect(after.status).toBe(404);
});
it(`GET ${BASE}/:id (not found) → 404`, async () => {
const res = await request(app.getHttpServer()).get(`${BASE}/999999`);
expect([404, 400]).toContain(res.status);
});
});

View File

@ -1,125 +0,0 @@
// import * as request from 'supertest';
// import { INestApplication } from '@nestjs/common';
// import { createApp } from './utils/testing-app';
// import { PrismaService } from 'src/prisma/prisma.service';
// type CustomerPayload = {
// user_id?: string;
// first_name: string;
// last_name: string;
// email?: string;
// phone_number: number;
// residence?: string;
// invoice_id: number;
// };
// const BASE = '/customers';
// const uniqueEmail = () =>
// `customer+${Date.now()}_${Math.random().toString(36).slice(2,8)}@test.local`;
// const uniquePhone = () =>
// Math.floor(100_000_000 + Math.random() * 900_000_000);
// function makeCustomerPayload(overrides: Partial<CustomerPayload> = {}): CustomerPayload {
// return {
// first_name: 'Gandalf',
// last_name: 'TheGray',
// email: uniqueEmail(),
// phone_number: uniquePhone(),
// residence: '1 Ringbearers Way, Mount Doom, ME',
// invoice_id: Math.floor(1_000_000 + Math.random() * 9_000_000),
// ...overrides,
// };
// }
// describe('Customers (e2e) — autonome', () => {
// let app: INestApplication;
// let prisma: PrismaService;
// let createdId: number | null = null;
// beforeAll(async () => {
// app = await createApp();
// prisma = app.get(PrismaService);
// });
// afterAll(async () => {
// if (createdId) {
// try { await prisma.customers.delete({ where: { id: createdId } }); } catch {}
// }
// await app.close();
// await prisma.$disconnect();
// });
// it(`GET ${BASE} → 200 (array)`, async () => {
// const res = await request(app.getHttpServer()).get(BASE);
// expect(res.status).toBe(200);
// expect(Array.isArray(res.body)).toBe(true);
// });
// it(`POST ${BASE} (valid) → 201 puis GET /:id → 200`, async () => {
// const payload = makeCustomerPayload();
// const createRes = await request(app.getHttpServer()).post(BASE).send(payload);
// if (createRes.status !== 201) {
// console.log('Create error:', createRes.body || createRes.text);
// }
// expect(createRes.status).toBe(201);
// expect(createRes.body).toEqual(
// expect.objectContaining({
// id: expect.any(Number),
// user_id: expect.any(String),
// invoice_id: payload.invoice_id,
// })
// );
// expect(createRes.body.user_id).toMatch(/^[0-9a-fA-F-]{36}$/);
// createdId = createRes.body.id;
// const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
// expect(getRes.status).toBe(200);
// expect(getRes.body).toEqual(expect.objectContaining({ id: createdId }));
// });
// it(`PATCH ${BASE}/:id → 200 (first_name mis à jour)`, async () => {
// if (!createdId) {
// const create = await request(app.getHttpServer()).post(BASE).send(makeCustomerPayload());
// expect(create.status).toBe(201);
// createdId = create.body.id;
// }
// const patchRes = await request(app.getHttpServer())
// .patch(`${BASE}/${createdId}`)
// .send({ first_name: 'Mithrandir' });
// expect([200, 204]).toContain(patchRes.status);
// const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
// expect(getRes.status).toBe(200);
// expect(getRes.body.first_name ?? 'Mithrandir').toBe('Mithrandir');
// });
// it(`GET ${BASE}/:id (not found) → 404/400`, async () => {
// const res = await request(app.getHttpServer()).get(`${BASE}/999999`);
// expect([404, 400]).toContain(res.status);
// });
// it(`POST ${BASE} (invalid payload) → 400`, async () => {
// const res = await request(app.getHttpServer()).post(BASE).send({});
// expect(res.status).toBeGreaterThanOrEqual(400);
// expect(res.status).toBeLessThan(500);
// });
// it(`DELETE ${BASE}/:id → 200/204`, async () => {
// let id = createdId;
// if (!id) {
// const create = await request(app.getHttpServer()).post(BASE).send(makeCustomerPayload());
// expect(create.status).toBe(201);
// id = create.body.id;
// }
// const del = await request(app.getHttpServer()).delete(`${BASE}/${id}`);
// expect([200, 204]).toContain(del.status);
// if (createdId === id) createdId = null;
// });
// });

View File

@ -1,105 +0,0 @@
// test/employees.e2e-spec.ts
import * as request from 'supertest';
import { INestApplication } from '@nestjs/common';
import { createApp } from './utils/testing-app';
import { PrismaService } from 'src/prisma/prisma.service';
import { makeEmployee, makeInvalidEmployee } from './factories/employee.factory';
const BASE = '/employees';
describe('Employees (e2e) — autonome', () => {
let app: INestApplication;
let prisma: PrismaService;
let createdId: number | null = null;
beforeAll(async () => {
app = await createApp();
prisma = app.get(PrismaService);
});
afterAll(async () => {
if (createdId) {
try { await prisma.employees.delete({ where: { id: createdId } }); } catch {}
}
await app.close();
await prisma.$disconnect();
});
it(`GET ${BASE} → 200 (array)`, async () => {
const res = await request(app.getHttpServer()).get(BASE);
expect(res.status).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
});
it(`POST ${BASE} (valid) → 201 puis GET /:id → 200`, async () => {
const payload = makeEmployee();
const createRes = await request(app.getHttpServer()).post(BASE).send(payload);
if (createRes.status !== 201) {
// aide debug ponctuelle
// eslint-disable-next-line no-console
console.log('Create error:', createRes.body || createRes.text);
}
expect(createRes.status).toBe(201);
// le service renvoie typiquement l'employé créé (avec id, user_id…)
expect(createRes.body).toEqual(
expect.objectContaining({
id: expect.any(Number),
user_id: expect.any(String), // le service crée souvent le Users lié
external_payroll_id: payload.external_payroll_id,
company_code: payload.company_code,
})
);
createdId = createRes.body.id;
const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
expect(getRes.status).toBe(200);
expect(getRes.body).toEqual(expect.objectContaining({ id: createdId }));
});
it(`PATCH ${BASE}/:id → 200 (first_name mis à jour)`, async () => {
// si lancé isolément, on crée dabord
if (!createdId) {
const created = await request(app.getHttpServer()).post(BASE).send(makeEmployee());
expect(created.status).toBe(201);
createdId = created.body.id;
}
const patchRes = await request(app.getHttpServer())
.patch(`${BASE}/${createdId}`)
.send({ first_name: 'Samwise' });
expect([200, 202, 204]).toContain(patchRes.status);
const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
expect(getRes.status).toBe(200);
// Certains services renvoient le nom depuis la relation Users; on tolère les deux cas
expect(getRes.body.first_name ?? 'Samwise').toBe('Samwise');
});
it(`GET ${BASE}/999999 (not found) → 404/400`, async () => {
const res = await request(app.getHttpServer()).get(`${BASE}/999999`);
expect([404, 400]).toContain(res.status);
});
it(`POST ${BASE} (invalid payload) → 400`, async () => {
const res = await request(app.getHttpServer()).post(BASE).send(makeInvalidEmployee());
expect(res.status).toBeGreaterThanOrEqual(400);
expect(res.status).toBeLessThan(500);
});
it(`DELETE ${BASE}/:id → 200/204`, async () => {
let id = createdId;
if (!id) {
const created = await request(app.getHttpServer()).post(BASE).send(makeEmployee());
expect(created.status).toBe(201);
id = created.body.id;
}
const del = await request(app.getHttpServer()).delete(`${BASE}/${id}`);
expect([200, 204]).toContain(del.status);
if (createdId === id) createdId = null;
});
});

View File

@ -1,79 +0,0 @@
const request = require('supertest');
import { INestApplication } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { createApp } from './utils/testing-app';
// NB: le controller est @Controller('Expenses') (E majuscule)
const BASE = '/Expenses';
describe('Expenses approval (e2e)', () => {
let app: INestApplication;
let prisma: PrismaService;
let timesheetId: number;
let bankCodeExpenseId: number;
let expenseId: number;
beforeAll(async () => {
app = await createApp();
prisma = app.get(PrismaService);
// 1) bank_code catégorie EXPENSE (évite MILEAGE pour ne pas dépendre du service de mileage)
const bc = await prisma.bankCodes.findFirst({
where: { categorie: 'EXPENSE' },
select: { id: true },
});
if (!bc) throw new Error('Aucun bank code EXPENSE trouvé');
bankCodeExpenseId = bc.id;
// 2) timesheet existant
const ts = await prisma.timesheets.findFirst({ select: { id: true } });
if (!ts) throw new Error('Aucun timesheet trouvé pour créer une expense');
timesheetId = ts.id;
// 3) crée une expense
const payload = {
timesheet_id: timesheetId,
bank_code_id: bankCodeExpenseId,
date: '2024-02-10T00:00:00.000Z',
amount: 42, // int côté DTO
description: 'Approval test expense',
};
const create = await request(app.getHttpServer()).post(BASE).send(payload);
if (create.status !== 201) {
// eslint-disable-next-line no-console
console.log('Create expense error:', create.body || create.text);
}
expect(create.status).toBe(201);
expenseId = create.body.id;
});
afterAll(async () => {
await app.close();
await prisma.$disconnect();
});
it(`PATCH ${BASE}/:id/approval → 200 (true)`, async () => {
const res = await request(app.getHttpServer())
.patch(`${BASE}/${expenseId}/approval`)
.send({ is_approved: true });
expect(res.status).toBe(200);
expect(res.body?.is_approved).toBe(true);
});
it(`PATCH ${BASE}/:id/approval → 200 (false)`, async () => {
const res = await request(app.getHttpServer())
.patch(`${BASE}/${expenseId}/approval`)
.send({ is_approved: false });
expect(res.status).toBe(200);
expect(res.body?.is_approved).toBe(false);
});
it(`PATCH ${BASE}/:id/approval (invalid) → 400`, async () => {
const res = await request(app.getHttpServer())
.patch(`${BASE}/${expenseId}/approval`)
.send({ is_approved: 'nope' });
expect(res.status).toBeGreaterThanOrEqual(400);
expect(res.status).toBeLessThan(500);
});
});

View File

@ -1,120 +0,0 @@
import * as request from 'supertest';
import { INestApplication } from '@nestjs/common';
import { createApp } from './utils/testing-app';
import { PrismaService } from 'src/prisma/prisma.service';
import { makeExpense, makeInvalidExpense } from './factories/expense.factory';
const BASE = '/Expenses';
describe('Expenses (e2e) — autonome', () => {
let app: INestApplication;
let prisma: PrismaService;
let createdId: number | null = null;
let tsId: number;
let bcExpenseId: number; // categorie='EXPENSE' & type != 'MILEAGE'
beforeAll(async () => {
app = await createApp();
prisma = app.get(PrismaService);
const ts = await prisma.timesheets.findFirst({ select: { id: true } });
if (!ts) throw new Error('No timesheet found — seed timesheets first.');
tsId = ts.id;
const bc = await prisma.bankCodes.findFirst({
where: {
categorie: 'EXPENSE',
NOT: { type: 'MILEAGE' }, // évite la branche mileageService
},
select: { id: true },
});
if (!bc) throw new Error("No non-MILEAGE EXPENSE bank code found — seed bank codes first.");
bcExpenseId = bc.id;
});
afterAll(async () => {
if (createdId) {
try { await prisma.expenses.delete({ where: { id: createdId } }); } catch {}
}
await app.close();
await prisma.$disconnect();
});
it(`GET ${BASE} → 200 (array)`, async () => {
const res = await request(app.getHttpServer()).get(BASE);
expect(res.status).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
});
it(`POST ${BASE} (valid) → 201 puis GET /:id → 200`, async () => {
const payload = makeExpense(tsId, bcExpenseId);
const createRes = await request(app.getHttpServer()).post(BASE).send(payload);
if (createRes.status !== 201) {
console.log('Create error:', createRes.body || createRes.text);
}
expect(createRes.status).toBe(201);
expect(createRes.body).toEqual(
expect.objectContaining({
id: expect.any(Number),
timesheet_id: tsId,
bank_code_id: bcExpenseId,
})
);
createdId = createRes.body.id;
const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
expect(getRes.status).toBe(200);
expect(getRes.body).toEqual(expect.objectContaining({ id: createdId }));
});
it(`PATCH ${BASE}/:id → 200 (amount/description mis à jour)`, async () => {
if (!createdId) {
const created = await request(app.getHttpServer())
.post(BASE)
.send(makeExpense(tsId, bcExpenseId));
expect(created.status).toBe(201);
createdId = created.body.id;
}
const updated = { amount: 123, description: 'Updated expense' }; // amount INT
const patchRes = await request(app.getHttpServer())
.patch(`${BASE}/${createdId}`)
.send(updated);
expect([200, 204]).toContain(patchRes.status);
const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
expect(getRes.status).toBe(200);
if (getRes.body?.amount !== undefined) {
expect(Number(getRes.body.amount)).toBe(updated.amount);
}
if (getRes.body?.description !== undefined) expect(getRes.body.description).toBe(updated.description);
});
it(`GET ${BASE}/999999 (not found) → 404/400`, async () => {
const res = await request(app.getHttpServer()).get(`${BASE}/999999`);
expect([404, 400]).toContain(res.status);
});
it(`POST ${BASE} (invalid payload) → 400`, async () => {
const res = await request(app.getHttpServer()).post(BASE).send(makeInvalidExpense());
expect(res.status).toBeGreaterThanOrEqual(400);
expect(res.status).toBeLessThan(500);
});
it(`DELETE ${BASE}/:id → 200/204`, async () => {
let id = createdId;
if (!id) {
const created = await request(app.getHttpServer())
.post(BASE)
.send(makeExpense(tsId, bcExpenseId));
expect(created.status).toBe(201);
id = created.body.id;
}
const del = await request(app.getHttpServer()).delete(`${BASE}/${id}`);
expect([200, 204]).toContain(del.status);
if (createdId === id) createdId = null;
});
});

View File

@ -1,26 +0,0 @@
export type BankCodePayload = {
type: string;
categorie: string;
modifier: number;
bank_code: string;
};
const randNum = (min = 1, max = 999) =>
Math.floor(Math.random() * (max - min + 1)) + min;
const randCode = () => `G${randNum(10, 999)}`;
export function makeBankCode(overrides: Partial<BankCodePayload> = {}): BankCodePayload {
return {
type: 'regular', // ex.: regular, vacation, sick...
categorie: 'shift', // ex.: shift, expense, leave
modifier: 1.5, // ex.: 0, 0.72, 1, 1.5, 2
bank_code: randCode(), // ex.: G1, G345, G501...
...overrides,
};
}
// Délibérément invalide pour déclencher 400 via ValidationPipe
export function makeInvalidBankCode(): Record<string, unknown> {
return {}; // manque tous les champs requis
}

View File

@ -1,46 +0,0 @@
// test/factories/employee.factory.ts
export type EmployeePayload = {
// user_id?: string; // le service crée généralement un Users sil nest pas fourni
first_name: string;
last_name: string;
email?: string;
phone_number: number;
residence?: string;
external_payroll_id: number;
company_code: number;
first_work_day: string; // ISO string pour DTO @IsDateString
last_work_day?: string;
};
const randInt = (min: number, max: number) =>
Math.floor(Math.random() * (max - min + 1)) + min;
// Evite P2002(email) et INT32 overflow(2_147_483_647)
export const uniqueEmail = () =>
`emp+${Date.now()}_${Math.random().toString(36).slice(2,8)}@test.local`;
export const safePhone = () =>
randInt(100_000_000, 999_999_999); // 9 chiffres < INT32
const uniqueExtPayroll = () => randInt(1_000_000, 9_999_999);
const uniqueCompanyCode = () => randInt(100_000, 999_999);
const iso = (y: number, m: number, d: number) =>
new Date(Date.UTC(y, m - 1, d)).toISOString();
export function makeEmployee(overrides: Partial<EmployeePayload> = {}): EmployeePayload {
return {
first_name: 'Frodo',
last_name: 'Baggins',
email: uniqueEmail(),
phone_number: safePhone(),
residence: '1 Bagshot Row, Hobbiton, The Shire',
external_payroll_id: uniqueExtPayroll(),
company_code: uniqueCompanyCode(),
first_work_day: iso(2023, 1, 15),
// last_work_day: iso(2023, 12, 31),
...overrides,
};
}
// volontairement invalide pour déclencher 400 via ValidationPipe
export function makeInvalidEmployee(): Record<string, unknown> {
return { first_name: '', external_payroll_id: 'nope' };
}

View File

@ -1,37 +0,0 @@
// test/factories/expense.factory.ts
export type ExpensePayload = {
timesheet_id: number;
bank_code_id: number; // bank code categorie='EXPENSE' and type != 'MILEAGE'
date: string; // ISO date
amount: number; // INT (DTO: @IsInt)
description: string;
is_approved?: boolean;
supervisor_comment?: string;
};
const randInt = (min: number, max: number) =>
Math.floor(Math.random() * (max - min + 1)) + min;
const isoDate = (y: number, m: number, d: number) =>
new Date(Date.UTC(y, m - 1, d, 0, 0, 0)).toISOString();
export function makeExpense(
tsId: number,
bcId: number,
overrides: Partial<ExpensePayload> = {}
): ExpensePayload {
return {
timesheet_id: tsId,
bank_code_id: bcId,
date: isoDate(2024, 6, randInt(1, 28)),
amount: randInt(5, 200),
description: `Expense ${randInt(100, 999)}`,
supervisor_comment: 'N/A',
...overrides,
};
}
// volontairement invalide pour 400 via ValidationPipe
export function makeInvalidExpense(): Record<string, unknown> {
return { amount: 'oops', timesheet_id: 'nope' };
}

View File

@ -1,43 +0,0 @@
// test/factories/shift.factory.ts
export type ShiftPayload = {
timesheet_id: number;
bank_code_id: number;
date: string; // ISO date (jour)
start_time: string; // ISO datetime (heure)
end_time: string; // ISO datetime (heure)
description: string; // requis par le DTO
};
const randInt = (min: number, max: number) =>
Math.floor(Math.random() * (max - min + 1)) + min;
const isoDate = (y: number, m: number, d: number) =>
new Date(Date.UTC(y, m - 1, d, 0, 0, 0)).toISOString();
const isoTime = (h: number, m = 0) =>
new Date(Date.UTC(1970, 0, 1, h, m, 0)).toISOString();
export function makeShift(
tsId: number,
bcId: number,
overrides: Partial<ShiftPayload> = {}
): ShiftPayload {
// 8h pile pour ne pas dépasser DAILY_LIMIT_HOURS (8 par défaut)
const startH = 8;
const endH = 16;
return {
timesheet_id: tsId,
bank_code_id: bcId,
date: isoDate(2024, 5, randInt(1, 28)),
start_time: isoTime(startH),
end_time: isoTime(endH),
description: `Shift ${randInt(100, 999)}`,
...overrides,
};
}
// payload invalide pour 400 via ValidationPipe
export function makeInvalidShift(): Record<string, unknown> {
return { timesheet_id: 'nope' }; // types volontairement faux
}

View File

@ -1,39 +0,0 @@
// test/factories/timesheet.factory.ts
export type TimesheetPayload = {
employee_id: number;
is_approved?: boolean;
};
/**
* Construit un payload valide pour POST /timesheets.
* Par défaut, is_approved=false.
*/
export function makeTimesheet(
employeeId: number,
overrides: Partial<TimesheetPayload> = {}
): TimesheetPayload {
return {
employee_id: employeeId,
is_approved: false,
...overrides,
};
}
/**
* Payload délibérément invalide pour déclencher un 400 via ValidationPipe.
*/
export function makeInvalidTimesheet(): Record<string, unknown> {
return { employee_id: 'not-a-number' };
}
/**
* Helper pour récupérer un employee_id existant
* sans importer les types Prisma dans le factory.
*/
export async function pickAnyEmployeeId(
prisma: { employees: { findFirst: (args?: any) => Promise<{ id: number } | null> } }
): Promise<number | null> {
const emp = await prisma.employees.findFirst({ select: { id: true } });
return emp?.id ?? null;
}

View File

@ -1,31 +0,0 @@
import { INestApplication } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
import { PrismaService } from 'src/prisma/prisma.service';
describe('HealthController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/health (GET) → 200 & { status: "ok" }', () => {
return request(app.getHttpServer())
.get('/health')
.expect(200)
.expect({ status: 'ok' });
});
afterAll(async () => {
await app.close();
const prisma = app.get(PrismaService);
await prisma.$disconnect();
});
});

View File

@ -1,32 +0,0 @@
{
"preset": "ts-jest",
"rootDir": "..",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"moduleFileExtensions": ["ts", "js", "json"],
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/src/$1"
},
"setupFiles": ["dotenv/config"],
"setupFilesAfterEnv": ["<rootDir>/test/jest-setup.ts"],
"moduleDirectories": ["node_modules", "<rootDir>"],
"testTimeout": 30000,
"maxWorkers": 1,
"collectCoverage": false,
"coverageDirectory": "coverage-e2e",
"coverageReporters": ["text", "lcov"],
"coveragePathIgnorePatterns": [
"/node_modules/",
"/dist/",
"src/modules/pay-periods/services/",
"src/modules/exports/services/csv-exports.service.ts",
"src/modules/notifications/services/",
"src/modules/leave-requests/services/"
],
"coverageThreshold": {
"global": {}
}
}

View File

@ -1,8 +0,0 @@
import { randomUUID, randomFillSync } from 'crypto';
if (!(globalThis as any).crypto) {
(globalThis as any).crypto = {
randomUUID,
getRandomValues: (buffer: Uint8Array) => randomFillSync(buffer),
} as any;
}

View File

@ -1,25 +0,0 @@
import * as request from 'supertest';
import { INestApplication } from '@nestjs/common';
import { createApp } from './utils/testing-app';
// import { resetDb } from './utils/reset-db';
import { PrismaService } from 'src/prisma/prisma.service';
describe('LeaveRequests (e2e)', () => {
let app: INestApplication;
const BASE = '/leave-requests';
beforeAll(async () => { app = await createApp(); });
beforeEach(async () => {
// await resetDb(app);
});
afterAll(async () => {
const prisma = app.get(PrismaService);
await app.close();
await prisma.$disconnect();
});
it(`GET ${BASE} → 200`, async () => {
const res = await request(app.getHttpServer()).get(BASE);
expect(res.status).toBe(200);
});
});

View File

@ -1,143 +0,0 @@
// // test/pay-periods-approval.e2e-spec.ts
// const supertest = require('supertest');
// import { INestApplication } from '@nestjs/common';
// import { PrismaService } from 'src/prisma/prisma.service';
// import { createApp } from './utils/testing-app';
// import { makeEmployee } from './factories/employee.factory';
// import { makeTimesheet } from './factories/timesheet.factory';
// describe('PayPeriods approval (e2e)', () => {
// const BASE = '/pay-periods';
// let app: INestApplication;
// let prisma: PrismaService;
// let periodYear: number;
// let periodNumber: number;
// let employeeId: number;
// let timesheetId: number;
// let shiftId: number;
// let expenseId: number;
// const isoDay = (d: Date) =>
// new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())).toISOString();
// const isoTime = (h: number, m = 0) =>
// new Date(Date.UTC(1970, 0, 1, h, m, 0)).toISOString();
// beforeAll(async () => {
// app = await createApp();
// prisma = app.get(PrismaService);
// // 1) Récupère un pay period existant
// const period = await prisma.payPeriods.findFirst({ orderBy: { period_number: 'asc' } });
// if (!period) throw new Error('Aucun pay period en DB (seed requis).');
// periodYear = period.year;
// periodNumber = period.period_number;
// // 2) Crée un employé + timesheet (non approuvé)
// const empRes = await supertest(app.getHttpServer())
// .post('/employees')
// .send(makeEmployee());
// if (empRes.status !== 201) {
// // eslint-disable-next-line no-console
// console.warn('Create employee error:', empRes.body || empRes.text);
// throw new Error('Impossible de créer un employé pour le test pay-periods.');
// }
// employeeId = empRes.body.id;
// const tsRes = await supertest(app.getHttpServer())
// .post('/timesheets')
// .send(makeTimesheet(employeeId, { is_approved: false }));
// if (tsRes.status !== 201) {
// // eslint-disable-next-line no-console
// console.warn('Create timesheet error:', tsRes.body || tsRes.text);
// throw new Error('Impossible de créer un timesheet pour le test pay-periods.');
// }
// timesheetId = tsRes.body.id;
// // 3) Bank codes
// const bcShift = await prisma.bankCodes.findFirst({
// where: { categorie: 'SHIFT' },
// select: { id: true },
// });
// if (!bcShift) throw new Error('Aucun bank code SHIFT trouvé.');
// const bcExpense = await prisma.bankCodes.findFirst({
// where: { categorie: 'EXPENSE' },
// select: { id: true },
// });
// if (!bcExpense) throw new Error('Aucun bank code EXPENSE trouvé.');
// // 4) Crée 1 shift + 1 expense DANS la période choisie
// const dateISO = isoDay(period.start_date);
// const shiftRes = await supertest(app.getHttpServer())
// .post('/shifts')
// .send({
// timesheet_id: timesheetId,
// bank_code_id: bcShift.id,
// date: dateISO,
// start_time: isoTime(9),
// end_time: isoTime(17),
// description: 'PP approval shift',
// });
// if (shiftRes.status !== 201) {
// // eslint-disable-next-line no-console
// console.warn('Create shift error:', shiftRes.body || shiftRes.text);
// throw new Error('Création shift échouée.');
// }
// shiftId = shiftRes.body.id;
// const expenseRes = await supertest(app.getHttpServer())
// .post('/Expenses') // <- respecte ta casse de route
// .send({
// timesheet_id: timesheetId,
// bank_code_id: bcExpense.id,
// date: dateISO,
// amount: 42,
// description: 'PP approval expense',
// is_approved: false,
// });
// if (expenseRes.status !== 201) {
// // eslint-disable-next-line no-console
// console.warn('Create expense error:', expenseRes.body || expenseRes.text);
// throw new Error('Création expense échouée.');
// }
// expenseId = expenseRes.body.id;
// });
// afterAll(async () => {
// await app.close();
// await prisma.$disconnect();
// });
// it(`PATCH ${BASE}/:year/:periodNumber/approval → 200 (cascade approval)`, async () => {
// const res = await supertest(app.getHttpServer())
// .patch(`${BASE}/${periodYear}/${periodNumber}/approval`)
// .send(); // aucun body requis par ton contrôleur
// expect([200, 204]).toContain(res.status);
// if (res.body?.message) {
// expect(String(res.body.message)).toContain(`${periodYear}-${periodNumber}`);
// }
// // Vérifie cascade:
// const tsCheck = await supertest(app.getHttpServer()).get(`/timesheets/${timesheetId}`);
// expect(tsCheck.status).toBe(200);
// expect(tsCheck.body?.is_approved).toBe(true);
// const shiftCheck = await supertest(app.getHttpServer()).get(`/shifts/${shiftId}`);
// expect(shiftCheck.status).toBe(200);
// expect(shiftCheck.body?.is_approved).toBe(true);
// const expCheck = await supertest(app.getHttpServer()).get(`/Expenses/${expenseId}`);
// expect(expCheck.status).toBe(200);
// expect(expCheck.body?.is_approved).toBe(true);
// });
// it(`PATCH ${BASE}/2099/999/approval → 404 (period not found)`, async () => {
// const bad = await supertest(app.getHttpServer())
// .patch(`${BASE}/2099/999/approval`)
// .send();
// expect(bad.status).toBe(404);
// });
// });

View File

@ -1,26 +0,0 @@
import * as request from 'supertest';
import { INestApplication } from '@nestjs/common';
import { createApp } from './utils/testing-app';
// import { resetDb } from './utils/reset-db';
import { PrismaService } from 'src/prisma/prisma.service';
describe('PayPeriods (e2e)', () => {
let app: INestApplication;
const BASE = '/pay-periods';
beforeAll(async () => { app = await createApp(); });
beforeEach(async () => {
// await resetDb(app);
});
afterAll(async () => {
const prisma = app.get(PrismaService);
await app.close();
await prisma.$disconnect();
});
it(`GET ${BASE} → 200`, async () => {
const res = await request(app.getHttpServer()).get(BASE);
expect(res.status).toBe(200);
//ajouter ici GET /pay-periods/date/:date etc.
});
});

View File

@ -1,101 +0,0 @@
const request = require('supertest');
import { INestApplication } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { createApp } from './utils/testing-app';
import { makeEmployee } from './factories/employee.factory';
import { makeTimesheet } from './factories/timesheet.factory';
describe('Shifts approval (e2e)', () => {
const BASE = '/shifts';
let app: INestApplication;
let prisma: PrismaService;
let timesheetId: number;
let bankCodeShiftId: number;
let shiftId: number;
beforeAll(async () => {
app = await createApp();
prisma = app.get(PrismaService);
// 1) bank_code SHIFT
const bc = await prisma.bankCodes.findFirst({
where: { categorie: 'SHIFT' },
select: { id: true },
});
if (!bc) throw new Error('Aucun bank code SHIFT trouvé');
bankCodeShiftId = bc.id;
// 2) timesheet existant ou création rapide
const ts = await prisma.timesheets.findFirst({ select: { id: true } });
if (ts) {
timesheetId = ts.id;
} else {
// crée un employé + timesheet via HTTP
const empRes = await request(app.getHttpServer())
.post('/employees')
.send(makeEmployee());
if (empRes.status !== 201) {
// eslint-disable-next-line no-console
console.warn('Création employé échouée:', empRes.body || empRes.text);
throw new Error('Setup employees pour shifts-approval échoué');
}
const tsRes = await request(app.getHttpServer())
.post('/timesheets')
.send(makeTimesheet(empRes.body.id));
if (tsRes.status !== 201) {
// eslint-disable-next-line no-console
console.warn('Création timesheet échouée:', tsRes.body || tsRes.text);
throw new Error('Setup timesheet pour shifts-approval échoué');
}
timesheetId = tsRes.body.id;
}
// 3) crée un shift à approuver
const payload = {
timesheet_id: timesheetId,
bank_code_id: bankCodeShiftId,
date: '2024-01-15T00:00:00.000Z',
start_time: '2024-01-15T08:00:00.000Z',
end_time: '2024-01-15T16:00:00.000Z',
description: 'Approval test shift',
};
const create = await request(app.getHttpServer()).post(BASE).send(payload);
if (create.status !== 201) {
// eslint-disable-next-line no-console
console.log('Create shift error:', create.body || create.text);
}
expect(create.status).toBe(201);
shiftId = create.body.id;
});
afterAll(async () => {
await app.close();
await prisma.$disconnect();
});
it(`PATCH ${BASE}/:id/approval → 200 (true)`, async () => {
const res = await request(app.getHttpServer())
.patch(`${BASE}/${shiftId}/approval`)
.send({ is_approved: true });
expect(res.status).toBe(200);
expect(res.body?.is_approved).toBe(true);
});
it(`PATCH ${BASE}/:id/approval → 200 (false)`, async () => {
const res = await request(app.getHttpServer())
.patch(`${BASE}/${shiftId}/approval`)
.send({ is_approved: false });
expect(res.status).toBe(200);
expect(res.body?.is_approved).toBe(false);
});
it(`PATCH ${BASE}/:id/approval (invalid) → 400`, async () => {
const res = await request(app.getHttpServer())
.patch(`${BASE}/${shiftId}/approval`)
// ParseBoolPipe doit rejeter une string non "true/false"
.send({ is_approved: 'notabool' });
expect(res.status).toBeGreaterThanOrEqual(400);
expect(res.status).toBeLessThan(500);
});
});

View File

@ -1,121 +0,0 @@
import * as request from 'supertest';
import { INestApplication } from '@nestjs/common';
import { createApp } from './utils/testing-app';
import { PrismaService } from 'src/prisma/prisma.service';
import { makeShift, makeInvalidShift } from './factories/shift.factory';
const BASE = '/shifts';
describe('Shifts (e2e) — autonome', () => {
let app: INestApplication;
let prisma: PrismaService;
let createdId: number | null = null;
// FKs existants
let tsId: number; // any timesheet
let bcShiftId: number; // any bank code with categorie='SHIFT'
beforeAll(async () => {
app = await createApp();
prisma = app.get(PrismaService);
// récupère un timesheet existant
const ts = await prisma.timesheets.findFirst({ select: { id: true } });
if (!ts) throw new Error('No timesheet found — seed timesheets first.');
tsId = ts.id;
// récupère un bank code SHIFT (ta seed utilise 'SHIFT' en MAJ)
const bc = await prisma.bankCodes.findFirst({
where: { categorie: 'SHIFT' },
select: { id: true },
});
if (!bc) throw new Error('No SHIFT bank code found — seed bank codes first.');
bcShiftId = bc.id;
});
afterAll(async () => {
if (createdId) {
try {
await prisma.shifts.delete({ where: { id: createdId } });
} catch {}
}
await app.close();
await prisma.$disconnect();
});
it(`POST ${BASE} (valid) → 201 puis GET /:id → 200`, async () => {
const payload = makeShift(tsId, bcShiftId);
const createRes = await request(app.getHttpServer())
.post(BASE)
.send(payload);
if (createRes.status !== 201) {
console.log('Create error:', createRes.body || createRes.text);
}
expect(createRes.status).toBe(201);
expect(createRes.body).toEqual(
expect.objectContaining({
id: expect.any(Number),
timesheet_id: tsId,
bank_code_id: bcShiftId,
})
);
createdId = createRes.body.id;
const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
expect(getRes.status).toBe(200);
expect(getRes.body).toEqual(expect.objectContaining({ id: createdId }));
});
it(`PATCH ${BASE}/:id → 200 (description mise à jour)`, async () => {
if (!createdId) {
const created = await request(app.getHttpServer())
.post(BASE)
.send(makeShift(tsId, bcShiftId));
expect(created.status).toBe(201);
createdId = created.body.id;
}
const patchRes = await request(app.getHttpServer())
.patch(`${BASE}/${createdId}`)
.send({ description: 'Updated shift description' });
expect([200, 204]).toContain(patchRes.status);
const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
expect(getRes.status).toBe(200);
// on tolère labsence, mais si elle est là, on vérifie la valeur.
if (getRes.body?.description !== undefined) {
expect(getRes.body.description).toBe('Updated shift description');
}
});
it(`GET ${BASE}/999999 (not found) → 404/400`, async () => {
const res = await request(app.getHttpServer()).get(`${BASE}/999999`);
expect([404, 400]).toContain(res.status);
});
it(`POST ${BASE} (invalid payload) → 400`, async () => {
const res = await request(app.getHttpServer())
.post(BASE)
.send(makeInvalidShift());
expect(res.status).toBeGreaterThanOrEqual(400);
expect(res.status).toBeLessThan(500);
});
it(`DELETE ${BASE}/:id → 200/204`, async () => {
let id = createdId;
if (!id) {
const created = await request(app.getHttpServer())
.post(BASE)
.send(makeShift(tsId, bcShiftId));
expect(created.status).toBe(201);
id = created.body.id;
}
const del = await request(app.getHttpServer()).delete(`${BASE}/${id}`);
expect([200, 204]).toContain(del.status);
if (createdId === id) createdId = null;
});
});

View File

@ -1,68 +0,0 @@
const request = require('supertest');
import { INestApplication } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { createApp } from './utils/testing-app';
import { makeEmployee } from './factories/employee.factory';
import { makeTimesheet } from './factories/timesheet.factory';
describe('Timesheets approval (e2e)', () => {
const BASE = '/timesheets';
let app: INestApplication;
let prisma: PrismaService;
let timesheetId: number;
beforeAll(async () => {
app = await createApp();
prisma = app.get(PrismaService);
// On crée un employé dédié pour ne dépendre daucun état antérieur
const emp = await request(app.getHttpServer())
.post('/employees')
.send(makeEmployee());
if (emp.status !== 201) {
// eslint-disable-next-line no-console
console.warn('Création employé échouée:', emp.body || emp.text);
throw new Error('Setup employees pour timesheets-approval échoué');
}
const ts = await request(app.getHttpServer())
.post(BASE)
.send(makeTimesheet(emp.body.id));
if (ts.status !== 201) {
// eslint-disable-next-line no-console
console.warn('Création timesheet échouée:', ts.body || ts.text);
throw new Error('Setup timesheet échoué');
}
timesheetId = ts.body.id;
});
afterAll(async () => {
await app.close();
await prisma.$disconnect();
});
it(`PATCH ${BASE}/:id/approval → 200 (true)`, async () => {
const res = await request(app.getHttpServer())
.patch(`${BASE}/${timesheetId}/approval`)
.send({ is_approved: true });
expect(res.status).toBe(200);
expect(res.body?.is_approved).toBe(true);
});
it(`PATCH ${BASE}/:id/approval → 200 (false)`, async () => {
const res = await request(app.getHttpServer())
.patch(`${BASE}/${timesheetId}/approval`)
.send({ is_approved: false });
expect(res.status).toBe(200);
expect(res.body?.is_approved).toBe(false);
});
it(`PATCH ${BASE}/:id/approval (invalid) → 400`, async () => {
const res = await request(app.getHttpServer())
.patch(`${BASE}/${timesheetId}/approval`)
.send({ is_approved: 'plop' });
expect(res.status).toBeGreaterThanOrEqual(400);
expect(res.status).toBeLessThan(500);
});
});

View File

@ -1,112 +0,0 @@
// test/timesheets.e2e-spec.ts
const request = require('supertest');
import { INestApplication } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import {
makeTimesheet,
makeInvalidTimesheet,
} from './factories/timesheet.factory';
import { makeEmployee } from './factories/employee.factory';
import { createApp } from './utils/testing-app';
describe('Timesheets (e2e) — autonome', () => {
const BASE = '/timesheets';
let app: INestApplication;
let employeeIdForCreation: number;
beforeAll(async () => {
app = await createApp();
// ✅ Crée un employé dédié pour cette suite
const empRes = await request(app.getHttpServer())
.post('/employees')
.send(makeEmployee());
if (empRes.status !== 201) {
// eslint-disable-next-line no-console
console.warn('Impossible de créer un employé pour les tests timesheets:', empRes.body || empRes.text);
throw new Error('Setup employé échoué');
}
employeeIdForCreation = empRes.body.id;
});
afterAll(async () => {
const prisma = app.get(PrismaService);
await app.close();
await prisma.$disconnect();
});
it(`GET ${BASE} → 200`, async () => {
const res = await request(app.getHttpServer()).get(BASE);
expect(res.status).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
});
it(`POST ${BASE} (valid) → 201 puis GET /:id → 200`, async () => {
const payload = makeTimesheet(employeeIdForCreation, { is_approved: true });
const createRes = await request(app.getHttpServer()).post(BASE).send(payload);
if (createRes.status !== 201) {
// eslint-disable-next-line no-console
console.log('Create error:', createRes.body || createRes.text);
}
expect(createRes.status).toBe(201);
expect(createRes.body).toEqual(
expect.objectContaining({
id: expect.any(Number),
employee_id: payload.employee_id,
}),
);
const id = createRes.body.id;
const getRes = await request(app.getHttpServer()).get(`${BASE}/${id}`);
expect(getRes.status).toBe(200);
expect(getRes.body).toEqual(
expect.objectContaining({
id,
employee_id: payload.employee_id,
}),
);
});
it(`PATCH ${BASE}/:id → 200 (is_approved toggled)`, async () => {
const created = await request(app.getHttpServer())
.post(BASE)
.send(makeTimesheet(employeeIdForCreation));
expect(created.status).toBe(201);
const id = created.body.id;
const updated = await request(app.getHttpServer())
.patch(`${BASE}/${id}`)
.send({ is_approved: true });
expect(updated.status).toBe(200);
expect(updated.body).toEqual(
expect.objectContaining({
id,
is_approved: true,
}),
);
});
it(`POST ${BASE} (invalid payload) → 400`, async () => {
const bad = await request(app.getHttpServer())
.post(BASE)
.send(makeInvalidTimesheet());
expect(bad.status).toBeGreaterThanOrEqual(400);
expect(bad.status).toBeLessThan(500);
});
it(`DELETE ${BASE}/:id → 200/204`, async () => {
const created = await request(app.getHttpServer())
.post(BASE)
.send(makeTimesheet(employeeIdForCreation));
expect(created.status).toBe(201);
const id = created.body.id;
const delRes = await request(app.getHttpServer()).delete(`${BASE}/${id}`);
expect([200, 204]).toContain(delRes.status);
const getRes = await request(app.getHttpServer()).get(`${BASE}/${id}`);
expect(getRes.status).toBe(404);
});
});

View File

@ -1,21 +0,0 @@
import { INestApplication } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
// export async function resetDb(app: INestApplication) {
// const prisma = app.get(PrismaService);
// const KEEP_USERS = process.env.E2E_KEEP_USERS === '1';
// const excludes = ['_prisma_migrations', ...(KEEP_USERS ? ['users'] : [])];
// const notIn = excludes.map(n => `'${n}'`).join(', ');
// const rows = await prisma.$queryRawUnsafe<Array<{ tablename: string }>>(`
// SELECT table_name AS tablename
// FROM information_schema.tables
// WHERE table_schema = 'public'
// AND table_type = 'BASE TABLE'
// AND table_name NOT IN (${notIn})
// `);
// if (!rows.length) return;
// const list = rows.map(r => `"public"."${r.tablename}"`).join(', ');
// await prisma.$executeRawUnsafe(`TRUNCATE TABLE ${list} RESTART IDENTITY CASCADE;`);
// }

View File

@ -1,20 +0,0 @@
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { AppModule } from 'src/app.module';
// si tu overrides des guards, garde-les comme avant
export async function createApp(): Promise<INestApplication> {
const mod = await Test.createTestingModule({ imports: [AppModule] }).compile();
const app = mod.createNestApplication();
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
forbidNonWhitelisted: true,
validateCustomDecorators: true,
}));
await app.init();
return app;
}