refactor(CSV): modify export csv to match checklist options
This commit is contained in:
parent
994e02ba7f
commit
5b2377796a
|
|
@ -4,6 +4,7 @@ import { Roles as RoleEnum } from '.prisma/client';
|
|||
import { CsvExportService } from "../services/csv-exports.service";
|
||||
// import { ExportCompany, ExportCsvOptionsDto, ExportType } from "../dtos/export-csv-options.dto";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { ExportCsvOptionsDto } from "../dtos/export-csv-options.dto";
|
||||
|
||||
|
||||
@Controller('exports')
|
||||
|
|
@ -11,34 +12,29 @@ import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
|||
export class CsvExportController {
|
||||
constructor(private readonly csvService: CsvExportService) {}
|
||||
|
||||
// @Get('csv/:year/:period_no')
|
||||
// @Header('Content-Type', 'text/csv; charset=utf-8')
|
||||
// @Header('Content-Disposition', 'attachment; filename="export.csv"')
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.ACCOUNTING, RoleEnum.HR)
|
||||
// async exportCsv(@Query() options: ExportCsvOptionsDto,
|
||||
// @Query('period') periodId: string ): Promise<Buffer> {
|
||||
// //modify to accept year and period_number
|
||||
// //sets default values
|
||||
// const companies = options.companies && options.companies.length ? options.companies :
|
||||
// [ ExportCompany.TARGO, ExportCompany.SOLUCOM];
|
||||
// const types = options.type && options.type.length ? options.type :
|
||||
// Object.values(ExportType);
|
||||
|
||||
// //collects all
|
||||
// const all = await this.csvService.collectTransaction(Number(periodId), companies);
|
||||
|
||||
// //filters by type
|
||||
// const filtered = all.filter(row => {
|
||||
// switch (row.bank_code.toLocaleLowerCase()) {
|
||||
// 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);
|
||||
// }
|
||||
// });
|
||||
|
||||
// //generating the csv file
|
||||
// return this.csvService.generateCsv(filtered);
|
||||
// }
|
||||
@Get('csv')
|
||||
@Header('Content-Type', 'text/csv; charset=utf-8')
|
||||
@Header('Content-Disposition', 'attachment; filename="export.csv"')
|
||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.ACCOUNTING, RoleEnum.HR)
|
||||
async exportCsv(@Query() query: ExportCsvOptionsDto ): Promise<Buffer> {
|
||||
const rows = await this.csvService.collectTransaction(
|
||||
query.year,
|
||||
query.period_no,
|
||||
{
|
||||
approved: query.approved ?? true,
|
||||
types: {
|
||||
shifts: query.shifts ?? true,
|
||||
expenses: query.expenses ?? true,
|
||||
holiday: query.holiday ?? true,
|
||||
vacation: query.vacation ?? true,
|
||||
},
|
||||
companies: {
|
||||
targo: query.targo ?? true,
|
||||
solucom: query.solucom ?? true,
|
||||
},
|
||||
}
|
||||
);
|
||||
return this.csvService.generateCsv(rows);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -31,187 +31,222 @@ type Filters = {
|
|||
export class CsvExportService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
// async collectTransaction(
|
||||
// year: number,
|
||||
// period_no: number,
|
||||
// filters: Filters,
|
||||
// approved: boolean = true
|
||||
// ): Promise<CsvRow[]> {
|
||||
async collectTransaction(
|
||||
year: number,
|
||||
period_no: number,
|
||||
filters: Filters,
|
||||
approved: boolean = true
|
||||
): Promise<CsvRow[]> {
|
||||
//fetch period
|
||||
// const period = await this.prisma.payPeriods.findFirst({
|
||||
// where: { pay_year: year, pay_period_no: period_no },
|
||||
// select: { period_start: true, period_end: true },
|
||||
// });
|
||||
// if(!period) throw new NotFoundException(`Pay period ${ year }-${ period_no } not found`);
|
||||
const period = await this.prisma.payPeriods.findFirst({
|
||||
where: { pay_year: year, pay_period_no: period_no },
|
||||
select: { period_start: true, period_end: true },
|
||||
});
|
||||
if(!period) throw new NotFoundException(`Pay period ${ year }-${ period_no } not found`);
|
||||
|
||||
// const start = period.period_start;
|
||||
// const end = period.period_end;
|
||||
const start = period.period_start;
|
||||
const end = period.period_end;
|
||||
|
||||
// //fetch company codes from .env
|
||||
// const comapany_codes = this.resolveCompanyCodes(filters.companies);
|
||||
// if(comapany_codes.length === 0) throw new BadRequestException('No company selected');
|
||||
//fetch company codes from .env
|
||||
const company_codes = this.resolveCompanyCodes(filters.companies);
|
||||
if(company_codes.length === 0) throw new BadRequestException('No company selected');
|
||||
|
||||
// //Flag 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) {
|
||||
// throw new BadRequestException(' No export type selected ');
|
||||
// }
|
||||
|
||||
// const approved_filter = filters.approved? { is_approved: true } : {};
|
||||
|
||||
// //Prisma queries
|
||||
// const [shifts, expenses] = await Promise.all([
|
||||
// want_shifts || want_expense || want_holiday || want_vacation
|
||||
// ])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// const company_codes = companies.map(c => c === ExportCompany.TARGO ? 1 : 2);
|
||||
|
||||
// const period = await this.prisma.payPeriods.findFirst({
|
||||
// where: { pay_period_no: period_id },
|
||||
// });
|
||||
// if(!period) throw new NotFoundException(`Pay period ${period_id} not found`);
|
||||
|
||||
// const start_date = period.period_start;
|
||||
// const end_date = period.period_end;
|
||||
|
||||
// const included_shifts = await this.prisma.shifts.findMany({
|
||||
// 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.");
|
||||
//Flag 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) {
|
||||
throw new BadRequestException(' No export type selected ');
|
||||
}
|
||||
|
||||
const approved_filter = filters.approved? { is_approved: true } : {};
|
||||
|
||||
const {holiday_code, vacation_code} = this.resolveLeaveCodes();
|
||||
|
||||
//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 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));
|
||||
|
||||
for (const expense of expenses) {
|
||||
const employee = expense.timesheet.employee;
|
||||
const week = this.computeWeekNumber(start, expense.date);
|
||||
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: '',
|
||||
})
|
||||
}
|
||||
|
||||
//Final mapping and sorts
|
||||
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;
|
||||
});
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
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 {
|
||||
const header = [
|
||||
'company_code',
|
||||
|
|
@ -225,17 +260,22 @@ export class CsvExportService {
|
|||
'holiday_date',
|
||||
].join(',') + '\n';
|
||||
|
||||
const body = rows.map(r => [
|
||||
r.company_code,
|
||||
r.external_payroll_id,
|
||||
`${r.full_name.replace(/"/g, '""')}`,
|
||||
r.bank_code,
|
||||
r.quantity_hours?.toFixed(2) ?? '',
|
||||
r.week_number,
|
||||
r.pay_date,
|
||||
r.holiday_date ?? '',
|
||||
].join(',')).join('\n');
|
||||
|
||||
const body = rows.map(row => {
|
||||
const full_name = `${String(row.full_name).replace(/"/g, '""')}`;
|
||||
const quantity_hours = (typeof row.quantity_hours === 'number') ? row.quantity_hours.toFixed(2) : '';
|
||||
const amount = (typeof row.amount === 'number') ? row.amount.toFixed(2) : '';
|
||||
return [
|
||||
row.company_code,
|
||||
row.external_payroll_id,
|
||||
full_name,
|
||||
row.bank_code,
|
||||
quantity_hours,
|
||||
amount,
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
toUTC(date: Date) {
|
||||
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
||||
}
|
||||
|
||||
private formatDate(d:Date): string {
|
||||
return d.toISOString().split('T')[0];
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user