From cc310e286d50a8d1fa483db60850787bb2d36c21 Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Tue, 7 Oct 2025 13:51:35 -0400 Subject: [PATCH] feat(expenses): method implementation to show a list of all expenses made by an employee using email, year and period_no --- docs/swagger/swagger-spec.json | 44 +++++++++ .../controllers/expenses.controller.ts | 15 ++- .../services/expenses-query.service.ts | 91 ++++++++++++++++++- 3 files changed, 143 insertions(+), 7 deletions(-) diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index 8170ec6..106add4 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -393,6 +393,50 @@ ] } }, + "/Expenses/list/{email}/{year}/{period_no}": { + "get": { + "operationId": "ExpensesController_findExpenseListByPayPeriodAndEmail", + "parameters": [ + { + "name": "email", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "year", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + }, + { + "name": "period_no", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "access-token": [] + } + ], + "tags": [ + "Expenses" + ] + } + }, "/shifts/upsert/{email}/{date}": { "put": { "operationId": "ShiftsController_upsert_by_date", diff --git a/src/modules/expenses/controllers/expenses.controller.ts b/src/modules/expenses/controllers/expenses.controller.ts index 03173e9..11bef7f 100644 --- a/src/modules/expenses/controllers/expenses.controller.ts +++ b/src/modules/expenses/controllers/expenses.controller.ts @@ -1,10 +1,12 @@ -import { Body, Controller, Param, Put, } from "@nestjs/common"; +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/dtos/timesheet-period.dto"; +import { ExpensesQueryService } from "../services/expenses-query.service"; @ApiTags('Expenses') @ApiBearerAuth('access-token') @@ -12,7 +14,7 @@ import { UpsertExpenseResult } from "../types and interfaces/expenses.types.inte @Controller('Expenses') export class ExpensesController { constructor( - // private readonly query: ExpensesQueryService, + private readonly query: ExpensesQueryService, private readonly command: ExpensesCommandService, ) {} @@ -25,6 +27,15 @@ export class ExpensesController { 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 { + return this.query.findExpenseListByPayPeriodAndEmail(email, year, period_no); + } + //_____________________________________________________________________________________________ // Deprecated or unused methods //_____________________________________________________________________________________________ diff --git a/src/modules/expenses/services/expenses-query.service.ts b/src/modules/expenses/services/expenses-query.service.ts index e82e0a4..9bfdca6 100644 --- a/src/modules/expenses/services/expenses-query.service.ts +++ b/src/modules/expenses/services/expenses-query.service.ts @@ -1,11 +1,92 @@ -import { Injectable } from "@nestjs/common"; +import { Injectable, NotFoundException } from "@nestjs/common"; +import { PrismaService } from "src/prisma/prisma.service"; +import { DayExpensesDto, ExpenseDto } from "src/modules/timesheets/dtos/timesheet-period.dto"; +import { EmployeesRepo } from "../repos/employee.repo"; +import { round2, toUTCDateOnly } from "src/modules/timesheets/utils/timesheet.helpers"; +import { EXPENSE_TYPES } from "src/modules/timesheets/types/timesheet.types"; @Injectable() export class ExpensesQueryService { - // constructor( - // private readonly prisma: PrismaService, - // private readonly mileageService: MileageService, - // ) {} + constructor( + private readonly prisma: PrismaService, + private readonly employeeRepo: EmployeesRepo, + ) {} + + + //fetchs all expenses for a selected employee using email, pay-period-year and number + async findExpenseListByPayPeriodAndEmail( + email: string, + year: number, + period_no: number + ): Promise { + //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`); + + 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 } }, + }, + }); + + //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)); + + 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 ?? '', + }); + } + + return { + expenses, + total_expense: round2(total_amount), + total_mileage: round2(total_mileage), + }; +} //_____________________________________________________________________________________________ // Deprecated or unused methods