refactor(shifts): changed to conflictException build for error management for the create function

This commit is contained in:
Matthieu Haineault 2025-11-04 15:59:03 -05:00
parent eda1f86235
commit 95f369fcbc

View File

@ -39,12 +39,15 @@ export class ShiftsUpsertService {
try { try {
const normed = await this.normalizeShiftDto(dto); const normed = await this.normalizeShiftDto(dto);
if (normed.end_time <= normed.start_time) { if (normed.end_time <= normed.start_time) {
return { const error = new ConflictException({
index, error_code: 'SHIFT_OVERLAP',
error: new BadRequestException( conflicts: [{
`end_time must be greater than start_time (index ${index})` start_time: toStringFromHHmm(normed.start_time),
), end_time: toStringFromHHmm(normed.end_time),
}; date: toStringFromDate(normed.date),
}],
});
return { index, error };
} }
const timesheet = await this.prisma.timesheets.findUnique({ const timesheet = await this.prisma.timesheets.findUnique({
@ -52,7 +55,15 @@ export class ShiftsUpsertService {
select: timesheet_select, select: timesheet_select,
}); });
if (!timesheet) { if (!timesheet) {
return { index, error: new NotFoundException(`Timesheet not found`) }; const error = new ConflictException({
error_code: 'INVALID_TIMESHEET',
conflicts: [{
start_time: toStringFromHHmm(normed.start_time),
end_time: toStringFromHHmm(normed.end_time),
date: toStringFromDate(normed.date),
}],
});
return { index, error };
} }
return { return {
@ -107,21 +118,17 @@ export class ShiftsUpsertService {
) )
) { ) {
const error = new ConflictException({ const error = new ConflictException({
error_code: 'SHIFT_OVERLAP_BATCH', error_code: 'SHIFT_OVERLAP',
message: 'New shift overlaps with another shift in the same batch (same day).', conflicts: [{
start_time: toStringFromHHmm(ordered[j].start),
end_time: toStringFromHHmm(ordered[j].end),
date: toStringFromDate(ordered[j].date),
}],
}); });
return dtos.map((_dto, key) => return dtos.map((_dto, key) =>
indices.includes(key) indices.includes(key)
? ({ ? ({ ok: false, error } as CreateShiftResult)
ok: false, : ({ ok: false, error }),
error
} as CreateShiftResult)
: ({
ok: false,
error: new BadRequestException(
'Batch aborted due to overlaps in another date group'
),
}),
); );
} }
} }
@ -139,10 +146,7 @@ export class ShiftsUpsertService {
where: { timesheet_id, date: day_date }, where: { timesheet_id, date: day_date },
select: { start_time: true, end_time: true, id: true, date: true }, select: { start_time: true, end_time: true, id: true, date: true },
}); });
existing_map.set( existing_map.set( key, rows.map((row) => ({ start_time: row.start_time, end_time: row.end_time, date: row.date })));
key,
rows.map((row) => ({ start_time: row.start_time, end_time: row.end_time, date: row.date })),
);
} }
normed_shifts.forEach((x, i) => { normed_shifts.forEach((x, i) => {
@ -158,18 +162,13 @@ export class ShiftsUpsertService {
existing = []; existing = [];
existing_map.set(map_key, existing); existing_map.set(map_key, existing);
} }
const hit = existing.find(exist => overlaps({ const hit = existing.find(exist => overlaps({ start: exist.start_time, end: exist.end_time, date: exist.date },
start: exist.start_time, end: exist.end_time, date: exist.date { start: normed.start_time, end: normed.end_time, date:normed.date}));
}, {
start: normed.start_time, end: normed.end_time, date:normed.date
})
);
if (hit) { if (hit) {
results[index] = { results[index] = {
ok: false, ok: false,
error: new ConflictException({ error: new ConflictException({
error_code: 'SHIFT_OVERLAP', error_code: 'SHIFT_OVERLAP',
message: 'New shift overlaps with existing shift(s)',
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),
@ -248,9 +247,7 @@ export class ShiftsUpsertService {
const updates: UpdateShiftPayload[] = await Promise.all(dtos.map((item) => { const updates: UpdateShiftPayload[] = await Promise.all(dtos.map((item) => {
const { id, ...rest } = item; const { id, ...rest } = item;
if (!Number.isInteger(id)) { if (!Number.isInteger(id)) throw new ConflictException({ error_code: 'INVALID_SHIFT'});
throw new BadRequestException('Update shift payload is missing a valid id');
}
const changes: UpdateShiftChanges = {}; const changes: UpdateShiftChanges = {};
if (rest.date !== undefined) changes.date = rest.date; if (rest.date !== undefined) changes.date = rest.date;
@ -338,8 +335,11 @@ export class ShiftsUpsertService {
? ({ ? ({
ok: false, id: exist.id, error: new ConflictException({ ok: false, id: exist.id, error: new ConflictException({
error_code: 'SHIFT_OVERLAP', error_code: 'SHIFT_OVERLAP',
message: 'New shift overlaps with existing shift(s)', conflicts: [{
conflicts: [{ start_time: toStringFromHHmm(conflict.start), end_time: toStringFromHHmm(conflict.end), type: 'UNKNOWN' }], start_time: toStringFromHHmm(conflict.start),
end_time: toStringFromHHmm(conflict.end),
date: toStringFromDate(conflict.date),
}],
}) })
} as UpdateShiftResult) } as UpdateShiftResult)
: ({ ok: false, id: exist.id, error: new BadRequestException('Batch aborted due to overlap in another update') }) : ({ ok: false, id: exist.id, error: new BadRequestException('Batch aborted due to overlap in another update') })
@ -347,17 +347,34 @@ export class ShiftsUpsertService {
} }
} }
const regoup_by_day = new Map<string, { id: number; start: Date; end: Date }[]>(); const regoup_by_day = new Map<string, { id: number; start: Date; end: Date; date: Date }[]>();
for (const planned of planned_updates) { for (const planned of planned_updates) {
const keys = key(planned.exist_shift.timesheet_id, planned.normed.date); const keys = key(planned.exist_shift.timesheet_id, planned.normed.date);
if (!regoup_by_day.has(keys)) regoup_by_day.set(keys, []); if (!regoup_by_day.has(keys)) regoup_by_day.set(keys, []);
regoup_by_day.get(keys)!.push({ id: planned.exist_shift.id, start: planned.normed.start_time, end: planned.normed.end_time }); regoup_by_day.get(keys)!.push({
id: planned.exist_shift.id,
start: planned.normed.start_time,
end: planned.normed.end_time,
date: planned.normed.date
});
} }
for (const arr of regoup_by_day.values()) { for (const arr of regoup_by_day.values()) {
arr.sort((a, b) => a.start.getTime() - b.start.getTime()); arr.sort((a, b) => a.start.getTime() - b.start.getTime());
for (let i = 1; i < arr.length; i++) { for (let i = 1; i < arr.length; i++) {
if (overlaps({ start: arr[i - 1].start, end: arr[i - 1].end }, { start: arr[i].start, end: arr[i].end })) { if (overlaps(
const error = new ConflictException({ error_code: 'SHIFT_OVERLAP_BATCH', message: 'Overlaps between updates within the same day.' }); { start: arr[i - 1].start, end: arr[i - 1].end, date: arr[i - 1].date },
{ start: arr[i].start, end: arr[i].end, date: arr[i].date })
) {
const error = new ConflictException({
error_code: 'SHIFT_OVERLAP',
conflicts: [{
start_time: toStringFromHHmm(arr[i].start),
end_time: toStringFromHHmm(arr[i].end),
date: toStringFromDate(arr[i].date),
}],
});
return updates.map(exist => ({ ok: false, id: exist.id, error: error })); return updates.map(exist => ({ ok: false, id: exist.id, error: error }));
} }
} }