144 lines
7.3 KiB
TypeScript
144 lines
7.3 KiB
TypeScript
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 { Result } from "src/common/errors/result-error.factory";
|
|
import { toStringFromHHmm, toStringFromDate, toDateFromString, overlaps, toDateFromHHmm } from "src/common/utils/date-utils";
|
|
import { ShiftDto } from "src/time-and-attendance/shifts/dtos/shift-create.dto";
|
|
|
|
@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<boolean, 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: true }
|
|
} 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: normed_shift.error };
|
|
if (normed_shift.data.end_time <= normed_shift.data.start_time) return { success: false, error: `INVALID_SHIFT_TIME` };
|
|
//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` };
|
|
//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` };
|
|
}
|
|
}
|
|
|
|
//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: `INVALID_SHIFT` };
|
|
}
|
|
}
|
|
|
|
//_________________________________________________________________
|
|
// 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: 'INVALID_SHIFT' }
|
|
|
|
//TODO: validate date and time to ensure "banana" is not accepted using an if statement and a REGEX
|
|
const date = toDateFromString(dto.date);
|
|
const start_time = toDateFromHHmm(dto.start_time);
|
|
const end_time = toDateFromHHmm(dto.end_time);
|
|
|
|
return { success: true, data: { date, start_time, end_time, bank_code_id: bank_code_id.data } };
|
|
}
|
|
}
|