fix(leave-requests): fix depencies for the Leave-Requests Module
This commit is contained in:
parent
79153c6de3
commit
caf03d8d68
|
|
@ -7,6 +7,7 @@ import { VacationLeaveRequestsService } from "./services/vacation-leave-requests
|
||||||
import { SickLeaveRequestsService } from "./services/sick-leave-requests.service";
|
import { SickLeaveRequestsService } from "./services/sick-leave-requests.service";
|
||||||
import { LeaveRequestsService } from "./services/leave-request.service";
|
import { LeaveRequestsService } from "./services/leave-request.service";
|
||||||
import { ShiftsModule } from "../shifts/shifts.module";
|
import { ShiftsModule } from "../shifts/shifts.module";
|
||||||
|
import { LeaveRequestsUtils } from "./utils/leave-request.util";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [BusinessLogicsModule, ShiftsModule],
|
imports: [BusinessLogicsModule, ShiftsModule],
|
||||||
|
|
@ -16,7 +17,8 @@ import { ShiftsModule } from "../shifts/shifts.module";
|
||||||
SickLeaveRequestsService,
|
SickLeaveRequestsService,
|
||||||
HolidayLeaveRequestsService,
|
HolidayLeaveRequestsService,
|
||||||
LeaveRequestsService,
|
LeaveRequestsService,
|
||||||
PrismaService
|
PrismaService,
|
||||||
|
LeaveRequestsUtils,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
LeaveRequestsService,
|
LeaveRequestsService,
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,27 @@
|
||||||
import { LeaveRequestsService, normalizeDates, toDateOnly } from './leave-request.service';
|
|
||||||
import { UpsertLeaveRequestDto, UpsertResult } from '../dtos/upsert-leave-request.dto';
|
import { UpsertLeaveRequestDto, UpsertResult } from '../dtos/upsert-leave-request.dto';
|
||||||
import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto';
|
import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto';
|
||||||
import { BadRequestException, Inject, Injectable, forwardRef } from '@nestjs/common';
|
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { LeaveApprovalStatus, LeaveTypes } from '@prisma/client';
|
import { LeaveApprovalStatus, LeaveTypes } from '@prisma/client';
|
||||||
import { HolidayService } from 'src/modules/business-logics/services/holiday.service';
|
import { HolidayService } from 'src/modules/business-logics/services/holiday.service';
|
||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
import { mapRowToView } from '../mappers/leave-requests.mapper';
|
import { mapRowToView } from '../mappers/leave-requests.mapper';
|
||||||
import { leaveRequestsSelect } from '../utils/leave-requests.select';
|
import { leaveRequestsSelect } from '../utils/leave-requests.select';
|
||||||
|
import { LeaveRequestsUtils, normalizeDates, toDateOnly } from '../utils/leave-request.util';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HolidayLeaveRequestsService {
|
export class HolidayLeaveRequestsService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly holidayService: HolidayService,
|
|
||||||
@Inject(forwardRef(() => LeaveRequestsService)) private readonly leaveService: LeaveRequestsService,
|
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly holidayService: HolidayService,
|
||||||
|
private readonly leaveUtils: LeaveRequestsUtils,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
//handle distribution to the right service according to the selected action
|
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
||||||
async handle(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
|
||||||
switch (dto.action) {
|
|
||||||
case 'create':
|
|
||||||
return this.createHoliday(dto);
|
|
||||||
case 'update':
|
|
||||||
return this.leaveService.update(dto, LeaveTypes.HOLIDAY);
|
|
||||||
case 'delete':
|
|
||||||
return this.leaveService.delete(dto, LeaveTypes.HOLIDAY);
|
|
||||||
default:
|
|
||||||
throw new BadRequestException(`Unknown action: ${dto.action}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createHoliday(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
|
||||||
const email = dto.email.trim();
|
const email = dto.email.trim();
|
||||||
const employee_id = await this.leaveService.resolveEmployeeIdByEmail(email);
|
const employee_id = await this.leaveUtils.resolveEmployeeIdByEmail(email);
|
||||||
const bank_code = await this.leaveService.resolveBankCodeByType(LeaveTypes.HOLIDAY);
|
const bank_code = await this.leaveUtils.resolveBankCodeByType(LeaveTypes.HOLIDAY);
|
||||||
|
if(!bank_code) throw new NotFoundException(`bank_code not found`);
|
||||||
const dates = normalizeDates(dto.dates);
|
const dates = normalizeDates(dto.dates);
|
||||||
if (!dates.length) throw new BadRequestException('Dates array must not be empty');
|
if (!dates.length) throw new BadRequestException('Dates array must not be empty');
|
||||||
|
|
||||||
|
|
@ -74,7 +61,7 @@ export class HolidayLeaveRequestsService {
|
||||||
|
|
||||||
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
||||||
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
||||||
await this.leaveService.syncShift(email, employee_id, iso_date, hours,LeaveTypes.HOLIDAY, row.comment);
|
await this.leaveUtils.syncShift(email, employee_id, iso_date, hours,LeaveTypes.HOLIDAY, row.comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
created.push({ ...mapRowToView(row), action: 'create' });
|
created.push({ ...mapRowToView(row), action: 'create' });
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { BadRequestException, Inject, Injectable, NotFoundException, forwardRef } from "@nestjs/common";
|
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||||
import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
|
import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
|
||||||
import { roundToQuarterHour } from "src/common/utils/date-utils";
|
import { roundToQuarterHour } from "src/common/utils/date-utils";
|
||||||
import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
|
import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
|
||||||
|
|
@ -11,137 +11,58 @@ import { VacationLeaveRequestsService } from "./vacation-leave-requests.service"
|
||||||
import { HolidayService } from "src/modules/business-logics/services/holiday.service";
|
import { HolidayService } from "src/modules/business-logics/services/holiday.service";
|
||||||
import { SickLeaveService } from "src/modules/business-logics/services/sick-leave.service";
|
import { SickLeaveService } from "src/modules/business-logics/services/sick-leave.service";
|
||||||
import { VacationService } from "src/modules/business-logics/services/vacation.service";
|
import { VacationService } from "src/modules/business-logics/services/vacation.service";
|
||||||
import { ShiftsCommandService } from "src/modules/shifts/services/shifts-command.service";
|
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
import { LeaveRequestsUtils, normalizeDates, toDateOnly, toISODateKey } from "../utils/leave-request.util";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LeaveRequestsService {
|
export class LeaveRequestsService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly holidayLeaveService: HolidayLeaveRequestsService,
|
||||||
private readonly holidayService: HolidayService,
|
private readonly holidayService: HolidayService,
|
||||||
private readonly sickLogic: SickLeaveService,
|
private readonly sickLogic: SickLeaveService,
|
||||||
|
private readonly sickLeaveService: SickLeaveRequestsService,
|
||||||
|
private readonly vacationLeaveService: VacationLeaveRequestsService,
|
||||||
private readonly vacationLogic: VacationService,
|
private readonly vacationLogic: VacationService,
|
||||||
@Inject(forwardRef(() => HolidayLeaveRequestsService)) private readonly holidayLeaveService: HolidayLeaveRequestsService,
|
private readonly leaveUtils: LeaveRequestsUtils,
|
||||||
@Inject(forwardRef(() => SickLeaveRequestsService)) private readonly sickLeaveService: SickLeaveRequestsService,
|
|
||||||
private readonly shiftsCommand: ShiftsCommandService,
|
|
||||||
@Inject(forwardRef(() => VacationLeaveRequestsService)) private readonly vacationLeaveService: VacationLeaveRequestsService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
//handle distribution to the right service according to the selected type and action
|
||||||
async handle(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
async handle(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
||||||
switch (dto.type) {
|
switch (dto.type) {
|
||||||
case LeaveTypes.HOLIDAY:
|
case LeaveTypes.HOLIDAY:
|
||||||
return this.holidayLeaveService.handle(dto);
|
if( dto.action === 'create'){
|
||||||
|
return this.holidayLeaveService.create(dto);
|
||||||
|
} else if (dto.action === 'update') {
|
||||||
|
return this.update(dto, LeaveTypes.HOLIDAY);
|
||||||
|
} else if (dto.action === 'delete'){
|
||||||
|
return this.delete(dto, LeaveTypes.HOLIDAY);
|
||||||
|
}
|
||||||
case LeaveTypes.VACATION:
|
case LeaveTypes.VACATION:
|
||||||
return this.vacationLeaveService.handle(dto);
|
if( dto.action === 'create'){
|
||||||
|
return this.vacationLeaveService.create(dto);
|
||||||
|
} else if (dto.action === 'update') {
|
||||||
|
return this.update(dto, LeaveTypes.VACATION);
|
||||||
|
} else if (dto.action === 'delete'){
|
||||||
|
return this.delete(dto, LeaveTypes.VACATION);
|
||||||
|
}
|
||||||
case LeaveTypes.SICK:
|
case LeaveTypes.SICK:
|
||||||
return this.sickLeaveService.handle(dto);
|
if( dto.action === 'create'){
|
||||||
|
return this.sickLeaveService.create(dto);
|
||||||
|
} else if (dto.action === 'update') {
|
||||||
|
return this.update(dto, LeaveTypes.SICK);
|
||||||
|
} else if (dto.action === 'delete'){
|
||||||
|
return this.delete(dto, LeaveTypes.SICK);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new BadRequestException(`Unsupported leave type: ${dto.type}`);
|
throw new BadRequestException(`Unsupported leave type: ${dto.type} or action: ${dto.action}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 resolveBankCodeByType(type: LeaveTypes) {
|
|
||||||
const bankCode = await this.prisma.bankCodes.findFirst({
|
|
||||||
where: { type },
|
|
||||||
select: { id: true, bank_code: true, modifier: true },
|
|
||||||
});
|
|
||||||
if (!bankCode) {
|
|
||||||
throw new BadRequestException(`Bank code type "${type}" not found`);
|
|
||||||
}
|
|
||||||
return bankCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
async syncShift(
|
|
||||||
email: string,
|
|
||||||
employee_id: number,
|
|
||||||
iso_date: string,
|
|
||||||
hours: number,
|
|
||||||
type: LeaveTypes,
|
|
||||||
comment?: string,
|
|
||||||
) {
|
|
||||||
if (hours <= 0) return;
|
|
||||||
|
|
||||||
const duration_minutes = Math.round(hours * 60);
|
|
||||||
if (duration_minutes > 8 * 60) {
|
|
||||||
throw new BadRequestException("Amount of hours cannot exceed 8 hours per day.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const start_minutes = 8 * 60;
|
|
||||||
const end_minutes = start_minutes + duration_minutes;
|
|
||||||
const toHHmm = (total: number) =>
|
|
||||||
`${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
|
|
||||||
|
|
||||||
const existing = await this.prisma.shifts.findFirst({
|
|
||||||
where: {
|
|
||||||
date: new Date(iso_date),
|
|
||||||
bank_code: { type },
|
|
||||||
timesheet: { employee_id: employee_id },
|
|
||||||
},
|
|
||||||
include: { bank_code: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.shiftsCommand.upsertShiftsByDate(email, iso_date, {
|
|
||||||
old_shift: existing
|
|
||||||
? {
|
|
||||||
start_time: existing.start_time.toISOString().slice(11, 16),
|
|
||||||
end_time: existing.end_time.toISOString().slice(11, 16),
|
|
||||||
type: existing.bank_code?.type ?? type,
|
|
||||||
is_remote: existing.is_remote,
|
|
||||||
comment: existing.comment ?? undefined,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
new_shift: {
|
|
||||||
start_time: toHHmm(start_minutes),
|
|
||||||
end_time: toHHmm(end_minutes),
|
|
||||||
is_remote: existing?.is_remote ?? false,
|
|
||||||
comment: comment ?? existing?.comment ?? "",
|
|
||||||
type: type,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeShift(
|
|
||||||
email: string,
|
|
||||||
employee_id: number,
|
|
||||||
iso_date: string,
|
|
||||||
type: LeaveTypes,
|
|
||||||
) {
|
|
||||||
const existing = await this.prisma.shifts.findFirst({
|
|
||||||
where: {
|
|
||||||
date: new Date(iso_date),
|
|
||||||
bank_code: { type },
|
|
||||||
timesheet: { employee_id: employee_id },
|
|
||||||
},
|
|
||||||
include: { bank_code: true },
|
|
||||||
});
|
|
||||||
if (!existing) return;
|
|
||||||
|
|
||||||
await this.shiftsCommand.upsertShiftsByDate(email, iso_date, {
|
|
||||||
old_shift: {
|
|
||||||
start_time: existing.start_time.toISOString().slice(11, 16),
|
|
||||||
end_time: existing.end_time.toISOString().slice(11, 16),
|
|
||||||
type: existing.bank_code?.type ?? type,
|
|
||||||
is_remote: existing.is_remote,
|
|
||||||
comment: existing.comment ?? undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
|
async delete(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
|
||||||
const email = dto.email.trim();
|
const email = dto.email.trim();
|
||||||
const employee_id = await this.resolveEmployeeIdByEmail(email);
|
|
||||||
const dates = normalizeDates(dto.dates);
|
const dates = normalizeDates(dto.dates);
|
||||||
|
const employee_id = await this.leaveUtils.resolveEmployeeIdByEmail(email);
|
||||||
if (!dates.length) throw new BadRequestException("Dates array must not be empty");
|
if (!dates.length) throw new BadRequestException("Dates array must not be empty");
|
||||||
|
|
||||||
const rows = await this.prisma.leaveRequests.findMany({
|
const rows = await this.prisma.leaveRequests.findMany({
|
||||||
|
|
@ -161,7 +82,7 @@ export class LeaveRequestsService {
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
||||||
const iso = toISODateKey(row.date);
|
const iso = toISODateKey(row.date);
|
||||||
await this.removeShift(email, employee_id, iso, type);
|
await this.leaveUtils.removeShift(email, employee_id, iso, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,8 +96,9 @@ export class LeaveRequestsService {
|
||||||
|
|
||||||
async update(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
|
async update(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
|
||||||
const email = dto.email.trim();
|
const email = dto.email.trim();
|
||||||
const employee_id = await this.resolveEmployeeIdByEmail(email);
|
const employee_id = await this.leaveUtils.resolveEmployeeIdByEmail(email);
|
||||||
const bank_code = await this.resolveBankCodeByType(type);
|
const bank_code = await this.leaveUtils.resolveBankCodeByType(type);
|
||||||
|
if(!bank_code) throw new NotFoundException(`bank_code not found`);
|
||||||
const modifier = Number(bank_code.modifier ?? 1);
|
const modifier = Number(bank_code.modifier ?? 1);
|
||||||
const dates = normalizeDates(dto.dates);
|
const dates = normalizeDates(dto.dates);
|
||||||
if (!dates.length) {
|
if (!dates.length) {
|
||||||
|
|
@ -196,9 +118,7 @@ export class LeaveRequestsService {
|
||||||
},
|
},
|
||||||
select: leaveRequestsSelect,
|
select: leaveRequestsSelect,
|
||||||
});
|
});
|
||||||
if (!existing) {
|
if (!existing) throw new NotFoundException(`No Leave request found for ${iso_date}`);
|
||||||
throw new NotFoundException(`No Leave request found for ${iso_date}`);
|
|
||||||
}
|
|
||||||
return { iso_date, date, existing };
|
return { iso_date, date, existing };
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -251,16 +171,14 @@ export class LeaveRequestsService {
|
||||||
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
||||||
|
|
||||||
if (!was_approved && is_approved) {
|
if (!was_approved && is_approved) {
|
||||||
await this.syncShift(email, employee_id, iso_date, hours, type, row.comment);
|
await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
|
||||||
} else if (was_approved && !is_approved) {
|
} else if (was_approved && !is_approved) {
|
||||||
await this.removeShift(email, employee_id, iso_date, type);
|
await this.leaveUtils.removeShift(email, employee_id, iso_date, type);
|
||||||
} else if (was_approved && is_approved) {
|
} else if (was_approved && is_approved) {
|
||||||
await this.syncShift(email, employee_id, iso_date, hours, type, row.comment);
|
await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
updated.push({ ...mapRowToView(row), action: "update" });
|
updated.push({ ...mapRowToView(row), action: "update" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return { action: "update", leave_requests: updated };
|
return { action: "update", leave_requests: updated };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,8 +214,8 @@ export class LeaveRequestsService {
|
||||||
const row = await this.prisma.leaveRequests.update({
|
const row = await this.prisma.leaveRequests.update({
|
||||||
where: { id: existing.id },
|
where: { id: existing.id },
|
||||||
data: {
|
data: {
|
||||||
comment: dto.comment ?? existing.comment,
|
|
||||||
requested_hours,
|
requested_hours,
|
||||||
|
comment: dto.comment ?? existing.comment,
|
||||||
payable_hours: payable,
|
payable_hours: payable,
|
||||||
bank_code_id: bank_code.id,
|
bank_code_id: bank_code.id,
|
||||||
approval_status: dto.approval_status ?? existing.approval_status,
|
approval_status: dto.approval_status ?? existing.approval_status,
|
||||||
|
|
@ -310,30 +228,16 @@ export class LeaveRequestsService {
|
||||||
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
||||||
|
|
||||||
if (!was_approved && is_approved) {
|
if (!was_approved && is_approved) {
|
||||||
await this.syncShift(email, employee_id, iso_date, hours, type, row.comment);
|
await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
|
||||||
} else if (was_approved && !is_approved) {
|
} else if (was_approved && !is_approved) {
|
||||||
await this.removeShift(email, employee_id, iso_date, type);
|
await this.leaveUtils.removeShift(email, employee_id, iso_date, type);
|
||||||
} else if (was_approved && is_approved) {
|
} else if (was_approved && is_approved) {
|
||||||
await this.syncShift(email, employee_id, iso_date, hours, type, row.comment);
|
await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
updated.push({ ...mapRowToView(row), action: "update" });
|
updated.push({ ...mapRowToView(row), action: "update" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return { action: "update", leave_requests: updated };
|
return { action: "update", leave_requests: updated };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toDateOnly = (iso: string): Date => {
|
|
||||||
const date = new Date(iso);
|
|
||||||
if (Number.isNaN(date.getTime())) {
|
|
||||||
throw new BadRequestException(`Invalid date: ${iso}`);
|
|
||||||
}
|
|
||||||
date.setHours(0, 0, 0, 0);
|
|
||||||
return date;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const toISODateKey = (date: Date): string => date.toISOString().slice(0, 10);
|
|
||||||
|
|
||||||
export const normalizeDates = (dates: string[]): string[] =>
|
|
||||||
Array.from(new Set(dates.map((iso) => toISODateKey(toDateOnly(iso)))));
|
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,28 @@
|
||||||
import { LeaveRequestsService, normalizeDates, toDateOnly } from "./leave-request.service";
|
import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
|
||||||
import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
|
|
||||||
import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
|
import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
|
||||||
import { BadRequestException, Inject, Injectable, forwardRef } from "@nestjs/common";
|
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||||
import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
|
import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
|
||||||
import { leaveRequestsSelect } from "../utils/leave-requests.select";
|
import { leaveRequestsSelect } from "../utils/leave-requests.select";
|
||||||
import { mapRowToView } from "../mappers/leave-requests.mapper";
|
import { mapRowToView } from "../mappers/leave-requests.mapper";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { SickLeaveService } from "src/modules/business-logics/services/sick-leave.service";
|
import { SickLeaveService } from "src/modules/business-logics/services/sick-leave.service";
|
||||||
import { roundToQuarterHour } from "src/common/utils/date-utils";
|
import { roundToQuarterHour } from "src/common/utils/date-utils";
|
||||||
|
import { LeaveRequestsUtils, normalizeDates, toDateOnly } from "../utils/leave-request.util";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SickLeaveRequestsService {
|
export class SickLeaveRequestsService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
@Inject(forwardRef(() => LeaveRequestsService)) private readonly leaveService: LeaveRequestsService,
|
|
||||||
private readonly sickService: SickLeaveService,
|
private readonly sickService: SickLeaveService,
|
||||||
|
private readonly leaveUtils: LeaveRequestsUtils,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
//handle distribution to the right service according to the selected action
|
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
||||||
async handle(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
|
||||||
switch (dto.action) {
|
|
||||||
case "create":
|
|
||||||
return this.createSick(dto);
|
|
||||||
case "update":
|
|
||||||
return this.leaveService.update(dto, LeaveTypes.SICK);
|
|
||||||
case "delete":
|
|
||||||
return this.leaveService.delete(dto, LeaveTypes.SICK);
|
|
||||||
default:
|
|
||||||
throw new BadRequestException(`Unknown action: ${dto.action}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createSick(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
|
||||||
const email = dto.email.trim();
|
const email = dto.email.trim();
|
||||||
const employee_id = await this.leaveService.resolveEmployeeIdByEmail(email);
|
const employee_id = await this.leaveUtils.resolveEmployeeIdByEmail(email);
|
||||||
const bank_code = await this.leaveService.resolveBankCodeByType(LeaveTypes.SICK);
|
const bank_code = await this.leaveUtils.resolveBankCodeByType(LeaveTypes.SICK);
|
||||||
|
if(!bank_code) throw new NotFoundException(`bank_code not found`);
|
||||||
|
|
||||||
const modifier = bank_code.modifier ?? 1;
|
const modifier = bank_code.modifier ?? 1;
|
||||||
const dates = normalizeDates(dto.dates);
|
const dates = normalizeDates(dto.dates);
|
||||||
if (!dates.length) throw new BadRequestException("Dates array must not be empty");
|
if (!dates.length) throw new BadRequestException("Dates array must not be empty");
|
||||||
|
|
@ -94,7 +82,7 @@ export class SickLeaveRequestsService {
|
||||||
|
|
||||||
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
||||||
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
||||||
await this.leaveService.syncShift(email, employee_id, iso, hours,LeaveTypes.SICK, row.comment);
|
await this.leaveUtils.syncShift(email, employee_id, iso, hours,LeaveTypes.SICK, row.comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
created.push({ ...mapRowToView(row), action: "create" });
|
created.push({ ...mapRowToView(row), action: "create" });
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,29 @@
|
||||||
import { LeaveRequestsService, normalizeDates, toDateOnly } from "./leave-request.service";
|
|
||||||
import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
|
import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
|
||||||
import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
|
import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
|
||||||
import { BadRequestException, Inject, Injectable, forwardRef } from "@nestjs/common";
|
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||||
import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
|
import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
|
||||||
import { VacationService } from "src/modules/business-logics/services/vacation.service";
|
import { VacationService } from "src/modules/business-logics/services/vacation.service";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { mapRowToView } from "../mappers/leave-requests.mapper";
|
import { mapRowToView } from "../mappers/leave-requests.mapper";
|
||||||
import { leaveRequestsSelect } from "../utils/leave-requests.select";
|
import { leaveRequestsSelect } from "../utils/leave-requests.select";
|
||||||
import { roundToQuarterHour } from "src/common/utils/date-utils";
|
import { roundToQuarterHour } from "src/common/utils/date-utils";
|
||||||
|
import { LeaveRequestsUtils, normalizeDates, toDateOnly } from "../utils/leave-request.util";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VacationLeaveRequestsService {
|
export class VacationLeaveRequestsService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
@Inject(forwardRef(() => LeaveRequestsService)) private readonly leaveService: LeaveRequestsService,
|
|
||||||
private readonly vacationService: VacationService,
|
private readonly vacationService: VacationService,
|
||||||
|
private readonly leaveUtils: LeaveRequestsUtils,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
||||||
switch (dto.action) {
|
|
||||||
case "create":
|
|
||||||
return this.createVacation(dto);
|
|
||||||
case "update":
|
|
||||||
return this.leaveService.update(dto, LeaveTypes.VACATION);
|
|
||||||
case "delete":
|
|
||||||
return this.leaveService.delete(dto, LeaveTypes.VACATION);
|
|
||||||
default:
|
|
||||||
throw new BadRequestException(`Unknown action: ${dto.action}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createVacation(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
|
||||||
const email = dto.email.trim();
|
const email = dto.email.trim();
|
||||||
const employee_id = await this.leaveService.resolveEmployeeIdByEmail(email);
|
const employee_id = await this.leaveUtils.resolveEmployeeIdByEmail(email);
|
||||||
const bank_code = await this.leaveService.resolveBankCodeByType(LeaveTypes.VACATION);
|
const bank_code = await this.leaveUtils.resolveBankCodeByType(LeaveTypes.VACATION);
|
||||||
|
if(!bank_code) throw new NotFoundException(`bank_code not found`);
|
||||||
|
|
||||||
const modifier = bank_code.modifier ?? 1;
|
const modifier = bank_code.modifier ?? 1;
|
||||||
const dates = normalizeDates(dto.dates);
|
const dates = normalizeDates(dto.dates);
|
||||||
const requested_hours_per_day = dto.requested_hours ?? 8;
|
const requested_hours_per_day = dto.requested_hours ?? 8;
|
||||||
|
|
@ -89,7 +79,7 @@ export class VacationLeaveRequestsService {
|
||||||
|
|
||||||
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
||||||
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
||||||
await this.leaveService.syncShift(email, employee_id, iso, hours, LeaveTypes.VACATION, row.comment);
|
await this.leaveUtils.syncShift(email, employee_id, iso, hours, LeaveTypes.VACATION, row.comment);
|
||||||
}
|
}
|
||||||
created.push({ ...mapRowToView(row), action: "create" });
|
created.push({ ...mapRowToView(row), action: "create" });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
124
src/modules/leave-requests/utils/leave-request.util.ts
Normal file
124
src/modules/leave-requests/utils/leave-request.util.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||||
|
import { LeaveTypes } from "@prisma/client";
|
||||||
|
import { ShiftsCommandService } from "src/modules/shifts/services/shifts-command.service";
|
||||||
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LeaveRequestsUtils {
|
||||||
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly shiftsCommand: ShiftsCommandService,
|
||||||
|
){}
|
||||||
|
|
||||||
|
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 resolveBankCodeByType(type: LeaveTypes) {
|
||||||
|
const bankCode = await this.prisma.bankCodes.findFirst({
|
||||||
|
where: { type },
|
||||||
|
select: { id: true, bank_code: true, modifier: true },
|
||||||
|
});
|
||||||
|
if (!bankCode) throw new BadRequestException(`Bank code type "${type}" not found`);
|
||||||
|
return bankCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
async syncShift(
|
||||||
|
email: string,
|
||||||
|
employee_id: number,
|
||||||
|
iso_date: string,
|
||||||
|
hours: number,
|
||||||
|
type: LeaveTypes,
|
||||||
|
comment?: string,
|
||||||
|
) {
|
||||||
|
if (hours <= 0) return;
|
||||||
|
|
||||||
|
const duration_minutes = Math.round(hours * 60);
|
||||||
|
if (duration_minutes > 8 * 60) {
|
||||||
|
throw new BadRequestException("Amount of hours cannot exceed 8 hours per day.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const start_minutes = 8 * 60;
|
||||||
|
const end_minutes = start_minutes + duration_minutes;
|
||||||
|
const toHHmm = (total: number) =>
|
||||||
|
`${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
|
||||||
|
|
||||||
|
const existing = await this.prisma.shifts.findFirst({
|
||||||
|
where: {
|
||||||
|
date: new Date(iso_date),
|
||||||
|
bank_code: { type },
|
||||||
|
timesheet: { employee_id: employee_id },
|
||||||
|
},
|
||||||
|
include: { bank_code: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.shiftsCommand.upsertShiftsByDate(email, iso_date, {
|
||||||
|
old_shift: existing
|
||||||
|
? {
|
||||||
|
start_time: existing.start_time.toISOString().slice(11, 16),
|
||||||
|
end_time: existing.end_time.toISOString().slice(11, 16),
|
||||||
|
type: existing.bank_code?.type ?? type,
|
||||||
|
is_remote: existing.is_remote,
|
||||||
|
comment: existing.comment ?? undefined,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
new_shift: {
|
||||||
|
start_time: toHHmm(start_minutes),
|
||||||
|
end_time: toHHmm(end_minutes),
|
||||||
|
is_remote: existing?.is_remote ?? false,
|
||||||
|
comment: comment ?? existing?.comment ?? "",
|
||||||
|
type: type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeShift(
|
||||||
|
email: string,
|
||||||
|
employee_id: number,
|
||||||
|
iso_date: string,
|
||||||
|
type: LeaveTypes,
|
||||||
|
) {
|
||||||
|
const existing = await this.prisma.shifts.findFirst({
|
||||||
|
where: {
|
||||||
|
date: new Date(iso_date),
|
||||||
|
bank_code: { type },
|
||||||
|
timesheet: { employee_id: employee_id },
|
||||||
|
},
|
||||||
|
include: { bank_code: true },
|
||||||
|
});
|
||||||
|
if (!existing) return;
|
||||||
|
|
||||||
|
await this.shiftsCommand.upsertShiftsByDate(email, iso_date, {
|
||||||
|
old_shift: {
|
||||||
|
start_time: existing.start_time.toISOString().slice(11, 16),
|
||||||
|
end_time: existing.end_time.toISOString().slice(11, 16),
|
||||||
|
type: existing.bank_code?.type ?? type,
|
||||||
|
is_remote: existing.is_remote,
|
||||||
|
comment: existing.comment ?? undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const toDateOnly = (iso: string): Date => {
|
||||||
|
const date = new Date(iso);
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
throw new BadRequestException(`Invalid date: ${iso}`);
|
||||||
|
}
|
||||||
|
date.setHours(0, 0, 0, 0);
|
||||||
|
return date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toISODateKey = (date: Date): string => date.toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
export const normalizeDates = (dates: string[]): string[] =>
|
||||||
|
Array.from(new Set(dates.map((iso) => toISODateKey(toDateOnly(iso)))));
|
||||||
Loading…
Reference in New Issue
Block a user