feat(leave-request): added holiday shift's creation and CRUD for holiday leave-requests.
This commit is contained in:
parent
d36d2f922b
commit
10d4f11f76
|
|
@ -3,7 +3,7 @@ import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||||
import { LeaveRequestsArchive, Roles as RoleEnum } from "@prisma/client";
|
import { LeaveRequestsArchive, Roles as RoleEnum } from "@prisma/client";
|
||||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
import { LeaveRequestViewDto } from "src/modules/leave-requests/dtos/leave-request.view.dto";
|
import { LeaveRequestViewDto } from "src/modules/leave-requests/dtos/leave-request.view.dto";
|
||||||
import { LeaveRequestsService } from "src/modules/leave-requests/services/leave-requests.service";
|
import { LeaveRequestsService } from "src/modules/leave-requests/services/holiday-leave-requests.service";
|
||||||
|
|
||||||
@ApiTags('LeaveRequests Archives')
|
@ApiTags('LeaveRequests Archives')
|
||||||
// @UseGuards()
|
// @UseGuards()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable, Logger } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
import { Cron } from "@nestjs/schedule";
|
import { Cron } from "@nestjs/schedule";
|
||||||
import { ExpensesQueryService } from "src/modules/expenses/services/expenses-query.service";
|
import { ExpensesQueryService } from "src/modules/expenses/services/expenses-query.service";
|
||||||
import { LeaveRequestsService } from "src/modules/leave-requests/services/leave-requests.service";
|
import { LeaveRequestsService } from "src/modules/leave-requests/services/holiday-leave-requests.service";
|
||||||
import { ShiftsQueryService } from "src/modules/shifts/services/shifts-query.service";
|
import { ShiftsQueryService } from "src/modules/shifts/services/shifts-query.service";
|
||||||
import { TimesheetsQueryService } from "src/modules/timesheets/services/timesheets-query.service";
|
import { TimesheetsQueryService } from "src/modules/timesheets/services/timesheets-query.service";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,7 @@ import {
|
||||||
Injectable,
|
Injectable,
|
||||||
NotFoundException
|
NotFoundException
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import {
|
import { ExpenseResponse, UpsertAction } from "../types and interfaces/expenses.types.interfaces";
|
||||||
DayExpenseResponse,
|
|
||||||
UpsertAction
|
|
||||||
} from "../types and interfaces/expenses.types.interfaces";
|
|
||||||
import {
|
import {
|
||||||
assertAndTrimComment,
|
assertAndTrimComment,
|
||||||
computeMileageAmount,
|
computeMileageAmount,
|
||||||
|
|
@ -26,9 +23,9 @@ import {
|
||||||
export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
||||||
constructor(
|
constructor(
|
||||||
prisma: PrismaService,
|
prisma: PrismaService,
|
||||||
private readonly bankCodesRepo: BankCodesRepo,
|
private readonly bankCodesRepo: BankCodesRepo,
|
||||||
private readonly timesheetsRepo: TimesheetsRepo,
|
private readonly timesheetsRepo: TimesheetsRepo,
|
||||||
private readonly employeesRepo: EmployeesRepo,
|
private readonly employeesRepo: EmployeesRepo,
|
||||||
) { super(prisma); }
|
) { super(prisma); }
|
||||||
|
|
||||||
protected get delegate() {
|
protected get delegate() {
|
||||||
|
|
@ -47,7 +44,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
||||||
|
|
||||||
//-------------------- Master CRUD function --------------------
|
//-------------------- Master CRUD function --------------------
|
||||||
readonly upsertExpensesByDate = async (email: string, date: string, dto: UpsertExpenseDto,
|
readonly upsertExpensesByDate = async (email: string, date: string, dto: UpsertExpenseDto,
|
||||||
): Promise<{ action:UpsertAction; day: DayExpenseResponse[] }> => {
|
): Promise<{ action:UpsertAction; day: ExpenseResponse[] }> => {
|
||||||
|
|
||||||
//validates if there is an existing expense, at least 1 old or new
|
//validates if there is an existing expense, at least 1 old or new
|
||||||
const { old_expense, new_expense } = dto ?? {};
|
const { old_expense, new_expense } = dto ?? {};
|
||||||
|
|
@ -68,7 +65,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
||||||
const timesheet_id = await this.ensureTimesheetForDate(employee_id, dateOnly);
|
const timesheet_id = await this.ensureTimesheetForDate(employee_id, dateOnly);
|
||||||
|
|
||||||
return this.prisma.$transaction(async (tx) => {
|
return this.prisma.$transaction(async (tx) => {
|
||||||
const loadDay = async (): Promise<DayExpenseResponse[]> => {
|
const loadDay = async (): Promise<ExpenseResponse[]> => {
|
||||||
const rows = await tx.expenses.findMany({
|
const rows = await tx.expenses.findMany({
|
||||||
where: {
|
where: {
|
||||||
timesheet_id: timesheet_id,
|
timesheet_id: timesheet_id,
|
||||||
|
|
@ -186,7 +183,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await tx.expenses.delete({where: { id: existing.id } });
|
await tx.expenses.delete({where: { id: existing.id } });
|
||||||
action = 'deleted';
|
action = 'delete';
|
||||||
}
|
}
|
||||||
//-------------------- CREATE --------------------
|
//-------------------- CREATE --------------------
|
||||||
else if (!old_expense && new_expense) {
|
else if (!old_expense && new_expense) {
|
||||||
|
|
@ -203,7 +200,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
||||||
is_approved: false,
|
is_approved: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
action = 'created';
|
action = 'create';
|
||||||
}
|
}
|
||||||
//-------------------- UPDATE --------------------
|
//-------------------- UPDATE --------------------
|
||||||
else if(old_expense && new_expense) {
|
else if(old_expense && new_expense) {
|
||||||
|
|
@ -227,7 +224,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
||||||
attachment: new_exp.attachment,
|
attachment: new_exp.attachment,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
action = 'updated';
|
action = 'update';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new BadRequestException('Invalid upsert combination');
|
throw new BadRequestException('Invalid upsert combination');
|
||||||
|
|
@ -310,7 +307,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
||||||
comment: string;
|
comment: string;
|
||||||
is_approved: boolean;
|
is_approved: boolean;
|
||||||
bank_code: { type: string } | null;
|
bank_code: { type: string } | null;
|
||||||
}): DayExpenseResponse => mapDbExpenseToDayResponse(row);
|
}): ExpenseResponse => mapDbExpenseToDayResponse(row);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export type UpsertAction = 'created' | 'updated' | 'deleted';
|
export type UpsertAction = 'create' | 'update' | 'delete';
|
||||||
|
|
||||||
export interface DayExpenseResponse {
|
export interface ExpenseResponse {
|
||||||
date: string;
|
date: string;
|
||||||
type: string;
|
type: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
|
|
@ -9,6 +9,5 @@ export interface DayExpenseResponse {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpsertExpenseResult = {
|
export type UpsertExpenseResult = {
|
||||||
action: UpsertAction;
|
expenses: ExpenseResponse[]
|
||||||
day: DayExpenseResponse[]
|
|
||||||
};
|
};
|
||||||
|
|
@ -1,13 +1,30 @@
|
||||||
import { Controller } from "@nestjs/common";
|
import { Body, Controller, Post } from "@nestjs/common";
|
||||||
import { LeaveRequestsService } from "../services/leave-requests.service";
|
import { HolidayLeaveRequestsService } from "../services/holiday-leave-requests.service";
|
||||||
|
|
||||||
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
||||||
|
import { UpsertHolidayDto } from "../dtos/upsert-holiday.dto";
|
||||||
|
|
||||||
@ApiTags('Leave Requests')
|
@ApiTags('Leave Requests')
|
||||||
@ApiBearerAuth('access-token')
|
@ApiBearerAuth('access-token')
|
||||||
// @UseGuards()
|
// @UseGuards()
|
||||||
@Controller('leave-requests')
|
@Controller('leave-requests')
|
||||||
export class LeaveRequestController {
|
export class LeaveRequestController {
|
||||||
constructor(private readonly leave_service: LeaveRequestsService){}
|
constructor(private readonly leave_service: HolidayLeaveRequestsService){}
|
||||||
|
|
||||||
|
@Post('holiday')
|
||||||
|
async upsertHoliday(@Body() dto: UpsertHolidayDto) {
|
||||||
|
const { action, leave_requests } = await this.leave_service.handleHoliday(dto);
|
||||||
|
return { action, leave_requests };
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:
|
||||||
|
/*
|
||||||
|
@Get('archive')
|
||||||
|
findAllArchived(){...}
|
||||||
|
|
||||||
|
@Get('archive/:id')
|
||||||
|
findOneArchived(id){...}
|
||||||
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
import { LeaveApprovalStatus } from "@prisma/client";
|
||||||
import { Type } from "class-transformer";
|
import { Type } from "class-transformer";
|
||||||
import {
|
import {
|
||||||
ArrayNotEmpty,
|
ArrayNotEmpty,
|
||||||
ArrayUnique,
|
ArrayUnique,
|
||||||
IsArray,
|
IsArray,
|
||||||
IsEmail,
|
IsEmail,
|
||||||
|
IsEnum,
|
||||||
IsIn,
|
IsIn,
|
||||||
IsISO8601,
|
IsISO8601,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
|
|
@ -39,4 +41,8 @@ export class UpsertHolidayDto {
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@Max(24)
|
@Max(24)
|
||||||
requested_hours?: number;
|
requested_hours?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(LeaveApprovalStatus)
|
||||||
|
approval_status?: LeaveApprovalStatus;
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,19 @@
|
||||||
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
import { HolidayService } from "../business-logics/services/holiday.service";
|
||||||
import { LeaveRequestController } from "./controllers/leave-requests.controller";
|
import { LeaveRequestController } from "./controllers/leave-requests.controller";
|
||||||
import { LeaveRequestsService } from "./services/leave-requests.service";
|
import { HolidayLeaveRequestsService } from "./services/holiday-leave-requests.service";
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module";
|
import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [BusinessLogicsModule],
|
imports: [BusinessLogicsModule],
|
||||||
controllers: [LeaveRequestController],
|
controllers: [LeaveRequestController],
|
||||||
providers: [LeaveRequestsService],
|
providers: [
|
||||||
exports: [LeaveRequestsService],
|
HolidayService,
|
||||||
|
HolidayLeaveRequestsService,
|
||||||
|
PrismaService,
|
||||||
|
],
|
||||||
|
exports: [HolidayLeaveRequestsService],
|
||||||
})
|
})
|
||||||
|
|
||||||
export class LeaveRequestsModule {}
|
export class LeaveRequestsModule {}
|
||||||
|
|
@ -1,46 +1,37 @@
|
||||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
import {
|
||||||
import { LeaveTypes, LeaveRequestsArchive } from "@prisma/client";
|
BadRequestException,
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
Injectable,
|
||||||
import { HolidayService } from "src/modules/business-logics/services/holiday.service";
|
NotFoundException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { LeaveApprovalStatus, LeaveTypes } from '@prisma/client';
|
||||||
|
|
||||||
import { UpsertHolidayDto, HolidayUpsertAction } from "../dtos/upsert-holiday.dto";
|
import { HolidayService } from 'src/modules/business-logics/services/holiday.service';
|
||||||
import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
|
import { ShiftsCommandService } from 'src/modules/shifts/services/shifts-command.service';
|
||||||
import { mapRowToView } from "../mappers/leave-requests.mapper";
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
import { mapArchiveRowToViewWithDays } from "../utils/leave-request.transform";
|
|
||||||
import { LeaveRequestArchiveRow, leaveRequestsArchiveSelect } from "../utils/leave-requests-archive.select";
|
import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto';
|
||||||
import { leaveRequestsSelect } from "../utils/leave-requests.select";
|
import { HolidayUpsertAction, UpsertHolidayDto } from '../dtos/upsert-holiday.dto';
|
||||||
|
import { mapRowToView } from '../mappers/leave-requests.mapper';
|
||||||
|
import { leaveRequestsSelect } from '../utils/leave-requests.select';
|
||||||
|
|
||||||
|
interface HolidayUpsertResult {
|
||||||
|
action: HolidayUpsertAction;
|
||||||
|
leave_requests: LeaveRequestViewDto[];
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LeaveRequestsService {
|
export class HolidayLeaveRequestsService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly holidayService: HolidayService,
|
private readonly holidayService: HolidayService,
|
||||||
|
private readonly shiftsCommand: ShiftsCommandService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
//-------------------- helpers --------------------
|
// ---------------------------------------------------------------------
|
||||||
private async resolveEmployeeIdByEmail(email: string): Promise<number> {
|
// Public API
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async resolveHolidayBankCode() {
|
async handleHoliday(dto: UpsertHolidayDto): Promise<HolidayUpsertResult> {
|
||||||
const bankCode = await this.prisma.bankCodes.findFirst({
|
|
||||||
where: { type: 'HOLIDAY' },
|
|
||||||
select: { id: true, bank_code: true, modifier: true },
|
|
||||||
});
|
|
||||||
if (!bankCode) {
|
|
||||||
throw new BadRequestException('Bank code type "HOLIDAY" not found');
|
|
||||||
}
|
|
||||||
return bankCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleHoliday(dto: UpsertHolidayDto): Promise<{ action: HolidayUpsertAction; leave_requests: LeaveRequestViewDto[] }> {
|
|
||||||
switch (dto.action) {
|
switch (dto.action) {
|
||||||
case 'create':
|
case 'create':
|
||||||
return this.createHoliday(dto);
|
return this.createHoliday(dto);
|
||||||
|
|
@ -53,7 +44,11 @@ export class LeaveRequestsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createHoliday(dto: UpsertHolidayDto): Promise<{ action: 'create'; leave_requests: LeaveRequestViewDto[] }> {
|
// ---------------------------------------------------------------------
|
||||||
|
// Create
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
private async createHoliday(dto: UpsertHolidayDto): Promise<HolidayUpsertResult> {
|
||||||
const email = dto.email.trim();
|
const email = dto.email.trim();
|
||||||
const employeeId = await this.resolveEmployeeIdByEmail(email);
|
const employeeId = await this.resolveEmployeeIdByEmail(email);
|
||||||
const bankCode = await this.resolveHolidayBankCode();
|
const bankCode = await this.resolveHolidayBankCode();
|
||||||
|
|
@ -63,8 +58,10 @@ export class LeaveRequestsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const created: LeaveRequestViewDto[] = [];
|
const created: LeaveRequestViewDto[] = [];
|
||||||
|
|
||||||
for (const isoDate of dates) {
|
for (const isoDate of dates) {
|
||||||
const date = toDateOnly(isoDate);
|
const date = toDateOnly(isoDate);
|
||||||
|
|
||||||
const existing = await this.prisma.leaveRequests.findUnique({
|
const existing = await this.prisma.leaveRequests.findUnique({
|
||||||
where: {
|
where: {
|
||||||
leave_per_employee_date: {
|
leave_per_employee_date: {
|
||||||
|
|
@ -76,7 +73,7 @@ export class LeaveRequestsService {
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
if (existing) {
|
if (existing) {
|
||||||
throw new BadRequestException(`A holiday request already exists for ${isoDate}`);
|
throw new BadRequestException(`Holiday request already exists for ${isoDate}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const payable = await this.holidayService.calculateHolidayPay(email, date, bankCode.modifier);
|
const payable = await this.holidayService.calculateHolidayPay(email, date, bankCode.modifier);
|
||||||
|
|
@ -87,19 +84,29 @@ export class LeaveRequestsService {
|
||||||
leave_type: LeaveTypes.HOLIDAY,
|
leave_type: LeaveTypes.HOLIDAY,
|
||||||
date,
|
date,
|
||||||
comment: dto.comment ?? '',
|
comment: dto.comment ?? '',
|
||||||
approval_status: undefined,
|
|
||||||
requested_hours: dto.requested_hours ?? 8,
|
requested_hours: dto.requested_hours ?? 8,
|
||||||
payable_hours: payable,
|
payable_hours: payable,
|
||||||
|
approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING,
|
||||||
},
|
},
|
||||||
select: leaveRequestsSelect,
|
select: leaveRequestsSelect,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
||||||
|
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
||||||
|
await this.syncHolidayShift(email, employeeId, isoDate, hours, row.comment);
|
||||||
|
}
|
||||||
|
|
||||||
created.push({ ...mapRowToView(row), action: 'create' });
|
created.push({ ...mapRowToView(row), action: 'create' });
|
||||||
}
|
}
|
||||||
|
|
||||||
return { action: 'create', leave_requests: created };
|
return { action: 'create', leave_requests: created };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateHoliday(dto: UpsertHolidayDto): Promise<{ action: 'update'; leave_requests: LeaveRequestViewDto[] }> {
|
// ---------------------------------------------------------------------
|
||||||
|
// Update
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
private async updateHoliday(dto: UpsertHolidayDto): Promise<HolidayUpsertResult> {
|
||||||
const email = dto.email.trim();
|
const email = dto.email.trim();
|
||||||
const employeeId = await this.resolveEmployeeIdByEmail(email);
|
const employeeId = await this.resolveEmployeeIdByEmail(email);
|
||||||
const bankCode = await this.resolveHolidayBankCode();
|
const bankCode = await this.resolveHolidayBankCode();
|
||||||
|
|
@ -109,8 +116,10 @@ export class LeaveRequestsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated: LeaveRequestViewDto[] = [];
|
const updated: LeaveRequestViewDto[] = [];
|
||||||
|
|
||||||
for (const isoDate of dates) {
|
for (const isoDate of dates) {
|
||||||
const date = toDateOnly(isoDate);
|
const date = toDateOnly(isoDate);
|
||||||
|
|
||||||
const existing = await this.prisma.leaveRequests.findUnique({
|
const existing = await this.prisma.leaveRequests.findUnique({
|
||||||
where: {
|
where: {
|
||||||
leave_per_employee_date: {
|
leave_per_employee_date: {
|
||||||
|
|
@ -126,23 +135,43 @@ export class LeaveRequestsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const payable = await this.holidayService.calculateHolidayPay(email, date, bankCode.modifier);
|
const payable = await this.holidayService.calculateHolidayPay(email, date, bankCode.modifier);
|
||||||
|
const previousStatus = existing.approval_status;
|
||||||
|
|
||||||
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,
|
comment: dto.comment ?? existing.comment,
|
||||||
requested_hours: dto.requested_hours ?? undefined,
|
requested_hours: dto.requested_hours ?? existing.requested_hours ?? 8,
|
||||||
payable_hours: payable,
|
payable_hours: payable,
|
||||||
bank_code_id: bankCode.id,
|
bank_code_id: bankCode.id,
|
||||||
|
approval_status: dto.approval_status ?? existing.approval_status,
|
||||||
},
|
},
|
||||||
select: leaveRequestsSelect,
|
select: leaveRequestsSelect,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const wasApproved = previousStatus === LeaveApprovalStatus.APPROVED;
|
||||||
|
const isApproved = row.approval_status === LeaveApprovalStatus.APPROVED;
|
||||||
|
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
||||||
|
|
||||||
|
if (!wasApproved && isApproved) {
|
||||||
|
await this.syncHolidayShift(email, employeeId, isoDate, hours, row.comment);
|
||||||
|
} else if (wasApproved && !isApproved) {
|
||||||
|
await this.removeHolidayShift(email, employeeId, isoDate);
|
||||||
|
} else if (wasApproved && isApproved) {
|
||||||
|
await this.syncHolidayShift(email, employeeId, isoDate, hours, 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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async deleteHoliday(dto: UpsertHolidayDto): Promise<{ action: 'delete'; leave_requests: LeaveRequestViewDto[] }> {
|
// ---------------------------------------------------------------------
|
||||||
|
// Delete
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
private async deleteHoliday(dto: UpsertHolidayDto): Promise<HolidayUpsertResult> {
|
||||||
const email = dto.email.trim();
|
const email = dto.email.trim();
|
||||||
const employeeId = await this.resolveEmployeeIdByEmail(email);
|
const employeeId = await this.resolveEmployeeIdByEmail(email);
|
||||||
const dates = normalizeDates(dto.dates);
|
const dates = normalizeDates(dto.dates);
|
||||||
|
|
@ -164,48 +193,118 @@ export class LeaveRequestsService {
|
||||||
throw new NotFoundException(`No HOLIDAY request found for: ${missing.join(', ')}`);
|
throw new NotFoundException(`No HOLIDAY request found for: ${missing.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
||||||
|
const iso = toISODateKey(row.date);
|
||||||
|
await this.removeHolidayShift(email, employeeId, iso);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await this.prisma.leaveRequests.deleteMany({
|
await this.prisma.leaveRequests.deleteMany({
|
||||||
where: { id: { in: rows.map((row) => row.id) } },
|
where: { id: { in: rows.map((row) => row.id) } },
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleted = rows.map((row) => ({ ...mapRowToView(row), action: 'delete' as const }));
|
const deleted = rows.map((row) => ({ ...mapRowToView(row), action: 'delete' }));
|
||||||
return { action: 'delete', leave_requests: deleted };
|
return { action: 'delete', leave_requests: deleted };
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------- archival --------------------
|
// ---------------------------------------------------------------------
|
||||||
async archiveExpired(): Promise<void> {
|
// Shift synchronisation
|
||||||
// TODO: adjust logic to the new LeaveRequests structure
|
// ---------------------------------------------------------------------
|
||||||
}
|
|
||||||
|
|
||||||
async findAllArchived(): Promise<LeaveRequestsArchive[]> {
|
private async syncHolidayShift(
|
||||||
return this.prisma.leaveRequestsArchive.findMany();
|
email: string,
|
||||||
}
|
employeeId: number,
|
||||||
|
isoDate: string,
|
||||||
|
hours: number,
|
||||||
|
comment?: string,
|
||||||
|
) {
|
||||||
|
if (hours <= 0) return;
|
||||||
|
|
||||||
async findOneArchived(id: number): Promise<LeaveRequestViewDto> {
|
const durationMinutes = Math.round(hours * 60);
|
||||||
const row: LeaveRequestArchiveRow | null = await this.prisma.leaveRequestsArchive.findUnique({
|
if (durationMinutes > 8 * 60) {
|
||||||
where: { id },
|
throw new BadRequestException('Holiday hours cannot exceed 8 hours.');
|
||||||
select: leaveRequestsArchiveSelect,
|
|
||||||
});
|
|
||||||
if (!row) {
|
|
||||||
throw new NotFoundException(`Archived Leave Request #${id} not found`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const emp = await this.prisma.employees.findUnique({
|
const startMinutes = 8 * 60;
|
||||||
where: { id: row.employee_id },
|
const endMinutes = startMinutes + durationMinutes;
|
||||||
select: {
|
const toHHmm = (total: number) => `${String(Math.floor(total / 60)).padStart(2, '0')}:${String(total % 60).padStart(2, '0')}`;
|
||||||
user: {
|
|
||||||
select: {
|
const existing = await this.prisma.shifts.findFirst({
|
||||||
email: true,
|
where: {
|
||||||
first_name: true,
|
date: new Date(isoDate),
|
||||||
last_name: true,
|
bank_code: { type: 'HOLIDAY' },
|
||||||
},
|
timesheet: { employee_id: employeeId },
|
||||||
},
|
},
|
||||||
|
include: { bank_code: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.shiftsCommand.upsertShiftsByDate(email, isoDate, {
|
||||||
|
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 ?? 'HOLIDAY',
|
||||||
|
is_remote: existing.is_remote,
|
||||||
|
comment: existing.comment ?? undefined,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
new_shift: {
|
||||||
|
start_time: toHHmm(startMinutes),
|
||||||
|
end_time: toHHmm(endMinutes),
|
||||||
|
type: 'HOLIDAY',
|
||||||
|
is_remote: existing?.is_remote ?? false,
|
||||||
|
comment: comment ?? existing?.comment ?? '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const email = emp?.user.email ?? '';
|
}
|
||||||
const fullName = emp ? `${emp.user.first_name} ${emp.user.last_name}` : '';
|
|
||||||
|
|
||||||
return mapArchiveRowToViewWithDays(row, email, fullName);
|
private async removeHolidayShift(email: string, employeeId: number, isoDate: string) {
|
||||||
|
const existing = await this.prisma.shifts.findFirst({
|
||||||
|
where: {
|
||||||
|
date: new Date(isoDate),
|
||||||
|
bank_code: { type: 'HOLIDAY' },
|
||||||
|
timesheet: { employee_id: employeeId },
|
||||||
|
},
|
||||||
|
include: { bank_code: true },
|
||||||
|
});
|
||||||
|
if (!existing) return;
|
||||||
|
|
||||||
|
await this.shiftsCommand.upsertShiftsByDate(email, isoDate, {
|
||||||
|
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 ?? 'HOLIDAY',
|
||||||
|
is_remote: existing.is_remote,
|
||||||
|
comment: existing.comment ?? undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveHolidayBankCode() {
|
||||||
|
const bankCode = await this.prisma.bankCodes.findFirst({
|
||||||
|
where: { type: 'HOLIDAY' },
|
||||||
|
select: { id: true, bank_code: true, modifier: true },
|
||||||
|
});
|
||||||
|
if (!bankCode) {
|
||||||
|
throw new BadRequestException('Bank code type "HOLIDAY" not found');
|
||||||
|
}
|
||||||
|
return bankCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user