feat(pay_periods): added Result Pattern to pay-period module
This commit is contained in:
parent
48f1220a4e
commit
ddb6fa2ada
|
|
@ -2,13 +2,14 @@ import { Body, Controller, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Query
|
||||||
import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto";
|
import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto";
|
||||||
import { PayPeriodsQueryService } from "../services/pay-periods-query.service";
|
import { PayPeriodsQueryService } from "../services/pay-periods-query.service";
|
||||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
import { Roles as RoleEnum } from '.prisma/client';
|
|
||||||
import { PayPeriodsCommandService } from "../services/pay-periods-command.service";
|
import { PayPeriodsCommandService } from "../services/pay-periods-command.service";
|
||||||
import { PayPeriodBundleDto } from "../dtos/bundle-pay-period.dto";
|
import { PayPeriodBundleDto } from "../dtos/bundle-pay-period.dto";
|
||||||
import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto";
|
import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto";
|
||||||
|
import { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes";
|
||||||
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
|
||||||
@Controller('pay-periods')
|
@Controller('pay-periods')
|
||||||
|
@RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
||||||
export class PayPeriodsController {
|
export class PayPeriodsController {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|
@ -17,12 +18,14 @@ export class PayPeriodsController {
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@Get('current-and-all')
|
@Get('current-and-all')
|
||||||
async getCurrentAndAll(@Query('date') date?: string): Promise<PayPeriodBundleDto> {
|
async getCurrentAndAll(@Query('date') date?: string): Promise<Result<PayPeriodBundleDto, string>> {
|
||||||
const [current, periods] = await Promise.all([
|
const current = await this.queryService.findCurrent(date);
|
||||||
this.queryService.findCurrent(date),
|
if (!current.success) return { success: false, error: 'INVALID_PAY_PERIOD' };
|
||||||
this.queryService.findAll(),
|
|
||||||
]);
|
const periods = await this.queryService.findAll();
|
||||||
return { current, periods };
|
if (!periods.success) return { success: false, error: 'INVALID_PAY_PERIOD' };
|
||||||
|
|
||||||
|
return { success: true, data: { current: current.data, periods: periods.data } };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("date/:date")
|
@Get("date/:date")
|
||||||
|
|
@ -39,31 +42,30 @@ export class PayPeriodsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch("crew/pay-period-approval")
|
@Patch("crew/pay-period-approval")
|
||||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
async bulkApproval(@Req() req, @Body() dto: BulkCrewApprovalDto) {
|
async bulkApproval(@Req() req, @Body() dto: BulkCrewApprovalDto) {
|
||||||
const email = req.user?.email;
|
const email = req.user?.email;
|
||||||
if(!email) throw new UnauthorizedException(`Session infos not found`);
|
if (!email) throw new UnauthorizedException(`Session infos not found`);
|
||||||
return this.commandService.bulkApproveCrew(email, dto);
|
return this.commandService.bulkApproveCrew(email, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('crew/:year/:periodNumber')
|
@Get('crew/:year/:periodNumber')
|
||||||
@RolesAllowed(RoleEnum.SUPERVISOR)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
async getCrewOverview(@Req() req,
|
async getCrewOverview(@Req() req,
|
||||||
@Param('year', ParseIntPipe) year: number,
|
@Param('year', ParseIntPipe) year: number,
|
||||||
@Param('periodNumber', ParseIntPipe) period_no: number,
|
@Param('periodNumber', ParseIntPipe) period_no: number,
|
||||||
@Query('includeSubtree', new ParseBoolPipe({ optional: true })) include_subtree = false,
|
@Query('includeSubtree', new ParseBoolPipe({ optional: true })) include_subtree = false,
|
||||||
): Promise<PayPeriodOverviewDto> {
|
): Promise<Result<PayPeriodOverviewDto, string>> {
|
||||||
const email = req.user?.email;
|
const email = req.user?.email;
|
||||||
if(!email) throw new UnauthorizedException(`Session infos not found`);
|
if (!email) throw new UnauthorizedException(`Session infos not found`);
|
||||||
return this.queryService.getCrewOverview(year, period_no, email, include_subtree);
|
return this.queryService.getCrewOverview(year, period_no, email, include_subtree);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('overview/:year/:periodNumber')
|
@Get('overview/:year/:periodNumber')
|
||||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
|
||||||
async getOverviewByYear(
|
async getOverviewByYear(
|
||||||
@Param('year', ParseIntPipe) year: number,
|
@Param('year', ParseIntPipe) year: number,
|
||||||
@Param('periodNumber', ParseIntPipe) period_no: number,
|
@Param('periodNumber', ParseIntPipe) period_no: number,
|
||||||
): Promise<PayPeriodOverviewDto> {
|
): Promise<Result<PayPeriodOverviewDto, string>> {
|
||||||
return this.queryService.getOverviewByYearPeriod(year, period_no);
|
return this.queryService.getOverviewByYearPeriod(year, period_no);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,47 @@
|
||||||
import { BadRequestException, ForbiddenException, 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";
|
||||||
import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto";
|
import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto";
|
||||||
import { PayPeriodsQueryService } from "./pay-periods-query.service";
|
import { PayPeriodsQueryService } from "./pay-periods-query.service";
|
||||||
import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service";
|
import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service";
|
||||||
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
|
||||||
|
|
||||||
//change promise to return result pattern
|
//change promise to return result pattern
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PayPeriodsCommandService {
|
export class PayPeriodsCommandService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly timesheetsApproval: TimesheetApprovalService,
|
private readonly timesheetsApproval: TimesheetApprovalService,
|
||||||
private readonly query: PayPeriodsQueryService,
|
private readonly query: PayPeriodsQueryService,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
//function to approve pay-periods according to selected crew members
|
//function to approve pay-periods according to selected crew members
|
||||||
async bulkApproveCrew(email: string, dto:BulkCrewApprovalDto): Promise<{updated: number}> {
|
async bulkApproveCrew(email: string, dto: BulkCrewApprovalDto): Promise<Result<{ updated: number }, string>> {
|
||||||
const { include_subtree, items } = dto;
|
const { include_subtree, items } = dto;
|
||||||
if(!items?.length) throw new BadRequestException('no items to process');
|
if (!items?.length) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||||
|
|
||||||
//fetch and validate supervisor status
|
//fetch and validate supervisor status
|
||||||
const supervisor = await this.query.getSupervisor(email);
|
const supervisor = await this.query.getSupervisor(email);
|
||||||
if(!supervisor) throw new NotFoundException('No employee record linked to current user');
|
if (!supervisor) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||||
if(!supervisor.is_supervisor) throw new ForbiddenException('Employee is not a supervisor');
|
if (!supervisor.is_supervisor) return { success: false, error: 'INVALID_EMPLOYEE' };
|
||||||
|
|
||||||
//fetches emails of crew members linked to supervisor
|
//fetches emails of crew members linked to supervisor
|
||||||
const crew_emails = await this.query.resolveCrewEmails(supervisor.id, include_subtree);
|
const crew_emails = await this.query.resolveCrewEmails(supervisor.id, include_subtree);
|
||||||
|
if (!crew_emails.success) return { success: false, error: 'INVALID_EMAIL' };
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
for(const item of items) {
|
if (!crew_emails.data.has(item.employee_email)) {
|
||||||
if(!crew_emails.has(item.employee_email)) {
|
return { success: false, error: 'INVALID_EMPLOYEE' }
|
||||||
throw new ForbiddenException(`Employee ${item.employee_email} not in supervisor crew`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const period_cache = new Map<string, {period_start: Date, period_end: Date}>();
|
const period_cache = new Map<string, { period_start: Date, period_end: Date }>();
|
||||||
const getPeriod = async (year:number, period_no: number) => {
|
const getPeriod = async (year: number, period_no: number) => {
|
||||||
const key = `${year}-${period_no}`;
|
const key = `${year}-${period_no}`;
|
||||||
if(period_cache.has(key)) return period_cache.get(key)!;
|
if (period_cache.has(key)) return period_cache.get(key)!;
|
||||||
|
|
||||||
const period = await this.query.getPeriodWindow(year,period_no);
|
const period = await this.query.getPeriodWindow(year, period_no);
|
||||||
if(!period) throw new NotFoundException(`Pay period ${year}-${period_no} not found`);
|
if (!period) throw new NotFoundException(`Pay period ${year}-${period_no} not found`);
|
||||||
period_cache.set(key, period);
|
period_cache.set(key, period);
|
||||||
return period;
|
return period;
|
||||||
};
|
};
|
||||||
|
|
@ -48,27 +49,27 @@ export class PayPeriodsCommandService {
|
||||||
let updated = 0;
|
let updated = 0;
|
||||||
|
|
||||||
await this.prisma.$transaction(async (transaction) => {
|
await this.prisma.$transaction(async (transaction) => {
|
||||||
for(const item of items) {
|
for (const item of items) {
|
||||||
const { period_start, period_end } = await getPeriod(item.pay_year, item.period_no);
|
const { period_start, period_end } = await getPeriod(item.pay_year, item.period_no);
|
||||||
|
|
||||||
const timesheets = await transaction.timesheets.findMany({
|
const timesheets = await transaction.timesheets.findMany({
|
||||||
where: {
|
where: {
|
||||||
employee: { user: { email: item.employee_email } },
|
employee: { user: { email: item.employee_email } },
|
||||||
OR: [
|
OR: [
|
||||||
{shift : { some: { date: { gte: period_start, lte: period_end } } } },
|
{ shift: { some: { date: { gte: period_start, lte: period_end } } } },
|
||||||
{expense: { some: { date: { gte: period_start, lte: period_end } } } },
|
{ expense: { some: { date: { gte: period_start, lte: period_end } } } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
for(const { id } of timesheets) {
|
for (const { id } of timesheets) {
|
||||||
await this.timesheetsApproval.cascadeApprovalWithtx(transaction, id, item.approve);
|
await this.timesheetsApproval.cascadeApprovalWithtx(transaction, id, item.approve);
|
||||||
updated++;
|
updated++;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {updated};
|
return { success: true, data: { updated } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
import { ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { computeHours, computePeriod, listPayYear, payYearOfDate } from "src/common/utils/date-utils";
|
import { computeHours, computePeriod, listPayYear, payYearOfDate } 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 { PayPeriodDto } from "../dtos/pay-period.dto";
|
import { PayPeriodDto } from "../dtos/pay-period.dto";
|
||||||
import { mapPayPeriodToDto } from "../mappers/pay-periods.mapper";
|
import { mapPayPeriodToDto } from "../mappers/pay-periods.mapper";
|
||||||
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PayPeriodsQueryService {
|
export class PayPeriodsQueryService {
|
||||||
constructor(private readonly prisma: PrismaService) { }
|
constructor(private readonly prisma: PrismaService) { }
|
||||||
|
|
||||||
//change promise to return result pattern
|
async getOverview(pay_period_no: number): Promise<Result<PayPeriodOverviewDto, string>> {
|
||||||
|
|
||||||
async getOverview(pay_period_no: number): Promise<PayPeriodOverviewDto> {
|
|
||||||
const period = await this.prisma.payPeriods.findFirst({
|
const period = await this.prisma.payPeriods.findFirst({
|
||||||
where: { pay_period_no },
|
where: { pay_period_no },
|
||||||
orderBy: { pay_year: "desc" },
|
orderBy: { pay_year: "desc" },
|
||||||
});
|
});
|
||||||
if (!period) throw new NotFoundException(`PAY_PERIOD_NOT_FOUND`);
|
if (!period) return { success: false, error: `PAY_PERIOD_NOT_FOUND` };
|
||||||
|
|
||||||
return this.buildOverview({
|
const overview = await this.buildOverview({
|
||||||
period_start: period.period_start,
|
period_start: period.period_start,
|
||||||
period_end: period.period_end,
|
period_end: period.period_end,
|
||||||
payday: period.payday,
|
payday: period.payday,
|
||||||
|
|
@ -26,23 +25,28 @@ export class PayPeriodsQueryService {
|
||||||
pay_year: period.pay_year,
|
pay_year: period.pay_year,
|
||||||
label: period.label,
|
label: period.label,
|
||||||
});
|
});
|
||||||
|
if (!overview.success) return { success: false, error: 'INVALID_PAY_PERIOD' }
|
||||||
|
|
||||||
|
return { success: true, data: overview.data }
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOverviewByYearPeriod(pay_year: number, period_no: number): Promise<PayPeriodOverviewDto> {
|
async getOverviewByYearPeriod(pay_year: number, period_no: number): Promise<Result<PayPeriodOverviewDto, string>> {
|
||||||
const period = computePeriod(pay_year, period_no);
|
const period = computePeriod(pay_year, period_no);
|
||||||
return this.buildOverview({
|
const overview = await this.buildOverview({
|
||||||
period_start: period.period_start,
|
period_start: period.period_start,
|
||||||
period_end: period.period_end,
|
period_end: period.period_end,
|
||||||
period_no: period.period_no,
|
period_no: period.period_no,
|
||||||
pay_year: period.pay_year,
|
pay_year: period.pay_year,
|
||||||
payday: period.payday,
|
payday: period.payday,
|
||||||
label: period.label,
|
label: period.label,
|
||||||
} as any);
|
});
|
||||||
|
if (!overview.success) return { success: false, error: 'INVALID_PAY_PERIOD' }
|
||||||
|
return { success: true, data: overview.data }
|
||||||
}
|
}
|
||||||
|
|
||||||
//find crew member associated with supervisor
|
//find crew member associated with supervisor
|
||||||
private async resolveCrew(supervisor_id: number, include_subtree: boolean):
|
private async resolveCrew(supervisor_id: number, include_subtree: boolean):
|
||||||
Promise<Array<{ id: number; first_name: string; last_name: string; email: string }>> {
|
Promise<Result<Array<{ id: number; first_name: string; last_name: string; email: string }>, string>> {
|
||||||
const result: Array<{ id: number; first_name: string; last_name: string; email: string; }> = [];
|
const result: Array<{ id: number; first_name: string; last_name: string; email: string; }> = [];
|
||||||
|
|
||||||
let frontier = await this.prisma.employees.findMany({
|
let frontier = await this.prisma.employees.findMany({
|
||||||
|
|
@ -53,7 +57,7 @@ export class PayPeriodsQueryService {
|
||||||
id: emp.id, first_name: emp.user.first_name, last_name: emp.user.last_name, email: emp.user.email
|
id: emp.id, first_name: emp.user.first_name, last_name: emp.user.last_name, email: emp.user.email
|
||||||
})));
|
})));
|
||||||
|
|
||||||
if (!include_subtree) return result;
|
if (!include_subtree) return { success: true, data: result };
|
||||||
|
|
||||||
while (frontier.length) {
|
while (frontier.length) {
|
||||||
const parent_ids = frontier.map(emp => emp.id);
|
const parent_ids = frontier.map(emp => emp.id);
|
||||||
|
|
@ -67,20 +71,21 @@ export class PayPeriodsQueryService {
|
||||||
})));
|
})));
|
||||||
frontier = next;
|
frontier = next;
|
||||||
}
|
}
|
||||||
return result;
|
return { success: true, data: result };
|
||||||
}
|
}
|
||||||
|
|
||||||
//fetchs crew emails
|
//fetchs crew emails
|
||||||
async resolveCrewEmails(supervisor_id: number, include_subtree: boolean): Promise<Set<string>> {
|
async resolveCrewEmails(supervisor_id: number, include_subtree: boolean): Promise<Result<Set<string>, string>> {
|
||||||
const crew = await this.resolveCrew(supervisor_id, include_subtree);
|
const crew = await this.resolveCrew(supervisor_id, include_subtree);
|
||||||
return new Set(crew.map(crew_member => crew_member.email).filter(Boolean));
|
if (!crew.success) return { success: false, error: crew.error }
|
||||||
|
return { success: true, data: new Set(crew.data.map(crew_member => crew_member.email).filter(Boolean)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCrewOverview(pay_year: number, period_no: number, email: string, include_subtree: boolean):
|
async getCrewOverview(pay_year: number, period_no: number, email: string, include_subtree: boolean):
|
||||||
Promise<PayPeriodOverviewDto> {
|
Promise<Result<PayPeriodOverviewDto, string>> {
|
||||||
// 1) Search for the period
|
// 1) Search for the period
|
||||||
const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no: period_no } });
|
const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no: period_no } });
|
||||||
if (!period) throw new NotFoundException(`PAY_PERIOD_NOT_FOUND`);
|
if (!period) return { success: false, error: 'PAY_PERIOD_NOT_FOUND' }
|
||||||
|
|
||||||
// 2) fetch supervisor
|
// 2) fetch supervisor
|
||||||
const supervisor = await this.prisma.employees.findFirst({
|
const supervisor = await this.prisma.employees.findFirst({
|
||||||
|
|
@ -91,15 +96,16 @@ export class PayPeriodsQueryService {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!supervisor) throw new NotFoundException('No employee record linked to current user');
|
if (!supervisor) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }
|
||||||
if (!supervisor.is_supervisor) throw new ForbiddenException('Employee is not a supervisor');
|
if (!supervisor.is_supervisor) return { success: false, error: 'INVALID_EMPLOYEE' }
|
||||||
|
|
||||||
// 3)fetchs crew members
|
// 3)fetchs crew members
|
||||||
const crew = await this.resolveCrew(supervisor.id, include_subtree); // [{ id, first_name, last_name }]
|
const crew = await this.resolveCrew(supervisor.id, include_subtree); // [{ id, first_name, last_name }]
|
||||||
const crew_ids = crew.map(c => c.id);
|
if (!crew.success) return { success: false, error: crew.error }
|
||||||
|
const crew_ids = crew.data.map(c => c.id);
|
||||||
// seed names map for employee without data
|
// seed names map for employee without data
|
||||||
const seed_names = new Map<number, { name: string; email: string }>(
|
const seed_names = new Map<number, { name: string; email: string }>(
|
||||||
crew.map(crew => [
|
crew.data.map(crew => [
|
||||||
crew.id,
|
crew.id,
|
||||||
{
|
{
|
||||||
name: `${crew.first_name} ${crew.last_name}`.trim(),
|
name: `${crew.first_name} ${crew.last_name}`.trim(),
|
||||||
|
|
@ -108,17 +114,18 @@ export class PayPeriodsQueryService {
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const overview = await this.buildOverview({
|
||||||
// 4) overview build
|
|
||||||
return this.buildOverview({
|
|
||||||
period_no: period.pay_period_no,
|
period_no: period.pay_period_no,
|
||||||
period_start: period.period_start,
|
period_start: period.period_start,
|
||||||
period_end: period.period_end,
|
period_end: period.period_end,
|
||||||
payday: period.payday,
|
payday: period.payday,
|
||||||
pay_year: period.pay_year,
|
pay_year: period.pay_year,
|
||||||
label: period.label,
|
label: period.label,
|
||||||
//add is_approved
|
}, { filtered_employee_ids: crew_ids, seed_names })
|
||||||
}, { filtered_employee_ids: crew_ids, seed_names });
|
if (!overview.success) return { success: false, error: 'INVALID_PAY_PERIOD' }
|
||||||
|
|
||||||
|
// 4) overview build
|
||||||
|
return { success: true, data: overview.data }
|
||||||
}
|
}
|
||||||
|
|
||||||
private async buildOverview(
|
private async buildOverview(
|
||||||
|
|
@ -127,7 +134,7 @@ export class PayPeriodsQueryService {
|
||||||
period_no: number; pay_year: number; label: string;
|
period_no: number; pay_year: number; label: string;
|
||||||
}, //add is_approved
|
}, //add is_approved
|
||||||
options?: { filtered_employee_ids?: number[]; seed_names?: Map<number, { name: string, email: string }> }
|
options?: { filtered_employee_ids?: number[]; seed_names?: Map<number, { name: string, email: string }> }
|
||||||
): Promise<PayPeriodOverviewDto> {
|
): Promise<Result<PayPeriodOverviewDto, string>> {
|
||||||
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));
|
||||||
|
|
||||||
|
|
@ -312,13 +319,16 @@ export class PayPeriodsQueryService {
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pay_period_no: period.period_no,
|
success: true,
|
||||||
pay_year: period.pay_year,
|
data: {
|
||||||
payday: toDateString(payd),
|
pay_period_no: period.period_no,
|
||||||
period_start: toDateString(start),
|
pay_year: period.pay_year,
|
||||||
period_end: toDateString(end),
|
payday: toDateString(payd),
|
||||||
label: period.label,
|
period_start: toDateString(start),
|
||||||
employees_overview,
|
period_end: toDateString(end),
|
||||||
|
label: period.label,
|
||||||
|
employees_overview,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,72 +339,82 @@ export class PayPeriodsQueryService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll(): Promise<PayPeriodDto[]> {
|
async findAll(): Promise<Result<PayPeriodDto[], string>> {
|
||||||
const currentPayYear = payYearOfDate(new Date());
|
const currentPayYear = payYearOfDate(new Date());
|
||||||
return listPayYear(currentPayYear).map(period => ({
|
return {
|
||||||
pay_period_no: period.period_no,
|
success: true,
|
||||||
pay_year: period.pay_year,
|
data: listPayYear(currentPayYear).map(period => ({
|
||||||
payday: period.payday,
|
pay_period_no: period.period_no,
|
||||||
period_start: period.period_start,
|
pay_year: period.pay_year,
|
||||||
period_end: period.period_end,
|
payday: period.payday,
|
||||||
label: period.label,
|
period_start: period.period_start,
|
||||||
//add is_approved
|
period_end: period.period_end,
|
||||||
}));
|
label: period.label,
|
||||||
|
}))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(period_no: number): Promise<PayPeriodDto> {
|
async findOne(period_no: number): Promise<Result<PayPeriodDto, string>> {
|
||||||
const row = await this.prisma.payPeriods.findFirst({
|
const row = await this.prisma.payPeriods.findFirst({
|
||||||
where: { pay_period_no: period_no },
|
where: { pay_period_no: period_no },
|
||||||
orderBy: { pay_year: "desc" },
|
orderBy: { pay_year: "desc" },
|
||||||
});
|
});
|
||||||
if (!row) throw new NotFoundException(`PAY_PERIOD_NOT_FOUND`);
|
if (!row) return { success: false, error: `PAY_PERIOD_NOT_FOUND` }
|
||||||
return mapPayPeriodToDto(row);
|
return { success: true, data: mapPayPeriodToDto(row) };
|
||||||
}
|
}
|
||||||
|
|
||||||
async findCurrent(date?: string): Promise<PayPeriodDto> {
|
async findCurrent(date?: string): Promise<Result<PayPeriodDto, string>> {
|
||||||
const iso_day = date ?? new Date().toISOString().slice(0, 10);
|
const iso_day = date ?? new Date().toISOString().slice(0, 10);
|
||||||
return this.findByDate(iso_day);
|
const pay_period = await this.findByDate(iso_day);
|
||||||
|
if (!pay_period.success) return { success: false, error: 'INVALID_PAY_PERIOD' }
|
||||||
|
return { success: true, data: pay_period.data }
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByYearPeriod(pay_year: number, period_no: number): Promise<PayPeriodDto> {
|
async findOneByYearPeriod(pay_year: number, period_no: number): Promise<Result<PayPeriodDto, string>> {
|
||||||
const row = await this.prisma.payPeriods.findFirst({
|
const row = await this.prisma.payPeriods.findFirst({
|
||||||
where: { pay_year, pay_period_no: period_no },
|
where: { pay_year, pay_period_no: period_no },
|
||||||
});
|
});
|
||||||
if (row) return mapPayPeriodToDto(row);
|
if (row) return { success: true, data: mapPayPeriodToDto(row) };
|
||||||
|
|
||||||
// fallback for outside of view periods
|
// fallback for outside of view periods
|
||||||
const period = computePeriod(pay_year, period_no);
|
const period = computePeriod(pay_year, period_no);
|
||||||
return {
|
return {
|
||||||
pay_period_no: period.period_no,
|
success: true,
|
||||||
pay_year: period.pay_year,
|
data: {
|
||||||
period_start: period.period_start,
|
pay_period_no: period.period_no,
|
||||||
payday: period.payday,
|
pay_year: period.pay_year,
|
||||||
period_end: period.period_end,
|
period_start: period.period_start,
|
||||||
label: period.label
|
payday: period.payday,
|
||||||
|
period_end: period.period_end,
|
||||||
|
label: period.label
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//function to cherry pick a Date to find a period
|
//function to cherry pick a Date to find a period
|
||||||
async findByDate(date: string): Promise<PayPeriodDto> {
|
async findByDate(date: string): Promise<Result<PayPeriodDto, string>> {
|
||||||
const dt = new Date(date);
|
const dt = new Date(date);
|
||||||
const row = await this.prisma.payPeriods.findFirst({
|
const row = await this.prisma.payPeriods.findFirst({
|
||||||
where: { period_start: { lte: dt }, period_end: { gte: dt } },
|
where: { period_start: { lte: dt }, period_end: { gte: dt } },
|
||||||
});
|
});
|
||||||
if (row) return mapPayPeriodToDto(row);
|
if (row) return { success: true, data: mapPayPeriodToDto(row) };
|
||||||
|
|
||||||
//fallback for outwside view periods
|
//fallback for outwside view periods
|
||||||
const pay_year = payYearOfDate(date);
|
const pay_year = payYearOfDate(date);
|
||||||
const periods = listPayYear(pay_year);
|
const periods = listPayYear(pay_year);
|
||||||
const hit = periods.find(period => date >= period.period_start && date <= period.period_end);
|
const hit = periods.find(period => date >= period.period_start && date <= period.period_end);
|
||||||
if (!hit) throw new NotFoundException(`PAY_PERIOD_NOT_FOUND`);
|
if (!hit) return { success: false, error: `PAY_PERIOD_NOT_FOUND` }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pay_period_no: hit.period_no,
|
success: true,
|
||||||
pay_year: hit.pay_year,
|
data: {
|
||||||
period_start: hit.period_start,
|
pay_period_no: hit.period_no,
|
||||||
period_end: hit.period_end,
|
pay_year: hit.pay_year,
|
||||||
payday: hit.payday,
|
period_start: hit.period_start,
|
||||||
label: hit.label
|
period_end: hit.period_end,
|
||||||
|
payday: hit.payday,
|
||||||
|
label: hit.label
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user