refactor(pay-period): ajusted logics services and controller of model pay-periods
This commit is contained in:
parent
ae6ce4bf97
commit
f765a99273
|
|
@ -3035,22 +3035,27 @@
|
|||
"PayPeriodDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"period_number": {
|
||||
"pay_period_no": {
|
||||
"type": "number",
|
||||
"example": 1,
|
||||
"description": "numéro cyclique de la période entre 1 et 26"
|
||||
},
|
||||
"start_date": {
|
||||
"period_start": {
|
||||
"type": "string",
|
||||
"example": "2023-12-17",
|
||||
"format": "date"
|
||||
},
|
||||
"end_date": {
|
||||
"period_end": {
|
||||
"type": "string",
|
||||
"example": "2023-12-30",
|
||||
"format": "date"
|
||||
},
|
||||
"year": {
|
||||
"payday": {
|
||||
"type": "string",
|
||||
"example": "2023-01-04",
|
||||
"format": "date"
|
||||
},
|
||||
"pay_year": {
|
||||
"type": "number",
|
||||
"example": 2023
|
||||
},
|
||||
|
|
@ -3060,10 +3065,11 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"period_number",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"year",
|
||||
"pay_period_no",
|
||||
"period_start",
|
||||
"period_end",
|
||||
"payday",
|
||||
"pay_year",
|
||||
"label"
|
||||
]
|
||||
},
|
||||
|
|
@ -3155,28 +3161,34 @@
|
|||
"PayPeriodOverviewDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"period_number": {
|
||||
"pay_period_no": {
|
||||
"type": "number",
|
||||
"example": 1,
|
||||
"description": "Period number (1–26)"
|
||||
},
|
||||
"year": {
|
||||
"pay_year": {
|
||||
"type": "number",
|
||||
"example": 2023,
|
||||
"description": "Calendar year of the period"
|
||||
},
|
||||
"start_date": {
|
||||
"period_start": {
|
||||
"type": "string",
|
||||
"example": "2023-12-17",
|
||||
"format": "date",
|
||||
"description": "Period start date (YYYY-MM-DD)"
|
||||
},
|
||||
"end_date": {
|
||||
"period_end": {
|
||||
"type": "string",
|
||||
"example": "2023-12-30",
|
||||
"format": "date",
|
||||
"description": "Period end date (YYYY-MM-DD)"
|
||||
},
|
||||
"payday": {
|
||||
"type": "string",
|
||||
"example": "2023-12-30",
|
||||
"format": "date",
|
||||
"description": "Period pay day(YYYY-MM-DD)"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"example": "2023-12-17 → 2023-12-30",
|
||||
|
|
@ -3191,10 +3203,11 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"period_number",
|
||||
"year",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"pay_period_no",
|
||||
"pay_year",
|
||||
"period_start",
|
||||
"period_end",
|
||||
"payday",
|
||||
"label",
|
||||
"employees_overview"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,39 +5,41 @@
|
|||
CREATE OR REPLACE VIEW pay_period AS
|
||||
WITH
|
||||
anchor AS (
|
||||
SELECT '2023-12-17'::date AS anchor_date
|
||||
),
|
||||
current_pay_period AS(
|
||||
SELECT
|
||||
((now()::date - anchor_date) % 14) +1 AS current_day_in_pay_period
|
||||
FROM anchor
|
||||
),
|
||||
bounds AS (
|
||||
SELECT
|
||||
(now()::date
|
||||
- INTERVAL '6 months'
|
||||
- (current_day_in_pay_period || ' days')::INTERVAL
|
||||
)::date AS start_bound,
|
||||
(now()::date + INTERVAL '1 month'
|
||||
- (current_day_in_pay_period || ' days')::INTERVAL
|
||||
)::date AS end_bound,
|
||||
anchor.anchor_date
|
||||
FROM anchor
|
||||
CROSS JOIN current_pay_period
|
||||
SELECT '2023-12-17'::date AS anchor_sunday
|
||||
),
|
||||
series AS (
|
||||
SELECT
|
||||
generate_series(bounds.start_bound, bounds.end_bound, '14 days') AS period_start,
|
||||
bounds.anchor_date
|
||||
FROM bounds
|
||||
gs::date AS period_start, -- Dimanche
|
||||
(gs + INTERVAL '13 days')::date AS period_end, -- Samedi
|
||||
(gs + INTERVAL '18 days')::date AS payday -- Jeudi suivant pour viser l'année fiscale
|
||||
FROM generate_series(
|
||||
(SELECT anchor_sunday FROM anchor),
|
||||
(CURRENT_DATE + INTERVAL '1 month')::date,
|
||||
INTERVAL '14 days'
|
||||
) AS gs
|
||||
),
|
||||
numbered AS (
|
||||
SELECT
|
||||
period_start,
|
||||
period_end,
|
||||
payday,
|
||||
EXTRACT(YEAR FROM payday)::int AS pay_year,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY EXTRACT(YEAR FROM payday)
|
||||
ORDER BY payday
|
||||
) AS pay_period_no
|
||||
FROM series
|
||||
)
|
||||
SELECT
|
||||
((row_number() OVER (ORDER BY period_start) - 1) % 26) + 1 AS period_number,
|
||||
period_start AS start_date,
|
||||
period_start + INTERVAL '13 days' AS end_date,
|
||||
EXTRACT(YEAR FROM period_start)::int AS year,
|
||||
period_start || ' -> ' ||
|
||||
to_char(period_start + INTERVAL '13 days', 'YYYY-MM-DD')
|
||||
AS label
|
||||
FROM series
|
||||
ORDER BY period_start;
|
||||
SELECT
|
||||
pay_year,
|
||||
pay_period_no,
|
||||
period_start,
|
||||
period_end,
|
||||
payday,
|
||||
to_char(period_start, 'YYYY-MM-DD') || '->' ||
|
||||
to_char(period_end, 'YYYY-MM-DD') AS label
|
||||
FROM numbered
|
||||
|
||||
WHERE payday BETWEEN (CURRENT_DATE - INTERVAL '6 months')::date
|
||||
AND (CURRENT_DATE + INTERVAL '1 month')::date
|
||||
ORDER BY period_start;
|
||||
|
|
@ -135,12 +135,13 @@ model LeaveRequestsArchive {
|
|||
|
||||
//pay-period vue
|
||||
view PayPeriods {
|
||||
period_number Int
|
||||
start_date DateTime @db.Date
|
||||
end_date DateTime @db.Date
|
||||
year Int
|
||||
label String
|
||||
|
||||
pay_year Int
|
||||
pay_period_no Int
|
||||
payday DateTime @db.Date
|
||||
period_start DateTime @db.Date
|
||||
period_end DateTime @db.Date
|
||||
label String
|
||||
|
||||
@@map("pay_period")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export class HolidayService {
|
|||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
//switch employeeId for email
|
||||
private async computeHoursPrevious4Weeks(employeeId: number, holidayDate: Date): Promise<number> {
|
||||
//sets the end of the window to 1ms before the week with the holiday
|
||||
const holidayWeekStart = getWeekStart(holidayDate);
|
||||
|
|
@ -31,6 +32,7 @@ export class HolidayService {
|
|||
return dailyHours;
|
||||
}
|
||||
|
||||
//switch employeeId for email
|
||||
async calculateHolidayPay( employeeId: number, holidayDate: Date, modifier: number): Promise<number> {
|
||||
const hours = await this.computeHoursPrevious4Weeks(employeeId, holidayDate);
|
||||
const dailyRate = Math.min(hours, 8);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export class OvertimeService {
|
|||
}
|
||||
|
||||
//calculate Weekly overtime
|
||||
//switch employeeId for email
|
||||
async getWeeklyOvertimeHours(employeeId: number, refDate: Date): Promise<number> {
|
||||
const weekStart = getWeekStart(refDate);
|
||||
const weekEnd = getWeekEnd(weekStart);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export class SickLeaveService {
|
|||
|
||||
private readonly logger = new Logger(SickLeaveService.name);
|
||||
|
||||
//switch employeeId for email
|
||||
async calculateSickLeavePay(employeeId: number, referenceDate: Date, daysRequested: number, modifier: number): Promise<number> {
|
||||
//sets the year to jan 1st to dec 31st
|
||||
const periodStart = getYearStart(referenceDate);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export class VacationService {
|
|||
* @param modifier Coefficient of hours(1)
|
||||
* @returns amount of payable hours
|
||||
*/
|
||||
//switch employeeId for email
|
||||
async calculateVacationPay(employeeId: number, startDate: Date, daysRequested: number, modifier: number): Promise<number> {
|
||||
//fetch hiring date
|
||||
const employee = await this.prisma.employees.findUnique({
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export class CsvExportController {
|
|||
|
||||
//filters by type
|
||||
const filtered = all.filter(r => {
|
||||
switch (r.bankCode.toLocaleLowerCase()) {
|
||||
switch (r.bank_code.toLocaleLowerCase()) {
|
||||
case 'holiday' : return types.includes(ExportType.HOLIDAY);
|
||||
case 'vacation' : return types.includes(ExportType.VACATION);
|
||||
case 'sick-leave': return types.includes(ExportType.SICK_LEAVE);
|
||||
|
|
|
|||
|
|
@ -3,33 +3,33 @@ import { ExportCompany } from "../dtos/export-csv-options.dto";
|
|||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
|
||||
export interface CsvRow {
|
||||
companyCode: number;
|
||||
externalPayrollId: number;
|
||||
fullName: string;
|
||||
bankCode: string;
|
||||
quantityHours?: number;
|
||||
company_code: number;
|
||||
external_payroll_id: number;
|
||||
full_name: string;
|
||||
bank_code: string;
|
||||
quantity_hours?: number;
|
||||
amount?: number;
|
||||
weekNumber: number;
|
||||
payDate: string;
|
||||
holidayDate?: string;
|
||||
week_number: number;
|
||||
pay_date: string;
|
||||
holiday_date?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CsvExportService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async collectTransaction(periodId: number, companies: ExportCompany[]): Promise<CsvRow[]> {
|
||||
async collectTransaction(period_id: number, companies: ExportCompany[]): Promise<CsvRow[]> {
|
||||
const companyCodes = companies.map(c => c === ExportCompany.TARGO ? 1 : 2);
|
||||
|
||||
const period = await this.prisma.payPeriods.findFirst({
|
||||
where: { period_number: periodId },
|
||||
where: { pay_period_no: period_id },
|
||||
});
|
||||
if(!period) {
|
||||
throw new NotFoundException(`Pay period ${periodId} not found`);
|
||||
throw new NotFoundException(`Pay period ${period_id} not found`);
|
||||
}
|
||||
|
||||
const startDate = period.start_date;
|
||||
const endDate = period.end_date;
|
||||
const startDate = period.period_start;
|
||||
const endDate = period.period_end;
|
||||
|
||||
//fetching shifts
|
||||
const shifts = await this.prisma.shifts.findMany({
|
||||
|
|
@ -72,39 +72,39 @@ export class CsvExportService {
|
|||
const rows: CsvRow[] = [];
|
||||
|
||||
//Shifts Mapping
|
||||
for (const s of shifts) {
|
||||
const emp = s.timesheet.employee;
|
||||
const weekNumber = this.computeWeekNumber(startDate, s.date);
|
||||
const hours = this.computeHours(s.start_time, s.end_time);
|
||||
for (const shift of shifts) {
|
||||
const emp = shift.timesheet.employee;
|
||||
const week_number = this.computeWeekNumber(startDate, shift.date);
|
||||
const hours = this.computeHours(shift.start_time, shift.end_time);
|
||||
|
||||
rows.push({
|
||||
companyCode: emp.company_code,
|
||||
externalPayrollId: emp.external_payroll_id,
|
||||
fullName: `${emp.user.first_name} ${emp.user.last_name}`,
|
||||
bankCode: s.bank_code.bank_code,
|
||||
quantityHours: hours,
|
||||
company_code: emp.company_code,
|
||||
external_payroll_id: emp.external_payroll_id,
|
||||
full_name: `${emp.user.first_name} ${emp.user.last_name}`,
|
||||
bank_code: shift.bank_code.bank_code,
|
||||
quantity_hours: hours,
|
||||
amount: undefined,
|
||||
weekNumber,
|
||||
payDate: this.formatDate(endDate),
|
||||
holidayDate: undefined,
|
||||
week_number,
|
||||
pay_date: this.formatDate(endDate),
|
||||
holiday_date: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
//Expenses Mapping
|
||||
for (const e of expenses) {
|
||||
const emp = e.timesheet.employee;
|
||||
const weekNumber = this.computeWeekNumber(startDate, e.date);
|
||||
const week_number = this.computeWeekNumber(startDate, e.date);
|
||||
|
||||
rows.push({
|
||||
companyCode: emp.company_code,
|
||||
externalPayrollId: emp.external_payroll_id,
|
||||
fullName: `${emp.user.first_name} ${emp.user.last_name}`,
|
||||
bankCode: e.bank_code.bank_code,
|
||||
quantityHours: undefined,
|
||||
company_code: emp.company_code,
|
||||
external_payroll_id: emp.external_payroll_id,
|
||||
full_name: `${emp.user.first_name} ${emp.user.last_name}`,
|
||||
bank_code: e.bank_code.bank_code,
|
||||
quantity_hours: undefined,
|
||||
amount: Number(e.amount),
|
||||
weekNumber,
|
||||
payDate: this.formatDate(endDate),
|
||||
holidayDate: undefined,
|
||||
week_number,
|
||||
pay_date: this.formatDate(endDate),
|
||||
holiday_date: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -115,56 +115,56 @@ export class CsvExportService {
|
|||
const start = l.start_date_time;
|
||||
const end = l.end_date_time ?? start;
|
||||
|
||||
const weekNumber = this.computeWeekNumber(startDate, start);
|
||||
const week_number = this.computeWeekNumber(startDate, start);
|
||||
const hours = this.computeHours(start, end);
|
||||
|
||||
rows.push({
|
||||
companyCode: emp.company_code,
|
||||
externalPayrollId: emp.external_payroll_id,
|
||||
fullName: `${emp.user.first_name} ${emp.user.last_name}`,
|
||||
bankCode: l.bank_code.bank_code,
|
||||
quantityHours: hours,
|
||||
company_code: emp.company_code,
|
||||
external_payroll_id: emp.external_payroll_id,
|
||||
full_name: `${emp.user.first_name} ${emp.user.last_name}`,
|
||||
bank_code: l.bank_code.bank_code,
|
||||
quantity_hours: hours,
|
||||
amount: undefined,
|
||||
weekNumber,
|
||||
payDate: this.formatDate(endDate),
|
||||
holidayDate: undefined,
|
||||
week_number,
|
||||
pay_date: this.formatDate(endDate),
|
||||
holiday_date: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
//Final Mapping and sorts
|
||||
return rows.sort((a,b) => {
|
||||
if(a.externalPayrollId !== b.externalPayrollId) {
|
||||
return a.externalPayrollId - b.externalPayrollId;
|
||||
if(a.external_payroll_id !== b.external_payroll_id) {
|
||||
return a.external_payroll_id - b.external_payroll_id;
|
||||
}
|
||||
if(a.bankCode !== b.bankCode) {
|
||||
return a.bankCode.localeCompare(b.bankCode);
|
||||
if(a.bank_code !== b.bank_code) {
|
||||
return a.bank_code.localeCompare(b.bank_code);
|
||||
}
|
||||
return a.weekNumber - b.weekNumber;
|
||||
return a.week_number - b.week_number;
|
||||
});
|
||||
}
|
||||
|
||||
generateCsv(rows: CsvRow[]): Buffer {
|
||||
const header = [
|
||||
'companyCode',
|
||||
'externalPayrolId',
|
||||
'fullName',
|
||||
'bankCode',
|
||||
'quantityHours',
|
||||
'company_code',
|
||||
'external_payrol_id',
|
||||
'full_name',
|
||||
'bank_code',
|
||||
'quantity_hours',
|
||||
'amount',
|
||||
'weekNumber',
|
||||
'payDate',
|
||||
'holidayDate',
|
||||
'week_number',
|
||||
'pay_date',
|
||||
'holiday_date',
|
||||
].join(',') + '\n';
|
||||
|
||||
const body = rows.map(r => [
|
||||
r.companyCode,
|
||||
r.externalPayrollId,
|
||||
`${r.fullName.replace(/"/g, '""')}"`,
|
||||
r.bankCode,
|
||||
r.quantityHours?.toFixed(2) ?? '',
|
||||
r.weekNumber,
|
||||
r.payDate,
|
||||
r.holidayDate ?? '',
|
||||
r.company_code,
|
||||
r.external_payroll_id,
|
||||
`${r.full_name.replace(/"/g, '""')}"`,
|
||||
r.bank_code,
|
||||
r.quantity_hours?.toFixed(2) ?? '',
|
||||
r.week_number,
|
||||
r.pay_date,
|
||||
r.holiday_date ?? '',
|
||||
].join(',')).join('\n');
|
||||
|
||||
return Buffer.from('\uFEFF' + header + body, 'utf8');
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import { Controller, ForbiddenException, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Query } from "@nestjs/common";
|
||||
import { Controller, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Query } from "@nestjs/common";
|
||||
import { ApiNotFoundResponse, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { PayPeriodDto } from "../dtos/pay-period.dto";
|
||||
import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto";
|
||||
import { PayPeriodsQueryService } from "../services/pay-periods-query.service";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
import { Req } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { PayPeriodsCommandService } from "../services/pay-periods-command.service";
|
||||
import { PayPeriodBundleDto } from "../dtos/bundle-pay-period.dto";
|
||||
|
||||
|
|
@ -54,9 +52,9 @@ export class PayPeriodsController {
|
|||
@ApiNotFoundResponse({ description: "Pay period not found" })
|
||||
async findOneByYear(
|
||||
@Param("year", ParseIntPipe) year: number,
|
||||
@Param("periodNumber", ParseIntPipe) periodNumber: number,
|
||||
@Param("periodNumber", ParseIntPipe) period_no: number,
|
||||
) {
|
||||
return this.queryService.findOneByYearPeriod(year, periodNumber);
|
||||
return this.queryService.findOneByYearPeriod(year, period_no);
|
||||
}
|
||||
|
||||
@Patch("approval/:year/:periodNumber")
|
||||
|
|
@ -67,10 +65,10 @@ export class PayPeriodsController {
|
|||
@ApiResponse({ status: 200, description: "Pay period approved" })
|
||||
async approve(
|
||||
@Param("year", ParseIntPipe) year: number,
|
||||
@Param("periodNumber", ParseIntPipe) periodNumber: number,
|
||||
@Param("periodNumber", ParseIntPipe) period_no: number,
|
||||
) {
|
||||
await this.commandService.approvalPayPeriod(year, periodNumber);
|
||||
return { message: `Pay-period ${year}-${periodNumber} approved` };
|
||||
await this.commandService.approvalPayPeriod(year, period_no);
|
||||
return { message: `Pay-period ${year}-${period_no} approved` };
|
||||
}
|
||||
|
||||
@Get(':year/:periodNumber/:email')
|
||||
|
|
@ -83,12 +81,11 @@ export class PayPeriodsController {
|
|||
@ApiNotFoundResponse({ description: 'Pay period not found' })
|
||||
async getCrewOverview(
|
||||
@Param('year', ParseIntPipe) year: number,
|
||||
@Param('periodNumber', ParseIntPipe) periodNumber: number,
|
||||
@Param('periodNumber', ParseIntPipe) period_no: number,
|
||||
@Param('email') email: string,
|
||||
@Query('includeSubtree', new ParseBoolPipe({ optional: true })) includeSubtree = false,
|
||||
@Req() req: Request,
|
||||
@Query('includeSubtree', new ParseBoolPipe({ optional: true })) include_subtree = false,
|
||||
): Promise<PayPeriodOverviewDto> {
|
||||
return this.queryService.getCrewOverview(year, periodNumber, email, includeSubtree);
|
||||
return this.queryService.getCrewOverview(year, period_no, email, include_subtree);
|
||||
}
|
||||
|
||||
@Get('overview/:year/:periodNumber')
|
||||
|
|
@ -99,8 +96,8 @@ export class PayPeriodsController {
|
|||
@ApiNotFoundResponse({ description: 'Pay period not found' })
|
||||
async getOverviewByYear(
|
||||
@Param('year', ParseIntPipe) year: number,
|
||||
@Param('periodNumber', ParseIntPipe) periodNumber: number,
|
||||
@Param('periodNumber', ParseIntPipe) period_no: number,
|
||||
): Promise<PayPeriodOverviewDto> {
|
||||
return this.queryService.getOverviewByYearPeriod(year, periodNumber);
|
||||
return this.queryService.getOverviewByYearPeriod(year, period_no);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import { EmployeePeriodOverviewDto } from './overview-employee-period.dto';
|
|||
|
||||
export class PayPeriodOverviewDto {
|
||||
@ApiProperty({ example: 1, description: 'Period number (1–26)' })
|
||||
period_number: number;
|
||||
pay_period_no: number;
|
||||
|
||||
@ApiProperty({ example: 2023, description: 'Calendar year of the period' })
|
||||
year: number;
|
||||
pay_year: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: '2023-12-17',
|
||||
|
|
@ -14,7 +14,7 @@ export class PayPeriodOverviewDto {
|
|||
format: 'date',
|
||||
description: "Period start date (YYYY-MM-DD)",
|
||||
})
|
||||
start_date: string;
|
||||
period_start: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '2023-12-30',
|
||||
|
|
@ -22,7 +22,15 @@ export class PayPeriodOverviewDto {
|
|||
format: 'date',
|
||||
description: "Period end date (YYYY-MM-DD)",
|
||||
})
|
||||
end_date: string;
|
||||
period_end: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '2023-12-30',
|
||||
type: String,
|
||||
format: 'date',
|
||||
description: "Period pay day(YYYY-MM-DD)",
|
||||
})
|
||||
payday: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '2023-12-17 → 2023-12-30',
|
||||
|
|
|
|||
|
|
@ -3,18 +3,22 @@ import { ApiProperty } from "@nestjs/swagger";
|
|||
export class PayPeriodDto {
|
||||
@ApiProperty({ example: 1,
|
||||
description: 'numéro cyclique de la période entre 1 et 26' })
|
||||
period_number: number;
|
||||
pay_period_no: number;
|
||||
|
||||
@ApiProperty({ example: '2023-12-17',
|
||||
type: String, format: 'date' })
|
||||
start_date: String;
|
||||
period_start: string;
|
||||
|
||||
@ApiProperty({ example: '2023-12-30',
|
||||
type: String, format: 'date' })
|
||||
end_date: String;
|
||||
period_end: string;
|
||||
|
||||
@ApiProperty({ example: '2023-01-04',
|
||||
type: String, format: 'date' })
|
||||
payday: string;
|
||||
|
||||
@ApiProperty({ example: 2023 })
|
||||
year: number;
|
||||
pay_year: number;
|
||||
|
||||
@ApiProperty({ example: '2023-12-17 → 2023-12-30' })
|
||||
label: string;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import { PayPeriods } from "@prisma/client";
|
||||
import { PayPeriodDto } from "../dtos/pay-period.dto";
|
||||
import { payYearOfDate } from "../utils/pay-year.util";
|
||||
|
||||
const toDateString = (d: Date) => d.toISOString().slice(0, 10); // "YYYY-MM-DD"
|
||||
const toDateString = (date: Date) => date.toISOString().slice(0, 10); // "YYYY-MM-DD"
|
||||
|
||||
export function mapPayPeriodToDto(row: PayPeriods): PayPeriodDto {
|
||||
const s = toDateString(row.start_date);
|
||||
const e = toDateString(row.end_date);
|
||||
const start = toDateString(row.period_start);
|
||||
const end = toDateString(row.period_end);
|
||||
const pay = toDateString(row.payday);
|
||||
return {
|
||||
period_number: row.period_number,
|
||||
start_date: toDateString(row.start_date),
|
||||
end_date: toDateString(row.end_date),
|
||||
year: payYearOfDate(s),
|
||||
label: `${s} => ${e}`,
|
||||
pay_period_no: row.pay_period_no,
|
||||
period_start: toDateString(row.period_start),
|
||||
period_end: toDateString(row.period_end),
|
||||
payday:pay,
|
||||
pay_year: new Date(pay).getFullYear(),
|
||||
label: `${start} => ${end}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,26 +6,26 @@ import { PrismaService } from "src/prisma/prisma.service";
|
|||
export class PayPeriodsCommandService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly timesheetsApproval: TimesheetsCommandService,
|
||||
private readonly timesheets_approval: TimesheetsCommandService,
|
||||
) {}
|
||||
|
||||
async approvalPayPeriod(year: number , periodNumber: number): Promise<void> {
|
||||
async approvalPayPeriod(pay_year: number , period_no: number): Promise<void> {
|
||||
const period = await this.prisma.payPeriods.findFirst({
|
||||
where: { year, period_number: periodNumber},
|
||||
where: { pay_year, pay_period_no: period_no},
|
||||
});
|
||||
if (!period) throw new NotFoundException(`PayPeriod #${year}-${periodNumber} not found`);
|
||||
if (!period) throw new NotFoundException(`PayPeriod #${pay_year}-${period_no} not found`);
|
||||
|
||||
//fetches timesheet of selected period if the timesheet has atleast 1 shift or 1 expense
|
||||
const timesheetList = await this.prisma.timesheets.findMany({
|
||||
const timesheet_ist = await this.prisma.timesheets.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ shift: {some: { date: { gte: period.start_date,
|
||||
lte: period.end_date,
|
||||
{ shift: {some: { date: { gte: period.period_start,
|
||||
lte: period.period_end,
|
||||
},
|
||||
}},
|
||||
},
|
||||
{ expense: { some: { date: { gte: period.start_date,
|
||||
lte: period.end_date,
|
||||
{ expense: { some: { date: { gte: period.period_start,
|
||||
lte: period.period_end,
|
||||
},
|
||||
}},
|
||||
},
|
||||
|
|
@ -36,8 +36,8 @@ export class PayPeriodsCommandService {
|
|||
|
||||
//approval of both timesheet (cascading to the approval of related shifts and expenses)
|
||||
await this.prisma.$transaction(async (transaction)=> {
|
||||
for(const {id} of timesheetList) {
|
||||
await this.timesheetsApproval.updateApprovalWithTx(transaction,id, true);
|
||||
for(const {id} of timesheet_ist) {
|
||||
await this.timesheets_approval.updateApprovalWithTx(transaction,id, true);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,53 +9,65 @@ import { mapPayPeriodToDto } from "../mappers/pay-periods.mapper";
|
|||
|
||||
@Injectable()
|
||||
export class PayPeriodsQueryService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
constructor( private readonly prisma: PrismaService) {}
|
||||
|
||||
async getOverview(periodNumber: number): Promise<PayPeriodOverviewDto> {
|
||||
const period = await this.prisma.payPeriods.findFirst({
|
||||
where: { period_number: periodNumber },
|
||||
orderBy: { year: "desc" },
|
||||
});
|
||||
if (!period) throw new NotFoundException(`Period #${periodNumber} not found`);
|
||||
return this.buildOverview(period);
|
||||
}
|
||||
|
||||
async getOverviewByYearPeriod(year: number, periodNumber: number): Promise<PayPeriodOverviewDto> {
|
||||
const period = computePeriod(year, periodNumber);
|
||||
async getOverviewByYearPeriod(pay_year: number, period_no: number): Promise<PayPeriodOverviewDto> {
|
||||
const period = computePeriod(pay_year, period_no);
|
||||
return this.buildOverview({
|
||||
start_date: period.start_date,
|
||||
end_date : period.end_date,
|
||||
period_number: period.period_number,
|
||||
year: period.year,
|
||||
label:period.label,
|
||||
period_start: period.period_start,
|
||||
period_end : period.period_end,
|
||||
period_no : period.period_no,
|
||||
pay_year : period.pay_year,
|
||||
payday : period.payday,
|
||||
label :period.label,
|
||||
} as any);
|
||||
}
|
||||
|
||||
async getOverview(pay_period_no: number): Promise<PayPeriodOverviewDto> {
|
||||
const period = await this.prisma.payPeriods.findFirst({
|
||||
where: { pay_period_no },
|
||||
orderBy: { pay_year: "desc" },
|
||||
});
|
||||
if (!period) throw new NotFoundException(`Period #${pay_period_no} not found`);
|
||||
|
||||
return this.buildOverview({
|
||||
period_start: period.period_start,
|
||||
period_end : period.period_end,
|
||||
payday : period.payday,
|
||||
period_no : period.pay_period_no,
|
||||
pay_year : period.pay_year,
|
||||
label : period.label,
|
||||
});
|
||||
}
|
||||
|
||||
private async buildOverview(
|
||||
period: { start_date: string | Date; end_date: string | Date; period_number: number; year: number; label: string; },
|
||||
options?: { filteredEmployeeIds?: number[]; seedNames?: Map<number, string> },
|
||||
period: { period_start: string | Date; period_end: string | Date; payday: string | Date;
|
||||
period_no: number; pay_year: number; label: string; },
|
||||
options?: { filtered_employee_ids?: number[]; seed_names?: Map<number, string> },
|
||||
): Promise<PayPeriodOverviewDto> {
|
||||
const toDateString = (d: Date) => d.toISOString().slice(0, 10);
|
||||
const toMoney = (v: any) => (typeof v === "object" && "toNumber" in v ? v.toNumber() : (v as number));
|
||||
|
||||
const start = period.start_date instanceof Date
|
||||
? period.start_date
|
||||
: new Date(`${period.start_date}T00:00:00.000Z`);
|
||||
const start = period.period_start instanceof Date
|
||||
? period.period_start
|
||||
: new Date(`${period.period_start}T00:00:00.000Z`);
|
||||
|
||||
const end = period.end_date instanceof Date
|
||||
? period.end_date
|
||||
: new Date(`${period.end_date}T00:00:00.000Z`);
|
||||
const end = period.period_end instanceof Date
|
||||
? period.period_end
|
||||
: new Date(`${period.period_end}T00:00:00.000Z`);
|
||||
|
||||
const payd = period.payday instanceof Date
|
||||
? period.payday
|
||||
: new Date (`${period.payday}T00:00:00.000Z`);
|
||||
|
||||
//restrictEmployeeIds = filter for shifts and expenses by employees
|
||||
const whereEmployee = options?.filteredEmployeeIds?.length ? { employee_id: { in: options.filteredEmployeeIds } }: {};
|
||||
const where_employee = options?.filtered_employee_ids?.length ? { employee_id: { in: options.filtered_employee_ids } }: {};
|
||||
|
||||
// SHIFTS (filtered by crew)
|
||||
const shifts = await this.prisma.shifts.findMany({
|
||||
where: {
|
||||
date: { gte: start, lte: end },
|
||||
timesheet: whereEmployee,
|
||||
timesheet: where_employee,
|
||||
},
|
||||
select: {
|
||||
start_time: true,
|
||||
|
|
@ -79,7 +91,7 @@ export class PayPeriodsQueryService {
|
|||
const expenses = await this.prisma.expenses.findMany({
|
||||
where: {
|
||||
date: { gte: start, lte: end },
|
||||
timesheet: whereEmployee,
|
||||
timesheet: where_employee,
|
||||
},
|
||||
select: {
|
||||
amount: true,
|
||||
|
|
@ -97,12 +109,12 @@ export class PayPeriodsQueryService {
|
|||
},
|
||||
});
|
||||
|
||||
const byEmployee = new Map<number, EmployeePeriodOverviewDto>();
|
||||
const by_employee = new Map<number, EmployeePeriodOverviewDto>();
|
||||
|
||||
// seed for employee without data
|
||||
if (options?.seedNames) {
|
||||
for (const [id, name] of options.seedNames.entries()) {
|
||||
byEmployee.set(id, {
|
||||
if (options?.seed_names) {
|
||||
for (const [id, name] of options.seed_names.entries()) {
|
||||
by_employee.set(id, {
|
||||
employee_id: id,
|
||||
employee_name: name,
|
||||
regular_hours: 0,
|
||||
|
|
@ -117,8 +129,8 @@ export class PayPeriodsQueryService {
|
|||
}
|
||||
|
||||
const ensure = (id: number, name: string) => {
|
||||
if (!byEmployee.has(id)) {
|
||||
byEmployee.set(id, {
|
||||
if (!by_employee.has(id)) {
|
||||
by_employee.set(id, {
|
||||
employee_id: id,
|
||||
employee_name: name,
|
||||
regular_hours: 0,
|
||||
|
|
@ -130,24 +142,24 @@ export class PayPeriodsQueryService {
|
|||
is_approved: true,
|
||||
});
|
||||
}
|
||||
return byEmployee.get(id)!;
|
||||
return by_employee.get(id)!;
|
||||
};
|
||||
|
||||
for (const shift of shifts) {
|
||||
const employee = shift.timesheet.employee;
|
||||
const name = `${employee.user.first_name} ${employee.user.last_name}`.trim();
|
||||
const rec = ensure(employee.id, name);
|
||||
const record = ensure(employee.id, name);
|
||||
|
||||
const hours = computeHours(shift.start_time, shift.end_time);
|
||||
const categorie = (shift.bank_code?.categorie || "REGULAR").toUpperCase();
|
||||
switch (categorie) {
|
||||
case "EVENING": rec.evening_hours += hours; break;
|
||||
case "EVENING": record.evening_hours += hours; break;
|
||||
case "EMERGENCY":
|
||||
case "URGENT": rec.emergency_hours += hours; break;
|
||||
case "OVERTIME": rec.overtime_hours += hours; break;
|
||||
default: rec.regular_hours += hours; break;
|
||||
case "URGENT": record.emergency_hours += hours; break;
|
||||
case "OVERTIME": record.overtime_hours += hours; break;
|
||||
default: record.regular_hours += hours; break;
|
||||
}
|
||||
rec.is_approved = rec.is_approved && shift.timesheet.is_approved;
|
||||
record.is_approved = record.is_approved && shift.timesheet.is_approved;
|
||||
}
|
||||
|
||||
for (const expense of expenses) {
|
||||
|
|
@ -166,25 +178,27 @@ export class PayPeriodsQueryService {
|
|||
record.is_approved = record.is_approved && expense.timesheet.is_approved;
|
||||
}
|
||||
|
||||
const employees_overview = Array.from(byEmployee.values()).sort((a, b) =>
|
||||
const employees_overview = Array.from(by_employee.values()).sort((a, b) =>
|
||||
a.employee_name.localeCompare(b.employee_name, "fr", { sensitivity: "base" }),
|
||||
);
|
||||
|
||||
return {
|
||||
period_number: period.period_number,
|
||||
year: period.year,
|
||||
start_date: toDateString(start),
|
||||
end_date: toDateString(end),
|
||||
pay_period_no: period.period_no,
|
||||
pay_year: period.pay_year,
|
||||
payday: toDateString(payd),
|
||||
period_start: toDateString(start),
|
||||
period_end: toDateString(end),
|
||||
label: period.label,
|
||||
employees_overview,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async getCrewOverview(year: number, periodNumber: number, email: string, includeSubtree: boolean): Promise<PayPeriodOverviewDto> {
|
||||
async getCrewOverview(pay_year: number, period_no: number, email: string, include_subtree: boolean):
|
||||
Promise<PayPeriodOverviewDto> {
|
||||
// 1) Search for the period
|
||||
const period = await this.prisma.payPeriods.findFirst({ where: { year, period_number: periodNumber } });
|
||||
if (!period) throw new NotFoundException(`Pay period ${year}-${periodNumber} not found`);
|
||||
const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no: period_no } });
|
||||
if (!period) throw new NotFoundException(`Pay period ${pay_year}-${period_no} not found`);
|
||||
|
||||
// 2) fetch supervisor
|
||||
const supervisor = await this.prisma.employees.findFirst({
|
||||
|
|
@ -199,34 +213,42 @@ export class PayPeriodsQueryService {
|
|||
if (!supervisor.is_supervisor) throw new ForbiddenException('Employee is not a supervisor');
|
||||
|
||||
// 3)fetchs crew members
|
||||
const crew = await this.resolveCrew(supervisor.id, includeSubtree); // [{ id, first_name, last_name }]
|
||||
const crewIds = crew.map(c => c.id);
|
||||
const crew = await this.resolveCrew(supervisor.id, include_subtree); // [{ id, first_name, last_name }]
|
||||
const crew_ids = crew.map(c => c.id);
|
||||
// seed names map for employee without data
|
||||
const seedNames = new Map<number, string>(crew.map(c => [c.id, `${c.first_name} ${c.last_name}`.trim()]));
|
||||
const seed_names = new Map<number, string>(crew.map(crew => [crew.id, `${crew.first_name} ${crew.last_name}`.trim()]));
|
||||
|
||||
// 4) overview build
|
||||
return this.buildOverview(period, { filteredEmployeeIds: crewIds, seedNames });
|
||||
return this.buildOverview({
|
||||
period_no : period.pay_period_no,
|
||||
period_start: period.period_start,
|
||||
period_end : period.period_end,
|
||||
payday : period.payday,
|
||||
pay_year : period.pay_year,
|
||||
label : period.label,
|
||||
}, { filtered_employee_ids: crew_ids, seed_names });
|
||||
}
|
||||
|
||||
private async resolveCrew(supervisorId: number, includeSubtree: boolean): Promise<Array<{ id: number; first_name: string; last_name: string }>> {
|
||||
private async resolveCrew(supervisor_id: number, include_subtree: boolean):
|
||||
Promise<Array<{ id: number; first_name: string; last_name: string }>> {
|
||||
const result: Array<{ id: number; first_name: string; last_name: string }> = [];
|
||||
|
||||
let frontier = await this.prisma.employees.findMany({
|
||||
where: { supervisor_id: supervisorId },
|
||||
where: { supervisor_id: supervisor_id },
|
||||
select: { id: true, user: { select: { first_name: true, last_name: true } } },
|
||||
});
|
||||
result.push(...frontier.map(e => ({ id: e.id, first_name: e.user.first_name, last_name: e.user.last_name })));
|
||||
result.push(...frontier.map(emp => ({ id: emp.id, first_name: emp.user.first_name, last_name: emp.user.last_name })));
|
||||
|
||||
if (!includeSubtree) return result;
|
||||
if (!include_subtree) return result;
|
||||
|
||||
while (frontier.length) {
|
||||
const parentIds = frontier.map(e => e.id);
|
||||
const parent_ids = frontier.map(emp => emp.id);
|
||||
const next = await this.prisma.employees.findMany({
|
||||
where: { supervisor_id: { in: parentIds } },
|
||||
where: { supervisor_id: { in: parent_ids } },
|
||||
select: { id: true, user: { select: { first_name: true, last_name: true } } },
|
||||
});
|
||||
if (next.length === 0) break;
|
||||
result.push(...next.map(e => ({ id: e.id, first_name: e.user.first_name, last_name: e.user.last_name })));
|
||||
result.push(...next.map(emp => ({ id: emp.id, first_name: emp.user.first_name, last_name: emp.user.last_name })));
|
||||
frontier = next;
|
||||
}
|
||||
|
||||
|
|
@ -237,54 +259,69 @@ private async resolveCrew(supervisorId: number, includeSubtree: boolean): Promis
|
|||
async findAll(): Promise<PayPeriodDto[]> {
|
||||
const currentPayYear = payYearOfDate(new Date());
|
||||
return listPayYear(currentPayYear).map(period =>({
|
||||
period_number: period.period_number,
|
||||
year: period.year,
|
||||
start_date: period.start_date,
|
||||
end_date: period.end_date,
|
||||
pay_period_no: period.period_no,
|
||||
pay_year: period.pay_year,
|
||||
payday: period.payday,
|
||||
period_start: period.period_start,
|
||||
period_end: period.period_end,
|
||||
label: period.label,
|
||||
}));
|
||||
}
|
||||
|
||||
async findOne(periodNumber: number): Promise<PayPeriodDto> {
|
||||
async findOne(period_no: number): Promise<PayPeriodDto> {
|
||||
const row = await this.prisma.payPeriods.findFirst({
|
||||
where: { period_number: periodNumber },
|
||||
orderBy: { year: "desc" },
|
||||
where: { pay_period_no: period_no },
|
||||
orderBy: { pay_year: "desc" },
|
||||
});
|
||||
if (!row) throw new NotFoundException(`Pay period #${periodNumber} not found`);
|
||||
if (!row) throw new NotFoundException(`Pay period #${period_no} not found`);
|
||||
return mapPayPeriodToDto(row);
|
||||
}
|
||||
|
||||
async findOneByYearPeriod(year: number, periodNumber: number): Promise<PayPeriodDto> {
|
||||
async findOneByYearPeriod(pay_year: number, period_no: number): Promise<PayPeriodDto> {
|
||||
const row = await this.prisma.payPeriods.findFirst({
|
||||
where: { year, period_number: periodNumber },
|
||||
where: { pay_year, pay_period_no: period_no },
|
||||
});
|
||||
if(row) return mapPayPeriodToDto(row);
|
||||
|
||||
// fallback for outside of view periods
|
||||
const p = computePeriod(year, periodNumber);
|
||||
return {period_number: p.period_number, year: p.year, start_date: p.start_date, end_date: p.end_date, label: p.label}
|
||||
const period = computePeriod(pay_year, period_no);
|
||||
return {
|
||||
pay_period_no: period.period_no,
|
||||
pay_year: period.pay_year,
|
||||
period_start: period.period_start,
|
||||
payday: period.payday,
|
||||
period_end: period.period_end,
|
||||
label: period.label
|
||||
}
|
||||
}
|
||||
|
||||
//function to cherry pick a Date to find a period
|
||||
async findByDate(date: string): Promise<PayPeriodDto> {
|
||||
const dt = new Date(date);
|
||||
const row = await this.prisma.payPeriods.findFirst({
|
||||
where: { start_date: { lte: dt }, end_date: { gte: dt } },
|
||||
where: { period_start: { lte: dt }, period_end: { gte: dt } },
|
||||
});
|
||||
if(row) return mapPayPeriodToDto(row);
|
||||
|
||||
//fallback for outwside view periods
|
||||
const payYear = payYearOfDate(date);
|
||||
const periods = listPayYear(payYear);
|
||||
const hit = periods.find(p => date >= p.start_date && date <= p.end_date);
|
||||
const pay_year = payYearOfDate(date);
|
||||
const periods = listPayYear(pay_year);
|
||||
const hit = periods.find(period => date >= period.period_start && date <= period.period_end);
|
||||
if(!hit) throw new NotFoundException(`No period found for ${date}`);
|
||||
|
||||
return { period_number: hit.period_number, year: hit.year, start_date: hit.start_date, end_date:hit.end_date, label: hit.label}
|
||||
return {
|
||||
pay_period_no: hit.period_no,
|
||||
pay_year : hit.pay_year,
|
||||
period_start : hit.period_start,
|
||||
period_end : hit.period_end,
|
||||
payday : hit.payday,
|
||||
label : hit.label
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async findCurrent(date?: string): Promise<PayPeriodDto> {
|
||||
const isoDay = date ?? new Date().toISOString().slice(0,10);
|
||||
return this.findByDate(isoDay);
|
||||
const iso_day = date ?? new Date().toISOString().slice(0,10);
|
||||
return this.findByDate(iso_day);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ const toUTCDate = (iso: string | Date) => {
|
|||
};
|
||||
export const toDateString = (d: Date) => d.toISOString().slice(0, 10);
|
||||
|
||||
const ANCHOR = toUTCDate(ANCHOR_ISO);
|
||||
|
||||
export function payYearOfDate(date: string | Date, anchorISO = ANCHOR_ISO): number {
|
||||
const ANCHOR = toUTCDate(anchorISO);
|
||||
const d = toUTCDate(date);
|
||||
|
|
@ -19,23 +17,25 @@ export function payYearOfDate(date: string | Date, anchorISO = ANCHOR_ISO): numb
|
|||
return ANCHOR.getUTCFullYear() + 1 + cycles;
|
||||
}
|
||||
//compute labels for periods
|
||||
export function computePeriod(payYear: number, periodNumber: number, anchorISO = ANCHOR_ISO) {
|
||||
export function computePeriod(pay_year: number, period_no: number, anchorISO = ANCHOR_ISO) {
|
||||
const ANCHOR = toUTCDate(anchorISO);
|
||||
const cycles = payYear - (ANCHOR.getUTCFullYear() + 1);
|
||||
const offsetPeriods = cycles * PERIODS_PER_YEAR + (periodNumber - 1);
|
||||
const cycles = pay_year - (ANCHOR.getUTCFullYear() + 1);
|
||||
const offsetPeriods = cycles * PERIODS_PER_YEAR + (period_no - 1);
|
||||
const start = new Date(+ANCHOR + offsetPeriods * PERIOD_DAYS * MS_PER_DAY);
|
||||
const end = new Date(+start + (PERIOD_DAYS - 1) * MS_PER_DAY);
|
||||
const pay = new Date(end.getTime() + 6 * MS_PER_DAY);
|
||||
return {
|
||||
period_number: periodNumber,
|
||||
year: payYear,
|
||||
start_date: toDateString(start),
|
||||
end_date: toDateString(end),
|
||||
period_no: period_no,
|
||||
pay_year: pay_year,
|
||||
payday: toDateString(pay),
|
||||
period_start: toDateString(start),
|
||||
period_end: toDateString(end),
|
||||
label: `${toDateString(start)} → ${toDateString(end)}`,
|
||||
start, end,
|
||||
};
|
||||
}
|
||||
|
||||
//list of all 26 periods for a full year
|
||||
export function listPayYear(payYear: number, anchorISO = ANCHOR_ISO) {
|
||||
return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(payYear, i + 1, anchorISO));
|
||||
export function listPayYear(pay_year: number, anchorISO = ANCHOR_ISO) {
|
||||
return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(pay_year, i + 1, anchorISO));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,16 +117,16 @@ export class ShiftsQueryService {
|
|||
async getSummary(period_id: number): Promise<OverviewRow[]> {
|
||||
//fetch pay-period to display
|
||||
const period = await this.prisma.payPeriods.findFirst({
|
||||
where: { period_number: period_id },
|
||||
where: { pay_period_no: period_id },
|
||||
});
|
||||
if(!period) {
|
||||
throw new NotFoundException(`pay-period ${period_id} not found`);
|
||||
}
|
||||
const { start_date, end_date } = period;
|
||||
const { period_start, period_end } = period;
|
||||
|
||||
//prepare shifts and expenses for display
|
||||
const shifts = await this.prisma.shifts.findMany({
|
||||
where: { date: { gte: start_date, lte: end_date } },
|
||||
where: { date: { gte: period_start, lte: period_end } },
|
||||
include: {
|
||||
bank_code: true,
|
||||
timesheet: { include: {
|
||||
|
|
@ -139,7 +139,7 @@ export class ShiftsQueryService {
|
|||
});
|
||||
|
||||
const expenses = await this.prisma.expenses.findMany({
|
||||
where: { date: { gte: start_date, lte: end_date } },
|
||||
where: { date: { gte: period_start, lte: period_end } },
|
||||
include: {
|
||||
bank_code: true,
|
||||
timesheet: { include: { employee: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user