refactor(schedules_employees): refactor schema relations between employees and presets

This commit is contained in:
Matthieu Haineault 2025-12-04 10:42:54 -05:00
parent 3e0e835cd8
commit d913f59eb5
19 changed files with 170 additions and 161 deletions

View File

@ -0,0 +1,20 @@
/*
Warnings:
- You are about to drop the column `employee_id` on the `schedule_presets` table. All the data in the column will be lost.
*/
-- DropForeignKey
ALTER TABLE "public"."schedule_presets" DROP CONSTRAINT "schedule_presets_employee_id_fkey";
-- DropIndex
DROP INDEX "public"."schedule_presets_employee_id_name_key";
-- AlterTable
ALTER TABLE "employees" ADD COLUMN "schedule_preset_id" INTEGER;
-- AlterTable
ALTER TABLE "schedule_presets" DROP COLUMN "employee_id";
-- AddForeignKey
ALTER TABLE "employees" ADD CONSTRAINT "employees_schedule_preset_id_fkey" FOREIGN KEY ("schedule_preset_id") REFERENCES "schedule_presets"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@ -47,11 +47,13 @@ model userModuleAccess {
} }
model Employees { model Employees {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
user Users @relation("UserEmployee", fields: [user_id], references: [id]) user Users @relation("UserEmployee", fields: [user_id], references: [id])
user_id String @unique @db.Uuid user_id String @unique @db.Uuid
supervisor Employees? @relation("EmployeeSupervisor", fields: [supervisor_id], references: [id]) supervisor Employees? @relation("EmployeeSupervisor", fields: [supervisor_id], references: [id])
supervisor_id Int? supervisor_id Int?
schedule_preset SchedulePresets? @relation("EmployeesSchedulePreset", fields: [schedule_preset_id], references: [id])
schedule_preset_id Int?
external_payroll_id Int external_payroll_id Int
company_code Int company_code Int
@ -60,10 +62,9 @@ model Employees {
job_title String? job_title String?
is_supervisor Boolean @default(false) is_supervisor Boolean @default(false)
crew Employees[] @relation("EmployeeSupervisor") crew Employees[] @relation("EmployeeSupervisor")
timesheet Timesheets[] @relation("TimesheetEmployee") timesheet Timesheets[] @relation("TimesheetEmployee")
leave_request LeaveRequests[] @relation("LeaveRequestEmployee") leave_request LeaveRequests[] @relation("LeaveRequestEmployee")
schedule_presets SchedulePresets[] @relation("SchedulePreset")
@@map("employees") @@map("employees")
} }
@ -149,16 +150,13 @@ model TimesheetsArchive {
} }
model SchedulePresets { model SchedulePresets {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
employee Employees @relation("SchedulePreset", fields: [employee_id], references: [id])
employee_id Int
name String name String
is_default Boolean @default(false) is_default Boolean @default(false)
shifts SchedulePresetShifts[] @relation("SchedulePresetShiftsSchedulePreset") shifts SchedulePresetShifts[] @relation("SchedulePresetShiftsSchedulePreset")
employees Employees[] @relation("EmployeesSchedulePreset")
@@unique([employee_id, name], name: "unique_preset_name_per_employee")
@@map("schedule_presets") @@map("schedule_presets")
} }

View File

@ -16,4 +16,5 @@ export class EmployeeDetailedDto {
@IsString() @IsOptional() residence?: string; @IsString() @IsOptional() residence?: string;
@IsInt() @IsPositive() @Type(() => Number) external_payroll_id: number; @IsInt() @IsPositive() @Type(() => Number) external_payroll_id: number;
@IsArray() @IsString({ each: true }) user_module_access: string[]; @IsArray() @IsString({ each: true }) user_module_access: string[];
@IsInt() @IsOptional() preset_id?: number;
} }

View File

@ -6,7 +6,7 @@ export class EmployeeDto {
@IsString() last_name!: string; @IsString() last_name!: string;
@IsString() @IsEmail() email!: string; @IsString() @IsEmail() email!: string;
@IsString() @IsOptional() supervisor_full_name: string | ''; @IsString() @IsOptional() supervisor_full_name: string | '';
@IsString() company_name: number; @IsString() company_name: string;
@IsString() @IsOptional() job_title: string; @IsString() @IsOptional() job_title: string;
@IsInt() @Type(()=> Number) external_payroll_id: number; @IsInt() @Type(()=> Number) external_payroll_id: number;
} }

View File

@ -44,6 +44,7 @@ export class EmployeesCreateService {
first_work_day: dto.first_work_day, first_work_day: dto.first_work_day,
is_supervisor: dto.is_supervisor, is_supervisor: dto.is_supervisor,
supervisor_id: supervisor_id, supervisor_id: supervisor_id,
schedule_preset_id: dto.preset_id,
}, },
}); });
}); });

View File

@ -1,12 +1,14 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { Result } from "src/common/errors/result-error.factory"; import { PrismaService } from "src/prisma/prisma.service";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { Modules, toStringFromBoolean } from "src/common/mappers/module-access.mapper"; import { Modules, toStringFromBoolean } from "src/common/mappers/module-access.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { toStringFromDate } from "src/common/utils/date-utils"; import { toStringFromDate } from "src/common/utils/date-utils";
import { Result } from "src/common/errors/result-error.factory";
import { toStringFromCompanyCode } from "src/identity-and-account/employees/utils/employee.utils";
import { EmployeeDetailedDto } from "src/identity-and-account/employees/dtos/employee-detailed.dto"; import { EmployeeDetailedDto } from "src/identity-and-account/employees/dtos/employee-detailed.dto";
import { EmployeeDto } from "src/identity-and-account/employees/dtos/employee.dto"; import { EmployeeDto } from "src/identity-and-account/employees/dtos/employee.dto";
import { toStringFromCompanyCode } from "src/identity-and-account/employees/utils/employee.utils";
import { PrismaService } from "src/prisma/prisma.service";
@Injectable() @Injectable()
export class EmployeesGetService { export class EmployeesGetService {
@ -44,13 +46,12 @@ export class EmployeesGetService {
first_name: r.user.first_name, first_name: r.user.first_name,
last_name: r.user.last_name, last_name: r.user.last_name,
email: r.user.email, email: r.user.email,
company_name: r.company_code, company_name: toStringFromCompanyCode(r.company_code),
job_title: r.job_title ?? '', job_title: r.job_title ?? '',
external_payroll_id: r.external_payroll_id, external_payroll_id: r.external_payroll_id,
employee_full_name: `${r.user.first_name} ${r.user.last_name}`, employee_full_name: `${r.user.first_name} ${r.user.last_name}`,
supervisor_full_name: `${r.supervisor?.user.first_name} ${r.supervisor?.user.last_name}`, supervisor_full_name: `${r.supervisor?.user.first_name} ${r.supervisor?.user.last_name}`,
})), })),)
)
return { success: true, data: employee_list } return { success: true, data: employee_list }
} }
@ -148,6 +149,7 @@ export class EmployeesGetService {
last_work_day: true, last_work_day: true,
external_payroll_id: true, external_payroll_id: true,
is_supervisor: true, is_supervisor: true,
schedule_preset_id: true,
} }
}); });
if (!employee) return { success: false, error: `EMPLOYEE_NOT_FOUND` }; if (!employee) return { success: false, error: `EMPLOYEE_NOT_FOUND` };

View File

@ -1,12 +1,14 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { Result } from "src/common/errors/result-error.factory";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { toBooleanFromString } from "src/common/mappers/module-access.mapper";
import { toDateFromString } from "src/common/utils/date-utils";
import { EmployeeDetailedDto } from "src/identity-and-account/employees/dtos/employee-detailed.dto";
import { toCompanyCodeFromString } from "src/identity-and-account/employees/utils/employee.utils";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { toBooleanFromString } from "src/common/mappers/module-access.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { toDateFromString } from "src/common/utils/date-utils";
import { Result } from "src/common/errors/result-error.factory";
import { toCompanyCodeFromString } from "src/identity-and-account/employees/utils/employee.utils";
import { EmployeeDetailedDto } from "src/identity-and-account/employees/dtos/employee-detailed.dto";
@Injectable() @Injectable()
export class EmployeesUpdateService { export class EmployeesUpdateService {
constructor( constructor(
@ -62,6 +64,8 @@ export class EmployeesUpdateService {
last_work_day: dto.last_work_day, last_work_day: dto.last_work_day,
is_supervisor: dto.is_supervisor, is_supervisor: dto.is_supervisor,
supervisor_id: supervisor_id, supervisor_id: supervisor_id,
schedule_preset_id: dto.preset_id,
external_payroll_id: dto.external_payroll_id,
}, },
}); });

View File

@ -1,57 +1,46 @@
import { Controller, Param, Body, Get, Post, ParseIntPipe, Delete, Patch } from "@nestjs/common"; import { Controller, Param, Body, Get, Post, ParseIntPipe, Delete, Patch } from "@nestjs/common";
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-presets.dto";
import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service";
import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
import { SchedulePresetsCreateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-create.service"; import { SchedulePresetsCreateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-create.service";
import { SchedulePresetUpdateDeleteService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-update-delete.service"; import { SchedulePresetUpdateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-update.service";
import { SchedulePresetDeleteService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-delete.service";
import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-presets.dto";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators"; import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from ".prisma/client"; import { Modules as ModulesEnum } from ".prisma/client";
import { Access } from "src/common/decorators/module-access.decorators";
@Controller('schedule-presets') @Controller('schedule-presets')
export class SchedulePresetsController { export class SchedulePresetsController {
constructor( constructor(
private readonly createService: SchedulePresetsCreateService,
private readonly getService: SchedulePresetsGetService, private readonly getService: SchedulePresetsGetService,
private readonly applyPresetsService: SchedulePresetsApplyService, private readonly createService: SchedulePresetsCreateService,
private readonly updateDeleteService: SchedulePresetUpdateDeleteService, private readonly updateService: SchedulePresetUpdateService,
private readonly deleteService: SchedulePresetDeleteService,
) { } ) { }
// used to create a schedule preset
@Post('create')
@ModuleAccessAllowed(ModulesEnum.employee_management)
async createPreset(@Access('email') email: string, @Body() dto: SchedulePresetsDto) {
return await this.createService.createPreset(email, dto);
}
//used to update an already existing schedule preset
@Patch('update/:preset_id')
@ModuleAccessAllowed(ModulesEnum.employee_management)
async updatePreset(
@Param('preset_id', ParseIntPipe) preset_id: number, @Body() dto: SchedulePresetsDto, @Access('email') email: string) {
return await this.updateDeleteService.updatePreset(preset_id, dto, email);
}
//used to delete a schedule preset
@Delete('delete/:preset_id')
@ModuleAccessAllowed(ModulesEnum.employee_management)
async deletePreset(
@Param('preset_id', ParseIntPipe) preset_id: number, @Access('email') email: string) {
return await this.updateDeleteService.deletePreset(preset_id, email);
}
//used to show the list of available schedule presets
@Get('find-list') @Get('find-list')
@ModuleAccessAllowed(ModulesEnum.employee_management) @ModuleAccessAllowed(ModulesEnum.employee_management)
async findListById(@Access('email') email: string) { async findListById() {
return this.getService.getSchedulePresets(email); return this.getService.getSchedulePresets();
} }
//used to apply a preset to a timesheet @Post('create')
@Post('apply-presets') @ModuleAccessAllowed(ModulesEnum.employee_management)
@ModuleAccessAllowed(ModulesEnum.timesheets) async createPreset(@Body() dto: SchedulePresetsDto) {
async applyPresets( return await this.createService.createPreset(dto);
@Body('preset') preset_id: number, @Body('start') start_date: string, @Access('email') email: string) { }
return this.applyPresetsService.applyToTimesheet(email, preset_id, start_date);
@Patch('update')
@ModuleAccessAllowed(ModulesEnum.employee_management)
async updatePreset(
@Body() dto: SchedulePresetsDto) {
return await this.updateService.updatePreset(dto);
}
@Delete('delete/:id')
@ModuleAccessAllowed(ModulesEnum.employee_management)
async deletePreset(
@Param('id', ParseIntPipe) id: number) {
return await this.deleteService.deletePreset(id);
} }
} }

View File

@ -3,11 +3,11 @@ import { HH_MM_REGEX } from "src/common/utils/constants.utils";
import { Weekday } from "@prisma/client"; import { Weekday } from "@prisma/client";
export class SchedulePresetShiftsDto { export class SchedulePresetShiftsDto {
@IsInt() preset_id!: number; @IsInt() preset_id!: number;
@IsEnum(Weekday) week_day!: Weekday; @IsEnum(Weekday) week_day!: Weekday;
@IsInt() @Min(1) sort_order!: number; @IsInt() @Min(1) sort_order!: number;
@IsString() type!: string; @IsString() type!: string;
@IsString() @Matches(HH_MM_REGEX) start_time!: string; @IsString() @Matches(HH_MM_REGEX) start_time!: string;
@IsString() @Matches(HH_MM_REGEX) end_time!: string; @IsString() @Matches(HH_MM_REGEX) end_time!: string;
@IsOptional() @IsBoolean() is_remote?: boolean; @IsOptional() @IsBoolean() is_remote?: boolean;
} }

View File

@ -1,9 +1,10 @@
import { ArrayMinSize, IsArray, IsBoolean, IsInt, IsOptional, IsString } from "class-validator"; import { ArrayMinSize, IsArray, IsBoolean, IsInt, IsOptional, IsString } from "class-validator";
import { SchedulePresetShiftsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-preset-shifts.dto"; import { SchedulePresetShiftsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-preset-shifts.dto";
export class SchedulePresetsDto { export class SchedulePresetsDto {
@IsInt() id!: number; @IsInt() id!: number;
@IsString() name!: string; @IsString() name!: string;
@IsBoolean() @IsOptional() is_default: boolean; @IsBoolean() @IsOptional() is_default: boolean;
@IsArray() @ArrayMinSize(1) preset_shifts: SchedulePresetShiftsDto[]; @IsArray() @ArrayMinSize(1) preset_shifts: SchedulePresetShiftsDto[];
} }

View File

@ -1,25 +1,25 @@
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { SchedulePresetsController } from "src/time-and-attendance/schedule-presets/controller/schedule-presets.controller"; import { SchedulePresetsController } from "src/time-and-attendance/schedule-presets/controller/schedule-presets.controller";
import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service";
import { SchedulePresetsCreateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-create.service";
import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service"; import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
import { SchedulePresetUpdateDeleteService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-update-delete.service"; import { SchedulePresetsCreateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-create.service";
import { SchedulePresetUpdateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-update.service";
import { SchedulePresetDeleteService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-delete.service";
@Module({ @Module({
controllers: [SchedulePresetsController], controllers: [SchedulePresetsController],
providers: [ providers: [
SchedulePresetsCreateService,
SchedulePresetUpdateDeleteService,
SchedulePresetsGetService, SchedulePresetsGetService,
SchedulePresetsApplyService, SchedulePresetsCreateService,
SchedulePresetUpdateService,
SchedulePresetDeleteService,
], ],
exports: [ exports: [
SchedulePresetsCreateService,
SchedulePresetUpdateDeleteService,
SchedulePresetsGetService, SchedulePresetsGetService,
SchedulePresetsApplyService, SchedulePresetsCreateService,
SchedulePresetUpdateService,
SchedulePresetDeleteService,
], ],
}) export class SchedulePresetsModule { } }) export class SchedulePresetsModule { }

View File

@ -1,35 +1,30 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { Weekday } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { Result } from "src/common/errors/result-error.factory";
import { overlaps, toDateFromHHmm } from "src/common/utils/date-utils";
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-presets.dto"; import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-presets.dto";
import { overlaps, toDateFromHHmm } from "src/common/utils/date-utils";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { Result } from "src/common/errors/result-error.factory";
@Injectable() @Injectable()
export class SchedulePresetsCreateService { export class SchedulePresetsCreateService {
constructor( constructor(
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly typeResolver: BankCodesResolver, private readonly typeResolver: BankCodesResolver,
private readonly emailResolver: EmailToIdResolver,
) { } ) { }
//_________________________________________________________________ //_________________________________________________________________
// CREATE // CREATE
//_________________________________________________________________ //_________________________________________________________________
async createPreset(email: string, dto: SchedulePresetsDto): Promise<Result<boolean, string>> { async createPreset(dto: SchedulePresetsDto): Promise<Result<boolean, string>> {
try { try {
//validate email and fetch employee_id
const employee_id = await this.emailResolver.findIdByEmail(email);
if (!employee_id.success) return { success: false, error: employee_id.error };
//validate new unique name //validate new unique name
const existing = await this.prisma.schedulePresets.findFirst({ const existing = await this.prisma.schedulePresets.findFirst({
where: { name: dto.name, employee_id: employee_id.data }, where: { name: dto.name },
select: { name: true }, select: { name: true },
}); });
if (!existing) return { success: false, error: 'INVALID_SCHEDULE_PRESET' }; if (existing) return { success: false, error: 'INVALID_SCHEDULE_PRESET' };
const normalized_shifts = dto.preset_shifts.map((shift) => ({ const normalized_shifts = dto.preset_shifts.map((shift) => ({
...shift, ...shift,
@ -63,14 +58,13 @@ export class SchedulePresetsCreateService {
//check if employee chose this preset has a default preset and ensure all others are false //check if employee chose this preset has a default preset and ensure all others are false
if (dto.is_default) { if (dto.is_default) {
await tx.schedulePresets.updateMany({ await tx.schedulePresets.updateMany({
where: { employee_id: employee_id.data, is_default: true }, where: { is_default: true },
data: { is_default: false }, data: { is_default: false },
}); });
} }
await tx.schedulePresets.create({ await tx.schedulePresets.create({
data: { data: {
employee_id: employee_id.data,
name: dto.name, name: dto.name,
is_default: dto.is_default ?? false, is_default: dto.is_default ?? false,
shifts: { shifts: {

View File

@ -0,0 +1,23 @@
import { Result } from "src/common/errors/result-error.factory";
import { PrismaService } from "src/prisma/prisma.service";
export class SchedulePresetDeleteService {
constructor(private readonly prisma: PrismaService) { }
//_________________________________________________________________
// DELETE
//_________________________________________________________________
async deletePreset(preset_id: number): Promise<Result<boolean, string>> {
const preset = await this.prisma.schedulePresets.findFirst({
where: { id: preset_id },
select: { id: true },
});
if (!preset) return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` };
await this.prisma.$transaction(async (tx) => {
await tx.schedulePresetShifts.deleteMany({ where: { preset_id: preset_id } });
await tx.schedulePresets.delete({ where: { id: preset_id } });
});
return { success: true, data: true };
}
}

View File

@ -1,23 +1,20 @@
import { PresetResponse, ShiftResponse } from "src/time-and-attendance/utils/type.utils";
import { PrismaService } from "src/prisma/prisma.service";
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { PrismaService } from "src/prisma/prisma.service";
import { PresetResponse, ShiftResponse } from "src/time-and-attendance/utils/type.utils";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
@Injectable() @Injectable()
export class SchedulePresetsGetService { export class SchedulePresetsGetService {
constructor( constructor(
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver,
) { } ) { }
async getSchedulePresets(email: string): Promise<Result<PresetResponse[], string>> { async getSchedulePresets(): Promise<Result<PresetResponse[], string>> {
try { try {
const employee_id = await this.emailResolver.findIdByEmail(email);
if (!employee_id.success) return { success: false, error: employee_id.error };
const presets = await this.prisma.schedulePresets.findMany({ const presets = await this.prisma.schedulePresets.findMany({
where: { employee_id: employee_id.data },
orderBy: [{ is_default: 'desc' }, { name: 'asc' }], orderBy: [{ is_default: 'desc' }, { name: 'asc' }],
include: { include: {
shifts: { shifts: {

View File

@ -1,38 +1,32 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { Result } from "src/common/errors/result-error.factory";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { overlaps, toDateFromHHmm } from "src/common/utils/date-utils";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-presets.dto"; import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-presets.dto";
import { overlaps, toDateFromHHmm } from "src/common/utils/date-utils";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { Result } from "src/common/errors/result-error.factory";
@Injectable() @Injectable()
export class SchedulePresetUpdateDeleteService { export class SchedulePresetUpdateService {
constructor( constructor(
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly typeResolver: BankCodesResolver, private readonly typeResolver: BankCodesResolver,
private readonly emailResolver: EmailToIdResolver,
) { } ) { }
//_________________________________________________________________ //_________________________________________________________________
// UPDATE // UPDATE
//_________________________________________________________________ //_________________________________________________________________
async updatePreset(preset_id: number, dto: SchedulePresetsDto, email: string): Promise<Result<boolean, string>> { async updatePreset(dto: SchedulePresetsDto): Promise<Result<boolean, string>> {
const employee_id = await this.emailResolver.findIdByEmail(email);
if (!employee_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }
//look for existing schedule_preset and return OG's data
const existing = await this.prisma.schedulePresets.findFirst({ const existing = await this.prisma.schedulePresets.findFirst({
where: { id: preset_id, name: dto.name, employee_id: employee_id.data }, where: { id: dto.id, name: dto.name },
select: { select: {
id: true, id: true,
is_default: true, is_default: true,
employee_id: true,
shifts: true, shifts: true,
}, },
}); });
if (!existing) return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` }; if (!existing) return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` };
//normalized shifts start and end time to make an overlap check with other shifts
const normalized_shifts = dto.preset_shifts.map((shift) => ({ const normalized_shifts = dto.preset_shifts.map((shift) => ({
...shift, ...shift,
start: toDateFromHHmm(shift.start_time), start: toDateFromHHmm(shift.start_time),
@ -41,10 +35,8 @@ export class SchedulePresetUpdateDeleteService {
for (const preset_shifts of normalized_shifts) { for (const preset_shifts of normalized_shifts) {
for (const other_shifts of normalized_shifts) { for (const other_shifts of normalized_shifts) {
//skip if same object or id week_day is not the same
if (preset_shifts === other_shifts) continue; if (preset_shifts === other_shifts) continue;
if (preset_shifts.week_day !== other_shifts.week_day) continue; if (preset_shifts.week_day !== other_shifts.week_day) continue;
//check overlaping possibilities
const has_overlap = overlaps( const has_overlap = overlaps(
{ start: preset_shifts.start, end: preset_shifts.end }, { start: preset_shifts.start, end: preset_shifts.end },
{ start: other_shifts.start, end: other_shifts.end }, { start: other_shifts.start, end: other_shifts.end },
@ -52,7 +44,6 @@ export class SchedulePresetUpdateDeleteService {
if (has_overlap) return { success: false, error: 'SCHEDULE_PRESET_OVERLAP' }; if (has_overlap) return { success: false, error: 'SCHEDULE_PRESET_OVERLAP' };
} }
} }
//validate bank_code_id/type and map them
const bank_code_results = await Promise.all(dto.preset_shifts.map((shift) => const bank_code_results = await Promise.all(dto.preset_shifts.map((shift) =>
this.typeResolver.findBankCodeIDByType(shift.type), this.typeResolver.findBankCodeIDByType(shift.type),
)); ));
@ -61,18 +52,15 @@ export class SchedulePresetUpdateDeleteService {
} }
await this.prisma.$transaction(async (tx) => { await this.prisma.$transaction(async (tx) => {
//check if employee chose this preset has a default preset and ensure all others are false
if (dto.is_default) { if (dto.is_default) {
await tx.schedulePresets.updateMany({ await tx.schedulePresets.updateMany({
where: { where: {
employee_id: existing.employee_id,
is_default: true, is_default: true,
NOT: { id: existing.id }, NOT: { id: existing.id },
}, },
data: { is_default: false }, data: { is_default: false },
}); });
} }
//deletes old preset shifts to make place to new ones
await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } }); await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } });
await tx.schedulePresets.update({ await tx.schedulePresets.update({
@ -82,7 +70,6 @@ export class SchedulePresetUpdateDeleteService {
is_default: dto.is_default ?? false, is_default: dto.is_default ?? false,
shifts: { shifts: {
create: dto.preset_shifts.map((shift, index) => { create: dto.preset_shifts.map((shift, index) => {
//validated bank_codes sent as a Result Array to access its data
const result = bank_code_results[index] as { success: true, data: number }; const result = bank_code_results[index] as { success: true, data: number };
return { return {
week_day: shift.week_day, week_day: shift.week_day,
@ -91,7 +78,6 @@ export class SchedulePresetUpdateDeleteService {
end_time: toDateFromHHmm(shift.end_time), end_time: toDateFromHHmm(shift.end_time),
is_remote: shift.is_remote ?? false, is_remote: shift.is_remote ?? false,
bank_code: { bank_code: {
//connect uses the FK links to set the bank_code_id
connect: { id: result.data }, connect: { id: result.data },
}, },
} }
@ -103,23 +89,4 @@ export class SchedulePresetUpdateDeleteService {
return { success: true, data: true }; return { success: true, data: true };
} }
//_________________________________________________________________
// DELETE
//_________________________________________________________________
async deletePreset(preset_id: number, email: string): Promise<Result<boolean, string>> {
const employee_id = await this.emailResolver.findIdByEmail(email);
if (!employee_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }
const preset = await this.prisma.schedulePresets.findFirst({
where: { id: preset_id, employee_id: employee_id.data },
select: { id: true },
});
if (!preset) return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` };
await this.prisma.$transaction(async (tx) => {
await tx.schedulePresetShifts.deleteMany({ where: { preset_id: preset_id } });
await tx.schedulePresets.delete({ where: { id: preset_id } });
});
return { success: true, data: true };
}
} }

View File

@ -19,7 +19,7 @@ export class ShiftsCreateService {
//_________________________________________________________________ //_________________________________________________________________
// CREATE WRAPPER FUNCTION FOR ONE OR MANY INPUT // CREATE WRAPPER FUNCTION FOR ONE OR MANY INPUT
//_________________________________________________________________ //_________________________________________________________________
async createOneOrManyShifts(email: string, shifts: ShiftDto[]): Promise<Result<ShiftDto[], string>> { async createOneOrManyShifts(email: string, shifts: ShiftDto[]): Promise<Result<boolean, string>> {
try { try {
//verify if array is empty or not //verify if array is empty or not
if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'No data received' }; if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'No data received' };
@ -51,7 +51,7 @@ export class ShiftsCreateService {
if (created_shifts.length === 0) return { success: false, error: errors.join(' | ') || 'No shift created' }; if (created_shifts.length === 0) return { success: false, error: errors.join(' | ') || 'No shift created' };
// returns array of created shifts // returns array of created shifts
return { success: true, data: created_shifts } return { success: true, data: true }
} catch (error) { } catch (error) {
return { success: false, error } return { success: false, error }
} }

View File

@ -16,7 +16,7 @@ export class ShiftsUpdateDeleteService {
private readonly timesheetResolver: EmployeeTimesheetResolver, private readonly timesheetResolver: EmployeeTimesheetResolver,
) { } ) { }
async updateOneOrManyShifts(shifts: ShiftDto[], email: string): Promise<Result<ShiftDto[], string>> { async updateOneOrManyShifts(shifts: ShiftDto[], email: string): Promise<Result<boolean, string>> {
try { try {
//verify if array is empty or not //verify if array is empty or not
if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'No data received' }; if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'No data received' };
@ -48,7 +48,7 @@ export class ShiftsUpdateDeleteService {
if (updated_shifts.length === 0) return { success: false, error: errors.join(' | ') || 'No shift updated' }; if (updated_shifts.length === 0) return { success: false, error: errors.join(' | ') || 'No shift updated' };
// returns array of updated shifts // returns array of updated shifts
return { success: true, data: updated_shifts } return { success: true, data: true }
} catch (error) { } catch (error) {
return { success: false, error } return { success: false, error }
} }

View File

@ -1,31 +1,41 @@
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-logics.module"; import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-logics.module";
import { ExpenseController } from "src/time-and-attendance/expenses/controllers/expense.controller"; import { ExpenseController } from "src/time-and-attendance/expenses/controllers/expense.controller";
import { ExpenseUpsertService } from "src/time-and-attendance/expenses/services/expense-upsert.service"; import { ExpenseUpsertService } from "src/time-and-attendance/expenses/services/expense-upsert.service";
import { PayperiodsModule } from "src/time-and-attendance/pay-period/pay-periods.module"; import { ExpensesModule } from "src/time-and-attendance/expenses/expenses.module";
import { TimesheetController } from "src/time-and-attendance/timesheets/controllers/timesheet.controller"; import { TimesheetController } from "src/time-and-attendance/timesheets/controllers/timesheet.controller";
import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service"; import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service";
import { GetTimesheetsOverviewService } from "src/time-and-attendance/timesheets/services/timesheet-get-overview.service"; import { GetTimesheetsOverviewService } from "src/time-and-attendance/timesheets/services/timesheet-get-overview.service";
import { TimesheetsModule } from "src/time-and-attendance/timesheets/timesheets.module"; import { TimesheetsModule } from "src/time-and-attendance/timesheets/timesheets.module";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper"; import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper"; import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper";
import { PayperiodsModule } from "src/time-and-attendance/pay-period/pay-periods.module";
import { PayPeriodsController } from "src/time-and-attendance/pay-period/controllers/pay-periods.controller"; import { PayPeriodsController } from "src/time-and-attendance/pay-period/controllers/pay-periods.controller";
import { ExpensesModule } from "src/time-and-attendance/expenses/expenses.module";
import { PayPeriodsQueryService } from "src/time-and-attendance/pay-period/services/pay-periods-query.service"; import { PayPeriodsQueryService } from "src/time-and-attendance/pay-period/services/pay-periods-query.service";
import { PayPeriodsCommandService } from "src/time-and-attendance/pay-period/services/pay-periods-command.service"; import { PayPeriodsCommandService } from "src/time-and-attendance/pay-period/services/pay-periods-command.service";
import { CsvExportModule } from "src/modules/exports/csv-exports.module"; import { CsvExportModule } from "src/modules/exports/csv-exports.module";
import { CsvExportService } from "src/modules/exports/services/csv-exports.service"; import { CsvExportService } from "src/modules/exports/services/csv-exports.service";
import { CsvExportController } from "src/modules/exports/controllers/csv-exports.controller"; import { CsvExportController } from "src/modules/exports/controllers/csv-exports.controller";
import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service";
import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
import { ShiftController } from "src/time-and-attendance/shifts/controllers/shift.controller"; import { ShiftController } from "src/time-and-attendance/shifts/controllers/shift.controller";
import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service"; import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";
import { ShiftsGetService } from "src/time-and-attendance/shifts/services/shifts-get.service"; import { ShiftsGetService } from "src/time-and-attendance/shifts/services/shifts-get.service";
import { ShiftsUpdateDeleteService } from "src/time-and-attendance/shifts/services/shifts-update-delete.service"; import { ShiftsUpdateDeleteService } from "src/time-and-attendance/shifts/services/shifts-update-delete.service";
import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
import { SchedulePresetsController } from "src/time-and-attendance/schedule-presets/controller/schedule-presets.controller";
import { SchedulePresetsModule } from "src/time-and-attendance/schedule-presets/schedule-presets.module";
import { SchedulePresetDeleteService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-delete.service";
import { SchedulePresetUpdateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-update.service";
import { SchedulePresetsCreateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-create.service";
@Module({ @Module({
imports: [ imports: [
BusinessLogicsModule, BusinessLogicsModule,
@ -33,12 +43,13 @@ import { ShiftsUpdateDeleteService } from "src/time-and-attendance/shifts/servic
TimesheetsModule, TimesheetsModule,
ExpensesModule, ExpensesModule,
PayperiodsModule, PayperiodsModule,
CsvExportModule, CsvExportModule,
SchedulePresetsModule,
], ],
controllers: [ controllers: [
TimesheetController, TimesheetController,
ShiftController, ShiftController,
// SchedulePresetsController, SchedulePresetsController,
ExpenseController, ExpenseController,
PayPeriodsController, PayPeriodsController,
CsvExportController, CsvExportController,
@ -50,9 +61,10 @@ import { ShiftsUpdateDeleteService } from "src/time-and-attendance/shifts/servic
ShiftsCreateService, ShiftsCreateService,
ShiftsUpdateDeleteService, ShiftsUpdateDeleteService,
ExpenseUpsertService, ExpenseUpsertService,
// SchedulePresetsUpsertService,
SchedulePresetsGetService, SchedulePresetsGetService,
SchedulePresetsApplyService, SchedulePresetDeleteService,
SchedulePresetUpdateService,
SchedulePresetsCreateService,
EmailToIdResolver, EmailToIdResolver,
BankCodesResolver, BankCodesResolver,
TimesheetApprovalService, TimesheetApprovalService,