refactor(expenses): commented module expenses et leave-request. needs refactor to match new timesheets and shifts module.

This commit is contained in:
Matthieu Haineault 2025-10-21 16:27:56 -04:00
parent 11f6cf2049
commit 6aeaf16993
28 changed files with 2036 additions and 2818 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,21 @@
import { BadRequestException, Module, ValidationPipe } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ArchivalModule } from './modules/archival/archival.module';
// import { ArchivalModule } from './modules/archival/archival.module';
import { AuthenticationModule } from './modules/authentication/auth.module';
import { BankCodesModule } from './modules/bank-codes/bank-codes.module';
import { BusinessLogicsModule } from './modules/business-logics/business-logics.module';
// import { CsvExportModule } from './modules/exports/csv-exports.module';
import { CustomersModule } from './modules/customers/customers.module';
import { EmployeesModule } from './modules/employees/employees.module';
import { ExpensesModule } from './modules/expenses/expenses.module';
// import { ExpensesModule } from './modules/expenses/expenses.module';
import { HealthModule } from './health/health.module';
import { HealthController } from './health/health.controller';
import { LeaveRequestsModule } from './modules/leave-requests/leave-requests.module';
// import { LeaveRequestsModule } from './modules/leave-requests/leave-requests.module';
import { NotificationsModule } from './modules/notifications/notifications.module';
import { OauthSessionsModule } from './modules/oauth-sessions/oauth-sessions.module';
import { OvertimeService } from './modules/business-logics/services/overtime.service';
import { PayperiodsModule } from './modules/pay-periods/pay-periods.module';
// import { PayperiodsModule } from './modules/pay-periods/pay-periods.module';
import { PreferencesModule } from './modules/preferences/preferences.module';
import { PrismaModule } from './prisma/prisma.module';
import { ScheduleModule } from '@nestjs/schedule';
@ -30,7 +30,7 @@ import { SchedulePresetsModule } from './modules/schedule-presets/schedule-prese
@Module({
imports: [
ArchivalModule,
// ArchivalModule,
AuthenticationModule,
BankCodesModule,
BusinessLogicsModule,
@ -38,12 +38,12 @@ import { SchedulePresetsModule } from './modules/schedule-presets/schedule-prese
// CsvExportModule,
CustomersModule,
EmployeesModule,
ExpensesModule,
// ExpensesModule,
HealthModule,
LeaveRequestsModule,
// LeaveRequestsModule,
NotificationsModule,
OauthSessionsModule,
PayperiodsModule,
// PayperiodsModule,
PreferencesModule,
PrismaModule,
ScheduleModule.forRoot(), //cronjobs

View File

@ -1,34 +1,34 @@
import { Module } from "@nestjs/common";
import { ScheduleModule } from "@nestjs/schedule";
import { TimesheetsModule } from "../timesheets/timesheets.module";
import { ExpensesModule } from "../expenses/expenses.module";
import { ShiftsModule } from "../shifts/shifts.module";
import { LeaveRequestsModule } from "../leave-requests/leave-requests.module";
import { ArchivalService } from "./services/archival.service";
import { EmployeesArchiveController } from "./controllers/employees-archive.controller";
import { ExpensesArchiveController } from "./controllers/expenses-archive.controller";
import { LeaveRequestsArchiveController } from "./controllers/leave-requests-archive.controller";
import { ShiftsArchiveController } from "./controllers/shifts-archive.controller";
import { TimesheetsArchiveController } from "./controllers/timesheets-archive.controller";
import { EmployeesModule } from "../employees/employees.module";
// import { Module } from "@nestjs/common";
// import { ScheduleModule } from "@nestjs/schedule";
// import { TimesheetsModule } from "../timesheets/timesheets.module";
// import { ExpensesModule } from "../expenses/expenses.module";
// import { ShiftsModule } from "../shifts/shifts.module";
// import { LeaveRequestsModule } from "../leave-requests/leave-requests.module";
// import { ArchivalService } from "./services/archival.service";
// import { EmployeesArchiveController } from "./controllers/employees-archive.controller";
// import { ExpensesArchiveController } from "./controllers/expenses-archive.controller";
// import { LeaveRequestsArchiveController } from "./controllers/leave-requests-archive.controller";
// import { ShiftsArchiveController } from "./controllers/shifts-archive.controller";
// import { TimesheetsArchiveController } from "./controllers/timesheets-archive.controller";
// import { EmployeesModule } from "../employees/employees.module";
@Module({
imports: [
EmployeesModule,
ScheduleModule,
TimesheetsModule,
ExpensesModule,
ShiftsModule,
LeaveRequestsModule,
],
providers: [ArchivalService],
controllers: [
EmployeesArchiveController,
ExpensesArchiveController,
LeaveRequestsArchiveController,
ShiftsArchiveController,
TimesheetsArchiveController,
],
})
// @Module({
// imports: [
// EmployeesModule,
// ScheduleModule,
// TimesheetsModule,
// ExpensesModule,
// ShiftsModule,
// LeaveRequestsModule,
// ],
// providers: [ArchivalService],
// controllers: [
// EmployeesArchiveController,
// ExpensesArchiveController,
// LeaveRequestsArchiveController,
// ShiftsArchiveController,
// TimesheetsArchiveController,
// ],
// })
export class ArchivalModule {}
// export class ArchivalModule {}

View File

@ -1,95 +1,95 @@
import { Body, Controller, Get, Param, Put, } from "@nestjs/common";
import { Roles as RoleEnum } from '.prisma/client';
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
import { RolesAllowed } from "src/common/decorators/roles.decorators";
import { ExpensesCommandService } from "../services/expenses-command.service";
import { UpsertExpenseDto } from "../dtos/upsert-expense.dto";
import { UpsertExpenseResult } from "../types and interfaces/expenses.types.interfaces";
import { DayExpensesDto } from "src/modules/timesheets/~misc_deprecated-files/timesheet-period.dto";
import { ExpensesQueryService } from "../services/expenses-query.service";
// import { Body, Controller, Get, Param, Put, } from "@nestjs/common";
// import { Roles as RoleEnum } from '.prisma/client';
// import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
// import { ExpensesCommandService } from "../services/expenses-command.service";
// import { UpsertExpenseDto } from "../dtos/upsert-expense.dto";
// import { UpsertExpenseResult } from "../types and interfaces/expenses.types.interfaces";
// import { DayExpensesDto } from "src/modules/timesheets/~misc_deprecated-files/timesheet-period.dto";
// import { ExpensesQueryService } from "../services/expenses-query.service";
@ApiTags('Expenses')
@ApiBearerAuth('access-token')
// @UseGuards()
@Controller('Expenses')
export class ExpensesController {
constructor(
private readonly query: ExpensesQueryService,
private readonly command: ExpensesCommandService,
) {}
// @ApiTags('Expenses')
// @ApiBearerAuth('access-token')
// // @UseGuards()
// @Controller('Expenses')
// export class ExpensesController {
// constructor(
// private readonly query: ExpensesQueryService,
// private readonly command: ExpensesCommandService,
// ) {}
@Put('upsert/:email/:date')
async upsert_by_date(
@Param('email') email: string,
@Param('date') date: string,
@Body() dto: UpsertExpenseDto,
): Promise<UpsertExpenseResult> {
return this.command.upsertExpensesByDate(email, date, dto);
}
// @Put('upsert/:email/:date')
// async upsert_by_date(
// @Param('email') email: string,
// @Param('date') date: string,
// @Body() dto: UpsertExpenseDto,
// ): Promise<UpsertExpenseResult> {
// return this.command.upsertExpensesByDate(email, date, dto);
// }
@Get('list/:email/:year/:period_no')
async findExpenseListByPayPeriodAndEmail(
@Param('email') email:string,
@Param('year') year: number,
@Param('period_no') period_no: number,
): Promise<DayExpensesDto> {
return this.query.findExpenseListByPayPeriodAndEmail(email, year, period_no);
}
// @Get('list/:email/:year/:period_no')
// async findExpenseListByPayPeriodAndEmail(
// @Param('email') email:string,
// @Param('year') year: number,
// @Param('period_no') period_no: number,
// ): Promise<DayExpensesDto> {
// return this.query.findExpenseListByPayPeriodAndEmail(email, year, period_no);
// }
//_____________________________________________________________________________________________
// Deprecated or unused methods
//_____________________________________________________________________________________________
// //_____________________________________________________________________________________________
// // Deprecated or unused methods
// //_____________________________________________________________________________________________
// @Post()
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// @ApiOperation({ summary: 'Create expense' })
// @ApiResponse({ status: 201, description: 'Expense created',type: CreateExpenseDto })
// @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' })
// create(@Body() dto: CreateExpenseDto): Promise<Expenses> {
// return this.query.create(dto);
// }
// // @Post()
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// // @ApiOperation({ summary: 'Create expense' })
// // @ApiResponse({ status: 201, description: 'Expense created',type: CreateExpenseDto })
// // @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' })
// // create(@Body() dto: CreateExpenseDto): Promise<Expenses> {
// // return this.query.create(dto);
// // }
// @Get()
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// @ApiOperation({ summary: 'Find all expenses' })
// @ApiResponse({ status: 201, description: 'List of expenses found',type: CreateExpenseDto, isArray: true })
// @ApiResponse({ status: 400, description: 'List of expenses not found' })
// @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
// findAll(@Query() filters: SearchExpensesDto): Promise<Expenses[]> {
// return this.query.findAll(filters);
// }
// // @Get()
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// // @ApiOperation({ summary: 'Find all expenses' })
// // @ApiResponse({ status: 201, description: 'List of expenses found',type: CreateExpenseDto, isArray: true })
// // @ApiResponse({ status: 400, description: 'List of expenses not found' })
// // @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
// // findAll(@Query() filters: SearchExpensesDto): Promise<Expenses[]> {
// // return this.query.findAll(filters);
// // }
// @Get(':id')
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// @ApiOperation({ summary: 'Find expense' })
// @ApiResponse({ status: 201, description: 'Expense found',type: CreateExpenseDto })
// @ApiResponse({ status: 400, description: 'Expense not found' })
// findOne(@Param('id', ParseIntPipe) id: number): Promise <Expenses> {
// return this.query.findOne(id);
// }
// // @Get(':id')
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// // @ApiOperation({ summary: 'Find expense' })
// // @ApiResponse({ status: 201, description: 'Expense found',type: CreateExpenseDto })
// // @ApiResponse({ status: 400, description: 'Expense not found' })
// // findOne(@Param('id', ParseIntPipe) id: number): Promise <Expenses> {
// // return this.query.findOne(id);
// // }
// @Patch(':id')
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// @ApiOperation({ summary: 'Expense shift' })
// @ApiResponse({ status: 201, description: 'Expense updated',type: CreateExpenseDto })
// @ApiResponse({ status: 400, description: 'Expense not found' })
// update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateExpenseDto) {
// return this.query.update(id,dto);
// }
// // @Patch(':id')
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// // @ApiOperation({ summary: 'Expense shift' })
// // @ApiResponse({ status: 201, description: 'Expense updated',type: CreateExpenseDto })
// // @ApiResponse({ status: 400, description: 'Expense not found' })
// // update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateExpenseDto) {
// // return this.query.update(id,dto);
// // }
// @Delete(':id')
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// @ApiOperation({ summary: 'Delete expense' })
// @ApiResponse({ status: 201, description: 'Expense deleted',type: CreateExpenseDto })
// @ApiResponse({ status: 400, description: 'Expense not found' })
// remove(@Param('id', ParseIntPipe) id: number): Promise<Expenses> {
// return this.query.remove(id);
// }
// // @Delete(':id')
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// // @ApiOperation({ summary: 'Delete expense' })
// // @ApiResponse({ status: 201, description: 'Expense deleted',type: CreateExpenseDto })
// // @ApiResponse({ status: 400, description: 'Expense not found' })
// // remove(@Param('id', ParseIntPipe) id: number): Promise<Expenses> {
// // return this.query.remove(id);
// // }
// @Patch('approval/:id')
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
// async approve(@Param('id', ParseIntPipe) id: number, @Body('is_approved', ParseBoolPipe) isApproved: boolean) {
// return this.command.updateApproval(id, isApproved);
// }
// // @Patch('approval/:id')
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
// // async approve(@Param('id', ParseIntPipe) id: number, @Body('is_approved', ParseBoolPipe) isApproved: boolean) {
// // return this.command.updateApproval(id, isApproved);
// // }
}
// }

View File

@ -1,23 +1,23 @@
import { ExpensesController } from "./controllers/expenses.controller";
import { Module } from "@nestjs/common";
import { ExpensesQueryService } from "./services/expenses-query.service";
import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module";
import { ExpensesCommandService } from "./services/expenses-command.service";
import { ExpensesArchivalService } from "./services/expenses-archival.service";
import { SharedModule } from "../shared/shared.module";
// import { ExpensesController } from "./controllers/expenses.controller";
// import { Module } from "@nestjs/common";
// import { ExpensesQueryService } from "./services/expenses-query.service";
// import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module";
// import { ExpensesCommandService } from "./services/expenses-command.service";
// import { ExpensesArchivalService } from "./services/expenses-archival.service";
// import { SharedModule } from "../shared/shared.module";
@Module({
imports: [BusinessLogicsModule, SharedModule],
controllers: [ExpensesController],
providers: [
ExpensesQueryService,
ExpensesArchivalService,
ExpensesCommandService,
],
exports: [
ExpensesQueryService,
ExpensesArchivalService,
],
})
// @Module({
// imports: [BusinessLogicsModule, SharedModule],
// controllers: [ExpensesController],
// providers: [
// ExpensesQueryService,
// ExpensesArchivalService,
// ExpensesCommandService,
// ],
// exports: [
// ExpensesQueryService,
// ExpensesArchivalService,
// ],
// })
export class ExpensesModule {}
// export class ExpensesModule {}

View File

@ -1,250 +1,249 @@
import { BaseApprovalService } from "src/common/shared/base-approval.service";
import { Expenses, Prisma } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service";
import { UpsertExpenseDto } from "../dtos/upsert-expense.dto";
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
import { ExpenseResponse, UpsertAction } from "../types and interfaces/expenses.types.interfaces";
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-timesheet.utils";
import {
BadRequestException,
Injectable,
NotFoundException
} from "@nestjs/common";
import {
assertAndTrimComment,
computeAmountDecimal,
computeMileageAmount,
mapDbExpenseToDayResponse,
normalizeType,
parseAttachmentId
} from "../utils/expenses.utils";
import { toDateOnly } from "src/modules/shifts/helpers/shifts-date-time-helpers";
// import { BaseApprovalService } from "src/common/shared/base-approval.service";
// import { Expenses, Prisma } from "@prisma/client";
// import { PrismaService } from "src/prisma/prisma.service";
// import { UpsertExpenseDto } from "../dtos/upsert-expense.dto";
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
// import { ExpenseResponse, UpsertAction } from "../types and interfaces/expenses.types.interfaces";
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
// import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-timesheet.utils";
// import {
// BadRequestException,
// Injectable,
// NotFoundException
// } from "@nestjs/common";
// import {
// assertAndTrimComment,
// computeAmountDecimal,
// computeMileageAmount,
// mapDbExpenseToDayResponse,
// normalizeType,
// parseAttachmentId
// } from "../utils/expenses.utils";
@Injectable()
export class ExpensesCommandService extends BaseApprovalService<Expenses> {
constructor(
prisma: PrismaService,
private readonly bankCodesResolver: BankCodesResolver,
private readonly timesheetsResolver: EmployeeTimesheetResolver,
private readonly emailResolver: EmailToIdResolver,
) { super(prisma); }
// @Injectable()
// export class ExpensesCommandService extends BaseApprovalService<Expenses> {
// constructor(
// prisma: PrismaService,
// private readonly bankCodesResolver: BankCodesResolver,
// private readonly timesheetsResolver: EmployeeTimesheetResolver,
// private readonly emailResolver: EmailToIdResolver,
// ) { super(prisma); }
//_____________________________________________________________________________________________
// APPROVAL TX-DELEGATE METHODS
//_____________________________________________________________________________________________
// //_____________________________________________________________________________________________
// // APPROVAL TX-DELEGATE METHODS
// //_____________________________________________________________________________________________
protected get delegate() {
return this.prisma.expenses;
}
// protected get delegate() {
// return this.prisma.expenses;
// }
protected delegateFor(transaction: Prisma.TransactionClient){
return transaction.expenses;
}
// protected delegateFor(transaction: Prisma.TransactionClient){
// return transaction.expenses;
// }
async updateApproval(id: number, isApproved: boolean): Promise<Expenses> {
return this.prisma.$transaction((transaction) =>
this.updateApprovalWithTransaction(transaction, id, isApproved),
);
}
// async updateApproval(id: number, isApproved: boolean): Promise<Expenses> {
// return this.prisma.$transaction((transaction) =>
// this.updateApprovalWithTransaction(transaction, id, isApproved),
// );
// }
//_____________________________________________________________________________________________
// MASTER CRUD FUNCTION
//_____________________________________________________________________________________________
readonly upsertExpensesByDate = async (email: string, date: string, dto: UpsertExpenseDto,
): Promise<{ action:UpsertAction; day: ExpenseResponse[] }> => {
// //_____________________________________________________________________________________________
// // MASTER CRUD FUNCTION
// //_____________________________________________________________________________________________
// readonly upsertExpensesByDate = async (email: string, date: string, dto: UpsertExpenseDto,
// ): Promise<{ action:UpsertAction; day: ExpenseResponse[] }> => {
//validates if there is an existing expense, at least 1 old or new
const { old_expense, new_expense } = dto ?? {};
if(!old_expense && !new_expense) throw new BadRequestException('At least one expense must be provided');
// //validates if there is an existing expense, at least 1 old or new
// const { old_expense, new_expense } = dto ?? {};
// if(!old_expense && !new_expense) throw new BadRequestException('At least one expense must be provided');
//validate date format
const date_only = toDateOnly(date);
if(Number.isNaN(date_only.getTime())) throw new BadRequestException('Invalid date format (expected: YYYY-MM-DD)');
// //validate date format
// const date_only = toDateOnly(date);
// if(Number.isNaN(date_only.getTime())) throw new BadRequestException('Invalid date format (expected: YYYY-MM-DD)');
//resolve employee_id by email
const employee_id = await this.emailResolver.findIdByEmail(email);
// //resolve employee_id by email
// const employee_id = await this.emailResolver.findIdByEmail(email);
//make sure a timesheet existes
const timesheet_id = await this.timesheetsResolver.findTimesheetIdByEmail(email, date_only);
if(!timesheet_id) throw new NotFoundException(`no timesheet found for employee #${employee_id}`)
const {id} = timesheet_id;
// //make sure a timesheet existes
// const timesheet_id = await this.timesheetsResolver.findTimesheetIdByEmail(email, date_only);
// if(!timesheet_id) throw new NotFoundException(`no timesheet found for employee #${employee_id}`)
// const {id} = timesheet_id;
return this.prisma.$transaction(async (tx) => {
const loadDay = async (): Promise<ExpenseResponse[]> => {
const rows = await tx.expenses.findMany({
where: {
timesheet_id: id,
date: date_only,
},
include: {
bank_code: {
select: {
type: true,
},
},
},
orderBy: [{ date: 'asc' }, { id: 'asc' }],
});
// return this.prisma.$transaction(async (tx) => {
// const loadDay = async (): Promise<ExpenseResponse[]> => {
// const rows = await tx.expenses.findMany({
// where: {
// timesheet_id: id,
// date: date_only,
// },
// include: {
// bank_code: {
// select: {
// type: true,
// },
// },
// },
// orderBy: [{ date: 'asc' }, { id: 'asc' }],
// });
return rows.map((r) =>
mapDbExpenseToDayResponse({
date: r.date,
amount: r.amount ?? 0,
mileage: r.mileage ?? 0,
comment: r.comment,
is_approved: r.is_approved,
bank_code: r.bank_code,
}));
};
// return rows.map((r) =>
// mapDbExpenseToDayResponse({
// date: r.date,
// amount: r.amount ?? 0,
// mileage: r.mileage ?? 0,
// comment: r.comment,
// is_approved: r.is_approved,
// bank_code: r.bank_code,
// }));
// };
const normalizePayload = async (payload: {
type: string;
amount?: number;
mileage?: number;
comment: string;
attachment?: string | number;
}): Promise<{
type: string;
bank_code_id: number;
amount: Prisma.Decimal;
mileage: number | null;
comment: string;
attachment: number | null;
}> => {
const type = normalizeType(payload.type);
const comment = assertAndTrimComment(payload.comment);
const attachment = parseAttachmentId(payload.attachment);
// const normalizePayload = async (payload: {
// type: string;
// amount?: number;
// mileage?: number;
// comment: string;
// attachment?: string | number;
// }): Promise<{
// type: string;
// bank_code_id: number;
// amount: Prisma.Decimal;
// mileage: number | null;
// comment: string;
// attachment: number | null;
// }> => {
// const type = normalizeType(payload.type);
// const comment = assertAndTrimComment(payload.comment);
// const attachment = parseAttachmentId(payload.attachment);
const { id: bank_code_id, modifier } = await this.bankCodesResolver.findByType(type);
let amount = computeAmountDecimal(type, payload, modifier);
let mileage: number | null = null;
// const { id: bank_code_id, modifier } = await this.bankCodesResolver.findByType(type);
// let amount = computeAmountDecimal(type, payload, modifier);
// let mileage: number | null = null;
if (type === 'MILEAGE') {
mileage = Number(payload.mileage ?? 0);
if (!(mileage > 0)) {
throw new BadRequestException('Mileage required and must be > 0 for type MILEAGE');
}
// if (type === 'MILEAGE') {
// mileage = Number(payload.mileage ?? 0);
// if (!(mileage > 0)) {
// throw new BadRequestException('Mileage required and must be > 0 for type MILEAGE');
// }
const amountNumber = computeMileageAmount(mileage, modifier);
amount = new Prisma.Decimal(amountNumber);
// const amountNumber = computeMileageAmount(mileage, modifier);
// amount = new Prisma.Decimal(amountNumber);
} else {
if (!(typeof payload.amount === 'number' && payload.amount >= 0)) {
throw new BadRequestException('Amount required for non-MILEAGE expense');
}
amount = new Prisma.Decimal(payload.amount);
}
// } else {
// if (!(typeof payload.amount === 'number' && payload.amount >= 0)) {
// throw new BadRequestException('Amount required for non-MILEAGE expense');
// }
// amount = new Prisma.Decimal(payload.amount);
// }
if (attachment !== null) {
const attachment_row = await tx.attachments.findUnique({
where: { id: attachment },
select: { status: true },
});
if (!attachment_row || attachment_row.status !== 'ACTIVE') {
throw new BadRequestException('Attachment not found or inactive');
}
}
// if (attachment !== null) {
// const attachment_row = await tx.attachments.findUnique({
// where: { id: attachment },
// select: { status: true },
// });
// if (!attachment_row || attachment_row.status !== 'ACTIVE') {
// throw new BadRequestException('Attachment not found or inactive');
// }
// }
return {
type,
bank_code_id,
amount,
mileage,
comment,
attachment
};
};
// return {
// type,
// bank_code_id,
// amount,
// mileage,
// comment,
// attachment
// };
// };
const findExactOld = async (norm: {
bank_code_id: number;
amount: Prisma.Decimal;
mileage: number | null;
comment: string;
attachment: number | null;
}) => {
return tx.expenses.findFirst({
where: {
timesheet_id: id,
date: date_only,
bank_code_id: norm.bank_code_id,
amount: norm.amount,
comment: norm.comment,
attachment: norm.attachment,
...(norm.mileage !== null ? { mileage: norm.mileage } : { mileage: null }),
},
select: { id: true },
});
};
// const findExactOld = async (norm: {
// bank_code_id: number;
// amount: Prisma.Decimal;
// mileage: number | null;
// comment: string;
// attachment: number | null;
// }) => {
// return tx.expenses.findFirst({
// where: {
// timesheet_id: id,
// date: date_only,
// bank_code_id: norm.bank_code_id,
// amount: norm.amount,
// comment: norm.comment,
// attachment: norm.attachment,
// ...(norm.mileage !== null ? { mileage: norm.mileage } : { mileage: null }),
// },
// select: { id: true },
// });
// };
let action : UpsertAction;
//_____________________________________________________________________________________________
// DELETE
//_____________________________________________________________________________________________
if(old_expense && !new_expense) {
const old_norm = await normalizePayload(old_expense);
const existing = await findExactOld(old_norm);
if(!existing) {
throw new NotFoundException({
error_code: 'EXPENSE_STALE',
message: 'The expense was modified or deleted by someone else',
});
}
await tx.expenses.delete({where: { id: existing.id } });
action = 'delete';
}
//_____________________________________________________________________________________________
// CREATE
//_____________________________________________________________________________________________
else if (!old_expense && new_expense) {
const new_exp = await normalizePayload(new_expense);
await tx.expenses.create({
data: {
timesheet_id: id,
date: date_only,
bank_code_id: new_exp.bank_code_id,
amount: new_exp.amount,
mileage: new_exp.mileage,
comment: new_exp.comment,
attachment: new_exp.attachment,
is_approved: false,
},
});
action = 'create';
}
//_____________________________________________________________________________________________
// UPDATE
//_____________________________________________________________________________________________
else if(old_expense && new_expense) {
const old_norm = await normalizePayload(old_expense);
const existing = await findExactOld(old_norm);
if(!existing) {
throw new NotFoundException({
error_code: 'EXPENSE_STALE',
message: 'The expense was modified or deleted by someone else',
});
}
// let action : UpsertAction;
// //_____________________________________________________________________________________________
// // DELETE
// //_____________________________________________________________________________________________
// if(old_expense && !new_expense) {
// const old_norm = await normalizePayload(old_expense);
// const existing = await findExactOld(old_norm);
// if(!existing) {
// throw new NotFoundException({
// error_code: 'EXPENSE_STALE',
// message: 'The expense was modified or deleted by someone else',
// });
// }
// await tx.expenses.delete({where: { id: existing.id } });
// action = 'delete';
// }
// //_____________________________________________________________________________________________
// // CREATE
// //_____________________________________________________________________________________________
// else if (!old_expense && new_expense) {
// const new_exp = await normalizePayload(new_expense);
// await tx.expenses.create({
// data: {
// timesheet_id: id,
// date: date_only,
// bank_code_id: new_exp.bank_code_id,
// amount: new_exp.amount,
// mileage: new_exp.mileage,
// comment: new_exp.comment,
// attachment: new_exp.attachment,
// is_approved: false,
// },
// });
// action = 'create';
// }
// //_____________________________________________________________________________________________
// // UPDATE
// //_____________________________________________________________________________________________
// else if(old_expense && new_expense) {
// const old_norm = await normalizePayload(old_expense);
// const existing = await findExactOld(old_norm);
// if(!existing) {
// throw new NotFoundException({
// error_code: 'EXPENSE_STALE',
// message: 'The expense was modified or deleted by someone else',
// });
// }
const new_exp = await normalizePayload(new_expense);
await tx.expenses.update({
where: { id: existing.id },
data: {
bank_code_id: new_exp.bank_code_id,
amount: new_exp.amount,
mileage: new_exp.mileage,
comment: new_exp.comment,
attachment: new_exp.attachment,
},
});
action = 'update';
}
else {
throw new BadRequestException('Invalid upsert combination');
}
// const new_exp = await normalizePayload(new_expense);
// await tx.expenses.update({
// where: { id: existing.id },
// data: {
// bank_code_id: new_exp.bank_code_id,
// amount: new_exp.amount,
// mileage: new_exp.mileage,
// comment: new_exp.comment,
// attachment: new_exp.attachment,
// },
// });
// action = 'update';
// }
// else {
// throw new BadRequestException('Invalid upsert combination');
// }
const day = await loadDay();
// const day = await loadDay();
return { action, day };
});
}
}
// return { action, day };
// });
// }
// }

View File

@ -1,174 +1,171 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service";
import { DayExpensesDto as ExpenseListResponseDto, ExpenseDto } from "src/modules/timesheets/~misc_deprecated-files/timesheet-period.dto";
import { round2, toUTCDateOnly } from "src/modules/timesheets/~misc_deprecated-files/utils-helpers-others/timesheet.helpers";
import { EXPENSE_TYPES } from "src/modules/timesheets/~misc_deprecated-files/utils-helpers-others/timesheet.types";
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
// import { Injectable, NotFoundException } from "@nestjs/common";
// import { PrismaService } from "src/prisma/prisma.service";
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
@Injectable()
export class ExpensesQueryService {
constructor(
private readonly prisma: PrismaService,
private readonly employeeRepo: EmailToIdResolver,
) {}
// @Injectable()
// export class ExpensesQueryService {
// constructor(
// private readonly prisma: PrismaService,
// private readonly employeeRepo: EmailToIdResolver,
// ) {}
//fetchs all expenses for a selected employee using email, pay-period-year and number
async findExpenseListByPayPeriodAndEmail(
email: string,
year: number,
period_no: number
): Promise<ExpenseListResponseDto> {
//fetch employe_id using email
const employee_id = await this.employeeRepo.findIdByEmail(email);
if(!employee_id) throw new NotFoundException(`Employee with email: ${email} not found`);
// //fetchs all expenses for a selected employee using email, pay-period-year and number
// async findExpenseListByPayPeriodAndEmail(
// email: string,
// year: number,
// period_no: number
// ): Promise<ExpenseListResponseDto> {
// //fetch employe_id using email
// const employee_id = await this.employeeRepo.findIdByEmail(email);
// if(!employee_id) throw new NotFoundException(`Employee with email: ${email} not found`);
//fetch pay-period using year and period_no
const pay_period = await this.prisma.payPeriods.findFirst({
where: {
pay_year: year,
pay_period_no: period_no
},
select: { period_start: true, period_end: true },
});
if(!pay_period) throw new NotFoundException(`Pay period ${year}- ${period_no} not found`);
// //fetch pay-period using year and period_no
// const pay_period = await this.prisma.payPeriods.findFirst({
// where: {
// pay_year: year,
// pay_period_no: period_no
// },
// select: { period_start: true, period_end: true },
// });
// if(!pay_period) throw new NotFoundException(`Pay period ${year}- ${period_no} not found`);
const start = toUTCDateOnly(pay_period.period_start);
const end = toUTCDateOnly(pay_period.period_end);
// const start = toUTCDateOnly(pay_period.period_start);
// const end = toUTCDateOnly(pay_period.period_end);
//sets rows data
const rows = await this.prisma.expenses.findMany({
where: {
date: { gte: start, lte: end },
timesheet: { is: { employee_id } },
},
orderBy: { date: 'asc'},
select: {
amount: true,
mileage: true,
comment: true,
is_approved: true,
supervisor_comment: true,
bank_code: {select: { type: true } },
},
});
// //sets rows data
// const rows = await this.prisma.expenses.findMany({
// where: {
// date: { gte: start, lte: end },
// timesheet: { is: { employee_id } },
// },
// orderBy: { date: 'asc'},
// select: {
// amount: true,
// mileage: true,
// comment: true,
// is_approved: true,
// supervisor_comment: true,
// bank_code: {select: { type: true } },
// },
// });
//declare return values
const expenses: ExpenseDto[] = [];
let total_amount = 0;
let total_mileage = 0;
// //declare return values
// const expenses: ExpenseDto[] = [];
// let total_amount = 0;
// let total_mileage = 0;
//set rows
for(const row of rows) {
const type = (row.bank_code?.type ?? '').toUpperCase();
const amount = round2(Number(row.amount ?? 0));
const mileage = round2(Number(row.mileage ?? 0));
// //set rows
// for(const row of rows) {
// const type = (row.bank_code?.type ?? '').toUpperCase();
// const amount = round2(Number(row.amount ?? 0));
// const mileage = round2(Number(row.mileage ?? 0));
if(type === EXPENSE_TYPES.MILEAGE) {
total_mileage += mileage;
} else {
total_amount += amount;
}
// if(type === EXPENSE_TYPES.MILEAGE) {
// total_mileage += mileage;
// } else {
// total_amount += amount;
// }
//fills rows array
expenses.push({
type,
amount,
mileage,
comment: row.comment ?? '',
is_approved: row.is_approved ?? false,
supervisor_comment: row.supervisor_comment ?? '',
});
}
// //fills rows array
// expenses.push({
// type,
// amount,
// mileage,
// comment: row.comment ?? '',
// is_approved: row.is_approved ?? false,
// supervisor_comment: row.supervisor_comment ?? '',
// });
// }
return {
expenses,
total_expense: round2(total_amount),
total_mileage: round2(total_mileage),
};
}
// return {
// expenses,
// total_expense: round2(total_amount),
// total_mileage: round2(total_mileage),
// };
// }
//_____________________________________________________________________________________________
// Deprecated or unused methods
//_____________________________________________________________________________________________
// //_____________________________________________________________________________________________
// // Deprecated or unused methods
// //_____________________________________________________________________________________________
// async create(dto: CreateExpenseDto): Promise<Expenses> {
// const { timesheet_id, bank_code_id, date, amount:rawAmount,
// comment, is_approved,supervisor_comment} = dto;
// //fetches type and modifier
// const bank_code = await this.prisma.bankCodes.findUnique({
// where: { id: bank_code_id },
// select: { type: true, modifier: true },
// });
// if(!bank_code) throw new NotFoundException(`bank_code #${bank_code_id} not found`);
// // async create(dto: CreateExpenseDto): Promise<Expenses> {
// // const { timesheet_id, bank_code_id, date, amount:rawAmount,
// // comment, is_approved,supervisor_comment} = dto;
// // //fetches type and modifier
// // const bank_code = await this.prisma.bankCodes.findUnique({
// // where: { id: bank_code_id },
// // select: { type: true, modifier: true },
// // });
// // if(!bank_code) throw new NotFoundException(`bank_code #${bank_code_id} not found`);
// //if mileage -> service, otherwise the ratio is amount:1
// let final_amount: number;
// if(bank_code.type === 'mileage') {
// final_amount = await this.mileageService.calculateReimbursement(rawAmount, bank_code_id);
// }else {
// final_amount = parseFloat( (rawAmount * bank_code.modifier).toFixed(2));
// }
// // //if mileage -> service, otherwise the ratio is amount:1
// // let final_amount: number;
// // if(bank_code.type === 'mileage') {
// // final_amount = await this.mileageService.calculateReimbursement(rawAmount, bank_code_id);
// // }else {
// // final_amount = parseFloat( (rawAmount * bank_code.modifier).toFixed(2));
// // }
// return this.prisma.expenses.create({
// data: {
// timesheet_id,
// bank_code_id,
// date,
// amount: final_amount,
// comment,
// is_approved,
// supervisor_comment
// },
// include: { timesheet: { include: { employee: { include: { user: true }}}},
// bank_code: true,
// },
// })
// }
// // return this.prisma.expenses.create({
// // data: {
// // timesheet_id,
// // bank_code_id,
// // date,
// // amount: final_amount,
// // comment,
// // is_approved,
// // supervisor_comment
// // },
// // include: { timesheet: { include: { employee: { include: { user: true }}}},
// // bank_code: true,
// // },
// // })
// // }
// async findAll(filters: SearchExpensesDto): Promise<Expenses[]> {
// const where = buildPrismaWhere(filters);
// const expenses = await this.prisma.expenses.findMany({ where })
// return expenses;
// }
// // async findAll(filters: SearchExpensesDto): Promise<Expenses[]> {
// // const where = buildPrismaWhere(filters);
// // const expenses = await this.prisma.expenses.findMany({ where })
// // return expenses;
// // }
// async findOne(id: number): Promise<Expenses> {
// const expense = await this.prisma.expenses.findUnique({
// where: { id },
// include: { timesheet: { include: { employee: { include: { user:true } } } },
// bank_code: true,
// },
// });
// if (!expense) {
// throw new NotFoundException(`Expense #${id} not found`);
// }
// return expense;
// }
// // async findOne(id: number): Promise<Expenses> {
// // const expense = await this.prisma.expenses.findUnique({
// // where: { id },
// // include: { timesheet: { include: { employee: { include: { user:true } } } },
// // bank_code: true,
// // },
// // });
// // if (!expense) {
// // throw new NotFoundException(`Expense #${id} not found`);
// // }
// // return expense;
// // }
// async update(id: number, dto: UpdateExpenseDto): Promise<Expenses> {
// await this.findOne(id);
// const { timesheet_id, bank_code_id, date, amount,
// comment, is_approved, supervisor_comment} = dto;
// return this.prisma.expenses.update({
// where: { id },
// data: {
// ...(timesheet_id !== undefined && { timesheet_id}),
// ...(bank_code_id !== undefined && { bank_code_id }),
// ...(date !== undefined && { date }),
// ...(amount !== undefined && { amount }),
// ...(comment !== undefined && { comment }),
// ...(is_approved !== undefined && { is_approved }),
// ...(supervisor_comment !== undefined && { supervisor_comment }),
// },
// include: { timesheet: { include: { employee: { include: { user: true } } } },
// bank_code: true,
// },
// });
// }
// // async update(id: number, dto: UpdateExpenseDto): Promise<Expenses> {
// // await this.findOne(id);
// // const { timesheet_id, bank_code_id, date, amount,
// // comment, is_approved, supervisor_comment} = dto;
// // return this.prisma.expenses.update({
// // where: { id },
// // data: {
// // ...(timesheet_id !== undefined && { timesheet_id}),
// // ...(bank_code_id !== undefined && { bank_code_id }),
// // ...(date !== undefined && { date }),
// // ...(amount !== undefined && { amount }),
// // ...(comment !== undefined && { comment }),
// // ...(is_approved !== undefined && { is_approved }),
// // ...(supervisor_comment !== undefined && { supervisor_comment }),
// // },
// // include: { timesheet: { include: { employee: { include: { user: true } } } },
// // bank_code: true,
// // },
// // });
// // }
// async remove(id: number): Promise<Expenses> {
// await this.findOne(id);
// return this.prisma.expenses.delete({ where: { id } });
// }
// // async remove(id: number): Promise<Expenses> {
// // await this.findOne(id);
// // return this.prisma.expenses.delete({ where: { id } });
// // }
}
// }

View File

@ -1,30 +1,30 @@
import { Body, Controller, Post } from "@nestjs/common";
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
import { LeaveRequestsService } from "../services/leave-request.service";
import { UpsertLeaveRequestDto } from "../dtos/upsert-leave-request.dto";
import { LeaveTypes } from "@prisma/client";
// import { Body, Controller, Post } from "@nestjs/common";
// import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
// import { LeaveRequestsService } from "../services/leave-request.service";
// import { UpsertLeaveRequestDto } from "../dtos/upsert-leave-request.dto";
// import { LeaveTypes } from "@prisma/client";
@ApiTags('Leave Requests')
@ApiBearerAuth('access-token')
// @UseGuards()
@Controller('leave-requests')
export class LeaveRequestController {
constructor(private readonly leave_service: LeaveRequestsService){}
// @ApiTags('Leave Requests')
// @ApiBearerAuth('access-token')
// // @UseGuards()
// @Controller('leave-requests')
// export class LeaveRequestController {
// constructor(private readonly leave_service: LeaveRequestsService){}
@Post('upsert')
async upsertLeaveRequest(@Body() dto: UpsertLeaveRequestDto) {
const { action, leave_requests } = await this.leave_service.handle(dto);
return { action, leave_requests };
}q
// @Post('upsert')
// async upsertLeaveRequest(@Body() dto: UpsertLeaveRequestDto) {
// const { action, leave_requests } = await this.leave_service.handle(dto);
// return { action, leave_requests };
// }q
//TODO:
/*
@Get('archive')
findAllArchived(){...}
// //TODO:
// /*
// @Get('archive')
// findAllArchived(){...}
@Get('archive/:id')
findOneArchived(id){...}
*/
// @Get('archive/:id')
// findOneArchived(id){...}
// */
}
// }

View File

@ -1,29 +1,29 @@
import { PrismaService } from "src/prisma/prisma.service";
import { LeaveRequestController } from "./controllers/leave-requests.controller";
import { HolidayLeaveRequestsService } from "./services/holiday-leave-requests.service";
import { Module } from "@nestjs/common";
import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module";
import { VacationLeaveRequestsService } from "./services/vacation-leave-requests.service";
import { SickLeaveRequestsService } from "./services/sick-leave-requests.service";
import { LeaveRequestsService } from "./services/leave-request.service";
import { ShiftsModule } from "../shifts/shifts.module";
import { LeaveRequestsUtils } from "./utils/leave-request.util";
import { SharedModule } from "../shared/shared.module";
// import { PrismaService } from "src/prisma/prisma.service";
// import { LeaveRequestController } from "./controllers/leave-requests.controller";
// import { HolidayLeaveRequestsService } from "./services/holiday-leave-requests.service";
// import { Module } from "@nestjs/common";
// import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module";
// import { VacationLeaveRequestsService } from "./services/vacation-leave-requests.service";
// import { SickLeaveRequestsService } from "./services/sick-leave-requests.service";
// import { LeaveRequestsService } from "./services/leave-request.service";
// import { ShiftsModule } from "../shifts/shifts.module";
// import { LeaveRequestsUtils } from "./utils/leave-request.util";
// import { SharedModule } from "../shared/shared.module";
@Module({
imports: [BusinessLogicsModule, ShiftsModule, SharedModule],
controllers: [LeaveRequestController],
providers: [
VacationLeaveRequestsService,
SickLeaveRequestsService,
HolidayLeaveRequestsService,
LeaveRequestsService,
PrismaService,
LeaveRequestsUtils,
],
exports: [
LeaveRequestsService,
],
})
// @Module({
// imports: [BusinessLogicsModule, ShiftsModule, SharedModule],
// controllers: [LeaveRequestController],
// providers: [
// VacationLeaveRequestsService,
// SickLeaveRequestsService,
// HolidayLeaveRequestsService,
// LeaveRequestsService,
// PrismaService,
// LeaveRequestsUtils,
// ],
// exports: [
// LeaveRequestsService,
// ],
// })
export class LeaveRequestsModule {}
// export class LeaveRequestsModule {}

View File

@ -1,78 +1,78 @@
import { UpsertLeaveRequestDto, UpsertResult } from '../dtos/upsert-leave-request.dto';
import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto';
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { LeaveApprovalStatus, LeaveTypes } from '@prisma/client';
import { HolidayService } from 'src/modules/business-logics/services/holiday.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { mapRowToView } from '../mappers/leave-requests.mapper';
import { leaveRequestsSelect } from '../utils/leave-requests.select';
import { LeaveRequestsUtils} from '../utils/leave-request.util';
import { normalizeDates, toDateOnly } from 'src/modules/shared/helpers/date-time.helpers';
import { BankCodesResolver } from 'src/modules/shared/utils/resolve-bank-type-id.utils';
import { EmailToIdResolver } from 'src/modules/shared/utils/resolve-email-id.utils';
// import { UpsertLeaveRequestDto, UpsertResult } from '../dtos/upsert-leave-request.dto';
// import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto';
// import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
// import { LeaveApprovalStatus, LeaveTypes } from '@prisma/client';
// import { HolidayService } from 'src/modules/business-logics/services/holiday.service';
// import { PrismaService } from 'src/prisma/prisma.service';
// import { mapRowToView } from '../mappers/leave-requests.mapper';
// import { leaveRequestsSelect } from '../utils/leave-requests.select';
// import { LeaveRequestsUtils} from '../utils/leave-request.util';
// import { normalizeDates, toDateOnly } from 'src/modules/shared/helpers/date-time.helpers';
// import { BankCodesResolver } from 'src/modules/shared/utils/resolve-bank-type-id.utils';
// import { EmailToIdResolver } from 'src/modules/shared/utils/resolve-email-id.utils';
@Injectable()
export class HolidayLeaveRequestsService {
constructor(
private readonly prisma: PrismaService,
private readonly holidayService: HolidayService,
private readonly leaveUtils: LeaveRequestsUtils,
private readonly emailResolver: EmailToIdResolver,
private readonly typeResolver: BankCodesResolver,
) {}
// @Injectable()
// export class HolidayLeaveRequestsService {
// constructor(
// private readonly prisma: PrismaService,
// private readonly holidayService: HolidayService,
// private readonly leaveUtils: LeaveRequestsUtils,
// private readonly emailResolver: EmailToIdResolver,
// private readonly typeResolver: BankCodesResolver,
// ) {}
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
const email = dto.email.trim();
const employee_id = await this.emailResolver.findIdByEmail(email);
const bank_code = await this.typeResolver.findByType(LeaveTypes.HOLIDAY);
if(!bank_code) throw new NotFoundException(`bank_code not found`);
const dates = normalizeDates(dto.dates);
if (!dates.length) throw new BadRequestException('Dates array must not be empty');
// async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
// const email = dto.email.trim();
// const employee_id = await this.emailResolver.findIdByEmail(email);
// const bank_code = await this.typeResolver.findByType(LeaveTypes.HOLIDAY);
// if(!bank_code) throw new NotFoundException(`bank_code not found`);
// const dates = normalizeDates(dto.dates);
// if (!dates.length) throw new BadRequestException('Dates array must not be empty');
const created: LeaveRequestViewDto[] = [];
// const created: LeaveRequestViewDto[] = [];
for (const iso_date of dates) {
const date = toDateOnly(iso_date);
// for (const iso_date of dates) {
// const date = toDateOnly(iso_date);
const existing = await this.prisma.leaveRequests.findUnique({
where: {
leave_per_employee_date: {
employee_id: employee_id,
leave_type: LeaveTypes.HOLIDAY,
date,
},
},
select: { id: true },
});
if (existing) {
throw new BadRequestException(`Holiday request already exists for ${iso_date}`);
}
// const existing = await this.prisma.leaveRequests.findUnique({
// where: {
// leave_per_employee_date: {
// employee_id: employee_id,
// leave_type: LeaveTypes.HOLIDAY,
// date,
// },
// },
// select: { id: true },
// });
// if (existing) {
// throw new BadRequestException(`Holiday request already exists for ${iso_date}`);
// }
const payable = await this.holidayService.calculateHolidayPay(email, date, bank_code.modifier);
const row = await this.prisma.leaveRequests.create({
data: {
employee_id: employee_id,
bank_code_id: bank_code.id,
leave_type: LeaveTypes.HOLIDAY,
date,
comment: dto.comment ?? '',
requested_hours: dto.requested_hours ?? 8,
payable_hours: payable,
approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING,
},
select: leaveRequestsSelect,
});
// const payable = await this.holidayService.calculateHolidayPay(email, date, bank_code.modifier);
// const row = await this.prisma.leaveRequests.create({
// data: {
// employee_id: employee_id,
// bank_code_id: bank_code.id,
// leave_type: LeaveTypes.HOLIDAY,
// date,
// comment: dto.comment ?? '',
// requested_hours: dto.requested_hours ?? 8,
// payable_hours: payable,
// approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING,
// },
// select: leaveRequestsSelect,
// });
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
await this.leaveUtils.syncShift(email, employee_id, iso_date, hours,LeaveTypes.HOLIDAY, row.comment);
}
// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
// if (row.approval_status === LeaveApprovalStatus.APPROVED) {
// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours,LeaveTypes.HOLIDAY, row.comment);
// }
created.push({ ...mapRowToView(row), action: 'create' });
}
// created.push({ ...mapRowToView(row), action: 'create' });
// }
return { action: 'create', leave_requests: created };
}
}
// return { action: 'create', leave_requests: created };
// }
// }

View File

@ -1,248 +1,248 @@
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
import { roundToQuarterHour } from "src/common/utils/date-utils";
import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
import { mapRowToView } from "../mappers/leave-requests.mapper";
import { leaveRequestsSelect } from "../utils/leave-requests.select";
import { HolidayLeaveRequestsService } from "./holiday-leave-requests.service";
import { SickLeaveRequestsService } from "./sick-leave-requests.service";
import { VacationLeaveRequestsService } from "./vacation-leave-requests.service";
import { HolidayService } from "src/modules/business-logics/services/holiday.service";
import { SickLeaveService } from "src/modules/business-logics/services/sick-leave.service";
import { VacationService } from "src/modules/business-logics/services/vacation.service";
import { PrismaService } from "src/prisma/prisma.service";
import { LeaveRequestsUtils } from "../utils/leave-request.util";
import { normalizeDates, toDateOnly, toISODateKey } from "src/modules/shared/helpers/date-time.helpers";
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
// import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
// import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
// import { roundToQuarterHour } from "src/common/utils/date-utils";
// import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
// import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
// import { mapRowToView } from "../mappers/leave-requests.mapper";
// import { leaveRequestsSelect } from "../utils/leave-requests.select";
// import { HolidayLeaveRequestsService } from "./holiday-leave-requests.service";
// import { SickLeaveRequestsService } from "./sick-leave-requests.service";
// import { VacationLeaveRequestsService } from "./vacation-leave-requests.service";
// import { HolidayService } from "src/modules/business-logics/services/holiday.service";
// import { SickLeaveService } from "src/modules/business-logics/services/sick-leave.service";
// import { VacationService } from "src/modules/business-logics/services/vacation.service";
// import { PrismaService } from "src/prisma/prisma.service";
// import { LeaveRequestsUtils } from "../utils/leave-request.util";
// import { normalizeDates, toDateOnly, toISODateKey } from "src/modules/shared/helpers/date-time.helpers";
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
@Injectable()
export class LeaveRequestsService {
constructor(
private readonly prisma: PrismaService,
private readonly holidayLeaveService: HolidayLeaveRequestsService,
private readonly holidayService: HolidayService,
private readonly sickLogic: SickLeaveService,
private readonly sickLeaveService: SickLeaveRequestsService,
private readonly vacationLeaveService: VacationLeaveRequestsService,
private readonly vacationLogic: VacationService,
private readonly leaveUtils: LeaveRequestsUtils,
private readonly emailResolver: EmailToIdResolver,
private readonly typeResolver: BankCodesResolver,
) {}
// @Injectable()
// export class LeaveRequestsService {
// constructor(
// private readonly prisma: PrismaService,
// private readonly holidayLeaveService: HolidayLeaveRequestsService,
// private readonly holidayService: HolidayService,
// private readonly sickLogic: SickLeaveService,
// private readonly sickLeaveService: SickLeaveRequestsService,
// private readonly vacationLeaveService: VacationLeaveRequestsService,
// private readonly vacationLogic: VacationService,
// private readonly leaveUtils: LeaveRequestsUtils,
// private readonly emailResolver: EmailToIdResolver,
// private readonly typeResolver: BankCodesResolver,
// ) {}
//handle distribution to the right service according to the selected type and action
async handle(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
switch (dto.type) {
case LeaveTypes.HOLIDAY:
if( dto.action === 'create'){
return this.holidayLeaveService.create(dto);
} else if (dto.action === 'update') {
return this.update(dto, LeaveTypes.HOLIDAY);
} else if (dto.action === 'delete'){
return this.delete(dto, LeaveTypes.HOLIDAY);
}
case LeaveTypes.VACATION:
if( dto.action === 'create'){
return this.vacationLeaveService.create(dto);
} else if (dto.action === 'update') {
return this.update(dto, LeaveTypes.VACATION);
} else if (dto.action === 'delete'){
return this.delete(dto, LeaveTypes.VACATION);
}
case LeaveTypes.SICK:
if( dto.action === 'create'){
return this.sickLeaveService.create(dto);
} else if (dto.action === 'update') {
return this.update(dto, LeaveTypes.SICK);
} else if (dto.action === 'delete'){
return this.delete(dto, LeaveTypes.SICK);
}
default:
throw new BadRequestException(`Unsupported leave type: ${dto.type} or action: ${dto.action}`);
}
}
// //handle distribution to the right service according to the selected type and action
// async handle(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
// switch (dto.type) {
// case LeaveTypes.HOLIDAY:
// if( dto.action === 'create'){
// return this.holidayLeaveService.create(dto);
// } else if (dto.action === 'update') {
// return this.update(dto, LeaveTypes.HOLIDAY);
// } else if (dto.action === 'delete'){
// return this.delete(dto, LeaveTypes.HOLIDAY);
// }
// case LeaveTypes.VACATION:
// if( dto.action === 'create'){
// return this.vacationLeaveService.create(dto);
// } else if (dto.action === 'update') {
// return this.update(dto, LeaveTypes.VACATION);
// } else if (dto.action === 'delete'){
// return this.delete(dto, LeaveTypes.VACATION);
// }
// case LeaveTypes.SICK:
// if( dto.action === 'create'){
// return this.sickLeaveService.create(dto);
// } else if (dto.action === 'update') {
// return this.update(dto, LeaveTypes.SICK);
// } else if (dto.action === 'delete'){
// return this.delete(dto, LeaveTypes.SICK);
// }
// default:
// throw new BadRequestException(`Unsupported leave type: ${dto.type} or action: ${dto.action}`);
// }
// }
async delete(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
const email = dto.email.trim();
const dates = normalizeDates(dto.dates);
const employee_id = await this.emailResolver.findIdByEmail(email);
if (!dates.length) throw new BadRequestException("Dates array must not be empty");
// async delete(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
// const email = dto.email.trim();
// const dates = normalizeDates(dto.dates);
// const employee_id = await this.emailResolver.findIdByEmail(email);
// if (!dates.length) throw new BadRequestException("Dates array must not be empty");
const rows = await this.prisma.leaveRequests.findMany({
where: {
employee_id: employee_id,
leave_type: type,
date: { in: dates.map((d) => toDateOnly(d)) },
},
select: leaveRequestsSelect,
});
// const rows = await this.prisma.leaveRequests.findMany({
// where: {
// employee_id: employee_id,
// leave_type: type,
// date: { in: dates.map((d) => toDateOnly(d)) },
// },
// select: leaveRequestsSelect,
// });
if (rows.length !== dates.length) {
const missing = dates.filter((isoDate) => !rows.some((row) => toISODateKey(row.date) === isoDate));
throw new NotFoundException(`No Leave request found for: ${missing.join(", ")}`);
}
// if (rows.length !== dates.length) {
// const missing = dates.filter((isoDate) => !rows.some((row) => toISODateKey(row.date) === isoDate));
// throw new NotFoundException(`No Leave request found for: ${missing.join(", ")}`);
// }
for (const row of rows) {
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
const iso = toISODateKey(row.date);
await this.leaveUtils.removeShift(email, employee_id, iso, type);
}
}
// for (const row of rows) {
// if (row.approval_status === LeaveApprovalStatus.APPROVED) {
// const iso = toISODateKey(row.date);
// await this.leaveUtils.removeShift(email, employee_id, iso, type);
// }
// }
await this.prisma.leaveRequests.deleteMany({
where: { id: { in: rows.map((row) => row.id) } },
});
// await this.prisma.leaveRequests.deleteMany({
// where: { id: { in: rows.map((row) => row.id) } },
// });
const deleted = rows.map((row) => ({ ...mapRowToView(row), action: "delete" as const }));
return { action: "delete", leave_requests: deleted };
}
// const deleted = rows.map((row) => ({ ...mapRowToView(row), action: "delete" as const }));
// return { action: "delete", leave_requests: deleted };
// }
async update(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
const email = dto.email.trim();
const employee_id = await this.emailResolver.findIdByEmail(email);
const bank_code = await this.typeResolver.findByType(type);
if(!bank_code) throw new NotFoundException(`bank_code not found`);
const modifier = Number(bank_code.modifier ?? 1);
const dates = normalizeDates(dto.dates);
if (!dates.length) {
throw new BadRequestException("Dates array must not be empty");
}
// async update(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
// const email = dto.email.trim();
// const employee_id = await this.emailResolver.findIdByEmail(email);
// const bank_code = await this.typeResolver.findByType(type);
// if(!bank_code) throw new NotFoundException(`bank_code not found`);
// const modifier = Number(bank_code.modifier ?? 1);
// const dates = normalizeDates(dto.dates);
// if (!dates.length) {
// throw new BadRequestException("Dates array must not be empty");
// }
const entries = await Promise.all(
dates.map(async (iso_date) => {
const date = toDateOnly(iso_date);
const existing = await this.prisma.leaveRequests.findUnique({
where: {
leave_per_employee_date: {
employee_id: employee_id,
leave_type: type,
date,
},
},
select: leaveRequestsSelect,
});
if (!existing) throw new NotFoundException(`No Leave request found for ${iso_date}`);
return { iso_date, date, existing };
}),
);
// const entries = await Promise.all(
// dates.map(async (iso_date) => {
// const date = toDateOnly(iso_date);
// const existing = await this.prisma.leaveRequests.findUnique({
// where: {
// leave_per_employee_date: {
// employee_id: employee_id,
// leave_type: type,
// date,
// },
// },
// select: leaveRequestsSelect,
// });
// if (!existing) throw new NotFoundException(`No Leave request found for ${iso_date}`);
// return { iso_date, date, existing };
// }),
// );
const updated: LeaveRequestViewDto[] = [];
// const updated: LeaveRequestViewDto[] = [];
if (type === LeaveTypes.SICK) {
const firstExisting = entries[0].existing;
const fallbackRequested =
firstExisting.requested_hours !== null && firstExisting.requested_hours !== undefined
? Number(firstExisting.requested_hours)
: 8;
const requested_hours_per_day = dto.requested_hours ?? fallbackRequested;
const reference_date = entries.reduce(
(latest, entry) => (entry.date > latest ? entry.date : latest),
entries[0].date,
);
const total_payable_hours = await this.sickLogic.calculateSickLeavePay(
employee_id,
reference_date,
entries.length,
requested_hours_per_day,
modifier,
);
let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours));
const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier);
// if (type === LeaveTypes.SICK) {
// const firstExisting = entries[0].existing;
// const fallbackRequested =
// firstExisting.requested_hours !== null && firstExisting.requested_hours !== undefined
// ? Number(firstExisting.requested_hours)
// : 8;
// const requested_hours_per_day = dto.requested_hours ?? fallbackRequested;
// const reference_date = entries.reduce(
// (latest, entry) => (entry.date > latest ? entry.date : latest),
// entries[0].date,
// );
// const total_payable_hours = await this.sickLogic.calculateSickLeavePay(
// employee_id,
// reference_date,
// entries.length,
// requested_hours_per_day,
// modifier,
// );
// let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours));
// const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier);
for (const { iso_date, existing } of entries) {
const previous_status = existing.approval_status;
const payable = Math.min(remaining_payable_hours, daily_payable_cap);
const payable_rounded = roundToQuarterHour(Math.max(0, payable));
remaining_payable_hours = roundToQuarterHour(
Math.max(0, remaining_payable_hours - payable_rounded),
);
// for (const { iso_date, existing } of entries) {
// const previous_status = existing.approval_status;
// const payable = Math.min(remaining_payable_hours, daily_payable_cap);
// const payable_rounded = roundToQuarterHour(Math.max(0, payable));
// remaining_payable_hours = roundToQuarterHour(
// Math.max(0, remaining_payable_hours - payable_rounded),
// );
const row = await this.prisma.leaveRequests.update({
where: { id: existing.id },
data: {
comment: dto.comment ?? existing.comment,
requested_hours: requested_hours_per_day,
payable_hours: payable_rounded,
bank_code_id: bank_code.id,
approval_status: dto.approval_status ?? existing.approval_status,
},
select: leaveRequestsSelect,
});
// const row = await this.prisma.leaveRequests.update({
// where: { id: existing.id },
// data: {
// comment: dto.comment ?? existing.comment,
// requested_hours: requested_hours_per_day,
// payable_hours: payable_rounded,
// bank_code_id: bank_code.id,
// approval_status: dto.approval_status ?? existing.approval_status,
// },
// select: leaveRequestsSelect,
// });
const was_approved = previous_status === LeaveApprovalStatus.APPROVED;
const is_approved = row.approval_status === LeaveApprovalStatus.APPROVED;
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
// const was_approved = previous_status === LeaveApprovalStatus.APPROVED;
// const is_approved = row.approval_status === LeaveApprovalStatus.APPROVED;
// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
if (!was_approved && is_approved) {
await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
} else if (was_approved && !is_approved) {
await this.leaveUtils.removeShift(email, employee_id, iso_date, type);
} else if (was_approved && is_approved) {
await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
}
updated.push({ ...mapRowToView(row), action: "update" });
}
return { action: "update", leave_requests: updated };
}
// if (!was_approved && is_approved) {
// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
// } else if (was_approved && !is_approved) {
// await this.leaveUtils.removeShift(email, employee_id, iso_date, type);
// } else if (was_approved && is_approved) {
// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
// }
// updated.push({ ...mapRowToView(row), action: "update" });
// }
// return { action: "update", leave_requests: updated };
// }
for (const { iso_date, date, existing } of entries) {
const previous_status = existing.approval_status;
const fallbackRequested =
existing.requested_hours !== null && existing.requested_hours !== undefined
? Number(existing.requested_hours)
: 8;
const requested_hours = dto.requested_hours ?? fallbackRequested;
// for (const { iso_date, date, existing } of entries) {
// const previous_status = existing.approval_status;
// const fallbackRequested =
// existing.requested_hours !== null && existing.requested_hours !== undefined
// ? Number(existing.requested_hours)
// : 8;
// const requested_hours = dto.requested_hours ?? fallbackRequested;
let payable: number;
switch (type) {
case LeaveTypes.HOLIDAY:
payable = await this.holidayService.calculateHolidayPay(email, date, modifier);
break;
case LeaveTypes.VACATION: {
const days_requested = requested_hours / 8;
payable = await this.vacationLogic.calculateVacationPay(
employee_id,
date,
Math.max(0, days_requested),
modifier,
);
break;
}
default:
payable = existing.payable_hours !== null && existing.payable_hours !== undefined
? Number(existing.payable_hours)
: requested_hours;
}
// let payable: number;
// switch (type) {
// case LeaveTypes.HOLIDAY:
// payable = await this.holidayService.calculateHolidayPay(email, date, modifier);
// break;
// case LeaveTypes.VACATION: {
// const days_requested = requested_hours / 8;
// payable = await this.vacationLogic.calculateVacationPay(
// employee_id,
// date,
// Math.max(0, days_requested),
// modifier,
// );
// break;
// }
// default:
// payable = existing.payable_hours !== null && existing.payable_hours !== undefined
// ? Number(existing.payable_hours)
// : requested_hours;
// }
const row = await this.prisma.leaveRequests.update({
where: { id: existing.id },
data: {
requested_hours,
comment: dto.comment ?? existing.comment,
payable_hours: payable,
bank_code_id: bank_code.id,
approval_status: dto.approval_status ?? existing.approval_status,
},
select: leaveRequestsSelect,
});
// const row = await this.prisma.leaveRequests.update({
// where: { id: existing.id },
// data: {
// requested_hours,
// comment: dto.comment ?? existing.comment,
// payable_hours: payable,
// bank_code_id: bank_code.id,
// approval_status: dto.approval_status ?? existing.approval_status,
// },
// select: leaveRequestsSelect,
// });
const was_approved = previous_status === LeaveApprovalStatus.APPROVED;
const is_approved = row.approval_status === LeaveApprovalStatus.APPROVED;
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
// const was_approved = previous_status === LeaveApprovalStatus.APPROVED;
// const is_approved = row.approval_status === LeaveApprovalStatus.APPROVED;
// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
if (!was_approved && is_approved) {
await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
} else if (was_approved && !is_approved) {
await this.leaveUtils.removeShift(email, employee_id, iso_date, type);
} else if (was_approved && is_approved) {
await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
}
updated.push({ ...mapRowToView(row), action: "update" });
}
return { action: "update", leave_requests: updated };
}
}
// if (!was_approved && is_approved) {
// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
// } else if (was_approved && !is_approved) {
// await this.leaveUtils.removeShift(email, employee_id, iso_date, type);
// } else if (was_approved && is_approved) {
// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
// }
// updated.push({ ...mapRowToView(row), action: "update" });
// }
// return { action: "update", leave_requests: updated };
// }
// }

View File

@ -1,98 +1,98 @@
import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
import { leaveRequestsSelect } from "../utils/leave-requests.select";
import { mapRowToView } from "../mappers/leave-requests.mapper";
import { PrismaService } from "src/prisma/prisma.service";
import { SickLeaveService } from "src/modules/business-logics/services/sick-leave.service";
import { roundToQuarterHour } from "src/common/utils/date-utils";
import { LeaveRequestsUtils } from "../utils/leave-request.util";
import { normalizeDates, toDateOnly } from "src/modules/shared/helpers/date-time.helpers";
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
// import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
// import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
// import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
// import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
// import { leaveRequestsSelect } from "../utils/leave-requests.select";
// import { mapRowToView } from "../mappers/leave-requests.mapper";
// import { PrismaService } from "src/prisma/prisma.service";
// import { SickLeaveService } from "src/modules/business-logics/services/sick-leave.service";
// import { roundToQuarterHour } from "src/common/utils/date-utils";
// import { LeaveRequestsUtils } from "../utils/leave-request.util";
// import { normalizeDates, toDateOnly } from "src/modules/shared/helpers/date-time.helpers";
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
@Injectable()
export class SickLeaveRequestsService {
constructor(
private readonly prisma: PrismaService,
private readonly sickService: SickLeaveService,
private readonly leaveUtils: LeaveRequestsUtils,
private readonly emailResolver: EmailToIdResolver,
private readonly typeResolver: BankCodesResolver,
) {}
// @Injectable()
// export class SickLeaveRequestsService {
// constructor(
// private readonly prisma: PrismaService,
// private readonly sickService: SickLeaveService,
// private readonly leaveUtils: LeaveRequestsUtils,
// private readonly emailResolver: EmailToIdResolver,
// private readonly typeResolver: BankCodesResolver,
// ) {}
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
const email = dto.email.trim();
const employee_id = await this.emailResolver.findIdByEmail(email);
const bank_code = await this.typeResolver.findByType(LeaveTypes.SICK);
if(!bank_code) throw new NotFoundException(`bank_code not found`);
// async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
// const email = dto.email.trim();
// const employee_id = await this.emailResolver.findIdByEmail(email);
// const bank_code = await this.typeResolver.findByType(LeaveTypes.SICK);
// if(!bank_code) throw new NotFoundException(`bank_code not found`);
const modifier = bank_code.modifier ?? 1;
const dates = normalizeDates(dto.dates);
if (!dates.length) throw new BadRequestException("Dates array must not be empty");
const requested_hours_per_day = dto.requested_hours ?? 8;
// const modifier = bank_code.modifier ?? 1;
// const dates = normalizeDates(dto.dates);
// if (!dates.length) throw new BadRequestException("Dates array must not be empty");
// const requested_hours_per_day = dto.requested_hours ?? 8;
const entries = dates.map((iso) => ({ iso, date: toDateOnly(iso) }));
const reference_date = entries.reduce(
(latest, entry) => (entry.date > latest ? entry.date : latest),
entries[0].date,
);
const total_payable_hours = await this.sickService.calculateSickLeavePay(
employee_id,
reference_date,
entries.length,
requested_hours_per_day,
modifier,
);
let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours));
const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier);
// const entries = dates.map((iso) => ({ iso, date: toDateOnly(iso) }));
// const reference_date = entries.reduce(
// (latest, entry) => (entry.date > latest ? entry.date : latest),
// entries[0].date,
// );
// const total_payable_hours = await this.sickService.calculateSickLeavePay(
// employee_id,
// reference_date,
// entries.length,
// requested_hours_per_day,
// modifier,
// );
// let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours));
// const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier);
const created: LeaveRequestViewDto[] = [];
// const created: LeaveRequestViewDto[] = [];
for (const { iso, date } of entries) {
const existing = await this.prisma.leaveRequests.findUnique({
where: {
leave_per_employee_date: {
employee_id: employee_id,
leave_type: LeaveTypes.SICK,
date,
},
},
select: { id: true },
});
if (existing) {
throw new BadRequestException(`Sick request already exists for ${iso}`);
}
// for (const { iso, date } of entries) {
// const existing = await this.prisma.leaveRequests.findUnique({
// where: {
// leave_per_employee_date: {
// employee_id: employee_id,
// leave_type: LeaveTypes.SICK,
// date,
// },
// },
// select: { id: true },
// });
// if (existing) {
// throw new BadRequestException(`Sick request already exists for ${iso}`);
// }
const payable = Math.min(remaining_payable_hours, daily_payable_cap);
const payable_rounded = roundToQuarterHour(Math.max(0, payable));
remaining_payable_hours = roundToQuarterHour(
Math.max(0, remaining_payable_hours - payable_rounded),
);
// const payable = Math.min(remaining_payable_hours, daily_payable_cap);
// const payable_rounded = roundToQuarterHour(Math.max(0, payable));
// remaining_payable_hours = roundToQuarterHour(
// Math.max(0, remaining_payable_hours - payable_rounded),
// );
const row = await this.prisma.leaveRequests.create({
data: {
employee_id: employee_id,
bank_code_id: bank_code.id,
leave_type: LeaveTypes.SICK,
comment: dto.comment ?? "",
requested_hours: requested_hours_per_day,
payable_hours: payable_rounded,
approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING,
date,
},
select: leaveRequestsSelect,
});
// const row = await this.prisma.leaveRequests.create({
// data: {
// employee_id: employee_id,
// bank_code_id: bank_code.id,
// leave_type: LeaveTypes.SICK,
// comment: dto.comment ?? "",
// requested_hours: requested_hours_per_day,
// payable_hours: payable_rounded,
// approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING,
// date,
// },
// select: leaveRequestsSelect,
// });
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
await this.leaveUtils.syncShift(email, employee_id, iso, hours,LeaveTypes.SICK, row.comment);
}
// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
// if (row.approval_status === LeaveApprovalStatus.APPROVED) {
// await this.leaveUtils.syncShift(email, employee_id, iso, hours,LeaveTypes.SICK, row.comment);
// }
created.push({ ...mapRowToView(row), action: "create" });
}
// created.push({ ...mapRowToView(row), action: "create" });
// }
return { action: "create", leave_requests: created };
}
}
// return { action: "create", leave_requests: created };
// }
// }

View File

@ -1,93 +1,93 @@

import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
import { VacationService } from "src/modules/business-logics/services/vacation.service";
import { PrismaService } from "src/prisma/prisma.service";
import { mapRowToView } from "../mappers/leave-requests.mapper";
import { leaveRequestsSelect } from "../utils/leave-requests.select";
import { roundToQuarterHour } from "src/common/utils/date-utils";
import { LeaveRequestsUtils } from "../utils/leave-request.util";
import { normalizeDates, toDateOnly } from "src/modules/shared/helpers/date-time.helpers";
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
// import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
// import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
// import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
// import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
// import { VacationService } from "src/modules/business-logics/services/vacation.service";
// import { PrismaService } from "src/prisma/prisma.service";
// import { mapRowToView } from "../mappers/leave-requests.mapper";
// import { leaveRequestsSelect } from "../utils/leave-requests.select";
// import { roundToQuarterHour } from "src/common/utils/date-utils";
// import { LeaveRequestsUtils } from "../utils/leave-request.util";
// import { normalizeDates, toDateOnly } from "src/modules/shared/helpers/date-time.helpers";
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
@Injectable()
export class VacationLeaveRequestsService {
constructor(
private readonly prisma: PrismaService,
private readonly vacationService: VacationService,
private readonly leaveUtils: LeaveRequestsUtils,
private readonly emailResolver: EmailToIdResolver,
private readonly typeResolver: BankCodesResolver,
) {}
// @Injectable()
// export class VacationLeaveRequestsService {
// constructor(
// private readonly prisma: PrismaService,
// private readonly vacationService: VacationService,
// private readonly leaveUtils: LeaveRequestsUtils,
// private readonly emailResolver: EmailToIdResolver,
// private readonly typeResolver: BankCodesResolver,
// ) {}
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
const email = dto.email.trim();
const employee_id = await this.emailResolver.findIdByEmail(email);
const bank_code = await this.typeResolver.findByType(LeaveTypes.VACATION);
if(!bank_code) throw new NotFoundException(`bank_code not found`);
// async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
// const email = dto.email.trim();
// const employee_id = await this.emailResolver.findIdByEmail(email);
// const bank_code = await this.typeResolver.findByType(LeaveTypes.VACATION);
// if(!bank_code) throw new NotFoundException(`bank_code not found`);
const modifier = bank_code.modifier ?? 1;
const dates = normalizeDates(dto.dates);
const requested_hours_per_day = dto.requested_hours ?? 8;
if (!dates.length) throw new BadRequestException("Dates array must not be empty");
// const modifier = bank_code.modifier ?? 1;
// const dates = normalizeDates(dto.dates);
// const requested_hours_per_day = dto.requested_hours ?? 8;
// if (!dates.length) throw new BadRequestException("Dates array must not be empty");
const entries = dates
.map((iso) => ({ iso, date: toDateOnly(iso) }))
.sort((a, b) => a.date.getTime() - b.date.getTime());
const start_date = entries[0].date;
const total_payable_hours = await this.vacationService.calculateVacationPay(
employee_id,
start_date,
entries.length,
modifier,
);
let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours));
const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier);
// const entries = dates
// .map((iso) => ({ iso, date: toDateOnly(iso) }))
// .sort((a, b) => a.date.getTime() - b.date.getTime());
// const start_date = entries[0].date;
// const total_payable_hours = await this.vacationService.calculateVacationPay(
// employee_id,
// start_date,
// entries.length,
// modifier,
// );
// let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours));
// const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier);
const created: LeaveRequestViewDto[] = [];
// const created: LeaveRequestViewDto[] = [];
for (const { iso, date } of entries) {
const existing = await this.prisma.leaveRequests.findUnique({
where: {
leave_per_employee_date: {
employee_id: employee_id,
leave_type: LeaveTypes.VACATION,
date,
},
},
select: { id: true },
});
if (existing) throw new BadRequestException(`Vacation request already exists for ${iso}`);
// for (const { iso, date } of entries) {
// const existing = await this.prisma.leaveRequests.findUnique({
// where: {
// leave_per_employee_date: {
// employee_id: employee_id,
// leave_type: LeaveTypes.VACATION,
// date,
// },
// },
// select: { id: true },
// });
// if (existing) throw new BadRequestException(`Vacation request already exists for ${iso}`);
const payable = Math.min(remaining_payable_hours, daily_payable_cap);
const payable_rounded = roundToQuarterHour(Math.max(0, payable));
remaining_payable_hours = roundToQuarterHour(
Math.max(0, remaining_payable_hours - payable_rounded),
);
// const payable = Math.min(remaining_payable_hours, daily_payable_cap);
// const payable_rounded = roundToQuarterHour(Math.max(0, payable));
// remaining_payable_hours = roundToQuarterHour(
// Math.max(0, remaining_payable_hours - payable_rounded),
// );
const row = await this.prisma.leaveRequests.create({
data: {
employee_id: employee_id,
bank_code_id: bank_code.id,
payable_hours: payable_rounded,
requested_hours: requested_hours_per_day,
leave_type: LeaveTypes.VACATION,
comment: dto.comment ?? "",
approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING,
date,
},
select: leaveRequestsSelect,
});
// const row = await this.prisma.leaveRequests.create({
// data: {
// employee_id: employee_id,
// bank_code_id: bank_code.id,
// payable_hours: payable_rounded,
// requested_hours: requested_hours_per_day,
// leave_type: LeaveTypes.VACATION,
// comment: dto.comment ?? "",
// approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING,
// date,
// },
// select: leaveRequestsSelect,
// });
const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
if (row.approval_status === LeaveApprovalStatus.APPROVED) {
await this.leaveUtils.syncShift(email, employee_id, iso, hours, LeaveTypes.VACATION, row.comment);
}
created.push({ ...mapRowToView(row), action: "create" });
}
return { action: "create", leave_requests: created };
}
}
// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
// if (row.approval_status === LeaveApprovalStatus.APPROVED) {
// await this.leaveUtils.syncShift(email, employee_id, iso, hours, LeaveTypes.VACATION, row.comment);
// }
// created.push({ ...mapRowToView(row), action: "create" });
// }
// return { action: "create", leave_requests: created };
// }
// }

View File

@ -1,105 +1,104 @@
import { hhmmFromLocal, toDateOnly, toStringFromDate } from "src/modules/shared/helpers/date-time.helpers";
import { BadRequestException, Injectable } from "@nestjs/common";
import { ShiftsCommandService } from "src/modules/shifts/_deprecated-files/shifts-command.service";
import { PrismaService } from "src/prisma/prisma.service";
import { LeaveTypes } from "@prisma/client";
import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
// import { hhmmFromLocal, toDateOnly, toStringFromDate } from "src/modules/shared/helpers/date-time.helpers";
// import { BadRequestException, Injectable } from "@nestjs/common";
// import { PrismaService } from "src/prisma/prisma.service";
// import { LeaveTypes } from "@prisma/client";
// import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
@Injectable()
export class LeaveRequestsUtils {
constructor(
private readonly prisma: PrismaService,
private readonly shiftsCommand: ShiftsCommandService,
){}
// @Injectable()
// export class LeaveRequestsUtils {
// constructor(
// private readonly prisma: PrismaService,
// private readonly shiftsCommand: ShiftsCommandService,
// ){}
async syncShift(
email: string,
employee_id: number,
date: string,
hours: number,
type: LeaveTypes,
comment?: string,
) {
if (hours <= 0) return;
// async syncShift(
// email: string,
// employee_id: number,
// date: string,
// hours: number,
// type: LeaveTypes,
// comment?: string,
// ) {
// if (hours <= 0) return;
const duration_minutes = Math.round(hours * 60);
if (duration_minutes > 8 * 60) {
throw new BadRequestException("Amount of hours cannot exceed 8 hours per day.");
}
const date_only = toDateOnly(date);
const yyyy_mm_dd = toStringFromDate(date_only);
// const duration_minutes = Math.round(hours * 60);
// if (duration_minutes > 8 * 60) {
// throw new BadRequestException("Amount of hours cannot exceed 8 hours per day.");
// }
// const date_only = toDateOnly(date);
// const yyyy_mm_dd = toStringFromDate(date_only);
const start_minutes = 8 * 60;
const end_minutes = start_minutes + duration_minutes;
const toHHmm = (total: number) =>
`${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
// const start_minutes = 8 * 60;
// const end_minutes = start_minutes + duration_minutes;
// const toHHmm = (total: number) =>
// `${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
const existing = await this.prisma.shifts.findFirst({
where: {
date: date_only,
bank_code: { type },
timesheet: { employee_id: employee_id },
},
include: { bank_code: true },
});
// const existing = await this.prisma.shifts.findFirst({
// where: {
// date: date_only,
// bank_code: { type },
// timesheet: { employee_id: employee_id },
// },
// include: { bank_code: true },
// });
const action: UpsertAction = existing ? 'update' : 'create';
// const action: UpsertAction = existing ? 'update' : 'create';
await this.shiftsCommand.upsertShifts(email, action, {
old_shift: existing
? {
date: yyyy_mm_dd,
start_time: existing.start_time.toISOString().slice(11, 16),
end_time: existing.end_time.toISOString().slice(11, 16),
type: existing.bank_code?.type ?? type,
is_remote: existing.is_remote,
is_approved:existing.is_approved,
comment: existing.comment ?? undefined,
}
: undefined,
new_shift: {
date: yyyy_mm_dd,
start_time: toHHmm(start_minutes),
end_time: toHHmm(end_minutes),
is_remote: existing?.is_remote ?? false,
is_approved:existing?.is_approved ?? false,
comment: comment ?? existing?.comment ?? "",
type: type,
},
});
}
// await this.shiftsCommand.upsertShifts(email, action, {
// old_shift: existing
// ? {
// date: yyyy_mm_dd,
// start_time: existing.start_time.toISOString().slice(11, 16),
// end_time: existing.end_time.toISOString().slice(11, 16),
// type: existing.bank_code?.type ?? type,
// is_remote: existing.is_remote,
// is_approved:existing.is_approved,
// comment: existing.comment ?? undefined,
// }
// : undefined,
// new_shift: {
// date: yyyy_mm_dd,
// start_time: toHHmm(start_minutes),
// end_time: toHHmm(end_minutes),
// is_remote: existing?.is_remote ?? false,
// is_approved:existing?.is_approved ?? false,
// comment: comment ?? existing?.comment ?? "",
// type: type,
// },
// });
// }
async removeShift(
email: string,
employee_id: number,
iso_date: string,
type: LeaveTypes,
) {
const date_only = toDateOnly(iso_date);
const yyyy_mm_dd = toStringFromDate(date_only);
const existing = await this.prisma.shifts.findFirst({
where: {
date: date_only,
bank_code: { type },
timesheet: { employee_id: employee_id },
},
include: { bank_code: true },
});
if (!existing) return;
// async removeShift(
// email: string,
// employee_id: number,
// iso_date: string,
// type: LeaveTypes,
// ) {
// const date_only = toDateOnly(iso_date);
// const yyyy_mm_dd = toStringFromDate(date_only);
// const existing = await this.prisma.shifts.findFirst({
// where: {
// date: date_only,
// bank_code: { type },
// timesheet: { employee_id: employee_id },
// },
// include: { bank_code: true },
// });
// if (!existing) return;
await this.shiftsCommand.upsertShifts(email, 'delete', {
old_shift: {
date: yyyy_mm_dd,
start_time: hhmmFromLocal(existing.start_time),
end_time: hhmmFromLocal(existing.end_time),
type: existing.bank_code?.type ?? type,
is_remote: existing.is_remote,
is_approved:existing.is_approved,
comment: existing.comment ?? undefined,
},
});
}
// await this.shiftsCommand.upsertShifts(email, 'delete', {
// old_shift: {
// date: yyyy_mm_dd,
// start_time: hhmmFromLocal(existing.start_time),
// end_time: hhmmFromLocal(existing.end_time),
// type: existing.bank_code?.type ?? type,
// is_remote: existing.is_remote,
// is_approved:existing.is_approved,
// comment: existing.comment ?? undefined,
// },
// });
// }
}
// }

View File

@ -1,33 +1,27 @@
import { PrismaModule } from "src/prisma/prisma.module";
import { PayPeriodsController } from "./controllers/pay-periods.controller";
import { Module } from "@nestjs/common";
import { PayPeriodsCommandService } from "./services/pay-periods-command.service";
import { PayPeriodsQueryService } from "./services/pay-periods-query.service";
import { TimesheetsModule } from "../timesheets/timesheets.module";
import { TimesheetsCommandService } from "../timesheets/~misc_deprecated-files/timesheets-command.service";
import { ExpensesCommandService } from "../expenses/services/expenses-command.service";
import { ShiftsCommandService } from "../shifts/_deprecated-files/shifts-command.service";
import { SharedModule } from "../shared/shared.module";
import { PrismaService } from "src/prisma/prisma.service";
import { BusinessLogicsModule } from "../business-logics/business-logics.module";
import { ShiftsHelpersService } from "../shifts/_deprecated-files/shifts.helpers";
// import { PrismaModule } from "src/prisma/prisma.module";
// import { PayPeriodsController } from "./controllers/pay-periods.controller";
// import { Module } from "@nestjs/common";
// import { PayPeriodsCommandService } from "./services/pay-periods-command.service";
// import { PayPeriodsQueryService } from "./services/pay-periods-query.service";
// import { TimesheetsModule } from "../timesheets/timesheets.module";
// import { ExpensesCommandService } from "../expenses/services/expenses-command.service";
// import { SharedModule } from "../shared/shared.module";
// import { PrismaService } from "src/prisma/prisma.service";
// import { BusinessLogicsModule } from "../business-logics/business-logics.module";
@Module({
imports: [PrismaModule, TimesheetsModule, SharedModule, BusinessLogicsModule],
providers: [
PayPeriodsQueryService,
PayPeriodsCommandService,
TimesheetsCommandService,
ExpensesCommandService,
ShiftsCommandService,
PrismaService,
ShiftsHelpersService,
],
controllers: [PayPeriodsController],
exports: [
PayPeriodsQueryService,
PayPeriodsCommandService,
]
})
// @Module({
// imports: [PrismaModule, TimesheetsModule, SharedModule, BusinessLogicsModule],
// providers: [
// PayPeriodsQueryService,
// PayPeriodsCommandService,
// ExpensesCommandService,
// PrismaService,
// ],
// controllers: [PayPeriodsController],
// exports: [
// PayPeriodsQueryService,
// PayPeriodsCommandService,
// ]
// })
export class PayperiodsModule {}
// export class PayperiodsModule {}

View File

@ -1,14 +1,14 @@
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
import { TimesheetsCommandService } from "src/modules/timesheets/~misc_deprecated-files/timesheets-command.service";
import { PrismaService } from "src/prisma/prisma.service";
import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto";
import { PayPeriodsQueryService } from "./pay-periods-query.service";
import { TimesheetApprovalService } from "src/modules/timesheets/services/timesheet-approval.service";
@Injectable()
export class PayPeriodsCommandService {
constructor(
private readonly prisma: PrismaService,
private readonly timesheets_approval: TimesheetsCommandService,
private readonly timesheets_approval: TimesheetApprovalService,
private readonly query: PayPeriodsQueryService,
) {}

View File

@ -0,0 +1,38 @@
import { Injectable } from "@nestjs/common";
import { Prisma, Timesheets } from "@prisma/client";
import { BaseApprovalService } from "src/common/shared/base-approval.service";
import { PrismaService } from "src/prisma/prisma.service";
@Injectable()
export class TimesheetApprovalService extends BaseApprovalService<Timesheets>{
constructor(prisma: PrismaService){super(prisma)}
//_____________________________________________________________________________________________
// APPROVAL AND DELEGATE METHODS
//_____________________________________________________________________________________________
protected get delegate() {
return this.prisma.timesheets;
}
protected delegateFor(transaction: Prisma.TransactionClient) {
return transaction.timesheets;
}
async updateApproval(id: number, isApproved: boolean): Promise<Timesheets> {
return this.prisma.$transaction((transaction) =>
this.updateApprovalWithTransaction(transaction, id, isApproved),
);
}
async cascadeApprovalWithtx(transaction: Prisma.TransactionClient, timesheetId: number, isApproved: boolean): Promise<Timesheets> {
const timesheet = await this.updateApprovalWithTransaction(transaction, timesheetId, isApproved);
await transaction.shifts.updateMany({
where: { timesheet_id: timesheetId },
data: { is_approved: isApproved },
});
await transaction.expenses.updateManyAndReturn({
where: { timesheet_id: timesheetId },
data: { is_approved: isApproved },
});
return timesheet;
}
}

View File

@ -1,33 +1,33 @@
import { Type } from "class-transformer";
import { IsArray, IsOptional, IsString, Length, Matches, ValidateNested } from "class-validator";
// import { Type } from "class-transformer";
// import { IsArray, IsOptional, IsString, Length, Matches, ValidateNested } from "class-validator";
export class CreateTimesheetDto {
// export class CreateTimesheetDto {
@IsString()
@Matches(/^\d{4}-\d{2}-\d{2}$/)
date!: string;
// @IsString()
// @Matches(/^\d{4}-\d{2}-\d{2}$/)
// date!: string;
@IsString()
@Length(1,64)
type!: string;
// @IsString()
// @Length(1,64)
// type!: string;
@IsString()
@Matches(/^\d{2}:\d{2}$/)
start_time!: string;
// @IsString()
// @Matches(/^\d{2}:\d{2}$/)
// start_time!: string;
@IsString()
@Matches(/^\d{2}:\d{2}$/)
end_time!: string;
// @IsString()
// @Matches(/^\d{2}:\d{2}$/)
// end_time!: string;
@IsOptional()
@IsString()
@Length(0,512)
comment?: string;
}
// @IsOptional()
// @IsString()
// @Length(0,512)
// comment?: string;
// }
export class CreateWeekShiftsDto {
@IsArray()
@ValidateNested({each:true})
@Type(()=> CreateTimesheetDto)
shifts!: CreateTimesheetDto[];
}
// export class CreateWeekShiftsDto {
// @IsArray()
// @ValidateNested({each:true})
// @Type(()=> CreateTimesheetDto)
// shifts!: CreateTimesheetDto[];
// }

View File

@ -1,20 +1,20 @@
import { Type } from "class-transformer";
import { IsBoolean, IsInt, IsOptional } from "class-validator";
// import { Type } from "class-transformer";
// import { IsBoolean, IsInt, IsOptional } from "class-validator";
export class SearchTimesheetDto {
@IsOptional()
@Type(() => Number)
@IsInt()
timesheet_id?: number;
// export class SearchTimesheetDto {
// @IsOptional()
// @Type(() => Number)
// @IsInt()
// timesheet_id?: number;
@IsOptional()
@Type(()=> Number)
@IsInt()
employee_id?: number;
// @IsOptional()
// @Type(()=> Number)
// @IsInt()
// employee_id?: number;
@IsOptional()
@Type(()=> Boolean)
@IsBoolean()
is_approved?: boolean;
}
// @IsOptional()
// @Type(()=> Boolean)
// @IsBoolean()
// is_approved?: boolean;
// }

View File

@ -1,75 +1,75 @@
export class TimesheetDto {
start_day: string;
end_day: string;
label: string;
shifts: ShiftDto[];
expenses: ExpenseDto[]
is_approved: boolean;
}
// export class TimesheetDto {
// start_day: string;
// end_day: string;
// label: string;
// shifts: ShiftDto[];
// expenses: ExpenseDto[]
// is_approved: boolean;
// }
export class ShiftDto {
date: string;
type: string;
start_time: string;
end_time : string;
comment: string;
is_approved: boolean;
is_remote: boolean;
}
// export class ShiftDto {
// date: string;
// type: string;
// start_time: string;
// end_time : string;
// comment: string;
// is_approved: boolean;
// is_remote: boolean;
// }
export class ExpenseDto {
type: string;
amount: number;
mileage: number;
comment: string;
is_approved: boolean;
supervisor_comment: string;
}
// export class ExpenseDto {
// type: string;
// amount: number;
// mileage: number;
// comment: string;
// is_approved: boolean;
// supervisor_comment: string;
// }
export type DayShiftsDto = ShiftDto[];
// export type DayShiftsDto = ShiftDto[];
export class DetailedShifts {
shifts: DayShiftsDto;
regular_hours: number;
evening_hours: number;
overtime_hours: number;
emergency_hours: number;
comment: string;
short_date: string;
break_durations?: number;
}
// export class DetailedShifts {
// shifts: DayShiftsDto;
// regular_hours: number;
// evening_hours: number;
// overtime_hours: number;
// emergency_hours: number;
// comment: string;
// short_date: string;
// break_durations?: number;
// }
export class DayExpensesDto {
expenses: ExpenseDto[] = [];
total_mileage: number;
total_expense: number;
}
// export class DayExpensesDto {
// expenses: ExpenseDto[] = [];
// total_mileage: number;
// total_expense: number;
// }
export class WeekDto {
is_approved: boolean;
shifts: {
sun: DetailedShifts;
mon: DetailedShifts;
tue: DetailedShifts;
wed: DetailedShifts;
thu: DetailedShifts;
fri: DetailedShifts;
sat: DetailedShifts;
}
expenses: {
sun: DayExpensesDto;
mon: DayExpensesDto;
tue: DayExpensesDto;
wed: DayExpensesDto;
thu: DayExpensesDto;
fri: DayExpensesDto;
sat: DayExpensesDto;
}
}
// export class WeekDto {
// is_approved: boolean;
// shifts: {
// sun: DetailedShifts;
// mon: DetailedShifts;
// tue: DetailedShifts;
// wed: DetailedShifts;
// thu: DetailedShifts;
// fri: DetailedShifts;
// sat: DetailedShifts;
// }
// expenses: {
// sun: DayExpensesDto;
// mon: DayExpensesDto;
// tue: DayExpensesDto;
// wed: DayExpensesDto;
// thu: DayExpensesDto;
// fri: DayExpensesDto;
// sat: DayExpensesDto;
// }
// }
export class TimesheetPeriodDto {
weeks: WeekDto[];
employee_full_name: string;
}
// export class TimesheetPeriodDto {
// weeks: WeekDto[];
// employee_full_name: string;
// }

View File

@ -1,67 +1,67 @@
import { MS_PER_DAY } from "src/modules/shared/constants/date-time.constant";
import { DAY_KEYS, DayKey } from "./timesheet.types";
// import { MS_PER_DAY } from "src/modules/shared/constants/date-time.constant";
// import { DAY_KEYS, DayKey } from "./timesheet.types";
export function toUTCDateOnly(date: Date | string): Date {
const d = new Date(date);
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
}
// export function toUTCDateOnly(date: Date | string): Date {
// const d = new Date(date);
// return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
// }
export function addDays(date:Date, days: number): Date {
return new Date(date.getTime() + days * MS_PER_DAY);
}
// export function addDays(date:Date, days: number): Date {
// return new Date(date.getTime() + days * MS_PER_DAY);
// }
export function endOfDayUTC(date: Date | string): Date {
const d = toUTCDateOnly(date);
return new Date(d.getTime() + MS_PER_DAY - 1);
}
// export function endOfDayUTC(date: Date | string): Date {
// const d = toUTCDateOnly(date);
// return new Date(d.getTime() + MS_PER_DAY - 1);
// }
export function isBetweenUTC(date: Date, start: Date, end_inclusive: Date): boolean {
const time = date.getTime();
return time >= start.getTime() && time <= end_inclusive.getTime();
}
// export function isBetweenUTC(date: Date, start: Date, end_inclusive: Date): boolean {
// const time = date.getTime();
// return time >= start.getTime() && time <= end_inclusive.getTime();
// }
export function toTimeString(date: Date): string {
const hours = String(date.getUTCHours()).padStart(2,'0');
const minutes = String(date.getUTCMinutes()).padStart(2,'0');
return `${hours}:${minutes}`;
}
// export function toTimeString(date: Date): string {
// const hours = String(date.getUTCHours()).padStart(2,'0');
// const minutes = String(date.getUTCMinutes()).padStart(2,'0');
// return `${hours}:${minutes}`;
// }
export function round2(num: number) {
return Math.round(num * 100) / 100;
}
// export function round2(num: number) {
// return Math.round(num * 100) / 100;
// }
export function shortDate(date:Date): string {
const mm = String(date.getUTCMonth()+1).padStart(2,'0');
const dd = String(date.getUTCDate()).padStart(2,'0');
return `${mm}/${dd}`;
}
// export function shortDate(date:Date): string {
// const mm = String(date.getUTCMonth()+1).padStart(2,'0');
// const dd = String(date.getUTCDate()).padStart(2,'0');
// return `${mm}/${dd}`;
// }
export function dayKeyFromDate(date: Date, useUTC = true): DayKey {
const index = useUTC ? date.getUTCDay() : date.getDay(); // 0=Sunday..6=Saturday
return DAY_KEYS[index];
}
// export function dayKeyFromDate(date: Date, useUTC = true): DayKey {
// const index = useUTC ? date.getUTCDay() : date.getDay(); // 0=Sunday..6=Saturday
// return DAY_KEYS[index];
// }
export const toHHmm = (date: Date) => date.toISOString().slice(11, 16);
// export const toHHmm = (date: Date) => date.toISOString().slice(11, 16);
export function parseISODate(iso: string): Date {
const [ y, m, d ] = iso.split('-').map(Number);
return new Date(y, (m ?? 1) - 1, d ?? 1);
}
// export function parseISODate(iso: string): Date {
// const [ y, m, d ] = iso.split('-').map(Number);
// return new Date(y, (m ?? 1) - 1, d ?? 1);
// }
export function parseHHmm(t: string): Date {
const [ hh, mm ] = t.split(':').map(Number);
return new Date(1970, 0, 1, hh || 0, mm || 0, 0, 0);
}
// export function parseHHmm(t: string): Date {
// const [ hh, mm ] = t.split(':').map(Number);
// return new Date(1970, 0, 1, hh || 0, mm || 0, 0, 0);
// }
export const toNum = (value: any) =>
value && typeof value.toNumber === 'function' ? value.toNumber() :
typeof value === 'number' ? value :
value ? Number(value) : 0;
// export const toNum = (value: any) =>
// value && typeof value.toNumber === 'function' ? value.toNumber() :
// typeof value === 'number' ? value :
// value ? Number(value) : 0;
export const upper = (s?: string | null) => String(s ?? '').toUpperCase();
// export const upper = (s?: string | null) => String(s ?? '').toUpperCase();
export const toRangeFromPeriod = (period: { period_start: Date; period_end: Date }) => ({
from: toUTCDateOnly(period.period_start),
to: endOfDayUTC(period.period_end),
});
// export const toRangeFromPeriod = (period: { period_start: Date; period_end: Date }) => ({
// from: toUTCDateOnly(period.period_start),
// to: endOfDayUTC(period.period_end),
// });

View File

@ -1,111 +1,111 @@
import { DayExpensesDto, WeekDto, DetailedShifts, TimesheetPeriodDto } from "../dtos/timesheet-period.dto";
import { ShiftRow, ExpenseRow, ExpensesAmount, TimesheetMap } from "./timesheet.types";
import { addDays, shortDate, toNum, upper } from "./timesheet.helpers";
import { Prisma } from "@prisma/client";
// import { DayExpensesDto, WeekDto, DetailedShifts, TimesheetPeriodDto } from "../dtos/timesheet-period.dto";
// import { ShiftRow, ExpenseRow, ExpensesAmount, TimesheetMap } from "./timesheet.types";
// import { addDays, shortDate, toNum, upper } from "./timesheet.helpers";
// import { Prisma } from "@prisma/client";
//mappers
export const mapShiftRow = (shift: {
date: Date;
start_time: Date;
end_time: Date;
comment?: string | null;
is_approved: boolean;
is_remote: boolean;
bank_code: { type: string };
}): ShiftRow => ({
date: shift.date,
start_time: shift.start_time,
end_time: shift.end_time,
comment: shift.comment ?? '',
is_approved: shift.is_approved,
is_remote: shift.is_remote,
type: upper(shift.bank_code.type),
});
// //mappers
// export const mapShiftRow = (shift: {
// date: Date;
// start_time: Date;
// end_time: Date;
// comment?: string | null;
// is_approved: boolean;
// is_remote: boolean;
// bank_code: { type: string };
// }): ShiftRow => ({
// date: shift.date,
// start_time: shift.start_time,
// end_time: shift.end_time,
// comment: shift.comment ?? '',
// is_approved: shift.is_approved,
// is_remote: shift.is_remote,
// type: upper(shift.bank_code.type),
// });
export const mapExpenseRow = (expense: {
date: Date;
amount: Prisma.Decimal | number | null;
mileage: Prisma.Decimal | number | null;
comment?: string | null;
is_approved: boolean;
supervisor_comment?: string|null;
bank_code: { type: string },
}): ExpenseRow => ({
date: expense.date,
amount: toNum(expense.amount),
mileage: toNum(expense.mileage),
comment: expense.comment ?? '',
is_approved: expense.is_approved,
supervisor_comment: expense.supervisor_comment ?? '',
type: upper(expense.bank_code.type),
});
// export const mapExpenseRow = (expense: {
// date: Date;
// amount: Prisma.Decimal | number | null;
// mileage: Prisma.Decimal | number | null;
// comment?: string | null;
// is_approved: boolean;
// supervisor_comment?: string|null;
// bank_code: { type: string },
// }): ExpenseRow => ({
// date: expense.date,
// amount: toNum(expense.amount),
// mileage: toNum(expense.mileage),
// comment: expense.comment ?? '',
// is_approved: expense.is_approved,
// supervisor_comment: expense.supervisor_comment ?? '',
// type: upper(expense.bank_code.type),
// });
// Factories
export function makeEmptyDayExpenses(): DayExpensesDto {
return {
expenses: [],
total_expense: -1,
total_mileage: -1,
};
}
// // Factories
// export function makeEmptyDayExpenses(): DayExpensesDto {
// return {
// expenses: [],
// total_expense: -1,
// total_mileage: -1,
// };
// }
export function makeEmptyWeek(week_start: Date): WeekDto {
const make_empty_shifts = (offset: number): DetailedShifts => ({
shifts: [],
regular_hours: 0,
evening_hours: 0,
emergency_hours: 0,
overtime_hours: 0,
comment: '',
short_date: shortDate(addDays(week_start, offset)),
break_durations: 0,
});
return {
is_approved: true,
shifts: {
sun: make_empty_shifts(0),
mon: make_empty_shifts(1),
tue: make_empty_shifts(2),
wed: make_empty_shifts(3),
thu: make_empty_shifts(4),
fri: make_empty_shifts(5),
sat: make_empty_shifts(6),
},
expenses: {
sun: makeEmptyDayExpenses(),
mon: makeEmptyDayExpenses(),
tue: makeEmptyDayExpenses(),
wed: makeEmptyDayExpenses(),
thu: makeEmptyDayExpenses(),
fri: makeEmptyDayExpenses(),
sat: makeEmptyDayExpenses(),
},
};
}
// export function makeEmptyWeek(week_start: Date): WeekDto {
// const make_empty_shifts = (offset: number): DetailedShifts => ({
// shifts: [],
// regular_hours: 0,
// evening_hours: 0,
// emergency_hours: 0,
// overtime_hours: 0,
// comment: '',
// short_date: shortDate(addDays(week_start, offset)),
// break_durations: 0,
// });
// return {
// is_approved: true,
// shifts: {
// sun: make_empty_shifts(0),
// mon: make_empty_shifts(1),
// tue: make_empty_shifts(2),
// wed: make_empty_shifts(3),
// thu: make_empty_shifts(4),
// fri: make_empty_shifts(5),
// sat: make_empty_shifts(6),
// },
// expenses: {
// sun: makeEmptyDayExpenses(),
// mon: makeEmptyDayExpenses(),
// tue: makeEmptyDayExpenses(),
// wed: makeEmptyDayExpenses(),
// thu: makeEmptyDayExpenses(),
// fri: makeEmptyDayExpenses(),
// sat: makeEmptyDayExpenses(),
// },
// };
// }
export function makeEmptyPeriod(): TimesheetPeriodDto {
return { weeks: [makeEmptyWeek(new Date()), makeEmptyWeek(new Date())], employee_full_name: '' };
}
// export function makeEmptyPeriod(): TimesheetPeriodDto {
// return { weeks: [makeEmptyWeek(new Date()), makeEmptyWeek(new Date())], employee_full_name: '' };
// }
export const makeAmounts = (): ExpensesAmount => ({
expense: 0,
mileage: 0,
});
// export const makeAmounts = (): ExpensesAmount => ({
// expense: 0,
// mileage: 0,
// });
export function makeEmptyTimesheet(params: {
start_day: string;
end_day: string;
label: string;
is_approved?: boolean;
}): TimesheetMap {
const { start_day, end_day, label, is_approved = false } = params;
return {
start_day,
end_day,
label,
shifts: [],
expenses: [],
is_approved,
};
}
// export function makeEmptyTimesheet(params: {
// start_day: string;
// end_day: string;
// label: string;
// is_approved?: boolean;
// }): TimesheetMap {
// const { start_day, end_day, label, is_approved = false } = params;
// return {
// start_day,
// end_day,
// label,
// shifts: [],
// expenses: [],
// is_approved,
// };
// }

View File

@ -1,46 +1,46 @@
import { EXPENSE_ASC_ORDER, EXPENSE_SELECT } from "../../../shared/selects/expenses.select";
import { Injectable, NotFoundException } from "@nestjs/common";
import { SHIFT_ASC_ORDER, SHIFT_SELECT } from "../../../shared/selects/shifts.select";
import { PAY_PERIOD_SELECT } from "../../../shared/selects/pay-periods.select";
import { PrismaService } from "src/prisma/prisma.service";
// import { EXPENSE_ASC_ORDER, EXPENSE_SELECT } from "../../../shared/selects/expenses.select";
// import { Injectable, NotFoundException } from "@nestjs/common";
// import { SHIFT_ASC_ORDER, SHIFT_SELECT } from "../../../shared/selects/shifts.select";
// import { PAY_PERIOD_SELECT } from "../../../shared/selects/pay-periods.select";
// import { PrismaService } from "src/prisma/prisma.service";
@Injectable()
export class TimesheetSelectorsService {
constructor(readonly prisma: PrismaService){}
// @Injectable()
// export class TimesheetSelectorsService {
// constructor(readonly prisma: PrismaService){}
async getPayPeriod(pay_year: number, pay_period_no: number) {
const period = await this.prisma.payPeriods.findFirst({
where: { pay_year, pay_period_no },
select: PAY_PERIOD_SELECT ,
});
if(!period) throw new NotFoundException(`period ${pay_year}-${pay_period_no} not found`);
return period;
}
// async getPayPeriod(pay_year: number, pay_period_no: number) {
// const period = await this.prisma.payPeriods.findFirst({
// where: { pay_year, pay_period_no },
// select: PAY_PERIOD_SELECT ,
// });
// if(!period) throw new NotFoundException(`period ${pay_year}-${pay_period_no} not found`);
// return period;
// }
async getShifts(employee_id: number, from: Date, to: Date) {
return this.prisma.shifts.findMany({
where: {timesheet: { is: { employee_id } }, date: { gte: from, lte: to } },
select: SHIFT_SELECT,
orderBy: SHIFT_ASC_ORDER,
});
}
// async getShifts(employee_id: number, from: Date, to: Date) {
// return this.prisma.shifts.findMany({
// where: {timesheet: { is: { employee_id } }, date: { gte: from, lte: to } },
// select: SHIFT_SELECT,
// orderBy: SHIFT_ASC_ORDER,
// });
// }
async getExpenses(employee_id: number, from: Date, to: Date) {
return this.prisma.expenses.findMany({
where: { timesheet: {is: { employee_id } }, date: { gte: from, lte: to } },
select: EXPENSE_SELECT,
orderBy: EXPENSE_ASC_ORDER,
});
}
// async getExpenses(employee_id: number, from: Date, to: Date) {
// return this.prisma.expenses.findMany({
// where: { timesheet: {is: { employee_id } }, date: { gte: from, lte: to } },
// select: EXPENSE_SELECT,
// orderBy: EXPENSE_ASC_ORDER,
// });
// }
async getTimesheetWithShiftsAndExpenses(employee_id: number, start_date_week: Date) {
return this.prisma.timesheets.findUnique({
where: { employee_id_start_date: { employee_id, start_date: start_date_week } },
select: {
is_approved: true,
shift: { select: SHIFT_SELECT, orderBy: SHIFT_ASC_ORDER },
expense: { select: EXPENSE_SELECT, orderBy: EXPENSE_ASC_ORDER },
},
});
}
}
// async getTimesheetWithShiftsAndExpenses(employee_id: number, start_date_week: Date) {
// return this.prisma.timesheets.findUnique({
// where: { employee_id_start_date: { employee_id, start_date: start_date_week } },
// select: {
// is_approved: true,
// shift: { select: SHIFT_SELECT, orderBy: SHIFT_ASC_ORDER },
// expense: { select: EXPENSE_SELECT, orderBy: EXPENSE_ASC_ORDER },
// },
// });
// }
// }

View File

@ -1,74 +1,74 @@
export type ShiftRow = {
date: Date;
start_time: Date;
end_time: Date;
comment: string;
is_approved?: boolean;
is_remote: boolean;
type: string
};
export type ExpenseRow = {
date: Date;
amount: number;
mileage?: number | null;
comment: string;
type: string;
is_approved?: boolean;
supervisor_comment: string;
};
// export type ShiftRow = {
// date: Date;
// start_time: Date;
// end_time: Date;
// comment: string;
// is_approved?: boolean;
// is_remote: boolean;
// type: string
// };
// export type ExpenseRow = {
// date: Date;
// amount: number;
// mileage?: number | null;
// comment: string;
// type: string;
// is_approved?: boolean;
// supervisor_comment: string;
// };
export type TimesheetMap = {
start_day: string;
end_day: string;
label: string;
shifts: ShiftRow[];
expenses: ExpenseRow[]
is_approved: boolean;
}
// export type TimesheetMap = {
// start_day: string;
// end_day: string;
// label: string;
// shifts: ShiftRow[];
// expenses: ExpenseRow[]
// is_approved: boolean;
// }
// Types
export const SHIFT_TYPES = {
REGULAR: 'REGULAR',
EVENING: 'EVENING',
OVERTIME: 'OVERTIME',
EMERGENCY: 'EMERGENCY',
HOLIDAY: 'HOLIDAY',
VACATION: 'VACATION',
SICK: 'SICK',
} as const;
// // Types
// export const SHIFT_TYPES = {
// REGULAR: 'REGULAR',
// EVENING: 'EVENING',
// OVERTIME: 'OVERTIME',
// EMERGENCY: 'EMERGENCY',
// HOLIDAY: 'HOLIDAY',
// VACATION: 'VACATION',
// SICK: 'SICK',
// } as const;
export const EXPENSE_TYPES = {
MILEAGE: 'MILEAGE',
EXPENSE: 'EXPENSES',
PER_DIEM: 'PER_DIEM',
ON_CALL: 'ON_CALL',
} as const;
// export const EXPENSE_TYPES = {
// MILEAGE: 'MILEAGE',
// EXPENSE: 'EXPENSES',
// PER_DIEM: 'PER_DIEM',
// ON_CALL: 'ON_CALL',
// } as const;
//makes the strings indexes for arrays
export const DAY_KEYS = ['sun','mon','tue','wed','thu','fri','sat'] as const;
export type DayKey = typeof DAY_KEYS[number];
// //makes the strings indexes for arrays
// export const DAY_KEYS = ['sun','mon','tue','wed','thu','fri','sat'] as const;
// export type DayKey = typeof DAY_KEYS[number];
//shifts's hour by type
export type ShiftsHours = {
regular: number;
evening: number;
overtime: number;
emergency: number;
sick: number;
vacation: number;
holiday: number;
};
export const make_hours = (): ShiftsHours => ({
regular: 0,
evening: 0,
overtime: 0,
emergency: 0,
sick: 0,
vacation: 0,
holiday: 0,
});
// //shifts's hour by type
// export type ShiftsHours = {
// regular: number;
// evening: number;
// overtime: number;
// emergency: number;
// sick: number;
// vacation: number;
// holiday: number;
// };
// export const make_hours = (): ShiftsHours => ({
// regular: 0,
// evening: 0,
// overtime: 0,
// emergency: 0,
// sick: 0,
// vacation: 0,
// holiday: 0,
// });
export type ExpensesAmount = {
expense: number;
mileage: number;
};
// export type ExpensesAmount = {
// expense: number;
// mileage: number;
// };

View File

@ -1,171 +1,171 @@
import {
DayKey, DAY_KEYS, EXPENSE_TYPES, ExpenseRow,
SHIFT_TYPES, ShiftRow, make_hours, ShiftsHours, ExpensesAmount
} from "./timesheet.types";
import {
isBetweenUTC, dayKeyFromDate, toTimeString, round2,
toUTCDateOnly, endOfDayUTC, addDays
} from "./timesheet.helpers";
import { WeekDto, ShiftDto, TimesheetPeriodDto, DayExpensesDto, ExpenseDto } from "../dtos/timesheet-period.dto";
import { getWeekStart, getWeekEnd, formatDateISO } from "src/common/utils/date-utils";
import { makeAmounts, makeEmptyWeek } from "./timesheet.mappers";
import { toDateString } from "src/modules/pay-periods/utils/pay-year.util";
import { MS_PER_HOUR } from "src/modules/shared/constants/date-time.constant";
// import {
// DayKey, DAY_KEYS, EXPENSE_TYPES, ExpenseRow,
// SHIFT_TYPES, ShiftRow, make_hours, ShiftsHours, ExpensesAmount
// } from "./timesheet.types";
// import {
// isBetweenUTC, dayKeyFromDate, toTimeString, round2,
// toUTCDateOnly, endOfDayUTC, addDays
// } from "./timesheet.helpers";
// import { WeekDto, ShiftDto, TimesheetPeriodDto, DayExpensesDto, ExpenseDto } from "../dtos/timesheet-period.dto";
// import { getWeekStart, getWeekEnd, formatDateISO } from "src/common/utils/date-utils";
// import { makeAmounts, makeEmptyWeek } from "./timesheet.mappers";
// import { toDateString } from "src/modules/pay-periods/utils/pay-year.util";
// import { MS_PER_HOUR } from "src/modules/shared/constants/date-time.constant";
export function computeWeekRange(week_offset = 0){
//sets current week Sunday -> Saturday
const base = new Date();
const offset = new Date(base);
offset.setDate(offset.getDate() + (week_offset * 7));
// export function computeWeekRange(week_offset = 0){
// //sets current week Sunday -> Saturday
// const base = new Date();
// const offset = new Date(base);
// offset.setDate(offset.getDate() + (week_offset * 7));
const start = getWeekStart(offset, 0);
const end = getWeekEnd(start);
const start_day = formatDateISO(start);
const end_day = formatDateISO(end);
const label = `${(start_day)}.${(end_day)}`;
// const start = getWeekStart(offset, 0);
// const end = getWeekEnd(start);
// const start_day = formatDateISO(start);
// const end_day = formatDateISO(end);
// const label = `${(start_day)}.${(end_day)}`;
return { start, end, start_day, end_day, label }
};
// return { start, end, start_day, end_day, label }
// };
export function buildWeek(
week_start: Date,
week_end: Date,
shifts: ShiftRow[],
expenses: ExpenseRow[],
): WeekDto {
const week = makeEmptyWeek(week_start);
let all_approved = true;
// export function buildWeek(
// week_start: Date,
// week_end: Date,
// shifts: ShiftRow[],
// expenses: ExpenseRow[],
// ): WeekDto {
// const week = makeEmptyWeek(week_start);
// let all_approved = true;
const day_times: Record<DayKey, Array<{ start: Date; end: Date }>> = DAY_KEYS.reduce((acc, key) => {
acc[key] = []; return acc;
}, {} as Record<DayKey, Array<{ start: Date; end: Date}>>);
// const day_times: Record<DayKey, Array<{ start: Date; end: Date }>> = DAY_KEYS.reduce((acc, key) => {
// acc[key] = []; return acc;
// }, {} as Record<DayKey, Array<{ start: Date; end: Date}>>);
const day_hours: Record<DayKey, ShiftsHours> = DAY_KEYS.reduce((acc, key) => {
acc[key] = make_hours(); return acc;
}, {} as Record<DayKey, ShiftsHours>);
// const day_hours: Record<DayKey, ShiftsHours> = DAY_KEYS.reduce((acc, key) => {
// acc[key] = make_hours(); return acc;
// }, {} as Record<DayKey, ShiftsHours>);
const day_amounts: Record<DayKey, ExpensesAmount> = DAY_KEYS.reduce((acc, key) => {
acc[key] = makeAmounts(); return acc;
}, {} as Record<DayKey, ExpensesAmount>);
// const day_amounts: Record<DayKey, ExpensesAmount> = DAY_KEYS.reduce((acc, key) => {
// acc[key] = makeAmounts(); return acc;
// }, {} as Record<DayKey, ExpensesAmount>);
const day_expense_rows: Record<DayKey, DayExpensesDto> = DAY_KEYS.reduce((acc, key) => {
acc[key] = {
expenses: [{
type: '',
amount: -1,
mileage: -1,
comment: '',
is_approved: false,
supervisor_comment: '',
}],
total_expense: -1,
total_mileage: -1,
};
return acc;
}, {} as Record<DayKey, DayExpensesDto>);
// const day_expense_rows: Record<DayKey, DayExpensesDto> = DAY_KEYS.reduce((acc, key) => {
// acc[key] = {
// expenses: [{
// type: '',
// amount: -1,
// mileage: -1,
// comment: '',
// is_approved: false,
// supervisor_comment: '',
// }],
// total_expense: -1,
// total_mileage: -1,
// };
// return acc;
// }, {} as Record<DayKey, DayExpensesDto>);
//regroup hours per type of shifts
const week_shifts = shifts.filter(shift => isBetweenUTC(shift.date, week_start, week_end));
for (const shift of week_shifts) {
const key = dayKeyFromDate(shift.date, true);
week.shifts[key].shifts.push({
date: toDateString(shift.date),
type: shift.type,
start_time: toTimeString(shift.start_time),
end_time: toTimeString(shift.end_time),
comment: shift.comment,
is_approved: shift.is_approved ?? true,
is_remote: shift.is_remote,
} as ShiftDto);
// //regroup hours per type of shifts
// const week_shifts = shifts.filter(shift => isBetweenUTC(shift.date, week_start, week_end));
// for (const shift of week_shifts) {
// const key = dayKeyFromDate(shift.date, true);
// week.shifts[key].shifts.push({
// date: toDateString(shift.date),
// type: shift.type,
// start_time: toTimeString(shift.start_time),
// end_time: toTimeString(shift.end_time),
// comment: shift.comment,
// is_approved: shift.is_approved ?? true,
// is_remote: shift.is_remote,
// } as ShiftDto);
day_times[key].push({ start: shift.start_time, end: shift.end_time});
// day_times[key].push({ start: shift.start_time, end: shift.end_time});
const duration = Math.max(0, (shift.end_time.getTime() - shift.start_time.getTime())/ MS_PER_HOUR);
const type = (shift.type || '').toUpperCase();
// const duration = Math.max(0, (shift.end_time.getTime() - shift.start_time.getTime())/ MS_PER_HOUR);
// const type = (shift.type || '').toUpperCase();
if ( type === SHIFT_TYPES.REGULAR) day_hours[key].regular += duration;
else if( type === SHIFT_TYPES.EVENING) day_hours[key].evening += duration;
else if( type === SHIFT_TYPES.EMERGENCY) day_hours[key].emergency += duration;
else if( type === SHIFT_TYPES.OVERTIME) day_hours[key].overtime += duration;
else if( type === SHIFT_TYPES.SICK) day_hours[key].sick += duration;
else if( type === SHIFT_TYPES.VACATION) day_hours[key].vacation += duration;
else if( type === SHIFT_TYPES.HOLIDAY) day_hours[key].holiday += duration;
// if ( type === SHIFT_TYPES.REGULAR) day_hours[key].regular += duration;
// else if( type === SHIFT_TYPES.EVENING) day_hours[key].evening += duration;
// else if( type === SHIFT_TYPES.EMERGENCY) day_hours[key].emergency += duration;
// else if( type === SHIFT_TYPES.OVERTIME) day_hours[key].overtime += duration;
// else if( type === SHIFT_TYPES.SICK) day_hours[key].sick += duration;
// else if( type === SHIFT_TYPES.VACATION) day_hours[key].vacation += duration;
// else if( type === SHIFT_TYPES.HOLIDAY) day_hours[key].holiday += duration;
all_approved = all_approved && (shift.is_approved ?? true );
}
// all_approved = all_approved && (shift.is_approved ?? true );
// }
//regroupe amounts to type of expenses
const week_expenses = expenses.filter(expense => isBetweenUTC(expense.date, week_start, week_end));
for (const expense of week_expenses) {
const key = dayKeyFromDate(expense.date, true);
const type = (expense.type || '').toUpperCase();
// //regroupe amounts to type of expenses
// const week_expenses = expenses.filter(expense => isBetweenUTC(expense.date, week_start, week_end));
// for (const expense of week_expenses) {
// const key = dayKeyFromDate(expense.date, true);
// const type = (expense.type || '').toUpperCase();
const row: ExpenseDto = {
type,
amount: round2(expense.amount ?? 0),
mileage: round2(expense.mileage ?? 0),
comment: expense.comment ?? '',
is_approved: expense.is_approved ?? true,
supervisor_comment: expense.supervisor_comment ?? '',
};
// const row: ExpenseDto = {
// type,
// amount: round2(expense.amount ?? 0),
// mileage: round2(expense.mileage ?? 0),
// comment: expense.comment ?? '',
// is_approved: expense.is_approved ?? true,
// supervisor_comment: expense.supervisor_comment ?? '',
// };
day_expense_rows[key].expenses.push(row);
// day_expense_rows[key].expenses.push(row);
if(type === EXPENSE_TYPES.MILEAGE) {
day_amounts[key].mileage += row.mileage ?? 0;
} else {
day_amounts[key].expense += row.amount;
}
// if(type === EXPENSE_TYPES.MILEAGE) {
// day_amounts[key].mileage += row.mileage ?? 0;
// } else {
// day_amounts[key].expense += row.amount;
// }
all_approved = all_approved && row.is_approved;
}
// all_approved = all_approved && row.is_approved;
// }
for (const key of DAY_KEYS) {
//return exposed dto data
week.shifts[key].regular_hours = round2(day_hours[key].regular);
week.shifts[key].evening_hours = round2(day_hours[key].evening);
week.shifts[key].overtime_hours = round2(day_hours[key].overtime);
week.shifts[key].emergency_hours = round2(day_hours[key].emergency);
// for (const key of DAY_KEYS) {
// //return exposed dto data
// week.shifts[key].regular_hours = round2(day_hours[key].regular);
// week.shifts[key].evening_hours = round2(day_hours[key].evening);
// week.shifts[key].overtime_hours = round2(day_hours[key].overtime);
// week.shifts[key].emergency_hours = round2(day_hours[key].emergency);
//calculate gaps between shifts
const times = day_times[key].sort((a,b) => a.start.getTime() - b.start.getTime());
let gaps = 0;
for (let i = 1; i < times.length; i++) {
const gap = (times[i].start.getTime() - times[i - 1].end.getTime()) / MS_PER_HOUR;
if(gap > 0) gaps += gap;
}
week.shifts[key].break_durations = round2(gaps);
// //calculate gaps between shifts
// const times = day_times[key].sort((a,b) => a.start.getTime() - b.start.getTime());
// let gaps = 0;
// for (let i = 1; i < times.length; i++) {
// const gap = (times[i].start.getTime() - times[i - 1].end.getTime()) / MS_PER_HOUR;
// if(gap > 0) gaps += gap;
// }
// week.shifts[key].break_durations = round2(gaps);
//daily totals
const totals = day_amounts[key];
// //daily totals
// const totals = day_amounts[key];
day_expense_rows[key].total_mileage = round2(totals.mileage);
day_expense_rows[key].total_expense = round2(totals.expense);
}
// day_expense_rows[key].total_mileage = round2(totals.mileage);
// day_expense_rows[key].total_expense = round2(totals.expense);
// }
week.is_approved = all_approved;
return week;
}
// week.is_approved = all_approved;
// return week;
// }
export function buildPeriod(
period_start: Date,
period_end: Date,
shifts: ShiftRow[],
expenses: ExpenseRow[],
employeeFullName = ''
): TimesheetPeriodDto {
const week1_start = toUTCDateOnly(period_start);
const week1_end = endOfDayUTC(addDays(week1_start, 6));
const week2_start = toUTCDateOnly(addDays(week1_start, 7));
const week2_end = endOfDayUTC(period_end);
// export function buildPeriod(
// period_start: Date,
// period_end: Date,
// shifts: ShiftRow[],
// expenses: ExpenseRow[],
// employeeFullName = ''
// ): TimesheetPeriodDto {
// const week1_start = toUTCDateOnly(period_start);
// const week1_end = endOfDayUTC(addDays(week1_start, 6));
// const week2_start = toUTCDateOnly(addDays(week1_start, 7));
// const week2_end = endOfDayUTC(period_end);
const weeks: WeekDto[] = [
buildWeek(week1_start, week1_end, shifts, expenses),
buildWeek(week2_start, week2_end, shifts, expenses),
];
// const weeks: WeekDto[] = [
// buildWeek(week1_start, week1_end, shifts, expenses),
// buildWeek(week2_start, week2_end, shifts, expenses),
// ];
return {
weeks,
employee_full_name: employeeFullName,
};
}
// return {
// weeks,
// employee_full_name: employeeFullName,
// };
// }

View File

@ -1,137 +1,137 @@
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-timesheet.utils";
import { getWeekEnd, getWeekStart } from "src/common/utils/date-utils";
import { parseISODate, parseHHmm } from "./utils-helpers-others/timesheet.helpers";
import { TimesheetsQueryService } from "./timesheets-query.service";
import { BaseApprovalService } from "src/common/shared/base-approval.service";
import { Prisma, Timesheets } from "@prisma/client";
import { CreateTimesheetDto } from "./create-timesheet.dto";
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
import { PrismaService } from "src/prisma/prisma.service";
import { TimesheetMap } from "./utils-helpers-others/timesheet.types";
import { Shift, Expense } from "../dtos/timesheet.dto";
// import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
// import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-timesheet.utils";
// import { getWeekEnd, getWeekStart } from "src/common/utils/date-utils";
// import { parseISODate, parseHHmm } from "./utils-helpers-others/timesheet.helpers";
// import { TimesheetsQueryService } from "./timesheets-query.service";
// import { BaseApprovalService } from "src/common/shared/base-approval.service";
// import { Prisma, Timesheets } from "@prisma/client";
// import { CreateTimesheetDto } from "./create-timesheet.dto";
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
// import { PrismaService } from "src/prisma/prisma.service";
// import { TimesheetMap } from "./utils-helpers-others/timesheet.types";
// import { Shift, Expense } from "../dtos/timesheet.dto";
@Injectable()
export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
constructor(
prisma: PrismaService,
private readonly query: TimesheetsQueryService,
private readonly emailResolver: EmailToIdResolver,
private readonly timesheetResolver: EmployeeTimesheetResolver,
private readonly bankTypeResolver: BankCodesResolver,
) {super(prisma);}
//_____________________________________________________________________________________________
// APPROVAL AND DELEGATE METHODS
//_____________________________________________________________________________________________
protected get delegate() {
return this.prisma.timesheets;
}
// @Injectable()
// export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
// constructor(
// prisma: PrismaService,
// private readonly query: TimesheetsQueryService,
// private readonly emailResolver: EmailToIdResolver,
// private readonly timesheetResolver: EmployeeTimesheetResolver,
// private readonly bankTypeResolver: BankCodesResolver,
// ) {super(prisma);}
// //_____________________________________________________________________________________________
// // APPROVAL AND DELEGATE METHODS
// //_____________________________________________________________________________________________
// protected get delegate() {
// return this.prisma.timesheets;
// }
protected delegateFor(transaction: Prisma.TransactionClient) {
return transaction.timesheets;
}
// protected delegateFor(transaction: Prisma.TransactionClient) {
// return transaction.timesheets;
// }
async updateApproval(id: number, isApproved: boolean): Promise<Timesheets> {
return this.prisma.$transaction((transaction) =>
this.updateApprovalWithTransaction(transaction, id, isApproved),
);
}
// async updateApproval(id: number, isApproved: boolean): Promise<Timesheets> {
// return this.prisma.$transaction((transaction) =>
// this.updateApprovalWithTransaction(transaction, id, isApproved),
// );
// }
async cascadeApprovalWithtx(transaction: Prisma.TransactionClient, timesheetId: number, isApproved: boolean): Promise<Timesheets> {
const timesheet = await this.updateApprovalWithTransaction(transaction, timesheetId, isApproved);
await transaction.shifts.updateMany({
where: { timesheet_id: timesheetId },
data: { is_approved: isApproved },
});
await transaction.expenses.updateManyAndReturn({
where: { timesheet_id: timesheetId },
data: { is_approved: isApproved },
});
return timesheet;
}
// async cascadeApprovalWithtx(transaction: Prisma.TransactionClient, timesheetId: number, isApproved: boolean): Promise<Timesheets> {
// const timesheet = await this.updateApprovalWithTransaction(transaction, timesheetId, isApproved);
// await transaction.shifts.updateMany({
// where: { timesheet_id: timesheetId },
// data: { is_approved: isApproved },
// });
// await transaction.expenses.updateManyAndReturn({
// where: { timesheet_id: timesheetId },
// data: { is_approved: isApproved },
// });
// return timesheet;
// }
/**_____________________________________________________________________________________________
create/update/delete shifts and expenses from 1 or many timesheet(s)
// /**_____________________________________________________________________________________________
// create/update/delete shifts and expenses from 1 or many timesheet(s)
-this function receives an email and an array of timesheets
// -this function receives an email and an array of timesheets
-this function will find the timesheets with all shifts and expenses
-this function will calculate total hours, total expenses, filtered by types,
cumulate in daily and weekly.
// -this function will find the timesheets with all shifts and expenses
// -this function will calculate total hours, total expenses, filtered by types,
// cumulate in daily and weekly.
-the timesheet_id will be determined using the employee email
-with the timesheet_id, all shifts and expenses will be fetched
// -the timesheet_id will be determined using the employee email
// -with the timesheet_id, all shifts and expenses will be fetched
-with shift_id and expense_id, this function will compare both
datas from the DB and from the body of the function and then:
-it will create a shift if no shift is found in the DB
-it will update a shift if a shift is found in the DB
-it will delete a shift if a shift is found and no data is received from the frontend
// -with shift_id and expense_id, this function will compare both
// datas from the DB and from the body of the function and then:
// -it will create a shift if no shift is found in the DB
// -it will update a shift if a shift is found in the DB
// -it will delete a shift if a shift is found and no data is received from the frontend
This function will be used for the Timesheet Page for an employee to enter, modify or delete and entry
This function will also be used in the modal of the timesheet validation page to
allow a supervisor to enter, modify or delete and entry of a selected employee
_____________________________________________________________________________________________*/
// This function will be used for the Timesheet Page for an employee to enter, modify or delete and entry
// This function will also be used in the modal of the timesheet validation page to
// allow a supervisor to enter, modify or delete and entry of a selected employee
// _____________________________________________________________________________________________*/
async findTimesheetsByEmailAndPayPeriod(email: string, year: number, period_no: number, timesheets: Timesheets[]): Promise<Timesheets[]> {
const employee_id = await this.emailResolver.findIdByEmail(email);
// async findTimesheetsByEmailAndPayPeriod(email: string, year: number, period_no: number, timesheets: Timesheets[]): Promise<Timesheets[]> {
// const employee_id = await this.emailResolver.findIdByEmail(email);
return timesheets;
}
// return timesheets;
// }
async upsertOrDeleteShiftsByEmailAndDate(email:string, shift_ids: Shift[]) {}
// async upsertOrDeleteShiftsByEmailAndDate(email:string, shift_ids: Shift[]) {}
async upsertOrDeleteExpensesByEmailAndDate(email:string, expenses_id: Expense[]) {}
// async upsertOrDeleteExpensesByEmailAndDate(email:string, expenses_id: Expense[]) {}
//_____________________________________________________________________________________________
//
//_____________________________________________________________________________________________
// //_____________________________________________________________________________________________
// //
// //_____________________________________________________________________________________________
async createWeekShiftsAndReturnOverview(
email:string,
shifts: CreateTimesheetDto[],
week_offset = 0,
): Promise<TimesheetMap> {
//fetchs employee matchint user's email
const employee_id = await this.emailResolver.findIdByEmail(email);
if(!employee_id) throw new NotFoundException(`employee for ${ email } not found`);
// async createWeekShiftsAndReturnOverview(
// email:string,
// shifts: CreateTimesheetDto[],
// week_offset = 0,
// ): Promise<TimesheetMap> {
// //fetchs employee matchint user's email
// const employee_id = await this.emailResolver.findIdByEmail(email);
// if(!employee_id) throw new NotFoundException(`employee for ${ email } not found`);
//insure that the week starts on sunday and finishes on saturday
const base = new Date();
base.setDate(base.getDate() + week_offset * 7);
const start_week = getWeekStart(base, 0);
const end_week = getWeekEnd(start_week);
// //insure that the week starts on sunday and finishes on saturday
// const base = new Date();
// base.setDate(base.getDate() + week_offset * 7);
// const start_week = getWeekStart(base, 0);
// const end_week = getWeekEnd(start_week);
const timesheet = await this.timesheetResolver.findTimesheetIdByEmail(email, base)
if(!timesheet) throw new NotFoundException(`no timesheet found for employe ${employee_id}`);
// const timesheet = await this.timesheetResolver.findTimesheetIdByEmail(email, base)
// if(!timesheet) throw new NotFoundException(`no timesheet found for employe ${employee_id}`);
//validations and insertions
for(const shift of shifts) {
const date = parseISODate(shift.date);
if (date < start_week || date > end_week) throw new BadRequestException(`date ${shift.date} not in current week`);
// //validations and insertions
// for(const shift of shifts) {
// const date = parseISODate(shift.date);
// if (date < start_week || date > end_week) throw new BadRequestException(`date ${shift.date} not in current week`);
const bank_code = await this.bankTypeResolver.findByType(shift.type)
if(!bank_code) throw new BadRequestException(`Invalid bank_code type: ${shift.type}`);
// const bank_code = await this.bankTypeResolver.findByType(shift.type)
// if(!bank_code) throw new BadRequestException(`Invalid bank_code type: ${shift.type}`);
await this.prisma.shifts.create({
data: {
timesheet_id: timesheet.id,
bank_code_id: bank_code.id,
date: date,
start_time: parseHHmm(shift.start_time),
end_time: parseHHmm(shift.end_time),
comment: shift.comment ?? null,
is_approved: false,
is_remote: false,
},
});
}
return this.query.getTimesheetByEmail(email, week_offset);
}
}
// await this.prisma.shifts.create({
// data: {
// timesheet_id: timesheet.id,
// bank_code_id: bank_code.id,
// date: date,
// start_time: parseHHmm(shift.start_time),
// end_time: parseHHmm(shift.end_time),
// comment: shift.comment ?? null,
// is_approved: false,
// is_remote: false,
// },
// });
// }
// return this.query.getTimesheetByEmail(email, week_offset);
// }
// }

View File

@ -1,54 +1,54 @@
import { makeEmptyTimesheet, mapExpenseRow, mapShiftRow } from './utils-helpers-others/timesheet.mappers';
import { buildPeriod, computeWeekRange } from './utils-helpers-others/timesheet.utils';
import { TimesheetSelectorsService } from './utils-helpers-others/timesheet.selectors';
import { TimesheetPeriodDto } from './timesheet-period.dto';
import { toRangeFromPeriod } from './utils-helpers-others/timesheet.helpers';
import { EmailToIdResolver } from 'src/modules/shared/utils/resolve-email-id.utils';
import { FullNameResolver } from 'src/modules/shared/utils/resolve-full-name.utils';
import { PrismaService } from 'src/prisma/prisma.service';
import { TimesheetMap } from './utils-helpers-others/timesheet.types';
import { Injectable } from '@nestjs/common';
// import { makeEmptyTimesheet, mapExpenseRow, mapShiftRow } from './utils-helpers-others/timesheet.mappers';
// import { buildPeriod, computeWeekRange } from './utils-helpers-others/timesheet.utils';
// import { TimesheetSelectorsService } from './utils-helpers-others/timesheet.selectors';
// import { TimesheetPeriodDto } from './timesheet-period.dto';
// import { toRangeFromPeriod } from './utils-helpers-others/timesheet.helpers';
// import { EmailToIdResolver } from 'src/modules/shared/utils/resolve-email-id.utils';
// import { FullNameResolver } from 'src/modules/shared/utils/resolve-full-name.utils';
// import { PrismaService } from 'src/prisma/prisma.service';
// import { TimesheetMap } from './utils-helpers-others/timesheet.types';
// import { Injectable } from '@nestjs/common';
@Injectable()
export class TimesheetsQueryService {
constructor(
private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver,
private readonly fullNameResolver: FullNameResolver,
private readonly selectors: TimesheetSelectorsService,
) {}
// @Injectable()
// export class TimesheetsQueryService {
// constructor(
// private readonly prisma: PrismaService,
// private readonly emailResolver: EmailToIdResolver,
// private readonly fullNameResolver: FullNameResolver,
// private readonly selectors: TimesheetSelectorsService,
// ) {}
async findAll(year: number, period_no: number, email: string): Promise<TimesheetPeriodDto> {
const employee_id = await this.emailResolver.findIdByEmail(email); //finds the employee using email
const full_name = await this.fullNameResolver.resolveFullName(employee_id); //finds the employee full name using employee_id
const period = await this.selectors.getPayPeriod(year, period_no);//finds the pay period using year and period_no
const{ from, to } = toRangeFromPeriod(period); //finds start and end dates
//finds all shifts from selected period
const [raw_shifts, raw_expenses] = await Promise.all([
this.selectors.getShifts(employee_id, from, to),
this.selectors.getExpenses(employee_id, from, to),
]);
// data mapping
const shifts = raw_shifts.map(mapShiftRow);
const expenses = raw_expenses.map(mapExpenseRow);
// async findAll(year: number, period_no: number, email: string): Promise<TimesheetPeriodDto> {
// const employee_id = await this.emailResolver.findIdByEmail(email); //finds the employee using email
// const full_name = await this.fullNameResolver.resolveFullName(employee_id); //finds the employee full name using employee_id
// const period = await this.selectors.getPayPeriod(year, period_no);//finds the pay period using year and period_no
// const{ from, to } = toRangeFromPeriod(period); //finds start and end dates
// //finds all shifts from selected period
// const [raw_shifts, raw_expenses] = await Promise.all([
// this.selectors.getShifts(employee_id, from, to),
// this.selectors.getExpenses(employee_id, from, to),
// ]);
// // data mapping
// const shifts = raw_shifts.map(mapShiftRow);
// const expenses = raw_expenses.map(mapExpenseRow);
return buildPeriod(period.period_start, period.period_end, shifts , expenses, full_name);
}
// return buildPeriod(period.period_start, period.period_end, shifts , expenses, full_name);
// }
async getTimesheetByEmail(email: string, week_offset = 0): Promise<TimesheetMap> {
const employee_id = await this.emailResolver.findIdByEmail(email); //finds the employee using email
const { start, start_day, end_day, label } = computeWeekRange(week_offset);
const timesheet = await this.selectors.getTimesheetWithShiftsAndExpenses(employee_id, start); //fetch timesheet shifts and expenses
if(!timesheet) return makeEmptyTimesheet({ start_day, end_day, label});
// async getTimesheetByEmail(email: string, week_offset = 0): Promise<TimesheetMap> {
// const employee_id = await this.emailResolver.findIdByEmail(email); //finds the employee using email
// const { start, start_day, end_day, label } = computeWeekRange(week_offset);
// const timesheet = await this.selectors.getTimesheetWithShiftsAndExpenses(employee_id, start); //fetch timesheet shifts and expenses
// if(!timesheet) return makeEmptyTimesheet({ start_day, end_day, label});
//maps all shifts of selected timesheet
const shifts = timesheet.shift.map(mapShiftRow);
const expenses = timesheet.expense.map(mapExpenseRow);
// //maps all shifts of selected timesheet
// const shifts = timesheet.shift.map(mapShiftRow);
// const expenses = timesheet.expense.map(mapExpenseRow);
return { start_day, end_day, label, shifts, expenses, is_approved: timesheet.is_approved};
}
}
// return { start_day, end_day, label, shifts, expenses, is_approved: timesheet.is_approved};
// }
// }

View File

@ -1,51 +1,51 @@
import { BadRequestException, Body, Controller, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
import { TimesheetsQueryService } from './timesheets-query.service';
import { CreateWeekShiftsDto } from './create-timesheet.dto';
import { RolesAllowed } from "src/common/decorators/roles.decorators";
import { Roles as RoleEnum } from '.prisma/client';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { TimesheetsCommandService } from './timesheets-command.service';
import { TimesheetPeriodDto } from './timesheet-period.dto';
import { TimesheetMap } from './timesheet.types';
// import { BadRequestException, Body, Controller, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
// import { TimesheetsQueryService } from './timesheets-query.service';
// import { CreateWeekShiftsDto } from './create-timesheet.dto';
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
// import { Roles as RoleEnum } from '.prisma/client';
// import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
// import { TimesheetsCommandService } from './timesheets-command.service';
// import { TimesheetPeriodDto } from './timesheet-period.dto';
// import { TimesheetMap } from './timesheet.types';
@ApiTags('Timesheets')
@ApiBearerAuth('access-token')
// @UseGuards()
@Controller('timesheets')
export class TimesheetsController {
constructor(
private readonly timesheetsQuery: TimesheetsQueryService,
private readonly timesheetsCommand: TimesheetsCommandService,
) {}
// @ApiTags('Timesheets')
// @ApiBearerAuth('access-token')
// // @UseGuards()
// @Controller('timesheets')
// export class TimesheetsController {
// constructor(
// private readonly timesheetsQuery: TimesheetsQueryService,
// private readonly timesheetsCommand: TimesheetsCommandService,
// ) {}
@Get()
//@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
async getPeriodByQuery(
@Query('year', ParseIntPipe ) year: number,
@Query('period_no', ParseIntPipe ) period_no: number,
@Query('email') email?: string
): Promise<TimesheetPeriodDto> {
if(!email || !(email = email.trim())) throw new BadRequestException('Query param "email" is mandatory for this route.');
return this.timesheetsQuery.findAll(year, period_no, email);
}
// @Get()
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
// async getPeriodByQuery(
// @Query('year', ParseIntPipe ) year: number,
// @Query('period_no', ParseIntPipe ) period_no: number,
// @Query('email') email?: string
// ): Promise<TimesheetPeriodDto> {
// if(!email || !(email = email.trim())) throw new BadRequestException('Query param "email" is mandatory for this route.');
// return this.timesheetsQuery.findAll(year, period_no, email);
// }
@Get('/:email')
async getByEmail(
@Param('email') email: string,
@Query('offset') offset?: string,
): Promise<TimesheetMap> {
const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
return this.timesheetsQuery.getTimesheetByEmail(email, week_offset);
}
// @Get('/:email')
// async getByEmail(
// @Param('email') email: string,
// @Query('offset') offset?: string,
// ): Promise<TimesheetMap> {
// const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
// return this.timesheetsQuery.getTimesheetByEmail(email, week_offset);
// }
@Post('shifts/:email')
async createTimesheetShifts(
@Param('email') email: string,
@Body() dto: CreateWeekShiftsDto,
@Query('offset') offset?: string,
): Promise<TimesheetMap> {
const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
return this.timesheetsCommand.createWeekShiftsAndReturnOverview(email, dto.shifts, week_offset);
}
}
// @Post('shifts/:email')
// async createTimesheetShifts(
// @Param('email') email: string,
// @Body() dto: CreateWeekShiftsDto,
// @Query('offset') offset?: string,
// ): Promise<TimesheetMap> {
// const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
// return this.timesheetsCommand.createWeekShiftsAndReturnOverview(email, dto.shifts, week_offset);
// }
// }