fix(employees): add second employeeDetailedUpsertDto to separate from GET dto that only requires partial PaidTimeOff access

This commit is contained in:
Nicolas Drolet 2026-01-09 12:12:27 -05:00
parent 791b3aacb9
commit 5ab1144d2c
4 changed files with 65 additions and 9 deletions

View File

@ -16,7 +16,27 @@ export class EmployeeDetailedDto {
@IsInt() daily_expected_hours: number; @IsInt() daily_expected_hours: number;
@IsDateString() @IsOptional() last_work_day?: string | null; @IsDateString() @IsOptional() last_work_day?: string | null;
@IsString() @IsOptional() residence?: string; @IsString() @IsOptional() residence?: string;
@IsOptional() @Type(() => PaidTimeOffDto) paid_time_off?: Partial<PaidTimeOffDto> | null; @IsOptional() @Type(() => PaidTimeOffDto) paid_time_off?: Partial<PaidTimeOffDto>;
@IsInt() @IsPositive() @Type(() => Number) external_payroll_id: number;
@IsArray() @IsString({ each: true }) user_module_access: string[];
@IsInt() @IsOptional() preset_id?: number;
}
export class EmployeeDetailedUpsertDto {
@IsString() @IsNotEmpty() first_name: string;
@IsString() @IsNotEmpty() last_name: string;
@IsString() @IsOptional() employee_full_name: string;
@IsString() @IsOptional() supervisor_full_name: string;
@IsOptional() @IsBoolean() is_supervisor: boolean;
@IsString() company_name: string;
@IsString() @IsOptional() job_title: string;
@IsEmail() @IsOptional() email: string;
@IsString() phone_number: string;
@IsDateString() first_work_day: string;
@IsInt() daily_expected_hours: number;
@IsDateString() @IsOptional() last_work_day?: string | null;
@IsString() @IsOptional() residence?: string;
@IsOptional() @Type(() => PaidTimeOffDto) paid_time_off?: PaidTimeOffDto;
@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; @IsInt() @IsOptional() preset_id?: number;

View File

@ -3,7 +3,7 @@ import { Modules as ModulesEnum } from ".prisma/client";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators"; import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Access } from "src/common/decorators/module-access.decorators"; import { Access } from "src/common/decorators/module-access.decorators";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { EmployeeDetailedDto } from "src/identity-and-account/employees/employee-detailed.dto"; import { EmployeeDetailedDto, EmployeeDetailedUpsertDto } from "src/identity-and-account/employees/employee-detailed.dto";
import { EmployeesGetService } from "src/identity-and-account/employees/services/employees-get.service"; import { EmployeesGetService } from "src/identity-and-account/employees/services/employees-get.service";
import { EmployeesCreateService } from "src/identity-and-account/employees/services/employees-create.service"; import { EmployeesCreateService } from "src/identity-and-account/employees/services/employees-create.service";
import { EmployeesUpdateService } from "src/identity-and-account/employees/services/employees-update.service"; import { EmployeesUpdateService } from "src/identity-and-account/employees/services/employees-update.service";
@ -37,13 +37,13 @@ export class EmployeesController {
@Post('create') @Post('create')
@ModuleAccessAllowed(ModulesEnum.employee_management) @ModuleAccessAllowed(ModulesEnum.employee_management)
async createEmployee(@Body() dto: EmployeeDetailedDto): Promise<Result<boolean, string>> { async createEmployee(@Body() dto: EmployeeDetailedUpsertDto): Promise<Result<boolean, string>> {
return await this.createService.createEmployee(dto); return await this.createService.createEmployee(dto);
} }
@Patch('update') @Patch('update')
@ModuleAccessAllowed(ModulesEnum.employee_management) @ModuleAccessAllowed(ModulesEnum.employee_management)
async updateEmployee(@Body() dto:EmployeeDetailedDto){ async updateEmployee(@Body() dto:EmployeeDetailedUpsertDto){
return await this.updateService.updateEmployee(dto); return await this.updateService.updateEmployee(dto);
} }
} }

View File

@ -1,14 +1,14 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { Modules } from "src/common/mappers/module-access.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { toDateFromString } from "src/common/utils/date-utils"; import { toDateFromString } from "src/common/utils/date-utils";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { toCompanyCodeFromString } from "src/identity-and-account/employees/employee.utils"; import { toCompanyCodeFromString } from "src/identity-and-account/employees/employee.utils";
import { EmployeeDetailedDto } from "src/identity-and-account/employees/employee-detailed.dto"; import { EmployeeDetailedUpsertDto } from "src/identity-and-account/employees/employee-detailed.dto";
import { toBooleanFromString } from "src/identity-and-account/employees/services/employees-get.service"; import { toBooleanFromString } from "src/identity-and-account/employees/services/employees-get.service";
import { PaidTimeOffDto } from "src/time-and-attendance/domains/paid-time-off.dto";
@Injectable() @Injectable()
export class EmployeesUpdateService { export class EmployeesUpdateService {
@ -17,9 +17,16 @@ export class EmployeesUpdateService {
private readonly emailResolver: EmailToIdResolver, private readonly emailResolver: EmailToIdResolver,
) { } ) { }
async updateEmployee(dto: EmployeeDetailedDto): Promise<Result<boolean, string>> { async updateEmployee(dto: EmployeeDetailedUpsertDto): Promise<Result<boolean, string>> {
const user_id = await this.emailResolver.resolveUserIdWithEmail(dto.email); const user_id = await this.emailResolver.resolveUserIdWithEmail(dto.email);
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' } if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND'}
const employee = await this.prisma.employees.findFirst({
where: { user_id: user_id.data },
select: { id: true },
})
if (!employee) return { success: false, error: 'EMPLOYEE_NOT_FOUND'};
const company_code = toCompanyCodeFromString(dto.company_name); const company_code = toCompanyCodeFromString(dto.company_name);
const supervisor_id = await this.toIdFromFullName(dto.supervisor_full_name); const supervisor_id = await this.toIdFromFullName(dto.supervisor_full_name);
@ -37,6 +44,7 @@ export class EmployeesUpdateService {
residence: dto.residence, residence: dto.residence,
}, },
}); });
await tx.userModuleAccess.upsert({ await tx.userModuleAccess.upsert({
where: { user_id: user_id.data }, where: { user_id: user_id.data },
update: { update: {
@ -57,6 +65,26 @@ export class EmployeesUpdateService {
timesheets_approval: normalized_access.timesheets_approval, timesheets_approval: normalized_access.timesheets_approval,
}, },
}); });
const employee_pto = dto.paid_time_off ?? new PaidTimeOffDto(employee.id);
await tx.paidTimeOff.upsert({
where: { employee_id: employee_pto.employee_id },
update: {
sick_hours: employee_pto.sick_hours,
vacation_hours: employee_pto.vacation_hours,
banked_hours: employee_pto.banked_hours,
last_updated: employee_pto.last_updated,
},
create: {
employee_id: employee_pto.employee_id,
sick_hours: employee_pto.sick_hours,
vacation_hours: employee_pto.vacation_hours,
banked_hours: employee_pto.banked_hours,
last_updated: employee_pto.last_updated,
}
})
return tx.employees.update({ return tx.employees.update({
where: { user_id: user_id.data }, where: { user_id: user_id.data },
data: { data: {

View File

@ -2,10 +2,18 @@ import { Type } from "class-transformer";
import { IsDateString, IsDecimal, IsInt, IsNotEmpty, IsNumber, IsOptional } from "class-validator"; import { IsDateString, IsDecimal, IsInt, IsNotEmpty, IsNumber, IsOptional } from "class-validator";
export class PaidTimeOffDto { export class PaidTimeOffDto {
@IsInt() @IsNotEmpty() id: number; @IsInt() id: number;
@IsInt() @IsNotEmpty() employee_id: number; @IsInt() @IsNotEmpty() employee_id: number;
@IsOptional() @Type(() => Number) vacation_hours?: number; @IsOptional() @Type(() => Number) vacation_hours?: number;
@IsOptional() @Type(() => Number) sick_hours?: number; @IsOptional() @Type(() => Number) sick_hours?: number;
@IsOptional() @Type(() => Number) banked_hours?: number; @IsOptional() @Type(() => Number) banked_hours?: number;
@IsDateString() @IsOptional() last_updated: string; @IsDateString() @IsOptional() last_updated: string;
constructor(employee_id: number) {
this.employee_id = employee_id;
this.banked_hours = 0;
this.sick_hours = 0;
this.vacation_hours = 0;
this.last_updated = new Date().toISOString();
}
} }