feat(presets): small ajustements

This commit is contained in:
Matthieu Haineault 2025-11-10 11:16:10 -05:00
parent 03d9fa2cf4
commit 6332a42fa7
4 changed files with 80 additions and 71 deletions

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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<ApplyResult> {
if(!preset_name?.trim()) throw new BadRequestException('A preset_name is required');
async applyToTimesheet( email: string, id: number, start_date_iso: string ): Promise<ApplyResult> {
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,

View File

@ -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<CreatePresetResult> {
async createPreset(email: string, dto: SchedulePresetsDto): Promise<CreatePresetResult> {
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<UpdatePresetResult> {
async updatePreset(preset_id: number, dto: SchedulePresetsDto): Promise<UpdatePresetResult> {
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 <DeletePresetResult> {
async deletePreset(preset_id: number): Promise<DeletePresetResult> {
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<Prisma.SchedulePresetShiftsCreateWithoutPresetInput[]>{
): Promise<Prisma.SchedulePresetShiftsCreateWithoutPresetInput[]> {
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<string, number>();
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<string>();
@ -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;