fix(shifts): fix a problem with overlaps

This commit is contained in:
Matthieu Haineault 2025-11-04 16:42:14 -05:00
parent 95f369fcbc
commit 407f04ac0b
2 changed files with 22 additions and 28 deletions

View File

@ -1,14 +1,14 @@
import { CreateShiftResult, NormedOk, NormedErr, UpdateShiftResult, UpdateShiftPayload, UpdateShiftChanges, Normalized } from "src/time-and-attendance/utils/type.utils"; import { CreateShiftResult, NormedOk, UpdateShiftResult, UpdateShiftPayload, UpdateShiftChanges, Normalized } from "src/time-and-attendance/utils/type.utils";
import { overlaps, toStringFromHHmm, toStringFromDate, toDateFromString, toHHmmFromString, weekStartSunday } from "src/time-and-attendance/utils/date-time.utils"; import { overlaps, toStringFromHHmm, toStringFromDate, toDateFromString, toHHmmFromString } from "src/time-and-attendance/utils/date-time.utils";
import { Injectable, BadRequestException, ConflictException, NotFoundException } from "@nestjs/common"; import { Injectable, BadRequestException, ConflictException, NotFoundException } from "@nestjs/common";
import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service";
import { PrismaService } from "src/prisma/prisma.service";
import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils";
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, timesheet_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"; 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";
import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service";
import { UpdateShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-update.dto";
import { PrismaService } from "src/prisma/prisma.service";
import { GetShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-get.dto";
import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
@ -49,6 +49,8 @@ export class ShiftsUpsertService {
}); });
return { index, error }; return { index, error };
} }
if(!normed.end_time) throw new BadRequestException('A shift needs an end_time');
if(!normed.start_time) throw new BadRequestException('A shift needs a start_time');
const timesheet = await this.prisma.timesheets.findUnique({ const timesheet = await this.prisma.timesheets.findUnique({
where: { id: dto.timesheet_id, employee_id }, where: { id: dto.timesheet_id, employee_id },
@ -191,24 +193,15 @@ export class ShiftsUpsertService {
}, },
select: shift_select, select: shift_select,
}); });
const normalizeHHmm = (value: Date) => toHHmmFromString(toStringFromHHmm(value)); const normalizeHHmm = (value: Date) => toHHmmFromString(toStringFromHHmm(value));
const normalized_row = {
for (const { key } of timesheet_keys) { start_time: normalizeHHmm(row.start_time),
existing.push({ end_time: normalizeHHmm(row.end_time),
start_time: normalizeHHmm(row.start_time), date: toDateFromString(row.date),
end_time: normalizeHHmm(row.end_time), };
date: toDateFromString(row.date), existing.push(normalized_row);
}); existing_map.set(map_key, existing);
existing_map.set(
key,
existing.map(row => ({
start_time: normalizeHHmm(row.start_time),
end_time: normalizeHHmm(row.end_time),
date: toDateFromString(row.date),
})),
);
}
const { type: bank_type } = await this.typeResolver.findTypeByBankCodeId(row.bank_code_id); const { type: bank_type } = await this.typeResolver.findTypeByBankCodeId(row.bank_code_id);
const summary = await this.overtime.getWeekOvertimeSummary(timesheet_id, normed.date, tx); const summary = await this.overtime.getWeekOvertimeSummary(timesheet_id, normed.date, tx);
@ -327,7 +320,8 @@ export class ShiftsUpsertService {
const group = groups.get(keys)!; const group = groups.get(keys)!;
const conflict = group.existing.find(row => const conflict = group.existing.find(row =>
row.id !== planned.exist_shift.id && overlaps({ start: row.start, end: row.end, date: row.date }, { start: planned.normed.start_time, end: planned.normed.end_time }) row.id !== planned.exist_shift.id && overlaps({ start: row.start, end: row.end, date: row.date },
{ start: planned.normed.start_time, end: planned.normed.end_time, date: planned.normed.date })
); );
if (conflict) { if (conflict) {
return updates.map(exist => return updates.map(exist =>
@ -432,7 +426,7 @@ export class ShiftsUpsertService {
where: { id: shift_id }, where: { id: shift_id },
select: { id: true, date: true, timesheet_id: true }, select: { id: true, date: true, timesheet_id: true },
}); });
if (!shift) throw new NotFoundException(`Shift with id #${shift_id} not found`); if (!shift) throw new ConflictException({ error_code: 'INVALID_SHIFT'});
await tx.shifts.delete({ where: { id: shift_id } }); await tx.shifts.delete({ where: { id: shift_id } });

View File

@ -89,7 +89,7 @@ export function listPayYear(pay_year: number, anchorISO = ANCHOR_ISO) {
} }
export const overlaps = (a: { start: Date; end: Date, date?: Date; }, b: { start: Date; end: Date; date?: Date; }) => export const overlaps = (a: { start: Date; end: Date, date?: Date; }, b: { start: Date; end: Date; date?: Date; }) =>
((a.date === b.date) && !(a.end <= b.start || a.start >= b.end)); ((a.date?.getTime() === b.date?.getTime()) && !(a.end <= b.start || a.start >= b.end));
export const hhmmFromLocal = (d: Date) => export const hhmmFromLocal = (d: Date) =>