From c0eef4e3a3595d1a2ee24ca0419375ae1d1ad6ca Mon Sep 17 00:00:00 2001 From: Nicolas Drolet Date: Wed, 29 Oct 2025 15:20:20 -0400 Subject: [PATCH 1/4] feat(docker): Add/Update Dockerfile for Remote Docker Lab deployment --- Dockerfile | 4 +++- src/main.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7063f1a..21299ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,9 @@ ENV AUTHENTIK_AUTH_URL="https://auth.targo.ca/application/o/authorize/" ENV AUTHENTIK_TOKEN_URL="https://auth.targo.ca/application/o/token/" ENV AUTHENTIK_USERINFO_URL="https://auth.targo.ca/application/o/userinfo/" -ENV TARGO_FRONTEND_URI="http://localhost:9000/" +ENV TARGO_FRONTEND_URI_1="http://targo-frontend-nicolas:9000" +ENV TARGO_FRONTEND_URI_2="http://targo-frontend-matthieu:9000" +ENV TARGO_FRONTEND_URI_3="http://targo-frontend-lion:9000" ENV ATTACHMENTS_SERVER_ID="server" ENV ATTACHMENTS_ROOT=C:/ diff --git a/src/main.ts b/src/main.ts index 88237b6..89eb564 100644 --- a/src/main.ts +++ b/src/main.ts @@ -46,7 +46,7 @@ async function bootstrap() { // Enable CORS app.enableCors({ - origin: 'http://localhost:9000', + origin: [process.env.TARGO_FRONTEND_URI_1, process.env.TARGO_FRONTEND_URI_2, process.env.TARGO_FRONTEND_URI_3], credentials: true, }); From 50521b7c681940eb684020666fdfd8742ab37c78 Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Thu, 30 Oct 2025 12:04:05 -0400 Subject: [PATCH 2/4] refactor(presets): removed upsertAction manipulation. created 3 seperate routes for create update et delete. --- .../domains/services/mileage.service.ts | 3 +- .../modules/shared/shared.module.ts | 6 +- .../shared/types/upsert-actions.types.ts | 1 - .../controller/schedule-presets.controller.ts | 57 +++-- .../dtos/create-schedule-preset-shifts.dto.ts | 4 + .../dtos/update-schedule-presets.dto.ts | 3 + .../schedule-presets.module.ts | 11 +- .../schedule-presets-apply.service.ts | 8 +- .../schedule-presets-command.service.ts | 206 ++++++++---------- .../schedule-presets-query.service.ts | 2 +- .../shifts/controllers/shift.controller.ts | 2 +- .../shifts/dtos/shift-update.dto.ts | 2 +- .../services/shifts-archival.service.ts | 4 +- .../shifts/services/shifts-get.service.ts | 4 +- .../shifts/services/shifts-upsert.service.ts | 7 +- .../utils/constants.utils.ts | 20 +- .../utils/date-time.utils.ts | 5 +- src/time-and-attendance/utils/type.utils.ts | 2 + 18 files changed, 175 insertions(+), 172 deletions(-) delete mode 100644 src/time-and-attendance/modules/shared/types/upsert-actions.types.ts create mode 100644 src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/update-schedule-presets.dto.ts diff --git a/src/time-and-attendance/domains/services/mileage.service.ts b/src/time-and-attendance/domains/services/mileage.service.ts index 8919532..030ce42 100644 --- a/src/time-and-attendance/domains/services/mileage.service.ts +++ b/src/time-and-attendance/domains/services/mileage.service.ts @@ -1,6 +1,5 @@ import { BadRequestException, Injectable, Logger } from "@nestjs/common"; -import { PrismaService } from "../../../prisma/prisma.service"; -import { Decimal } from "@prisma/client/runtime/library"; +import { PrismaService } from '../../../prisma/prisma.service'; @Injectable() export class MileageService { diff --git a/src/time-and-attendance/modules/shared/shared.module.ts b/src/time-and-attendance/modules/shared/shared.module.ts index 8d4aa95..0e26d7b 100644 --- a/src/time-and-attendance/modules/shared/shared.module.ts +++ b/src/time-and-attendance/modules/shared/shared.module.ts @@ -1,9 +1,9 @@ -import { Module } from "@nestjs/common"; -import { EmailToIdResolver } from "./utils/resolve-email-id.utils"; import { EmployeeTimesheetResolver } from "./utils/resolve-timesheet.utils"; -import { FullNameResolver } from "./utils/resolve-full-name.utils"; +import { EmailToIdResolver } from "./utils/resolve-email-id.utils"; import { BankCodesResolver } from "./utils/resolve-bank-type-id.utils"; +import { FullNameResolver } from "./utils/resolve-full-name.utils"; import { PrismaModule } from "src/prisma/prisma.module"; +import { Module } from "@nestjs/common"; @Module({ imports: [PrismaModule], diff --git a/src/time-and-attendance/modules/shared/types/upsert-actions.types.ts b/src/time-and-attendance/modules/shared/types/upsert-actions.types.ts deleted file mode 100644 index 9342d75..0000000 --- a/src/time-and-attendance/modules/shared/types/upsert-actions.types.ts +++ /dev/null @@ -1 +0,0 @@ -export type UpsertAction = 'create' | 'update' | 'delete'; \ No newline at end of file diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller.ts index f02b958..965e43b 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller.ts @@ -1,44 +1,63 @@ -import { BadRequestException, Body, Controller, Get, NotFoundException, Param, Post, Put, Query } from "@nestjs/common"; -import { SchedulePresetsCommandService } from "../services/schedule-presets-command.service"; -import { SchedulePresetsQueryService } from "../services/schedule-presets-query.service"; -import { SchedulePresetsDto } from "../dtos/create-schedule-presets.dto"; -import { UpsertAction } from "src/time-and-attendance/modules/shared/types/upsert-actions.types"; +import { Controller, Put, Param, Query, Body, Get, Post, BadRequestException, ParseIntPipe, Delete, Patch } from "@nestjs/common"; +import { SchedulePresetsCommandService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-command.service"; +import { SchedulePresetsApplyService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service"; +import { SchedulePresetsQueryService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service"; +import { SchedulePresetsDto } from "src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-presets.dto"; +import { SchedulePresetsUpdateDto } from "src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/update-schedule-presets.dto"; + + @Controller('schedule-presets') export class SchedulePresetsController { constructor( private readonly commandService: SchedulePresetsCommandService, + private readonly applyPresetsService: SchedulePresetsApplyService, private readonly queryService: SchedulePresetsQueryService, ){} - //used to create, update or delete a schedule preset - @Put(':email') - async upsert( - @Param('email') email: string, - @Query('action') action: UpsertAction, - @Body() dto: SchedulePresetsDto, + //used to create a schedule preset + @Post(':employee_id') + async createPreset( + @Param('employee_id', ParseIntPipe) employee_id: number, + @Body() dto: SchedulePresetsDto, ) { - const actions: UpsertAction[] = ['create','update','delete']; - if(!actions) throw new NotFoundException(`No action found for ${actions}`) - return this.commandService.upsertSchedulePreset(email, action, dto); + return await this.commandService.createPreset(employee_id, dto); } + //used to update an already existing schedule preset + @Patch(':preset_id') + async updatePreset( + @Param('preset_id', ParseIntPipe) preset_id: number, + @Body() dto: SchedulePresetsUpdateDto, + ) { + return await this.commandService.updatePreset(preset_id, dto); + } + + //used to delete a schedule preset + @Delete(':preset_id') + async deletePreset( + @Param('preset_id') preset_id: number, + ) { + return await this.commandService.deletePreset(preset_id); + } + + //used to show the list of available schedule presets @Get(':email') async findListByEmail( - @Param('email') email: string, + @Param('email') email: string, ) { return this.queryService.findSchedulePresetsByEmail(email); } //used to apply a preset to a timesheet @Post('/apply-presets/:email') async applyPresets( - @Param('email') email: string, - @Query('preset') preset_name: string, - @Query('start') start_date: string, + @Param('email') email: string, + @Query('preset') preset_name: string, + @Query('start') start_date: string, ) { if(!preset_name?.trim()) throw new BadRequestException('Query "preset" is required'); if(!start_date?.trim()) throw new BadRequestException('Query "start" is required YYYY-MM-DD'); - return this.applyPresets(email, preset_name, start_date); + return this.applyPresetsService.applyToTimesheet(email, preset_name, start_date); } } \ No newline at end of file diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-preset-shifts.dto.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-preset-shifts.dto.ts index 33c06cd..5e37dcb 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-preset-shifts.dto.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-preset-shifts.dto.ts @@ -1,10 +1,14 @@ import { IsBoolean, IsEnum, IsInt, IsOptional, IsString, Matches, Min } from "class-validator"; +import { HH_MM_REGEX } from "src/time-and-attendance/utils/constants.utils"; import { Weekday } from "@prisma/client"; export class SchedulePresetShiftsDto { @IsEnum(Weekday) week_day!: Weekday; + @IsInt() + preset_id!: number; + @IsInt() @Min(1) sort_order!: number; diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/update-schedule-presets.dto.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/update-schedule-presets.dto.ts new file mode 100644 index 0000000..2246c20 --- /dev/null +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/update-schedule-presets.dto.ts @@ -0,0 +1,3 @@ +import { SchedulePresetsDto } from "src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-presets.dto"; + +export class SchedulePresetsUpdateDto extends SchedulePresetsDto{} \ No newline at end of file diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/schedule-presets.module.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/schedule-presets.module.ts index 70d34f5..1a8f9bf 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/schedule-presets.module.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/schedule-presets.module.ts @@ -1,9 +1,10 @@ -import { Module } from "@nestjs/common"; -import { SchedulePresetsCommandService } from "./services/schedule-presets-command.service"; -import { SchedulePresetsQueryService } from "./services/schedule-presets-query.service"; -import { SchedulePresetsController } from "./controller/schedule-presets.controller"; -import { SchedulePresetsApplyService } from "./services/schedule-presets-apply.service"; +import { SchedulePresetsCommandService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-command.service"; +import { SchedulePresetsApplyService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service"; +import { SchedulePresetsQueryService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service"; +import { SchedulePresetsController } from "src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller"; import { SharedModule } from "src/time-and-attendance/modules/shared/shared.module"; +import { Module } from "@nestjs/common"; + @Module({ imports: [SharedModule], diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts index 7bad7f6..3d7d0f1 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts @@ -1,9 +1,11 @@ -import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common"; +import { Injectable, BadRequestException, NotFoundException, ConflictException } from "@nestjs/common"; import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils"; +import { Weekday, Prisma } from "@prisma/client"; +import { DATE_ISO_FORMAT } from "src/time-and-attendance/utils/constants.utils"; import { PrismaService } from "src/prisma/prisma.service"; -import { Prisma, Weekday } from "@prisma/client"; -import { WEEKDAY } from "../../../../utils/mappers.utils"; import { ApplyResult } from "src/time-and-attendance/utils/type.utils"; +import { WEEKDAY } from "src/time-and-attendance/utils/mappers.utils"; + @Injectable() export class SchedulePresetsApplyService { diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-command.service.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-command.service.ts index 3803004..cb35378 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-command.service.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-command.service.ts @@ -1,73 +1,31 @@ -import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common"; -import { BankCodesResolver } from "src/time-and-attendance/modules/shared/utils/resolve-bank-type-id.utils"; -import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils"; -import { UpsertAction } from "src/time-and-attendance/modules/shared/types/upsert-actions.types"; -import { PrismaService } from "src/prisma/prisma.service"; -import { SchedulePresetsDto } from "../dtos/create-schedule-presets.dto"; -import { Prisma, Weekday } from "@prisma/client"; +import { Injectable, BadRequestException, NotFoundException, ConflictException } from "@nestjs/common"; +import { SchedulePresetsDto } from "src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-presets.dto"; +import { BankCodesResolver } from "src/time-and-attendance/modules/shared/utils/resolve-bank-type-id.utils"; +import { Prisma, Weekday } from "@prisma/client"; +import { PrismaService } from "src/prisma/prisma.service"; +import { toHHmmFromDate } from "src/time-and-attendance/utils/date-time.utils"; + +type DeleteResult = { ok: true; id: number; } | { ok: false; id: number; error: any }; +type CreateResult = { ok: true; } | { ok: false; error: any }; +type UpdateResult = { ok: true; id: number; data: SchedulePresetsDto } | { ok: false; id: number; error: any }; @Injectable() export class SchedulePresetsCommandService { constructor( private readonly prisma: PrismaService, - private readonly emailResolver: EmailToIdResolver, private readonly typeResolver : BankCodesResolver, - ){} - - //_________________________________________________________________ - // MASTER CRUD FUNCTION - //_________________________________________________________________ - async upsertSchedulePreset( - email: string, - action: UpsertAction, - dto: SchedulePresetsDto, - ): Promise<{ - action: UpsertAction; - preset_id?: number; - total_items?: number; - }>{ - if(!dto.name?.trim()) throw new BadRequestException(`A Name is required`); - - //resolve employee_id using email - const employee_id = await this.emailResolver.findIdByEmail(email); - if(!employee_id) throw new NotFoundException(`employee with email: ${email} not found`); - - //DELETE - if(action === 'delete') { - return this.deletePreset(employee_id, dto.name); - } - - if(!Array.isArray(dto.preset_shifts) || dto.preset_shifts.length === 0) { - throw new BadRequestException(`Empty array, no detected shifts`); - } - const shifts_data = await this.resolveAndBuildPresetShifts(dto); - - //CREATE AND UPDATE - if(action === 'create') { - return this.createPreset(employee_id, dto, shifts_data); - } else if (action === 'update') { - return this.updatePreset(employee_id, dto, shifts_data); - } - throw new BadRequestException(`Unknown action: ${ action }`); - } - + ){} //_________________________________________________________________ // CREATE //_________________________________________________________________ - private async createPreset( - employee_id: number, - dto: SchedulePresetsDto, - shifts_data: Prisma.SchedulePresetShiftsCreateWithoutPresetInput[], - ): Promise<{ - action: UpsertAction; - preset_id: number; - total_items: number; - }> { + async createPreset( employee_id: number, dto: SchedulePresetsDto): Promise { try { - const result = await this.prisma.$transaction(async (tx)=> { + const shifts_data = await this.resolveAndBuildPresetShifts(dto); + if(!shifts_data) throw new BadRequestException(`Employee with id: ${employee_id} or dto not found`); + await this.prisma.$transaction(async (tx)=> { if(dto.is_default) { await tx.schedulePresets.updateMany({ - where: { employee_id, is_default: true }, + where: { is_default: true, employee_id }, data: { is_default: false }, }); } @@ -78,56 +36,53 @@ export class SchedulePresetsCommandService { is_default: !!dto.is_default, shifts: { create: shifts_data}, }, - include: { shifts: true }, }); return created; }); - return { action: 'create', preset_id: result.id, total_items: result.shifts.length }; + return { ok: true }; + } catch (error: unknown) { - if(error instanceof Prisma.PrismaClientKnownRequestError){ - if(error?.code === 'P2002') { - throw new ConflictException(`The name ${dto.name} is already used for another schedule preset`); - } - if (error.code === 'P2003' || error.code === 'P2011') { - throw new ConflictException('Invalid constraint on preset shifts'); - } - } - throw error; + return { ok: false, error }; } } //_________________________________________________________________ // UPDATE //_________________________________________________________________ - private async updatePreset( - employee_id: number, - dto: SchedulePresetsDto, - shifts_data: Prisma.SchedulePresetShiftsCreateWithoutPresetInput[], - ): Promise<{ - action: UpsertAction; - preset_id?: number; - total_items?: number; - }> { - const existing = await this.prisma.schedulePresets.findFirst({ - where: { employee_id, name: dto.name }, - select: { id:true, is_default: true }, - }); - if(!existing) throw new NotFoundException(`Preset "${dto.name}" not found`); - + async updatePreset( preset_id: number, dto: SchedulePresetsDto ): Promise { try { - const result = await this.prisma.$transaction(async (tx) => { + const existing = await this.prisma.schedulePresets.findFirst({ + where: { id: preset_id }, + select: { + id:true, + is_default: true, + employee_id: true, + }, + }); + if(!existing) throw new NotFoundException(`Preset "${dto.name}" not found`); + + const shifts_data = await this.resolveAndBuildPresetShifts(dto); + await this.prisma.$transaction(async (tx) => { if(typeof dto.is_default === 'boolean'){ if(dto.is_default) { await tx.schedulePresets.updateMany({ - where: { employee_id, is_default: true, NOT: { id: existing.id } }, + where: { + employee_id: existing.employee_id, + is_default: true, + NOT: { id: existing.id }, + }, data: { is_default: false }, }); } await tx.schedulePresets.update({ where: { id: existing.id }, - data: { is_default: dto.is_default }, + data: { + is_default: dto.is_default, + name: dto.name, + }, }); } + if(shifts_data.length <= 0) throw new BadRequestException('Preset shifts to update not found'); await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } }); @@ -148,45 +103,62 @@ export class SchedulePresetsCommandService { }; }); await tx.schedulePresetShifts.createMany({data: create_many_data}); - - const count = await tx.schedulePresetShifts.count({ where: { preset_id: existing.id } }); - return { id: existing.id, total: count }; }); - return { action: 'update', preset_id: result.id, total_items: result.total }; - } catch (error: unknown){ - if(error instanceof Prisma.PrismaClientKnownRequestError){ - if(error?.code === 'P2003' || error?.code === 'P2011') { - throw new ConflictException(`Invalid constraint on preset shifts`); - } - } - throw error; + + const saved = await this.prisma.schedulePresets.findUnique({ + where: { id: existing.id }, + include: { shifts: { + orderBy: [{ week_day: 'asc' }, { sort_order: 'asc' }], + include: { bank_code: { select: { type: true }}}, + }}, + }); + if(!saved) throw new NotFoundException(`Preset with id: ${existing.id} not found`); + + const response_dto: SchedulePresetsDto = { + name: saved.name, + is_default: saved.is_default, + preset_shifts: saved.shifts.map((shift) => ({ + preset_id: shift.preset_id, + week_day: shift.week_day, + sort_order: shift.sort_order, + type: shift.bank_code.type, + start_time: toHHmmFromDate(shift.start_time), + end_time: toHHmmFromDate(shift.end_time), + is_remote: shift.is_remote, + })), + }; + + return { ok: true, id: existing.id, data: response_dto }; + } catch (error){ + return { ok: false, id: preset_id, error } } } //_________________________________________________________________ // DELETE //_________________________________________________________________ - private async deletePreset( - employee_id: number, - name: string, - ): Promise<{ - action: UpsertAction; - preset_id?: number; - total_items?: number; - }> { - const existing = await this.prisma.schedulePresets.findFirst({ - where: { employee_id, name }, - select: { id: true }, - }); - if(!existing) throw new NotFoundException(`Preset "${name}" not found`); - await this.prisma.$transaction(async (tx) => { - await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } }); - await tx.schedulePresets.delete({where: { id: existing.id } }); - }); - return { action: 'delete', preset_id: existing.id, total_items: 0 }; + async deletePreset( preset_id: number ): Promise { + try { + await this.prisma.$transaction(async (tx) => { + const preset = await tx.schedulePresets.findFirst({ + where: { id: preset_id }, + select: { id: true }, + }); + if(!preset) throw new NotFoundException(`Preset with id ${ preset_id } not found`); + await tx.schedulePresets.delete({where: { id: preset_id } }); + + return { success: true }; + }); + return { ok: true, id: preset_id }; + + } catch (error) { + if(error) throw new NotFoundException(`Preset schedule with id ${ preset_id } not found`); + return { ok: false, id: preset_id, error }; + } } - //PRIVATE HELPER + //PRIVATE HELPERS + //resolve bank_code_id using type and convert hours to TIME and valid shifts end/start private async resolveAndBuildPresetShifts( dto: SchedulePresetsDto diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service.ts index 74d464b..ec0d134 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service.ts @@ -1,8 +1,8 @@ import { Injectable, NotFoundException } from "@nestjs/common"; +import { PresetResponse, ShiftResponse } from "src/time-and-attendance/utils/type.utils"; import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils"; import { PrismaService } from "src/prisma/prisma.service"; import { Prisma } from "@prisma/client"; -import { PresetResponse, ShiftResponse } from "src/time-and-attendance/utils/type.utils"; @Injectable() export class SchedulePresetsQueryService { diff --git a/src/time-and-attendance/modules/time-tracker/shifts/controllers/shift.controller.ts b/src/time-and-attendance/modules/time-tracker/shifts/controllers/shift.controller.ts index 1658e3a..6c4968c 100644 --- a/src/time-and-attendance/modules/time-tracker/shifts/controllers/shift.controller.ts +++ b/src/time-and-attendance/modules/time-tracker/shifts/controllers/shift.controller.ts @@ -1,8 +1,8 @@ import { BadRequestException, Body, Controller, Delete, Param, Patch, Post } from "@nestjs/common"; import { CreateResult, UpdateResult } from "src/time-and-attendance/utils/type.utils"; import { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service"; -import { ShiftDto } from "../dtos/shift-create.dto"; import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto"; +import { ShiftDto } from "../dtos/shift-create.dto"; @Controller('shift') diff --git a/src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto.ts b/src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto.ts index a9f2901..99645cb 100644 --- a/src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto.ts +++ b/src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto.ts @@ -1,6 +1,6 @@ import { PartialType, OmitType } from "@nestjs/swagger"; -import { IsInt } from "class-validator"; import { ShiftDto } from "./shift-create.dto"; +import { IsInt } from "class-validator"; export class UpdateShiftDto extends PartialType( // allows update using ShiftDto and preventing OmitType variables to be modified diff --git a/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-archival.service.ts b/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-archival.service.ts index 6f60390..a5a833f 100644 --- a/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-archival.service.ts +++ b/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-archival.service.ts @@ -1,6 +1,6 @@ -import { Injectable } from "@nestjs/common"; -import { ShiftsArchive } from "@prisma/client"; import { PrismaService } from "src/prisma/prisma.service"; +import { ShiftsArchive } from "@prisma/client"; +import { Injectable } from "@nestjs/common"; /** * _____________________________________________________________________________________ diff --git a/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-get.service.ts b/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-get.service.ts index 67f887a..8971cf8 100644 --- a/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-get.service.ts +++ b/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-get.service.ts @@ -1,8 +1,8 @@ +import { toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time.utils"; import { Injectable, NotFoundException } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; -import { GetShiftDto } from "../dtos/shift-get.dto"; -import { toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time.utils"; import { shift_select } from "src/time-and-attendance/utils/selects.utils"; +import { GetShiftDto } from "../dtos/shift-get.dto"; /** * _____________________________________________________________________________________ diff --git a/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service.ts b/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service.ts index e9c18d8..cca5e5a 100644 --- a/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service.ts +++ b/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service.ts @@ -1,15 +1,14 @@ import { CreateResult, NormedOk, NormedErr, UpdatePayload, UpdateResult, Normalized, UpdateChanges } from "src/time-and-attendance/utils/type.utils"; -import { toDateFromString, toHHmmFromString, toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time.utils"; +import { overlaps, toDateFromString, toHHmmFromString, toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time.utils"; import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common"; import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service"; +import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto"; import { PrismaService } from "src/prisma/prisma.service"; import { shift_select } from "src/time-and-attendance/utils/selects.utils"; import { GetShiftDto } from "../dtos/shift-get.dto"; import { ShiftDto } from "../dtos/shift-create.dto"; -import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto"; -const overlaps = (a: { start: Date; end: Date }, b: { start: Date; end: Date }) => - !(a.end <= b.start || a.start >= b.end); + @Injectable() export class ShiftsUpsertService { diff --git a/src/time-and-attendance/utils/constants.utils.ts b/src/time-and-attendance/utils/constants.utils.ts index 10318ce..c02df87 100644 --- a/src/time-and-attendance/utils/constants.utils.ts +++ b/src/time-and-attendance/utils/constants.utils.ts @@ -1,12 +1,12 @@ -const NUMBER_OF_TIMESHEETS_TO_RETURN = 2; -const DAILY_LIMIT_HOURS = 8; -const WEEKLY_LIMIT_HOURS = 40; -const PAY_PERIOD_ANCHOR = 2023-12-17; -const ANCHOR_ISO = '2023-12-17'; // ancre date -const PERIOD_DAYS = 14; -const PERIODS_PER_YEAR = 26; -const MS_PER_DAY = 86_400_000; +export const NUMBER_OF_TIMESHEETS_TO_RETURN = 2; +export const DAILY_LIMIT_HOURS = 8; +export const WEEKLY_LIMIT_HOURS = 40; +export const PAY_PERIOD_ANCHOR = 2023-12-17; +export const ANCHOR_ISO = '2023-12-17'; // ancre date +export const PERIOD_DAYS = 14; +export const PERIODS_PER_YEAR = 26; +export const MS_PER_DAY = 86_400_000; //REGEX CONSTANTS -const DATE_ISO_FORMAT = /^\d{4}-\d{2}-\d{2}$/; -const HH_MM_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/; +export const DATE_ISO_FORMAT = /^\d{4}-\d{2}-\d{2}$/; +export const HH_MM_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/; diff --git a/src/time-and-attendance/utils/date-time.utils.ts b/src/time-and-attendance/utils/date-time.utils.ts index 4373fe7..a37dc24 100644 --- a/src/time-and-attendance/utils/date-time.utils.ts +++ b/src/time-and-attendance/utils/date-time.utils.ts @@ -83,4 +83,7 @@ export function computePeriod(pay_year: number, period_no: number, anchorISO = A //list of all 26 periods for a full year export function listPayYear(pay_year: number, anchorISO = ANCHOR_ISO) { return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(pay_year, i + 1, anchorISO)); -} \ No newline at end of file +} + +export const overlaps = (a: { start: Date; end: Date }, b: { start: Date; end: Date }) => + !(a.end <= b.start || a.start >= b.end); \ No newline at end of file diff --git a/src/time-and-attendance/utils/type.utils.ts b/src/time-and-attendance/utils/type.utils.ts index e25b41c..0903b87 100644 --- a/src/time-and-attendance/utils/type.utils.ts +++ b/src/time-and-attendance/utils/type.utils.ts @@ -62,3 +62,5 @@ export type ApplyResult = { } export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>; + +export type UpsertAction = 'create' | 'update' | 'delete'; \ No newline at end of file From 1385777122780730eac8419aa15614ad8cd08829 Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Thu, 30 Oct 2025 12:22:12 -0400 Subject: [PATCH 3/4] refactor(presets): modified routes name and switch Param(email) to use employee_id instead. ajusted these methods to use employee_id accordingly --- .../controller/schedule-presets.controller.ts | 30 +++++++++---------- .../schedule-presets-apply.service.ts | 11 ++----- .../schedule-presets-query.service.ts | 13 ++------ 3 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller.ts index 965e43b..bd05224 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller.ts @@ -1,11 +1,9 @@ -import { Controller, Put, Param, Query, Body, Get, Post, BadRequestException, ParseIntPipe, Delete, Patch } from "@nestjs/common"; +import { Controller, Param, Query, Body, Get, Post, BadRequestException, ParseIntPipe, Delete, Patch } from "@nestjs/common"; import { SchedulePresetsCommandService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-command.service"; import { SchedulePresetsApplyService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service"; import { SchedulePresetsQueryService } from "src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service"; -import { SchedulePresetsDto } from "src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-presets.dto"; import { SchedulePresetsUpdateDto } from "src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/update-schedule-presets.dto"; - - +import { SchedulePresetsDto } from "src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-presets.dto"; @Controller('schedule-presets') export class SchedulePresetsController { @@ -16,7 +14,7 @@ export class SchedulePresetsController { ){} //used to create a schedule preset - @Post(':employee_id') + @Post('create/:employee_id') async createPreset( @Param('employee_id', ParseIntPipe) employee_id: number, @Body() dto: SchedulePresetsDto, @@ -25,7 +23,7 @@ export class SchedulePresetsController { } //used to update an already existing schedule preset - @Patch(':preset_id') + @Patch('update/:preset_id') async updatePreset( @Param('preset_id', ParseIntPipe) preset_id: number, @Body() dto: SchedulePresetsUpdateDto, @@ -34,7 +32,7 @@ export class SchedulePresetsController { } //used to delete a schedule preset - @Delete(':preset_id') + @Delete('delete/:preset_id') async deletePreset( @Param('preset_id') preset_id: number, ) { @@ -43,21 +41,23 @@ export class SchedulePresetsController { //used to show the list of available schedule presets - @Get(':email') - async findListByEmail( - @Param('email') email: string, + @Get('find/:employee_id') + async findListById( + @Param('employee_id', ParseIntPipe) employee_id: number, ) { - return this.queryService.findSchedulePresetsByEmail(email); + return this.queryService.findSchedulePresets(employee_id); } + + //used to apply a preset to a timesheet - @Post('/apply-presets/:email') + @Post('/apply-presets/:employee_id') async applyPresets( - @Param('email') email: string, + @Param('employee_id') employee_id: number, @Query('preset') preset_name: string, - @Query('start') start_date: string, + @Query('start') start_date: string, ) { if(!preset_name?.trim()) throw new BadRequestException('Query "preset" is required'); if(!start_date?.trim()) throw new BadRequestException('Query "start" is required YYYY-MM-DD'); - return this.applyPresetsService.applyToTimesheet(email, preset_name, start_date); + return this.applyPresetsService.applyToTimesheet(employee_id, preset_name, start_date); } } \ No newline at end of file diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts index 3d7d0f1..75bdf1c 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts @@ -1,5 +1,4 @@ import { Injectable, BadRequestException, NotFoundException, ConflictException } from "@nestjs/common"; -import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils"; import { Weekday, Prisma } from "@prisma/client"; import { DATE_ISO_FORMAT } from "src/time-and-attendance/utils/constants.utils"; import { PrismaService } from "src/prisma/prisma.service"; @@ -9,22 +8,16 @@ import { WEEKDAY } from "src/time-and-attendance/utils/mappers.utils"; @Injectable() export class SchedulePresetsApplyService { - constructor( - private readonly prisma: PrismaService, - private readonly emailResolver: EmailToIdResolver, - ) {} + constructor( private readonly prisma: PrismaService) {} async applyToTimesheet( - email: string, + employee_id: number, preset_name: string, start_date_iso: string, ): Promise { if(!preset_name?.trim()) throw new BadRequestException('A preset_name is required'); if(!DATE_ISO_FORMAT.test(start_date_iso)) throw new BadRequestException('start_date must be of format :YYYY-MM-DD'); - const employee_id = await this.emailResolver.findIdByEmail(email); - if(!employee_id) throw new NotFoundException(`Employee with email: ${email} not found`); - const preset = await this.prisma.schedulePresets.findFirst({ where: { employee_id, name: preset_name }, include: { diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service.ts index ec0d134..b16911c 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service.ts @@ -1,20 +1,13 @@ -import { Injectable, NotFoundException } from "@nestjs/common"; import { PresetResponse, ShiftResponse } from "src/time-and-attendance/utils/type.utils"; -import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils"; import { PrismaService } from "src/prisma/prisma.service"; +import { Injectable } from "@nestjs/common"; import { Prisma } from "@prisma/client"; @Injectable() export class SchedulePresetsQueryService { - constructor( - private readonly prisma: PrismaService, - private readonly emailResolver: EmailToIdResolver, - ){} - - async findSchedulePresetsByEmail(email:string): Promise { - const employee_id = await this.emailResolver.findIdByEmail(email); - if(!employee_id) throw new NotFoundException(`Employee with email: ${email} not found`); + constructor( private readonly prisma: PrismaService ){} + async findSchedulePresets(employee_id: number): Promise { try { const presets = await this.prisma.schedulePresets.findMany({ where: { employee_id }, From 2debd408712aab9bda3fc0f10bea1ca65684b175 Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Thu, 30 Oct 2025 12:24:24 -0400 Subject: [PATCH 4/4] fix(imports): fix constants imports --- docs/swagger/swagger-spec.json | 96 +++++++++++++++---- .../utils/leave-request.util.ts | 6 -- .../timesheet-get-overview.service.ts | 1 + .../utils/date-time.utils.ts | 2 + 4 files changed, 78 insertions(+), 27 deletions(-) diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index 4167a3f..e9c9b27 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -454,24 +454,16 @@ ] } }, - "/schedule-presets/{email}": { - "put": { - "operationId": "SchedulePresetsController_upsert", + "/schedule-presets/create/{employee_id}": { + "post": { + "operationId": "SchedulePresetsController_createPreset", "parameters": [ { - "name": "email", + "name": "employee_id", "required": true, "in": "path", "schema": { - "type": "string" - } - }, - { - "name": "action", - "required": true, - "in": "query", - "schema": { - "type": "string" + "type": "number" } } ], @@ -485,6 +477,39 @@ } } }, + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "SchedulePresets" + ] + } + }, + "/schedule-presets/update/{preset_id}": { + "patch": { + "operationId": "SchedulePresetsController_updatePreset", + "parameters": [ + { + "name": "preset_id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SchedulePresetsUpdateDto" + } + } + } + }, "responses": { "200": { "description": "" @@ -493,16 +518,18 @@ "tags": [ "SchedulePresets" ] - }, - "get": { - "operationId": "SchedulePresetsController_findListByEmail", + } + }, + "/schedule-presets/delete/{preset_id}": { + "delete": { + "operationId": "SchedulePresetsController_deletePreset", "parameters": [ { - "name": "email", + "name": "preset_id", "required": true, "in": "path", "schema": { - "type": "string" + "type": "number" } } ], @@ -516,16 +543,39 @@ ] } }, - "/schedule-presets/apply-presets/{email}": { + "/schedule-presets/find/{employee_id}": { + "get": { + "operationId": "SchedulePresetsController_findListById", + "parameters": [ + { + "name": "employee_id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "SchedulePresets" + ] + } + }, + "/schedule-presets/apply-presets/{employee_id}": { "post": { "operationId": "SchedulePresetsController_applyPresets", "parameters": [ { - "name": "email", + "name": "employee_id", "required": true, "in": "path", "schema": { - "type": "string" + "type": "number" } }, { @@ -853,6 +903,10 @@ "type": "object", "properties": {} }, + "SchedulePresetsUpdateDto": { + "type": "object", + "properties": {} + }, "ExpenseDto": { "type": "object", "properties": {} diff --git a/src/time-and-attendance/modules/leave-requests/utils/leave-request.util.ts b/src/time-and-attendance/modules/leave-requests/utils/leave-request.util.ts index 7c66afb..1fdceaa 100644 --- a/src/time-and-attendance/modules/leave-requests/utils/leave-request.util.ts +++ b/src/time-and-attendance/modules/leave-requests/utils/leave-request.util.ts @@ -2,16 +2,11 @@ import { BadRequestException, Injectable } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; import { LeaveTypes } from "@prisma/client"; -// import { toDateOnly, toStringFromDate } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers"; -import { UpsertAction } from "src/time-and-attendance/modules/shared/types/upsert-actions.types"; import { toDateFromString, toStringFromDate } from "src/time-and-attendance/utils/date-time.utils"; -// import { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service"; - @Injectable() export class LeaveRequestsUtils { constructor( private readonly prisma: PrismaService, - // private readonly shiftsService: ShiftsUpsertService, ){} async syncShift( @@ -47,7 +42,6 @@ export class LeaveRequestsUtils { include: { bank_code: true }, }); - const action: UpsertAction = existing ? 'update' : 'create'; // await this.shiftsService.upsertShifts(email, action, { // old_shift: existing diff --git a/src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-get-overview.service.ts b/src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-get-overview.service.ts index 86b9134..24aa158 100644 --- a/src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-get-overview.service.ts +++ b/src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-get-overview.service.ts @@ -1,5 +1,6 @@ import { Injectable, NotFoundException } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; +import { NUMBER_OF_TIMESHEETS_TO_RETURN } from "src/time-and-attendance/utils/constants.utils"; import { sevenDaysFrom, toStringFromDate, toHHmmFromDate, toDateFromString } from "src/time-and-attendance/utils/date-time.utils"; import { TotalExpenses, TotalHours } from "src/time-and-attendance/utils/type.utils"; diff --git a/src/time-and-attendance/utils/date-time.utils.ts b/src/time-and-attendance/utils/date-time.utils.ts index a37dc24..c43337d 100644 --- a/src/time-and-attendance/utils/date-time.utils.ts +++ b/src/time-and-attendance/utils/date-time.utils.ts @@ -1,3 +1,5 @@ +import { ANCHOR_ISO, MS_PER_DAY, PERIODS_PER_YEAR, PERIOD_DAYS } from "src/time-and-attendance/utils/constants.utils"; + //ensures the week starts from sunday export function weekStartSunday(date_local: Date): Date { const start = new Date(Date.UTC(date_local.getFullYear(), date_local.getMonth(), date_local.getDate()));