Merge branch 'dev/setup/modules/MatthieuH' of git.targo.ca:Targo/targo_backend
This commit is contained in:
commit
213b68fb7d
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "public"."users" ALTER COLUMN "phone_number" SET DATA TYPE TEXT;
|
||||||
|
|
@ -9,6 +9,7 @@ async function main() {
|
||||||
['EVENING' ,'SHIFT', 1.25, 'G43'],
|
['EVENING' ,'SHIFT', 1.25, 'G43'],
|
||||||
['Emergency','SHIFT', 2 , 'G48'],
|
['Emergency','SHIFT', 2 , 'G48'],
|
||||||
['HOLIDAY' ,'SHIFT', 2.0 , 'G700'],
|
['HOLIDAY' ,'SHIFT', 2.0 , 'G700'],
|
||||||
|
|
||||||
|
|
||||||
['EXPENSES','EXPENSE', 1.0 , 'G517'],
|
['EXPENSES','EXPENSE', 1.0 , 'G517'],
|
||||||
['MILEAGE' ,'EXPENSE', 0.72, 'G57'],
|
['MILEAGE' ,'EXPENSE', 0.72, 'G57'],
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
import { PrismaClient, Roles } from '@prisma/client';
|
import { PrismaClient, Roles } from '@prisma/client';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
const BASE_PHONE = '1_100_000_000'; // < 2_147_483_647
|
|
||||||
|
// base sans underscore, en string
|
||||||
|
const BASE_PHONE = "1100000000";
|
||||||
|
|
||||||
function emailFor(i: number) {
|
function emailFor(i: number) {
|
||||||
return `user${i + 1}@example.test`;
|
return `user${i + 1}@example.test`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// 50 users total: 40 employees + 10 customers
|
|
||||||
// Roles distribution for the 40 employees:
|
|
||||||
// 1 ADMIN, 4 SUPERVISOR, 1 HR, 1 ACCOUNTING, 33 EMPLOYEE
|
|
||||||
// 10 CUSTOMER (non-employees)
|
|
||||||
const usersData: {
|
const usersData: {
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
|
|
@ -24,7 +22,6 @@ async function main() {
|
||||||
const firstNames = ['Alex','Sam','Chris','Jordan','Taylor','Morgan','Jamie','Robin','Avery','Casey'];
|
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 lastNames = ['Smith','Johnson','Williams','Brown','Jones','Miller','Davis','Wilson','Taylor','Clark'];
|
||||||
|
|
||||||
// helper to pick
|
|
||||||
const pick = <T>(arr: T[]) => arr[Math.floor(Math.random() * arr.length)];
|
const pick = <T>(arr: T[]) => arr[Math.floor(Math.random() * arr.length)];
|
||||||
|
|
||||||
const rolesForEmployees: Roles[] = [
|
const rolesForEmployees: Roles[] = [
|
||||||
|
|
@ -37,14 +34,14 @@ async function main() {
|
||||||
|
|
||||||
// 40 employees
|
// 40 employees
|
||||||
for (let i = 0; i < 40; i++) {
|
for (let i = 0; i < 40; i++) {
|
||||||
|
|
||||||
const fn = pick(firstNames);
|
const fn = pick(firstNames);
|
||||||
const ln = pick(lastNames);
|
const ln = pick(lastNames);
|
||||||
usersData.push({
|
usersData.push({
|
||||||
first_name: fn,
|
first_name: fn,
|
||||||
last_name: ln,
|
last_name: ln,
|
||||||
email: emailFor(i),
|
email: emailFor(i),
|
||||||
phone_number: BASE_PHONE + i,
|
// on concatène proprement en string
|
||||||
|
phone_number: BASE_PHONE + i.toString(),
|
||||||
residence: Math.random() < 0.5 ? 'QC' : 'ON',
|
residence: Math.random() < 0.5 ? 'QC' : 'ON',
|
||||||
role: rolesForEmployees[i],
|
role: rolesForEmployees[i],
|
||||||
});
|
});
|
||||||
|
|
@ -58,7 +55,7 @@ async function main() {
|
||||||
first_name: fn,
|
first_name: fn,
|
||||||
last_name: ln,
|
last_name: ln,
|
||||||
email: emailFor(i),
|
email: emailFor(i),
|
||||||
phone_number: BASE_PHONE + i,
|
phone_number: BASE_PHONE + i.toString(),
|
||||||
residence: Math.random() < 0.5 ? 'QC' : 'ON',
|
residence: Math.random() < 0.5 ? 'QC' : 'ON',
|
||||||
role: Roles.CUSTOMER,
|
role: Roles.CUSTOMER,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,46 +2,107 @@ import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
function timeAt(hour:number, minute:number) {
|
// Stocker une heure (Postgres TIME) via Date (UTC 1970-01-01)
|
||||||
// stocker une heure (Postgres TIME) via Date (UTC 1970-01-01)
|
function timeAt(hour: number, minute: number) {
|
||||||
return new Date(Date.UTC(1970, 0, 1, hour, minute, 0));
|
return new Date(Date.UTC(1970, 0, 1, hour, minute, 0));
|
||||||
}
|
}
|
||||||
function daysAgo(n:number) {
|
|
||||||
const d = new Date();
|
// Lundi de la semaine (en UTC) pour la date courante
|
||||||
d.setUTCDate(d.getUTCDate() - n);
|
function mondayOfThisWeekUTC(now = new Date()) {
|
||||||
d.setUTCHours(0,0,0,0);
|
// converti en UTC (sans l'heure)
|
||||||
|
const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
||||||
|
const day = d.getUTCDay(); // 0=Dim, 1=Lun, ...
|
||||||
|
const diffToMonday = (day + 6) % 7; // 0 si lundi, 1 si mardi, ... 6 si dimanche
|
||||||
|
d.setUTCDate(d.getUTCDate() - diffToMonday);
|
||||||
|
d.setUTCHours(0, 0, 0, 0);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retourne les 5 dates Lundi→Vendredi (UTC, à minuit)
|
||||||
|
function currentWeekDates() {
|
||||||
|
const monday = mondayOfThisWeekUTC();
|
||||||
|
return Array.from({ length: 5 }, (_, i) => {
|
||||||
|
const d = new Date(monday);
|
||||||
|
d.setUTCDate(monday.getUTCDate() + i);
|
||||||
|
return d;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const bankCodes = await prisma.bankCodes.findMany({ where: { categorie: 'SHIFT' }, select: { id: true } });
|
// On récupère les bank codes requis (ajuste le nom de la colonne "code" si besoin)
|
||||||
if (!bankCodes.length) throw new Error('Need SHIFT bank codes');
|
const BANKS = ['G1', 'G305', 'G105'] 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]));
|
||||||
|
|
||||||
|
// Vérifications
|
||||||
|
for (const c of BANKS) {
|
||||||
|
if (!bcMap.has(c)) throw new Error(`Bank code manquant: ${c}`);
|
||||||
|
}
|
||||||
|
|
||||||
const employees = await prisma.employees.findMany({ select: { id: true } });
|
const employees = await prisma.employees.findMany({ select: { id: true } });
|
||||||
|
if (!employees.length) {
|
||||||
|
console.log('Aucun employé — rien à insérer.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const weekDays = currentWeekDates();
|
||||||
|
|
||||||
|
// Par défaut: G1 (régulier). 1 employé sur 5 : 1 jour de la semaine passe à G305 OU G105 (au hasard)
|
||||||
|
// Horaires: on décale par employé pour garantir des horaires différents.
|
||||||
|
// - start = 7, 7h30, 8, 8h30 selon l’index employé
|
||||||
|
// - durée = 8h sauf vendredi (7h) pour varier un peu
|
||||||
|
for (let ei = 0; ei < employees.length; ei++) {
|
||||||
|
const e = employees[ei];
|
||||||
|
|
||||||
for (const e of employees) {
|
|
||||||
const tss = await prisma.timesheets.findMany({
|
const tss = await prisma.timesheets.findMany({
|
||||||
where: { employee_id: e.id },
|
where: { employee_id: e.id },
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
|
orderBy: { id: 'asc' }, // ajuste si tu préfères "created_at" etc.
|
||||||
});
|
});
|
||||||
if (!tss.length) continue;
|
if (!tss.length) continue;
|
||||||
|
|
||||||
// 10 shifts / employee
|
// Horaires spécifiques à l’employé (déterministes)
|
||||||
for (let i = 0; i < 10; i++) {
|
const startHalfHourSlot = ei % 4; // 0..3 -> 7:00, 7:30, 8:00, 8:30
|
||||||
const ts = tss[i % tss.length];
|
const startHourBase = 7 + Math.floor(startHalfHourSlot / 2); // 7 ou 8
|
||||||
const bc = bankCodes[i % bankCodes.length];
|
const startMinuteBase = (startHalfHourSlot % 2) * 30; // 0 ou 30
|
||||||
const date = daysAgo(7 + i); // la dernière quinzaine
|
|
||||||
const startH = 8 + (i % 3); // 8..10
|
// Doit-on donner un jour "différent" de G1 à cet employé ?
|
||||||
const endH = startH + 7 + (i % 2); // 15..17
|
const isSpecial = (ei % 5) === 0; // 1 sur 5
|
||||||
|
const specialDayIdx = isSpecial ? Math.floor(Math.random() * 5) : -1;
|
||||||
|
const specialCode = isSpecial ? (Math.random() < 0.5 ? 'G305' : 'G105') : 'G1';
|
||||||
|
|
||||||
|
// 5 jours (lun→ven)
|
||||||
|
for (let di = 0; di < weekDays.length; di++) {
|
||||||
|
const date = weekDays[di];
|
||||||
|
|
||||||
|
// Bank code du jour
|
||||||
|
const codeToday = (di === specialDayIdx) ? specialCode : 'G1';
|
||||||
|
const bank_code_id = bcMap.get(codeToday)!;
|
||||||
|
|
||||||
|
// Durée : 8h habituellement, 7h le vendredi pour varier (di==4)
|
||||||
|
const duration = (di === 4) ? 7 : 8;
|
||||||
|
|
||||||
|
// Légère variation journalière (+0..2h) pour casser la monotonie, mais bornée
|
||||||
|
const dayOffset = di % 3; // 0,1,2
|
||||||
|
const startH = Math.min(10, startHourBase + dayOffset);
|
||||||
|
const startM = startMinuteBase;
|
||||||
|
|
||||||
|
const endH = startH + duration;
|
||||||
|
const endM = startM;
|
||||||
|
|
||||||
|
const ts = tss[di % tss.length];
|
||||||
|
|
||||||
await prisma.shifts.create({
|
await prisma.shifts.create({
|
||||||
data: {
|
data: {
|
||||||
timesheet_id: ts.id,
|
timesheet_id: ts.id,
|
||||||
bank_code_id: bc.id,
|
bank_code_id,
|
||||||
description: `Shift ${i + 1} for emp ${e.id}`,
|
description: `Shift ${di + 1} (Semaine courante) emp ${e.id} — ${codeToday}`,
|
||||||
date,
|
date, // Date du jour (UTC minuit)
|
||||||
start_time: timeAt(startH, 0),
|
start_time: timeAt(startH, startM),
|
||||||
end_time: timeAt(endH, 0),
|
end_time: timeAt(endH, endM),
|
||||||
is_approved: Math.random() < 0.5,
|
is_approved: Math.random() < 0.5,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -49,7 +110,7 @@ async function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const total = await prisma.shifts.count();
|
const total = await prisma.shifts.count();
|
||||||
console.log(`✓ Shifts: ${total} total rows`);
|
console.log(`✓ Shifts: ${total} total rows (semaine courante L→V)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
main().finally(() => prisma.$disconnect());
|
main().finally(() => prisma.$disconnect());
|
||||||
|
|
|
||||||
|
|
@ -2,43 +2,111 @@ import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
function daysAgo(n:number) {
|
// Lundi de la semaine (en UTC) pour la date courante
|
||||||
const d = new Date();
|
function mondayOfThisWeekUTC(now = new Date()) {
|
||||||
d.setUTCDate(d.getUTCDate() - n);
|
const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
||||||
d.setUTCHours(0,0,0,0);
|
const day = d.getUTCDay(); // 0=Dim, 1=Lun, ...
|
||||||
|
const diffToMonday = (day + 6) % 7; // 0 si lundi
|
||||||
|
d.setUTCDate(d.getUTCDate() - diffToMonday);
|
||||||
|
d.setUTCHours(0, 0, 0, 0);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
// Retourne les 5 dates Lundi→Vendredi (UTC, à minuit)
|
||||||
const expenseCodes = await prisma.bankCodes.findMany({ where: { categorie: 'EXPENSE' }, select: { id: true } });
|
function currentWeekDates() {
|
||||||
if (!expenseCodes.length) throw new Error('Need EXPENSE bank codes');
|
const monday = mondayOfThisWeekUTC();
|
||||||
|
return Array.from({ length: 5 }, (_, i) => {
|
||||||
|
const d = new Date(monday);
|
||||||
|
d.setUTCDate(monday.getUTCDate() + i);
|
||||||
|
return d;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const timesheets = await prisma.timesheets.findMany({ select: { id: true } });
|
function rndInt(min: number, max: number) {
|
||||||
if (!timesheets.length) {
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
console.warn('No timesheets found; aborting expenses seed.');
|
}
|
||||||
|
function rndAmount(minCents: number, maxCents: number) {
|
||||||
|
const cents = rndInt(minCents, maxCents);
|
||||||
|
return (cents / 100).toFixed(2); // string (ex: "123.45")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// On veut explicitement G503 (mileage) et G517 (remboursement)
|
||||||
|
const wanted = ['G57', 'G517'] as const;
|
||||||
|
const codes = await prisma.bankCodes.findMany({
|
||||||
|
where: { bank_code: { in: wanted as unknown as string[] } },
|
||||||
|
select: { id: true, bank_code: true },
|
||||||
|
});
|
||||||
|
const map = new Map(codes.map(c => [c.bank_code, c.id]));
|
||||||
|
for (const c of wanted) {
|
||||||
|
if (!map.has(c)) throw new Error(`Bank code manquant: ${c}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const employees = await prisma.employees.findMany({ select: { id: true } });
|
||||||
|
if (!employees.length) {
|
||||||
|
console.warn('Aucun employé — rien à insérer.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5 expenses distribuées aléatoirement parmi les employés (via timesheets)
|
const weekDays = currentWeekDates();
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
const ts = timesheets[Math.floor(Math.random() * timesheets.length)];
|
// Règles:
|
||||||
const bc = expenseCodes[i % expenseCodes.length];
|
// - (index % 5) === 0 -> mileage G503 (km)
|
||||||
await prisma.expenses.create({
|
// - (index % 5) === 1 -> remboursement G517 ($)
|
||||||
data: {
|
// Les autres: pas de dépense
|
||||||
timesheet_id: ts.id,
|
// On met la dépense un des jours de la semaine (déterministe mais varié).
|
||||||
bank_code_id: bc.id,
|
let created = 0;
|
||||||
date: daysAgo(3 + i),
|
|
||||||
amount: (50 + i * 10).toFixed(2),
|
for (let ei = 0; ei < employees.length; ei++) {
|
||||||
attachement: null,
|
const e = employees[ei];
|
||||||
description: `Expense #${i + 1}`,
|
|
||||||
is_approved: Math.random() < 0.5,
|
const ts = await prisma.timesheets.findFirst({
|
||||||
supervisor_comment: Math.random() < 0.3 ? 'OK' : null,
|
where: { employee_id: e.id },
|
||||||
},
|
select: { id: true },
|
||||||
|
orderBy: { id: 'asc' }, // ajuste si tu préfères par date
|
||||||
});
|
});
|
||||||
|
if (!ts) continue;
|
||||||
|
|
||||||
|
const dayIdx = ei % 5; // 0..4 -> répartit sur la semaine
|
||||||
|
const date = weekDays[dayIdx];
|
||||||
|
|
||||||
|
if (ei % 5 === 0) {
|
||||||
|
// Mileage (G503) — amount = km
|
||||||
|
const km = rndInt(10, 180); // 10..180 km
|
||||||
|
await prisma.expenses.create({
|
||||||
|
data: {
|
||||||
|
timesheet_id: ts.id,
|
||||||
|
bank_code_id: map.get('G57')!,
|
||||||
|
date,
|
||||||
|
amount: km.toString(), // on stocke le nombre de km dans amount (si tu as un champ "quantity_km", remplace ici)
|
||||||
|
attachement: null,
|
||||||
|
description: `Mileage ${km} km (emp ${e.id})`,
|
||||||
|
is_approved: Math.random() < 0.6,
|
||||||
|
supervisor_comment: Math.random() < 0.2 ? 'OK' : null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
created++;
|
||||||
|
} else if (ei % 5 === 1) {
|
||||||
|
// Remboursement (G517) — amount = $
|
||||||
|
const dollars = rndAmount(2000, 25000); // 20.00$..250.00$
|
||||||
|
await prisma.expenses.create({
|
||||||
|
data: {
|
||||||
|
timesheet_id: ts.id,
|
||||||
|
bank_code_id: map.get('G517')!,
|
||||||
|
date,
|
||||||
|
amount: dollars,
|
||||||
|
attachement: null,
|
||||||
|
description: `Remboursement ${dollars}$ (emp ${e.id})`,
|
||||||
|
is_approved: Math.random() < 0.6,
|
||||||
|
supervisor_comment: Math.random() < 0.2 ? 'OK' : null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
created++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const total = await prisma.expenses.count();
|
const total = await prisma.expenses.count();
|
||||||
console.log(`✓ Expenses: ${total} total rows`);
|
console.log(`✓ Expenses: ${created} nouvelles lignes, ${total} total rows (semaine courante)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
main().finally(() => prisma.$disconnect());
|
main().finally(() => prisma.$disconnect());
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { Roles as RoleEnum } from '.prisma/client';
|
||||||
import { CsvExportService } from "../services/csv-exports.service";
|
import { CsvExportService } from "../services/csv-exports.service";
|
||||||
// import { ExportCompany, ExportCsvOptionsDto, ExportType } from "../dtos/export-csv-options.dto";
|
// import { ExportCompany, ExportCsvOptionsDto, ExportType } from "../dtos/export-csv-options.dto";
|
||||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
|
import { ExportCsvOptionsDto } from "../dtos/export-csv-options.dto";
|
||||||
|
|
||||||
|
|
||||||
@Controller('exports')
|
@Controller('exports')
|
||||||
|
|
@ -11,34 +12,29 @@ import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
export class CsvExportController {
|
export class CsvExportController {
|
||||||
constructor(private readonly csvService: CsvExportService) {}
|
constructor(private readonly csvService: CsvExportService) {}
|
||||||
|
|
||||||
// @Get('csv/:year/:period_no')
|
@Get('csv')
|
||||||
// @Header('Content-Type', 'text/csv; charset=utf-8')
|
@Header('Content-Type', 'text/csv; charset=utf-8')
|
||||||
// @Header('Content-Disposition', 'attachment; filename="export.csv"')
|
@Header('Content-Disposition', 'attachment; filename="export.csv"')
|
||||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.ACCOUNTING, RoleEnum.HR)
|
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.ACCOUNTING, RoleEnum.HR)
|
||||||
// async exportCsv(@Query() options: ExportCsvOptionsDto,
|
async exportCsv(@Query() query: ExportCsvOptionsDto ): Promise<Buffer> {
|
||||||
// @Query('period') periodId: string ): Promise<Buffer> {
|
const rows = await this.csvService.collectTransaction(
|
||||||
// //modify to accept year and period_number
|
query.year,
|
||||||
// //sets default values
|
query.period_no,
|
||||||
// const companies = options.companies && options.companies.length ? options.companies :
|
{
|
||||||
// [ ExportCompany.TARGO, ExportCompany.SOLUCOM];
|
approved: query.approved ?? true,
|
||||||
// const types = options.type && options.type.length ? options.type :
|
types: {
|
||||||
// Object.values(ExportType);
|
shifts: query.shifts ?? true,
|
||||||
|
expenses: query.expenses ?? true,
|
||||||
// //collects all
|
holiday: query.holiday ?? true,
|
||||||
// const all = await this.csvService.collectTransaction(Number(periodId), companies);
|
vacation: query.vacation ?? true,
|
||||||
|
},
|
||||||
// //filters by type
|
companies: {
|
||||||
// const filtered = all.filter(row => {
|
targo: query.targo ?? true,
|
||||||
// switch (row.bank_code.toLocaleLowerCase()) {
|
solucom: query.solucom ?? true,
|
||||||
// case 'holiday' : return types.includes(ExportType.HOLIDAY);
|
},
|
||||||
// case 'vacation' : return types.includes(ExportType.VACATION);
|
}
|
||||||
// case 'expenses' : return types.includes(ExportType.EXPENSES);
|
);
|
||||||
// default : return types.includes(ExportType.SHIFTS);
|
return this.csvService.generateCsv(rows);
|
||||||
// }
|
}
|
||||||
// });
|
|
||||||
|
|
||||||
// //generating the csv file
|
|
||||||
// return this.csvService.generateCsv(filtered);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -31,187 +31,222 @@ type Filters = {
|
||||||
export class CsvExportService {
|
export class CsvExportService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
// async collectTransaction(
|
async collectTransaction(
|
||||||
// year: number,
|
year: number,
|
||||||
// period_no: number,
|
period_no: number,
|
||||||
// filters: Filters,
|
filters: Filters,
|
||||||
// approved: boolean = true
|
approved: boolean = true
|
||||||
// ): Promise<CsvRow[]> {
|
): Promise<CsvRow[]> {
|
||||||
//fetch period
|
//fetch period
|
||||||
// const period = await this.prisma.payPeriods.findFirst({
|
const period = await this.prisma.payPeriods.findFirst({
|
||||||
// where: { pay_year: year, pay_period_no: period_no },
|
where: { pay_year: year, pay_period_no: period_no },
|
||||||
// select: { period_start: true, period_end: true },
|
select: { period_start: true, period_end: true },
|
||||||
// });
|
});
|
||||||
// if(!period) throw new NotFoundException(`Pay period ${ year }-${ period_no } not found`);
|
if(!period) throw new NotFoundException(`Pay period ${ year }-${ period_no } not found`);
|
||||||
|
|
||||||
// const start = period.period_start;
|
const start = period.period_start;
|
||||||
// const end = period.period_end;
|
const end = period.period_end;
|
||||||
|
|
||||||
// //fetch company codes from .env
|
//fetch company codes from .env
|
||||||
// const comapany_codes = this.resolveCompanyCodes(filters.companies);
|
const company_codes = this.resolveCompanyCodes(filters.companies);
|
||||||
// if(comapany_codes.length === 0) throw new BadRequestException('No company selected');
|
if(company_codes.length === 0) throw new BadRequestException('No company selected');
|
||||||
|
|
||||||
// //Flag types
|
//Flag types
|
||||||
// const { shifts: want_shifts, expenses: want_expense, holiday: want_holiday, vacation: want_vacation } = filters.types;
|
const { shifts: want_shifts, expenses: want_expense, holiday: want_holiday, vacation: want_vacation } = filters.types;
|
||||||
// if(!want_shifts && !want_expense && !want_holiday && !want_vacation) {
|
if(!want_shifts && !want_expense && !want_holiday && !want_vacation) {
|
||||||
// throw new BadRequestException(' No export type selected ');
|
throw new BadRequestException(' No export type selected ');
|
||||||
// }
|
}
|
||||||
|
|
||||||
// const approved_filter = filters.approved? { is_approved: true } : {};
|
const approved_filter = filters.approved? { is_approved: true } : {};
|
||||||
|
|
||||||
// //Prisma queries
|
const {holiday_code, vacation_code} = this.resolveLeaveCodes();
|
||||||
// const [shifts, expenses] = await Promise.all([
|
|
||||||
// want_shifts || want_expense || want_holiday || want_vacation
|
|
||||||
// ])
|
|
||||||
|
|
||||||
|
//Prisma queries
|
||||||
|
const promises: Array<Promise<any[]>> = [];
|
||||||
|
|
||||||
|
if (want_shifts) {
|
||||||
|
promises.push( this.prisma.shifts.findMany({
|
||||||
|
where: {
|
||||||
|
date: { gte: start, lte: end },
|
||||||
|
...approved_filter,
|
||||||
|
bank_code: { bank_code: { notIn: [ holiday_code, vacation_code ] } },
|
||||||
|
timesheet: { employee: { company_code: { in: company_codes } } },
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
date: true,
|
||||||
|
start_time: true,
|
||||||
|
end_time: true,
|
||||||
|
bank_code: { select: { bank_code: true } },
|
||||||
|
timesheet: { select: {
|
||||||
|
employee: { select: {
|
||||||
|
company_code: true,
|
||||||
|
external_payroll_id: true,
|
||||||
|
user: { select: { first_name: true, last_name: true } },
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
promises.push(Promise.resolve([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(want_holiday) {
|
||||||
|
promises.push( this.prisma.shifts.findMany({
|
||||||
|
where: {
|
||||||
|
date: { gte: start, lte: end },
|
||||||
|
...approved_filter,
|
||||||
|
bank_code: { bank_code: holiday_code },
|
||||||
|
timesheet: { employee: { company_code: { in: company_codes } } },
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
date: true,
|
||||||
|
start_time: true,
|
||||||
|
end_time: true,
|
||||||
|
bank_code: { select: { bank_code: true } },
|
||||||
|
timesheet: { select: {
|
||||||
|
employee: { select: {
|
||||||
|
company_code: true,
|
||||||
|
external_payroll_id: true,
|
||||||
|
user: { select: { first_name: true,last_name: true } },
|
||||||
|
} },
|
||||||
|
} },
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}else {
|
||||||
|
promises.push(Promise.resolve([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(want_vacation) {
|
||||||
|
promises.push( this.prisma.shifts.findMany({
|
||||||
|
where: {
|
||||||
|
date: { gte: start, lte: end },
|
||||||
|
...approved_filter,
|
||||||
|
bank_code: { bank_code: vacation_code },
|
||||||
|
timesheet: { employee: { company_code: { in: company_codes } } },
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
date: true,
|
||||||
|
start_time: true,
|
||||||
|
end_time: true,
|
||||||
|
bank_code: { select: { bank_code: true } },
|
||||||
|
timesheet: { select: {
|
||||||
|
employee: { select: {
|
||||||
|
company_code: true,
|
||||||
|
external_payroll_id: true,
|
||||||
|
user: { select: { first_name: true,last_name: true } },
|
||||||
|
} },
|
||||||
|
} },
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}else {
|
||||||
|
promises.push(Promise.resolve([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(want_expense) {
|
||||||
|
promises.push( this.prisma.expenses.findMany({
|
||||||
|
where: {
|
||||||
|
date: { gte: start, lte: end },
|
||||||
|
...approved_filter,
|
||||||
|
timesheet: { employee: { company_code: { in: company_codes } } },
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
date: true,
|
||||||
|
amount: true,
|
||||||
|
bank_code: { select: { bank_code: true } },
|
||||||
|
timesheet: { select: {
|
||||||
|
employee: { select: {
|
||||||
|
company_code: true,
|
||||||
|
external_payroll_id: true,
|
||||||
|
user: { select: { first_name: true, last_name: true } },
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
promises.push(Promise.resolve([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
//array of arrays
|
||||||
|
const [ base_shifts, holiday_shifts, vacation_shifts, expenses ] = await Promise.all(promises);
|
||||||
|
//mapping
|
||||||
|
const rows: CsvRow[] = [];
|
||||||
|
|
||||||
// const company_codes = companies.map(c => c === ExportCompany.TARGO ? 1 : 2);
|
const map_shifts = (shift: any, is_holiday: boolean) => {
|
||||||
|
const employee = shift.timesheet.employee;
|
||||||
|
const week = this.computeWeekNumber(start, shift.date);
|
||||||
|
return {
|
||||||
|
company_code: employee.company_code,
|
||||||
|
external_payroll_id: employee.external_payroll_id,
|
||||||
|
full_name: `${employee.first_name} ${ employee.last_name}`,
|
||||||
|
bank_code: shift.bank_code?.bank_code ?? '',
|
||||||
|
quantity_hours: this.computeHours(shift.start_time, shift.end_time),
|
||||||
|
amount: undefined,
|
||||||
|
week_number: week,
|
||||||
|
pay_date: this.formatDate(end),
|
||||||
|
holiday_date: is_holiday? this.formatDate(shift.date) : '',
|
||||||
|
} as CsvRow;
|
||||||
|
};
|
||||||
|
//final mapping of all shifts based filters
|
||||||
|
for (const shift of base_shifts) rows.push(map_shifts(shift, false));
|
||||||
|
for (const shift of holiday_shifts) rows.push(map_shifts(shift, true ));
|
||||||
|
for (const shift of vacation_shifts) rows.push(map_shifts(shift, false));
|
||||||
|
|
||||||
// const period = await this.prisma.payPeriods.findFirst({
|
for (const expense of expenses) {
|
||||||
// where: { pay_period_no: period_id },
|
const employee = expense.timesheet.employee;
|
||||||
// });
|
const week = this.computeWeekNumber(start, expense.date);
|
||||||
// if(!period) throw new NotFoundException(`Pay period ${period_id} not found`);
|
rows.push({
|
||||||
|
company_code: employee.company_code,
|
||||||
|
external_payroll_id: employee.external_payroll_id,
|
||||||
|
full_name: `${employee.first_name} ${ employee.last_name}`,
|
||||||
|
bank_code: expense.bank_code?.bank_code ?? '',
|
||||||
|
quantity_hours: undefined,
|
||||||
|
amount: Number(expense.amount),
|
||||||
|
week_number: week,
|
||||||
|
pay_date: this.formatDate(end),
|
||||||
|
holiday_date: '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// const start_date = period.period_start;
|
//Final mapping and sorts
|
||||||
// const end_date = period.period_end;
|
rows.sort((a,b) => {
|
||||||
|
if(a.external_payroll_id !== b.external_payroll_id) {
|
||||||
|
return a.external_payroll_id - b.external_payroll_id;
|
||||||
|
}
|
||||||
|
const bk_code = String(a.bank_code).localeCompare(String(b.bank_code));
|
||||||
|
if(bk_code !== 0) return bk_code;
|
||||||
|
if(a.bank_code !== b.bank_code) return a.bank_code.localeCompare(b.bank_code);
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
// const included_shifts = await this.prisma.shifts.findMany({
|
return rows;
|
||||||
// where: { }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const approved_filter = approved ? { is_approved: true } : {};
|
|
||||||
|
|
||||||
// //fetching shifts
|
|
||||||
// const shifts = await this.prisma.shifts.findMany({
|
|
||||||
// where: {
|
|
||||||
// date: { gte: start_date, lte: end_date },
|
|
||||||
// ...approved_filter,
|
|
||||||
// timesheet: {
|
|
||||||
// employee: { company_code: { in: company_codes} } },
|
|
||||||
// },
|
|
||||||
// include: {
|
|
||||||
// bank_code: true,
|
|
||||||
// timesheet: { include: {
|
|
||||||
// employee: { include: {
|
|
||||||
// user:true,
|
|
||||||
// supervisor: { include: {
|
|
||||||
// user:true } } } } } },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// //fetching expenses
|
|
||||||
// const expenses = await this.prisma.expenses.findMany({
|
|
||||||
// where: {
|
|
||||||
// date: { gte: start_date, lte: end_date },
|
|
||||||
// ...approved_filter,
|
|
||||||
// timesheet: { employee: { company_code: { in: company_codes} } },
|
|
||||||
// },
|
|
||||||
// include: { bank_code: true,
|
|
||||||
// timesheet: { include: {
|
|
||||||
// employee: { include: {
|
|
||||||
// user: true,
|
|
||||||
// supervisor: { include: {
|
|
||||||
// user:true } } } } } },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// //fetching leave requests
|
|
||||||
// const leaves = await this.prisma.leaveRequests.findMany({
|
|
||||||
// where : {
|
|
||||||
// start_date_time: { gte: start_date, lte: end_date },
|
|
||||||
// employee: { company_code: { in: company_codes } },
|
|
||||||
// },
|
|
||||||
// include: {
|
|
||||||
// bank_code: true,
|
|
||||||
// employee: { include: {
|
|
||||||
// user: true,
|
|
||||||
// supervisor: { include: {
|
|
||||||
// user: true } } } },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const rows: CsvRow[] = [];
|
|
||||||
|
|
||||||
// //Shifts Mapping
|
|
||||||
// for (const shift of shifts) {
|
|
||||||
// const emp = shift.timesheet.employee;
|
|
||||||
// const week_number = this.computeWeekNumber(start_date, shift.date);
|
|
||||||
// const hours = this.computeHours(shift.start_time, shift.end_time);
|
|
||||||
|
|
||||||
// rows.push({
|
|
||||||
// company_code: emp.company_code,
|
|
||||||
// external_payroll_id: emp.external_payroll_id,
|
|
||||||
// full_name: `${emp.user.first_name} ${emp.user.last_name}`,
|
|
||||||
// bank_code: shift.bank_code.bank_code,
|
|
||||||
// quantity_hours: hours,
|
|
||||||
// amount: undefined,
|
|
||||||
// week_number,
|
|
||||||
// pay_date: this.formatDate(end_date),
|
|
||||||
// holiday_date: undefined,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //Expenses Mapping
|
|
||||||
// for (const e of expenses) {
|
|
||||||
// const emp = e.timesheet.employee;
|
|
||||||
// const week_number = this.computeWeekNumber(start_date, e.date);
|
|
||||||
|
|
||||||
// rows.push({
|
|
||||||
// company_code: emp.company_code,
|
|
||||||
// external_payroll_id: emp.external_payroll_id,
|
|
||||||
// full_name: `${emp.user.first_name} ${emp.user.last_name}`,
|
|
||||||
// bank_code: e.bank_code.bank_code,
|
|
||||||
// quantity_hours: undefined,
|
|
||||||
// amount: Number(e.amount),
|
|
||||||
// week_number,
|
|
||||||
// pay_date: this.formatDate(end_date),
|
|
||||||
// holiday_date: undefined,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //Leaves Mapping
|
|
||||||
// for(const l of leaves) {
|
|
||||||
// if(!l.bank_code) continue;
|
|
||||||
// const emp = l.employee;
|
|
||||||
// const start = l.start_date_time;
|
|
||||||
// const end = l.end_date_time ?? start;
|
|
||||||
|
|
||||||
// const week_number = this.computeWeekNumber(start_date, start);
|
|
||||||
// const hours = this.computeHours(start, end);
|
|
||||||
|
|
||||||
// rows.push({
|
|
||||||
// company_code: emp.company_code,
|
|
||||||
// external_payroll_id: emp.external_payroll_id,
|
|
||||||
// full_name: `${emp.user.first_name} ${emp.user.last_name}`,
|
|
||||||
// bank_code: l.bank_code.bank_code,
|
|
||||||
// quantity_hours: hours,
|
|
||||||
// amount: undefined,
|
|
||||||
// week_number,
|
|
||||||
// pay_date: this.formatDate(end_date),
|
|
||||||
// holiday_date: undefined,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
//Final Mapping and sorts
|
|
||||||
// return rows.sort((a,b) => {
|
|
||||||
// if(a.external_payroll_id !== b.external_payroll_id) {
|
|
||||||
// return a.external_payroll_id - b.external_payroll_id;
|
|
||||||
// }
|
|
||||||
// if(a.bank_code !== b.bank_code) {
|
|
||||||
// return a.bank_code.localeCompare(b.bank_code);
|
|
||||||
// }
|
|
||||||
// return a.week_number - b.week_number;
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
resolveCompanyCodes(companies: { targo: boolean; solucom: boolean; }) {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolveLeaveCodes(): { holiday_code: string; vacation_code: string; } {
|
||||||
|
const holiday_code = process.env.HOLIDAY_CODE?.trim();
|
||||||
|
if(!holiday_code) throw new BadRequestException('Missing Holiday bank code');
|
||||||
|
|
||||||
|
const vacation_code = process.env.VACATION_CODE?.trim();
|
||||||
|
if(!vacation_code) throw new BadRequestException('Missing Vacation bank code');
|
||||||
|
|
||||||
|
return { holiday_code, vacation_code};
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveCompanyCodes(companies: { targo: boolean; solucom: boolean; }): number[] {
|
||||||
|
const out: number[] = [];
|
||||||
|
if (companies.targo) {
|
||||||
|
const code_no = parseInt(process.env.TARGO_NO ?? '', 10);
|
||||||
|
if(!Number.isFinite(code_no) || code_no <= 0) throw new BadRequestException('Invalid Targo code in env');
|
||||||
|
out.push(code_no);
|
||||||
|
}
|
||||||
|
if (companies.solucom) {
|
||||||
|
const code_no = parseInt(process.env.SOLUCOM_NO ?? '', 10);
|
||||||
|
if(!Number.isFinite(code_no) || code_no <= 0) throw new BadRequestException('Invalid Solucom code in env');
|
||||||
|
out.push(code_no);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
//csv builder and "mise en page"
|
||||||
generateCsv(rows: CsvRow[]): Buffer {
|
generateCsv(rows: CsvRow[]): Buffer {
|
||||||
const header = [
|
const header = [
|
||||||
'company_code',
|
'company_code',
|
||||||
|
|
@ -225,18 +260,23 @@ export class CsvExportService {
|
||||||
'holiday_date',
|
'holiday_date',
|
||||||
].join(',') + '\n';
|
].join(',') + '\n';
|
||||||
|
|
||||||
const body = rows.map(r => [
|
const body = rows.map(row => {
|
||||||
r.company_code,
|
const full_name = `${String(row.full_name).replace(/"/g, '""')}`;
|
||||||
r.external_payroll_id,
|
const quantity_hours = (typeof row.quantity_hours === 'number') ? row.quantity_hours.toFixed(2) : '';
|
||||||
`${r.full_name.replace(/"/g, '""')}`,
|
const amount = (typeof row.amount === 'number') ? row.amount.toFixed(2) : '';
|
||||||
r.bank_code,
|
return [
|
||||||
r.quantity_hours?.toFixed(2) ?? '',
|
row.company_code,
|
||||||
r.week_number,
|
row.external_payroll_id,
|
||||||
r.pay_date,
|
full_name,
|
||||||
r.holiday_date ?? '',
|
row.bank_code,
|
||||||
].join(',')).join('\n');
|
quantity_hours,
|
||||||
|
amount,
|
||||||
return Buffer.from('\uFEFF' + header + body, 'utf8');
|
row.week_number,
|
||||||
|
row.pay_date,
|
||||||
|
row.holiday_date ?? '',
|
||||||
|
].join(',');
|
||||||
|
}).join('\n');
|
||||||
|
return Buffer.from('\uFEFF' + header + body, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -246,9 +286,13 @@ export class CsvExportService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private computeWeekNumber(start: Date, date: Date): number {
|
private computeWeekNumber(start: Date, date: Date): number {
|
||||||
const days = Math.floor((date.getTime() - start.getTime()) / (1000*60*60*24));
|
const dayMS = 86400000;
|
||||||
|
const days = Math.floor((this.toUTC(date).getTime() - this.toUTC(start).getTime())/ dayMS);
|
||||||
return Math.floor(days / 7 ) + 1;
|
return Math.floor(days / 7 ) + 1;
|
||||||
}
|
}
|
||||||
|
toUTC(date: Date) {
|
||||||
|
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
||||||
|
}
|
||||||
|
|
||||||
private formatDate(d:Date): string {
|
private formatDate(d:Date): string {
|
||||||
return d.toISOString().split('T')[0];
|
return d.toISOString().split('T')[0];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user