feat(archival): setup services and modules for archivation options via Cron job. small fixes to schema.prisma
This commit is contained in:
parent
a7c8b62012
commit
5274bf41c1
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "shifts_archive" ALTER COLUMN "archive_at" SET DEFAULT CURRENT_TIMESTAMP;
|
||||
|
|
@ -43,7 +43,7 @@ model Employees {
|
|||
|
||||
supervisor Employees? @relation("EmployeeSupervisor", fields: [supervisor_id], references: [id])
|
||||
supervisor_id Int?
|
||||
managed_employees Employees[] @relation("EmployeeSupervisor") //changer pour crew à la prochaine MaJ
|
||||
crew Employees[] @relation("EmployeeSupervisor")
|
||||
|
||||
archive EmployeesArchive[] @relation("EmployeeToArchive")
|
||||
timesheet Timesheets[] @relation("TimesheetEmployee")
|
||||
|
|
@ -183,7 +183,7 @@ model ShiftsArchive {
|
|||
id Int @id @default(autoincrement())
|
||||
shift Shifts @relation("ShiftsToArchive", fields: [shift_id], references: [id])
|
||||
shift_id Int
|
||||
archive_at DateTime
|
||||
archive_at DateTime @default(now())
|
||||
timesheet_id Int
|
||||
shift_code_id Int
|
||||
description String?
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ import { ExpensesModule } from './modules/expenses/expenses.module';
|
|||
import { ExpenseCodesModule } from './modules/expense-codes/expense-codes.module';
|
||||
import { PayperiodsModule } from './modules/pay-periods/pay-periods.module';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { ArchivalModule } from './modules/archival/archival.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ScheduleModule.forRoot(),
|
||||
ArchivalModule,
|
||||
PrismaModule,
|
||||
HealthModule,
|
||||
UsersModule,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import 'reflect-metadata';
|
||||
//import and if case for @nestjs/schedule Cron jobs
|
||||
import * as nodeCrypto from 'crypto';
|
||||
if(!(globalThis as any).crypto) {
|
||||
(globalThis as any).crypto = nodeCrypto;
|
||||
}
|
||||
import { ModuleRef, NestFactory, Reflector } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
|
|
|
|||
20
src/modules/archival/archival.module.ts
Normal file
20
src/modules/archival/archival.module.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { ScheduleModule } from "@nestjs/schedule";
|
||||
import { TimesheetsModule } from "../timesheets/timesheets.module";
|
||||
import { ExpensesModule } from "../expenses/expenses.module";
|
||||
import { ShiftsModule } from "../shifts/shifts.module";
|
||||
import { LeaveRequestsModule } from "../leave-requests/leave-requests.module";
|
||||
import { ArchivalService } from "./services/archival.service";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ScheduleModule,
|
||||
TimesheetsModule,
|
||||
ExpensesModule,
|
||||
ShiftsModule,
|
||||
LeaveRequestsModule,
|
||||
],
|
||||
providers: [ArchivalService],
|
||||
})
|
||||
|
||||
export class ArchivalModule {}
|
||||
40
src/modules/archival/services/archival.service.ts
Normal file
40
src/modules/archival/services/archival.service.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { Cron, Timeout } from "@nestjs/schedule";
|
||||
import { ExpensesService } from "src/modules/expenses/services/expenses.service";
|
||||
import { LeaveRequestsService } from "src/modules/leave-requests/services/leave-request.service";
|
||||
import { ShiftsService } from "src/modules/shifts/services/shifts.service";
|
||||
import { TimesheetsService } from "src/modules/timesheets/services/timesheets.service";
|
||||
|
||||
@Injectable()
|
||||
export class ArchivalService {
|
||||
private readonly logger = new Logger(ArchivalService.name);
|
||||
|
||||
constructor(
|
||||
private readonly timesheetsService: TimesheetsService,
|
||||
private readonly expensesService: ExpensesService,
|
||||
private readonly shiftsService: ShiftsService,
|
||||
private readonly leaveRequestsService: LeaveRequestsService,
|
||||
) {}
|
||||
|
||||
@Cron('0 0 3 * * 1', {timeZone:'America/Toronto'}) // chaque premier lundi du mois à 03h00
|
||||
async handleMonthlyArchival() {
|
||||
const today = new Date();
|
||||
const dayOfMonth = today.getDate();
|
||||
|
||||
if (dayOfMonth > 7) {
|
||||
this.logger.log('Archive {awaiting 1st monday of the month for archivation process}')
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log('monthly archivation in process');
|
||||
try {
|
||||
await this.timesheetsService.archiveOld();
|
||||
await this.expensesService.archiveOld();
|
||||
await this.shiftsService.archiveOld();
|
||||
await this.leaveRequestsService.archiveExpired();
|
||||
this.logger.log('archivation process done');
|
||||
} catch (err) {
|
||||
this.logger.log('an error occured during archivation ', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Body,Controller,Delete,Get,Param,ParseIntPipe,Patch,Post,UseGuards } from '@nestjs/common';
|
||||
import { Body,Controller,Delete,Get,NotFoundException,Param,ParseIntPipe,Patch,Post,UseGuards } from '@nestjs/common';
|
||||
import { Employees, Roles as RoleEnum } from '@prisma/client';
|
||||
import { EmployeesService } from '../services/employees.service';
|
||||
import { CreateEmployeeDto } from '../dtos/create-employee.dto';
|
||||
|
|
@ -62,4 +62,16 @@ export class EmployeesController {
|
|||
remove(@Param('id', ParseIntPipe) id: number): Promise<Employees> {
|
||||
return this.employeesService.remove(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
async updateOrArchiveOrRestore(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateEmployeeDto,
|
||||
) {
|
||||
const result = await this.employeesService.patchEmployee(+id, dto);
|
||||
if(!result) {
|
||||
throw new NotFoundException(`Employee #${ id } not found in active or archive.`)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateEmployeeDto } from './create-employee.dto';
|
||||
|
||||
export class UpdateEmployeeDto extends PartialType(CreateEmployeeDto) {}
|
||||
export class UpdateEmployeeDto extends PartialType(CreateEmployeeDto) {
|
||||
first_work_day?: Date;
|
||||
last_work_day?: Date;
|
||||
supervisor_id?: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,4 +107,86 @@ async update(
|
|||
await this.findOne(id);
|
||||
return this.prisma.employees.delete({ where: { id } });
|
||||
}
|
||||
|
||||
|
||||
//archivation function
|
||||
async patchEmployee(id: number, dto: UpdateEmployeeDto): Promise<any> {
|
||||
//fetching existing employee
|
||||
const existing = await this.prisma.employees.findUnique({
|
||||
where: { id },
|
||||
include: { user: true, archive: true },
|
||||
});
|
||||
if (existing) {
|
||||
//verify last_work_day is not null => trigger archivation
|
||||
if(dto.last_work_day != undefined && existing.last_work_day == null) {
|
||||
return this.archiveOnTermination(existing, dto);
|
||||
}
|
||||
//if null => regular update
|
||||
return this.prisma.employees.update({
|
||||
where: { id },
|
||||
data: dto,
|
||||
});
|
||||
}
|
||||
//if not found => fetch archives side for restoration
|
||||
const archived = await this.prisma.employeesArchive.findFirst({
|
||||
where: { employee_id: id },
|
||||
include: { employee: true, user: true },
|
||||
});
|
||||
if (archived) {
|
||||
//conditions for restoration
|
||||
const restore = dto.last_work_day === null || dto.first_work_day != null;
|
||||
if(restore) {
|
||||
return this.restoreEmployee(archived, dto);
|
||||
}
|
||||
}
|
||||
//if neither activated nor archivated => 404
|
||||
return null;
|
||||
}
|
||||
|
||||
//transfers the employee to archive and then delete from employees table
|
||||
private async archiveOnTermination(existing: any, dto: UpdateEmployeeDto): Promise<any> {
|
||||
return this.prisma.$transaction(async transaction => {
|
||||
//archive insertion
|
||||
const archived = await transaction.employeesArchive.create({
|
||||
data: {
|
||||
employee_id: existing.id,
|
||||
user_id: existing.user_id,
|
||||
first_name: existing.first_name,
|
||||
last_name: existing.last_name,
|
||||
external_payroll_id: existing.external_payroll_id,
|
||||
company_code: existing.company_code,
|
||||
first_Work_Day: existing.first_Work_Day,
|
||||
last_work_day: existing.last_work_day,
|
||||
supervisor_id: existing.supervisor_id ?? null,
|
||||
},
|
||||
});
|
||||
//delete from employees table
|
||||
await transaction.employees.delete({ where: { id: existing.id } });
|
||||
//return archived employee
|
||||
return archived
|
||||
});
|
||||
}
|
||||
|
||||
//transfers the employee from archive to the employees table
|
||||
private async restoreEmployee(archived: any, dto: UpdateEmployeeDto): Promise<any> {
|
||||
return this.prisma.$transaction(async transaction => {
|
||||
//restores the archived employee into the employees table
|
||||
const restored = await transaction.employees.create({
|
||||
data: {
|
||||
id: archived.employee_id,
|
||||
user_id: archived.user_id,
|
||||
external_payroll_id: dto.external_payroll_id ?? archived.external_payroll_id,
|
||||
company_code: dto.company_code ?? archived.company_code,
|
||||
first_work_day: dto.first_work_day ?? archived.first_Work_Day,
|
||||
last_work_day: null,
|
||||
supervisor_id: dto.supervisor_id ?? archived.supervisor_id,
|
||||
},
|
||||
});
|
||||
//deleting archived entry by id
|
||||
await transaction.employeesArchive.delete({ where: { id: archived.id } });
|
||||
|
||||
//return restored employee
|
||||
return restored;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import { ExpensesService } from "./services/expenses.service";
|
|||
|
||||
@Module({
|
||||
controllers: [ExpensesController],
|
||||
providers: [ExpensesService, PrismaService]
|
||||
providers: [ExpensesService, PrismaService],
|
||||
exports: [ ExpensesService ],
|
||||
})
|
||||
|
||||
export class ExpensesModule {}
|
||||
|
|
@ -86,4 +86,50 @@ export class ExpensesService {
|
|||
await this.findOne(id);
|
||||
return this.prisma.expenses.delete({ where: { id } });
|
||||
}
|
||||
|
||||
|
||||
//archivation function
|
||||
async archiveOld(): Promise<void> {
|
||||
//fetches archived timesheet's Ids
|
||||
const archivedTimesheets = await this.prisma.timesheetsArchive.findMany({
|
||||
select: { timesheet_id: true },
|
||||
});
|
||||
|
||||
const timesheetIds = archivedTimesheets.map(sheet => sheet.timesheet_id);
|
||||
if(timesheetIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// copy/delete transaction
|
||||
await this.prisma.$transaction(async transaction => {
|
||||
//fetches expenses to move to archive
|
||||
const expensesToArchive = await transaction.expenses.findMany({
|
||||
where: { timesheet_id: { in: timesheetIds } },
|
||||
});
|
||||
if(expensesToArchive.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//copies sent to archive table
|
||||
await transaction.expensesArchive.createMany({
|
||||
data: expensesToArchive.map(exp => ({
|
||||
expense_id: exp.id,
|
||||
timesheet_id: exp.timesheet_id,
|
||||
expense_code_id: exp.expense_code_id,
|
||||
date: exp.date,
|
||||
amount: exp.amount,
|
||||
attachement: exp.attachement,
|
||||
description: exp.description,
|
||||
is_approved: exp.is_approved,
|
||||
supervisor_comment: exp.supervisor_comment,
|
||||
})),
|
||||
});
|
||||
|
||||
//delete from expenses table
|
||||
await transaction.expenses.deleteMany({
|
||||
where: { id: { in: expensesToArchive.map(exp => exp.id) } },
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import { Module } from "@nestjs/common";
|
|||
@Module({
|
||||
controllers: [LeaveRequestController],
|
||||
providers: [ LeaveRequestsService, PrismaService],
|
||||
exports: [ LeaveRequestsService],
|
||||
})
|
||||
|
||||
export class LeaveRequestsModule {}
|
||||
|
|
@ -106,4 +106,36 @@ export class LeaveRequestsService {
|
|||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
//archivation function
|
||||
async archiveExpired(): Promise<void> {
|
||||
const now = new Date();
|
||||
|
||||
await this.prisma.$transaction(async transaction => {
|
||||
//fetches expired leave requests
|
||||
const expired = await transaction.leaveRequests.findMany({
|
||||
where: { end_date_time: { lt: now } },
|
||||
});
|
||||
if(expired.length === 0) {
|
||||
return;
|
||||
}
|
||||
//copy unto archive table
|
||||
await transaction.leaveRequestsArchive.createMany({
|
||||
data: expired.map(request => ({
|
||||
leave_request_id: request.id,
|
||||
employee_id: request.employee_id,
|
||||
leave_type: request.leave_type,
|
||||
start_date_time: request.start_date_time,
|
||||
end_date_time: request.end_date_time,
|
||||
comment: request.comment,
|
||||
approval_status: request.approval_status,
|
||||
})),
|
||||
});
|
||||
//delete from leave_requests table
|
||||
await transaction.leaveRequests.deleteMany({
|
||||
where: { id: { in: expired.map(request => request.id ) } },
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -86,4 +86,48 @@ export class ShiftsService {
|
|||
await this.findOne(id);
|
||||
return this.prisma.shifts.delete({ where: { id } });
|
||||
}
|
||||
|
||||
//archivation function
|
||||
async archiveOld(): Promise<void> {
|
||||
//fetches archived timesheet's Ids
|
||||
const archivedTimesheets = await this.prisma.timesheetsArchive.findMany({
|
||||
select: { timesheet_id: true },
|
||||
});
|
||||
|
||||
const timesheetIds = archivedTimesheets.map(sheet => sheet.timesheet_id);
|
||||
if(timesheetIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// copy/delete transaction
|
||||
await this.prisma.$transaction(async transaction => {
|
||||
//fetches shifts to move to archive
|
||||
const shiftsToArchive = await transaction.shifts.findMany({
|
||||
where: { timesheet_id: { in: timesheetIds } },
|
||||
});
|
||||
if(shiftsToArchive.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//copies sent to archive table
|
||||
await transaction.shiftsArchive.createMany({
|
||||
data: shiftsToArchive.map(shift => ({
|
||||
shift_id: shift.id,
|
||||
timesheet_id: shift.timesheet_id,
|
||||
shift_code_id: shift.shift_code_id,
|
||||
description: shift.description ?? undefined,
|
||||
date: shift.date,
|
||||
start_time: shift.start_time,
|
||||
end_time: shift.end_time,
|
||||
})),
|
||||
});
|
||||
|
||||
//delete from shifts table
|
||||
await transaction.shifts.deleteMany({
|
||||
where: { id: { in: shiftsToArchive.map(shift => shift.id) } },
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import { PrismaService } from 'src/prisma/prisma.service';
|
|||
|
||||
@Module({
|
||||
controllers: [ShiftsController],
|
||||
providers: [ShiftsService, PrismaService]
|
||||
providers: [ShiftsService, PrismaService],
|
||||
exports: [ShiftsService],
|
||||
})
|
||||
export class ShiftsModule {}
|
||||
|
|
|
|||
|
|
@ -75,4 +75,44 @@ export class TimesheetsService {
|
|||
}
|
||||
|
||||
|
||||
//Archivation function
|
||||
async archiveOld(): Promise<void> {
|
||||
//calcul du cutoff pour archivation
|
||||
const cutoff = new Date();
|
||||
cutoff.setMonth(cutoff.getMonth() - 6)
|
||||
|
||||
await this.prisma.$transaction(async transaction => {
|
||||
//fetches all timesheets to cutoff
|
||||
const oldSheets = await transaction.timesheets.findMany({
|
||||
where: { shift: { every: { date: { lt: cutoff } },
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
employee_id: true,
|
||||
is_approved: true,
|
||||
},
|
||||
});
|
||||
if( oldSheets.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//preping data for archivation
|
||||
const archiveDate = oldSheets.map(sheet => ({
|
||||
timesheet_id: sheet.id,
|
||||
employee_id: sheet.employee_id,
|
||||
is_approved: sheet.is_approved,
|
||||
}));
|
||||
|
||||
//copying data from timesheets table to archive table
|
||||
await transaction.timesheetsArchive.createMany({
|
||||
data: archiveDate,
|
||||
});
|
||||
|
||||
//removing data from timesheets table
|
||||
await transaction.timesheets.deleteMany({
|
||||
where: { id: { in: oldSheets.map(s => s.id) } },
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { TimesheetsService } from './services/timesheets.service';
|
|||
|
||||
@Module({
|
||||
controllers: [TimesheetsController],
|
||||
providers: [TimesheetsService]
|
||||
providers: [TimesheetsService],
|
||||
exports: [TimesheetsService],
|
||||
})
|
||||
export class TimesheetsModule {}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user