From 44da99e7c16fe1b3d0c3f29e825023847be5a533 Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Thu, 7 Aug 2025 09:11:50 -0400 Subject: [PATCH] feat(module): search and filter querries for shifts, expenses, timesheets, leave-requests --- src/common/shared/build-prisma-where.ts | 21 ++++++++++ .../controllers/expenses.controller.ts | 12 +++--- ...reate-expense.ts => create-expense.dto.ts} | 0 .../expenses/dtos/search-expense.dto.ts | 26 ++++++++++++ ...pdate-expense.ts => update-expense.dto.ts} | 2 +- src/modules/expenses/expenses.module.ts | 1 - .../expenses/services/expenses.service.ts | 14 ++++--- .../controllers/leave-requests.controller.ts | 22 +++++++--- .../dtos/search-leave-requests.dto.ts | 27 +++++++++++++ .../services/leave-requests.service.ts | 15 ++++++- .../controllers/shifts-overview.controller.ts | 4 +- .../shifts/controllers/shifts.controller.ts | 12 +++--- ...eate-shifts.dto.ts => create-shift.dto.ts} | 0 .../shifts/dtos/get-shifts-overview.dto.ts | 2 +- src/modules/shifts/dtos/search-shifts.dto.ts | 29 ++++++++++++++ ...date-shifts.dto.ts => update-shift.dto.ts} | 2 +- .../services/shifts-overview.service.ts | 6 +-- src/modules/shifts/services/shifts.service.ts | 14 ++++--- .../controllers/timesheets.controller.ts | 8 ++-- .../timesheets/dtos/search-timesheets.dto.ts | 20 ++++++++++ .../timesheets/services/timesheets.service.ts | 40 +++++++++++-------- 21 files changed, 221 insertions(+), 56 deletions(-) create mode 100644 src/common/shared/build-prisma-where.ts rename src/modules/expenses/dtos/{create-expense.ts => create-expense.dto.ts} (100%) create mode 100644 src/modules/expenses/dtos/search-expense.dto.ts rename src/modules/expenses/dtos/{update-expense.ts => update-expense.dto.ts} (67%) create mode 100644 src/modules/leave-requests/dtos/search-leave-requests.dto.ts rename src/modules/shifts/dtos/{create-shifts.dto.ts => create-shift.dto.ts} (100%) create mode 100644 src/modules/shifts/dtos/search-shifts.dto.ts rename src/modules/shifts/dtos/{update-shifts.dto.ts => update-shift.dto.ts} (67%) create mode 100644 src/modules/timesheets/dtos/search-timesheets.dto.ts diff --git a/src/common/shared/build-prisma-where.ts b/src/common/shared/build-prisma-where.ts new file mode 100644 index 0000000..1b8f8fc --- /dev/null +++ b/src/common/shared/build-prisma-where.ts @@ -0,0 +1,21 @@ +//Prisma 'where' clause for DTO filters +export function buildPrismaWhere>(dto: T): Record { + const where: Record = {}; + + for (const [key,value] of Object.entries(dto)) { + if (value === undefined || value === null) continue; + + if (key.endsWith('_contains')) { + const field = key.slice(0, - '_contains'.length); + where[field] = { constains: value }; + } else if (key === 'start_date' || key === 'end_date') { + where.date = where.date || {}; + const op = key === 'start_date' ? 'gte' : 'lte'; + where.date[op] = new Date(value); + } else { + where[key] = value; + } + } + + return where; +} \ No newline at end of file diff --git a/src/modules/expenses/controllers/expenses.controller.ts b/src/modules/expenses/controllers/expenses.controller.ts index 36e64c5..671bc33 100644 --- a/src/modules/expenses/controllers/expenses.controller.ts +++ b/src/modules/expenses/controllers/expenses.controller.ts @@ -1,14 +1,15 @@ -import { Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, UseGuards } from "@nestjs/common"; +import { Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, Query, UseGuards, UsePipes, ValidationPipe } from "@nestjs/common"; import { ExpensesService } from "../services/expenses.service"; -import { CreateExpenseDto } from "../dtos/create-expense"; +import { CreateExpenseDto } from "../dtos/create-expense.dto"; import { Expenses } from "@prisma/client"; import { Roles as RoleEnum } from '.prisma/client'; -import { UpdateExpenseDto } from "../dtos/update-expense"; +import { UpdateExpenseDto } from "../dtos/update-expense.dto"; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { JwtAuthGuard } from "src/modules/authentication/guards/jwt-auth.guard"; import { ExpenseEntity } from "../dtos/swagger-entities/expenses.entity"; import { ExpensesApprovalService } from "../services/expenses-approval.service"; +import { SearchExpensesDto } from "../dtos/search-expense.dto"; @ApiTags('Expenses') @ApiBearerAuth('access-token') @@ -34,8 +35,9 @@ export class ExpensesController { @ApiOperation({ summary: 'Find all expenses' }) @ApiResponse({ status: 201, description: 'List of expenses found',type: ExpenseEntity, isArray: true }) @ApiResponse({ status: 400, description: 'List of expenses not found' }) - findAll(): Promise { - return this.expensesService.findAll(); + @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) + findAll(@Query() filters: SearchExpensesDto): Promise { + return this.expensesService.findAll(filters); } @Get(':id') diff --git a/src/modules/expenses/dtos/create-expense.ts b/src/modules/expenses/dtos/create-expense.dto.ts similarity index 100% rename from src/modules/expenses/dtos/create-expense.ts rename to src/modules/expenses/dtos/create-expense.dto.ts diff --git a/src/modules/expenses/dtos/search-expense.dto.ts b/src/modules/expenses/dtos/search-expense.dto.ts new file mode 100644 index 0000000..5058167 --- /dev/null +++ b/src/modules/expenses/dtos/search-expense.dto.ts @@ -0,0 +1,26 @@ +import { Type } from "class-transformer"; +import { IsDateString, IsInt, IsOptional, IsString } from "class-validator"; + +export class SearchExpensesDto { + @IsOptional() + @Type(()=> Number) + @IsInt() + timesheet_id?: number; + + @IsOptional() + @Type(()=> Number) + @IsInt() + bank_code_id?: number; + + @IsOptional() + @IsString() + description_contains?: string; + + @IsOptional() + @IsDateString() + start_date: string; + + @IsOptional() + @IsDateString() + end_date: string; +} \ No newline at end of file diff --git a/src/modules/expenses/dtos/update-expense.ts b/src/modules/expenses/dtos/update-expense.dto.ts similarity index 67% rename from src/modules/expenses/dtos/update-expense.ts rename to src/modules/expenses/dtos/update-expense.dto.ts index dda40eb..1b2426f 100644 --- a/src/modules/expenses/dtos/update-expense.ts +++ b/src/modules/expenses/dtos/update-expense.dto.ts @@ -1,4 +1,4 @@ import { PartialType } from "@nestjs/swagger"; -import { CreateExpenseDto } from "./create-expense"; +import { CreateExpenseDto } from "./create-expense.dto"; export class UpdateExpenseDto extends PartialType(CreateExpenseDto) {} \ No newline at end of file diff --git a/src/modules/expenses/expenses.module.ts b/src/modules/expenses/expenses.module.ts index 527d783..fec1a3d 100644 --- a/src/modules/expenses/expenses.module.ts +++ b/src/modules/expenses/expenses.module.ts @@ -1,4 +1,3 @@ -import { PrismaService } from "src/prisma/prisma.service"; import { ExpensesController } from "./controllers/expenses.controller"; import { Module } from "@nestjs/common"; import { ExpensesService } from "./services/expenses.service"; diff --git a/src/modules/expenses/services/expenses.service.ts b/src/modules/expenses/services/expenses.service.ts index 48b05b3..9d1515c 100644 --- a/src/modules/expenses/services/expenses.service.ts +++ b/src/modules/expenses/services/expenses.service.ts @@ -1,9 +1,11 @@ import { Injectable, NotFoundException } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; -import { CreateExpenseDto } from "../dtos/create-expense"; +import { CreateExpenseDto } from "../dtos/create-expense.dto"; import { Expenses, ExpensesArchive } from "@prisma/client"; -import { UpdateExpenseDto } from "../dtos/update-expense"; +import { UpdateExpenseDto } from "../dtos/update-expense.dto"; import { MileageService } from "src/modules/business-logics/services/mileage.service"; +import { SearchExpensesDto } from "../dtos/search-expense.dto"; +import { buildPrismaWhere } from "src/common/shared/build-prisma-where"; @Injectable() export class ExpensesService { @@ -42,10 +44,10 @@ export class ExpensesService { }) } - findAll(): Promise { - return this.prisma.expenses.findMany({ - include: { timesheet: { include: { employee: { include: { user: true } } } } }, - }); + async findAll(filters: SearchExpensesDto): Promise { + const where = buildPrismaWhere(filters); + const expenses = await this.prisma.expenses.findMany({ where }) + return expenses; } async findOne(id: number): Promise { diff --git a/src/modules/leave-requests/controllers/leave-requests.controller.ts b/src/modules/leave-requests/controllers/leave-requests.controller.ts index 9a27b4b..d97bc37 100644 --- a/src/modules/leave-requests/controllers/leave-requests.controller.ts +++ b/src/modules/leave-requests/controllers/leave-requests.controller.ts @@ -1,13 +1,14 @@ -import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from "@nestjs/common"; +import { BadRequestException, Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseEnumPipe, ParseIntPipe, Patch, Post, Query, UseGuards, UsePipes, ValidationPipe } from "@nestjs/common"; import { LeaveRequestsService } from "../services/leave-requests.service"; import { CreateLeaveRequestsDto } from "../dtos/create-leave-requests.dto"; import { LeaveRequests } from "@prisma/client"; import { UpdateLeaveRequestsDto } from "../dtos/update-leave-requests.dto"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; -import { Roles as RoleEnum } from '.prisma/client'; +import { LeaveApprovalStatus, Roles as RoleEnum } from '.prisma/client'; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { JwtAuthGuard } from "src/modules/authentication/guards/jwt-auth.guard"; import { LeaveRequestEntity } from "../dtos/swagger-entities/leave-requests.entity"; +import { SearchLeaveRequestsDto } from "../dtos/search-leave-requests.dto"; @ApiTags('Leave Requests') @ApiBearerAuth('access-token') @@ -30,8 +31,9 @@ export class LeaveRequestController { @ApiOperation({summary: 'Find all leave request' }) @ApiResponse({ status: 201, description: 'List of Leave requests found',type: LeaveRequestEntity, isArray: true }) @ApiResponse({ status: 400, description: 'List of leave request not found' }) - findAll(): Promise { - return this.leaveRequetsService.findAll(); + @UsePipes(new ValidationPipe({transform: true, whitelist: true})) + findAll(@Query() filters: SearchLeaveRequestsDto): Promise<(LeaveRequests & {daysRequested:number; cost: number})[]> { + return this.leaveRequetsService.findAll(filters); } @Get(':id') @@ -60,4 +62,14 @@ export class LeaveRequestController { remove(@Param('id', ParseIntPipe) id: number): Promise { return this.leaveRequetsService.remove(id); } -} \ No newline at end of file + + @Patch(':id/approval') + updateApproval( @Param('id', ParseIntPipe) id: number, + @Body('is_approved', ParseBoolPipe) isApproved: boolean): Promise { + const approvalStatus = isApproved ? + LeaveApprovalStatus.APPROVED : LeaveApprovalStatus.DENIED; + return this.leaveRequetsService.update(id, { approval_status: approvalStatus }); + } + + } + diff --git a/src/modules/leave-requests/dtos/search-leave-requests.dto.ts b/src/modules/leave-requests/dtos/search-leave-requests.dto.ts new file mode 100644 index 0000000..cfa566b --- /dev/null +++ b/src/modules/leave-requests/dtos/search-leave-requests.dto.ts @@ -0,0 +1,27 @@ +import { LeaveApprovalStatus } from "@prisma/client"; +import { Type } from "class-transformer"; +import { IsOptional, IsInt, IsEnum, IsDateString } from "class-validator"; + +export class SearchLeaveRequestsDto { + @IsOptional() + @Type(()=> Number) + @IsInt() + employee_id?: number; + + @IsOptional() + @Type(()=> Number) + @IsInt() + bank_code_id?: number; + + @IsOptional() + @IsEnum(LeaveApprovalStatus) + approval_status?: LeaveApprovalStatus + + @IsOptional() + @IsDateString() + start_date?: Date; + + @IsOptional() + @IsDateString() + end_date?: Date; +} \ No newline at end of file diff --git a/src/modules/leave-requests/services/leave-requests.service.ts b/src/modules/leave-requests/services/leave-requests.service.ts index 2cd2bed..0be337e 100644 --- a/src/modules/leave-requests/services/leave-requests.service.ts +++ b/src/modules/leave-requests/services/leave-requests.service.ts @@ -6,6 +6,8 @@ import { UpdateLeaveRequestsDto } from "../dtos/update-leave-requests.dto"; 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 { SearchLeaveRequestsDto } from "../dtos/search-leave-requests.dto"; +import { buildPrismaWhere } from "src/common/shared/build-prisma-where"; @Injectable() export class LeaveRequestsService { @@ -30,8 +32,19 @@ export class LeaveRequestsService { }); } - async findAll(): Promise { + async findAll(filters: SearchLeaveRequestsDto): Promise { + const {start_date, end_date, ...otherFilters } = filters; + const where: Record = buildPrismaWhere(otherFilters); + + if (start_date) { + where.start_date_time = { ...(where.start_date_time ?? {}), gte: new Date(start_date) }; + } + if(end_date) { + where.end_date_time = { ...(where.end_date_time ?? {}), lte: new Date(end_date) }; + } + const list = await this.prisma.leaveRequests.findMany({ + where, include: { employee: { include: { user: true } }, bank_code: true, }, diff --git a/src/modules/shifts/controllers/shifts-overview.controller.ts b/src/modules/shifts/controllers/shifts-overview.controller.ts index 3958286..d1ef6d4 100644 --- a/src/modules/shifts/controllers/shifts-overview.controller.ts +++ b/src/modules/shifts/controllers/shifts-overview.controller.ts @@ -8,14 +8,14 @@ export class ShiftsOverviewController { @Get() async getSummary( @Query() query: GetShiftsOverviewDto): Promise { - return this.shiftsValidationService.getSummary(query.periodId); + return this.shiftsValidationService.getSummary(query.period_id); } @Get('export.csv') @Header('Content-Type', 'text/csv; charset=utf-8') @Header('Content-Disposition', 'attachment; filename="shifts-validation.csv"') async exportCsv(@Query() query: GetShiftsOverviewDto): Promise{ - const rows = await this.shiftsValidationService.getSummary(query.periodId); + const rows = await this.shiftsValidationService.getSummary(query.period_id); //CSV Headers const header = [ diff --git a/src/modules/shifts/controllers/shifts.controller.ts b/src/modules/shifts/controllers/shifts.controller.ts index 981eb00..faf0384 100644 --- a/src/modules/shifts/controllers/shifts.controller.ts +++ b/src/modules/shifts/controllers/shifts.controller.ts @@ -1,14 +1,15 @@ -import { Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, UseGuards } from "@nestjs/common"; +import { Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, Query, UseGuards, UsePipes, ValidationPipe } from "@nestjs/common"; import { ShiftsService } from "../services/shifts.service"; import { Shifts } from "@prisma/client"; -import { CreateShiftDto } from "../dtos/create-shifts.dto"; -import { UpdateShiftsDto } from "../dtos/update-shifts.dto"; +import { CreateShiftDto } from "../dtos/create-shift.dto"; +import { UpdateShiftsDto } from "../dtos/update-shift.dto"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { Roles as RoleEnum } from '.prisma/client'; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { JwtAuthGuard } from "src/modules/authentication/guards/jwt-auth.guard"; import { ShiftEntity } from "../dtos/swagger-entities/shift.entity"; import { ShiftsApprovalService } from "../services/shifts-approval.service"; +import { SearchShiftsDto } from "../dtos/search-shifts.dto"; @ApiTags('Shifts') @ApiBearerAuth('access-token') @@ -34,8 +35,9 @@ export class ShiftsController { @ApiOperation({ summary: 'Find all shifts' }) @ApiResponse({ status: 201, description: 'List of shifts found',type: ShiftEntity, isArray: true }) @ApiResponse({ status: 400, description: 'List of shifts not found' }) - findAll(): Promise { - return this.shiftsService.findAll(); + @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) + findAll(@Query() filters: SearchShiftsDto) { + return this.shiftsService.findAll(filters); } @Get(':id') diff --git a/src/modules/shifts/dtos/create-shifts.dto.ts b/src/modules/shifts/dtos/create-shift.dto.ts similarity index 100% rename from src/modules/shifts/dtos/create-shifts.dto.ts rename to src/modules/shifts/dtos/create-shift.dto.ts diff --git a/src/modules/shifts/dtos/get-shifts-overview.dto.ts b/src/modules/shifts/dtos/get-shifts-overview.dto.ts index 8d2dfc0..e8ccdd2 100644 --- a/src/modules/shifts/dtos/get-shifts-overview.dto.ts +++ b/src/modules/shifts/dtos/get-shifts-overview.dto.ts @@ -6,5 +6,5 @@ export class GetShiftsOverviewDto { @IsInt() @Min(1) @Max(26) - periodId: number; + period_id: number; } \ No newline at end of file diff --git a/src/modules/shifts/dtos/search-shifts.dto.ts b/src/modules/shifts/dtos/search-shifts.dto.ts new file mode 100644 index 0000000..b1b772d --- /dev/null +++ b/src/modules/shifts/dtos/search-shifts.dto.ts @@ -0,0 +1,29 @@ +import { Type } from "class-transformer"; +import { IsDateString, IsInt, IsOptional, IsString } from "class-validator"; + +export class SearchShiftsDto { + @IsOptional() + @Type(()=> Number) + @IsInt() + employee_id?: number; + + @IsOptional() + @Type(()=> Number) + @IsInt() + bank_code_id?: number; + + @IsOptional() + @IsString() + description_contains?: string; + + @IsOptional() + @IsDateString() + end_date?: string; + + @IsOptional() + @Type(()=> Number) + @IsInt() + timesheet_id?: number; + + +} \ No newline at end of file diff --git a/src/modules/shifts/dtos/update-shifts.dto.ts b/src/modules/shifts/dtos/update-shift.dto.ts similarity index 67% rename from src/modules/shifts/dtos/update-shifts.dto.ts rename to src/modules/shifts/dtos/update-shift.dto.ts index 8b10139..53f033f 100644 --- a/src/modules/shifts/dtos/update-shifts.dto.ts +++ b/src/modules/shifts/dtos/update-shift.dto.ts @@ -1,4 +1,4 @@ import { PartialType } from "@nestjs/swagger"; -import { CreateShiftDto } from "./create-shifts.dto"; +import { CreateShiftDto } from "./create-shift.dto"; export class UpdateShiftsDto extends PartialType(CreateShiftDto){} \ No newline at end of file diff --git a/src/modules/shifts/services/shifts-overview.service.ts b/src/modules/shifts/services/shifts-overview.service.ts index d5544dd..ae539f1 100644 --- a/src/modules/shifts/services/shifts-overview.service.ts +++ b/src/modules/shifts/services/shifts-overview.service.ts @@ -17,13 +17,13 @@ export interface OverviewRow { export class ShiftsOverviewService { constructor(private readonly prisma: PrismaService) {} - async getSummary(periodId: number): Promise { + async getSummary(period_id: number): Promise { //fetch pay-period to display const period = await this.prisma.payPeriods.findUnique({ - where: { period_number: periodId }, + where: { period_number: period_id }, }); if(!period) { - throw new NotFoundException(`pay-period ${periodId} not found`); + throw new NotFoundException(`pay-period ${period_id} not found`); } const { start_date, end_date } = period; diff --git a/src/modules/shifts/services/shifts.service.ts b/src/modules/shifts/services/shifts.service.ts index d1961c0..6a8ae99 100644 --- a/src/modules/shifts/services/shifts.service.ts +++ b/src/modules/shifts/services/shifts.service.ts @@ -1,8 +1,10 @@ import { Injectable, NotFoundException } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; -import { CreateShiftDto } from "../dtos/create-shifts.dto"; +import { CreateShiftDto } from "../dtos/create-shift.dto"; import { Shifts, ShiftsArchive } from "@prisma/client"; -import { UpdateShiftsDto } from "../dtos/update-shifts.dto"; +import { UpdateShiftsDto } from "../dtos/update-shift.dto"; +import { buildPrismaWhere } from "src/common/shared/build-prisma-where"; +import { SearchShiftsDto } from "../dtos/search-shifts.dto"; @Injectable() export class ShiftsService { @@ -18,10 +20,10 @@ export class ShiftsService { }); } - findAll(): Promise { - return this.prisma.shifts.findMany({ - include: { timesheet: { include: { employee: { include: { user:true } } } } }, - }); + async findAll(filters: SearchShiftsDto): Promise { + const where = buildPrismaWhere(filters); + const shifts = await this.prisma.shifts.findMany({ where }) + return shifts; } async findOne(id: number): Promise { diff --git a/src/modules/timesheets/controllers/timesheets.controller.ts b/src/modules/timesheets/controllers/timesheets.controller.ts index 504d66b..9352efd 100644 --- a/src/modules/timesheets/controllers/timesheets.controller.ts +++ b/src/modules/timesheets/controllers/timesheets.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, UseGuards } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, Query, UseGuards, UsePipes, ValidationPipe } from '@nestjs/common'; import { TimesheetsService } from '../services/timesheets.service'; import { CreateTimesheetDto } from '../dtos/create-timesheet.dto'; import { Timesheets } from '@prisma/client'; @@ -9,6 +9,7 @@ import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagg import { JwtAuthGuard } from 'src/modules/authentication/guards/jwt-auth.guard'; import { TimesheetEntity } from '../dtos/swagger-entities/timesheet.entity'; import { TimesheetsApprovalService } from '../services/timesheets-approval.service'; +import { SearchTimesheetDto } from '../dtos/search-timesheets.dto'; @ApiTags('Timesheets') @ApiBearerAuth('access-token') @@ -34,8 +35,9 @@ export class TimesheetsController { @ApiOperation({ summary: 'Find all timesheets' }) @ApiResponse({ status: 201, description: 'List of timesheet found', type: TimesheetEntity, isArray: true }) @ApiResponse({ status: 400, description: 'List of timesheets not found' }) - findAll(): Promise { - return this.timesheetsService.findAll(); + @UsePipes(new ValidationPipe({transform: true, whitelist: true })) + findAll(@Query() filters: SearchTimesheetDto): Promise { + return this.timesheetsService.findAll(filters); } @Get(':id') diff --git a/src/modules/timesheets/dtos/search-timesheets.dto.ts b/src/modules/timesheets/dtos/search-timesheets.dto.ts new file mode 100644 index 0000000..0d61d59 --- /dev/null +++ b/src/modules/timesheets/dtos/search-timesheets.dto.ts @@ -0,0 +1,20 @@ +import { Type } from "class-transformer"; +import { IsBoolean, IsInt, IsOptional } from "class-validator"; + + +export class SearchTimesheetDto { + @IsOptional() + @Type(() => Number) + @IsInt() + timesheet_id: number; + + @IsOptional() + @Type(()=> Number) + @IsInt() + employee_id: number; + + @IsOptional() + @Type(()=> Boolean) + @IsBoolean() + is_approved: boolean; +} \ No newline at end of file diff --git a/src/modules/timesheets/services/timesheets.service.ts b/src/modules/timesheets/services/timesheets.service.ts index 4ee0c73..97cfda5 100644 --- a/src/modules/timesheets/services/timesheets.service.ts +++ b/src/modules/timesheets/services/timesheets.service.ts @@ -5,6 +5,8 @@ import { Timesheets, TimesheetsArchive } from '@prisma/client'; import { UpdateTimesheetDto } from '../dtos/update-timesheet.dto'; import { OvertimeService } from 'src/modules/business-logics/services/overtime.service'; import { computeHours } from 'src/common/utils/date-utils'; +import { buildPrismaWhere } from 'src/common/shared/build-prisma-where'; +import { SearchTimesheetDto } from '../dtos/search-timesheets.dto'; @Injectable() export class TimesheetsService { @@ -24,32 +26,38 @@ export class TimesheetsService { }); } - async findAll(): Promise { - const list = await this.prisma.timesheets.findMany({ - include: { - shift: { include: { bank_code: true } }, - expense: { include: { bank_code: true } }, - employee: { include: { user : true } }, + async findAll(filters: SearchTimesheetDto): Promise { + const where = buildPrismaWhere(filters); + + //fetchs lists of shifts and expenses for a selected timesheet + const rawlist = await this.prisma.timesheets.findMany({ + where, include: { + shift: { include: {bank_code: true } }, + expense: { include: { bank_code: true } }, + employee: { include: { user : true } }, }, }); - return Promise.all( - list.map(async timesheet => { - const detailedShifts = timesheet.shift.map(s => { - const hours = computeHours(s.start_time, s.end_time,5); - const regularHours = Math.min(8, hours); - const dailyOvertime = this.overtime.getDailyOvertimeHours(s.start_time, s.end_time); - const payRegular = regularHours * s.bank_code.modifier; - const payOvertime = this.overtime.calculateOvertimePay(dailyOvertime, s.bank_code.modifier); - return { ...s, hours, payRegular, payOvertime }; + const detailedlist = await Promise.all( + rawlist.map(async timesheet => { + //detailed shifts + const detailedShifts = timesheet.shift.map(shift => { + const totalhours = computeHours(shift.start_time, shift.end_time,5); + const regularHours = Math.min(8, totalhours); + const dailyOvertime = this.overtime.getDailyOvertimeHours(shift.start_time, shift.end_time); + const payRegular = regularHours * shift.bank_code.modifier; + const payOvertime = this.overtime.calculateOvertimePay(dailyOvertime, shift.bank_code.modifier); + return { ...shift, totalhours, payRegular, payOvertime }; }); + //calculate overtime weekly const weeklyOvertimeHours = detailedShifts.length ? await this.overtime.getWeeklyOvertimeHours( timesheet.employee_id, timesheet.shift[0].date): 0; return { ...timesheet, shift: detailedShifts, weeklyOvertimeHours }; - }) + }), ); + return detailedlist; } async findOne(id: number): Promise {