reafactor(EsLint): Eslint corrections

This commit is contained in:
Matthieu Haineault 2026-02-27 13:19:38 -05:00
parent 368c5b1a2a
commit c427cc7718
37 changed files with 286 additions and 217 deletions

View File

@ -26,6 +26,7 @@ export default tseslint.config(
}, },
{ {
rules: { rules: {
"no-unused-vars": "off",
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn', '@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn' '@typescript-eslint/no-unsafe-argument': 'warn'

View File

@ -28,7 +28,9 @@ import { CustomerSupportModule } from 'src/customer-support/customer-support.mod
ChatbotModule, ChatbotModule,
CustomerSupportModule, CustomerSupportModule,
], ],
controllers: [AppController], controllers: [
AppController
],
providers: [ providers: [
AppService, AppService,
{ {

View File

@ -3,8 +3,11 @@ import { TicketController } from "src/customer-support/tickets/ticket.controller
import { TicketService } from "src/customer-support/tickets/ticket.service"; import { TicketService } from "src/customer-support/tickets/ticket.service";
@Module({ @Module({
imports: [], controllers: [
controllers: [TicketController], TicketController
providers: [TicketService], ],
exports: [], providers: [
TicketService
],
}) export class CustomerSupportModule { } }) export class CustomerSupportModule { }

View File

@ -3,7 +3,11 @@ import { TicketController } from "src/customer-support/tickets/ticket.controller
import { TicketService } from "src/customer-support/tickets/ticket.service"; import { TicketService } from "src/customer-support/tickets/ticket.service";
@Module({ @Module({
imports: [TicketService], imports: [
providers: [TicketController] TicketService
],
providers: [
TicketController
]
}) })
export class TicketModule { } export class TicketModule { }

View File

@ -1,8 +1,3 @@
import 'reflect-metadata';
// import * as nodeCrypto from 'crypto';
// if (!(globalThis as any).crypto) {
// (globalThis as any).crypto = nodeCrypto;
// }
import { NestFactory, Reflector } from '@nestjs/core'; import { NestFactory, Reflector } from '@nestjs/core';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { ModulesGuard } from './common/guards/modules.guard'; import { ModulesGuard } from './common/guards/modules.guard';
@ -21,7 +16,7 @@ async function bootstrap() {
const reflector = app.get(Reflector); const reflector = app.get(Reflector);
app.useGlobalGuards( app.useGlobalGuards(
new ModulesGuard(reflector), //deny-by-default and Module-based Access Control new ModulesGuard(reflector),
); );
// Authentication and session // Authentication and session
@ -46,19 +41,11 @@ async function bootstrap() {
// Enable CORS // Enable CORS
app.enableCors({ app.enableCors({
origin: ['http://10.100.251.2:9011', 'http://10.5.14.111:9012', 'http://10.100.251.2:9013', 'http://localhost:9000', 'https://app.targo.ca', 'https://portail.targo.ca','https://staging.app.targo.ca'], origin: ['http://10.100.251.2:9011', 'http://10.5.14.111:9012', 'http://10.100.251.2:9013', 'http://localhost:9000', 'https://app.targo.ca', 'https://portail.targo.ca', 'https://staging.app.targo.ca'],
credentials: true, credentials: true,
}); });
await app.listen(process.env.PORT ?? 3000); await app.listen(process.env.PORT ?? 3000);
// migration function calls
// await initializePaidTimeOff();
// await initializePreferences();
// await extractOldTimesheets();
// await extractOldShifts();
// await extractOldExpenses();
// await initSupervisor();
} }
bootstrap(); bootstrap();

View File

@ -0,0 +1,3 @@
export class BankCodeDto {
type:string;
}

View File

@ -6,7 +6,8 @@ import { applyHolidayRequalifications, applyOvertimeRequalifications, computeWee
import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service"; import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service";
import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service"; 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 { 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.types"; import { BillableShiftType } from "src/time-and-attendance/shifts/shift.dto";
@Injectable() @Injectable()
export class CsvExportService { export class CsvExportService {

View File

@ -8,7 +8,9 @@ import { PaidTimeOffController } from "src/time-and-attendance/paid-time-off/pai
import { PaidTimeOffBankHoursService } from "src/time-and-attendance/paid-time-off/paid-time-off.service"; import { PaidTimeOffBankHoursService } from "src/time-and-attendance/paid-time-off/paid-time-off.service";
@Module({ @Module({
controllers: [PaidTimeOffController], controllers: [
PaidTimeOffController
],
providers: [ providers: [
PrismaPostgresService, PrismaPostgresService,
EmailToIdResolver, EmailToIdResolver,

View File

@ -18,7 +18,9 @@ export class PaidTimeOffBankHoursService {
private readonly emailResolver: EmailToIdResolver, private readonly emailResolver: EmailToIdResolver,
) { } ) { }
getPaidTimeOffTotalsWithEmployeeEmail = async (email: string): Promise<Result<Partial<PaidTimeOffDto>, string>> => { getPaidTimeOffTotalsWithEmployeeEmail = async (
email: string
): Promise<Result<Partial<PaidTimeOffDto>, string>> => {
const employee_info = await this.emailResolver.findIdByEmail(email); const employee_info = await this.emailResolver.findIdByEmail(email);
@ -112,6 +114,7 @@ export class PaidTimeOffBankHoursService {
}); });
return { success: true, data: true }; return { success: true, data: true };
} catch (error) { } catch (error) {
console.error(error);
return { success: false, error: 'PAID_TIME_OFF_NOT_FOUND' }; return { success: false, error: 'PAID_TIME_OFF_NOT_FOUND' };
} }
}; };

View File

@ -2,25 +2,13 @@ import { Type } from "class-transformer";
import { IsArray, IsBoolean, IsEmail, IsInt, ValidateNested } from "class-validator"; import { IsArray, IsBoolean, IsEmail, IsInt, ValidateNested } from "class-validator";
export class BulkCrewApprovalItemDto { export class BulkCrewApprovalItemDto {
@IsInt() @IsInt() pay_year: number;
pay_year: number; @IsInt() period_no: number;
@IsEmail() employee_email: string;
@IsInt() @IsBoolean() approve: boolean;
period_no: number;
@IsEmail()
employee_email!: string;
@IsBoolean()
approve: boolean;
} }
export class BulkCrewApprovalDto { export class BulkCrewApprovalDto {
@IsBoolean() @IsBoolean() include_subtree: boolean = false;
include_subtree: boolean = false; @IsArray() @ValidateNested({ each: true }) @Type(() => BulkCrewApprovalItemDto) items: BulkCrewApprovalItemDto[]
@IsArray()
@ValidateNested({each: true})
@Type(()=> BulkCrewApprovalItemDto)
items: BulkCrewApprovalItemDto[]
} }

View File

@ -26,7 +26,9 @@ export class PayPeriodsController {
@Get("date/:date") @Get("date/:date")
@ModuleAccessAllowed(ModulesEnum.timesheets) @ModuleAccessAllowed(ModulesEnum.timesheets)
async findByDate(@Param("date") date: string) { async findByDate(
@Param("date") date: string
) {
return this.queryService.findByDate(date); return this.queryService.findByDate(date);
} }

View File

@ -1,9 +1,13 @@
import { PayPeriods } from "prisma/postgres/generated/prisma/client/postgres/client"; import { PayPeriods } from "prisma/postgres/generated/prisma/client/postgres/client";
import { PayPeriodDto } from "src/time-and-attendance/pay-period/dtos/overview-pay-period.dto"; import { PayPeriodDto } from "src/time-and-attendance/pay-period/dtos/overview-pay-period.dto";
const toDateString = (date: Date) => date.toISOString().slice(0, 10); // "YYYY-MM-DD" const toDateString = (
date: Date
) => date.toISOString().slice(0, 10); // "YYYY-MM-DD"
export function mapPayPeriodToDto(row: PayPeriods): PayPeriodDto { export function mapPayPeriodToDto(
row: PayPeriods
): PayPeriodDto {
const start = toDateString(row.period_start); const start = toDateString(row.period_start);
const end = toDateString(row.period_end); const end = toDateString(row.period_end);
const pay = toDateString(row.payday); const pay = toDateString(row.payday);
@ -12,12 +16,13 @@ export function mapPayPeriodToDto(row: PayPeriods): PayPeriodDto {
period_start: toDateString(row.period_start), period_start: toDateString(row.period_start),
period_end: toDateString(row.period_end), period_end: toDateString(row.period_end),
payday:pay, payday:pay,
// pay_year: new Date(pay).getFullYear(),
pay_year: row.pay_year, pay_year: row.pay_year,
label: `${start}.${end}`, label: `${start}.${end}`,
}; };
} }
export function mapMany(rows: PayPeriods[]): PayPeriodDto[] { export function mapMany(
rows: PayPeriods[]
): PayPeriodDto[] {
return rows.map(mapPayPeriodToDto); return rows.map(mapPayPeriodToDto);
} }

View File

@ -8,8 +8,12 @@ import { GetOverviewService } from "src/time-and-attendance/pay-period/services/
import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service"; import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service";
@Module({ @Module({
imports:[TimesheetsModule], imports: [
controllers: [PayPeriodsController], TimesheetsModule
],
controllers: [
PayPeriodsController
],
providers: [ providers: [
PayPeriodsQueryService, PayPeriodsQueryService,
PayPeriodsCommandService, PayPeriodsCommandService,
@ -17,6 +21,4 @@ import { PayPeriodEventService } from "src/time-and-attendance/pay-period/servic
PayPeriodEventService, PayPeriodEventService,
EmailToIdResolver, EmailToIdResolver,
], ],
}) }) export class PayperiodsModule { }
export class PayperiodsModule {}

View File

@ -11,7 +11,10 @@ export class GetOverviewService {
private readonly prisma: PrismaPostgresService, private readonly prisma: PrismaPostgresService,
) { } ) { }
async getOverviewByYearPeriod(pay_year: number, period_no: number): Promise<Result<PayPeriodOverviewDto, string>> { async getOverviewByYearPeriod(
pay_year: number,
period_no: number
): Promise<Result<PayPeriodOverviewDto, string>> {
const period = computePeriod(pay_year, period_no); const period = computePeriod(pay_year, period_no);
const overview = await this.buildOverview({ const overview = await this.buildOverview({
period_start: period.period_start, period_start: period.period_start,
@ -26,7 +29,9 @@ export class GetOverviewService {
return { success: true, data: overview.data } return { success: true, data: overview.data }
} }
async buildOverview(overview: Overview): Promise<Result<PayPeriodOverviewDto, string>> { async buildOverview(
overview: Overview
): Promise<Result<PayPeriodOverviewDto, string>> {
const employee_overviews = await this.prisma.employees.findMany({ const employee_overviews = await this.prisma.employees.findMany({
where: { where: {
OR: [ OR: [
@ -103,7 +108,12 @@ export class GetOverviewService {
} }
} }
const ensure = (id: number, first_name: string, last_name: string, email: string) => { const ensure = (
id: number,
first_name: string,
last_name: string,
email: string
) => {
if (!by_employee.has(id)) { if (!by_employee.has(id)) {
by_employee.set(id, this.createEmployeeSeeds(email, first_name, last_name)); by_employee.set(id, this.createEmployeeSeeds(email, first_name, last_name));
} }

View File

@ -14,7 +14,11 @@ export class PayPeriodsCommandService {
) { } ) { }
//function to approve pay-periods according to selected crew members //function to approve pay-periods according to selected crew members
async bulkApproveEmployee(email: string, timesheet_ids: number[], is_approved: boolean): Promise<Result<{ shifts: number, expenses: number }, string>> { async bulkApproveEmployee(
email: string,
timesheet_ids: number[],
is_approved: boolean
): Promise<Result<{ shifts: number, expenses: number }, string>> {
let shifts: Prisma.BatchPayload; let shifts: Prisma.BatchPayload;
let expenses: Prisma.BatchPayload; let expenses: Prisma.BatchPayload;
@ -56,7 +60,8 @@ export class PayPeriodsCommandService {
is_approved: is_approved, is_approved: is_approved,
} }
}) })
} catch (_error) { } catch (error) {
console.error(error);
return { success: false, error: 'UNKNOWN_ERROR_VALIDATING' } return { success: false, error: 'UNKNOWN_ERROR_VALIDATING' }
} }

View File

@ -10,7 +10,10 @@ export class PayPeriodsQueryService {
constructor( constructor(
private readonly prisma: PrismaPostgresService) { } private readonly prisma: PrismaPostgresService) { }
async findOneByYearPeriod(pay_year: number, period_no: number): Promise<Result<PayPeriodDto, string>> { async findOneByYearPeriod(
pay_year: number,
period_no: number
): Promise<Result<PayPeriodDto, string>> {
const row = await this.prisma.payPeriods.findFirst({ const row = await this.prisma.payPeriods.findFirst({
where: { pay_year, pay_period_no: period_no }, where: { pay_year, pay_period_no: period_no },
}); });
@ -32,7 +35,9 @@ export class PayPeriodsQueryService {
} }
//function to cherry pick a Date to find a period //function to cherry pick a Date to find a period
async findByDate(date: string): Promise<Result<PayPeriodDto, string>> { async findByDate(
date: string
): Promise<Result<PayPeriodDto, string>> {
const dt = new Date(date); const dt = new Date(date);
const row = await this.prisma.payPeriods.findFirst({ const row = await this.prisma.payPeriods.findFirst({
where: { period_start: { lte: dt }, period_end: { gte: dt } }, where: { period_start: { lte: dt }, period_end: { gte: dt } },

View File

@ -1,11 +1,9 @@
import { Controller, Param, Body, Get, Post, Delete, Patch } from "@nestjs/common"; import { Controller, Param, Body, Get, Post, Delete, Patch } from "@nestjs/common";
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 { 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 { SchedulePresetDeleteService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-delete.service"; import { SchedulePresetDeleteService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-delete.service";
import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service"; import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/schedule-presets.dto"; import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/schedule-presets.dto";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators"; import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client"; import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
import { Access } from "src/common/decorators/module-access.decorators"; import { Access } from "src/common/decorators/module-access.decorators";
@ -29,21 +27,25 @@ export class SchedulePresetsController {
@Post('create') @Post('create')
@ModuleAccessAllowed(ModulesEnum.employee_management) @ModuleAccessAllowed(ModulesEnum.employee_management)
async createPreset(@Body() dto: SchedulePresetsDto) { async createPreset(
@Body() dto: SchedulePresetsDto
) {
return await this.createService.createPreset(dto); return await this.createService.createPreset(dto);
} }
@Patch('update') @Patch('update')
@ModuleAccessAllowed(ModulesEnum.employee_management) @ModuleAccessAllowed(ModulesEnum.employee_management)
async updatePreset( async updatePreset(
@Body() dto: SchedulePresetsDto) { @Body() dto: SchedulePresetsDto
) {
return await this.updateService.updatePreset(dto); return await this.updateService.updatePreset(dto);
} }
@Delete('delete/:id') @Delete('delete/:id')
@ModuleAccessAllowed(ModulesEnum.employee_management) @ModuleAccessAllowed(ModulesEnum.employee_management)
async deletePreset( async deletePreset(
@Param('id') id: number) { @Param('id') id: number
) {
return await this.deleteService.deletePreset(id); return await this.deleteService.deletePreset(id);
} }

View File

@ -1,5 +1,5 @@
import { Weekday } from "prisma/postgres/generated/prisma/client/postgres/client"; import { Weekday } from "prisma/postgres/generated/prisma/client/postgres/client";
import { ArrayMinSize, IsArray, IsBoolean, IsEnum, IsInt, IsOptional, IsString, Matches} from "class-validator"; import { ArrayMinSize, IsArray, IsBoolean, IsEnum, IsInt, IsOptional, IsString, Matches } from "class-validator";
import { HH_MM_REGEX } from "src/common/utils/constants.utils"; import { HH_MM_REGEX } from "src/common/utils/constants.utils";
export class SchedulePresetsDto { export class SchedulePresetsDto {
@ -17,12 +17,12 @@ export class SchedulePresetShiftsDto {
@IsOptional() @IsBoolean() is_remote?: boolean; @IsOptional() @IsBoolean() is_remote?: boolean;
} }
export const WEEKDAY_MAP: Record<Weekday, number> = { export const WEEKDAY_MAP: Weekday[] = [
SUN: 0, 'SUN',
MON: 1, 'MON',
TUE: 2, 'TUE',
WED: 3, 'WED',
THU: 4, 'THU',
FRI: 5, 'FRI',
SAT: 6, 'SAT'
}; ]

View File

@ -18,7 +18,9 @@ import { PayPeriodEventService } from "../pay-period/services/pay-period-event.s
@Module({ @Module({
controllers: [SchedulePresetsController], controllers: [
SchedulePresetsController
],
providers: [ providers: [
SchedulePresetsGetService, SchedulePresetsGetService,
SchedulePresetsCreateService, SchedulePresetsCreateService,

View File

@ -1,17 +1,15 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service"; import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { sevenDaysFrom, toDateFromString, toStringFromDate, toStringFromHHmm } from "src/common/utils/date-utils"; import { sevenDaysFrom, toDateFromString, toStringFromDate, toStringFromHHmm } from "src/common/utils/date-utils";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper"; import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service"; import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";
import { timesheet_select } from "src/time-and-attendance/utils/selects.utils"; import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto"; import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto";
import { WEEKDAY_MAP } from "src/time-and-attendance/schedule-presets/schedule-presets.dto"; import { WEEKDAY_MAP } from "src/time-and-attendance/schedule-presets/schedule-presets.dto";
import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service"; import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service";
import { $Enums, SchedulePresetShifts } from "prisma/postgres/generated/prisma/client/postgres/client"; import { SchedulePresetShifts } from "prisma/postgres/generated/prisma/client/postgres/client";
@Injectable() @Injectable()
@ -24,7 +22,11 @@ export class SchedulePresetsApplyService {
private readonly payPeriodEventService: PayPeriodEventService, private readonly payPeriodEventService: PayPeriodEventService,
) { } ) { }
async applyPresetToTimesheet(email: string, timesheet_id: number, employee_email?: string): Promise<Result<boolean, string>> { async applyPresetToTimesheet(
email: string,
timesheet_id: number,
employee_email?: string
): Promise<Result<boolean, string>> {
const user_email = employee_email ?? email; const user_email = employee_email ?? email;
const employee_id = await this.emailResolver.findIdByEmail(user_email); const employee_id = await this.emailResolver.findIdByEmail(user_email);
if (!employee_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }; if (!employee_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
@ -63,15 +65,15 @@ export class SchedulePresetsApplyService {
if (timesheet.is_approved) return { success: false, error: 'INVALID_TIMESHEET' }; if (timesheet.is_approved) return { success: false, error: 'INVALID_TIMESHEET' };
if (timesheet.shift.length > 0) return { success: false, error: 'INVALID_TIMESHEET' }; if (timesheet.shift.length > 0) return { success: false, error: 'INVALID_TIMESHEET' };
const dated_map = await sevenDaysFrom(timesheet.start_date); const dated_map = sevenDaysFrom(timesheet.start_date);
let created_shifts: ShiftDto[] = []; const created_shifts: ShiftDto[] = [];
for (const preset_shift of default_preset_shifts) { for (const preset_shift of default_preset_shifts) {
const date = dated_map.find(date => date.getUTCDay() === WEEKDAY_MAP[preset_shift.week_day]) const date = dated_map.find(date => date.getUTCDay() === WEEKDAY_MAP[preset_shift.week_day])
if (!date) return { success: false, error: 'INVALID_PRESET_DATE' }; if (!date) return { success: false, error: 'INVALID_PRESET_DATE' };
const shift = await this.createShiftFromPreset(preset_shift, date!, timesheet.id) const shift = await this.createShiftFromPreset(preset_shift, date, timesheet.id)
if (!shift.success) return { success: false, error: shift.error }; if (!shift.success) return { success: false, error: shift.error };
created_shifts.push(shift.data); created_shifts.push(shift.data);
@ -99,7 +101,7 @@ export class SchedulePresetsApplyService {
const user_email = employee_email ?? email; const user_email = employee_email ?? email;
const employee_id = await this.emailResolver.findIdByEmail(user_email); const employee_id = await this.emailResolver.findIdByEmail(user_email);
if (!employee_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }; if (!employee_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
const week_day = Object.keys(WEEKDAY_MAP)[week_day_index]; const week_day = WEEKDAY_MAP[week_day_index];
const preset_shift = await this.prisma.employees.findFirst({ const preset_shift = await this.prisma.employees.findFirst({
where: { id: employee_id.data, }, where: { id: employee_id.data, },
@ -108,7 +110,7 @@ export class SchedulePresetsApplyService {
select: { select: {
id: true, id: true,
shifts: { shifts: {
where: { week_day: $Enums.Weekday[week_day] }, where: { week_day },
select: { select: {
bank_code_id: true, bank_code_id: true,
start_time: true, start_time: true,
@ -146,7 +148,11 @@ export class SchedulePresetsApplyService {
return { success: true, data: true }; return { success: true, data: true };
} }
private createShiftFromPreset = async (preset: Partial<SchedulePresetShifts>, date: Date, timesheet_id: number): Promise<Result<ShiftDto, string>> => { private createShiftFromPreset = async (
preset: Partial<SchedulePresetShifts>,
date: Date,
timesheet_id: number
): Promise<Result<ShiftDto, string>> => {
const type = await this.typeResolver.findTypeByBankCodeId(preset.bank_code_id!); const type = await this.typeResolver.findTypeByBankCodeId(preset.bank_code_id!);
if (!type.success) return { success: false, error: 'INVALID_PRESET_SHIFT' }; if (!type.success) return { success: false, error: 'INVALID_PRESET_SHIFT' };

View File

@ -1,9 +1,6 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service"; import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/schedule-presets.dto"; import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/schedule-presets.dto";
import { overlaps, toDateFromHHmm } from "src/common/utils/date-utils"; import { overlaps, toDateFromHHmm } from "src/common/utils/date-utils";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper"; import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
@ -14,10 +11,10 @@ export class SchedulePresetsCreateService {
private readonly prisma: PrismaPostgresService, private readonly prisma: PrismaPostgresService,
private readonly typeResolver: BankCodesResolver, private readonly typeResolver: BankCodesResolver,
) { } ) { }
//_________________________________________________________________
// CREATE async createPreset(
//_________________________________________________________________ dto: SchedulePresetsDto
async createPreset(dto: SchedulePresetsDto): Promise<Result<boolean, string>> { ): Promise<Result<boolean, string>> {
try { try {
//validate new unique name //validate new unique name
const existing = await this.prisma.schedulePresets.findFirst({ const existing = await this.prisma.schedulePresets.findFirst({
@ -80,6 +77,7 @@ export class SchedulePresetsCreateService {
}); });
return { success: true, data: true } return { success: true, data: true }
} catch (error) { } catch (error) {
console.error(error);
return { success: false, error: 'INVALID_SCHEDULE_PRESET' } return { success: false, error: 'INVALID_SCHEDULE_PRESET' }
} }
} }

View File

@ -6,10 +6,9 @@ import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
export class SchedulePresetDeleteService { export class SchedulePresetDeleteService {
constructor(private readonly prisma: PrismaPostgresService) { } constructor(private readonly prisma: PrismaPostgresService) { }
//_________________________________________________________________ async deletePreset(
// DELETE preset_id: number
//_________________________________________________________________ ): Promise<Result<boolean, string>> {
async deletePreset(preset_id: number): Promise<Result<boolean, string>> {
const preset = await this.prisma.schedulePresets.findUnique({ const preset = await this.prisma.schedulePresets.findUnique({
where: { id: preset_id }, where: { id: preset_id },
@ -17,7 +16,7 @@ export class SchedulePresetDeleteService {
}); });
if (!preset) return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` }; if (!preset) return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` };
const updated_employees = await this.prisma.employees.updateMany({ await this.prisma.employees.updateMany({
where: { schedule_preset_id: preset_id }, where: { schedule_preset_id: preset_id },
data: { data: {
schedule_preset_id: 0, schedule_preset_id: 0,

View File

@ -1,9 +1,6 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service"; import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { SchedulePresetsDto, SchedulePresetShiftsDto } from "../schedule-presets.dto"; import { SchedulePresetsDto, SchedulePresetShiftsDto } from "../schedule-presets.dto";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
@Injectable() @Injectable()
@ -40,6 +37,7 @@ export class SchedulePresetsGetService {
return { success: true, data: response }; return { success: true, data: response };
} catch (error) { } catch (error) {
console.error(error);
return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` }; return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` };
} }
} }

View File

@ -1,8 +1,6 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service"; import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/schedule-presets.dto"; import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/schedule-presets.dto";
import { overlaps, toDateFromHHmm } from "src/common/utils/date-utils"; import { overlaps, toDateFromHHmm } from "src/common/utils/date-utils";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper"; import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
@ -14,10 +12,9 @@ export class SchedulePresetUpdateService {
private readonly typeResolver: BankCodesResolver, private readonly typeResolver: BankCodesResolver,
) { } ) { }
//_________________________________________________________________ async updatePreset(
// UPDATE dto: SchedulePresetsDto
//_________________________________________________________________ ): Promise<Result<boolean, string>> {
async updatePreset(dto: SchedulePresetsDto): Promise<Result<boolean, string>> {
const existing = await this.prisma.schedulePresets.findFirst({ const existing = await this.prisma.schedulePresets.findFirst({
where: { id: dto.id }, where: { id: dto.id },
select: { select: {

View File

@ -25,10 +25,11 @@ export class ShiftsCreateService {
private readonly payPeriodEventService: PayPeriodEventService, private readonly payPeriodEventService: PayPeriodEventService,
) { } ) { }
//_________________________________________________________________ async createOneOrManyShifts(
// CREATE WRAPPER FUNCTION FOR ONE OR MANY INPUT email: string,
//_________________________________________________________________ shifts: ShiftDto[],
async createOneOrManyShifts(email: string, shifts: ShiftDto[], is_from_timesheet: boolean = true): Promise<Result<boolean, string>> { is_from_timesheet: boolean = true
): Promise<Result<boolean, string>> {
try { try {
//verify if array is empty or not //verify if array is empty or not
if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'NO_DATA_RECEIVED' }; if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'NO_DATA_RECEIVED' };
@ -71,13 +72,16 @@ export class ShiftsCreateService {
// returns array of created shifts // returns array of created shifts
return { success: true, data: true } return { success: true, data: true }
} catch (error) { } catch (error) {
return { success: false, error } return { success: false, error: `${error}` }
} }
} }
//_________________________________________________________________ //_________________________________________________________________
// CREATE // CREATE
//_________________________________________________________________ //_________________________________________________________________
async createShift(employee_id: number, dto: ShiftDto): Promise<Result<ShiftDto, string>> { async createShift(
employee_id: number,
dto: ShiftDto
): Promise<Result<ShiftDto, string>> {
try { try {
//transform string format to date and HHmm //transform string format to date and HHmm
const normed_shift = await this.normalizeShiftDto(dto); const normed_shift = await this.normalizeShiftDto(dto);
@ -99,9 +103,9 @@ export class ShiftsCreateService {
select: { id: true, date: true, start_time: true, end_time: true }, select: { id: true, date: true, start_time: true, end_time: true },
}); });
for (const existing of existing_shifts) { for (const existing of existing_shifts) {
const existing_start = await toDateFromString(existing.start_time); const existing_start = toDateFromString(existing.start_time);
const existing_end = await toDateFromString(existing.end_time); const existing_end = toDateFromString(existing.end_time);
const existing_date = await toDateFromString(existing.date); const existing_date = toDateFromString(existing.date);
const has_overlap = overlaps( const has_overlap = overlaps(
{ start: normed_shift.data.start_time, end: normed_shift.data.end_time, date: normed_shift.data.date }, { start: normed_shift.data.start_time, end: normed_shift.data.end_time, date: normed_shift.data.date },
@ -183,13 +187,11 @@ export class ShiftsCreateService {
} }
return { success: true, data: shift }; return { success: true, data: shift };
} catch (error) { } catch (error) {
console.error(error);
return { success: false, error: `INVALID_SHIFT` }; return { success: false, error: `INVALID_SHIFT` };
} }
} }
//_________________________________________________________________
// LOCAL HELPERS
//_________________________________________________________________
//converts all string hours and date to Date and HHmm formats //converts all string hours and date to Date and HHmm formats
private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => { private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => {
const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type); const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type);
@ -200,6 +202,14 @@ export class ShiftsCreateService {
const start_time = toDateFromHHmm(dto.start_time); const start_time = toDateFromHHmm(dto.start_time);
const end_time = toDateFromHHmm(dto.end_time); const end_time = toDateFromHHmm(dto.end_time);
return { success: true, data: { date, start_time, end_time, bank_code_id: bank_code_id.data } }; return {
success: true,
data: {
date,
start_time,
end_time,
bank_code_id: bank_code_id.data
}
};
} }
} }

View File

@ -13,13 +13,12 @@ export class ShiftsDeleteService {
private readonly emailResolver: EmailToIdResolver, private readonly emailResolver: EmailToIdResolver,
private readonly payPeriodEventService: PayPeriodEventService, private readonly payPeriodEventService: PayPeriodEventService,
) { } ) { }
//_________________________________________________________________
// DELETE async deleteShift(
//_________________________________________________________________ shift_id: number,
//finds shifts using shit_ids email: string,
//ajust paid-time-off banks is_from_timesheet: boolean = true
//blocs deletion if approved ): Promise<Result<number, string>> {
async deleteShift(shift_id: number, email: string, is_from_timesheet: boolean = true): Promise<Result<number, string>> {
try { try {
//verify if email is valid or not //verify if email is valid or not
@ -39,7 +38,7 @@ export class ShiftsDeleteService {
}); });
if (!shift || shift.timesheet.employee_id !== employee_id.data) if (!shift || shift.timesheet.employee_id !== employee_id.data)
return { success: false, error: 'SHIFT_NOT_FOUND'} return { success: false, error: 'SHIFT_NOT_FOUND' }
// return deletion result // return deletion result
return await this.prisma.$transaction(async (tx) => { return await this.prisma.$transaction(async (tx) => {
@ -80,6 +79,7 @@ export class ShiftsDeleteService {
return { success: true, data: shift.id }; return { success: true, data: shift.id };
}); });
} catch (error) { } catch (error) {
console.error(error);
return { success: false, error: `SHIFT_NOT_FOUND` }; return { success: false, error: `SHIFT_NOT_FOUND` };
} }
} }

View File

@ -1,11 +1,9 @@
import { toDateFromString, toStringFromHHmm, toStringFromDate, toDateFromHHmm, overlaps, computeHours } from "src/common/utils/date-utils"; import { toDateFromString, toStringFromHHmm, toStringFromDate, toDateFromHHmm, overlaps, computeHours } from "src/common/utils/date-utils";
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service"; import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper"; import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper"; import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { shift_select } from "src/time-and-attendance/utils/selects.utils"; import { shift_select } from "src/time-and-attendance/utils/selects.utils";
import { Normalized } from "src/time-and-attendance/utils/type.utils"; import { Normalized } from "src/time-and-attendance/utils/type.utils";
import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto"; import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto";
@ -25,14 +23,18 @@ export class ShiftsUpdateService {
private readonly payPeriodEventService: PayPeriodEventService, private readonly payPeriodEventService: PayPeriodEventService,
) { } ) { }
async updateOneOrManyShifts(shifts: ShiftDto[], email: string, is_from_timesheet: boolean = true): Promise<Result<boolean, string>> { async updateOneOrManyShifts(
shifts: ShiftDto[],
email: string,
is_from_timesheet: boolean = true
): Promise<Result<boolean, string>> {
try { try {
//verify if array is empty or not //verify if array is empty or not
if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'No data received' }; if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'No data received' };
//check for overlap inside dto objects //check for overlap inside dto objects
const overlap_check = await this.overlapChecker(shifts); const overlap_check = this.overlapChecker(shifts);
if (!overlap_check.success) return overlap_check; if (!overlap_check.success) return { success: false, error: `${overlap_check.error}` };
//calls the update functions and await the return of successfull result or not //calls the update functions and await the return of successfull result or not
const results = await Promise.allSettled(shifts.map(shift => this.updateShift(shift, email))); const results = await Promise.allSettled(shifts.map(shift => this.updateShift(shift, email)));
@ -68,12 +70,10 @@ export class ShiftsUpdateService {
// returns array of updated shifts // returns array of updated shifts
return { success: true, data: true } return { success: true, data: true }
} catch (error) { } catch (error) {
return { success: false, error } return { success: false, error: `${error}` }
} }
} }
//_________________________________________________________________
// UPDATE
//_________________________________________________________________
async updateShift(dto: ShiftDto, email: string): Promise<Result<ShiftDto, string>> { async updateShift(dto: ShiftDto, email: string): Promise<Result<ShiftDto, string>> {
try { try {
const timesheet = await this.timesheetResolver.findTimesheetIdByEmail(email, toDateFromString(dto.date)); const timesheet = await this.timesheetResolver.findTimesheetIdByEmail(email, toDateFromString(dto.date));
@ -164,6 +164,7 @@ export class ShiftsUpdateService {
return { success: true, data: shift }; return { success: true, data: shift };
} catch (error) { } catch (error) {
console.error(error);
return { success: false, error: `INVALID_SHIFT` }; return { success: false, error: `INVALID_SHIFT` };
} }
} }
@ -185,7 +186,7 @@ export class ShiftsUpdateService {
}; };
} }
private overlapChecker = async (shifts: ShiftDto[]): Promise<Result<void, string>> => { private overlapChecker = (shifts: ShiftDto[]) => {
for (let i = 0; i < shifts.length; i++) { for (let i = 0; i < shifts.length; i++) {
for (let j = i + 1; j < shifts.length; j++) { for (let j = i + 1; j < shifts.length; j++) {
const shift_a = shifts[i]; const shift_a = shifts[i];

View File

@ -1,11 +1,9 @@
import { Body, Controller, Delete, Param, Patch, Post } from "@nestjs/common"; import { Body, Controller, Delete, Param, Patch, Post } from "@nestjs/common";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client"; import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto"; import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto";
import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service"; import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";
import { ShiftsUpdateService } from "src/time-and-attendance/shifts/services/shifts-update.service"; import { ShiftsUpdateService } from "src/time-and-attendance/shifts/services/shifts-update.service";
import { ShiftsDeleteService } from "src/time-and-attendance/shifts/services/shifts-delete.service"; import { ShiftsDeleteService } from "src/time-and-attendance/shifts/services/shifts-delete.service";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators"; import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { Access } from "src/common/decorators/module-access.decorators"; import { Access } from "src/common/decorators/module-access.decorators";
@ -20,37 +18,55 @@ export class ShiftController {
@Post('create') @Post('create')
@ModuleAccessAllowed(ModulesEnum.timesheets) @ModuleAccessAllowed(ModulesEnum.timesheets)
createBatch(@Access('email') email: string, @Body() dtos: ShiftDto[]): Promise<Result<boolean, string>> { createBatch(
@Access('email') email: string,
@Body() dtos: ShiftDto[]
): Promise<Result<boolean, string>> {
return this.create_service.createOneOrManyShifts(email, dtos); return this.create_service.createOneOrManyShifts(email, dtos);
} }
@Post('create/:email') @Post('create/:email')
@ModuleAccessAllowed(ModulesEnum.timesheets_approval) @ModuleAccessAllowed(ModulesEnum.timesheets_approval)
createBatchByTimesheetsApproval(@Param('email') email: string, @Body() dtos: ShiftDto[]): Promise<Result<boolean, string>> { createBatchByTimesheetsApproval(
@Param('email') email: string,
@Body() dtos: ShiftDto[]
): Promise<Result<boolean, string>> {
return this.create_service.createOneOrManyShifts(email, dtos, false); return this.create_service.createOneOrManyShifts(email, dtos, false);
} }
@Patch('update') @Patch('update')
@ModuleAccessAllowed(ModulesEnum.timesheets) @ModuleAccessAllowed(ModulesEnum.timesheets)
updateBatch(@Access('email') email: string, @Body() dtos: ShiftDto[]): Promise<Result<boolean, string>> { updateBatch(
@Access('email') email: string,
@Body() dtos: ShiftDto[]
): Promise<Result<boolean, string>> {
return this.update_service.updateOneOrManyShifts(dtos, email); return this.update_service.updateOneOrManyShifts(dtos, email);
} }
@Patch('update/:email') @Patch('update/:email')
@ModuleAccessAllowed(ModulesEnum.timesheets_approval) @ModuleAccessAllowed(ModulesEnum.timesheets_approval)
updateBatchByTimesheetApproval(@Param('email') email: string, @Body() dtos: ShiftDto[]): Promise<Result<boolean, string>> { updateBatchByTimesheetApproval(
@Param('email') email: string,
@Body() dtos: ShiftDto[]
): Promise<Result<boolean, string>> {
return this.update_service.updateOneOrManyShifts(dtos, email, false); return this.update_service.updateOneOrManyShifts(dtos, email, false);
} }
@Delete(':shift_id') @Delete(':shift_id')
@ModuleAccessAllowed(ModulesEnum.timesheets) @ModuleAccessAllowed(ModulesEnum.timesheets)
remove(@Access('email') email: string, @Param('shift_id') shift_id: number): Promise<Result<number, string>> { remove(
@Access('email') email: string,
@Param('shift_id') shift_id: number
): Promise<Result<number, string>> {
return this.delete_service.deleteShift(shift_id, email); return this.delete_service.deleteShift(shift_id, email);
} }
@Delete(':shift_id/:email') @Delete(':shift_id/:email')
@ModuleAccessAllowed(ModulesEnum.timesheets) @ModuleAccessAllowed(ModulesEnum.timesheets)
removeByTimesheetApproval(@Param('shift_id') shift_id: number, @Param('email') email: string): Promise<Result<number, string>> { removeByTimesheetApproval(
@Param('shift_id') shift_id: number,
@Param('email') email: string
): Promise<Result<number, string>> {
return this.delete_service.deleteShift(shift_id, email, false); return this.delete_service.deleteShift(shift_id, email, false);
} }
} }

View File

@ -2,12 +2,14 @@ import { IsBoolean, IsInt, IsOptional, IsString, MaxLength } from "class-validat
export class ShiftDto { export class ShiftDto {
@IsInt() @IsOptional() id?: number; @IsInt() @IsOptional() id?: number;
@IsInt() timesheet_id!: number; @IsInt() timesheet_id: number;
@IsString() type!: string; @IsString() type: string;
@IsString() date!: string; @IsString() date: string;
@IsString() start_time!: string; @IsString() start_time: string;
@IsString() end_time!: string; @IsString() end_time: string;
@IsBoolean() is_approved!: boolean; @IsBoolean() is_approved: boolean;
@IsBoolean() is_remote!: boolean; @IsBoolean() is_remote: boolean;
@IsOptional() @IsString() @MaxLength(280) comment?: string; @IsOptional() @IsString() @MaxLength(280) comment?: string;
} }
export type BillableShiftType = 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'VACATION' | 'HOLIDAY' | 'SICK' | 'OVERTIME';

View File

@ -1 +0,0 @@
export type BillableShiftType = 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'VACATION' | 'HOLIDAY' | 'SICK' | 'OVERTIME';

View File

@ -1,6 +1,5 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ShiftController } from 'src/time-and-attendance/shifts/shift.controller'; import { ShiftController } from 'src/time-and-attendance/shifts/shift.controller';
import { ShiftsCreateService } from 'src/time-and-attendance/shifts/services/shifts-create.service'; import { ShiftsCreateService } from 'src/time-and-attendance/shifts/services/shifts-create.service';
import { ShiftsDeleteService } from 'src/time-and-attendance/shifts/services/shifts-delete.service'; import { ShiftsDeleteService } from 'src/time-and-attendance/shifts/services/shifts-delete.service';
@ -12,8 +11,12 @@ import { PaidTimeOffBankHoursService } from 'src/time-and-attendance/paid-time-o
import { PayPeriodEventService } from 'src/time-and-attendance/pay-period/services/pay-period-event.service'; import { PayPeriodEventService } from 'src/time-and-attendance/pay-period/services/pay-period-event.service';
@Module({ @Module({
imports: [PaidTimeOffModule], imports: [
controllers: [ShiftController], PaidTimeOffModule
],
controllers: [
ShiftController
],
providers: [ providers: [
ShiftsCreateService, ShiftsCreateService,
ShiftsUpdateService, ShiftsUpdateService,

View File

@ -5,38 +5,31 @@ import { BankedHoursService } from "src/time-and-attendance/domains/services/ban
import { PaidTimeOffModule } from "src/time-and-attendance/paid-time-off/paid-time-off.module"; import { PaidTimeOffModule } from "src/time-and-attendance/paid-time-off/paid-time-off.module";
import { PaidTimeOffController } from "src/time-and-attendance/paid-time-off/paid-time-off.controller"; import { PaidTimeOffController } from "src/time-and-attendance/paid-time-off/paid-time-off.controller";
import { PaidTimeOffBankHoursService } from "src/time-and-attendance/paid-time-off/paid-time-off.service"; import { PaidTimeOffBankHoursService } from "src/time-and-attendance/paid-time-off/paid-time-off.service";
import { ExpenseController } from "src/time-and-attendance/expenses/expense.controller"; import { ExpenseController } from "src/time-and-attendance/expenses/expense.controller";
import { ExpenseCreateService } from "src/time-and-attendance/expenses/services/expense-create.service"; import { ExpenseCreateService } from "src/time-and-attendance/expenses/services/expense-create.service";
import { ExpenseUpdateService } from "src/time-and-attendance/expenses/services/expense-update.service"; import { ExpenseUpdateService } from "src/time-and-attendance/expenses/services/expense-update.service";
import { ExpenseDeleteService } from "src/time-and-attendance/expenses/services/expense-delete.service"; import { ExpenseDeleteService } from "src/time-and-attendance/expenses/services/expense-delete.service";
import { ExpensesModule } from "src/time-and-attendance/expenses/expenses.module"; import { ExpensesModule } from "src/time-and-attendance/expenses/expenses.module";
import { TimesheetController } from "src/time-and-attendance/timesheets/timesheet.controller"; import { TimesheetController } from "src/time-and-attendance/timesheets/timesheet.controller";
import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service"; import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service";
import { GetTimesheetsOverviewService } from "src/time-and-attendance/timesheets/services/timesheet-employee-overview.service"; import { GetTimesheetsOverviewService } from "src/time-and-attendance/timesheets/services/timesheet-employee-overview.service";
import { TimesheetsModule } from "src/time-and-attendance/timesheets/timesheets.module"; import { TimesheetsModule } from "src/time-and-attendance/timesheets/timesheets.module";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper"; import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper"; import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper";
import { PayperiodsModule } from "src/time-and-attendance/pay-period/pay-periods.module"; import { PayperiodsModule } from "src/time-and-attendance/pay-period/pay-periods.module";
import { PayPeriodsController } from "src/time-and-attendance/pay-period/pay-periods.controller"; import { PayPeriodsController } from "src/time-and-attendance/pay-period/pay-periods.controller";
import { GetOverviewService } from "src/time-and-attendance/pay-period/services/pay-periods-build-overview.service"; import { GetOverviewService } from "src/time-and-attendance/pay-period/services/pay-periods-build-overview.service";
import { PayPeriodsQueryService } from "src/time-and-attendance/pay-period/services/pay-periods-query.service"; import { PayPeriodsQueryService } from "src/time-and-attendance/pay-period/services/pay-periods-query.service";
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/services/csv-exports.service"; import { CsvExportService } from "src/time-and-attendance/exports/services/csv-exports.service";
import { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.service"; import { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.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";
import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service"; import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";
import { ShiftsUpdateService } from "src/time-and-attendance/shifts/services/shifts-update.service"; import { ShiftsUpdateService } from "src/time-and-attendance/shifts/services/shifts-update.service";
import { ShiftsDeleteService } from "src/time-and-attendance/shifts/services/shifts-delete.service"; import { ShiftsDeleteService } from "src/time-and-attendance/shifts/services/shifts-delete.service";
import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service"; import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
import { SchedulePresetsController } from "src/time-and-attendance/schedule-presets/schedule-presets.controller"; import { SchedulePresetsController } from "src/time-and-attendance/schedule-presets/schedule-presets.controller";
import { SchedulePresetsModule } from "src/time-and-attendance/schedule-presets/schedule-presets.module"; import { SchedulePresetsModule } from "src/time-and-attendance/schedule-presets/schedule-presets.module";
@ -94,5 +87,7 @@ import { PayPeriodEventService } from "./pay-period/services/pay-period-event.se
PaidTimeOffBankHoursService, PaidTimeOffBankHoursService,
PayPeriodEventService, PayPeriodEventService,
], ],
exports: [TimesheetApprovalService], exports: [
TimesheetApprovalService
],
}) export class TimeAndAttendanceModule { }; }) export class TimeAndAttendanceModule { };

View File

@ -1,36 +1,39 @@
import { Injectable, NotFoundException } from "@nestjs/common"; import { Injectable, NotFoundException } from "@nestjs/common";
import { Prisma, Timesheets } from "prisma/postgres/generated/prisma/client/postgres/client"; import { Timesheets } from "prisma/postgres/generated/prisma/client/postgres/client";
import { PrismaPostgresService, TransactionClient } from "prisma/postgres/prisma-postgres.service"; import { PrismaPostgresService, TransactionClient } from "prisma/postgres/prisma-postgres.service";
import { BaseApprovalService } from "src/common/shared/base-approval.service"; import { BaseApprovalService } from "src/common/shared/base-approval.service";
import { timesheet_select } from "src/time-and-attendance/utils/selects.utils"; import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
@Injectable()
export class TimesheetApprovalService extends BaseApprovalService<Timesheets> {
@Injectable()
export class TimesheetApprovalService extends BaseApprovalService<Timesheets>{
constructor( constructor(
prisma: PrismaPostgresService, prisma: PrismaPostgresService,
){super(prisma)} ) { super(prisma) }
//_____________________________________________________________________________________________
// APPROVAL AND DELEGATE METHODS
//_____________________________________________________________________________________________
protected get delegate() { protected get delegate() {
return this.prisma.timesheets; return this.prisma.timesheets;
} }
protected delegateFor(tx: TransactionClient) { protected delegateFor(
tx: TransactionClient
) {
return tx.timesheets; return tx.timesheets;
} }
async updateApproval(id: number, is_approved: boolean): Promise<Timesheets> { async updateApproval(
id: number,
is_approved: boolean
): Promise<Timesheets> {
return this.prisma.$transaction((tx) => return this.prisma.$transaction((tx) =>
this.updateApprovalWithTransaction(tx, id, is_approved), this.updateApprovalWithTransaction(tx, id, is_approved),
); );
} }
async cascadeApprovalWithtx(tx: TransactionClient, timesheet_id: number, is_approved: boolean): Promise<Timesheets> { async cascadeApprovalWithtx(
tx: TransactionClient,
timesheet_id: number,
is_approved: boolean
): Promise<Timesheets> {
const timesheet = await this.updateApprovalWithTransaction(tx, timesheet_id, is_approved); const timesheet = await this.updateApprovalWithTransaction(tx, timesheet_id, is_approved);
await tx.shifts.updateMany({ await tx.shifts.updateMany({
where: { timesheet_id: timesheet_id }, where: { timesheet_id: timesheet_id },
@ -43,13 +46,16 @@ import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
return timesheet; return timesheet;
} }
async approveTimesheetById( timesheet_id: number, is_approved: boolean){ async approveTimesheetById(
timesheet_id: number,
is_approved: boolean
) {
return this.prisma.$transaction(async (tx) => { return this.prisma.$transaction(async (tx) => {
const timesheet = await tx.timesheets.findUnique({ const timesheet = await tx.timesheets.findUnique({
where: { id: timesheet_id }, where: { id: timesheet_id },
select: { id: true }, select: { id: true },
}); });
if(!timesheet) throw new NotFoundException(`Timesheet with id: ${timesheet_id} not found`); if (!timesheet) throw new NotFoundException(`Timesheet with id: ${timesheet_id} not found`);
await this.cascadeApprovalWithtx(tx, timesheet_id, is_approved); await this.cascadeApprovalWithtx(tx, timesheet_id, is_approved);
@ -59,4 +65,4 @@ import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
}); });
}); });
} }
} }

View File

@ -15,10 +15,12 @@ export class GetTimesheetsOverviewService {
private readonly emailResolver: EmailToIdResolver, private readonly emailResolver: EmailToIdResolver,
) { } ) { }
//----------------------------------------------------------------------------------- async getTimesheetsForEmployeeByPeriod(
// GET TIMESHEETS FOR A SELECTED EMPLOYEE email: string,
//----------------------------------------------------------------------------------- pay_year: number,
async getTimesheetsForEmployeeByPeriod(email: string, pay_year: number, pay_period_no: number, employee_email?: string): Promise<Result<Timesheets, string>> { pay_period_no: number,
employee_email?: string
): Promise<Result<Timesheets, string>> {
try { try {
const account_email = employee_email ?? email; const account_email = employee_email ?? email;
@ -82,11 +84,11 @@ export class GetTimesheetsOverviewService {
} }
} }
//----------------------------------------------------------------------------------- private async loadTimesheets(
// MAPPERS & HELPERS employee_id: number,
//----------------------------------------------------------------------------------- period_start: Date,
//fetch timesheet's infos period_end: Date
private async loadTimesheets(employee_id: number, period_start: Date, period_end: Date) { ) {
return this.prisma.timesheets.findMany({ return this.prisma.timesheets.findMany({
where: { employee_id, start_date: { gte: period_start, lte: period_end } }, where: { employee_id, start_date: { gte: period_start, lte: period_end } },
include: { include: {
@ -98,7 +100,10 @@ export class GetTimesheetsOverviewService {
}); });
} }
private ensureTimesheet = async (employee_id: number, start_date: Date | string) => { private ensureTimesheet = async (
employee_id: number,
start_date: Date | string
) => {
const start = toDateFromString(start_date); const start = toDateFromString(start_date);
let row = await this.prisma.timesheets.findFirst({ let row = await this.prisma.timesheets.findFirst({

View File

@ -1,15 +1,18 @@
import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client"; import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client";
import { toDateFromString, sevenDaysFrom, toStringFromDate, toHHmmFromDate } from "src/common/utils/date-utils"; import { toDateFromString, sevenDaysFrom, toStringFromDate, toHHmmFromDate } from "src/common/utils/date-utils";
import { BankCodeDto } from "src/time-and-attendance/bank-codes/bank-codes.dto";
import { Timesheet } from "src/time-and-attendance/timesheets/timesheet.dto"; import { Timesheet } from "src/time-and-attendance/timesheets/timesheet.dto";
export const mapOneTimesheet = async (timesheet: Prisma.TimesheetsGetPayload<{ export const mapOneTimesheet = (
timesheet: Prisma.TimesheetsGetPayload<{
include: { include: {
employee: { include: { user } }, employee: { include: { user } },
shift: { include: { bank_code }, orderBy: { start_time: 'asc' } }, shift: { include: { bank_code }, orderBy: { start_time: 'asc' } },
expense: { include: { bank_code } }, expense: { include: { bank_code } },
} }
}>): Promise<Timesheet> => { }>
): Timesheet => {
//converts string to UTC date format //converts string to UTC date format
const start = toDateFromString(timesheet.start_date); const start = toDateFromString(timesheet.start_date);
const day_dates = sevenDaysFrom(start); const day_dates = sevenDaysFrom(start);
@ -23,7 +26,7 @@ export const mapOneTimesheet = async (timesheet: Prisma.TimesheetsGetPayload<{
shifts_by_date.set(date_string, arr); shifts_by_date.set(date_string, arr);
} }
//map of expenses by days //map of expenses by days
const expenses_by_date = new Map<string, Prisma.ExpensesGetPayload<{ include: { bank_code: {} } }>[]>(); const expenses_by_date = new Map<string, Prisma.ExpensesGetPayload<{ include: { bank_code: Prisma.BankCodesDefaultArgs } }>[]>();
for (const expense of timesheet.expense) { for (const expense of timesheet.expense) {
const date_string = toStringFromDate(expense.date); const date_string = toStringFromDate(expense.date);
const arr = expenses_by_date.get(date_string) ?? []; const arr = expenses_by_date.get(date_string) ?? [];
@ -150,7 +153,7 @@ const diffOfHours = (a: Date, b: Date): number => {
const num = (value: any): number => { return value ? Number(value) : 0 }; const num = (value: any): number => { return value ? Number(value) : 0 };
// shift's subgroup types // shift's subgroup types
const hoursSubGroupFromBankCode = (bank_code: any): keyof TotalHours => { const hoursSubGroupFromBankCode = (bank_code: BankCodeDto): keyof TotalHours => {
const type = bank_code.type; const type = bank_code.type;
if (type.includes('EVENING')) return 'evening'; if (type.includes('EVENING')) return 'evening';
if (type.includes('EMERGENCY')) return 'emergency'; if (type.includes('EMERGENCY')) return 'emergency';
@ -164,7 +167,7 @@ const hoursSubGroupFromBankCode = (bank_code: any): keyof TotalHours => {
} }
// expense's subgroup types // expense's subgroup types
const expenseSubgroupFromBankCode = (bank_code: any): keyof TotalExpenses => { const expenseSubgroupFromBankCode = (bank_code: BankCodeDto): keyof TotalExpenses => {
const type = bank_code.type; const type = bank_code.type;
if (type.includes('MILEAGE')) return 'mileage'; if (type.includes('MILEAGE')) return 'mileage';
if (type.includes('PER_DIEM')) return 'per_diem'; if (type.includes('PER_DIEM')) return 'per_diem';

View File

@ -6,12 +6,16 @@ import { GetTimesheetsOverviewService } from 'src/time-and-attendance/timesheets
import { EmailToIdResolver } from 'src/common/mappers/email-id.mapper'; import { EmailToIdResolver } from 'src/common/mappers/email-id.mapper';
@Module({ @Module({
controllers: [TimesheetController], controllers: [
TimesheetController
],
providers: [ providers: [
GetTimesheetsOverviewService, GetTimesheetsOverviewService,
EmailToIdResolver, EmailToIdResolver,
TimesheetApprovalService, TimesheetApprovalService,
], ],
exports: [TimesheetApprovalService], exports: [
TimesheetApprovalService
],
}) })
export class TimesheetsModule {} export class TimesheetsModule { }