From 6adb614931ac7d36a2e395c43f4da26f01d6864b Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Tue, 4 Nov 2025 08:31:38 -0500 Subject: [PATCH] refactor(shifts): modified return and switched bank_code_id for types --- docs/swagger/swagger-spec.json | 14 ++++ src/common/shared/base-approval.service.ts | 12 +-- .../holiday-leave-requests.service.ts | 2 +- .../services/leave-request.service.ts | 2 +- .../services/sick-leave-requests.service.ts | 2 +- .../vacation-leave-requests.service.ts | 2 +- .../pay-period/pay-periods.module.ts | 4 +- .../services/pay-periods-command.service.ts | 8 +- .../time-and-attendance.module.ts | 6 +- .../schedule-presets-upsert.service.ts | 2 +- .../shifts/controllers/shift.controller.ts | 6 +- .../shifts/dtos/shift-create.dto.ts | 2 +- .../time-tracker/shifts/dtos/shift-get.dto.ts | 8 +- .../shifts/services/shifts-get.service.ts | 2 +- .../shifts/services/shifts-upsert.service.ts | 52 ++++++------ .../controllers/timesheet.controller.ts | 20 ++++- .../services/timesheet-approval.service.ts | 51 ++++++++---- .../services/timesheet-archive.service.ts | 82 +++++++++---------- .../timesheets/timesheets.module.ts | 7 +- .../utils/resolve-bank-type-id.utils.ts | 23 +++++- .../utils/selects.utils.ts | 12 +++ src/time-and-attendance/utils/type.utils.ts | 2 +- 22 files changed, 203 insertions(+), 118 deletions(-) diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index 4e6ef5a..4aad9e8 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -279,6 +279,20 @@ ] } }, + "/timesheets/timesheet-approval": { + "patch": { + "operationId": "TimesheetController_approveTimesheet", + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Timesheet" + ] + } + }, "/preferences": { "patch": { "operationId": "PreferencesController_updatePreferences", diff --git a/src/common/shared/base-approval.service.ts b/src/common/shared/base-approval.service.ts index e3660e1..ab44a0d 100644 --- a/src/common/shared/base-approval.service.ts +++ b/src/common/shared/base-approval.service.ts @@ -18,14 +18,14 @@ export abstract class BaseApprovalService { //returns the corresponding Prisma delegate protected abstract get delegate(): UpdatableDelegate; - protected abstract delegateFor(transaction: Prisma.TransactionClient): UpdatableDelegate; + protected abstract delegateFor(tx: Prisma.TransactionClient): UpdatableDelegate; //standard update Aproval - async updateApproval(id: number, isApproved: boolean): Promise { + async updateApproval(id: number, is_approved: boolean): Promise { try{ return await this.delegate.update({ where: { id }, - data: { is_approved: isApproved }, + data: { is_approved: is_approved }, }); }catch (error: any) { if (error instanceof PrismaClientKnownRequestError && error.code === "P2025") { @@ -36,11 +36,11 @@ export abstract class BaseApprovalService { } //approval with transaction to avoid many requests - async updateApprovalWithTransaction(transaction: Prisma.TransactionClient, id: number, isApproved: boolean): Promise { + async updateApprovalWithTransaction(tx: Prisma.TransactionClient, id: number, is_approved: boolean): Promise { try { - return await this.delegateFor(transaction).update({ + return await this.delegateFor(tx).update({ where: { id }, - data: { is_approved: isApproved }, + data: { is_approved: is_approved }, }); } catch (error: any ){ if(error instanceof PrismaClientKnownRequestError && error.code === 'P2025') { diff --git a/src/time-and-attendance/leave-requests/services/holiday-leave-requests.service.ts b/src/time-and-attendance/leave-requests/services/holiday-leave-requests.service.ts index ce4807b..cf18c2e 100644 --- a/src/time-and-attendance/leave-requests/services/holiday-leave-requests.service.ts +++ b/src/time-and-attendance/leave-requests/services/holiday-leave-requests.service.ts @@ -26,7 +26,7 @@ export class HolidayLeaveRequestsService { async create(dto: UpsertLeaveRequestDto): Promise { const email = dto.email.trim(); const employee_id = await this.emailResolver.findIdByEmail(email); - const bank_code = await this.typeResolver.findByType(LeaveTypes.HOLIDAY); + const bank_code = await this.typeResolver.findIdAndModifierByType(LeaveTypes.HOLIDAY); const dates = normalizeDates(dto.dates); if (!bank_code) throw new NotFoundException(`bank_code not found`); if (!dates.length) throw new BadRequestException('Dates array must not be empty'); diff --git a/src/time-and-attendance/leave-requests/services/leave-request.service.ts b/src/time-and-attendance/leave-requests/services/leave-request.service.ts index 499c2d8..8b9d7d9 100644 --- a/src/time-and-attendance/leave-requests/services/leave-request.service.ts +++ b/src/time-and-attendance/leave-requests/services/leave-request.service.ts @@ -95,7 +95,7 @@ export class LeaveRequestsService { async update(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise { const email = dto.email.trim(); const employee_id = await this.emailResolver.findIdByEmail(email); - const bank_code = await this.typeResolver.findByType(type); + const bank_code = await this.typeResolver.findIdAndModifierByType(type); if(!bank_code) throw new NotFoundException(`bank_code not found`); const modifier = Number(bank_code.modifier ?? 1); const dates = normalizeDates(dto.dates); diff --git a/src/time-and-attendance/leave-requests/services/sick-leave-requests.service.ts b/src/time-and-attendance/leave-requests/services/sick-leave-requests.service.ts index 1cff7a8..0b35f88 100644 --- a/src/time-and-attendance/leave-requests/services/sick-leave-requests.service.ts +++ b/src/time-and-attendance/leave-requests/services/sick-leave-requests.service.ts @@ -25,7 +25,7 @@ export class SickLeaveRequestsService { async create(dto: UpsertLeaveRequestDto): Promise { const email = dto.email.trim(); const employee_id = await this.emailResolver.findIdByEmail(email); - const bank_code = await this.typeResolver.findByType(LeaveTypes.SICK); + const bank_code = await this.typeResolver.findIdAndModifierByType(LeaveTypes.SICK); if(!bank_code) throw new NotFoundException(`bank_code not found`); const modifier = bank_code.modifier ?? 1; diff --git a/src/time-and-attendance/leave-requests/services/vacation-leave-requests.service.ts b/src/time-and-attendance/leave-requests/services/vacation-leave-requests.service.ts index 2e71c39..63e0077 100644 --- a/src/time-and-attendance/leave-requests/services/vacation-leave-requests.service.ts +++ b/src/time-and-attendance/leave-requests/services/vacation-leave-requests.service.ts @@ -24,7 +24,7 @@ export class VacationLeaveRequestsService { async create(dto: UpsertLeaveRequestDto): Promise { const email = dto.email.trim(); const employee_id = await this.emailResolver.findIdByEmail(email); - const bank_code = await this.typeResolver.findByType(LeaveTypes.VACATION); + const bank_code = await this.typeResolver.findIdAndModifierByType(LeaveTypes.VACATION); if(!bank_code) throw new NotFoundException(`bank_code not found`); const modifier = bank_code.modifier ?? 1; diff --git a/src/time-and-attendance/pay-period/pay-periods.module.ts b/src/time-and-attendance/pay-period/pay-periods.module.ts index f971084..aa49f23 100644 --- a/src/time-and-attendance/pay-period/pay-periods.module.ts +++ b/src/time-and-attendance/pay-period/pay-periods.module.ts @@ -4,7 +4,6 @@ import { Module } from "@nestjs/common"; import { PayPeriodsCommandService } from "src/time-and-attendance/pay-period/services/pay-periods-command.service"; import { TimesheetsModule } from "src/time-and-attendance/time-tracker/timesheets/timesheets.module"; import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; -import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service"; @Module({ imports:[TimesheetsModule], @@ -13,8 +12,7 @@ import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/t PayPeriodsQueryService, PayPeriodsCommandService, EmailToIdResolver, - TimesheetApprovalService, ], }) -export class PayperiodsModule {} \ No newline at end of file +export class PayperiodsModule {} diff --git a/src/time-and-attendance/pay-period/services/pay-periods-command.service.ts b/src/time-and-attendance/pay-period/services/pay-periods-command.service.ts index 6c48fe2..ede4caa 100644 --- a/src/time-and-attendance/pay-period/services/pay-periods-command.service.ts +++ b/src/time-and-attendance/pay-period/services/pay-periods-command.service.ts @@ -8,7 +8,7 @@ import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/t export class PayPeriodsCommandService { constructor( private readonly prisma: PrismaService, - private readonly timesheets_approval: TimesheetApprovalService, + private readonly timesheetsApproval: TimesheetApprovalService, private readonly query: PayPeriodsQueryService, ) {} @@ -49,7 +49,7 @@ export class PayPeriodsCommandService { for(const item of items) { const { period_start, period_end } = await getPeriod(item.pay_year, item.period_no); - const t_sheets = await transaction.timesheets.findMany({ + const timesheets = await transaction.timesheets.findMany({ where: { employee: { user: { email: item.employee_email } }, OR: [ @@ -60,8 +60,8 @@ export class PayPeriodsCommandService { select: { id: true }, }); - for(const { id } of t_sheets) { - await this.timesheets_approval.cascadeApprovalWithtx(transaction, id, item.approve); + for(const { id } of timesheets) { + await this.timesheetsApproval.cascadeApprovalWithtx(transaction, id, item.approve); updated++; } diff --git a/src/time-and-attendance/time-and-attendance.module.ts b/src/time-and-attendance/time-and-attendance.module.ts index 1d052df..5d03b59 100644 --- a/src/time-and-attendance/time-and-attendance.module.ts +++ b/src/time-and-attendance/time-and-attendance.module.ts @@ -12,7 +12,9 @@ import { ShiftController } from "src/time-and-attendance/time-tracker/shifts/con import { ShiftsGetService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-get.service"; import { ShiftsUpsertService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service"; import { TimesheetController } from "src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller"; +import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service"; import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service"; +import { TimesheetsModule } from "src/time-and-attendance/time-tracker/timesheets/timesheets.module"; import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils"; import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils"; @@ -20,6 +22,7 @@ import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-i imports: [ BusinessLogicsModule, PayperiodsModule, + TimesheetsModule, ], controllers: [ TimesheetController, @@ -37,6 +40,7 @@ import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-i SchedulePresetsApplyService, EmailToIdResolver, BankCodesResolver, + TimesheetApprovalService, ], - exports: [], + exports: [TimesheetApprovalService ], }) export class TimeAndAttendanceModule { }; \ No newline at end of file diff --git a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts index 0eaef09..cb1938e 100644 --- a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts +++ b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts @@ -171,7 +171,7 @@ export class SchedulePresetsUpsertService { const bank_code_set = new Map(); for (const type of types) { - const { id } = await this.typeResolver.findByType(type); + const { id } = await this.typeResolver.findIdAndModifierByType(type); bank_code_set.set(type, id) } const toTime = (hhmm: string) => new Date(`1970-01-01T${hhmm}:00.000Z`); diff --git a/src/time-and-attendance/time-tracker/shifts/controllers/shift.controller.ts b/src/time-and-attendance/time-tracker/shifts/controllers/shift.controller.ts index 767191b..29e15fb 100644 --- a/src/time-and-attendance/time-tracker/shifts/controllers/shift.controller.ts +++ b/src/time-and-attendance/time-tracker/shifts/controllers/shift.controller.ts @@ -5,10 +5,14 @@ import { ShiftsUpsertService } from "src/time-and-attendance/time-tracker/shifts import { CreateShiftResult, UpdateShiftResult } from "src/time-and-attendance/utils/type.utils"; import { Roles as RoleEnum } from '.prisma/client'; import { RolesAllowed } from "src/common/decorators/roles.decorators"; +import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils"; @Controller('shift') export class ShiftController { - constructor( private readonly upsert_service: ShiftsUpsertService ){} + constructor( + private readonly upsert_service: ShiftsUpsertService, + private readonly typeResolver: BankCodesResolver, + ){} @Post('create') @RolesAllowed(RoleEnum.EMPLOYEE, RoleEnum.ACCOUNTING, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ADMIN) diff --git a/src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto.ts b/src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto.ts index 9393b04..c762b0f 100644 --- a/src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto.ts +++ b/src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto.ts @@ -3,7 +3,7 @@ import { IsBoolean, IsInt, IsOptional, IsString, MaxLength } from "class-validat export class ShiftDto { @IsInt() @IsOptional() id: number; @IsInt() timesheet_id!: number; - @IsInt() bank_code_id!: number; + @IsString() type!: string; @IsString() date!: string; @IsString() start_time!: string; diff --git a/src/time-and-attendance/time-tracker/shifts/dtos/shift-get.dto.ts b/src/time-and-attendance/time-tracker/shifts/dtos/shift-get.dto.ts index 21b479f..969ca98 100644 --- a/src/time-and-attendance/time-tracker/shifts/dtos/shift-get.dto.ts +++ b/src/time-and-attendance/time-tracker/shifts/dtos/shift-get.dto.ts @@ -1,10 +1,10 @@ export class GetShiftDto { timesheet_id: number; - bank_code_id: number; - date: string; + type: string; + date: string; start_time: string; - end_time: string; - is_remote: boolean; + end_time: string; + is_remote: boolean; is_approved: boolean; comment?: string; } diff --git a/src/time-and-attendance/time-tracker/shifts/services/shifts-get.service.ts b/src/time-and-attendance/time-tracker/shifts/services/shifts-get.service.ts index d1eed96..fb3dc48 100644 --- a/src/time-and-attendance/time-tracker/shifts/services/shifts-get.service.ts +++ b/src/time-and-attendance/time-tracker/shifts/services/shifts-get.service.ts @@ -44,7 +44,7 @@ export class ShiftsGetService { const shift = row_by_id.get(id)!; return { timesheet_id: shift.timesheet_id, - bank_code_id: shift.bank_code_id, + type: shift.bank_code.type, date: toStringFromDate(shift.date), start_time: toStringFromHHmm(shift.start_time), end_time: toStringFromHHmm(shift.end_time), diff --git a/src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service.ts b/src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service.ts index 59dc811..f2c5bd0 100644 --- a/src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service.ts +++ b/src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service.ts @@ -7,7 +7,8 @@ import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-i import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto"; import { GetShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-get.dto"; import { UpdateShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-update.dto"; -import { shift_select } from "src/time-and-attendance/utils/selects.utils"; +import { shift_select, timesheet_select } from "src/time-and-attendance/utils/selects.utils"; +import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils"; @@ -17,6 +18,7 @@ export class ShiftsUpsertService { private readonly prisma: PrismaService, private readonly overtime: OvertimeService, private readonly emailResolver: EmailToIdResolver, + private readonly typeResolver: BankCodesResolver, ) { } //_________________________________________________________________ @@ -35,7 +37,7 @@ export class ShiftsUpsertService { const normed_shifts = await Promise.all( dtos.map(async (dto, index) => { try { - const normed = this.normalizeShiftDto(dto); + const normed = await this.normalizeShiftDto(dto); if (normed.end_time <= normed.start_time) { return { index, @@ -45,18 +47,12 @@ export class ShiftsUpsertService { }; } - const start_date = weekStartSunday(normed.date); - - const timesheet = await this.prisma.timesheets.findFirst({ - where: { start_date, employee_id }, - select: { id: true }, + const timesheet = await this.prisma.timesheets.findUnique({ + where: { id: dto.timesheet_id, employee_id }, + select: timesheet_select, }); if (!timesheet) { - return { - index, - error: new NotFoundException(`Timesheet not found`), - }; - + return { index, error: new NotFoundException(`Timesheet not found`)}; } return { @@ -181,7 +177,7 @@ export class ShiftsUpsertService { const row = await tx.shifts.create({ data: { timesheet_id: timesheet_id, - bank_code_id: dto.bank_code_id, + bank_code_id: normed.id, date: normed.date, start_time: normed.start_time, end_time: normed.end_time, @@ -194,10 +190,12 @@ export class ShiftsUpsertService { existing.push({ start_time: row.start_time, end_time: row.end_time }); existing_map.set(map_key, existing); + const {type: bank_type} = await this.typeResolver.findTypeByBankCodeId(row.bank_code_id); const summary = await this.overtime.getWeekOvertimeSummary(timesheet_id, normed.date, tx); + const shift: GetShiftDto = { timesheet_id: timesheet_id, - bank_code_id: row.bank_code_id, + type: bank_type, date: toStringFromDate(row.date), start_time: toStringFromHHmm(row.start_time), end_time: toStringFromHHmm(row.end_time), @@ -227,7 +225,7 @@ export class ShiftsUpsertService { async updateShifts(dtos: UpdateShiftDto[]): Promise { if (!Array.isArray(dtos) || dtos.length === 0) return []; - const updates: UpdateShiftPayload[] = dtos.map((item) => { + const updates: UpdateShiftPayload[] = await Promise.all(dtos.map((item) => { const { id, ...rest } = item; if (!Number.isInteger(id)) { throw new BadRequestException('Update shift payload is missing a valid id'); @@ -237,12 +235,12 @@ export class ShiftsUpsertService { if (rest.date !== undefined) changes.date = rest.date; if (rest.start_time !== undefined) changes.start_time = rest.start_time; if (rest.end_time !== undefined) changes.end_time = rest.end_time; - if (rest.bank_code_id !== undefined) changes.bank_code_id = rest.bank_code_id; + if (rest.type !== undefined) changes.type = rest.type; if (rest.is_remote !== undefined) changes.is_remote = rest.is_remote; if (rest.comment !== undefined) changes.comment = rest.comment; return { id, dto: changes }; - }); + })); return this.prisma.$transaction(async (tx) => { const shift_ids = updates.map(update_shift => update_shift.id); @@ -266,7 +264,7 @@ export class ShiftsUpsertService { } } - const planned_updates = updates.map(update => { + const planned_updates = updates.map( update => { const exist_shift = regroup_id.get(update.id)!; const date_string = update.dto.date ?? toStringFromDate(exist_shift.date); const start_string = update.dto.start_time ?? toStringFromHHmm(exist_shift.start_time); @@ -275,6 +273,7 @@ export class ShiftsUpsertService { date: toDateFromString(date_string), start_time: toHHmmFromString(start_string), end_time: toHHmmFromString(end_string), + id: exist_shift.id, }; return { update, exist_shift, normed }; }); @@ -342,12 +341,12 @@ export class ShiftsUpsertService { for (const planned of planned_updates) { const data: any = {}; const { dto } = planned.update; - if (dto.date !== undefined) data.date = planned.normed.date; + if (dto.date !== undefined) data.date = planned.normed.date; if (dto.start_time !== undefined) data.start_time = planned.normed.start_time; - if (dto.end_time !== undefined) data.end_time = planned.normed.end_time; - if (dto.bank_code_id !== undefined) data.bank_code_id = dto.bank_code_id; - if (dto.is_remote !== undefined) data.is_remote = dto.is_remote; - if (dto.comment !== undefined) data.comment = dto.comment ?? null; + if (dto.end_time !== undefined) data.end_time = planned.normed.end_time; + if (dto.type !== undefined) data.type = dto.type; + if (dto.is_remote !== undefined) data.is_remote = dto.is_remote; + if (dto.comment !== undefined) data.comment = dto.comment ?? null; const row = await tx.shifts.update({ where: { id: planned.exist_shift.id }, @@ -362,7 +361,7 @@ export class ShiftsUpsertService { const shift: GetShiftDto = { timesheet_id: row.timesheet_id, - bank_code_id: row.bank_code_id, + type: data.type, date: toStringFromDate(row.date), start_time: toStringFromHHmm(row.start_time), end_time: toStringFromHHmm(row.end_time), @@ -406,10 +405,11 @@ export class ShiftsUpsertService { // LOCAL HELPERS //_________________________________________________________________ //converts all string hours and date to Date and HHmm formats - private normalizeShiftDto = (dto: ShiftDto): Normalized => { + private normalizeShiftDto = async (dto: ShiftDto): Promise => { + const { id: bank_code_id} = await this.typeResolver.findBankCodeIDByType(dto.type); const date = toDateFromString(dto.date); const start_time = toHHmmFromString(dto.start_time); const end_time = toHHmmFromString(dto.end_time); - return { date, start_time, end_time }; + return { date, start_time, end_time, id: bank_code_id }; } } diff --git a/src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller.ts b/src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller.ts index ffde38b..849a121 100644 --- a/src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller.ts +++ b/src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller.ts @@ -1,19 +1,33 @@ -import { Controller, Get, ParseIntPipe, Query, Req, UnauthorizedException} from "@nestjs/common"; +import { Body, Controller, Get, ParseBoolPipe, ParseIntPipe, Patch, Query, Req, UnauthorizedException} from "@nestjs/common"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service"; import { Roles as RoleEnum } from '.prisma/client'; +import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service"; @Controller('timesheets') export class TimesheetController { - constructor( private readonly timesheetOverview: GetTimesheetsOverviewService ){} + constructor( + private readonly timesheetOverview: GetTimesheetsOverviewService, + private readonly approvalService: TimesheetApprovalService, + ){} @Get() @RolesAllowed(RoleEnum.SUPERVISOR, RoleEnum.HR, RoleEnum.ACCOUNTING, RoleEnum.ADMIN) async getTimesheetByIds( @Req() req, @Query('year', ParseIntPipe) year:number, @Query('period_number', ParseIntPipe) period_number: number) { const email = req.user?.email; - if(!email) throw new UnauthorizedException('Unauthorized User');  + if(!email) throw new UnauthorizedException('Unauthorized User'); return this.timesheetOverview.getTimesheetsForEmployeeByPeriod(email, year, period_number); } + + @Patch('timesheet-approval') + @RolesAllowed(RoleEnum.SUPERVISOR, RoleEnum.HR, RoleEnum.ACCOUNTING, RoleEnum.ADMIN) + async approveTimesheet( + @Body('timesheet_id', ParseIntPipe) timesheet_id: number, + @Body('is_approved' , ParseBoolPipe) is_approved: boolean, + ) { + return this.approvalService.approveTimesheetById(timesheet_id, is_approved); + } } + diff --git a/src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service.ts b/src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service.ts index 84756c2..82f9c18 100644 --- a/src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service.ts +++ b/src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service.ts @@ -1,11 +1,15 @@ import { BaseApprovalService } from "src/common/shared/base-approval.service"; import { Prisma, Timesheets } from "@prisma/client"; import { PrismaService } from "src/prisma/prisma.service"; -import { Injectable } from "@nestjs/common"; +import { Injectable, NotFoundException } from "@nestjs/common"; +import { timesheet_select } from "src/time-and-attendance/utils/selects.utils"; @Injectable() export class TimesheetApprovalService extends BaseApprovalService{ - constructor(prisma: PrismaService){super(prisma)} + constructor( + prisma: PrismaService, + ){super(prisma)} + //_____________________________________________________________________________________________ // APPROVAL AND DELEGATE METHODS //_____________________________________________________________________________________________ @@ -13,26 +17,43 @@ import { Injectable } from "@nestjs/common"; return this.prisma.timesheets; } - protected delegateFor(transaction: Prisma.TransactionClient) { - return transaction.timesheets; + protected delegateFor(tx: Prisma.TransactionClient) { + return tx.timesheets; } - async updateApproval(id: number, isApproved: boolean): Promise { - return this.prisma.$transaction((transaction) => - this.updateApprovalWithTransaction(transaction, id, isApproved), + async updateApproval(id: number, is_approved: boolean): Promise { + return this.prisma.$transaction((tx) => + this.updateApprovalWithTransaction(tx, id, is_approved), ); } - async cascadeApprovalWithtx(transaction: Prisma.TransactionClient, timesheetId: number, isApproved: boolean): Promise { - const timesheet = await this.updateApprovalWithTransaction(transaction, timesheetId, isApproved); - await transaction.shifts.updateMany({ - where: { timesheet_id: timesheetId }, - data: { is_approved: isApproved }, + async cascadeApprovalWithtx(tx: Prisma.TransactionClient, timesheet_id: number, is_approved: boolean): Promise { + const timesheet = await this.updateApprovalWithTransaction(tx, timesheet_id, is_approved); + await tx.shifts.updateMany({ + where: { timesheet_id: timesheet_id }, + data: { is_approved: is_approved }, }); - await transaction.expenses.updateManyAndReturn({ - where: { timesheet_id: timesheetId }, - data: { is_approved: isApproved }, + await tx.expenses.updateManyAndReturn({ + where: { timesheet_id: timesheet_id }, + data: { is_approved: is_approved }, }); return timesheet; } + + async approveTimesheetById( timesheet_id: number, is_approved: boolean){ + return this.prisma.$transaction(async (tx) => { + const timesheet = await tx.timesheets.findUnique({ + where: { id: timesheet_id }, + select: { id: true }, + }); + if(!timesheet) throw new NotFoundException(`Timesheet with id: ${timesheet_id} not found`); + + await this.cascadeApprovalWithtx(tx, timesheet_id, is_approved); + + return tx.timesheets.findUnique({ + where: { id: timesheet_id }, + select: timesheet_select, + }); + }); + } } \ No newline at end of file diff --git a/src/time-and-attendance/time-tracker/timesheets/services/timesheet-archive.service.ts b/src/time-and-attendance/time-tracker/timesheets/services/timesheet-archive.service.ts index c75bdc3..33fbf76 100644 --- a/src/time-and-attendance/time-tracker/timesheets/services/timesheet-archive.service.ts +++ b/src/time-and-attendance/time-tracker/timesheets/services/timesheet-archive.service.ts @@ -1,49 +1,49 @@ -import { TimesheetsArchive } from "@prisma/client"; -import { PrismaService } from "src/prisma/prisma.service"; +// import { TimesheetsArchive } from "@prisma/client"; +// import { PrismaService } from "src/prisma/prisma.service"; -export class TimesheetArchiveService { - constructor(private readonly prisma: PrismaService){} +// export class TimesheetArchiveService { +// constructor(private readonly prisma: PrismaService){} - async archiveOld(): Promise { - //calcul du cutoff pour archivation - const cutoff = new Date(); - cutoff.setMonth(cutoff.getMonth() - 6) +// async archiveOld(): Promise { +// //calcul du cutoff pour archivation +// const cutoff = new Date(); +// cutoff.setMonth(cutoff.getMonth() - 6) - await this.prisma.$transaction(async transaction => { - //fetches all timesheets to cutoff - const oldSheets = await transaction.timesheets.findMany({ - where: { shift: { some: { date: { lt: cutoff } } }, - }, - select: { - id: true, - employee_id: true, - is_approved: true, - }, - }); - if( oldSheets.length === 0) return; +// await this.prisma.$transaction(async transaction => { +// //fetches all timesheets to cutoff +// const oldSheets = await transaction.timesheets.findMany({ +// where: { shift: { some: { date: { lt: cutoff } } }, +// }, +// select: { +// id: true, +// employee_id: true, +// is_approved: true, +// }, +// }); +// if( oldSheets.length === 0) return; - //preping data for archivation - const archiveDate = oldSheets.map(sheet => ({ - timesheet_id: sheet.id, - employee_id: sheet.employee_id, - is_approved: sheet.is_approved, - })); +// //preping data for archivation +// const archiveDate = oldSheets.map(sheet => ({ +// timesheet_id: sheet.id, +// employee_id: sheet.employee_id, +// is_approved: sheet.is_approved, +// })); - //copying data from timesheets table to archive table - await transaction.timesheetsArchive.createMany({ data: archiveDate }); +// //copying data from timesheets table to archive table +// await transaction.timesheetsArchive.createMany({ data: archiveDate }); - //removing data from timesheets table - await transaction.timesheets.deleteMany({ where: { id: { in: oldSheets.map(s => s.id) } } }); - }); - } +// //removing data from timesheets table +// await transaction.timesheets.deleteMany({ where: { id: { in: oldSheets.map(s => s.id) } } }); +// }); +// } - //fetches all archived timesheets - async findAllArchived(): Promise { - return this.prisma.timesheetsArchive.findMany(); - } +// //fetches all archived timesheets +// async findAllArchived(): Promise { +// return this.prisma.timesheetsArchive.findMany(); +// } - //fetches an archived timesheet - async findOneArchived(id: number): Promise { - return this.prisma.timesheetsArchive.findUniqueOrThrow({ where: { id } }); - } -} \ No newline at end of file +// //fetches an archived timesheet +// async findOneArchived(id: number): Promise { +// return this.prisma.timesheetsArchive.findUniqueOrThrow({ where: { id } }); +// } +// } \ No newline at end of file diff --git a/src/time-and-attendance/time-tracker/timesheets/timesheets.module.ts b/src/time-and-attendance/time-tracker/timesheets/timesheets.module.ts index 761a78e..9e96b4d 100644 --- a/src/time-and-attendance/time-tracker/timesheets/timesheets.module.ts +++ b/src/time-and-attendance/time-tracker/timesheets/timesheets.module.ts @@ -2,19 +2,16 @@ import { Module } from '@nestjs/common'; import { TimesheetController } from 'src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller'; import { TimesheetApprovalService } from 'src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service'; -import { TimesheetArchiveService } from 'src/time-and-attendance/time-tracker/timesheets/services/timesheet-archive.service'; import { GetTimesheetsOverviewService } from 'src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service'; import { EmailToIdResolver } from 'src/time-and-attendance/utils/resolve-email-id.utils'; @Module({ - controllers: [TimesheetController], providers: [ - TimesheetArchiveService, GetTimesheetsOverviewService, - TimesheetApprovalService, EmailToIdResolver, + TimesheetApprovalService, ], - exports: [], + exports: [TimesheetApprovalService], }) export class TimesheetsModule {} diff --git a/src/time-and-attendance/utils/resolve-bank-type-id.utils.ts b/src/time-and-attendance/utils/resolve-bank-type-id.utils.ts index 039543f..017c49f 100644 --- a/src/time-and-attendance/utils/resolve-bank-type-id.utils.ts +++ b/src/time-and-attendance/utils/resolve-bank-type-id.utils.ts @@ -9,7 +9,7 @@ export class BankCodesResolver { constructor(private readonly prisma: PrismaService) {} //find id and modifier by type - readonly findByType = async ( type: string, client?: Tx + readonly findIdAndModifierByType = async ( type: string, client?: Tx ): Promise<{id:number; modifier: number }> => { const db = client ?? this.prisma; const bank = await db.bankCodes.findFirst({ @@ -20,4 +20,25 @@ export class BankCodesResolver { if(!bank) throw new NotFoundException(`Unknown bank code type: ${type}`); return { id: bank.id, modifier: bank.modifier }; }; + + //finds only id by type + readonly findBankCodeIDByType = async (type: string, client?: Tx) => { + const db = client ?? this.prisma; + const bank_code_id = await db.bankCodes.findFirst({ + where: { type }, + select: {id: true}, + }); + if(!bank_code_id) throw new NotFoundException(`Unkown bank type: ${type}`); + return bank_code_id; + } + + readonly findTypeByBankCodeId = async (bank_code_id: number, client?: Tx) => { + const db = client ?? this.prisma; + const type = await db.bankCodes.findFirst({ + where: { id: bank_code_id }, + select: { type: true }, + }); + if(!type) throw new NotFoundException(`Type with id : ${bank_code_id} not found`); + return type; + } } \ No newline at end of file diff --git a/src/time-and-attendance/utils/selects.utils.ts b/src/time-and-attendance/utils/selects.utils.ts index fd4a356..fd8476e 100644 --- a/src/time-and-attendance/utils/selects.utils.ts +++ b/src/time-and-attendance/utils/selects.utils.ts @@ -1,4 +1,5 @@ import { Prisma } from "@prisma/client"; +import { dmmfToRuntimeDataModel } from "@prisma/client/runtime/library"; export const expense_select = { id: true, @@ -17,6 +18,9 @@ export const shift_select = { id: true, timesheet_id: true, bank_code_id: true, + bank_code: { + select: { type: true }, + }, date: true, start_time: true, end_time: true, @@ -78,3 +82,11 @@ export const SHIFT_SELECT = { export const SHIFT_ASC_ORDER = [{date: 'asc' as const}, {start_time: 'asc' as const}]; +export const timesheet_select = { + id: true, + employee_id: true, + shift: true, + expense: true, + start_date: true, + is_approved: true, +} satisfies Prisma.TimesheetsSelect; \ 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 412566a..c9613bb 100644 --- a/src/time-and-attendance/utils/type.utils.ts +++ b/src/time-and-attendance/utils/type.utils.ts @@ -25,7 +25,7 @@ export type TotalExpenses = { mileage: number; }; -export type Normalized = { date: Date; start_time: Date; end_time: Date; }; +export type Normalized = { date: Date; start_time: Date; end_time: Date; id: number}; export type ShiftWithOvertimeDto = { shift: GetShiftDto;