diff --git a/src/modules/shifts/helpers/shifts-date-time-helpers.ts b/src/modules/shifts/helpers/shifts-date-time-helpers.ts index b00cf7b..c899dc8 100644 --- a/src/modules/shifts/helpers/shifts-date-time-helpers.ts +++ b/src/modules/shifts/helpers/shifts-date-time-helpers.ts @@ -1,23 +1,27 @@ -export function timeFromHHMMUTC(hhmm: string): Date { - const [hour, min] = hhmm.split(':').map(Number); - return new Date(Date.UTC(1970,0,1,hour, min,0)); +export function timeFromHHMM(hhmm: string): Date { + const [hour, min] = hhmm.split(':').map(Number); + return new Date(1970, 0, 1, hour, min, 0, 0); } -export function weekStartSundayUTC(d: Date): Date { - const day = d.getUTCDay(); - const start = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())); - start.setUTCDate(start.getUTCDate()- day); - return start; +export function weekStartSunday(d: Date): Date { + const start = new Date(d.getFullYear(), d.getMonth(), d.getDate()); + const day = start.getDay(); // 0 = dimanche + start.setDate(start.getDate() - day); + start.setHours(0, 0, 0, 0); + return start; } -export function toDateOnlyUTC(input: string | Date): Date { - const date = new Date(input); - return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())); +export function toDateOnly(input: string | Date): Date { + const base = (typeof input === 'string') ? new Date(input) : new Date(input); + const y = (typeof input === 'string') ? Number(input.slice(0,4)) : base.getFullYear(); + const m = (typeof input === 'string') ? Number(input.slice(5,7)) - 1 : base.getMonth(); + const d = (typeof input === 'string') ? Number(input.slice(8,10)) : base.getDate(); + return new Date(y, m, d, 0, 0, 0, 0); } export function formatHHmm(time: Date): string { - const hh = String(time.getUTCHours()).padStart(2,'0'); - const mm = String(time.getUTCMinutes()).padStart(2,'0'); - return `${hh}:${mm}`; + const hh = String(time.getHours()).padStart(2,'0'); + const mm = String(time.getMinutes()).padStart(2,'0'); + return `${hh}:${mm}`; } diff --git a/src/modules/shifts/services/shifts-command.service.ts b/src/modules/shifts/services/shifts-command.service.ts index a47e628..fc65119 100644 --- a/src/modules/shifts/services/shifts-command.service.ts +++ b/src/modules/shifts/services/shifts-command.service.ts @@ -1,5 +1,4 @@ import { BadRequestException, ConflictException, Injectable, Logger, NotFoundException, UnprocessableEntityException } from "@nestjs/common"; -import { formatHHmm, toDateOnlyUTC, weekStartSundayUTC } from "../helpers/shifts-date-time-helpers"; import { normalizeShiftPayload, overlaps } from "../utils/shifts.utils"; import { DayShiftResponse, UpsertAction } from "../types-and-interfaces/shifts-upsert.types"; import { EmployeeIdEmailResolver } from "src/modules/shared/utils/resolve-email-id.utils"; @@ -9,6 +8,7 @@ import { UpsertShiftDto } from "../dtos/upsert-shift.dto"; import { BaseApprovalService } from "src/common/shared/base-approval.service"; import { PrismaService } from "src/prisma/prisma.service"; import { OvertimeService } from "src/modules/business-logics/services/overtime.service"; +import { formatHHmm, toDateOnly, weekStartSunday } from "../helpers/shifts-date-time-helpers"; @Injectable() export class ShiftsCommandService extends BaseApprovalService { @@ -49,11 +49,11 @@ export class ShiftsCommandService extends BaseApprovalService { throw new BadRequestException('At least one of old or new shift must be provided'); } - const date_only = toDateOnlyUTC(date_string); + const date_only = toDateOnly(date_string); const employee_id = await this.emailResolver.findIdByEmail(email); return this.prisma.$transaction(async (tx) => { - const start_of_week = weekStartSundayUTC(date_only); + const start_of_week = weekStartSunday(date_only); const timesheet = await tx.timesheets.upsert({ where: { employee_id_start_date: { employee_id, start_date: start_of_week } }, @@ -65,16 +65,16 @@ export class ShiftsCommandService extends BaseApprovalService { //validation/sanitation //resolve bank_code_id using type const old_norm_shift = old_shift ? await normalizeShiftPayload(old_shift) : undefined; - // if (old_norm_shift && old_norm_shift.end_time.getTime() <= old_norm_shift.start_time.getTime()) { - // throw new UnprocessableEntityException(' old_shift.end_time must be > old_shift.start_time'); - // } + if (old_norm_shift && old_norm_shift.end_time.getTime() <= old_norm_shift.start_time.getTime()) { + throw new UnprocessableEntityException(' old_shift.end_time must be > old_shift.start_time'); + } const old_bank_code_id: number | undefined = old_norm_shift ? (await this.bankTypeResolver.findByType(old_norm_shift.type, tx))?.id : undefined; const new_norm_shift = new_shift ? await normalizeShiftPayload(new_shift) : undefined; - // if (new_norm_shift && new_norm_shift.end_time.getTime() <= new_norm_shift.start_time.getTime()) { - // throw new UnprocessableEntityException(' new_shift.end_time must be > new_shift.start_time'); - // } + if (new_norm_shift && new_norm_shift.end_time.getTime() <= new_norm_shift.start_time.getTime()) { + throw new UnprocessableEntityException(' new_shift.end_time must be > new_shift.start_time'); + } const new_bank_code_id: number | undefined = new_norm_shift ? (await this.bankTypeResolver.findByType(new_norm_shift.type, tx))?.id : undefined; @@ -105,28 +105,28 @@ export class ShiftsCommandService extends BaseApprovalService { }); }; - // //checks for overlaping shifts - // const assertNoOverlap = (exclude_shift_id?: number)=> { - // if (!new_norm_shift) return; - // const overlap_with = day_shifts.filter((shift)=> { - // if(exclude_shift_id && shift.id === exclude_shift_id) return false; - // return overlaps( - // new_norm_shift.start_time.getTime(), - // new_norm_shift.end_time.getTime(), - // shift.start_time.getTime(), - // shift.end_time.getTime(), - // ); - // }); + //checks for overlaping shifts + const assertNoOverlap = (exclude_shift_id?: number)=> { + if (!new_norm_shift) return; + const overlap_with = day_shifts.filter((shift)=> { + if(exclude_shift_id && shift.id === exclude_shift_id) return false; + return overlaps( + new_norm_shift.start_time.getTime(), + new_norm_shift.end_time.getTime(), + shift.start_time.getTime(), + shift.end_time.getTime(), + ); + }); - // if(overlap_with.length > 0) { - // const conflicts = overlap_with.map((shift)=> ({ - // start_time: formatHHmm(shift.start_time), - // end_time: formatHHmm(shift.end_time), - // type: shift.bank_code?.type ?? 'UNKNOWN', - // })); - // throw new ConflictException({ error_code: 'SHIFT_OVERLAP', message: 'New shift overlaps with existing shift(s)', conflicts}); - // } - // }; + if(overlap_with.length > 0) { + const conflicts = overlap_with.map((shift)=> ({ + start_time: formatHHmm(shift.start_time), + end_time: formatHHmm(shift.end_time), + type: shift.bank_code?.type ?? 'UNKNOWN', + })); + throw new ConflictException({ error_code: 'SHIFT_OVERLAP', message: 'New shift overlaps with existing shift(s)', conflicts}); + } + }; let action: UpsertAction; //_____________________________________________________________________________________________ // DELETE @@ -148,7 +148,7 @@ export class ShiftsCommandService extends BaseApprovalService { //_____________________________________________________________________________________________ else if (!old_shift && new_shift) { if (new_bank_code_id === undefined) throw new NotFoundException(`bank code not found for new_shift.type: ${new_norm_shift?.type ?? ''}`); - // assertNoOverlap(); + assertNoOverlap(); await tx.shifts.create({ data: { timesheet_id: timesheet.id, @@ -170,7 +170,7 @@ export class ShiftsCommandService extends BaseApprovalService { if (new_bank_code_id === undefined) throw new NotFoundException(`bank code not found for new_shift.type: ${new_norm_shift?.type ?? ''}`); const existing = await findExactOldShift(); if(!existing) throw new NotFoundException({ error_code: 'SHIFT_STALE', message: 'The shift was modified or deleted by someone else'}); - // assertNoOverlap(existing.id); + assertNoOverlap(existing.id); await tx.shifts.update({ where: { diff --git a/src/modules/shifts/utils/shifts.utils.ts b/src/modules/shifts/utils/shifts.utils.ts index 7988388..2ec24c2 100644 --- a/src/modules/shifts/utils/shifts.utils.ts +++ b/src/modules/shifts/utils/shifts.utils.ts @@ -1,6 +1,6 @@ import { NotFoundException } from "@nestjs/common"; import { ShiftPayloadDto } from "../dtos/upsert-shift.dto"; -import { timeFromHHMMUTC } from "../helpers/shifts-date-time-helpers"; +import { timeFromHHMM } from "../helpers/shifts-date-time-helpers"; export function overlaps( a_start_ms: number, @@ -24,8 +24,8 @@ export function resolveBankCodeByType(type: string): Promise { export function normalizeShiftPayload(payload: ShiftPayloadDto) { //normalize shift's infos - const start_time = payload.start_time; - const end_time = payload.end_time; + const start_time = timeFromHHMM(payload.start_time); + const end_time = timeFromHHMM(payload.end_time ); const type = (payload.type || '').trim().toUpperCase(); const is_remote = payload.is_remote === true; const is_approved = payload.is_approved === false; @@ -34,5 +34,5 @@ export function resolveBankCodeByType(type: string): Promise { const trimmed = typeof raw_comment === 'string' ? raw_comment.trim() : null; const comment = trimmed && trimmed.length > 0 ? trimmed: null; - return { start_time, end_time, type, is_remote, comment, is_approved }; + return { start_time, end_time, type, is_remote, is_approved, comment }; } \ No newline at end of file