targo-backend/src/time-and-attendance/time-tracker/shifts/services/shifts-create.service.ts
2025-11-13 15:23:17 -05:00

161 lines
8.2 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 { Normalized } from "src/time-and-attendance/utils/type.utils";
import { Injectable } from "@nestjs/common";
import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { PrismaService } from "src/prisma/prisma.service";
import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
import { Result } from "src/common/errors/result-error.factory";
import { toStringFromHHmm, toStringFromDate, toDateFromString, overlaps, toHHmmFromString } from "src/common/utils/date-utils";
@Injectable()
export class ShiftsCreateService {
constructor(
private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver,
private readonly typeResolver: BankCodesResolver,
) { }
//_________________________________________________________________
// CREATE WRAPPER FUNCTION FOR ONE OR MANY INPUT
//_________________________________________________________________
async createOneOrManyShifts(email: string, shifts: ShiftDto[]): 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' };
//verify if email is valid or not
const employee_id = await this.emailResolver.findIdByEmail(email);
if (!employee_id.success) return { success: false, error: employee_id.error };
//calls the create functions and await the return of successfull result or not
const results = await Promise.allSettled(shifts.map(shift => this.createShift(employee_id.data, shift)));
//return arrays of created shifts or errors
const created_shifts: ShiftDto[] = [];
const errors: string[] = [];
//filters results into created_shifts or errors arrays depending on the return from "allSettled" Promise
for (const result of results) {
if (result.status === 'fulfilled') {
if (result.value.success) {
created_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 created and returns an array of errors if needed
if (created_shifts.length === 0) return { success: false, error: errors.join(' | ') || 'No shift created' };
// returns array of created shifts
return { success: true, data: created_shifts }
} catch (error) {
return { success: false, error }
}
}
//_________________________________________________________________
// CREATE
//_________________________________________________________________
async createShift(employee_id: number, dto: ShiftDto): Promise<Result<ShiftDto, string>> {
try {
//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)}.`
}
//fetch the right timesheet
const timesheet = await this.prisma.timesheets.findUnique({
where: { id: dto.timesheet_id, employee_id },
select: timesheet_select,
});
if (!timesheet) return {
success: false,
error: `INVALID_TIMESHEET -`
+ `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_id = await this.typeResolver.findBankCodeIDByType(dto.type);
if (!bank_code_id.success) return { success: false, error: bank_code_id.error };
//fetchs existing shifts from DB to check for overlaps
const existing_shifts = await this.prisma.shifts.findMany({
where: { timesheet_id: timesheet.id, date: normed_shift.data.date },
select: { id: true, date: true, start_time: true, end_time: true },
});
for (const existing of existing_shifts) {
const existing_start = await toDateFromString(existing.start_time);
const existing_end = await toDateFromString(existing.end_time);
const existing_date = await toDateFromString(existing.date);
const has_overlap = overlaps(
{ start: normed_shift.data.start_time, end: normed_shift.data.end_time, date: normed_shift.data.date },
{ start: existing_start, end: existing_end, date: existing_date },
);
if (has_overlap) {
return {
success: false,
error: `SHIFT_OVERLAP`
+ `new shift: ${toStringFromHHmm(normed_shift.data.start_time)}${toStringFromHHmm(normed_shift.data.end_time)} `
+ `existing shift: ${toStringFromHHmm(existing.start_time)}${toStringFromHHmm(existing.end_time)} `
+ `date: ${toStringFromDate(normed_shift.data.date)})`,
}
}
}
//sends data for creation of a shift in db
const created_shift = await this.prisma.shifts.create({
data: {
timesheet_id: timesheet.id,
bank_code_id: bank_code_id.data,
date: normed_shift.data.date,
start_time: normed_shift.data.start_time,
end_time: normed_shift.data.end_time,
is_approved: dto.is_approved,
is_remote: dto.is_remote,
comment: dto.comment ?? '',
},
});
//builds an object to return for display in the frontend
const shift: ShiftDto = {
id: created_shift.id,
timesheet_id: timesheet.id,
type: dto.type,
date: toStringFromDate(created_shift.date),
start_time: toStringFromHHmm(created_shift.start_time),
end_time: toStringFromHHmm(created_shift.end_time),
is_approved: created_shift.is_approved,
is_remote: created_shift.is_remote,
comment: created_shift.comment ?? '',
}
return { success: true, data: shift };
} catch (error) {
return { success: false, error: `An error occured during creation, invalid data` };
}
}
//_________________________________________________________________
// LOCAL HELPERS
//_________________________________________________________________
//converts all string hours and date to Date and HHmm formats
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'}
const date = toDateFromString(dto.date);
const start_time = toHHmmFromString(dto.start_time);
const end_time = toHHmmFromString(dto.end_time);
return { success: true, data: {date, start_time, end_time, bank_code_id: bank_code_id.data} };
}
}