Merge branch 'main' of git.targo.ca:Targo/targo_backend

This commit is contained in:
Nicolas Drolet 2026-01-06 07:34:42 -05:00
commit e3006b9b82
12 changed files with 308 additions and 109 deletions

38
.env.development Normal file
View 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
View 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
View 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

View File

@ -53,7 +53,7 @@ async function bootstrap() {
// Enable CORS // Enable CORS
app.enableCors({ 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, credentials: true,
}); });

View File

@ -1,4 +1,4 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client'; import { Prisma, PrismaClient } from '@prisma/client';
import { getWeekStart, getWeekEnd, computeHours } from 'src/common/utils/date-utils'; import { getWeekStart, getWeekEnd, computeHours } from 'src/common/utils/date-utils';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';

View File

@ -1,12 +1,17 @@
import { Controller, Get, Param, Query, Res } from "@nestjs/common"; 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 { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from ".prisma/client"; import { Modules as ModulesEnum } from ".prisma/client";
import { Response } from "express"; import { Response } from "express";
import { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.service";
@Controller('exports') @Controller('exports')
export class CsvExportController { export class CsvExportController {
constructor(private readonly csvService: CsvExportService) { } constructor(
private readonly csvService: CsvExportService,
private readonly generator: CsvGeneratorService,
) { }
@Get('csv/:year/:period_no') @Get('csv/:year/:period_no')
@ModuleAccessAllowed(ModulesEnum.employee_management) @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({ response.set({
'Content-Type': 'text/csv; charset=utf-8', 'Content-Type': 'text/csv; charset=utf-8',

View File

@ -1,11 +1,13 @@
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { CsvExportController } from "./csv-exports.controller"; 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({ @Module({
providers:[CsvExportService], providers: [CsvExportService, CsvGeneratorService, OvertimeService],
controllers: [CsvExportController], controllers: [CsvExportController],
}) })
export class CsvExportModule {} export class CsvExportModule { }

View 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];
}

View File

@ -58,6 +58,9 @@ export interface CsvRow {
holiday_date?: string; holiday_date?: string;
} }
export type InternalCsvRow = CsvRow & { timesheet_id: number; shift_date: Date; }
export type Filters = { export type Filters = {
types: { types: {
shifts: boolean; shifts: boolean;

View File

@ -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');
}
}

View File

@ -1,13 +1,16 @@
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; 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 { 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() @Injectable()
export class CsvExportService { export class CsvExportService {
constructor(private readonly prisma: PrismaService) { } constructor(
private readonly prisma: PrismaService,
private readonly overtime_service: OvertimeService,
) { }
async collectTransaction( async collectTransaction(
year: number, year: number,
@ -26,7 +29,7 @@ export class CsvExportService {
const end = period.period_end; const end = period.period_end;
//fetch company codes //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'); if (company_codes.length === 0) throw new BadRequestException('No company selected');
//Flag types //Flag types
@ -59,6 +62,7 @@ export class CsvExportService {
bank_code: { select: { bank_code: true } }, bank_code: { select: { bank_code: true } },
timesheet: { timesheet: {
select: { select: {
id: true,
employee: { employee: {
select: { select: {
company_code: true, company_code: true,
@ -89,6 +93,7 @@ export class CsvExportService {
bank_code: { select: { bank_code: true } }, bank_code: { select: { bank_code: true } },
timesheet: { timesheet: {
select: { select: {
id: true,
employee: { employee: {
select: { select: {
company_code: true, company_code: true,
@ -119,6 +124,7 @@ export class CsvExportService {
bank_code: { select: { bank_code: true } }, bank_code: { select: { bank_code: true } },
timesheet: { timesheet: {
select: { select: {
id: true,
employee: { employee: {
select: { select: {
company_code: true, company_code: true,
@ -147,6 +153,7 @@ export class CsvExportService {
bank_code: { select: { bank_code: true } }, bank_code: { select: { bank_code: true } },
timesheet: { timesheet: {
select: { select: {
id: true,
employee: { employee: {
select: { select: {
company_code: true, company_code: true,
@ -165,22 +172,24 @@ export class CsvExportService {
//array of arrays //array of arrays
const [base_shifts, holiday_shifts, vacation_shifts, expenses] = await Promise.all(promises); const [base_shifts, holiday_shifts, vacation_shifts, expenses] = await Promise.all(promises);
//mapping //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 employee = shift.timesheet.employee;
const week = this.computeWeekNumber(start, shift.date); const week = computeWeekNumber(start, shift.date);
return { return {
company_code: employee.company_code, company_code: employee.company_code,
external_payroll_id: employee.external_payroll_id, 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}`, full_name: `${employee.user.first_name} ${employee.user.last_name}`,
bank_code: shift.bank_code?.bank_code ?? '', bank_code: shift.bank_code?.bank_code ?? '',
quantity_hours: computeHours(shift.start_time, shift.end_time), quantity_hours: computeHours(shift.start_time, shift.end_time),
amount: undefined, amount: undefined,
week_number: week, week_number: week,
pay_date: this.formatDate(end), pay_date: formatDate(end),
holiday_date: is_holiday ? this.formatDate(shift.date) : '', holiday_date: is_holiday ? formatDate(shift.date) : '',
} as CsvRow; }
}; };
//final mapping of all shifts based filters //final mapping of all shifts based filters
for (const shift of base_shifts) rows.push(map_shifts(shift, false)); for (const shift of base_shifts) rows.push(map_shifts(shift, false));
@ -189,17 +198,19 @@ export class CsvExportService {
for (const expense of expenses) { for (const expense of expenses) {
const employee = expense.timesheet.employee; const employee = expense.timesheet.employee;
const week = this.computeWeekNumber(start, expense.date); const week = computeWeekNumber(start, expense.date);
rows.push({ rows.push({
company_code: employee.company_code, company_code: employee.company_code,
external_payroll_id: employee.external_payroll_id, external_payroll_id: employee.external_payroll_id,
timesheet_id: expense.timesheet.id,
full_name: `${employee.user.first_name} ${employee.user.last_name}`, full_name: `${employee.user.first_name} ${employee.user.last_name}`,
bank_code: expense.bank_code?.bank_code ?? '', bank_code: expense.bank_code?.bank_code ?? '',
quantity_hours: undefined, quantity_hours: undefined,
amount: Number(expense.amount), amount: Number(expense.amount),
week_number: week, week_number: week,
pay_date: this.formatDate(end), pay_date: formatDate(end),
holiday_date: '', holiday_date: '',
shift_date : expense.date,
}) })
} }
@ -214,32 +225,11 @@ export class CsvExportService {
return 0; 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[] => { return requalified_rows;
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> => { resolveHolidayTypeCode = async (holiday: string): Promise<string> => {
@ -247,6 +237,11 @@ export class CsvExportService {
where: { type: holiday }, where: { type: holiday },
select: { select: {
bank_code: true, bank_code: true,
shifts: {
select: {
date: true,
},
},
}, },
}); });
if (!holiday_code) throw new BadRequestException('Missing Holiday bank code'); if (!holiday_code) throw new BadRequestException('Missing Holiday bank code');
@ -259,69 +254,15 @@ export class CsvExportService {
where: { type: vacation }, where: { type: vacation },
select: { select: {
bank_code: true, bank_code: true,
shifts: {
select: {
date: true,
},
},
}, },
}); });
if (!vacation_code) throw new BadRequestException('Missing Vacation bank code'); if (!vacation_code) throw new BadRequestException('Missing Vacation bank code');
return vacation_code.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];
}
} }

View File

@ -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 { 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 { 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 { CsvExportController } from "src/time-and-attendance/exports/csv-exports.controller";
import { ShiftController } from "src/time-and-attendance/shifts/shift.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 { 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 { 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 { 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({ @Module({
imports: [ imports: [
@ -46,8 +47,8 @@ import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-pr
TimesheetsModule, TimesheetsModule,
ExpensesModule, ExpensesModule,
PayperiodsModule, PayperiodsModule,
CsvExportModule, CsvExportModule,
SchedulePresetsModule, SchedulePresetsModule,
], ],
controllers: [ controllers: [
TimesheetController, TimesheetController,
@ -56,7 +57,7 @@ import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-pr
ExpenseController, ExpenseController,
PayPeriodsController, PayPeriodsController,
CsvExportController, CsvExportController,
], ],
providers: [ providers: [
GetTimesheetsOverviewService, GetTimesheetsOverviewService,
@ -78,6 +79,7 @@ import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-pr
PayPeriodsQueryService, PayPeriodsQueryService,
PayPeriodsCommandService, PayPeriodsCommandService,
CsvExportService, CsvExportService,
CsvGeneratorService,
], ],
exports: [TimesheetApprovalService ], exports: [TimesheetApprovalService],
}) export class TimeAndAttendanceModule { }; }) export class TimeAndAttendanceModule { };