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 { Roles as RoleEnum } from '.prisma/client';
import { RolesAllowed } from "src/common/decorators/roles.decorators";
import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils";
@Controller('shift')
export class ShiftController {
constructor(
private readonly upsert_service: ShiftsUpsertService,
private readonly typeResolver: BankCodesResolver,
){}
@Post('create')

View File

@ -93,7 +93,8 @@ export class ShiftsUpsertService {
return {
index: index,
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());
@ -101,8 +102,8 @@ export class ShiftsUpsertService {
for (let j = 1; j < ordered.length; j++) {
if (
overlaps(
{ start: ordered[j - 1].start, end: ordered[j - 1].end },
{ start: ordered[j].start, end: ordered[j].end }
{ start: ordered[j - 1].start, end: ordered[j - 1].end, date: ordered[j - 1].date },
{ start: ordered[j].start, end: ordered[j].end, date: ordered[j].date },
)
) {
const error = new ConflictException({
@ -130,17 +131,17 @@ export class ShiftsUpsertService {
{ length: dtos.length },
() => ({ 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) {
const day_date = new Date(day);
const rows = await tx.shifts.findMany({
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(
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_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) {
results[index] = {
ok: false,
@ -167,7 +173,7 @@ export class ShiftsUpsertService {
conflicts: [{
start_time: toStringFromHHmm(hit.start_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) {
existing.push({
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(
key,
existing.map(row => ({
start_time: normalizeHHmm(row.start_time),
end_time: normalizeHHmm(row.end_time),
date: toDateFromString(row.date),
})),
);
}
@ -291,7 +299,7 @@ export class ShiftsUpsertService {
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) {
const day_date = new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
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 existing = await tx.shifts.findMany({
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) {
@ -317,7 +330,7 @@ export class ShiftsUpsertService {
const group = groups.get(keys)!;
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) {
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));
}
export const overlaps = (a: { start: Date; end: Date }, b: { start: Date; end: Date }) =>
!(a.end <= b.start || a.start >= b.end);
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));
export const hhmmFromLocal = (d: Date) =>