diff --git a/src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller.ts b/src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller.ts index 5e0c354..a4a7b9e 100644 --- a/src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller.ts +++ b/src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller.ts @@ -55,10 +55,10 @@ export class SchedulePresetsController { @Post('apply-presets') async applyPresets( @Req() req, - @Body('preset') preset_name: string, + @Body('preset') preset_id: number, @Body('start') start_date: string ) { const email = req.user?.email; - return this.applyPresetsService.applyToTimesheet(email, preset_name, start_date); + return this.applyPresetsService.applyToTimesheet(email, preset_id, start_date); } } \ No newline at end of file diff --git a/src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-presets.dto.ts b/src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-presets.dto.ts index fc6b7d8..fb58f69 100644 --- a/src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-presets.dto.ts +++ b/src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-presets.dto.ts @@ -1,7 +1,11 @@ -import { ArrayMinSize, IsArray, IsBoolean, IsOptional, IsString } from "class-validator"; +import { ArrayMinSize, IsArray, IsBoolean, IsInt, IsOptional, IsString } from "class-validator"; import { SchedulePresetShiftsDto } from "src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-preset-shifts.dto"; export class SchedulePresetsDto { + + @IsInt() + id!: number; + @IsString() name!: string; diff --git a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts index c055373..122c30c 100644 --- a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts +++ b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts @@ -11,18 +11,19 @@ import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-i export class SchedulePresetsApplyService { constructor( private readonly prisma: PrismaService, private readonly emailResolver: EmailToIdResolver) {} - async applyToTimesheet( email: string, preset_name: string, start_date_iso: string ): Promise { - if(!preset_name?.trim()) throw new BadRequestException('A preset_name is required'); + async applyToTimesheet( email: string, id: number, start_date_iso: string ): Promise { + if(!id) throw new BadRequestException(`Schedule preset with id: ${id} not found`); if(!DATE_ISO_FORMAT.test(start_date_iso)) throw new BadRequestException('start_date must be of format :YYYY-MM-DD'); const employee_id = await this.emailResolver.findIdByEmail(email); const preset = await this.prisma.schedulePresets.findFirst({ - where: { employee_id, name: preset_name }, + where: { employee_id, id }, include: { shifts: { orderBy: [{ week_day: 'asc'}, { sort_order: 'asc'}], select: { + id: true, week_day: true, sort_order: true, start_time: true, diff --git a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts index cb1938e..c590d57 100644 --- a/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts +++ b/src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service.ts @@ -10,127 +10,131 @@ import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-i @Injectable() export class SchedulePresetsUpsertService { constructor( - private readonly prisma: PrismaService, - private readonly typeResolver : BankCodesResolver, + private readonly prisma: PrismaService, + private readonly typeResolver: BankCodesResolver, private readonly emailResolver: EmailToIdResolver, - ){} + ) { } //_________________________________________________________________ // CREATE //_________________________________________________________________ - async createPreset( email: string, dto: SchedulePresetsDto): Promise { + async createPreset(email: string, dto: SchedulePresetsDto): Promise { try { const shifts_data = await this.resolveAndBuildPresetShifts(dto); const employee_id = await this.emailResolver.findIdByEmail(email); - if(!shifts_data) throw new BadRequestException(`Employee with email: ${email} or dto not found`); + if (!shifts_data) throw new BadRequestException(`Employee with email: ${email} or dto not found`); - await this.prisma.$transaction(async (tx)=> { - if(dto.is_default) { + await this.prisma.$transaction(async (tx) => { + if (dto.is_default) { await tx.schedulePresets.updateMany({ where: { is_default: true, employee_id }, - data: { is_default: false }, + data: { is_default: false }, }); } const created = await tx.schedulePresets.create({ data: { + id: dto.id, employee_id, - name: dto.name, + name: dto.name, is_default: !!dto.is_default, - shifts: { create: shifts_data}, + shifts: { create: shifts_data }, }, }); return created; }); return { ok: true }; - + } catch (error: unknown) { - return { ok: false, error }; + return { ok: false, error }; } } //_________________________________________________________________ // UPDATE //_________________________________________________________________ - async updatePreset( preset_id: number, dto: SchedulePresetsDto ): Promise { + async updatePreset(preset_id: number, dto: SchedulePresetsDto): Promise { try { const existing = await this.prisma.schedulePresets.findFirst({ - where: { id: preset_id }, - select: { - id:true, - is_default: true, + where: { id: preset_id }, + select: { + id: true, + is_default: true, employee_id: true, }, }); - if(!existing) throw new NotFoundException(`Preset "${dto.name}" not found`); + if (!existing) throw new NotFoundException(`Preset "${dto.name}" not found`); const shifts_data = await this.resolveAndBuildPresetShifts(dto); await this.prisma.$transaction(async (tx) => { - if(typeof dto.is_default === 'boolean'){ - if(dto.is_default) { + if (typeof dto.is_default === 'boolean') { + if (dto.is_default) { await tx.schedulePresets.updateMany({ - where: { + where: { employee_id: existing.employee_id, - is_default: true, - NOT: { id: existing.id }, + is_default: true, + NOT: { id: existing.id }, }, data: { is_default: false }, }); } await tx.schedulePresets.update({ where: { id: existing.id }, - data: { + data: { is_default: dto.is_default, name: dto.name, }, }); } - if(shifts_data.length <= 0) throw new BadRequestException('Preset shifts to update not found'); + if (shifts_data.length <= 0) throw new BadRequestException('Preset shifts to update not found'); await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } }); - const create_many_data: Prisma.SchedulePresetShiftsCreateManyInput[] = - shifts_data.map((shift)=> { - if(!shift.bank_code || !('connect' in shift.bank_code) || typeof shift.bank_code.connect?.id !=='number'){ + const create_many_data: Prisma.SchedulePresetShiftsCreateManyInput[] = + shifts_data.map((shift) => { + if (!shift.bank_code || !('connect' in shift.bank_code) || typeof shift.bank_code.connect?.id !== 'number') { throw new NotFoundException(`Bank code is required for updates( ${shift.week_day}, ${shift.sort_order})`); } const bank_code_id = shift.bank_code.connect.id; return { - preset_id: existing.id, - week_day: shift.week_day, - sort_order: shift.sort_order, - start_time: shift.start_time, - end_time: shift.end_time, - is_remote: shift.is_remote ?? false, + preset_id: existing.id, + week_day: shift.week_day, + sort_order: shift.sort_order, + start_time: shift.start_time, + end_time: shift.end_time, + is_remote: shift.is_remote ?? false, bank_code_id: bank_code_id, }; - }); - await tx.schedulePresetShifts.createMany({data: create_many_data}); + }); + await tx.schedulePresetShifts.createMany({ data: create_many_data }); }); const saved = await this.prisma.schedulePresets.findUnique({ where: { id: existing.id }, - include: { shifts: { - orderBy: [{ week_day: 'asc' }, { sort_order: 'asc' }], - include: { bank_code: { select: { type: true }}}, - }}, + include: { + shifts: { + orderBy: [{ week_day: 'asc' }, { sort_order: 'asc' }], + include: { bank_code: { select: { type: true } } }, + } + }, }); - if(!saved) throw new NotFoundException(`Preset with id: ${existing.id} not found`); + if (!saved) throw new NotFoundException(`Preset with id: ${existing.id} not found`); const response_dto: SchedulePresetsDto = { + id: saved.id, name: saved.name, is_default: saved.is_default, - preset_shifts: saved.shifts.map((shift) => ({ - preset_id: shift.preset_id, - week_day: shift.week_day, + preset_shifts: saved.shifts.map((shift) => ({ + preset_id: shift.preset_id, + week_day: shift.week_day, sort_order: shift.sort_order, - type: shift.bank_code.type, + type: shift.bank_code.type, start_time: toHHmmFromDate(shift.start_time), - end_time: toHHmmFromDate(shift.end_time), - is_remote: shift.is_remote, + end_time: toHHmmFromDate(shift.end_time), + is_remote: shift.is_remote, })), }; return { ok: true, id: existing.id, data: response_dto }; - } catch (error){ + } catch (error) { return { ok: false, id: preset_id, error } } } @@ -138,22 +142,22 @@ export class SchedulePresetsUpsertService { //_________________________________________________________________ // DELETE //_________________________________________________________________ - async deletePreset( preset_id: number ): Promise { + async deletePreset(preset_id: number): Promise { try { await this.prisma.$transaction(async (tx) => { const preset = await tx.schedulePresets.findFirst({ where: { id: preset_id }, select: { id: true }, }); - if(!preset) throw new NotFoundException(`Preset with id ${ preset_id } not found`); - await tx.schedulePresets.delete({where: { id: preset_id } }); + if (!preset) throw new NotFoundException(`Preset with id ${preset_id} not found`); + await tx.schedulePresets.delete({ where: { id: preset_id } }); return { success: true }; }); return { ok: true, id: preset_id }; } catch (error) { - if(error) throw new NotFoundException(`Preset schedule with id ${ preset_id } not found`); + if (error) throw new NotFoundException(`Preset schedule with id ${preset_id} not found`); return { ok: false, id: preset_id, error }; } } @@ -161,19 +165,19 @@ export class SchedulePresetsUpsertService { //PRIVATE HELPERS //resolve bank_code_id using type and convert hours to TIME and valid shifts end/start - private async resolveAndBuildPresetShifts( + private async resolveAndBuildPresetShifts( dto: SchedulePresetsDto - ): Promise{ + ): Promise { - if(!dto.preset_shifts?.length) throw new NotFoundException(`Empty or preset shifts not found`); + if (!dto.preset_shifts?.length) throw new NotFoundException(`Empty or preset shifts not found`); - const types = Array.from(new Set(dto.preset_shifts.map((shift)=> shift.type))); + const types = Array.from(new Set(dto.preset_shifts.map((shift) => shift.type))); const bank_code_set = new Map(); for (const type of types) { const { id } = await this.typeResolver.findIdAndModifierByType(type); bank_code_set.set(type, id) - } + } const toTime = (hhmm: string) => new Date(`1970-01-01T${hhmm}:00.000Z`); const pair_set = new Set(); @@ -185,25 +189,25 @@ export class SchedulePresetsUpsertService { pair_set.add(key); } - const items: Prisma.SchedulePresetShiftsCreateWithoutPresetInput[] = dto.preset_shifts.map((shift)=> { + const items: Prisma.SchedulePresetShiftsCreateWithoutPresetInput[] = dto.preset_shifts.map((shift) => { const bank_code_id = bank_code_set.get(shift.type); - if(!bank_code_id) throw new NotFoundException(`Bank code not found for type ${shift.type}`); + if (!bank_code_id) throw new NotFoundException(`Bank code not found for type ${shift.type}`); if (!shift.start_time || !shift.end_time) { throw new BadRequestException(`start_time and end_time are required for (${shift.week_day}, ${shift.sort_order})`); } const start = toTime(shift.start_time); - const end = toTime(shift.end_time); - if(end.getTime() <= start.getTime()) { + const end = toTime(shift.end_time); + if (end.getTime() <= start.getTime()) { throw new ConflictException(`end_time must be > start_time ( day: ${shift.week_day}, order: ${shift.sort_order})`); } return { - week_day: shift.week_day as Weekday, + week_day: shift.week_day as Weekday, sort_order: shift.sort_order, - bank_code: { connect: { id: bank_code_id} }, + bank_code: { connect: { id: bank_code_id } }, start_time: start, - end_time: end, - is_remote: !!shift.is_remote, + end_time: end, + is_remote: !!shift.is_remote, }; }); return items;