Merge branch 'main' of git.targo.ca:Targo/targo_backend
This commit is contained in:
commit
c47dcb1f2f
|
|
@ -1,9 +1,9 @@
|
|||
import { Controller, Get, Param, Query, Res } from "@nestjs/common";
|
||||
import { Body, Controller, Param, Post, StreamableFile } from "@nestjs/common";
|
||||
import { CsvExportService } from "./services/csv-exports.service";
|
||||
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
|
||||
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
|
||||
import { Response } from "express";
|
||||
import { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.service";
|
||||
import type { CsvFilters } from "src/time-and-attendance/exports/export-csv-options.dto";
|
||||
|
||||
@Controller('exports')
|
||||
export class CsvExportController {
|
||||
|
|
@ -13,44 +13,19 @@ export class CsvExportController {
|
|||
|
||||
) { }
|
||||
|
||||
@Get('csv/:year/:period_no')
|
||||
@Post('csv/:year/:period_no')
|
||||
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
||||
async exportCsv(
|
||||
@Query('approved') approved: string,
|
||||
@Query('shifts') shifts: string,
|
||||
@Query('expenses') expenses: string,
|
||||
@Query('holiday') holiday: string,
|
||||
@Query('vacation') vacation: string,
|
||||
@Query('targo') targo: string,
|
||||
@Query('solucom') solucom: string,
|
||||
@Param('year') year: number,
|
||||
@Param('period_no') period_no: number,
|
||||
@Res() response: Response,
|
||||
): Promise<void> {
|
||||
const rows = await this.csvService.collectTransaction(
|
||||
year,
|
||||
period_no,
|
||||
{
|
||||
approved: approved === 'true',
|
||||
types: {
|
||||
shifts: shifts === 'true',
|
||||
expenses: expenses === 'true',
|
||||
holiday: holiday === 'true',
|
||||
vacation: vacation === 'true',
|
||||
},
|
||||
companies: {
|
||||
targo: targo === 'true',
|
||||
solucom: solucom === 'true',
|
||||
},
|
||||
}
|
||||
);
|
||||
const csv_buffer = this.generator.generateCsv(rows);
|
||||
@Body() filters: CsvFilters,
|
||||
) {
|
||||
const rows = await this.csvService.collectTransaction(year, period_no, filters);
|
||||
const buffer = this.generator.generateCsv(rows);
|
||||
|
||||
response.set({
|
||||
'Content-Type': 'text/csv; charset=utf-8',
|
||||
'Content-Disposition': 'attachment; filename="export.csv"',
|
||||
return new StreamableFile(buffer, {
|
||||
type: 'text/csv',
|
||||
disposition: 'attachment; filename=export.csv'
|
||||
});
|
||||
response.send(csv_buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -134,17 +134,8 @@ export const applyOvertimeRequalifications = (
|
|||
return result;
|
||||
}
|
||||
|
||||
export const resolveCompanyCodes = (companies: { targo: boolean; solucom: boolean; }): number[] => {
|
||||
const out: number[] = [];
|
||||
if (companies.targo) {
|
||||
const code_no = 271583;
|
||||
out.push(code_no);
|
||||
}
|
||||
if (companies.solucom) {
|
||||
const code_no = 271585;
|
||||
out.push(code_no);
|
||||
}
|
||||
return out;
|
||||
export const resolveCompanyCode = (company: 'Targo' | 'Solucom'): number => {
|
||||
return company === 'Targo' ? 271583 : 271585;
|
||||
}
|
||||
|
||||
export const computeWeekNumber = (start: Date, date: Date): number => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Transform } from "class-transformer";
|
||||
import { IsBoolean, IsInt, IsOptional, Max, Min } from "class-validator";
|
||||
import { ShiftType } from "src/time-and-attendance/shifts/shift.dto";
|
||||
|
||||
const toBoolean = (v: any) => {
|
||||
if (typeof v === 'boolean') return v;
|
||||
|
|
@ -68,16 +69,8 @@ export interface CsvRow {
|
|||
export type InternalCsvRow = CsvRow & { timesheet_id: number; shift_date: Date; }
|
||||
|
||||
|
||||
export type Filters = {
|
||||
types: {
|
||||
shifts: boolean;
|
||||
expenses: boolean;
|
||||
holiday: boolean;
|
||||
vacation: boolean;
|
||||
};
|
||||
companies: {
|
||||
targo: boolean;
|
||||
solucom: boolean;
|
||||
};
|
||||
approved: boolean;
|
||||
export type CsvFilters = {
|
||||
companyName: 'Targo' | 'Solucom';
|
||||
includeExpenses: boolean;
|
||||
shiftTypes: ShiftType[];
|
||||
};
|
||||
|
|
@ -29,6 +29,6 @@ export class CsvGeneratorService {
|
|||
row.dernier_jour_absence ?? '',
|
||||
].join(';');
|
||||
}).join('\n');
|
||||
return Buffer.from('\uFEFF' + body, 'utf8');
|
||||
return Buffer.from(body);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { Filters, CsvRow, InternalCsvRow } from "src/time-and-attendance/exports/export-csv-options.dto";
|
||||
import { computeHours } from "src/common/utils/date-utils";
|
||||
import { applyHolidayRequalifications, applyOvertimeRequalifications, computeWeekNumber, consolidateRowHoursAndAmountByType, formatDate, resolveCompanyCodes } from "src/time-and-attendance/exports/csv-exports.utils";
|
||||
import { applyHolidayRequalifications, applyOvertimeRequalifications, computeWeekNumber, consolidateRowHoursAndAmountByType, formatDate, resolveCompanyCode } from "src/time-and-attendance/exports/csv-exports.utils";
|
||||
import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service";
|
||||
import { select_csv_expense_lines, select_csv_shift_lines } from "src/time-and-attendance/utils/selects.utils";
|
||||
import { BillableShiftType } from "src/time-and-attendance/shifts/shift.dto";
|
||||
import type { CsvRow, InternalCsvRow, CsvFilters } from "src/time-and-attendance/exports/export-csv-options.dto";
|
||||
import type { ShiftType } from "src/time-and-attendance/shifts/shift.dto";
|
||||
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -35,39 +35,35 @@ export class CsvExportService {
|
|||
async collectTransaction(
|
||||
year: number,
|
||||
period_no: number,
|
||||
filters: Filters
|
||||
filters: CsvFilters
|
||||
): Promise<CsvRow[]> {
|
||||
const BILLABLE_SHIFT_TYPES: BillableShiftType[] = [];
|
||||
const PTO_SHIFT_CODES = ['G104', 'G105', 'G109']; // [ HOLIDAY, SICK, VACATION ]
|
||||
const HOLIDAY_SHIFT_CODE = 'G104';
|
||||
|
||||
if (filters.types.shifts) BILLABLE_SHIFT_TYPES.push('REGULAR', 'OVERTIME', 'EMERGENCY', 'EVENING', 'SICK');
|
||||
if (filters.types.holiday) BILLABLE_SHIFT_TYPES.push('HOLIDAY');
|
||||
if (filters.types.vacation) BILLABLE_SHIFT_TYPES.push('VACATION');
|
||||
const requestedShiftCodes = await this.resolveShiftTypeCode(filters.shiftTypes);
|
||||
|
||||
const BILLABLE_SHIFT_CODES = await this.resolveShiftTypeCode(BILLABLE_SHIFT_TYPES);
|
||||
const PTO_SHIFT_CODES = await this.resolveShiftTypeCode(['VACATION', 'SICK', 'HOLIDAY']);
|
||||
const HOLIDAY_SHIFT_CODE = await this.resolveShiftTypeCode(['HOLIDAY']);
|
||||
if (requestedShiftCodes.length < 1)
|
||||
throw new BadRequestException('NO_TYPE_SELECTED');
|
||||
|
||||
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`);
|
||||
|
||||
if (!period)
|
||||
throw new NotFoundException(`Pay period ${year}-${period_no} not found`);
|
||||
|
||||
const start = period.period_start;
|
||||
const end = period.period_end;
|
||||
|
||||
const company_codes = resolveCompanyCodes(filters.companies);
|
||||
if (company_codes.length === 0) throw new BadRequestException('NO_COMPANY_SELECTED');
|
||||
|
||||
if (Object.values(filters.types).every(type => type === false))
|
||||
throw new BadRequestException('NO_TYPE_SELECTED');
|
||||
const companyCode = resolveCompanyCode(filters.companyName);
|
||||
|
||||
const exportedShifts = await this.prisma.shifts.findMany({
|
||||
where: {
|
||||
date: { gte: start, lte: end },
|
||||
is_approved: true,
|
||||
timesheet: { employee: { company_code: { in: company_codes } } },
|
||||
bank_code: { bank_code: { in: BILLABLE_SHIFT_CODES } },
|
||||
timesheet: { employee: { company_code: companyCode } },
|
||||
bank_code: { bank_code: { in: requestedShiftCodes } },
|
||||
},
|
||||
select: select_csv_shift_lines,
|
||||
});
|
||||
|
|
@ -101,12 +97,12 @@ export class CsvExportService {
|
|||
}
|
||||
});
|
||||
|
||||
if (filters.types.expenses) {
|
||||
if (filters.includeExpenses) {
|
||||
const exportedExpenses = await this.prisma.expenses.findMany({
|
||||
where: {
|
||||
date: { gte: start, lte: end },
|
||||
is_approved: true,
|
||||
timesheet: { employee: { company_code: { in: company_codes } } },
|
||||
timesheet: { employee: { company_code: companyCode } },
|
||||
},
|
||||
select: select_csv_expense_lines,
|
||||
});
|
||||
|
|
@ -149,7 +145,7 @@ export class CsvExportService {
|
|||
a.employee_matricule - b.employee_matricule
|
||||
);
|
||||
|
||||
const holiday_rows = await applyHolidayRequalifications(rows, this.holiday_service, HOLIDAY_SHIFT_CODE[0]);
|
||||
const holiday_rows = await applyHolidayRequalifications(rows, this.holiday_service, HOLIDAY_SHIFT_CODE);
|
||||
const consolidated_rows = consolidateRowHoursAndAmountByType(holiday_rows);
|
||||
//requalifies regular hours into overtime when needed
|
||||
const requalified_rows = applyOvertimeRequalifications(consolidated_rows);
|
||||
|
|
@ -157,7 +153,7 @@ export class CsvExportService {
|
|||
return requalified_rows;
|
||||
}
|
||||
|
||||
resolveShiftTypeCode = async (shift_type: BillableShiftType[]): Promise<string[]> => {
|
||||
resolveShiftTypeCode = async (shift_type: ShiftType[]): Promise<string[]> => {
|
||||
const billableBankCodes = await this.prisma.bankCodes.findMany({
|
||||
where: { type: { in: shift_type } },
|
||||
select: {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export class PaidTimeOffController {
|
|||
) { }
|
||||
|
||||
@Get('totals')
|
||||
@ModuleAccessAllowed('timesheets', 'timesheets_approval', 'employee_management')
|
||||
@ModuleAccessAllowed('timesheets')
|
||||
async getPaidTimeOffTotalsForOneEmployee(
|
||||
@Access('email') email: string,
|
||||
@Query('email') employee_email?: string,
|
||||
|
|
|
|||
|
|
@ -146,13 +146,7 @@ export class GetOverviewService {
|
|||
|
||||
switch (type) {
|
||||
case "EVENING":
|
||||
if (total_weekly_hours + hours <= 40) {
|
||||
record.other_hours.evening_hours += Math.min(hours, 8 - daily_hours);
|
||||
record.other_hours.overtime_hours += Math.max(daily_hours + hours - 8, 0);
|
||||
} else {
|
||||
record.other_hours.evening_hours += Math.max(40 - total_weekly_hours, 0);
|
||||
record.other_hours.overtime_hours += Math.min(total_weekly_hours + hours - 40, hours);
|
||||
}
|
||||
record.other_hours.evening_hours += Math.min(hours, 8 - daily_hours);
|
||||
total_weekly_hours += hours;
|
||||
record.total_hours += hours;
|
||||
break;
|
||||
|
|
@ -169,12 +163,16 @@ export class GetOverviewService {
|
|||
record.total_hours += hours;
|
||||
total_weekly_hours += hours;
|
||||
break;
|
||||
case "VACATION": record.other_hours.vacation_hours += hours;
|
||||
case "VACATION":
|
||||
record.other_hours.vacation_hours += hours;
|
||||
total_weekly_hours += hours;
|
||||
break;
|
||||
case "REGULAR":
|
||||
if (total_weekly_hours + hours <= 40) {
|
||||
record.regular_hours += Math.min(hours, 8 - daily_hours);
|
||||
record.other_hours.overtime_hours += Math.max(daily_hours + hours - 8, 0);
|
||||
record.regular_hours += hours;
|
||||
// TODO: ADD DAILY OVERTIME CHECK HERE
|
||||
// record.regular_hours += Math.min(hours, 8 - daily_hours);
|
||||
// record.other_hours.overtime_hours += Math.max(daily_hours + hours - 8, 0);
|
||||
} else {
|
||||
record.regular_hours += Math.max(40 - total_weekly_hours, 0);
|
||||
record.other_hours.overtime_hours += Math.min(total_weekly_hours + hours - 40, hours);
|
||||
|
|
|
|||
|
|
@ -12,4 +12,4 @@ export class ShiftDto {
|
|||
@IsOptional() @IsString() @MaxLength(280) comment?: string;
|
||||
}
|
||||
|
||||
export type BillableShiftType = 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'VACATION' | 'HOLIDAY' | 'SICK' | 'OVERTIME';
|
||||
export type ShiftType = 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'VACATION' | 'HOLIDAY' | 'SICK';
|
||||
|
|
@ -82,6 +82,7 @@ export const mapOneTimesheet = (
|
|||
weekly_hours[subgroup] += hours;
|
||||
}
|
||||
|
||||
// TODO: ADD DAILY OVERTIME CHECK HERE
|
||||
// const dailyOvertimeOwed = Math.max(daily_hours.regular - timesheet.employee.daily_expected_hours, 0)
|
||||
// daily_hours.overtime = dailyOvertimeOwed;
|
||||
// daily_hours.regular -= dailyOvertimeOwed;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user