feat(modules): added pay-periods view module with functions to navigate and search by date
This commit is contained in:
parent
49f99a6b9c
commit
4d538fc78a
|
|
@ -66,6 +66,17 @@ model LeaveRequests {
|
||||||
@@map("leave_requests")
|
@@map("leave_requests")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//pay-period vue
|
||||||
|
model PayPeriods {
|
||||||
|
period_number Int @id
|
||||||
|
start_date DateTime
|
||||||
|
end_date DateTime
|
||||||
|
year Int
|
||||||
|
label String
|
||||||
|
|
||||||
|
@@map("pay_period")
|
||||||
|
}
|
||||||
|
|
||||||
model Timesheets {
|
model Timesheets {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
employee Employees @relation("TimesheetEmployee", fields: [employee_id], references: [id])
|
employee Employees @relation("TimesheetEmployee", fields: [employee_id], references: [id])
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { TimesheetsModule } from './modules/timesheets/timesheets.module';
|
||||||
import { AuthenticationModule } from './modules/authentication/auth.module';
|
import { AuthenticationModule } from './modules/authentication/auth.module';
|
||||||
import { ExpensesModule } from './modules/expenses/expenses.module';
|
import { ExpensesModule } from './modules/expenses/expenses.module';
|
||||||
import { ExpenseCodesModule } from './modules/expense-codes/expense-codes.module';
|
import { ExpenseCodesModule } from './modules/expense-codes/expense-codes.module';
|
||||||
|
import { PayperiodModule } from './modules/pay-periods/pay-periods.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -31,6 +32,7 @@ import { ExpenseCodesModule } from './modules/expense-codes/expense-codes.module
|
||||||
ShiftsModule,
|
ShiftsModule,
|
||||||
TimesheetsModule,
|
TimesheetsModule,
|
||||||
AuthenticationModule,
|
AuthenticationModule,
|
||||||
|
PayperiodModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController, HealthController],
|
controllers: [AppController, HealthController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export class LeaveRequestController {
|
||||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||||
@ApiOperation({summary: 'Find all leave request' })
|
@ApiOperation({summary: 'Find all leave request' })
|
||||||
@ApiResponse({ status: 201, description: 'List of Leave requests found',type: LeaveRequestEntity, isArray: true })
|
@ApiResponse({ status: 201, description: 'List of Leave requests found',type: LeaveRequestEntity, isArray: true })
|
||||||
@ApiResponse({ status: 400, description: 'Leave request not found' })
|
@ApiResponse({ status: 400, description: 'List of leave request not found' })
|
||||||
findAll(): Promise<LeaveRequests[]> {
|
findAll(): Promise<LeaveRequests[]> {
|
||||||
return this.leaveRequetsService.findAll();
|
return this.leaveRequetsService.findAll();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { Controller, Get, Param, ParseIntPipe } from "@nestjs/common";
|
||||||
|
import { PayPeriods } from "@prisma/client";
|
||||||
|
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||||
|
import { PayPeriodsService } from "../services/pay-periods.service";
|
||||||
|
import { PayPeriodsOverviewService } from "../services/pay-periods-overview.service";
|
||||||
|
import { PayPeriodEntity } from "../dtos/swagger-entities/pay-period.dto";
|
||||||
|
import { PayPeriodOverviewDto } from "../dtos/pay-period-overview.dto";
|
||||||
|
|
||||||
|
|
||||||
|
@ApiTags('pay-periods')
|
||||||
|
@Controller('pay-periods')
|
||||||
|
export class PayPeriodsController {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly payPeriodsService: PayPeriodsService,
|
||||||
|
private readonly overviewService: PayPeriodsOverviewService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Find all pay period' })
|
||||||
|
@ApiResponse({status: 200,description: 'List of pay period found', type: PayPeriodEntity, isArray: true })
|
||||||
|
@ApiResponse({status: 400, description: 'List of pay period not found' })
|
||||||
|
async findAll(): Promise<PayPeriods[]> {
|
||||||
|
return this.payPeriodsService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':periodNumber')
|
||||||
|
@ApiOperation({ summary: 'Find pay period' })
|
||||||
|
@ApiResponse({status: 200,description: 'Pay period found', type: PayPeriodEntity })
|
||||||
|
@ApiResponse({status: 400, description: 'Pay period not found' })
|
||||||
|
findOne(@Param('periodNumber', ParseIntPipe) periodNumber: number): Promise<PayPeriods | null> {
|
||||||
|
return this.payPeriodsService.findOne(periodNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':periodNumber/overview')
|
||||||
|
@ApiOperation({ summary: 'detailed view of a pay period'})
|
||||||
|
@ApiResponse({ status: 200,description: 'Pay period overview found', type: PayPeriodOverviewDto })
|
||||||
|
@ApiResponse({status: 400, description: 'Pay period not found' })
|
||||||
|
|
||||||
|
async getOverview(@Param('periodNumber', ParseIntPipe) periodNumber: number):
|
||||||
|
Promise<PayPeriodOverviewDto> {
|
||||||
|
return this.overviewService.getOverview(periodNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('date/:date')
|
||||||
|
@ApiOperation({ summary: 'cherry picking a date to find a period'})
|
||||||
|
@ApiResponse({status:200, description: 'Pay period found for the selected date', type: PayPeriodEntity })
|
||||||
|
@ApiResponse({status:400, description: 'Pay period not found for the selected date date' })
|
||||||
|
async findByDate(@Param('date') date: string ):
|
||||||
|
Promise<PayPeriodEntity> {
|
||||||
|
return this.payPeriodsService.findByDate(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/modules/pay-periods/dtos/employee-period-overview.dto.ts
Normal file
27
src/modules/pay-periods/dtos/employee-period-overview.dto.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class EmployeePeriodOverviewDto {
|
||||||
|
@ApiProperty({
|
||||||
|
example: 'a1b2c3d4',
|
||||||
|
description: "Employee`s ID",
|
||||||
|
})
|
||||||
|
employee_id: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: 'Alex Dupont',
|
||||||
|
description: 'Employee`s full name',
|
||||||
|
})
|
||||||
|
employee_name: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: 34,
|
||||||
|
description: 'period`s total worked hours',
|
||||||
|
})
|
||||||
|
total_hours: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: true,
|
||||||
|
description: 'All timesheets are approved for this employee',
|
||||||
|
})
|
||||||
|
is_approved: boolean;
|
||||||
|
}
|
||||||
38
src/modules/pay-periods/dtos/pay-period-overview.dto.ts
Normal file
38
src/modules/pay-periods/dtos/pay-period-overview.dto.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { EmployeePeriodOverviewDto } from './employee-period-overview.dto';
|
||||||
|
|
||||||
|
export class PayPeriodOverviewDto {
|
||||||
|
@ApiProperty({
|
||||||
|
example: 1,
|
||||||
|
description: 'period`s number ( 1-26 )',
|
||||||
|
})
|
||||||
|
period_number: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: '2023-12-17',
|
||||||
|
type: String,
|
||||||
|
format: 'date',
|
||||||
|
description: 'Period`s starting date',
|
||||||
|
})
|
||||||
|
start_date: Date;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: '2023-12-30',
|
||||||
|
type: String,
|
||||||
|
format: 'date',
|
||||||
|
description: 'Period`s ending date',
|
||||||
|
})
|
||||||
|
end_date: Date;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: '2023-12-17 → 2023-12-30',
|
||||||
|
description: 'period`s label for showing',
|
||||||
|
})
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: [EmployeePeriodOverviewDto],
|
||||||
|
description: 'Detailed view by employee for a chosen period',
|
||||||
|
})
|
||||||
|
employees_overview: EmployeePeriodOverviewDto[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
export class PayPeriodEntity {
|
||||||
|
@ApiProperty({
|
||||||
|
example: 1,
|
||||||
|
description: 'numéro cyclique de la période entre 1 et 26'
|
||||||
|
})
|
||||||
|
period_number: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: '2023-12-17',
|
||||||
|
type: String,
|
||||||
|
format: 'date'
|
||||||
|
})
|
||||||
|
start_date: Date;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: '2023-12-30',
|
||||||
|
type: String,
|
||||||
|
format: 'date'
|
||||||
|
})
|
||||||
|
end_date: Date;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: 2023
|
||||||
|
})
|
||||||
|
year: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: '2023-12-17 → 2023-12-30'
|
||||||
|
})
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
12
src/modules/pay-periods/pay-periods.module.ts
Normal file
12
src/modules/pay-periods/pay-periods.module.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { PrismaModule } from "src/prisma/prisma.module";
|
||||||
|
import { PayPeriodsService } from "./services/pay-periods.service";
|
||||||
|
import { PayPeriodsController } from "./controllers/pay-periods.controller";
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
providers: [PayPeriodsService],
|
||||||
|
controllers: [PayPeriodsController],
|
||||||
|
})
|
||||||
|
|
||||||
|
export class PayperiodModule {}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||||
|
import { EmployeePeriodOverviewDto } from "../dtos/employee-period-overview.dto";
|
||||||
|
import { PayPeriodOverviewDto } from "../dtos/pay-period-overview.dto";
|
||||||
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PayPeriodsOverviewService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
//function to get a full overview of a selected period filtered by employee ID
|
||||||
|
async getOverview(periodNumber: number): Promise<PayPeriodOverviewDto> {
|
||||||
|
//fetch the period
|
||||||
|
const period = await this.prisma.payPeriods.findUnique({
|
||||||
|
where: { period_number: periodNumber },
|
||||||
|
});
|
||||||
|
if(!period) {
|
||||||
|
throw new NotFoundException(`Period #${periodNumber} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//fetch all included shifts for that period
|
||||||
|
const shifts = await this.prisma.shifts.findMany({
|
||||||
|
where: {
|
||||||
|
date: {
|
||||||
|
gte: period.start_date,
|
||||||
|
lte: period.end_date,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
timesheet: {
|
||||||
|
include: {
|
||||||
|
employee: { include: { user: true }},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//regroup by employee
|
||||||
|
const map = new Map<string, EmployeePeriodOverviewDto>();
|
||||||
|
for (const shift of shifts) {
|
||||||
|
const employee_record = shift.timesheet.employee;
|
||||||
|
const user = employee_record.user;
|
||||||
|
const employee_id = employee_record.user_id;
|
||||||
|
const employee_name = `${user.first_name} ${user.last_name}`;
|
||||||
|
const hours = (shift.end_time.getTime() - shift.start_time.getTime() / 3600000);
|
||||||
|
|
||||||
|
//check if employee had prior shifts and adds hours of found shift to the total hours
|
||||||
|
if (map.has(employee_id)) {
|
||||||
|
const summary = map.get(employee_id)!;
|
||||||
|
summary.total_hours += hours;
|
||||||
|
//keeps is_approved false as long as a single shift is left un-validated
|
||||||
|
summary.is_approved = summary.is_approved && shift.timesheet.is_approved;
|
||||||
|
} else {
|
||||||
|
//if first shift of an employee is found, it adds a new entry
|
||||||
|
map.set(employee_id, {
|
||||||
|
employee_id: employee_id,
|
||||||
|
employee_name: employee_name,
|
||||||
|
total_hours: hours,
|
||||||
|
is_approved: shift.timesheet.is_approved,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
period_number: period.period_number,
|
||||||
|
start_date: period.start_date,
|
||||||
|
end_date: period.end_date,
|
||||||
|
label: period.label,
|
||||||
|
employees_overview: Array.from(map.values()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/modules/pay-periods/services/pay-periods.service.ts
Normal file
37
src/modules/pay-periods/services/pay-periods.service.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||||
|
import { PayPeriods } from "@prisma/client";
|
||||||
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
import { PayPeriodOverviewDto } from "../dtos/pay-period-overview.dto";
|
||||||
|
import { EmployeePeriodOverviewDto } from "../dtos/employee-period-overview.dto";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PayPeriodsService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async findAll(): Promise<PayPeriods[]> {
|
||||||
|
return this.prisma.payPeriods.findMany({
|
||||||
|
orderBy: { period_number: 'asc'},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(periodNumber: number): Promise<PayPeriods | null> {
|
||||||
|
return this.prisma.payPeriods.findUnique({
|
||||||
|
where: { period_number: periodNumber},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//function to cherry pick a Date to find a period
|
||||||
|
async findByDate(date:string): Promise<PayPeriods> {
|
||||||
|
const dt = new Date(date);
|
||||||
|
const period = await this.prisma.payPeriods.findFirst({
|
||||||
|
where: {
|
||||||
|
start_date: { lte: dt },
|
||||||
|
end_date: { gte: dt },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if(!period) {
|
||||||
|
throw new NotFoundException(`No period found for this date: ${date}`);
|
||||||
|
}
|
||||||
|
return period;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user