feat(business-logic): implementation of vacation.service.ts, sick-leave.service.ts and update leave-requests. service
This commit is contained in:
parent
2e6bafeb18
commit
5766715d77
|
|
@ -0,0 +1,61 @@
|
|||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
|
||||
@Injectable()
|
||||
export class SickLeaveService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
private readonly logger = new Logger(SickLeaveService.name);
|
||||
|
||||
async calculateSickLeavePay(employeeId: number, startDate: Date, daysRequested: number, modifier: number): Promise<number> {
|
||||
//sets the year to jan 1st to dec 31st
|
||||
const periodStart = new Date(startDate.getFullYear(), 0, 1);
|
||||
const periodEnd = startDate;
|
||||
|
||||
//fetches all shifts of a selected employee
|
||||
const shifts = await this.prisma.shifts.findMany({
|
||||
where: {
|
||||
timesheet: { employee_id: employeeId },
|
||||
date: { gte: periodStart, lte: periodEnd},
|
||||
},
|
||||
select: { date: true },
|
||||
});
|
||||
|
||||
//count the amount of worked days
|
||||
const workedDates = new Set(
|
||||
shifts.map(shift => shift.date.toISOString().slice(0,10))
|
||||
);
|
||||
const daysWorked = workedDates.size;
|
||||
this.logger.debug(`Sick leave: days worked= ${daysWorked} in ${periodStart.toDateString()} -> ${periodEnd.toDateString()}`);
|
||||
|
||||
//less than 30 worked days returns 0
|
||||
if (daysWorked < 30) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
//default 3 days allowed after 30 worked days
|
||||
let acquiredDays = 3;
|
||||
|
||||
//identify the date of the 30th worked day
|
||||
const orderedDates = Array.from(workedDates).sort();
|
||||
const thresholdDate = new Date(orderedDates[29]); // index 29 is the 30th day
|
||||
|
||||
//calculate each completed month, starting the 1st of the next month
|
||||
const firstBonusDate = new Date(thresholdDate.getFullYear(), thresholdDate.getMonth() +1, 1);
|
||||
let months = (periodEnd.getFullYear() - firstBonusDate.getFullYear()) * 12 +
|
||||
(periodEnd.getMonth() - firstBonusDate.getMonth()) + 1;
|
||||
if(months < 0) months = 0;
|
||||
acquiredDays += months;
|
||||
|
||||
//cap of 10 days
|
||||
if (acquiredDays > 10) acquiredDays = 10;
|
||||
|
||||
this.logger.debug(`Sick leave: threshold Date = ${thresholdDate.toDateString()}, bonusMonths = ${months}, acquired Days = ${acquiredDays}`);
|
||||
|
||||
const payableDays = Math.min(acquiredDays, daysRequested);
|
||||
const rawHours = payableDays * 8 * modifier;
|
||||
const rounded = Math.round(rawHours * 4) / 4;
|
||||
this.logger.debug(`Sick leave pay: days= ${payableDays}, modifier= ${modifier}, hours= ${rounded}`);
|
||||
return rounded;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
import { Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
|
||||
@Injectable()
|
||||
export class VacationService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
private readonly logger = new Logger(VacationService.name);
|
||||
/**
|
||||
* Calculate the ammount allowed for vacation days.
|
||||
*
|
||||
* @param employeeId employee ID
|
||||
* @param startDate first day of vacation
|
||||
* @param daysRequested number of days requested
|
||||
* @param modifier Coefficient of hours(1)
|
||||
* @returns amount of payable hours
|
||||
*/
|
||||
async calculateVacationPay(employeeId: number, startDate: Date, daysRequested: number, modifier: number): Promise<number> {
|
||||
//fetch hiring date
|
||||
const employee = await this.prisma.employees.findUnique({
|
||||
where: { id: employeeId },
|
||||
select: { first_work_day: true },
|
||||
});
|
||||
if(!employee) {
|
||||
throw new NotFoundException(`Employee #${employeeId} not found`);
|
||||
}
|
||||
const hireDate = employee.first_work_day;
|
||||
|
||||
//sets "year" to may 1st to april 30th
|
||||
//check if hiring date is in may or later, we use hiring year, otherwise we use the year before
|
||||
const yearOfRequest = startDate.getMonth() >= 4
|
||||
? startDate.getFullYear() : startDate.getFullYear() -1;
|
||||
const periodStart = new Date(yearOfRequest, 4, 1); //may = 4
|
||||
const periodEnd = new Date(yearOfRequest + 1, 4, 0); //day 0 of may == april 30th
|
||||
|
||||
this.logger.debug(`Vacation period for request: ${periodStart.toDateString()} -> ${periodEnd.toDateString()}`);
|
||||
|
||||
//steps to reach to get more vacation weeks in years
|
||||
const checkpoint = [5, 10, 15];
|
||||
const anniversaries = checkpoint.map(years => {
|
||||
const anniversaryDate = new Date(hireDate);
|
||||
anniversaryDate.setFullYear(anniversaryDate.getFullYear() + years);
|
||||
return anniversaryDate;
|
||||
}).filter(d => d>= periodStart && d <= periodEnd).sort((a,b) => a.getTime() - b.getTime());
|
||||
|
||||
this.logger.debug(`anniversatries steps during the period: ${anniversaries.map(date => date.toDateString()).join(',') || 'aucun'}`);
|
||||
|
||||
const boundaries = [periodStart, ...anniversaries,periodEnd];
|
||||
//calculate prorata per segment
|
||||
let totalVacationDays = 0;
|
||||
const msPerDay = 1000 * 60 * 60 * 24;
|
||||
|
||||
for (let i = 0; i < boundaries.length -1; i++) {
|
||||
const segmentStart = boundaries[i];
|
||||
const segmentEnd = boundaries[i+1];
|
||||
|
||||
//number of days in said segment
|
||||
const daysInSegment = Math.round((segmentEnd.getTime() - segmentStart.getTime())/ msPerDay);
|
||||
const yearsSinceHire = (segmentStart.getFullYear() - hireDate.getFullYear()) -
|
||||
(segmentStart < new Date(segmentStart.getFullYear(), hireDate.getMonth()) ? 1 : 0);
|
||||
let allocDays: number;
|
||||
if(yearsSinceHire < 5) allocDays = 10;
|
||||
else if(yearsSinceHire < 10) allocDays = 15;
|
||||
else if(yearsSinceHire < 15) allocDays = 20;
|
||||
else allocDays = 25;
|
||||
|
||||
//prorata for said segment
|
||||
const prorata = (allocDays / 365) * daysInSegment;
|
||||
totalVacationDays += prorata;
|
||||
}
|
||||
//compares allowed vacation pools with requested days
|
||||
const payableDays = Math.min(totalVacationDays, daysRequested);
|
||||
|
||||
|
||||
const rawHours = payableDays * 8 * modifier;
|
||||
const roundedHours = Math.round(rawHours * 4) / 4;
|
||||
this.logger.debug(`Vacation pay: entitledDays=${totalVacationDays.toFixed(2)}, requestedDays=${daysRequested},
|
||||
payableDays=${payableDays.toFixed(2)}, hours=${roundedHours}`);
|
||||
|
||||
return roundedHours;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ import { LeaveRequestController } from "./controllers/leave-requests.controller"
|
|||
import { LeaveRequestsService } from "./services/leave-requests.service";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { HolidayService } from "src/business-logic/holiday.service";
|
||||
import { VacationService } from "src/business-logic/vacation.service";
|
||||
import { SickLeaveService } from "src/business-logic/sick-leave.service";
|
||||
|
||||
@Module({
|
||||
controllers: [LeaveRequestController],
|
||||
|
|
@ -10,6 +12,8 @@ import { HolidayService } from "src/business-logic/holiday.service";
|
|||
LeaveRequestsService,
|
||||
PrismaService,
|
||||
HolidayService,
|
||||
VacationService,
|
||||
SickLeaveService,
|
||||
],
|
||||
exports: [ LeaveRequestsService],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { CreateLeaveRequestsDto } from "../dtos/create-leave-requests.dto";
|
||||
import { LeaveRequests, LeaveRequestsArchive } from "@prisma/client";
|
||||
import { UpdateLeaveRequestsDto } from "../dtos/update-leave-requests.dto";
|
||||
import { HolidayService } from "src/business-logic/holiday.service";
|
||||
import { VacationService } from "src/business-logic/vacation.service";
|
||||
import { SickLeaveService } from "src/business-logic/sick-leave.service";
|
||||
|
||||
@Injectable()
|
||||
export class LeaveRequestsService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly holidayService: HolidayService,
|
||||
private readonly vacationService: VacationService,
|
||||
private readonly sickLeaveService: SickLeaveService
|
||||
) {}
|
||||
|
||||
async create(dto: CreateLeaveRequestsDto): Promise<LeaveRequests> {
|
||||
|
|
@ -20,7 +24,9 @@ export class LeaveRequestsService {
|
|||
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 } } },
|
||||
include: { employee: { include: { user: true } },
|
||||
bank_code: true
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -31,18 +37,38 @@ export class LeaveRequestsService {
|
|||
},
|
||||
});
|
||||
|
||||
const msPerDay = 1000 * 60 * 60 * 24;
|
||||
|
||||
return Promise.all(
|
||||
list.map(async request => {
|
||||
if(request.bank_code?.type === 'holiday') {
|
||||
const cost = await this.holidayService.calculateHolidayPay(
|
||||
request.employee_id,
|
||||
request.start_date_time,
|
||||
request.bank_code.modifier
|
||||
);
|
||||
return { ...request, cost };
|
||||
// end_date fallback
|
||||
const endDate = request.end_date_time ?? request.start_date_time;
|
||||
|
||||
//Requested days
|
||||
const diffDays = Math.round((endDate.getTime() - request.start_date_time.getTime()) / msPerDay) +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}`);
|
||||
}
|
||||
return request;
|
||||
}),
|
||||
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,diffDays, modifier );
|
||||
break;
|
||||
case 'sick' :
|
||||
cost = await this.sickLeaveService.calculateSickLeavePay( request.employee_id, request.start_date_time, diffDays, modifier );
|
||||
break;
|
||||
default:
|
||||
cost = diffDays * modifier;
|
||||
}
|
||||
return {...request, daysRequested: diffDays, cost };
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -56,17 +82,34 @@ export class LeaveRequestsService {
|
|||
if(!request) {
|
||||
throw new NotFoundException(`LeaveRequest #${id} not found`);
|
||||
}
|
||||
|
||||
//search for leave type. if holiday
|
||||
if (request.bank_code?.type === 'holiday') {
|
||||
const cost = await this.holidayService.calculateHolidayPay(
|
||||
request.employee_id,
|
||||
request.start_date_time,
|
||||
request.bank_code.modifier,
|
||||
);
|
||||
return { ...request, cost };
|
||||
//validation and fallback for end_date_time
|
||||
const endDate = request.end_date_time ?? request.start_date_time;
|
||||
|
||||
//calculate included days
|
||||
const msPerDay = 1000 * 60 * 60 * 24;
|
||||
const diffDays = Math.floor((endDate.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}`);
|
||||
}
|
||||
return request;
|
||||
const modifier = request.bank_code.modifier;
|
||||
|
||||
//calculate cost based on bank_code types
|
||||
let cost = diffDays * 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, diffDays, modifier );
|
||||
break;
|
||||
case 'sick':
|
||||
cost = await this.sickLeaveService.calculateSickLeavePay( request.employee_id, request.start_date_time, diffDays, modifier );
|
||||
break;
|
||||
default:
|
||||
cost = diffDays * modifier;
|
||||
}
|
||||
return {...request, daysRequested: diffDays, cost };
|
||||
}
|
||||
|
||||
async update(
|
||||
|
|
@ -78,12 +121,12 @@ export class LeaveRequestsService {
|
|||
return this.prisma.leaveRequests.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...(employee_id !== undefined && { employee_id }),
|
||||
...(leave_type !== undefined && { leave_type } ),
|
||||
...(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 }),
|
||||
...(end_date_time !== undefined && { end_date_time }),
|
||||
...(comment !== undefined && { comment }),
|
||||
...(approval_status !== undefined && { approval_status }),
|
||||
},
|
||||
include: { employee: { include: { user:true } } },
|
||||
});
|
||||
|
|
@ -112,14 +155,14 @@ export class LeaveRequestsService {
|
|||
//copy unto archive table
|
||||
await transaction.leaveRequestsArchive.createMany({
|
||||
data: expired.map(request => ({
|
||||
leave_request_id: request.id,
|
||||
employee_id: request.employee_id,
|
||||
leave_type: request.leave_type,
|
||||
start_date_time: request.start_date_time,
|
||||
end_date_time: request.end_date_time,
|
||||
comment: request.comment,
|
||||
approval_status: request.approval_status,
|
||||
})),
|
||||
leave_request_id: request.id,
|
||||
employee_id: request.employee_id,
|
||||
leave_type: request.leave_type,
|
||||
start_date_time: request.start_date_time,
|
||||
end_date_time: request.end_date_time,
|
||||
comment: request.comment,
|
||||
approval_status: request.approval_status,
|
||||
})),
|
||||
});
|
||||
//delete from leave_requests table
|
||||
await transaction.leaveRequests.deleteMany({
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user