refactor(leave-request): refactor to use email instead of employee ids
This commit is contained in:
parent
bdf6662374
commit
9a150715b0
|
|
@ -1,23 +1,12 @@
|
|||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsEnum, IsInt, IsNotEmpty, IsOptional, IsString } from "class-validator";
|
||||
import { IsDateString, IsEmail, IsEnum, IsInt, IsISO8601, IsNotEmpty, IsOptional, IsString } from "class-validator";
|
||||
|
||||
export class CreateLeaveRequestsDto {
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
description: 'Leave request`s unique id(auto-incremented)',
|
||||
})
|
||||
id: number;
|
||||
|
||||
//remove emp_id and use email
|
||||
@ApiProperty({
|
||||
example: '4655867',
|
||||
description: 'Employee`s id',
|
||||
})
|
||||
@Type(()=> Number)
|
||||
@IsInt()
|
||||
employee_id: number;
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 7,
|
||||
|
|
@ -38,17 +27,16 @@ export class CreateLeaveRequestsDto {
|
|||
example: '22/06/2463',
|
||||
description: 'Leave request`s start date',
|
||||
})
|
||||
@Type(()=>Date)
|
||||
@IsNotEmpty()
|
||||
start_date_time:Date;
|
||||
@IsISO8601()
|
||||
start_date_time:string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '25/03/3019',
|
||||
description: 'Leave request`s end date',
|
||||
})
|
||||
@Type(()=>Date)
|
||||
@IsOptional()
|
||||
end_date_time?: Date;
|
||||
@IsISO8601()
|
||||
end_date_time?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'My precious',
|
||||
|
|
|
|||
13
src/modules/leave-requests/dtos/leave-request.view.dto.ts
Normal file
13
src/modules/leave-requests/dtos/leave-request.view.dto.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
|
||||
|
||||
export class LeaveRequestViewDto {
|
||||
id!: number;
|
||||
leave_type!: LeaveTypes;
|
||||
start_date_time!: string;
|
||||
end_date_time!: string | null;
|
||||
comment!: string | null;
|
||||
approval_status: LeaveApprovalStatus;
|
||||
email!: string;
|
||||
employee_full_name: string;
|
||||
days_requested?: number;
|
||||
}
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
import { LeaveApprovalStatus } from "@prisma/client";
|
||||
import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsOptional, IsInt, IsEnum, IsDateString } from "class-validator";
|
||||
import { IsOptional, IsInt, IsEnum, IsDateString, IsEmail } from "class-validator";
|
||||
|
||||
export class SearchLeaveRequestsDto {
|
||||
//remove emp_id and use email
|
||||
@IsOptional()
|
||||
@Type(()=> Number)
|
||||
@IsInt()
|
||||
employee_id?: number;
|
||||
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(()=> Number)
|
||||
|
|
@ -20,9 +18,13 @@ export class SearchLeaveRequestsDto {
|
|||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
start_date?: Date;
|
||||
start_date?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
end_date?: Date;
|
||||
end_date?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(LeaveTypes)
|
||||
leave_type?: LeaveTypes;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { LeaveRequestViewDto } from "../dtos/leave-request.view.dto";
|
||||
import { LeaveRequestArchiveRow } from "../utils/leave-requests-archive.select";
|
||||
|
||||
const toISO = (date: Date | null): string | null => (date ? date.toISOString().slice(0,10): null);
|
||||
|
||||
export function mapArchiveRowToView(row: LeaveRequestArchiveRow, email: string, employee_full_name:string): LeaveRequestViewDto {
|
||||
return {
|
||||
id: row.id,
|
||||
leave_type: row.leave_type,
|
||||
start_date_time: toISO(row.start_date_time)!,
|
||||
end_date_time: toISO(row.end_date_time),
|
||||
comment: row.comment,
|
||||
approval_status: row.approval_status,
|
||||
email,
|
||||
employee_full_name,
|
||||
}
|
||||
}
|
||||
19
src/modules/leave-requests/mappers/leave-requests.mapper.ts
Normal file
19
src/modules/leave-requests/mappers/leave-requests.mapper.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { LeaveRequestViewDto } from "../dtos/leave-request.view.dto";
|
||||
import { LeaveRequestRow } from "../utils/leave-requests.select";
|
||||
|
||||
function toISODateString(date:Date | null): string | null {
|
||||
return date ? date.toISOString().slice(0,10) : null;
|
||||
}
|
||||
|
||||
export function mapRowToView(row: LeaveRequestRow): LeaveRequestViewDto {
|
||||
return {
|
||||
id: row.id,
|
||||
leave_type: row.leave_type,
|
||||
start_date_time: toISODateString(row.start_date_time)!,
|
||||
end_date_time: toISODateString(row.end_date_time),
|
||||
comment: row.comment,
|
||||
approval_status: row.approval_status,
|
||||
email: row.employee.user.email,
|
||||
employee_full_name: `${row.employee.user.first_name} ${row.employee.user.last_name}`
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,13 @@ import { SickLeaveService } from "src/modules/business-logics/services/sick-leav
|
|||
import { VacationService } from "src/modules/business-logics/services/vacation.service";
|
||||
import { SearchLeaveRequestsDto } from "../dtos/search-leave-request.dto";
|
||||
import { buildPrismaWhere } from "src/common/shared/build-prisma-where";
|
||||
import { LeaveRequestViewDto } from "../dtos/leave-request.view.dto";
|
||||
import { LeaveRequestRow, leaveRequestsSelect } from "../utils/leave-requests.select";
|
||||
import { mapRowToView } from "../mappers/leave-requests.mapper";
|
||||
import { LeaveRequestsArchiveController } from "src/modules/archival/controllers/leave-requests-archive.controller";
|
||||
import { LeaveRequestArchiveRow, leaveRequestsArchiveSelect } from "../utils/leave-requests-archive.select";
|
||||
import { mapArchiveRowToView } from "../mappers/leave-requests-archive.mapper";
|
||||
import { mapArchiveRowToViewWithDays, mapRowToViewWithDays } from "../utils/leave-request.transform";
|
||||
|
||||
@Injectable()
|
||||
export class LeaveRequestsService {
|
||||
|
|
@ -18,138 +25,93 @@ export class LeaveRequestsService {
|
|||
private readonly sickLeaveService: SickLeaveService
|
||||
) {}
|
||||
|
||||
//remove emp_id and use email
|
||||
async create(dto: CreateLeaveRequestsDto): Promise<LeaveRequests> {
|
||||
const { employee_id, bank_code_id, leave_type, start_date_time,
|
||||
end_date_time, comment, approval_status } = dto;
|
||||
|
||||
return this.prisma.leaveRequests.create({
|
||||
data: { employee_id, bank_code_id, leave_type, start_date_time,
|
||||
end_date_time, comment, approval_status: approval_status ?? undefined
|
||||
},
|
||||
include: { employee: { include: { user: true } },
|
||||
bank_code: true
|
||||
},
|
||||
//function to avoid using employee_id as identifier in the frontend.
|
||||
private async resolveEmployeeIdByEmail(email: string): Promise<number> {
|
||||
const employee = await this.prisma.employees.findFirst({
|
||||
where: { user: { email} },
|
||||
select: { id:true },
|
||||
});
|
||||
if(!employee) throw new NotFoundException(`Employee with email ${email} not found`);
|
||||
return employee.id;
|
||||
}
|
||||
|
||||
async findAll(filters: SearchLeaveRequestsDto): Promise<any[]> {
|
||||
const {start_date, end_date, ...other_filters } = filters;
|
||||
const where: Record<string, any> = buildPrismaWhere(other_filters);
|
||||
|
||||
if (start_date) {
|
||||
where.start_date_time = { ...(where.start_date_time ?? {}), gte: new Date(start_date) };
|
||||
}
|
||||
if(end_date) {
|
||||
where.end_date_time = { ...(where.end_date_time ?? {}), lte: new Date(end_date) };
|
||||
}
|
||||
|
||||
const list = await this.prisma.leaveRequests.findMany({
|
||||
where,
|
||||
include: { employee: { include: { user: true } },
|
||||
bank_code: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ms_per_day = 1000 * 60 * 60 * 24;
|
||||
|
||||
return Promise.all(
|
||||
list.map(async request => {
|
||||
// end_date fallback
|
||||
const end_date = request.end_date_time ?? request.start_date_time;
|
||||
|
||||
//Requested days
|
||||
const diff_days = Math.round((end_date.getTime() - request.start_date_time.getTime()) / ms_per_day) +1;
|
||||
|
||||
// modifier fallback/validation
|
||||
if (!request.bank_code || request.bank_code.modifier == null) {
|
||||
throw new BadRequestException(`Modifier manquant pour bank_code_id=${request.bank_code_id}`);
|
||||
}
|
||||
const modifier = request.bank_code.modifier;
|
||||
|
||||
let cost: number;
|
||||
switch (request.bank_code.type) {
|
||||
case 'holiday' :
|
||||
cost = await this.holidayService.calculateHolidayPay( request.employee_id, request.start_date_time, modifier );
|
||||
break;
|
||||
case 'vacation' :
|
||||
cost = await this.vacationService.calculateVacationPay( request.employee_id, request.start_date_time,diff_days, modifier );
|
||||
break;
|
||||
case 'sick' :
|
||||
cost = await this.sickLeaveService.calculateSickLeavePay( request.employee_id, request.start_date_time, diff_days, modifier );
|
||||
break;
|
||||
default:
|
||||
cost = diff_days * modifier;
|
||||
}
|
||||
return {...request, days_requested: diff_days, cost };
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
//remove emp_id and use email
|
||||
async findOne(id:number): Promise<any> {
|
||||
const request = await this.prisma.leaveRequests.findUnique({
|
||||
where: { id },
|
||||
include: { employee: { include: { user: true } },
|
||||
bank_code: true,
|
||||
},
|
||||
});
|
||||
if(!request) {
|
||||
throw new NotFoundException(`LeaveRequest #${id} not found`);
|
||||
}
|
||||
//validation and fallback for end_date_time
|
||||
const end_Date = request.end_date_time ?? request.start_date_time;
|
||||
|
||||
//calculate included days
|
||||
const msPerDay = 1000 * 60 * 60 * 24;
|
||||
const diff_days = Math.floor((end_Date.getTime() - request.start_date_time.getTime())/ msPerDay) + 1;
|
||||
|
||||
if (!request.bank_code || request.bank_code.modifier == null) {
|
||||
throw new BadRequestException(`Modifier missing for code ${request.bank_code_id}`);
|
||||
}
|
||||
const modifier = request.bank_code.modifier;
|
||||
|
||||
//calculate cost based on bank_code types
|
||||
let cost = diff_days * modifier;
|
||||
switch(request.bank_code.type) {
|
||||
case 'holiday':
|
||||
cost = await this.holidayService.calculateHolidayPay( request.employee_id, request.start_date_time, modifier );
|
||||
break;
|
||||
case 'vacation':
|
||||
cost = await this.vacationService.calculateVacationPay( request.employee_id, request.start_date_time, diff_days, modifier );
|
||||
break;
|
||||
case 'sick':
|
||||
cost = await this.sickLeaveService.calculateSickLeavePay( request.employee_id, request.start_date_time, diff_days, modifier );
|
||||
break;
|
||||
default:
|
||||
cost = diff_days * modifier;
|
||||
}
|
||||
return {...request, days_requested: diff_days, cost };
|
||||
}
|
||||
|
||||
//remove emp_id and use email
|
||||
async update(id: number, dto: UpdateLeaveRequestsDto): Promise<LeaveRequests> {
|
||||
await this.findOne(id);
|
||||
const { employee_id, leave_type, start_date_time, end_date_time, comment, approval_status } = dto;
|
||||
return this.prisma.leaveRequests.update({
|
||||
where: { id },
|
||||
//create a leave-request without the use of employee_id
|
||||
async create(dto: CreateLeaveRequestsDto): Promise<LeaveRequestViewDto> {
|
||||
const employee_id = await this.resolveEmployeeIdByEmail(dto.email);
|
||||
const row: LeaveRequestRow = await this.prisma.leaveRequests.create({
|
||||
data: {
|
||||
...(employee_id !== undefined && { employee_id }),
|
||||
...(leave_type !== undefined && { leave_type } ),
|
||||
...(start_date_time !== undefined && { start_date_time }),
|
||||
...(end_date_time !== undefined && { end_date_time }),
|
||||
...(comment !== undefined && { comment }),
|
||||
...(approval_status !== undefined && { approval_status }),
|
||||
employee_id,
|
||||
bank_code_id: dto.bank_code_id,
|
||||
leave_type: dto.leave_type,
|
||||
start_date_time: new Date(dto.start_date_time),
|
||||
end_date_time: dto.end_date_time ? new Date(dto.end_date_time) : null,
|
||||
comment: dto.comment,
|
||||
approval_status: dto.approval_status ?? undefined,
|
||||
},
|
||||
include: { employee: { include: { user:true } } },
|
||||
select: leaveRequestsSelect,
|
||||
});
|
||||
return mapRowToViewWithDays(row);
|
||||
}
|
||||
|
||||
async remove(id:number): Promise<LeaveRequests> {
|
||||
await this.findOne(id);
|
||||
return this.prisma.leaveRequests.delete({
|
||||
where: { id },
|
||||
//fetches all leave-requests using email
|
||||
async findAll(filters: SearchLeaveRequestsDto): Promise<LeaveRequestViewDto[]> {
|
||||
const {start_date, end_date,email, leave_type, approval_status, bank_code_id } = filters;
|
||||
const where: any = {};
|
||||
|
||||
if (start_date) where.start_date_time = { ...(where.start_date_time ?? {}), gte: new Date(start_date) };
|
||||
if (end_date) where.end_date_time = { ...(where.end_date_time ?? {}), lte: new Date(end_date) };
|
||||
if (email) where.employee = { user: { email } };
|
||||
if (leave_type) where.leave_type = leave_type;
|
||||
if (approval_status) where.approval_status = approval_status;
|
||||
if (typeof bank_code_id === 'number') where.bank_code_id = bank_code_id;
|
||||
|
||||
const rows= await this.prisma.leaveRequests.findMany({
|
||||
where,
|
||||
select: leaveRequestsSelect,
|
||||
orderBy: { start_date_time: 'desc' },
|
||||
});
|
||||
|
||||
return rows.map(mapRowToViewWithDays);
|
||||
}
|
||||
|
||||
//fetch 1 leave-request using email
|
||||
async findOne(id:number): Promise<LeaveRequestViewDto> {
|
||||
const row: LeaveRequestRow | null = await this.prisma.leaveRequests.findUnique({
|
||||
where: { id },
|
||||
select: leaveRequestsSelect,
|
||||
});
|
||||
if(!row) throw new NotFoundException(`Leave Request #${id} not found`);
|
||||
return mapRowToViewWithDays(row);
|
||||
}
|
||||
|
||||
//updates 1 leave-request using email
|
||||
async update(id: number, dto: UpdateLeaveRequestsDto): Promise<LeaveRequestViewDto> {
|
||||
await this.findOne(id);
|
||||
const data: Record<string, any> = {};
|
||||
|
||||
if(dto.email !== undefined) data.employee_id = await this.resolveEmployeeIdByEmail(dto.email);
|
||||
if(dto.leave_type !== undefined) data.bank_code_id = dto.bank_code_id;
|
||||
if(dto.start_date_time !== undefined) data.start_date_time = new Date(dto.start_date_time);
|
||||
if(dto.end_date_time !== undefined) data.end_date_time = new Date(dto.end_date_time);
|
||||
if(dto.comment !== undefined) data.comment = dto.comment;
|
||||
if(dto.approval_status !== undefined) data.approval_status = dto.approval_status;
|
||||
|
||||
const row: LeaveRequestRow = await this.prisma.leaveRequests.update({
|
||||
where: { id },
|
||||
data,
|
||||
select: leaveRequestsSelect,
|
||||
});
|
||||
return mapRowToViewWithDays(row);
|
||||
}
|
||||
|
||||
//removes 1 leave-request using email
|
||||
async remove(id:number): Promise<LeaveRequestViewDto> {
|
||||
await this.findOne(id);
|
||||
const row: LeaveRequestRow = await this.prisma.leaveRequests.delete({
|
||||
where: { id },
|
||||
select: leaveRequestsSelect,
|
||||
});
|
||||
return mapRowToViewWithDays(row);
|
||||
}
|
||||
|
||||
//archivation functions ******************************************************
|
||||
|
|
@ -184,14 +146,30 @@ export class LeaveRequestsService {
|
|||
});
|
||||
}
|
||||
|
||||
//fetches all archived employees
|
||||
//fetches all archived leave-requests
|
||||
async findAllArchived(): Promise<LeaveRequestsArchive[]> {
|
||||
return this.prisma.leaveRequestsArchive.findMany();
|
||||
}
|
||||
|
||||
//remove emp_id and use email
|
||||
//fetches an archived employee
|
||||
async findOneArchived(id: number): Promise<LeaveRequestsArchive> {
|
||||
return this.prisma.leaveRequestsArchive.findUniqueOrThrow({ where: { id } });
|
||||
async findOneArchived(id: number): Promise<LeaveRequestViewDto> {
|
||||
const row: LeaveRequestArchiveRow | null = await this.prisma.leaveRequestsArchive.findUnique({
|
||||
where: { id },
|
||||
select: leaveRequestsArchiveSelect,
|
||||
});
|
||||
if(!row) throw new NotFoundException(`Archived Leave Request #${id} not found`);
|
||||
|
||||
const emp = await this.prisma.employees.findUnique({
|
||||
where: { id: row.employee_id },
|
||||
select: { user: {select: { email:true,
|
||||
first_name: true,
|
||||
last_name: true,
|
||||
}}},
|
||||
});
|
||||
const email = emp?.user.email ?? "";
|
||||
const full_name = emp ? `${emp.user.first_name} ${emp.user.last_name}` : "";
|
||||
|
||||
return mapArchiveRowToViewWithDays(row, email, full_name);
|
||||
}
|
||||
}
|
||||
32
src/modules/leave-requests/utils/leave-request.transform.ts
Normal file
32
src/modules/leave-requests/utils/leave-request.transform.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { LeaveRequestViewDto } from "../dtos/leave-request.view.dto";
|
||||
import { mapArchiveRowToView } from "../mappers/leave-requests-archive.mapper";
|
||||
import { mapRowToView } from "../mappers/leave-requests.mapper";
|
||||
import { LeaveRequestArchiveRow } from "./leave-requests-archive.select";
|
||||
import { LeaveRequestRow } from "./leave-requests.select";
|
||||
|
||||
function toUTCDateOnly(date: Date): Date {
|
||||
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
||||
}
|
||||
|
||||
const MS_PER_DAY = 86_400_000;
|
||||
function computeDaysRequested(start_date: Date, end_date?: Date | null): number {
|
||||
const start = toUTCDateOnly(start_date);
|
||||
const end = toUTCDateOnly(end_date ?? start_date);
|
||||
const diff = Math.floor((end.getTime() - start.getTime()) / MS_PER_DAY) + 1;
|
||||
return Math.max(1, diff);
|
||||
}
|
||||
|
||||
/** Active (table leave_requests) : map + days_requested */
|
||||
export function mapRowToViewWithDays(row: LeaveRequestRow): LeaveRequestViewDto {
|
||||
const view = mapRowToView(row);
|
||||
view.days_requested = computeDaysRequested(row.start_date_time, row.end_date_time);
|
||||
return view;
|
||||
}
|
||||
|
||||
/** Archive (table leave_requests_archive) : map + days_requested */
|
||||
export function mapArchiveRowToViewWithDays(row: LeaveRequestArchiveRow, email: string, employee_full_name?: string):
|
||||
LeaveRequestViewDto {
|
||||
const view = mapArchiveRowToView(row, email, employee_full_name!);
|
||||
view.days_requested = computeDaysRequested(row.start_date_time, row.end_date_time);
|
||||
return view;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
|
||||
export const leaveRequestsArchiveSelect = {
|
||||
id: true,
|
||||
leave_request_id: true,
|
||||
archived_at: true,
|
||||
employee_id: true,
|
||||
leave_type: true,
|
||||
start_date_time: true,
|
||||
end_date_time: true,
|
||||
comment: true,
|
||||
approval_status: true,
|
||||
|
||||
} satisfies Prisma.LeaveRequestsArchiveSelect;
|
||||
|
||||
export type LeaveRequestArchiveRow = Prisma.LeaveRequestsArchiveGetPayload<{ select: typeof leaveRequestsArchiveSelect}>;
|
||||
22
src/modules/leave-requests/utils/leave-requests.select.ts
Normal file
22
src/modules/leave-requests/utils/leave-requests.select.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
|
||||
//custom prisma select to avoid employee_id exposure
|
||||
export const leaveRequestsSelect = {
|
||||
id: true,
|
||||
bank_code_id: true,
|
||||
leave_type: true,
|
||||
start_date_time: true,
|
||||
end_date_time: true,
|
||||
comment: true,
|
||||
approval_status: true,
|
||||
employee: { select: {
|
||||
id: true,
|
||||
user: { select: {
|
||||
email: true,
|
||||
first_name: true,
|
||||
last_name: true,
|
||||
}},
|
||||
}},
|
||||
} satisfies Prisma.LeaveRequestsSelect;
|
||||
|
||||
export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>;
|
||||
Loading…
Reference in New Issue
Block a user