refactor(csv): ajusted extract logics

This commit is contained in:
Matthieu Haineault 2025-12-12 08:25:31 -05:00
parent be957d8180
commit 157a7908be
2 changed files with 48 additions and 24 deletions

View File

@ -1,8 +1,8 @@
import { Controller, Get, Header, Param, Query } from "@nestjs/common";
import { Controller, Get, Param, Query, Res } from "@nestjs/common";
import { CsvExportService } from "./csv-exports.service";
import { ExportCsvOptionsDto } from "./export-csv-options.dto";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from ".prisma/client";
import { Response } from "express";
@Controller('exports')
export class CsvExportController {
@ -10,8 +10,6 @@ export class CsvExportController {
@Get('csv/:year/:period_no')
@ModuleAccessAllowed(ModulesEnum.employee_management)
@Header('Content-Type', 'text/csv; charset=utf-8')
@Header('Content-Disposition', 'attachment; filename="export.csv"')
async exportCsv(
@Query('approved') approved: boolean,
@Query('shifts') shifts: boolean,
@ -22,7 +20,8 @@ export class CsvExportController {
@Query('solucom') solucom: boolean,
@Param('year') year: number,
@Param('period_no') period_no: number,
): Promise<Buffer> {
@Res() response: Response,
): Promise<void> {
const rows = await this.csvService.collectTransaction(
year,
period_no,
@ -40,7 +39,13 @@ export class CsvExportController {
},
}
);
return this.csvService.generateCsv(rows);
const csv_buffer = await this.csvService.generateCsv(rows);
response.set({
'Content-Type': 'text/csv; charset=utf-8',
'Content-Disposition': 'attachment; filename="export.csv"',
});
response.send(csv_buffer);
}
}

View File

@ -1,6 +1,7 @@
import { PrismaService } from "src/prisma/prisma.service";
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { Filters, CsvRow } from "src/time-and-attendance/exports/export-csv-options.dto";
import { computeHours } from "src/common/utils/date-utils";
@ -35,9 +36,10 @@ export class CsvExportService {
}
const approved_filter = filters.approved ? { is_approved: true } : {};
const holiday_code = await this.resolveHolidayTypeCode('HOLIDAY');
const vacation_code = await this.resolveVacationTypeCode('VACATION');
const code_filter = [holiday_code, vacation_code];
const leave_code_filter = [holiday_code, vacation_code];
//Prisma queries
const promises: Array<Promise<any[]>> = [];
@ -47,7 +49,7 @@ export class CsvExportService {
where: {
date: { gte: start, lte: end },
...approved_filter,
bank_code: { bank_code: { notIn: code_filter } },
bank_code: { bank_code: { notIn: leave_code_filter } },
timesheet: { employee: { company_code: { in: company_codes } } },
},
select: {
@ -171,9 +173,9 @@ export class CsvExportService {
return {
company_code: employee.company_code,
external_payroll_id: employee.external_payroll_id,
full_name: `${employee.first_name} ${employee.last_name}`,
full_name: `${employee.user.first_name} ${employee.user.last_name}`,
bank_code: shift.bank_code?.bank_code ?? '',
quantity_hours: this.computeHours(shift.start_time, shift.end_time),
quantity_hours: computeHours(shift.start_time, shift.end_time),
amount: undefined,
week_number: week,
pay_date: this.formatDate(end),
@ -191,7 +193,7 @@ export class CsvExportService {
rows.push({
company_code: employee.company_code,
external_payroll_id: employee.external_payroll_id,
full_name: `${employee.first_name} ${employee.last_name}`,
full_name: `${employee.user.first_name} ${employee.user.last_name}`,
bank_code: expense.bank_code?.bank_code ?? '',
quantity_hours: undefined,
amount: Number(expense.amount),
@ -206,13 +208,38 @@ export class CsvExportService {
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;
const bank_code = String(a.bank_code).localeCompare(String(b.bank_code));
if (bank_code !== 0) return bank_code;
if (a.bank_code !== b.bank_code) return a.bank_code.localeCompare(b.bank_code);
return 0;
});
return rows;
const consolidated_rows = this.consolidateRowHoursAndAmountByType(rows)
return consolidated_rows;
}
consolidateRowHoursAndAmountByType = (rows: CsvRow[]): CsvRow[] => {
type ConsolidateRow = CsvRow & { quantity_hours: number, amount: number };
const shifts_map = new Map<string, ConsolidateRow>();
for (const row of rows) {
const key = `${row.company_code}|${row.external_payroll_id}|${row.full_name}|${row.bank_code}|${row.week_number}`;
const hours = row.quantity_hours ?? 0;
const amounts = row.amount ?? 0;
if (!shifts_map.has(key)) {
shifts_map.set(key, {
...row,
quantity_hours: hours,
amount: amounts,
});
} else {
const existing = shifts_map.get(key)!;
existing.quantity_hours += hours;
existing.amount += amounts;
}
}
return Array.from(shifts_map.values());
}
resolveHolidayTypeCode = async (holiday: string): Promise<string> => {
@ -241,13 +268,11 @@ export class CsvExportService {
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');
const code_no = 271583;
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');
const code_no = 251585;
out.push(code_no);
}
return out;
@ -286,12 +311,6 @@ export class CsvExportService {
return Buffer.from('\uFEFF' + header + body, 'utf8');
}
private computeHours(start: Date, end: Date): number {
const diffMs = end.getTime() - start.getTime();
return +(diffMs / 1000 / 3600).toFixed(2);
}
private computeWeekNumber(start: Date, date: Date): number {
const dayMS = 86400000;
const days = Math.floor((this.toUTC(date).getTime() - this.toUTC(start).getTime()) / dayMS);