diff --git a/src/common/utils/date-utils.ts b/src/common/utils/date-utils.ts new file mode 100644 index 0000000..e8aeb69 --- /dev/null +++ b/src/common/utils/date-utils.ts @@ -0,0 +1,50 @@ + +//lenght of a shift, rouded to nearest 'x' minute +export function computeHours(start: Date, end: Date, roundToMinutes?: number): number { + const diffMs = end.getTime() - start.getTime(); + const totalMinutes = diffMs / 60000; + const minutes = roundToMinutes ? + Math.round(totalMinutes / roundToMinutes) * roundToMinutes : + totalMinutes; + return +(minutes / 60).toFixed(2); +} + +//round the amount of hours to quarter +export function roundToQuarterHour(hours: number): number { + return Math.round(hours *4) / 4; +} + +//calculate the number of the week (1 or 2) +export function computeWeekNumber(periodStart: Date, targetDate: Date): number { + const days = Math.floor( targetDate.getTime() - periodStart.getTime()) / + (1000 * 60 * 60 * 24); + return Math.floor(days / 7) +1; +} + +//Date format YYY-MM-DD +export function formatDateISO(d:Date): string { + return d.toISOString().split('T')[0]; +} + +//fetch firts day of the week (Sunday) +export function getWeekStart(date:Date, firstDayOfWeek = 0): Date { + const d = new Date(date); + const day = d.getDay(); + const diff = (day < firstDayOfWeek ? 7 : 0) + (day - firstDayOfWeek); + d.setDate(d.getDate() - diff); + d.setHours(0,0,0,0); + return d; +} + +//fetch last day of the week (Saturday) +export function getWeekEnd(startOfWeek: Date): Date { + const d = new Date(startOfWeek); + d.setDate(d.getDate() + 6); + d.setHours(23,59,59,999); + return d; +} + +//returns january 1st of the selected date's year +export function getYearStart(date:Date): Date { + return new Date(date.getFullYear(),0,1,0,0,0,0); +} diff --git a/src/modules/business-logics/services/after-hours.service.ts b/src/modules/business-logics/services/after-hours.service.ts index 0f857fd..bc0e271 100644 --- a/src/modules/business-logics/services/after-hours.service.ts +++ b/src/modules/business-logics/services/after-hours.service.ts @@ -2,7 +2,7 @@ import { BadRequestException, Injectable, Logger } from "@nestjs/common"; import { PrismaService } from "../../../prisma/prisma.service"; -//THIS SERVICE IS NOT USED RULES TO BE DETERMINED WITH MIKE/HR/ACCOUNTING +//THIS SERVICE IS NOT USED, RULES TO BE DETERMINED WITH MIKE/HR/ACCOUNTING @Injectable() export class AfterHoursService { private readonly logger = new Logger(AfterHoursService.name); diff --git a/src/modules/business-logics/services/holiday.service.ts b/src/modules/business-logics/services/holiday.service.ts index cf898ad..2d377b6 100644 --- a/src/modules/business-logics/services/holiday.service.ts +++ b/src/modules/business-logics/services/holiday.service.ts @@ -1,5 +1,6 @@ import { Injectable, Logger } from "@nestjs/common"; import { PrismaService } from "../../../prisma/prisma.service"; +import { computeHours, getWeekStart } from "src/common/utils/date-utils"; @Injectable() export class HolidayService { @@ -7,32 +8,15 @@ export class HolidayService { constructor(private readonly prisma: PrismaService) {} - //return the sunday of the current week that includes the holiday - private getWeekStart(date: Date): Date { - const day = new Date(date); - const offset = day.getDay(); - day.setDate(day.getDate() - offset); - day.setHours(0,0,0,0); - return day; - } - - //rounds minutes to 5s - private computeHours(start: Date, end: Date): number { - const durationMS = end.getTime() - start.getTime(); - const totalMinutes = durationMS / 60000; - const rounded = Math.round(totalMinutes / 5) * 5; - return rounded / 60; - } - private async computeHoursPrevious4Weeks(employeeId: number, holidayDate: Date): Promise { //sets the end of the window to 1ms before the week with the holiday - const holidayWeekStart = this.getWeekStart(holidayDate); + const holidayWeekStart = getWeekStart(holidayDate); const windowEnd = new Date(holidayWeekStart.getTime() - 1); //sets the start of the window to 28 days ( 4 completed weeks ) before the week with the holiday const windowStart = new Date(windowEnd.getTime() - 28 * 24 * 60 * 60000 + 1 ) const validCodes = ['G1', 'G45', 'G56', 'G104', 'G105', 'G700']; - //fetches all shift of the employee in said window ( 4 completed weeks ) + //fetches all shift of the employee in said window ( 4 previous completed weeks ) const shifts = await this.prisma.shifts.findMany({ where: { timesheet: { employee_id: employeeId } , date: { gte: windowStart, lte: windowEnd }, @@ -41,16 +25,16 @@ export class HolidayService { select: { date: true, start_time: true, end_time: true }, }); - const totalHours = shifts.map(s => this.computeHours(s.start_time, s.end_time)).reduce((sum, h)=> sum + h, 0); + const totalHours = shifts.map(s => computeHours(s.start_time, s.end_time)).reduce((sum, h)=> sum + h, 0); const dailyHours = totalHours / 20; return dailyHours; } async calculateHolidayPay( employeeId: number, holidayDate: Date, modifier: number): Promise { - const hours = await this. computeHoursPrevious4Weeks(employeeId, holidayDate); + const hours = await this.computeHoursPrevious4Weeks(employeeId, holidayDate); const dailyRate = Math.min(hours, 8); - this.logger.debug(`Holiday pay calculation: hours=${hours.toFixed(2)}`); + this.logger.debug(`Holiday pay calculation: hours= ${hours.toFixed(2)}`); return dailyRate * modifier; } } \ No newline at end of file diff --git a/src/modules/business-logics/services/overtime.service.ts b/src/modules/business-logics/services/overtime.service.ts index 8107c11..b58c511 100644 --- a/src/modules/business-logics/services/overtime.service.ts +++ b/src/modules/business-logics/services/overtime.service.ts @@ -1,5 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { PrismaService } from '../../../prisma/prisma.service'; +import { getWeekStart, getWeekEnd, computeHours } from 'src/common/utils/date-utils'; @Injectable() export class OvertimeService { @@ -10,47 +11,18 @@ export class OvertimeService { constructor(private prisma: PrismaService) {} - // calculate decimal hours rounded to nearest 5 min - computedHours(start: Date, end: Date): number { - const durationMs = end.getTime() - start.getTime(); - const totalMinutes = durationMs / 60000; - - //rounded to 5 min - const rounded = Math.round(totalMinutes / 5) * 5; - const hours = rounded / 60; - this.logger.debug(`computedHours: raw=${totalMinutes.toFixed(1)}min rounded = ${rounded}min (${hours.toFixed(2)}h)`); - return hours; - } - //calculate Daily overtime getDailyOvertimeHours(start: Date, end: Date): number { - const hours = this.computedHours(start, end); + const hours = computeHours(start, end, 5); const overtime = Math.max(0, hours - this.dailyMax); this.logger.debug(`getDailyOvertimeHours : ${overtime.toFixed(2)}h (threshold ${this.dailyMax})`); return overtime; } - //sets first day of the week to be sunday - private getWeekStart(date:Date): Date { - const d = new Date(date); - const day = d.getDay(); // return sunday = 0, monday = 1, etc - d.setDate(d.getDate() - day); - d.setHours(0,0,0,0,); // puts start of the week at sunday morning at 00:00 - return d; - } - - //sets last day of the week to be saturday - private getWeekEnd(startDate:Date): Date { - const d = new Date(startDate); - d.setDate(d.getDate() +6); //sets last day to be saturday - d.setHours(23,59,59,999); //puts end of the week at saturday night at 00:00 minus 1ms - return d; - } - //calculate Weekly overtime async getWeeklyOvertimeHours(employeeId: number, refDate: Date): Promise { - const weekStart = this.getWeekStart(refDate); - const weekEnd = this.getWeekEnd(weekStart); + const weekStart = getWeekStart(refDate); + const weekEnd = getWeekEnd(weekStart); //fetches all shifts containing hours const shifts = await this.prisma.shifts.findMany({ @@ -63,7 +35,7 @@ export class OvertimeService { }); //calculate total hours of those shifts minus weekly Max to find total overtime hours - const total = shifts.map(shift => this.computedHours(shift.start_time, shift.end_time)) + const total = shifts.map(shift => computeHours(shift.start_time, shift.end_time, 5)) .reduce((sum, hours)=> sum+hours, 0); const overtime = Math.max(0, total - this.weeklyMax); diff --git a/src/modules/business-logics/services/sick-leave.service.ts b/src/modules/business-logics/services/sick-leave.service.ts index a36bf29..1c8d9ee 100644 --- a/src/modules/business-logics/services/sick-leave.service.ts +++ b/src/modules/business-logics/services/sick-leave.service.ts @@ -1,5 +1,6 @@ import { Injectable, Logger } from "@nestjs/common"; import { PrismaService } from "../../../prisma/prisma.service"; +import { getYearStart, roundToQuarterHour } from "src/common/utils/date-utils"; @Injectable() export class SickLeaveService { @@ -7,10 +8,10 @@ export class SickLeaveService { private readonly logger = new Logger(SickLeaveService.name); - async calculateSickLeavePay(employeeId: number, startDate: Date, daysRequested: number, modifier: number): Promise { + async calculateSickLeavePay(employeeId: number, referenceDate: Date, daysRequested: number, modifier: number): Promise { //sets the year to jan 1st to dec 31st - const periodStart = new Date(startDate.getFullYear(), 0, 1); - const periodEnd = startDate; + const periodStart = getYearStart(referenceDate); + const periodEnd = referenceDate; //fetches all shifts of a selected employee const shifts = await this.prisma.shifts.findMany({ @@ -54,7 +55,7 @@ export class SickLeaveService { const payableDays = Math.min(acquiredDays, daysRequested); const rawHours = payableDays * 8 * modifier; - const rounded = Math.round(rawHours * 4) / 4; + const rounded = roundToQuarterHour(rawHours) this.logger.debug(`Sick leave pay: days= ${payableDays}, modifier= ${modifier}, hours= ${rounded}`); return rounded; } diff --git a/src/modules/pay-periods/services/pay-periods-overview.service.ts b/src/modules/pay-periods/services/pay-periods-overview.service.ts index cc3ce0c..3fc984c 100644 --- a/src/modules/pay-periods/services/pay-periods-overview.service.ts +++ b/src/modules/pay-periods/services/pay-periods-overview.service.ts @@ -2,6 +2,7 @@ import { Injectable, NotFoundException } from "@nestjs/common"; import { EmployeePeriodOverviewDto } from "../dtos/overview-employee-period.dto"; import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto"; import { PrismaService } from "src/prisma/prisma.service"; +import { computeHours } from "src/common/utils/date-utils"; @Injectable() export class PayPeriodsOverviewService { @@ -40,7 +41,7 @@ export class PayPeriodsOverviewService { const user = employee_record.user; const employee_id = employee_record.user_id; const employee_name = `${user.first_name} ${user.last_name}`; - const hours = (shift.end_time.getTime() - shift.start_time.getTime() / 3600000); + const hours = computeHours(shift.start_time, shift.end_time); //check if employee had prior shifts and adds hours of found shift to the total hours if (map.has(employee_id)) { diff --git a/src/modules/shifts/validation/services/shifts-validation.service.ts b/src/modules/shifts/validation/services/shifts-validation.service.ts index 5d05f40..3692c0a 100644 --- a/src/modules/shifts/validation/services/shifts-validation.service.ts +++ b/src/modules/shifts/validation/services/shifts-validation.service.ts @@ -1,4 +1,5 @@ import { Injectable, NotFoundException } from "@nestjs/common"; +import { computeHours } from "src/common/utils/date-utils"; import { PrismaService } from "src/prisma/prisma.service"; export interface ValidationRow { @@ -16,12 +17,6 @@ export interface ValidationRow { export class ShiftsValidationService { constructor(private readonly prisma: PrismaService) {} - private computeHours(start: Date, end: Date): number { - const diffMs = end.getTime() - start.getTime(); - const hours = diffMs / 1000 / 3600; - return parseFloat(hours.toFixed(2)); - } - async getSummary(periodId: number): Promise { //fetch pay-period to display const period = await this.prisma.payPeriods.findUnique({ @@ -77,7 +72,7 @@ export class ShiftsValidationService { isValidated: false, }; } - const hours = this.computeHours(s.start_time, s.end_time); + const hours = computeHours(s.start_time, s.end_time); switch(s.bank_code.type) { case 'regular' : row.totalRegularHrs += hours; diff --git a/src/modules/timesheets/services/timesheets.service.ts b/src/modules/timesheets/services/timesheets.service.ts index 48b5703..4ee0c73 100644 --- a/src/modules/timesheets/services/timesheets.service.ts +++ b/src/modules/timesheets/services/timesheets.service.ts @@ -4,6 +4,7 @@ import { CreateTimesheetDto } from '../dtos/create-timesheet.dto'; import { Timesheets, TimesheetsArchive } from '@prisma/client'; import { UpdateTimesheetDto } from '../dtos/update-timesheet.dto'; import { OvertimeService } from 'src/modules/business-logics/services/overtime.service'; +import { computeHours } from 'src/common/utils/date-utils'; @Injectable() export class TimesheetsService { @@ -35,7 +36,7 @@ export class TimesheetsService { return Promise.all( list.map(async timesheet => { const detailedShifts = timesheet.shift.map(s => { - const hours = this.overtime.computedHours(s.start_time, s.end_time); + const hours = computeHours(s.start_time, s.end_time,5); const regularHours = Math.min(8, hours); const dailyOvertime = this.overtime.getDailyOvertimeHours(s.start_time, s.end_time); const payRegular = regularHours * s.bank_code.modifier; @@ -65,7 +66,7 @@ export class TimesheetsService { } const detailedShifts = timesheet.shift.map( s => { - const hours = this.overtime.computedHours(s.start_time, s.end_time); + const hours = computeHours(s.start_time, s.end_time); const regularHours = Math.min(8, hours); const dailyOvertime = this.overtime.getDailyOvertimeHours(s.start_time, s.end_time); const payRegular = regularHours * s.bank_code.modifier;