fix(shifts): fix overlap checks for updating shifts

This commit is contained in:
Matthieu Haineault 2025-11-13 09:53:48 -05:00
parent 2003b5357d
commit 0764eebc98
2 changed files with 34 additions and 31 deletions

View File

@ -22,8 +22,9 @@ export class ShiftController {
} }
@Patch('update') @Patch('update')
updateBatch( @Body() dtos: ShiftDto[]): Promise<Result<ShiftDto[], string>>{ updateBatch( @Body() dtos: ShiftDto[], @Req() req): Promise<Result<ShiftDto[], string>>{
return this.update_delete_service.updateOneOrManyShifts(dtos); const email = req.user?.email;
return this.update_delete_service.updateOneOrManyShifts(dtos, email);
} }
@Delete(':shift_id') @Delete(':shift_id')

View File

@ -6,21 +6,27 @@ import { Injectable } from "@nestjs/common";
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/time-tracker/shifts/dtos/shift-create.dto"; import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { EmployeeTimesheetResolver } from "src/time-and-attendance/utils/resolve-timesheet.utils";
@Injectable() @Injectable()
export class ShiftsUpdateDeleteService { export class ShiftsUpdateDeleteService {
constructor( constructor(
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly typeResolver: BankCodesResolver, private readonly typeResolver: BankCodesResolver,
private readonly timesheetResolver: EmployeeTimesheetResolver,
) { } ) { }
async updateOneOrManyShifts(shifts: ShiftDto[]): Promise<Result<ShiftDto[], string>> { async updateOneOrManyShifts(shifts: ShiftDto[], email: string): Promise<Result<ShiftDto[], 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
const overlap_check = await this.overlapChecker(shifts);
if (!overlap_check.success) return overlap_check;
//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))); const results = await Promise.allSettled(shifts.map(shift => this.updateShift(shift, email)));
//return arrays of updated shifts or errors //return arrays of updated shifts or errors
const updated_shifts: ShiftDto[] = []; const updated_shifts: ShiftDto[] = [];
@ -50,11 +56,14 @@ export class ShiftsUpdateDeleteService {
//_________________________________________________________________ //_________________________________________________________________
// UPDATE // UPDATE
//_________________________________________________________________ //_________________________________________________________________
async updateShift(dto: ShiftDto): Promise<Result<ShiftDto, string>> { async updateShift(dto: ShiftDto, email): Promise<Result<ShiftDto, string>> {
try { try {
const timesheet = await this.timesheetResolver.findTimesheetIdByEmail(email, toDateFromString(dto.date));
if (!timesheet.success) return { success: false, error: ' timesheet not found' }
//finds original shift //finds original shift
const original = await this.prisma.shifts.findFirst({ const original = await this.prisma.shifts.findFirst({
where: { id: dto.id }, where: { id: dto.id, timesheet_id: timesheet.data.id },
select: shift_select, select: shift_select,
}); });
if (!original) return { success: false, error: `Shift with id: ${dto.id} not found` }; if (!original) return { success: false, error: `Shift with id: ${dto.id} not found` };
@ -69,12 +78,10 @@ export class ShiftsUpdateDeleteService {
+ `end_time: ${toStringFromHHmm(normed_shift.data.end_time)},` + `end_time: ${toStringFromHHmm(normed_shift.data.end_time)},`
+ `date: ${toStringFromDate(normed_shift.data.date)}.` + `date: ${toStringFromDate(normed_shift.data.date)}.`
}; };
const overlap_check = await this.overlapChecker(normed_shift.data);
if(!overlap_check.success) return { success: false, error: 'Invalid shift, overlaps with existing shifts'}
//finds bank_code_id using the type //finds bank_code_id using the type
const bank_code = await this.typeResolver.findBankCodeIDByType(dto.type); const bank_code = await this.typeResolver.findBankCodeIDByType(dto.type);
if (!bank_code.success) return { success: false, error: 'No bank_code_id found' }; if (!bank_code.success) return { success: false, error: bank_code.error };
//updates sent to DB //updates sent to DB
const updated = await this.prisma.shifts.update({ const updated = await this.prisma.shifts.update({
@ -147,28 +154,23 @@ export class ShiftsUpdateDeleteService {
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 } };
} }
private overlapChecker = async (dto: Normalized): Promise<Result<void, string>> => { private overlapChecker = async (shifts: ShiftDto[]): Promise<Result<void, string>> => {
for (const shift_a of shifts) {
const existing_shifts = await this.prisma.shifts.findMany({ for(const shift_b of shifts){
where: { date: dto.date }, if(shift_a.date === shift_b.date){
select: { id: true, date: true, start_time: true, end_time: true },
});
for (const existing of existing_shifts) {
const existing_start = toDateFromString(existing.start_time);
const existing_end = toDateFromString(existing.end_time);
const existing_date = toDateFromString(existing.date);
const has_overlap = overlaps( const has_overlap = overlaps(
{ start: dto.start_time, end: dto.end_time, date: dto.date }, { start: toHHmmFromString(shift_a.start_time), end: toHHmmFromString(shift_a.end_time) },
{ start: existing_start, end: existing_end, date: existing_date }, { start: toDateFromString(shift_b.start_time), end: toDateFromString(shift_b.end_time) },
); );
if (has_overlap) { if (has_overlap) {
return { return {
success: false, success: false,
error: `SHIFT_OVERLAP` error: `SHIFT_OVERLAP`
+ `new shift: ${toStringFromHHmm(dto.start_time)}${toStringFromHHmm(dto.end_time)} ` + `new shift: ${shift_a.start_time}${shift_a.end_time} `
+ `existing shift: ${toStringFromHHmm(existing.start_time)}${toStringFromHHmm(existing.end_time)} ` + `existing shift: ${shift_b.start_time}${shift_b.end_time} `
+ `date: ${toStringFromDate(dto.date)})`, + `date: ${shift_a.date})`,
}
}
} }
} }
} }