fix(leaves and expenses): import name fixes

This commit is contained in:
Matthieu Haineault 2025-10-03 10:22:27 -04:00
parent 10d4f11f76
commit 3984540edb
14 changed files with 93 additions and 419 deletions

View File

@ -77,30 +77,6 @@
] ]
} }
}, },
"/archives/leaveRequests": {
"get": {
"operationId": "LeaveRequestsArchiveController_findOneArchived",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "Archived leaveRequest found"
}
},
"summary": "Fetch leaveRequest in archives with its Id",
"tags": [
"LeaveRequests Archives"
]
}
},
"/archives/shifts": { "/archives/shifts": {
"get": { "get": {
"operationId": "ShiftsArchiveController_findOneArchived", "operationId": "ShiftsArchiveController_findOneArchived",
@ -1256,215 +1232,22 @@
] ]
} }
}, },
"/leave-requests": { "/leave-requests/holiday": {
"post": { "post": {
"operationId": "LeaveRequestController_create", "operationId": "LeaveRequestController_upsertHoliday",
"parameters": [], "parameters": [],
"requestBody": { "requestBody": {
"required": true, "required": true,
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/CreateLeaveRequestsDto" "$ref": "#/components/schemas/UpsertHolidayDto"
} }
} }
} }
}, },
"responses": { "responses": {
"201": { "201": {
"description": "Leave request created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateLeaveRequestsDto"
}
}
}
},
"400": {
"description": "Incomplete task or invalid data"
}
},
"security": [
{
"access-token": []
}
],
"summary": "Create leave request",
"tags": [
"Leave Requests"
]
},
"get": {
"operationId": "LeaveRequestController_findAll",
"parameters": [],
"responses": {
"201": {
"description": "List of Leave requests found",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LeaveRequestViewDto"
}
}
}
}
},
"400": {
"description": "List of leave request not found"
}
},
"security": [
{
"access-token": []
}
],
"summary": "Find all leave request",
"tags": [
"Leave Requests"
]
}
},
"/leave-requests/{id}": {
"get": {
"operationId": "LeaveRequestController_findOne",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"201": {
"description": "Leave request found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LeaveRequestViewDto"
}
}
}
},
"400": {
"description": "Leave request not found"
}
},
"security": [
{
"access-token": []
}
],
"summary": "Find leave request",
"tags": [
"Leave Requests"
]
},
"patch": {
"operationId": "LeaveRequestController_update",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateLeaveRequestsDto"
}
}
}
},
"responses": {
"201": {
"description": "Leave request updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LeaveRequestViewDto"
}
}
}
},
"400": {
"description": "Leave request not found"
}
},
"security": [
{
"access-token": []
}
],
"summary": "Update leave request",
"tags": [
"Leave Requests"
]
},
"delete": {
"operationId": "LeaveRequestController_remove",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"201": {
"description": "Leave request deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateLeaveRequestsDto"
}
}
}
},
"400": {
"description": "Leave request not found"
}
},
"security": [
{
"access-token": []
}
],
"summary": "Delete leave request",
"tags": [
"Leave Requests"
]
}
},
"/leave-requests/approval/{id}": {
"patch": {
"operationId": "LeaveRequestController_updateApproval",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "" "description": ""
} }
}, },
@ -2691,88 +2474,10 @@
} }
} }
}, },
"CreateLeaveRequestsDto": { "UpsertHolidayDto": {
"type": "object",
"properties": {
"bank_code_id": {
"type": "number",
"example": 7,
"description": "ID number of a leave-request code (link with bank-codes)"
},
"leave_type": {
"type": "string",
"example": "Sick or Vacation or Unpaid or Bereavement or Parental or Legal",
"description": "type of leave request for an accounting perception"
},
"start_date_time": {
"type": "string",
"example": "22/06/2463",
"description": "Leave request`s start date"
},
"end_date_time": {
"type": "string",
"example": "25/03/3019",
"description": "Leave request`s end date"
},
"comment": {
"type": "string",
"example": "My precious",
"description": "Leave request`s comment"
},
"approval_status": {
"type": "string",
"example": "True or False or Pending or Denied or Cancelled or Escalated",
"description": "Leave request`s approval status"
}
},
"required": [
"bank_code_id",
"leave_type",
"start_date_time",
"end_date_time",
"comment",
"approval_status"
]
},
"LeaveRequestViewDto": {
"type": "object", "type": "object",
"properties": {} "properties": {}
}, },
"UpdateLeaveRequestsDto": {
"type": "object",
"properties": {
"bank_code_id": {
"type": "number",
"example": 7,
"description": "ID number of a leave-request code (link with bank-codes)"
},
"leave_type": {
"type": "string",
"example": "Sick or Vacation or Unpaid or Bereavement or Parental or Legal",
"description": "type of leave request for an accounting perception"
},
"start_date_time": {
"type": "string",
"example": "22/06/2463",
"description": "Leave request`s start date"
},
"end_date_time": {
"type": "string",
"example": "25/03/3019",
"description": "Leave request`s end date"
},
"comment": {
"type": "string",
"example": "My precious",
"description": "Leave request`s comment"
},
"approval_status": {
"type": "string",
"example": "True or False or Pending or Denied or Cancelled or Escalated",
"description": "Leave request`s approval status"
}
}
},
"CreateBankCodeDto": { "CreateBankCodeDto": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -1,14 +1,14 @@
import { PrismaClient, Prisma, LeaveTypes, LeaveApprovalStatus } from '@prisma/client'; import { PrismaClient, Prisma, LeaveTypes, LeaveApprovalStatus } from '@prisma/client';
if (process.env.SKIP_LEAVE_REQUESTS === 'true') { if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)"); console.log('?? Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)');
process.exit(0); process.exit(0);
} }
const prisma = new PrismaClient(); const prisma = new PrismaClient();
function dateOn(y: number, m: number, d: number) { function dateOn(y: number, m: number, d: number) {
// stocke une date (pour @db.Date) à minuit UTC // stocke une date (@db.Date) à minuit UTC
return new Date(Date.UTC(y, m - 1, d, 0, 0, 0)); return new Date(Date.UTC(y, m - 1, d, 0, 0, 0));
} }
@ -19,7 +19,7 @@ async function main() {
const employees = await prisma.employees.findMany({ select: { id: true } }); const employees = await prisma.employees.findMany({ select: { id: true } });
const bankCodes = await prisma.bankCodes.findMany({ const bankCodes = await prisma.bankCodes.findMany({
where: { categorie: 'LEAVE' }, where: { categorie: 'LEAVE' },
select: { id: true }, select: { id: true, type: true },
}); });
if (!employees.length || !bankCodes.length) { if (!employees.length || !bankCodes.length) {
@ -44,30 +44,31 @@ async function main() {
LeaveApprovalStatus.ESCALATED, LeaveApprovalStatus.ESCALATED,
]; ];
const futureMonths = [8, 9, 10, 11, 12]; // Août→Déc (1-based) const futureMonths = [8, 9, 10, 11, 12]; // Août ? Déc. (1-based)
// ✅ typer rows pour éviter never[]
const rows: Prisma.LeaveRequestsCreateManyInput[] = []; const rows: Prisma.LeaveRequestsCreateManyInput[] = [];
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const emp = employees[i % employees.length]; const emp = employees[i % employees.length];
const m = futureMonths[i % futureMonths.length]; const m = futureMonths[i % futureMonths.length];
const start = dateOn(year, m, 5 + i); // 5..14 const date = dateOn(year, m, 5 + i); // 5..14
if (start <= today) continue; // garantir "futur" if (date <= today) continue; // garantir « futur »
const end = Math.random() < 0.5 ? null : dateOn(year, m, 6 + i);
const type = types[i % types.length]; const type = types[i % types.length];
const status = statuses[i % statuses.length]; const status = statuses[i % statuses.length];
const bc = bankCodes[i % bankCodes.length]; const bc = bankCodes[i % bankCodes.length];
const requestedHours = 4 + (i % 5); // 4 ? 8 h
const payableHours = status === LeaveApprovalStatus.APPROVED ? Math.min(requestedHours, 8) : null;
rows.push({ rows.push({
employee_id: emp.id, employee_id: emp.id,
bank_code_id: bc.id, bank_code_id: bc.id,
leave_type: type, leave_type: type,
start_date_time: start, date,
end_date_time: end, // ok: Date | null comment: `Future leave #${i + 1} (${bc.type})`,
comment: `Future leave #${i + 1}`,
approval_status: status, approval_status: status,
requested_hours: requestedHours,
payable_hours: payableHours,
}); });
} }
@ -75,7 +76,7 @@ async function main() {
await prisma.leaveRequests.createMany({ data: rows }); await prisma.leaveRequests.createMany({ data: rows });
} }
console.log(` LeaveRequests (future): ${rows.length} rows`); console.log(`? LeaveRequests (future): ${rows.length} rows`);
} }
main().finally(() => prisma.$disconnect()); main().finally(() => prisma.$disconnect());

View File

@ -1,7 +1,7 @@
import { PrismaClient, LeaveApprovalStatus, LeaveRequests } from '@prisma/client'; import { PrismaClient, LeaveApprovalStatus, LeaveTypes } from '@prisma/client';
if (process.env.SKIP_LEAVE_REQUESTS === 'true') { if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)"); console.log('?? Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)');
process.exit(0); process.exit(0);
} }
@ -15,65 +15,73 @@ function daysAgo(n: number) {
} }
async function main() { async function main() {
// 1) Récupère tous les employés
const employees = await prisma.employees.findMany({ select: { id: true } }); const employees = await prisma.employees.findMany({ select: { id: true } });
if (!employees.length) { if (!employees.length) {
throw new Error('Aucun employé trouvé. Exécute le seed employees avant celui-ci.'); throw new Error('Aucun employé trouvé. Exécute le seed employees avant celui-ci.');
} }
// 2) Va chercher les bank codes dont le type est SICK, VACATION ou HOLIDAY
const leaveCodes = await prisma.bankCodes.findMany({ const leaveCodes = await prisma.bankCodes.findMany({
where: { type: { in: ['SICK', 'VACATION'] } }, where: { type: { in: ['SICK', 'VACATION', 'HOLIDAY'] } },
select: { id: true, type: true, bank_code: true }, select: { id: true, type: true },
}); });
if (!leaveCodes.length) { if (!leaveCodes.length) {
throw new Error("Aucun bank code trouvé avec type in ('SICK','VACATION','HOLIDAY'). Vérifie ta table bank_codes."); throw new Error("Aucun bank code trouvé avec type in ('SICK','VACATION','HOLIDAY'). Vérifie ta table bank_codes.");
} }
const statuses = Object.values(LeaveApprovalStatus); const statuses = Object.values(LeaveApprovalStatus);
const created: LeaveRequests[] = []; const created = [] as Array<{ id: number; employee_id: number; leave_type: LeaveTypes; date: Date; comment: string; approval_status: LeaveApprovalStatus; requested_hours: number; payable_hours: number | null }>;
// 3) Crée quelques leave requests
const COUNT = 12; const COUNT = 12;
for (let i = 0; i < COUNT; i++) { for (let i = 0; i < COUNT; i++) {
const emp = employees[i % employees.length]; const emp = employees[i % employees.length];
const leaveCode = leaveCodes[Math.floor(Math.random() * leaveCodes.length)]; const leaveCode = leaveCodes[Math.floor(Math.random() * leaveCodes.length)];
const start = daysAgo(120 - i * 3); const date = daysAgo(120 - i * 3);
const end = Math.random() < 0.6 ? daysAgo(119 - i * 3) : null; const status = statuses[(i + 2) % statuses.length];
const requestedHours = 4 + (i % 5); // 4 ? 8 h
const payableHours = status === LeaveApprovalStatus.APPROVED ? Math.min(requestedHours, 8) : null;
const lr = await prisma.leaveRequests.create({ const lr = await prisma.leaveRequests.create({
data: { data: {
employee_id: emp.id, employee_id: emp.id,
bank_code_id: leaveCode.id, bank_code_id: leaveCode.id,
// on stocke le "type" tel quil est défini dans bank_codes leave_type: leaveCode.type as LeaveTypes,
leave_type: leaveCode.type as any, date,
start_date_time: start,
end_date_time: end,
comment: `Past leave #${i + 1} (${leaveCode.type})`, comment: `Past leave #${i + 1} (${leaveCode.type})`,
approval_status: statuses[(i + 2) % statuses.length], approval_status: status,
requested_hours: requestedHours,
payable_hours: payableHours,
}, },
}); });
created.push(lr); created.push({
id: lr.id,
employee_id: lr.employee_id,
leave_type: lr.leave_type,
date: lr.date,
comment: lr.comment,
approval_status: lr.approval_status,
requested_hours: requestedHours,
payable_hours: payableHours,
});
} }
// 4) Archive
for (const lr of created) { for (const lr of created) {
await prisma.leaveRequestsArchive.create({ await prisma.leaveRequestsArchive.create({
data: { data: {
leave_request_id: lr.id, leave_request_id: lr.id,
employee_id: lr.employee_id, employee_id: lr.employee_id,
leave_type: lr.leave_type, leave_type: lr.leave_type,
start_date_time: lr.start_date_time, date: lr.date,
end_date_time: lr.end_date_time,
comment: lr.comment, comment: lr.comment,
approval_status: lr.approval_status, approval_status: lr.approval_status,
requested_hours: lr.requested_hours,
payable_hours: lr.payable_hours,
}, },
}); });
} }
console.log(` LeaveRequestsArchive: ${created.length} rows`); console.log(`? LeaveRequestsArchive: ${created.length} rows`);
} }
main().finally(() => prisma.$disconnect()); main().finally(() => prisma.$disconnect());

View File

@ -28,7 +28,7 @@ import { EmployeesModule } from "../employees/employees.module";
LeaveRequestsArchiveController, LeaveRequestsArchiveController,
ShiftsArchiveController, ShiftsArchiveController,
TimesheetsArchiveController, TimesheetsArchiveController,
] ],
}) })
export class ArchivalModule {} export class ArchivalModule {}

View File

@ -1,33 +1,7 @@
import { Get, Param, ParseIntPipe, NotFoundException, Controller, UseGuards } from "@nestjs/common"; import { Controller } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { ApiTags } from '@nestjs/swagger';
import { LeaveRequestsArchive, Roles as RoleEnum } from "@prisma/client";
import { RolesAllowed } from "src/common/decorators/roles.decorators";
import { LeaveRequestViewDto } from "src/modules/leave-requests/dtos/leave-request.view.dto";
import { LeaveRequestsService } from "src/modules/leave-requests/services/holiday-leave-requests.service";
@ApiTags('LeaveRequests Archives') @ApiTags('LeaveRequests Archives')
// @UseGuards() // @UseGuards()
@Controller('archives/leaveRequests') @Controller('archives/leaveRequests')
export class LeaveRequestsArchiveController { export class LeaveRequestsArchiveController {}
constructor(private readonly leaveRequestsService: LeaveRequestsService) {}
@Get()
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
@ApiOperation({ summary: 'List of archived leaveRequests'})
@ApiResponse({ status: 200, description: 'List of archived leaveRequests', isArray: true })
async findAllArchived(): Promise<LeaveRequestsArchive[]> {
return this.leaveRequestsService.findAllArchived();
}
@Get()
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
@ApiOperation({ summary: 'Fetch leaveRequest in archives with its Id'})
@ApiResponse({ status: 200, description: 'Archived leaveRequest found'})
async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<LeaveRequestViewDto> {
try{
return await this.leaveRequestsService.findOneArchived(id);
}catch {
throw new NotFoundException(`Archived leaveRequest #${id} not found`);
}
}
}

View File

@ -1,7 +1,6 @@
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/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";
@ -13,7 +12,6 @@ export class ArchivalService {
private readonly timesheetsService: TimesheetsQueryService, private readonly timesheetsService: TimesheetsQueryService,
private readonly expensesService: ExpensesQueryService, private readonly expensesService: ExpensesQueryService,
private readonly shiftsService: ShiftsQueryService, private readonly shiftsService: ShiftsQueryService,
private readonly leaveRequestsService: LeaveRequestsService,
) {} ) {}
@Cron('0 0 3 * * 1', {timeZone:'America/Toronto'}) // chaque premier lundi du mois à 03h00 @Cron('0 0 3 * * 1', {timeZone:'America/Toronto'}) // chaque premier lundi du mois à 03h00
@ -31,7 +29,7 @@ export class ArchivalService {
await this.timesheetsService.archiveOld(); await this.timesheetsService.archiveOld();
await this.expensesService.archiveOld(); await this.expensesService.archiveOld();
await this.shiftsService.archiveOld(); await this.shiftsService.archiveOld();
await this.leaveRequestsService.archiveExpired(); // await this.leaveRequestsService.archiveExpired();
this.logger.log('archivation process done'); this.logger.log('archivation process done');
} catch (err) { } catch (err) {
this.logger.error('an error occured during archivation process ', err); this.logger.error('an error occured during archivation process ', err);

View File

@ -53,8 +53,8 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
} }
//validate date format //validate date format
const dateOnly = toDateOnlyUTC(date); const date_only = toDateOnlyUTC(date);
if(Number.isNaN(dateOnly.getTime())) { if(Number.isNaN(date_only.getTime())) {
throw new BadRequestException('Invalid date format (expected: YYYY-MM-DD)'); throw new BadRequestException('Invalid date format (expected: YYYY-MM-DD)');
} }
@ -62,14 +62,14 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
const employee_id = await this.resolveEmployeeIdByEmail(email); const employee_id = await this.resolveEmployeeIdByEmail(email);
//make sure a timesheet existes //make sure a timesheet existes
const timesheet_id = await this.ensureTimesheetForDate(employee_id, dateOnly); const timesheet_id = await this.ensureTimesheetForDate(employee_id, date_only);
return this.prisma.$transaction(async (tx) => { return this.prisma.$transaction(async (tx) => {
const loadDay = async (): Promise<ExpenseResponse[]> => { 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,
date: dateOnly, date: date_only,
}, },
include: { include: {
bank_code: { bank_code: {
@ -160,7 +160,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
return tx.expenses.findFirst({ return tx.expenses.findFirst({
where: { where: {
timesheet_id: timesheet_id, timesheet_id: timesheet_id,
date: dateOnly, date: date_only,
bank_code_id: norm.bank_code_id, bank_code_id: norm.bank_code_id,
amount: norm.amount, amount: norm.amount,
comment: norm.comment, comment: norm.comment,
@ -191,7 +191,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
await tx.expenses.create({ await tx.expenses.create({
data: { data: {
timesheet_id: timesheet_id, timesheet_id: timesheet_id,
date: dateOnly, date: date_only,
bank_code_id: new_exp.bank_code_id, bank_code_id: new_exp.bank_code_id,
amount: new_exp.amount, amount: new_exp.amount,
mileage: new_exp.mileage, mileage: new_exp.mileage,

View File

@ -9,5 +9,6 @@ export interface ExpenseResponse {
}; };
export type UpsertExpenseResult = { export type UpsertExpenseResult = {
expenses: ExpenseResponse[] action: UpsertAction;
day: ExpenseResponse[]
}; };

View File

@ -1,5 +1,5 @@
import { BadRequestException } from "@nestjs/common"; import { BadRequestException } from "@nestjs/common";
import { DayExpenseResponse } from "../types and interfaces/expenses.types.interfaces"; import { ExpenseResponse } from "../types and interfaces/expenses.types.interfaces";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
//uppercase and trim for validation //uppercase and trim for validation
@ -55,7 +55,7 @@ export function mapDbExpenseToDayResponse(row: {
comment: string; comment: string;
is_approved: boolean; is_approved: boolean;
bank_code?: { type?: string | null } | null; bank_code?: { type?: string | null } | null;
}): DayExpenseResponse { }): ExpenseResponse {
const yyyyMmDd = row.date.toISOString().slice(0,10); const yyyyMmDd = row.date.toISOString().slice(0,10);
const toNum = (value: any)=> (value == null ? 0 : Number(value)); const toNum = (value: any)=> (value == null ? 0 : Number(value));
return { return {

View File

@ -10,5 +10,5 @@ export class LeaveRequestViewDto {
employee_full_name!: string; employee_full_name!: string;
payable_hours?: number; payable_hours?: number;
requested_hours?: number; requested_hours?: number;
action?: 'created' | 'updated' | 'deleted'; action?: 'create' | 'update' | 'delete';
} }

View File

@ -4,6 +4,7 @@ import { LeaveRequestController } from "./controllers/leave-requests.controller"
import { HolidayLeaveRequestsService } from "./services/holiday-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";
import { ShiftsCommandService } from "../shifts/services/shifts-command.service";
@Module({ @Module({
imports: [BusinessLogicsModule], imports: [BusinessLogicsModule],
@ -12,6 +13,7 @@ import { BusinessLogicsModule } from "src/modules/business-logics/business-logic
HolidayService, HolidayService,
HolidayLeaveRequestsService, HolidayLeaveRequestsService,
PrismaService, PrismaService,
ShiftsCommandService,
], ],
exports: [HolidayLeaveRequestsService], exports: [HolidayLeaveRequestsService],
}) })

View File

@ -6,13 +6,13 @@ const toNum = (value?: Prisma.Decimal | null) =>
value !== null && value !== undefined ? Number(value) : undefined; value !== null && value !== undefined ? Number(value) : undefined;
export function mapRowToView(row: LeaveRequestRow): LeaveRequestViewDto { export function mapRowToView(row: LeaveRequestRow): LeaveRequestViewDto {
const isoDate = row.date?.toISOString().slice(0, 10); const iso_date = row.date?.toISOString().slice(0, 10);
if (!isoDate) throw new Error(`Leave request #${row.id} has no date set.`); if (!iso_date) throw new Error(`Leave request #${row.id} has no date set.`);
return { return {
id: row.id, id: row.id,
leave_type: row.leave_type, leave_type: row.leave_type,
date: isoDate, date: iso_date,
payable_hours: toNum(row.payable_hours), payable_hours: toNum(row.payable_hours),
requested_hours: toNum(row.requested_hours), requested_hours: toNum(row.requested_hours),
comment: row.comment, comment: row.comment,

View File

@ -3,16 +3,14 @@ import {
Injectable, Injectable,
NotFoundException, NotFoundException,
} from '@nestjs/common'; } 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 { ShiftsCommandService } from 'src/modules/shifts/services/shifts-command.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto';
import { HolidayUpsertAction, UpsertHolidayDto } from '../dtos/upsert-holiday.dto'; import { HolidayUpsertAction, UpsertHolidayDto } from '../dtos/upsert-holiday.dto';
import { mapRowToView } from '../mappers/leave-requests.mapper'; import { HolidayService } from 'src/modules/business-logics/services/holiday.service';
import { leaveRequestsSelect } from '../utils/leave-requests.select'; import { ShiftsCommandService } from 'src/modules/shifts/services/shifts-command.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto';
import { mapRowToView } from '../mappers/leave-requests.mapper';
import { leaveRequestsSelect } from '../utils/leave-requests.select';
interface HolidayUpsertResult { interface HolidayUpsertResult {
action: HolidayUpsertAction; action: HolidayUpsertAction;
@ -204,7 +202,7 @@ export class HolidayLeaveRequestsService {
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' })); const deleted = rows.map((row) => ({ ...mapRowToView(row), action: 'delete' as const}));
return { action: 'delete', leave_requests: deleted }; return { action: 'delete', leave_requests: deleted };
} }
@ -320,4 +318,4 @@ const toDateOnly = (iso: string): Date => {
const toISODateKey = (date: Date): string => date.toISOString().slice(0, 10); const toISODateKey = (date: Date): string => date.toISOString().slice(0, 10);
const normalizeDates = (dates: string[]): string[] => const normalizeDates = (dates: string[]): string[] =>
Array.from(new Set(dates.map((iso) => toISODateKey(toDateOnly(iso))))); Array.from(new Set(dates.map((iso) => toISODateKey(toDateOnly(iso)))));

View File

@ -1,32 +1,19 @@
import { LeaveRequestViewDto } from "../dtos/leave-request.view.dto"; import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto';
import { mapArchiveRowToView } from "../mappers/leave-requests-archive.mapper"; import { mapArchiveRowToView } from '../mappers/leave-requests-archive.mapper';
import { mapRowToView } from "../mappers/leave-requests.mapper"; import { mapRowToView } from '../mappers/leave-requests.mapper';
import { LeaveRequestArchiveRow } from "./leave-requests-archive.select"; import { LeaveRequestArchiveRow } from './leave-requests-archive.select';
import { LeaveRequestRow } from "./leave-requests.select"; import { LeaveRequestRow } from './leave-requests.select';
function toUTCDateOnly(date: Date): Date { /** Active (table leave_requests) : proxy to base mapper */
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
}
const MS_PER_DAY = 86_400_000;
function computeDaysRequested(start_date: Date, end_date?: Date | null): number {
const start = toUTCDateOnly(start_date);
const end = toUTCDateOnly(end_date ?? start_date);
const diff = Math.floor((end.getTime() - start.getTime()) / MS_PER_DAY) + 1;
return Math.max(1, diff);
}
/** Active (table leave_requests) : map + days_requested */
export function mapRowToViewWithDays(row: LeaveRequestRow): LeaveRequestViewDto { export function mapRowToViewWithDays(row: LeaveRequestRow): LeaveRequestViewDto {
const view = mapRowToView(row); return mapRowToView(row);
view.days_requested = computeDaysRequested(row.start_date_time, row.end_date_time);
return view;
} }
/** Archive (table leave_requests_archive) : map + days_requested */ /** Archive (table leave_requests_archive) : proxy to base mapper */
export function mapArchiveRowToViewWithDays(row: LeaveRequestArchiveRow, email: string, employee_full_name?: string): export function mapArchiveRowToViewWithDays(
LeaveRequestViewDto { row: LeaveRequestArchiveRow,
const view = mapArchiveRowToView(row, email, employee_full_name!); email: string,
view.days_requested = computeDaysRequested(row.start_date_time, row.end_date_time); employee_full_name?: string,
return view; ): LeaveRequestViewDto {
return mapArchiveRowToView(row, email, employee_full_name!);
} }