diff --git a/src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service.ts b/src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service.ts index a9212b8..6594cdb 100644 --- a/src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service.ts +++ b/src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service.ts @@ -39,12 +39,15 @@ export class ShiftsUpsertService { try { const normed = await this.normalizeShiftDto(dto); if (normed.end_time <= normed.start_time) { - return { - index, - error: new BadRequestException( - `end_time must be greater than start_time (index ${index})` - ), - }; + const error = new ConflictException({ + error_code: 'SHIFT_OVERLAP', + conflicts: [{ + 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({ @@ -52,7 +55,15 @@ export class ShiftsUpsertService { select: timesheet_select, }); 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 { @@ -107,21 +118,17 @@ export class ShiftsUpsertService { ) ) { const error = new ConflictException({ - error_code: 'SHIFT_OVERLAP_BATCH', - message: 'New shift overlaps with another shift in the same batch (same day).', + error_code: 'SHIFT_OVERLAP', + conflicts: [{ + start_time: toStringFromHHmm(ordered[j].start), + end_time: toStringFromHHmm(ordered[j].end), + date: toStringFromDate(ordered[j].date), + }], }); return dtos.map((_dto, key) => indices.includes(key) - ? ({ - ok: false, - error - } as CreateShiftResult) - : ({ - ok: false, - error: new BadRequestException( - 'Batch aborted due to overlaps in another date group' - ), - }), + ? ({ ok: false, error } as CreateShiftResult) + : ({ ok: false, error }), ); } } @@ -139,10 +146,7 @@ export class ShiftsUpsertService { where: { timesheet_id, date: day_date }, 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, date: row.date })), - ); + existing_map.set( key, rows.map((row) => ({ start_time: row.start_time, end_time: row.end_time, date: row.date }))); } normed_shifts.forEach((x, i) => { @@ -158,18 +162,13 @@ export class ShiftsUpsertService { existing = []; existing_map.set(map_key, existing); } - 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 - }) - ); + 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, error: new ConflictException({ error_code: 'SHIFT_OVERLAP', - message: 'New shift overlaps with existing shift(s)', conflicts: [{ start_time: toStringFromHHmm(hit.start_time), end_time: toStringFromHHmm(hit.end_time), @@ -248,9 +247,7 @@ export class ShiftsUpsertService { const updates: UpdateShiftPayload[] = await Promise.all(dtos.map((item) => { const { id, ...rest } = item; - if (!Number.isInteger(id)) { - throw new BadRequestException('Update shift payload is missing a valid id'); - } + if (!Number.isInteger(id)) throw new ConflictException({ error_code: 'INVALID_SHIFT'}); const changes: UpdateShiftChanges = {}; if (rest.date !== undefined) changes.date = rest.date; @@ -338,8 +335,11 @@ export class ShiftsUpsertService { ? ({ ok: false, id: exist.id, error: new ConflictException({ error_code: 'SHIFT_OVERLAP', - message: 'New shift overlaps with existing shift(s)', - conflicts: [{ start_time: toStringFromHHmm(conflict.start), end_time: toStringFromHHmm(conflict.end), type: 'UNKNOWN' }], + conflicts: [{ + start_time: toStringFromHHmm(conflict.start), + end_time: toStringFromHHmm(conflict.end), + date: toStringFromDate(conflict.date), + }], }) } as UpdateShiftResult) : ({ 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(); + const regoup_by_day = new Map(); for (const planned of planned_updates) { const keys = key(planned.exist_shift.timesheet_id, planned.normed.date); 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()) { arr.sort((a, b) => a.start.getTime() - b.start.getTime()); 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 })) { - const error = new ConflictException({ error_code: 'SHIFT_OVERLAP_BATCH', message: 'Overlaps between updates within the same day.' }); + if (overlaps( + { 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 })); } }