refactor(shifts): added date to overlap comparisons

This commit is contained in:
Matthieu Haineault 2025-11-04 15:15:04 -05:00
parent 0a3d4e2960
commit eda1f86235
3 changed files with 28 additions and 17 deletions

View File

@ -5,13 +5,11 @@ import { ShiftsUpsertService } from "src/time-and-attendance/time-tracker/shifts
import { CreateShiftResult, UpdateShiftResult } from "src/time-and-attendance/utils/type.utils"; import { CreateShiftResult, UpdateShiftResult } from "src/time-and-attendance/utils/type.utils";
import { Roles as RoleEnum } from '.prisma/client'; import { Roles as RoleEnum } from '.prisma/client';
import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { RolesAllowed } from "src/common/decorators/roles.decorators";
import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils";
@Controller('shift') @Controller('shift')
export class ShiftController { export class ShiftController {
constructor( constructor(
private readonly upsert_service: ShiftsUpsertService, private readonly upsert_service: ShiftsUpsertService,
private readonly typeResolver: BankCodesResolver,
){} ){}
@Post('create') @Post('create')

View File

@ -93,7 +93,8 @@ export class ShiftsUpsertService {
return { return {
index: index, index: index,
start: item.normed.start_time, start: item.normed.start_time,
end: item.normed.end_time end: item.normed.end_time,
date: item.normed.date,
}; };
}) })
.sort((a, b) => a.start.getTime() - b.start.getTime()); .sort((a, b) => a.start.getTime() - b.start.getTime());
@ -101,8 +102,8 @@ export class ShiftsUpsertService {
for (let j = 1; j < ordered.length; j++) { for (let j = 1; j < ordered.length; j++) {
if ( if (
overlaps( overlaps(
{ start: ordered[j - 1].start, end: ordered[j - 1].end }, { start: ordered[j - 1].start, end: ordered[j - 1].end, date: ordered[j - 1].date },
{ start: ordered[j].start, end: ordered[j].end } { start: ordered[j].start, end: ordered[j].end, date: ordered[j].date },
) )
) { ) {
const error = new ConflictException({ const error = new ConflictException({
@ -130,17 +131,17 @@ export class ShiftsUpsertService {
{ length: dtos.length }, { length: dtos.length },
() => ({ ok: false, error: new Error('uninitialized') })); () => ({ ok: false, error: new Error('uninitialized') }));
const existing_map = new Map<string, { start_time: Date; end_time: Date }[]>(); const existing_map = new Map<string, { start_time: Date; end_time: Date, date: Date }[]>();
for (const { timesheet_id, day, key } of timesheet_keys) { for (const { timesheet_id, day, key } of timesheet_keys) {
const day_date = new Date(day); const day_date = new Date(day);
const rows = await tx.shifts.findMany({ const rows = await tx.shifts.findMany({
where: { timesheet_id, date: day_date }, where: { timesheet_id, date: day_date },
select: { start_time: true, end_time: true, id: true }, select: { start_time: true, end_time: true, id: true, date: true },
}); });
existing_map.set( existing_map.set(
key, key,
rows.map((row) => ({ start_time: row.start_time, end_time: row.end_time })), rows.map((row) => ({ start_time: row.start_time, end_time: row.end_time, date: row.date })),
); );
} }
@ -157,7 +158,12 @@ export class ShiftsUpsertService {
existing = []; existing = [];
existing_map.set(map_key, existing); existing_map.set(map_key, existing);
} }
const hit = existing.find(e => overlaps({ start: e.start_time, end: e.end_time }, { start: normed.start_time, end: normed.end_time })); const hit = existing.find(exist => overlaps({
start: exist.start_time, end: exist.end_time, date: exist.date
}, {
start: normed.start_time, end: normed.end_time, date:normed.date
})
);
if (hit) { if (hit) {
results[index] = { results[index] = {
ok: false, ok: false,
@ -167,7 +173,7 @@ export class ShiftsUpsertService {
conflicts: [{ conflicts: [{
start_time: toStringFromHHmm(hit.start_time), start_time: toStringFromHHmm(hit.start_time),
end_time: toStringFromHHmm(hit.end_time), end_time: toStringFromHHmm(hit.end_time),
type: 'UNKNOWN', date: toStringFromDate(hit.date),
}], }],
}), }),
}; };
@ -192,13 +198,15 @@ export class ShiftsUpsertService {
for (const { key } of timesheet_keys) { for (const { key } of timesheet_keys) {
existing.push({ existing.push({
start_time: normalizeHHmm(row.start_time), start_time: normalizeHHmm(row.start_time),
end_time: normalizeHHmm(row.end_time) end_time: normalizeHHmm(row.end_time),
date: toDateFromString(row.date),
}); });
existing_map.set( existing_map.set(
key, key,
existing.map(row => ({ existing.map(row => ({
start_time: normalizeHHmm(row.start_time), start_time: normalizeHHmm(row.start_time),
end_time: normalizeHHmm(row.end_time), end_time: normalizeHHmm(row.end_time),
date: toDateFromString(row.date),
})), })),
); );
} }
@ -291,7 +299,7 @@ export class ShiftsUpsertService {
return { update, exist_shift, normed }; return { update, exist_shift, normed };
}); });
const groups = new Map<string, { existing: { start: Date; end: Date; id: number }[], incoming: typeof planned_updates }>(); const groups = new Map<string, { existing: { start: Date; end: Date; id: number; date: Date; }[], incoming: typeof planned_updates }>();
function key(timesheet: number, d: Date) { function key(timesheet: number, d: Date) {
const day_date = new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()); const day_date = new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
return `${timesheet}|${day_date.getTime()}`; return `${timesheet}|${day_date.getTime()}`;
@ -307,9 +315,14 @@ export class ShiftsUpsertService {
const day_date = new Date(group.date.getUTCFullYear(), group.date.getUTCMonth(), group.date.getUTCDate()); const day_date = new Date(group.date.getUTCFullYear(), group.date.getUTCMonth(), group.date.getUTCDate());
const existing = await tx.shifts.findMany({ const existing = await tx.shifts.findMany({
where: { timesheet_id: group.timesheet_id, date: day_date }, where: { timesheet_id: group.timesheet_id, date: day_date },
select: { id: true, start_time: true, end_time: true }, select: { id: true, start_time: true, end_time: true, date: true },
}); });
groups.set(key(group.timesheet_id, day_date), { existing: existing.map(row => ({ id: row.id, start: row.start_time, end: row.end_time })), incoming: planned_updates }); groups.set(key(group.timesheet_id, day_date), { existing: existing.map(row => ({
id: row.id,
start: row.start_time,
end: row.end_time,
date: row.date,
})), incoming: planned_updates });
} }
for (const planned of planned_updates) { for (const planned of planned_updates) {
@ -317,7 +330,7 @@ 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 }, { 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 })
); );
if (conflict) { if (conflict) {
return updates.map(exist => return updates.map(exist =>

View File

@ -88,8 +88,8 @@ export function listPayYear(pay_year: number, anchorISO = ANCHOR_ISO) {
return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(pay_year, i + 1, anchorISO)); return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(pay_year, i + 1, anchorISO));
} }
export const overlaps = (a: { start: Date; end: Date }, b: { start: Date; end: Date }) => export const overlaps = (a: { start: Date; end: Date, date?: Date; }, b: { start: Date; end: Date; date?: Date; }) =>
!(a.end <= b.start || a.start >= b.end); ((a.date === b.date) && !(a.end <= b.start || a.start >= b.end));
export const hhmmFromLocal = (d: Date) => export const hhmmFromLocal = (d: Date) =>