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')
updateBatch( @Body() dtos: ShiftDto[]): Promise<Result<ShiftDto[], string>>{
return this.update_delete_service.updateOneOrManyShifts(dtos);
updateBatch( @Body() dtos: ShiftDto[], @Req() req): Promise<Result<ShiftDto[], string>>{
const email = req.user?.email;
return this.update_delete_service.updateOneOrManyShifts(dtos, email);
}
@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 { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
import { Result } from "src/common/errors/result-error.factory";
import { EmployeeTimesheetResolver } from "src/time-and-attendance/utils/resolve-timesheet.utils";
@Injectable()
export class ShiftsUpdateDeleteService {
constructor(
private readonly prisma: PrismaService,
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 {
//verify if array is empty or not
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
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
const updated_shifts: ShiftDto[] = [];
@ -50,11 +56,14 @@ export class ShiftsUpdateDeleteService {
//_________________________________________________________________
// UPDATE
//_________________________________________________________________
async updateShift(dto: ShiftDto): Promise<Result<ShiftDto, string>> {
async updateShift(dto: ShiftDto, email): Promise<Result<ShiftDto, string>> {
try {
const timesheet = await this.timesheetResolver.findTimesheetIdByEmail(email, toDateFromString(dto.date));
if (!timesheet.success) return { success: false, error: ' timesheet not found' }
//finds original shift
const original = await this.prisma.shifts.findFirst({
where: { id: dto.id },
where: { id: dto.id, timesheet_id: timesheet.data.id },
select: shift_select,
});
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)},`
+ `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
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
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 } };
}
private overlapChecker = async (dto: Normalized): Promise<Result<void, string>> => {
const existing_shifts = await this.prisma.shifts.findMany({
where: { date: dto.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(
{ start: dto.start_time, end: dto.end_time, date: dto.date },
{ start: existing_start, end: existing_end, date: existing_date },
);
if (has_overlap) {
return {
success: false,
error: `SHIFT_OVERLAP`
+ `new shift: ${toStringFromHHmm(dto.start_time)}${toStringFromHHmm(dto.end_time)} `
+ `existing shift: ${toStringFromHHmm(existing.start_time)}${toStringFromHHmm(existing.end_time)} `
+ `date: ${toStringFromDate(dto.date)})`,
private overlapChecker = async (shifts: ShiftDto[]): Promise<Result<void, string>> => {
for (const shift_a of shifts) {
for(const shift_b of shifts){
if(shift_a.date === shift_b.date){
const has_overlap = overlaps(
{ start: toHHmmFromString(shift_a.start_time), end: toHHmmFromString(shift_a.end_time) },
{ start: toDateFromString(shift_b.start_time), end: toDateFromString(shift_b.end_time) },
);
if (has_overlap) {
return {
success: false,
error: `SHIFT_OVERLAP`
+ `new shift: ${shift_a.start_time}${shift_a.end_time} `
+ `existing shift: ${shift_b.start_time}${shift_b.end_time} `
+ `date: ${shift_a.date})`,
}
}
}
}
}