targo-backend/src/time-and-attendance/time-tracker/shifts/services/shifts-update-delete.service.ts
2025-11-13 13:52:48 -05:00

185 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { toDateFromString, toHHmmFromString, toStringFromHHmm, toStringFromDate, overlaps, toUTCDateFromString } from "src/time-and-attendance/utils/date-time.utils";
import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils";
import { PrismaService } from "src/prisma/prisma.service";
import { shift_select } from "src/time-and-attendance/utils/selects.utils";
import { Injectable } from "@nestjs/common";
import { Normalized } from "src/time-and-attendance/utils/type.utils";
import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
import { Result } from "src/common/errors/result-error.factory";
import { EmployeeTimesheetResolver } from "src/time-and-attendance/utils/resolve-timesheet.utils";
@Injectable()
export class ShiftsUpdateDeleteService {
constructor(
private readonly prisma: PrismaService,
private readonly typeResolver: BankCodesResolver,
private readonly timesheetResolver: EmployeeTimesheetResolver,
) { }
async updateOneOrManyShifts(shifts: ShiftDto[], email: string): Promise<Result<ShiftDto[], string>> {
try {
//verify if array is empty or not
if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'No data received' };
//check for overlap inside dto objects
const overlap_check = await this.overlapChecker(shifts);
if (!overlap_check.success) return overlap_check;
//calls the update functions and await the return of successfull result or not
const results = await Promise.allSettled(shifts.map(shift => this.updateShift(shift, email)));
//return arrays of updated shifts or errors
const updated_shifts: ShiftDto[] = [];
const errors: string[] = [];
//filters results into updated_shifts or errors arrays depending on the return from "allSettled" Promise
for (const result of results) {
if (result.status === 'fulfilled') {
if (result.value.success) {
updated_shifts.push(result.value.data);
} else {
errors.push(result.value.error);
}
} else {
errors.push(result.reason instanceof Error ? result.reason.message : String(result.reason));
}
}
//verify if shifts were updated and returns an array of errors if needed
if (updated_shifts.length === 0) return { success: false, error: errors.join(' | ') || 'No shift updated' };
// returns array of updated shifts
return { success: true, data: updated_shifts }
} catch (error) {
return { success: false, error }
}
}
//_________________________________________________________________
// UPDATE
//_________________________________________________________________
async updateShift(dto: ShiftDto, email: string): Promise<Result<ShiftDto, string>> {
try {
const timesheet = await this.timesheetResolver.findTimesheetIdByEmail(email, toDateFromString(dto.date));
if (!timesheet.success) return { success: false, error: timesheet.error }
//finds original shift
const original = await this.prisma.shifts.findFirst({
where: { id: dto.id, timesheet_id: timesheet.data.id },
select: shift_select,
});
if (!original) return { success: false, error: `Shift with id: ${dto.id} not found` };
//transform string format to date and HHmm
const normed_shift = await this.normalizeShiftDto(dto);
if (!normed_shift.success) return { success: false, error: 'An error occured during normalization' }
if (normed_shift.data.end_time <= normed_shift.data.start_time) return {
success: false,
error: `INVALID_SHIFT - `
+ `start_time: ${toStringFromHHmm(normed_shift.data.start_time)},`
+ `end_time: ${toStringFromHHmm(normed_shift.data.end_time)},`
+ `date: ${toStringFromDate(normed_shift.data.date)}.`
};
//finds bank_code_id using the type
const bank_code = await this.typeResolver.findBankCodeIDByType(dto.type);
if (!bank_code.success) return { success: false, error: bank_code.error };
//updates sent to DB
const updated = await this.prisma.shifts.update({
where: { id: original.id },
data: {
date: normed_shift.data.date,
start_time: normed_shift.data.start_time,
end_time: normed_shift.data.end_time,
bank_code_id: bank_code.data,
comment: dto.comment,
is_approved: dto.is_approved,
is_remote: dto.is_remote,
},
select: shift_select,
});
if (!updated) return { success: false, error: ' An error occured during update, Invalid Datas' };
// builds an object to return for display in the frontend
const shift: ShiftDto = {
id: updated.id,
timesheet_id: updated.timesheet_id,
type: dto.type,
date: toStringFromDate(updated.date),
start_time: toStringFromHHmm(updated.start_time),
end_time: toStringFromHHmm(updated.end_time),
is_approved: updated.is_approved,
is_remote: updated.is_remote,
comment: updated.comment ?? '',
}
return { success: true, data: shift };
} catch (error) {
return { success: false, error: `An error occured during update. Invalid Data` };
}
}
//_________________________________________________________________
// DELETE
//_________________________________________________________________
//finds shifts using shit_ids
//blocs deletion if approved
async deleteShift(shift_id: number): Promise<Result<number, string>> {
try {
return await this.prisma.$transaction(async (tx) => {
const shift = await tx.shifts.findUnique({
where: { id: shift_id },
select: { id: true, date: true, timesheet_id: true },
});
if (!shift) return { success: false, error: `shift with id ${shift_id} not found ` };
await tx.shifts.delete({ where: { id: shift_id } });
return { success: true, data: shift.id };
});
} catch (error) {
return { success: false, error: `INVALID_SHIFT, shift with id ${shift_id} not found` }
}
}
//_________________________________________________________________
// helpers
//_________________________________________________________________
private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => {
const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type);
if (!bank_code_id.success) return { success: false, error: 'Bank_code not found' }
return {
success: true,
data: {
date: toDateFromString(dto.date),
start_time: toHHmmFromString(dto.start_time),
end_time: toHHmmFromString(dto.end_time),
bank_code_id: bank_code_id.data
}
};
}
private overlapChecker = async (shifts: ShiftDto[]): Promise<Result<void, string>> => {
for (let i = 0; i < shifts.length; i++) {
for (let j = i + 1; j < shifts.length; j++) {
const shift_a = shifts[i];
const shift_b = shifts[j];
if (shift_a.date !== shift_b.date) continue;
if (shift_a.id === shift_b.id) continue;
const has_overlap = overlaps(
{ start: toHHmmFromString(shift_a.start_time), end: toHHmmFromString(shift_a.end_time) },
{ start: toHHmmFromString(shift_b.start_time), end: toHHmmFromString(shift_b.end_time) },
);
if (has_overlap) {
return {
success: false,
error: `SHIFT_OVERLAP`
+ `new shift: ${shift_a.start_time}${shift_a.end_time} `
+ `existing shift: ${shift_b.start_time}${shift_b.end_time} `
+ `date: ${shift_a.date})`,
}
}
}
}
return { success: true, data: undefined }
}
}