import { Injectable } from "@nestjs/common"; import { Weekday, Prisma } from "@prisma/client"; import { DATE_ISO_FORMAT, WEEKDAY } from "src/common/utils/constants.utils"; import { PrismaService } from "src/prisma/prisma.service"; import { ApplyResult } from "src/time-and-attendance/utils/type.utils"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { Result } from "src/common/errors/result-error.factory"; @Injectable() export class SchedulePresetsApplyService { constructor( private readonly prisma: PrismaService, private readonly emailResolver: EmailToIdResolver ) { } async applyToTimesheet(email: string, id: number, start_date_iso: string): Promise> { if (!DATE_ISO_FORMAT.test(start_date_iso)) return { success: false, error: 'INVALID_PRESET' }; const employee_id = await this.emailResolver.findIdByEmail(email); if (!employee_id.success) return { success: false, error: employee_id.error } const preset = await this.prisma.schedulePresets.findFirst({ where: { employee_id: employee_id.data, id }, include: { shifts: { orderBy: [{ week_day: 'asc' }, { sort_order: 'asc' }], select: { id: true, week_day: true, sort_order: true, start_time: true, end_time: true, is_remote: true, bank_code_id: true, }, }, }, }); if (!preset) return { success: false, error: `PRESET_NOT_FOUND` }; const start_date = new Date(`${start_date_iso}T00:00:00.000Z`); const timesheet = await this.prisma.timesheets.upsert({ where: { employee_id_start_date: { employee_id: employee_id.data, start_date: start_date } }, update: {}, create: { employee_id: employee_id.data, start_date: start_date }, select: { id: true }, }); //index shifts by weekday const index_by_day = new Map(); for (const shift of preset.shifts) { const list = index_by_day.get(shift.week_day) ?? []; list.push(shift); index_by_day.set(shift.week_day, list); } const addDays = (date: Date, days: number) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days)); const overlaps = (aStart: Date, aEnd: Date, bStart: Date, bEnd: Date) => aStart.getTime() < bEnd.getTime() && aEnd.getTime() > bStart.getTime(); let created = 0; let skipped = 0; await this.prisma.$transaction(async (tx) => { for (let i = 0; i < 7; i++) { const date = addDays(start_date, i); const week_day = WEEKDAY[date.getUTCDay()]; const shifts = index_by_day.get(week_day) ?? []; if (shifts.length === 0) continue; const existing = await tx.shifts.findMany({ where: { timesheet_id: timesheet.id, date: date }, orderBy: { start_time: 'asc' }, select: { start_time: true, end_time: true, bank_code_id: true, is_remote: true, comment: true, }, }); const payload: Prisma.ShiftsCreateManyInput[] = []; for (const shift of shifts) { if (shift.end_time.getTime() <= shift.start_time.getTime()) { return { success: false, error: `INVALID_PRESET_SHIFT` }; } const conflict = existing.find((existe) => overlaps( shift.start_time, shift.end_time, existe.start_time, existe.end_time, )); if (conflict) return { success: false, error: `OVERLAPING_SHIFT` }; payload.push({ timesheet_id: timesheet.id, date: date, start_time: shift.start_time, end_time: shift.end_time, is_remote: shift.is_remote, comment: null, bank_code_id: shift.bank_code_id, }); } if (payload.length) { const response = await tx.shifts.createMany({ data: payload, skipDuplicates: true }); created += response.count; skipped += payload.length - response.count; } } }); return { success: true, data: { timesheet_id: timesheet.id, created, skipped } }; } }