feat(business-logic): base setup for business logic implementation, overtime.service and updated timesheets.service to returned overtime infos.
This commit is contained in:
parent
e91fad5105
commit
75615f7c33
|
|
@ -17,6 +17,7 @@ import { PayperiodsModule } from './modules/pay-periods/pay-periods.module';
|
|||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { ArchivalModule } from './modules/archival/archival.module';
|
||||
import { BankCodesModule } from './modules/bank-codes/bank-codes.module';
|
||||
import { OvertimeService } from './business-logic/overtime.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -37,6 +38,6 @@ import { BankCodesModule } from './modules/bank-codes/bank-codes.module';
|
|||
PayperiodsModule,
|
||||
],
|
||||
controllers: [AppController, HealthController],
|
||||
providers: [AppService],
|
||||
providers: [AppService, OvertimeService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
|||
0
src/business-logic/holiday.service.ts
Normal file
0
src/business-logic/holiday.service.ts
Normal file
82
src/business-logic/overtime.service.ts
Normal file
82
src/business-logic/overtime.service.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class OvertimeService {
|
||||
|
||||
private logger = new Logger(OvertimeService.name);
|
||||
private dailyMax = 8; // maximum for regular hours per day
|
||||
private weeklyMax = 40; //maximum for regular hours per week
|
||||
|
||||
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 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<number> {
|
||||
const weekStart = this.getWeekStart(refDate);
|
||||
const weekEnd = this.getWeekEnd(weekStart);
|
||||
|
||||
//fetches all shifts containing hours
|
||||
const shifts = await this.prisma.shifts.findMany({
|
||||
where: { timesheet: { employee_id: employeeId, shift: {
|
||||
every: {date: { gte: weekStart, lte: weekEnd } }
|
||||
},
|
||||
},
|
||||
},
|
||||
select: { start_time: true, end_time: true },
|
||||
});
|
||||
|
||||
//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))
|
||||
.reduce((sum, hours)=> sum+hours, 0);
|
||||
const overtime = Math.max(0, total - this.weeklyMax);
|
||||
|
||||
this.logger.debug(`weekly total = ${total.toFixed(2)}h, weekly Overtime= ${overtime.toFixed(2)}h`);
|
||||
return overtime;
|
||||
}
|
||||
|
||||
//apply modifier to overtime hours
|
||||
calculateOvertimePay(overtimeHours: number, modifier: number): number {
|
||||
const pay = overtimeHours * modifier;
|
||||
this.logger.debug(`Overtime payable hours = ${pay.toFixed(2)} (hours ${overtimeHours}, modifier ${modifier})`);
|
||||
|
||||
return pay;
|
||||
}
|
||||
|
||||
}
|
||||
0
src/business-logic/sick-leave.service.ts
Normal file
0
src/business-logic/sick-leave.service.ts
Normal file
0
src/business-logic/vacation.service.ts
Normal file
0
src/business-logic/vacation.service.ts
Normal file
|
|
@ -3,51 +3,80 @@ import { PrismaService } from 'src/prisma/prisma.service';
|
|||
import { CreateTimesheetDto } from '../dtos/create-timesheet.dto';
|
||||
import { Timesheets, TimesheetsArchive } from '@prisma/client';
|
||||
import { UpdateTimesheetDto } from '../dtos/update-timesheet.dto';
|
||||
import { OvertimeService } from 'src/business-logic/overtime.service';
|
||||
|
||||
@Injectable()
|
||||
export class TimesheetsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly overtime: OvertimeService,
|
||||
) {}
|
||||
|
||||
async create(dto : CreateTimesheetDto): Promise<Timesheets> {
|
||||
const { employee_id, is_approved } = dto;
|
||||
return this.prisma.timesheets.create({
|
||||
data: {
|
||||
employee_id,
|
||||
is_approved: is_approved ?? false,
|
||||
},
|
||||
data: { employee_id, is_approved: is_approved ?? false },
|
||||
include: {
|
||||
employee: {
|
||||
include: { user: true }
|
||||
employee: { include: { user: true }
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findAll(): Promise<Timesheets[]> {
|
||||
return this.prisma.timesheets.findMany({
|
||||
include: {
|
||||
employee: {
|
||||
include: { user: true },
|
||||
},
|
||||
async findAll(): Promise<any[]> {
|
||||
const list = await this.prisma.timesheets.findMany({
|
||||
include: {
|
||||
shift: { include: { bank_code: true } },
|
||||
expense: { include: { bank_code: true } },
|
||||
employee: { include: { user : true } },
|
||||
},
|
||||
});
|
||||
|
||||
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 regularHours = Math.min(8, hours);
|
||||
const dailyOvertime = this.overtime.getDailyOvertimeHours(s.start_time, s.end_time);
|
||||
const payRegular = regularHours * s.bank_code.modifier;
|
||||
const payOvertime = this.overtime.calculateOvertimePay(dailyOvertime, s.bank_code.modifier);
|
||||
return { ...s, hours, payRegular, payOvertime };
|
||||
});
|
||||
const weeklyOvertimeHours = detailedShifts.length
|
||||
? await this.overtime.getWeeklyOvertimeHours(
|
||||
timesheet.employee_id,
|
||||
timesheet.shift[0].date): 0;
|
||||
return { ...timesheet, shift: detailedShifts, weeklyOvertimeHours };
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async findOne(id: number): Promise<Timesheets> {
|
||||
const record = await this.prisma.timesheets.findUnique({
|
||||
async findOne(id: number): Promise<any> {
|
||||
const timesheet = await this.prisma.timesheets.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
employee: {
|
||||
include: {
|
||||
user:true
|
||||
}
|
||||
},
|
||||
},
|
||||
include: {
|
||||
shift: { include: { bank_code: true } },
|
||||
expense: { include: { bank_code: true } },
|
||||
employee: { include: { user: true } },
|
||||
},
|
||||
});
|
||||
if(!record) {
|
||||
if(!timesheet) {
|
||||
throw new NotFoundException(`Timesheet #${id} not found`);
|
||||
}
|
||||
return record;
|
||||
|
||||
const detailedShifts = timesheet.shift.map( s => {
|
||||
const hours = this.overtime.computedHours(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;
|
||||
const payOvertime = this.overtime.calculateOvertimePay(dailyOvertime, s.bank_code.modifier);
|
||||
return { ...s, hours, payRegular, payOvertime };
|
||||
});
|
||||
const weeklyOvertimeHours = detailedShifts.length
|
||||
? await this.overtime.getWeeklyOvertimeHours(
|
||||
timesheet.employee_id,
|
||||
timesheet.shift[0].date): 0;
|
||||
return { ...timesheet, shift: detailedShifts, weeklyOvertimeHours };
|
||||
}
|
||||
|
||||
async update(id: number, dto:UpdateTimesheetDto): Promise<Timesheets> {
|
||||
|
|
@ -59,19 +88,14 @@ export class TimesheetsService {
|
|||
...(employee_id !== undefined && { employee_id }),
|
||||
...(is_approved !== undefined && { is_approved }),
|
||||
},
|
||||
include: {
|
||||
employee: {
|
||||
include: { user: true }
|
||||
},
|
||||
include: { employee: { include: { user: true } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async remove(id: number): Promise<Timesheets> {
|
||||
await this.findOne(id);
|
||||
return this.prisma.timesheets.delete({
|
||||
where: { id },
|
||||
});
|
||||
return this.prisma.timesheets.delete({ where: { id } });
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -85,8 +109,7 @@ export class TimesheetsService {
|
|||
await this.prisma.$transaction(async transaction => {
|
||||
//fetches all timesheets to cutoff
|
||||
const oldSheets = await transaction.timesheets.findMany({
|
||||
where: { shift: { every: { date: { lt: cutoff } },
|
||||
},
|
||||
where: { shift: { every: { date: { lt: cutoff } } },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
|
|
@ -106,14 +129,10 @@ export class TimesheetsService {
|
|||
}));
|
||||
|
||||
//copying data from timesheets table to archive table
|
||||
await transaction.timesheetsArchive.createMany({
|
||||
data: archiveDate,
|
||||
});
|
||||
await transaction.timesheetsArchive.createMany({ data: archiveDate });
|
||||
|
||||
//removing data from timesheets table
|
||||
await transaction.timesheets.deleteMany({
|
||||
where: { id: { in: oldSheets.map(s => s.id) } },
|
||||
});
|
||||
await transaction.timesheets.deleteMany({ where: { id: { in: oldSheets.map(s => s.id) } } });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user