Merge branch 'main' of git.targo.ca:Targo/targo_backend
This commit is contained in:
commit
e3006b9b82
38
.env.development
Normal file
38
.env.development
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
DATABASE_URL="postgresql://apptargo:6wLAZrb0HZnd3mrmqXiArPcqLyui0o9e@10.100.0.116/app_targo_db_dev?schema=public"
|
||||
DATABASE_URL_LEGACY="postgresql://genieacs:DnZHC3XezD7A8keEtaUocqPw@10.100.0.116/targo?schema=public"
|
||||
|
||||
|
||||
AUTHENTIK_ISSUER="https://auth.targo.ca/application/o/montargo/"
|
||||
AUTHENTIK_CLIENT_ID="KUmSmvpu2aDDy4JfNwas7XriNFtPcj2Ka2PyLO5v"
|
||||
AUTHENTIK_CLIENT_SECRET="N55BgX1mxT7eiY99LOo5zXr5cKz9FgTsaCA9MdC7D8ZuhOGqozvqtNXVGbpY1eCg2kkYwJeJLP89sQ8R4cYybIJI7EwKijb19bzZQpUPwBosWwG3irUwdTnZOyw8yW5i"
|
||||
AUTHENTIK_CALLBACK_URL="http://localhost:3000/auth/callback"
|
||||
AUTHENTIK_AUTH_URL="https://auth.targo.ca/application/o/authorize/"
|
||||
AUTHENTIK_TOKEN_URL="https://auth.targo.ca/application/o/token/"
|
||||
AUTHENTIK_USERINFO_URL="https://auth.targo.ca/application/o/userinfo/"
|
||||
|
||||
REDIRECT_URL_STAGING="http://10.100.251.2:9013/#/login-success"
|
||||
REDIRECT_URL_DEV="http://localhost:9000/#/login-success"
|
||||
|
||||
TARGO_FRONTEND_URI="http://localhost:9000/"
|
||||
|
||||
ATTACHMENTS_SERVER_ID="server"
|
||||
ATTACHMENTS_ROOT=C:/
|
||||
|
||||
#ATTACHMENT_SERVER_SECRET="*"
|
||||
#ATTACHEMENT_SERVER_PASSWORD="enterpasswordhere"
|
||||
|
||||
#attachments storage variables, manage max amount of MB per upload and types
|
||||
MAX_UPLOAD_MB=25
|
||||
ALLOWED_MIME=image/jpeg,image/png,image/webp,application/pdf
|
||||
|
||||
#attachment archive variables:
|
||||
ARCHIVE_CRON=0 3 * * 1 #checkup every monday
|
||||
ARCHIVE_BATCH_SIZE=1000 #max batch size to avoid large locks
|
||||
|
||||
#attachment garbage collector variables:
|
||||
GC_CRON=15 4 * * * #everyday at 04h15
|
||||
GC_BTACH_SIZE= 500
|
||||
|
||||
#attachment variants variables, REDIS and BULL variables:
|
||||
REDIS_URL=redis://localhost:6379
|
||||
BULL_PREFIX=attachments
|
||||
37
.env.production
Normal file
37
.env.production
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
DATABASE_URL="postgresql://apptargo:6wLAZrb0HZnd3mrmqXiArPcqLyui0o9e@10.100.0.116/app_targo_db?schema=public"
|
||||
|
||||
AUTHENTIK_ISSUER="https://auth.targo.ca/application/o/montargo/"
|
||||
AUTHENTIK_CLIENT_ID="KUmSmvpu2aDDy4JfNwas7XriNFtPcj2Ka2PyLO5v"
|
||||
AUTHENTIK_CLIENT_SECRET="N55BgX1mxT7eiY99LOo5zXr5cKz9FgTsaCA9MdC7D8ZuhOGqozvqtNXVGbpY1eCg2kkYwJeJLP89sQ8R4cYybIJI7EwKijb19bzZQpUPwBosWwG3irUwdTnZOyw8yW5i"
|
||||
AUTHENTIK_CALLBACK_URL="http://localhost:3000/auth/callback"
|
||||
AUTHENTIK_AUTH_URL="https://auth.targo.ca/application/o/authorize/"
|
||||
AUTHENTIK_TOKEN_URL="https://auth.targo.ca/application/o/token/"
|
||||
AUTHENTIK_USERINFO_URL="https://auth.targo.ca/application/o/userinfo/"
|
||||
|
||||
REDIRECT_URL_STAGING="http://10.100.251.2:9013/#/login-success"
|
||||
REDIRECT_URL_DEV="http://localhost:9000/#/login-success"
|
||||
|
||||
TARGO_FRONTEND_URI="http://localhost:9000/"
|
||||
|
||||
ATTACHMENTS_SERVER_ID="server"
|
||||
ATTACHMENTS_ROOT=C:/
|
||||
|
||||
#ATTACHMENT_SERVER_SECRET="*"
|
||||
#ATTACHEMENT_SERVER_PASSWORD="enterpasswordhere"
|
||||
|
||||
#attachments storage variables, manage max amount of MB per upload and types
|
||||
MAX_UPLOAD_MB=25
|
||||
ALLOWED_MIME=image/jpeg,image/png,image/webp,application/pdf
|
||||
|
||||
#attachment archive variables:
|
||||
ARCHIVE_CRON=0 3 * * 1 #checkup every monday
|
||||
ARCHIVE_BATCH_SIZE=1000 #max batch size to avoid large locks
|
||||
|
||||
#attachment garbage collector variables:
|
||||
GC_CRON=15 4 * * * #everyday at 04h15
|
||||
GC_BTACH_SIZE= 500
|
||||
|
||||
#attachment variants variables, REDIS and BULL variables:
|
||||
REDIS_URL=redis://localhost:6379
|
||||
BULL_PREFIX=attachments
|
||||
|
||||
29
.env.staging
Normal file
29
.env.staging
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
DATABASE_URL="postgresql://apptargo:6wLAZrb0HZnd3mrmqXiArPcqLyui0o9e@10.100.0.116/app_targo_db_staging?schema=public"
|
||||
DATABASE_URL_LEGACY="postgresql://genieacs:DnZHC3XezD7A8keEtaUocqPw@10.100.0.116/targo?schema=public"
|
||||
|
||||
AUTHENTIK_ISSUER="https://auth.targo.ca/application/o/montargo/"
|
||||
AUTHENTIK_CLIENT_ID="KUmSmvpu2aDDy4JfNwas7XriNFtPcj2Ka2PyLO5v"
|
||||
AUTHENTIK_CLIENT_SECRET="N55BgX1mxT7eiY99LOo5zXr5cKz9FgTsaCA9MdC7D8ZuhOGqozvqtNXVGbpY1eCg2kkYwJeJLP89sQ8R4cYybIJI7EwKijb19bzZQpUPwBosWwG3irUwdTnZOyw8yW5i"
|
||||
AUTHENTIK_CALLBACK_URL="http://localhost:3000/auth/callback"
|
||||
AUTHENTIK_AUTH_URL="https://auth.targo.ca/application/o/authorize/"
|
||||
AUTHENTIK_TOKEN_URL="https://auth.targo.ca/application/o/token/"
|
||||
AUTHENTIK_USERINFO_URL="https://auth.targo.ca/application/o/userinfo/"
|
||||
|
||||
REDIRECT_URL_STAGING="http://10.100.251.2:9013/#/login-success"
|
||||
REDIRECT_URL_DEV="http://localhost:9000/#/login-success"
|
||||
|
||||
TARGO_FRONTEND_URI="http://localhost:9000/"
|
||||
|
||||
ATTACHMENTS_SERVER_ID="server"
|
||||
ATTACHMENTS_ROOT=C:/
|
||||
|
||||
MAX_UPLOAD_MB=25
|
||||
ALLOWED_MIME=image/jpeg,image/png,image/webp,application/pdf
|
||||
|
||||
#attachment garbage collector variables:
|
||||
GC_CRON=15 4 * * * #everyday at 04h15
|
||||
GC_BTACH_SIZE= 500
|
||||
|
||||
#attachment variants variables, REDIS and BULL variables:
|
||||
REDIS_URL=redis://localhost:6379
|
||||
BULL_PREFIX=attachments
|
||||
|
|
@ -53,7 +53,7 @@ async function bootstrap() {
|
|||
|
||||
// Enable CORS
|
||||
app.enableCors({
|
||||
origin: ['http://10.100.251.2:9011', 'http://10.100.251.2:9012', 'http://10.100.251.2:9013', 'http://localhost:9000'],
|
||||
origin: ['http://10.100.251.2:9011', 'http://10.100.251.2:9012', 'http://10.100.251.2:9013', 'http://localhost:9000', 'https://app.targo.ca', 'https://staging.app.targo.ca'],
|
||||
credentials: true,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma, PrismaClient } from '@prisma/client';
|
||||
import { getWeekStart, getWeekEnd, computeHours } from 'src/common/utils/date-utils';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
import { Controller, Get, Param, Query, Res } from "@nestjs/common";
|
||||
import { CsvExportService } from "./csv-exports.service";
|
||||
import { CsvExportService } from "./services/csv-exports.service";
|
||||
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
|
||||
import { Modules as ModulesEnum } from ".prisma/client";
|
||||
import { Response } from "express";
|
||||
import { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.service";
|
||||
|
||||
@Controller('exports')
|
||||
export class CsvExportController {
|
||||
constructor(private readonly csvService: CsvExportService) { }
|
||||
constructor(
|
||||
private readonly csvService: CsvExportService,
|
||||
private readonly generator: CsvGeneratorService,
|
||||
|
||||
) { }
|
||||
|
||||
@Get('csv/:year/:period_no')
|
||||
@ModuleAccessAllowed(ModulesEnum.employee_management)
|
||||
|
|
@ -39,7 +44,7 @@ export class CsvExportController {
|
|||
},
|
||||
}
|
||||
);
|
||||
const csv_buffer = await this.csvService.generateCsv(rows);
|
||||
const csv_buffer = await this.generator.generateCsv(rows);
|
||||
|
||||
response.set({
|
||||
'Content-Type': 'text/csv; charset=utf-8',
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { CsvExportController } from "./csv-exports.controller";
|
||||
import { CsvExportService } from "./csv-exports.service";
|
||||
import { CsvExportService } from "./services/csv-exports.service";
|
||||
import { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.service";
|
||||
import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service";
|
||||
|
||||
@Module({
|
||||
providers:[CsvExportService],
|
||||
providers: [CsvExportService, CsvGeneratorService, OvertimeService],
|
||||
controllers: [CsvExportController],
|
||||
})
|
||||
export class CsvExportModule {}
|
||||
export class CsvExportModule { }
|
||||
|
||||
|
||||
|
|
|
|||
104
src/time-and-attendance/exports/csv-exports.utils.ts
Normal file
104
src/time-and-attendance/exports/csv-exports.utils.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service";
|
||||
import { CsvRow, InternalCsvRow } from "src/time-and-attendance/exports/export-csv-options.dto";
|
||||
|
||||
const REGULAR = 'G1';
|
||||
const OVERTIME = 'G43';
|
||||
|
||||
export const consolidateRowHoursAndAmountByType = (rows: InternalCsvRow[]): InternalCsvRow[] => {
|
||||
const map = new Map<string, InternalCsvRow>();
|
||||
|
||||
for (const row of rows) {
|
||||
const key = `${row.timesheet_id}|${row.bank_code}|${row.week_number}|${row.shift_date.toISOString()}`;
|
||||
if (!map.has(key)) {
|
||||
map.set(key, { ...row });
|
||||
} else {
|
||||
const existing = map.get(key)!;
|
||||
existing.quantity_hours = (existing.quantity_hours ?? 0) + (row.quantity_hours ?? 0);
|
||||
existing.amount = (existing.amount ?? 0) + (row.amount ?? 0);
|
||||
}
|
||||
}
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
export const applyOvertimeRequalifications = async (
|
||||
consolidated_rows: InternalCsvRow[],
|
||||
overtime_service: OvertimeService,
|
||||
): Promise<CsvRow[]> => {
|
||||
const result: CsvRow[] = [];
|
||||
//grouped by timesheet and week number
|
||||
const grouped_rows = new Map<string, InternalCsvRow[]>();
|
||||
|
||||
for (const row of consolidated_rows) {
|
||||
const key = `${row.timesheet_id}|${row.week_number}`;
|
||||
if (!grouped_rows.has(key)) {
|
||||
grouped_rows.set(key, []);
|
||||
}
|
||||
grouped_rows.get(key)!.push({ ...row });
|
||||
}
|
||||
|
||||
for (const [, rows] of grouped_rows) {
|
||||
//serves only to get the right timesheet_id and a date to find the "week_start of the getWeekOvertimeSummary"
|
||||
const representative = rows[0];
|
||||
const summary = await overtime_service.getWeekOvertimeSummary(representative.timesheet_id, representative.shift_date);
|
||||
if (!summary.success || summary.data.total_overtime <= 0) {
|
||||
result.push(...rows);
|
||||
continue;
|
||||
}
|
||||
|
||||
const overtime_hours = summary.data.total_overtime;
|
||||
|
||||
const regular_hours = rows.find(r => r.bank_code === REGULAR);
|
||||
if (!regular_hours || !regular_hours.quantity_hours) {
|
||||
result.push(...rows);
|
||||
continue;
|
||||
}
|
||||
|
||||
const deducted = Math.min(overtime_hours, regular_hours.quantity_hours);
|
||||
|
||||
for (const row of rows) {
|
||||
if (row === regular_hours) {
|
||||
const remaining = regular_hours.quantity_hours - deducted;
|
||||
if (remaining > 0) {
|
||||
result.push({ ...regular_hours, quantity_hours: remaining });
|
||||
}
|
||||
} else {
|
||||
result.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
result.push({
|
||||
...regular_hours,
|
||||
bank_code: OVERTIME,
|
||||
quantity_hours: deducted,
|
||||
});
|
||||
}
|
||||
|
||||
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 computeWeekNumber = (start: Date, date: Date): number => {
|
||||
const dayMS = 86400000;
|
||||
const days = Math.floor((toUTC(date).getTime() - toUTC(start).getTime()) / dayMS);
|
||||
return Math.floor(days / 7) + 1;
|
||||
}
|
||||
|
||||
export const toUTC = (date: Date) => {
|
||||
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
||||
}
|
||||
|
||||
export const formatDate = (d: Date): string => {
|
||||
return d.toISOString().split('T')[0];
|
||||
}
|
||||
|
|
@ -58,6 +58,9 @@ export interface CsvRow {
|
|||
holiday_date?: string;
|
||||
}
|
||||
|
||||
export type InternalCsvRow = CsvRow & { timesheet_id: number; shift_date: Date; }
|
||||
|
||||
|
||||
export type Filters = {
|
||||
types: {
|
||||
shifts: boolean;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { CsvRow } from "src/time-and-attendance/exports/export-csv-options.dto";
|
||||
|
||||
@Injectable()
|
||||
export class CsvGeneratorService {
|
||||
//csv builder and "mise en page"
|
||||
generateCsv(rows: CsvRow[]): Buffer {
|
||||
const header = [
|
||||
'company_code',
|
||||
'external_payroll_id',
|
||||
'full_name',
|
||||
'bank_code',
|
||||
'quantity_hours',
|
||||
'amount',
|
||||
'week_number',
|
||||
'pay_date',
|
||||
'holiday_date',
|
||||
].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');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
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 { Filters, CsvRow, InternalCsvRow } from "src/time-and-attendance/exports/export-csv-options.dto";
|
||||
import { computeHours } from "src/common/utils/date-utils";
|
||||
|
||||
|
||||
import { applyOvertimeRequalifications, computeWeekNumber, consolidateRowHoursAndAmountByType, formatDate, resolveCompanyCodes } from "src/time-and-attendance/exports/csv-exports.utils";
|
||||
import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service";
|
||||
|
||||
@Injectable()
|
||||
export class CsvExportService {
|
||||
constructor(private readonly prisma: PrismaService) { }
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly overtime_service: OvertimeService,
|
||||
) { }
|
||||
|
||||
async collectTransaction(
|
||||
year: number,
|
||||
|
|
@ -26,7 +29,7 @@ export class CsvExportService {
|
|||
const end = period.period_end;
|
||||
|
||||
//fetch company codes
|
||||
const company_codes = this.resolveCompanyCodes(filters.companies);
|
||||
const company_codes = resolveCompanyCodes(filters.companies);
|
||||
if (company_codes.length === 0) throw new BadRequestException('No company selected');
|
||||
|
||||
//Flag types
|
||||
|
|
@ -59,6 +62,7 @@ export class CsvExportService {
|
|||
bank_code: { select: { bank_code: true } },
|
||||
timesheet: {
|
||||
select: {
|
||||
id: true,
|
||||
employee: {
|
||||
select: {
|
||||
company_code: true,
|
||||
|
|
@ -89,6 +93,7 @@ export class CsvExportService {
|
|||
bank_code: { select: { bank_code: true } },
|
||||
timesheet: {
|
||||
select: {
|
||||
id: true,
|
||||
employee: {
|
||||
select: {
|
||||
company_code: true,
|
||||
|
|
@ -119,6 +124,7 @@ export class CsvExportService {
|
|||
bank_code: { select: { bank_code: true } },
|
||||
timesheet: {
|
||||
select: {
|
||||
id: true,
|
||||
employee: {
|
||||
select: {
|
||||
company_code: true,
|
||||
|
|
@ -147,6 +153,7 @@ export class CsvExportService {
|
|||
bank_code: { select: { bank_code: true } },
|
||||
timesheet: {
|
||||
select: {
|
||||
id: true,
|
||||
employee: {
|
||||
select: {
|
||||
company_code: true,
|
||||
|
|
@ -165,22 +172,24 @@ export class CsvExportService {
|
|||
//array of arrays
|
||||
const [base_shifts, holiday_shifts, vacation_shifts, expenses] = await Promise.all(promises);
|
||||
//mapping
|
||||
const rows: CsvRow[] = [];
|
||||
const rows: InternalCsvRow[] = [];
|
||||
|
||||
const map_shifts = (shift: any, is_holiday: boolean) => {
|
||||
const map_shifts = (shift: any, is_holiday: boolean): InternalCsvRow => {
|
||||
const employee = shift.timesheet.employee;
|
||||
const week = this.computeWeekNumber(start, shift.date);
|
||||
const week = computeWeekNumber(start, shift.date);
|
||||
return {
|
||||
company_code: employee.company_code,
|
||||
external_payroll_id: employee.external_payroll_id,
|
||||
timesheet_id: shift.timesheet.id,
|
||||
shift_date: shift.date,
|
||||
full_name: `${employee.user.first_name} ${employee.user.last_name}`,
|
||||
bank_code: shift.bank_code?.bank_code ?? '',
|
||||
quantity_hours: 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;
|
||||
pay_date: formatDate(end),
|
||||
holiday_date: is_holiday ? formatDate(shift.date) : '',
|
||||
}
|
||||
};
|
||||
//final mapping of all shifts based filters
|
||||
for (const shift of base_shifts) rows.push(map_shifts(shift, false));
|
||||
|
|
@ -189,17 +198,19 @@ export class CsvExportService {
|
|||
|
||||
for (const expense of expenses) {
|
||||
const employee = expense.timesheet.employee;
|
||||
const week = this.computeWeekNumber(start, expense.date);
|
||||
const week = computeWeekNumber(start, expense.date);
|
||||
rows.push({
|
||||
company_code: employee.company_code,
|
||||
external_payroll_id: employee.external_payroll_id,
|
||||
timesheet_id: expense.timesheet.id,
|
||||
full_name: `${employee.user.first_name} ${employee.user.last_name}`,
|
||||
bank_code: expense.bank_code?.bank_code ?? '',
|
||||
quantity_hours: undefined,
|
||||
amount: Number(expense.amount),
|
||||
week_number: week,
|
||||
pay_date: this.formatDate(end),
|
||||
pay_date: formatDate(end),
|
||||
holiday_date: '',
|
||||
shift_date : expense.date,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -214,32 +225,11 @@ export class CsvExportService {
|
|||
return 0;
|
||||
});
|
||||
|
||||
const consolidated_rows = this.consolidateRowHoursAndAmountByType(rows)
|
||||
const consolidated_rows = consolidateRowHoursAndAmountByType(rows);
|
||||
|
||||
return consolidated_rows;
|
||||
}
|
||||
const requalified_rows = await applyOvertimeRequalifications(consolidated_rows, this.overtime_service);
|
||||
|
||||
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());
|
||||
return requalified_rows;
|
||||
}
|
||||
|
||||
resolveHolidayTypeCode = async (holiday: string): Promise<string> => {
|
||||
|
|
@ -247,6 +237,11 @@ export class CsvExportService {
|
|||
where: { type: holiday },
|
||||
select: {
|
||||
bank_code: true,
|
||||
shifts: {
|
||||
select: {
|
||||
date: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!holiday_code) throw new BadRequestException('Missing Holiday bank code');
|
||||
|
|
@ -259,69 +254,15 @@ export class CsvExportService {
|
|||
where: { type: vacation },
|
||||
select: {
|
||||
bank_code: true,
|
||||
shifts: {
|
||||
select: {
|
||||
date: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!vacation_code) throw new BadRequestException('Missing Vacation bank code');
|
||||
return vacation_code.bank_code;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//csv builder and "mise en page"
|
||||
generateCsv(rows: CsvRow[]): Buffer {
|
||||
const header = [
|
||||
'company_code',
|
||||
'external_payroll_id',
|
||||
'full_name',
|
||||
'bank_code',
|
||||
'quantity_hours',
|
||||
'amount',
|
||||
'week_number',
|
||||
'pay_date',
|
||||
'holiday_date',
|
||||
].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');
|
||||
}
|
||||
|
||||
private computeWeekNumber(start: Date, date: Date): number {
|
||||
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];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ import { PayPeriodsQueryService } from "src/time-and-attendance/pay-period/servi
|
|||
import { PayPeriodsCommandService } from "src/time-and-attendance/pay-period/services/pay-periods-command.service";
|
||||
|
||||
import { CsvExportModule } from "src/time-and-attendance/exports/csv-exports.module";
|
||||
import { CsvExportService } from "src/time-and-attendance/exports/csv-exports.service";
|
||||
import { CsvExportService } from "src/time-and-attendance/exports/services/csv-exports.service";
|
||||
import { CsvExportController } from "src/time-and-attendance/exports/csv-exports.controller";
|
||||
|
||||
import { ShiftController } from "src/time-and-attendance/shifts/shift.controller";
|
||||
|
|
@ -38,6 +38,7 @@ import { SchedulePresetDeleteService } from "src/time-and-attendance/schedule-pr
|
|||
import { SchedulePresetUpdateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-update.service";
|
||||
import { SchedulePresetsCreateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-create.service";
|
||||
import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service";
|
||||
import { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.service";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -78,6 +79,7 @@ import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-pr
|
|||
PayPeriodsQueryService,
|
||||
PayPeriodsCommandService,
|
||||
CsvExportService,
|
||||
CsvGeneratorService,
|
||||
],
|
||||
exports: [TimesheetApprovalService ],
|
||||
exports: [TimesheetApprovalService],
|
||||
}) export class TimeAndAttendanceModule { };
|
||||
Loading…
Reference in New Issue
Block a user