refactor(validation): Partial modification of the file structure for validation process, added migration 20250806
This commit is contained in:
parent
b0406b3a4c
commit
cb6ec29992
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "shifts" ADD COLUMN "is_approved" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
|
@ -175,6 +175,7 @@ model Shifts {
|
||||||
date DateTime @db.Date
|
date DateTime @db.Date
|
||||||
start_time DateTime @db.Time(0)
|
start_time DateTime @db.Time(0)
|
||||||
end_time DateTime @db.Time(0)
|
end_time DateTime @db.Time(0)
|
||||||
|
is_approved Boolean @default(false)
|
||||||
|
|
||||||
archive ShiftsArchive[] @relation("ShiftsToArchive")
|
archive ShiftsArchive[] @relation("ShiftsToArchive")
|
||||||
|
|
||||||
|
|
@ -283,7 +284,8 @@ enum LeaveTypes {
|
||||||
BEREAVEMENT // deuil de famille
|
BEREAVEMENT // deuil de famille
|
||||||
PARENTAL // maternite/paternite/adoption
|
PARENTAL // maternite/paternite/adoption
|
||||||
LEGAL // obligations legales comme devoir de juree
|
LEGAL // obligations legales comme devoir de juree
|
||||||
|
WEDDING // mariage
|
||||||
|
|
||||||
@@map("leave_types")
|
@@map("leave_types")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import { ArchivalModule } from './modules/archival/archival.module';
|
||||||
import { BankCodesModule } from './modules/bank-codes/bank-codes.module';
|
import { BankCodesModule } from './modules/bank-codes/bank-codes.module';
|
||||||
import { OvertimeService } from './modules/business-logics/services/overtime.service';
|
import { OvertimeService } from './modules/business-logics/services/overtime.service';
|
||||||
import { BusinessLogicsModule } from './modules/business-logics/business-logics.module';
|
import { BusinessLogicsModule } from './modules/business-logics/business-logics.module';
|
||||||
import { ShiftsValidationModule } from './modules/shifts/validation/shifts-validation.module';
|
|
||||||
import { OauthSessionsModule } from './modules/oauth-sessions/oauth-sessions.module';
|
import { OauthSessionsModule } from './modules/oauth-sessions/oauth-sessions.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
|
@ -37,7 +36,6 @@ import { OauthSessionsModule } from './modules/oauth-sessions/oauth-sessions.mod
|
||||||
PayperiodsModule,
|
PayperiodsModule,
|
||||||
PrismaModule,
|
PrismaModule,
|
||||||
ShiftsModule,
|
ShiftsModule,
|
||||||
ShiftsValidationModule,
|
|
||||||
TimesheetsModule,
|
TimesheetsModule,
|
||||||
UsersModule,
|
UsersModule,
|
||||||
],
|
],
|
||||||
|
|
|
||||||
11
src/modules/expenses/services/expenses-approval.service.ts
Normal file
11
src/modules/expenses/services/expenses-approval.service.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ExpensesApprovalService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async updateApproval(expenseId: number, isApproved: boolean) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { TimesheetsApprovalService } from "src/modules/timesheets/services/timesheets-approval.service";
|
||||||
|
|
||||||
|
export class PayPeriodsApprovalService {
|
||||||
|
constructor(private readonly timesheetsApproval: TimesheetsApprovalService) {}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import { Controller, Get, Header, Query } from "@nestjs/common";
|
import { Controller, Get, Header, Query } from "@nestjs/common";
|
||||||
import { ShiftsValidationService, ValidationRow } from "../services/shifts-validation.service";
|
import { OverviewRow, ShiftsOverviewService } from "../services/shifts-overview.service";
|
||||||
import { GetShiftsValidationDto } from "../dtos/get-shifts-validation.dto";
|
import { GetShiftsOverviewDto } from "../dtos/get-shifts-overview.dto";
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class ShiftsValidationController {
|
export class ShiftsOverviewController {
|
||||||
constructor(private readonly shiftsValidationService: ShiftsValidationService) {}
|
constructor(private readonly shiftsValidationService: ShiftsOverviewService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
async getSummary( @Query() query: GetShiftsValidationDto): Promise<ValidationRow[]> {
|
async getSummary( @Query() query: GetShiftsOverviewDto): Promise<OverviewRow[]> {
|
||||||
return this.shiftsValidationService.getSummary(query.periodId);
|
return this.shiftsValidationService.getSummary(query.periodId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('export.csv')
|
@Get('export.csv')
|
||||||
@Header('Content-Type', 'text/csv; charset=utf-8')
|
@Header('Content-Type', 'text/csv; charset=utf-8')
|
||||||
@Header('Content-Disposition', 'attachment; filename="shifts-validation.csv"')
|
@Header('Content-Disposition', 'attachment; filename="shifts-validation.csv"')
|
||||||
async exportCsv(@Query() query: GetShiftsValidationDto): Promise<Buffer>{
|
async exportCsv(@Query() query: GetShiftsOverviewDto): Promise<Buffer>{
|
||||||
const rows = await this.shiftsValidationService.getSummary(query.periodId);
|
const rows = await this.shiftsValidationService.getSummary(query.periodId);
|
||||||
|
|
||||||
//CSV Headers
|
//CSV Headers
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from "@nestjs/common";
|
import { Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, UseGuards } from "@nestjs/common";
|
||||||
import { ShiftsService } from "../services/shifts.service";
|
import { ShiftsService } from "../services/shifts.service";
|
||||||
import { Shifts } from "@prisma/client";
|
import { Shifts } from "@prisma/client";
|
||||||
import { CreateShiftDto } from "../dtos/create-shifts.dto";
|
import { CreateShiftDto } from "../dtos/create-shifts.dto";
|
||||||
|
|
@ -8,57 +8,67 @@ import { Roles as RoleEnum } from '.prisma/client';
|
||||||
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||||
import { JwtAuthGuard } from "src/modules/authentication/guards/jwt-auth.guard";
|
import { JwtAuthGuard } from "src/modules/authentication/guards/jwt-auth.guard";
|
||||||
import { ShiftEntity } from "../dtos/swagger-entities/shift.entity";
|
import { ShiftEntity } from "../dtos/swagger-entities/shift.entity";
|
||||||
|
import { ShiftsApprovalService } from "../services/shifts-approval.service";
|
||||||
|
|
||||||
@ApiTags('Shifts')
|
@ApiTags('Shifts')
|
||||||
@ApiBearerAuth('access-token')
|
@ApiBearerAuth('access-token')
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Controller('shifts')
|
@Controller('shifts')
|
||||||
export class ShiftsController {
|
export class ShiftsController {
|
||||||
constructor(private readonly shiftsService: ShiftsService){}
|
constructor(
|
||||||
|
private readonly shiftsService: ShiftsService,
|
||||||
@Post()
|
private readonly shiftsApprovalService: ShiftsApprovalService,
|
||||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
){}
|
||||||
@ApiOperation({ summary: 'Create shift' })
|
|
||||||
@ApiResponse({ status: 201, description: 'Shift created',type: ShiftEntity })
|
|
||||||
@ApiResponse({ status: 400, description: 'Incomplete task or invalid data' })
|
|
||||||
create(@Body() dto: CreateShiftDto): Promise<Shifts> {
|
|
||||||
return this.shiftsService.create(dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
@Post()
|
||||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||||
@ApiOperation({ summary: 'Find all shifts' })
|
@ApiOperation({ summary: 'Create shift' })
|
||||||
@ApiResponse({ status: 201, description: 'List of shifts found',type: ShiftEntity, isArray: true })
|
@ApiResponse({ status: 201, description: 'Shift created',type: ShiftEntity })
|
||||||
@ApiResponse({ status: 400, description: 'List of shifts not found' })
|
@ApiResponse({ status: 400, description: 'Incomplete task or invalid data' })
|
||||||
findAll(): Promise<Shifts[]> {
|
create(@Body() dto: CreateShiftDto): Promise<Shifts> {
|
||||||
return this.shiftsService.findAll();
|
return this.shiftsService.create(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get()
|
||||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||||
@ApiOperation({ summary: 'Find shift' })
|
@ApiOperation({ summary: 'Find all shifts' })
|
||||||
@ApiResponse({ status: 201, description: 'Shift found',type: ShiftEntity })
|
@ApiResponse({ status: 201, description: 'List of shifts found',type: ShiftEntity, isArray: true })
|
||||||
@ApiResponse({ status: 400, description: 'Shift not found' })
|
@ApiResponse({ status: 400, description: 'List of shifts not found' })
|
||||||
findOne(@Param('id', ParseIntPipe) id: number): Promise<Shifts> {
|
findAll(): Promise<Shifts[]> {
|
||||||
return this.shiftsService.findOne(id);
|
return this.shiftsService.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Get(':id')
|
||||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||||
@ApiOperation({ summary: 'Update shift' })
|
@ApiOperation({ summary: 'Find shift' })
|
||||||
@ApiResponse({ status: 201, description: 'Shift updated',type: ShiftEntity })
|
@ApiResponse({ status: 201, description: 'Shift found',type: ShiftEntity })
|
||||||
@ApiResponse({ status: 400, description: 'Shift not found' })
|
@ApiResponse({ status: 400, description: 'Shift not found' })
|
||||||
update(@Param('id', ParseIntPipe) id: number,@Body() dto: UpdateShiftsDto): Promise<Shifts> {
|
findOne(@Param('id', ParseIntPipe) id: number): Promise<Shifts> {
|
||||||
return this.shiftsService.update(id, dto);
|
return this.shiftsService.findOne(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Patch(':id')
|
||||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||||
@ApiOperation({ summary: 'Delete shift' })
|
@ApiOperation({ summary: 'Update shift' })
|
||||||
@ApiResponse({ status: 201, description: 'Shift deleted',type: ShiftEntity })
|
@ApiResponse({ status: 201, description: 'Shift updated',type: ShiftEntity })
|
||||||
@ApiResponse({ status: 400, description: 'Shift not found' })
|
@ApiResponse({ status: 400, description: 'Shift not found' })
|
||||||
remove(@Param('id', ParseIntPipe) id: number): Promise<Shifts> {
|
update(@Param('id', ParseIntPipe) id: number,@Body() dto: UpdateShiftsDto): Promise<Shifts> {
|
||||||
return this.shiftsService.remove(id);
|
return this.shiftsService.update(id, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||||
|
@ApiOperation({ summary: 'Delete shift' })
|
||||||
|
@ApiResponse({ status: 201, description: 'Shift deleted',type: ShiftEntity })
|
||||||
|
@ApiResponse({ status: 400, description: 'Shift not found' })
|
||||||
|
remove(@Param('id', ParseIntPipe) id: number): Promise<Shifts> {
|
||||||
|
return this.shiftsService.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id/approval')
|
||||||
|
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||||
|
async approve(@Param('id', ParseIntPipe) id: number, @Body('is_approved', ParseBoolPipe) isApproved: boolean) {
|
||||||
|
return this.shiftsApprovalService.updateApproval(id, isApproved);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Type } from "class-transformer";
|
import { Type } from "class-transformer";
|
||||||
import { IsInt, Min, Max } from "class-validator";
|
import { IsInt, Min, Max } from "class-validator";
|
||||||
|
|
||||||
export class GetShiftsValidationDto {
|
export class GetShiftsOverviewDto {
|
||||||
@Type(()=> Number)
|
@Type(()=> Number)
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(1)
|
@Min(1)
|
||||||
21
src/modules/shifts/services/shifts-approval.service.ts
Normal file
21
src/modules/shifts/services/shifts-approval.service.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||||
|
import { Shifts } from "@prisma/client";
|
||||||
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ShiftsApprovalService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async updateApproval(shiftId: number, isApproved: boolean): Promise<Shifts> {
|
||||||
|
const shift = await this.prisma.shifts.update({
|
||||||
|
where: { id: shiftId },
|
||||||
|
data: { is_approved: isApproved },
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!shift) {
|
||||||
|
throw new NotFoundException(`Shift # ${shiftId} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return shift;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
||||||
export interface ValidationRow {
|
export interface OverviewRow {
|
||||||
fullName: string;
|
fullName: string;
|
||||||
supervisor: string;
|
supervisor: string;
|
||||||
totalRegularHrs: number;
|
totalRegularHrs: number;
|
||||||
|
|
@ -13,7 +13,7 @@ export interface ValidationRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ShiftsValidationService {
|
export class ShiftsOverviewService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
private computeHours(start: Date, end: Date): number {
|
private computeHours(start: Date, end: Date): number {
|
||||||
|
|
@ -22,7 +22,7 @@ export class ShiftsValidationService {
|
||||||
return parseFloat(hours.toFixed(2));
|
return parseFloat(hours.toFixed(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSummary(periodId: number): Promise<ValidationRow[]> {
|
async getSummary(periodId: number): Promise<OverviewRow[]> {
|
||||||
//fetch pay-period to display
|
//fetch pay-period to display
|
||||||
const period = await this.prisma.payPeriods.findUnique({
|
const period = await this.prisma.payPeriods.findUnique({
|
||||||
where: { period_number: periodId },
|
where: { period_number: periodId },
|
||||||
|
|
@ -57,7 +57,7 @@ export class ShiftsValidationService {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapRow = new Map<string, ValidationRow>();
|
const mapRow = new Map<string, OverviewRow>();
|
||||||
|
|
||||||
for(const s of shifts) {
|
for(const s of shifts) {
|
||||||
const employeeId = s.timesheet.employee.user_id;
|
const employeeId = s.timesheet.employee.user_id;
|
||||||
|
|
@ -119,4 +119,5 @@ export class ShiftsValidationService {
|
||||||
//return by default the list of employee in ascending alphabetical order
|
//return by default the list of employee in ascending alphabetical order
|
||||||
return Array.from(mapRow.values()).sort((a,b) => a.fullName.localeCompare(b.fullName));
|
return Array.from(mapRow.values()).sort((a,b) => a.fullName.localeCompare(b.fullName));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,15 +2,13 @@ import { Module } from '@nestjs/common';
|
||||||
import { ShiftsController } from './controllers/shifts.controller';
|
import { ShiftsController } from './controllers/shifts.controller';
|
||||||
import { ShiftsService } from './services/shifts.service';
|
import { ShiftsService } from './services/shifts.service';
|
||||||
import { BusinessLogicsModule } from 'src/modules/business-logics/business-logics.module';
|
import { BusinessLogicsModule } from 'src/modules/business-logics/business-logics.module';
|
||||||
import { ShiftsValidationModule } from './validation/shifts-validation.module';
|
import { ShiftsOverviewController } from './controllers/shifts-overview.controller';
|
||||||
|
import { ShiftsOverviewService } from './services/shifts-overview.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [BusinessLogicsModule],
|
||||||
BusinessLogicsModule,
|
controllers: [ShiftsController, ShiftsOverviewController],
|
||||||
ShiftsValidationModule,
|
providers: [ShiftsService, ShiftsOverviewService],
|
||||||
],
|
exports: [ShiftsService, ShiftsOverviewService],
|
||||||
controllers: [ShiftsController],
|
|
||||||
providers: [ShiftsService],
|
|
||||||
exports: [ShiftsService],
|
|
||||||
})
|
})
|
||||||
export class ShiftsModule {}
|
export class ShiftsModule {}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import { Module } from "@nestjs/common";
|
|
||||||
import { ShiftsValidationController } from "./controllers/shifts-validation.controller";
|
|
||||||
import { ShiftsValidationService } from "./services/shifts-validation.service";
|
|
||||||
import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module";
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [BusinessLogicsModule],
|
|
||||||
controllers: [ShiftsValidationController],
|
|
||||||
providers: [ShiftsValidationService],
|
|
||||||
})
|
|
||||||
export class ShiftsValidationModule {}
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||||
|
import { Timesheets } from "@prisma/client";
|
||||||
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TimesheetsApprovalService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async updateApproval(timesheetId: number, isApproved: boolean): Promise<Timesheets> {
|
||||||
|
const timesheet = await this.prisma.timesheets.update({
|
||||||
|
where: { id: timesheetId },
|
||||||
|
data: { is_approved: isApproved},
|
||||||
|
});
|
||||||
|
if (!timesheet) throw new NotFoundException(`Timesheet # ${timesheetId} not found`);
|
||||||
|
|
||||||
|
return timesheet;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user