fix(pay-periods): added fallback for archive purposes. minor fix for findAll
This commit is contained in:
parent
242e3179f4
commit
a99f39bbf6
|
|
@ -1864,46 +1864,68 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/pay-periods/{year}/{periodNumber}/overview": {
|
"/pay-periods/bundle/current-and-all": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "PayPeriodsController_getOverviewByYear",
|
"operationId": "PayPeriodsController_getCurrentAndAll",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "year",
|
"name": "date",
|
||||||
"required": true,
|
"required": false,
|
||||||
"in": "path",
|
"in": "query",
|
||||||
|
"description": "Override for resolving the current period",
|
||||||
"schema": {
|
"schema": {
|
||||||
"example": 2024,
|
"example": "2025-08-11",
|
||||||
"type": "number"
|
"type": "string"
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "periodNumber",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"description": "1..26",
|
|
||||||
"schema": {
|
|
||||||
"example": 1,
|
|
||||||
"type": "number"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Pay period overview found",
|
"description": "Find current and all pay periods",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/PayPeriodOverviewDto"
|
"$ref": "#/components/schemas/PayPeriodBundleDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": "Return current pay period and the full list",
|
||||||
|
"tags": [
|
||||||
|
"pay-periods"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/pay-periods/date/{date}": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "PayPeriodsController_findByDate",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "date",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Pay period found for the selected date",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PayPeriodDto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "Pay period not found"
|
"description": "Pay period not found for the selected date"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"summary": "Detailed view of a pay period by year + number",
|
"summary": "Resolve a period by a date within it",
|
||||||
"tags": [
|
"tags": [
|
||||||
"pay-periods"
|
"pay-periods"
|
||||||
]
|
]
|
||||||
|
|
@ -1954,40 +1976,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/pay-periods/date/{date}": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "PayPeriodsController_findByDate",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "date",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Pay period found for the selected date",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/PayPeriodDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Pay period not found for the selected date"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"summary": "Resolve a period by a date within it",
|
|
||||||
"tags": [
|
|
||||||
"pay-periods"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/pay-periods/{year}/{periodNumber}/approval": {
|
"/pay-periods/{year}/{periodNumber}/approval": {
|
||||||
"patch": {
|
"patch": {
|
||||||
"operationId": "PayPeriodsController_approve",
|
"operationId": "PayPeriodsController_approve",
|
||||||
|
|
@ -2077,6 +2065,51 @@
|
||||||
"pay-periods"
|
"pay-periods"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/pay-periods/{year}/{periodNumber}/overview": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "PayPeriodsController_getOverviewByYear",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "year",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"example": 2024,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "periodNumber",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"description": "1..26",
|
||||||
|
"schema": {
|
||||||
|
"example": 1,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Pay period overview found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PayPeriodOverviewDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Pay period not found"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": "Detailed view of a pay period by year + number",
|
||||||
|
"tags": [
|
||||||
|
"pay-periods"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
|
|
@ -2953,6 +2986,30 @@
|
||||||
"label"
|
"label"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"PayPeriodBundleDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"current": {
|
||||||
|
"description": "Current pay period (resolved from date)",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/PayPeriodDto"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"periods": {
|
||||||
|
"description": "All pay periods",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/PayPeriodDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"current",
|
||||||
|
"periods"
|
||||||
|
]
|
||||||
|
},
|
||||||
"EmployeePeriodOverviewDto": {
|
"EmployeePeriodOverviewDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -29,19 +29,25 @@ export class PayPeriodsController {
|
||||||
return this.payPeriodsService.findAll();
|
return this.payPeriodsService.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':year/:periodNumber/overview')
|
@Get('bundle/current-and-all')
|
||||||
@ApiOperation({ summary: 'Detailed view of a pay period by year + number' })
|
@ApiOperation({summary: 'Return current pay period and the full list'})
|
||||||
@ApiParam({ name: 'year', type: Number, example: 2024 })
|
@ApiQuery({name: 'date', required:false, example: '2025-08-11', description:'Override for resolving the current period'})
|
||||||
@ApiParam({ name: 'periodNumber', type: Number, example: 1, description: '1..26' })
|
@ApiResponse({status: 200, description:'Find current and all pay periods', type: PayPeriodBundleDto})
|
||||||
@ApiResponse({ status: 200, description: 'Pay period overview found', type: PayPeriodOverviewDto })
|
async getCurrentAndAll(@Query('date') date?: string): Promise<PayPeriodBundleDto> {
|
||||||
@ApiNotFoundResponse({ description: 'Pay period not found' })
|
const [current, periods] = await Promise.all([
|
||||||
async getOverviewByYear(
|
this.payPeriodsService.findCurrent(date),
|
||||||
@Param('year', ParseIntPipe) year: number,
|
this.payPeriodsService.findAll(),
|
||||||
@Param('periodNumber', ParseIntPipe) periodNumber: number,
|
]);
|
||||||
): Promise<PayPeriodOverviewDto> {
|
return { current, periods };
|
||||||
return this.queryService.getOverviewByYearPeriod(year, periodNumber);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get("date/:date")
|
||||||
|
@ApiOperation({ summary: "Resolve a period by a date within it" })
|
||||||
|
@ApiResponse({ status: 200, description: "Pay period found for the selected date", type: PayPeriodDto })
|
||||||
|
@ApiNotFoundResponse({ description: "Pay period not found for the selected date" })
|
||||||
|
async findByDate(@Param("date") date: string) {
|
||||||
|
return this.payPeriodsService.findByDate(date);
|
||||||
|
}
|
||||||
|
|
||||||
@Get(":year/:periodNumber")
|
@Get(":year/:periodNumber")
|
||||||
@ApiOperation({ summary: "Find pay period by year and period number" })
|
@ApiOperation({ summary: "Find pay period by year and period number" })
|
||||||
|
|
@ -56,14 +62,6 @@ export class PayPeriodsController {
|
||||||
return this.payPeriodsService.findOneByYearPeriod(year, periodNumber);
|
return this.payPeriodsService.findOneByYearPeriod(year, periodNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("date/:date")
|
|
||||||
@ApiOperation({ summary: "Resolve a period by a date within it" })
|
|
||||||
@ApiResponse({ status: 200, description: "Pay period found for the selected date", type: PayPeriodDto })
|
|
||||||
@ApiNotFoundResponse({ description: "Pay period not found for the selected date" })
|
|
||||||
async findByDate(@Param("date") date: string) {
|
|
||||||
return this.payPeriodsService.findByDate(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Patch(":year/:periodNumber/approval")
|
@Patch(":year/:periodNumber/approval")
|
||||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||||
@ApiOperation({ summary: "Approve all timesheets with activity in the period" })
|
@ApiOperation({ summary: "Approve all timesheets with activity in the period" })
|
||||||
|
|
@ -102,17 +100,16 @@ export class PayPeriodsController {
|
||||||
return this.queryService.getCrewOverview(year, periodNumber, userId, includeSubtree);
|
return this.queryService.getCrewOverview(year, periodNumber, userId, includeSubtree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get(':year/:periodNumber/overview')
|
||||||
@Get('bundle/current-and-all')
|
@ApiOperation({ summary: 'Detailed view of a pay period by year + number' })
|
||||||
@ApiOperation({summary: 'Return current pay period and the full list'})
|
@ApiParam({ name: 'year', type: Number, example: 2024 })
|
||||||
@ApiQuery({name: 'date', required:false, example: '2025-01-01', description:'Override for resolving the current period'})
|
@ApiParam({ name: 'periodNumber', type: Number, example: 1, description: '1..26' })
|
||||||
@ApiResponse({status: 200, description:'Find current and all pay periods', type: PayPeriodBundleDto})
|
@ApiResponse({ status: 200, description: 'Pay period overview found', type: PayPeriodOverviewDto })
|
||||||
async getCurrentAndAll(@Query('date') date?: string): Promise<PayPeriodBundleDto> {
|
@ApiNotFoundResponse({ description: 'Pay period not found' })
|
||||||
const [current, periods] = await Promise.all([
|
async getOverviewByYear(
|
||||||
this.payPeriodsService.findCurrent(date),
|
@Param('year', ParseIntPipe) year: number,
|
||||||
this.payPeriodsService.findAll(),
|
@Param('periodNumber', ParseIntPipe) periodNumber: number,
|
||||||
]);
|
): Promise<PayPeriodOverviewDto> {
|
||||||
return { current, periods };
|
return this.queryService.getOverviewByYearPeriod(year, periodNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
import { PayPeriods } from "@prisma/client";
|
import { PayPeriods } from "@prisma/client";
|
||||||
import { PayPeriodDto } from "../dtos/pay-period.dto";
|
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 = (d: Date) => d.toISOString().slice(0, 10); // "YYYY-MM-DD"
|
||||||
|
|
||||||
export function mapPayPeriodToDto(row: PayPeriods): PayPeriodDto {
|
export function mapPayPeriodToDto(row: PayPeriods): PayPeriodDto {
|
||||||
|
const s = toDateString(row.start_date);
|
||||||
|
const e = toDateString(row.end_date);
|
||||||
return {
|
return {
|
||||||
period_number: row.period_number,
|
period_number: row.period_number,
|
||||||
start_date: toDateString(row.start_date),
|
start_date: toDateString(row.start_date),
|
||||||
end_date: toDateString(row.end_date),
|
end_date: toDateString(row.end_date),
|
||||||
year: row.year,
|
year: payYearOfDate(s),
|
||||||
label: row.label,
|
label: `${s} => ${e}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { computeHours } from "src/common/utils/date-utils";
|
import { computeHours } from "src/common/utils/date-utils";
|
||||||
import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto";
|
import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto";
|
||||||
import { EmployeePeriodOverviewDto } from "../dtos/overview-employee-period.dto";
|
import { EmployeePeriodOverviewDto } from "../dtos/overview-employee-period.dto";
|
||||||
|
import { computePeriod } from "../utils/pay-year.util";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PayPeriodsQueryService {
|
export class PayPeriodsQueryService {
|
||||||
|
|
@ -18,36 +19,51 @@ export class PayPeriodsQueryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOverviewByYearPeriod(year: number, periodNumber: number): Promise<PayPeriodOverviewDto> {
|
async getOverviewByYearPeriod(year: number, periodNumber: number): Promise<PayPeriodOverviewDto> {
|
||||||
const period = await this.prisma.payPeriods.findFirst({
|
const p = computePeriod(year, periodNumber);
|
||||||
where: { year, period_number: periodNumber },
|
return this.buildOverview({
|
||||||
});
|
start_date: p.start_date,
|
||||||
if (!period) throw new NotFoundException(`Period ${year}-${periodNumber} not found`);
|
end_date : p.end_date,
|
||||||
return this.buildOverview(period);
|
period_number: p.period_number,
|
||||||
|
year: p.year,
|
||||||
|
label:p.label,
|
||||||
|
} as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async buildOverview(
|
private async buildOverview(
|
||||||
period: { start_date: Date; end_date: Date; period_number: number; year: number; label: string; },
|
period: { start_date: string | Date; end_date: string | Date; period_number: number; year: number; label: string; },
|
||||||
opts?: { restrictEmployeeIds?: number[]; seedNames?: Map<number, string> },
|
opts?: { restrictEmployeeIds?: number[]; seedNames?: Map<number, string> },
|
||||||
): Promise<PayPeriodOverviewDto> {
|
): Promise<PayPeriodOverviewDto> {
|
||||||
const toDateString = (d: Date) => d.toISOString().slice(0, 10);
|
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 toMoney = (v: any) => (typeof v === "object" && "toNumber" in v ? v.toNumber() : (v as number));
|
||||||
const whereEmployee = opts?.restrictEmployeeIds?.length
|
|
||||||
? { employee_id: { in: opts.restrictEmployeeIds } }
|
const start = period.start_date instanceof Date
|
||||||
: {};
|
? period.start_date
|
||||||
|
: new Date(`${period.start_date}T00:00:00.000Z`);
|
||||||
|
|
||||||
|
const end = period.end_date instanceof Date
|
||||||
|
? period.end_date
|
||||||
|
: new Date(`${period.end_date}T00:00:00.000Z`);
|
||||||
|
|
||||||
|
const whereEmployee = opts?.restrictEmployeeIds?.length ? { employee_id: { in: opts.restrictEmployeeIds } }: {};
|
||||||
|
|
||||||
// SHIFTS (filtrés par crew si besoin)
|
// SHIFTS (filtrés par crew si besoin)
|
||||||
const shifts = await this.prisma.shifts.findMany({
|
const shifts = await this.prisma.shifts.findMany({
|
||||||
where: {
|
where: {
|
||||||
date: { gte: period.start_date, lte: period.end_date },
|
date: { gte: start, lte: end },
|
||||||
timesheet: whereEmployee,
|
timesheet: whereEmployee,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
start_time: true,
|
start_time: true,
|
||||||
end_time: true,
|
end_time: true,
|
||||||
timesheet: {
|
timesheet: { select: {
|
||||||
select: {
|
|
||||||
is_approved: true,
|
is_approved: true,
|
||||||
employee: { select: { id: true, user: { select: { first_name: true, last_name: true } } } },
|
employee: { select: {
|
||||||
|
id: true,
|
||||||
|
user: { select: {
|
||||||
|
first_name: true,
|
||||||
|
last_name: true,
|
||||||
|
} },
|
||||||
|
} },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
bank_code: { select: { categorie: true } },
|
bank_code: { select: { categorie: true } },
|
||||||
|
|
@ -57,17 +73,21 @@ export class PayPeriodsQueryService {
|
||||||
// EXPENSES (filtrés par crew si besoin)
|
// EXPENSES (filtrés par crew si besoin)
|
||||||
const expenses = await this.prisma.expenses.findMany({
|
const expenses = await this.prisma.expenses.findMany({
|
||||||
where: {
|
where: {
|
||||||
date: { gte: period.start_date, lte: period.end_date },
|
date: { gte: start, lte: end },
|
||||||
timesheet: whereEmployee,
|
timesheet: whereEmployee,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
amount: true,
|
amount: true,
|
||||||
timesheet: {
|
timesheet: { select: {
|
||||||
select: {
|
|
||||||
is_approved: true,
|
is_approved: true,
|
||||||
employee: { select: { id: true, user: { select: { first_name: true, last_name: true } } } },
|
employee: { select: {
|
||||||
},
|
id: true,
|
||||||
},
|
user: { select: {
|
||||||
|
first_name: true,
|
||||||
|
last_name: true
|
||||||
|
} },
|
||||||
|
} },
|
||||||
|
} },
|
||||||
bank_code: { select: { categorie: true, modifier: true } },
|
bank_code: { select: { categorie: true, modifier: true } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -149,8 +169,8 @@ export class PayPeriodsQueryService {
|
||||||
return {
|
return {
|
||||||
period_number: period.period_number,
|
period_number: period.period_number,
|
||||||
year: period.year,
|
year: period.year,
|
||||||
start_date: toDateString(period.start_date),
|
start_date: toDateString(start),
|
||||||
end_date: toDateString(period.end_date),
|
end_date: toDateString(end),
|
||||||
label: period.label,
|
label: period.label,
|
||||||
employees_overview,
|
employees_overview,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,22 @@ import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { PayPeriodsCommandService } from "./pay-periods-command.service";
|
import { PayPeriodsCommandService } from "./pay-periods-command.service";
|
||||||
import { mapMany, mapPayPeriodToDto } from "../mappers/pay-periods.mapper";
|
import { mapMany, mapPayPeriodToDto } from "../mappers/pay-periods.mapper";
|
||||||
import { PayPeriodDto } from "../dtos/pay-period.dto";
|
import { PayPeriodDto } from "../dtos/pay-period.dto";
|
||||||
|
import { computePeriod, listPayYear, payYearOfDate } from "../utils/pay-year.util";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PayPeriodsService {
|
export class PayPeriodsService {
|
||||||
constructor(private readonly prisma: PrismaService,
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
private readonly payperiodsApprovalService: PayPeriodsCommandService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async findAll(): Promise<PayPeriodDto[]> {
|
async findAll(): Promise<PayPeriodDto[]> {
|
||||||
const rows = await this.prisma.payPeriods.findMany({
|
const currentPayYear = payYearOfDate(new Date());
|
||||||
orderBy: [{ year: 'desc'}, { period_number: "asc"}],
|
return listPayYear(currentPayYear).map(period =>({
|
||||||
});
|
period_number: period.period_number,
|
||||||
return mapMany(rows);
|
year: period.year,
|
||||||
|
start_date: period.start_date,
|
||||||
|
end_date: period.end_date,
|
||||||
|
label: period.label,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(periodNumber: number): Promise<PayPeriodDto> {
|
async findOne(periodNumber: number): Promise<PayPeriodDto> {
|
||||||
|
|
@ -31,8 +34,11 @@ export class PayPeriodsService {
|
||||||
const row = await this.prisma.payPeriods.findFirst({
|
const row = await this.prisma.payPeriods.findFirst({
|
||||||
where: { year, period_number: periodNumber },
|
where: { year, period_number: periodNumber },
|
||||||
});
|
});
|
||||||
if (!row) throw new NotFoundException(`Pay period ${year}-${periodNumber} not found`);
|
if(row) return mapPayPeriodToDto(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}
|
||||||
}
|
}
|
||||||
|
|
||||||
//function to cherry pick a Date to find a period
|
//function to cherry pick a Date to find a period
|
||||||
|
|
@ -41,8 +47,16 @@ export class PayPeriodsService {
|
||||||
const row = await this.prisma.payPeriods.findFirst({
|
const row = await this.prisma.payPeriods.findFirst({
|
||||||
where: { start_date: { lte: dt }, end_date: { gte: dt } },
|
where: { start_date: { lte: dt }, end_date: { gte: dt } },
|
||||||
});
|
});
|
||||||
if (!row) throw new NotFoundException(`No period found for this date: ${date}`);
|
if(row) return mapPayPeriodToDto(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);
|
||||||
|
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}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findCurrent(date?: string): Promise<PayPeriodDto> {
|
async findCurrent(date?: string): Promise<PayPeriodDto> {
|
||||||
|
|
|
||||||
38
src/modules/pay-periods/utils/pay-year.util.ts
Normal file
38
src/modules/pay-periods/utils/pay-year.util.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
export const ANCHOR_ISO = '2023-12-17'; // ancre date
|
||||||
|
const PERIOD_DAYS = 14;
|
||||||
|
const PERIODS_PER_YEAR = 26;
|
||||||
|
|
||||||
|
const toUTCDate = (iso: string | Date) => {
|
||||||
|
const d = typeof iso === 'string' ? new Date(iso + 'T00:00:00.000Z') : iso;
|
||||||
|
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
||||||
|
};
|
||||||
|
export const toDateString = (d: Date) => d.toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
const ANCHOR = toUTCDate(ANCHOR_ISO);
|
||||||
|
|
||||||
|
export function payYearOfDate(date: string | Date): number {
|
||||||
|
const d = toUTCDate(date);
|
||||||
|
const days = Math.floor((+d - +ANCHOR) / 86400000);
|
||||||
|
const cycles = Math.floor(days / (PERIODS_PER_YEAR * PERIOD_DAYS));
|
||||||
|
return ANCHOR.getUTCFullYear() + 1 + cycles;
|
||||||
|
}
|
||||||
|
//compute labels for periods
|
||||||
|
export function computePeriod(payYear: number, periodNumber: number) {
|
||||||
|
const cycles = payYear - (ANCHOR.getUTCFullYear() + 1);
|
||||||
|
const offsetPeriods = cycles * PERIODS_PER_YEAR + (periodNumber - 1);
|
||||||
|
const start = new Date(+ANCHOR + offsetPeriods * PERIOD_DAYS * 86400000);
|
||||||
|
const end = new Date(+start + (PERIOD_DAYS - 1) * 86400000);
|
||||||
|
return {
|
||||||
|
period_number: periodNumber,
|
||||||
|
year: payYear,
|
||||||
|
start_date: toDateString(start),
|
||||||
|
end_date: toDateString(end),
|
||||||
|
label: `${toDateString(start)} → ${toDateString(end)}`,
|
||||||
|
start, end,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//list of all 26 periods for a full year
|
||||||
|
export function listPayYear(payYear: number) {
|
||||||
|
return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(payYear, i + 1));
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user