diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index 1ecbc6b..15ab59d 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -429,6 +429,359 @@ ] } }, + "/pay-periods/current-and-all": { + "get": { + "operationId": "PayPeriodsController_getCurrentAndAll", + "parameters": [ + { + "name": "date", + "required": false, + "in": "query", + "description": "Override for resolving the current period", + "schema": { + "example": "2025-08-11", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Find current and all pay periods", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayPeriodBundleDto" + } + } + } + } + }, + "summary": "Return current pay period and the full list", + "tags": [ + "pay-periods" + ] + } + }, + "/pay-periods/date/{date}": { + "get": { + "operationId": "PayPeriodsController_findByDate", + "parameters": [ + { + "name": "date", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Pay period found for the selected date", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayPeriodDto" + } + } + } + }, + "404": { + "description": "Pay period not found for the selected date" + } + }, + "summary": "Resolve a period by a date within it", + "tags": [ + "pay-periods" + ] + } + }, + "/pay-periods/{year}/{periodNumber}": { + "get": { + "operationId": "PayPeriodsController_findOneByYear", + "parameters": [ + { + "name": "year", + "required": true, + "in": "path", + "schema": { + "example": 2024, + "type": "number" + } + }, + { + "name": "periodNumber", + "required": true, + "in": "path", + "description": "1..26", + "schema": { + "example": 1, + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "Pay period found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayPeriodDto" + } + } + } + }, + "404": { + "description": "Pay period not found" + } + }, + "summary": "Find pay period by year and period number", + "tags": [ + "pay-periods" + ] + } + }, + "/pay-periods/{year}/{periodNumber}/{email}": { + "get": { + "operationId": "PayPeriodsController_getCrewOverview", + "parameters": [ + { + "name": "year", + "required": true, + "in": "path", + "schema": { + "example": 2024, + "type": "number" + } + }, + { + "name": "periodNumber", + "required": true, + "in": "path", + "description": "1..26", + "schema": { + "example": 1, + "type": "number" + } + }, + { + "name": "email", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "includeSubtree", + "required": false, + "in": "query", + "description": "Include indirect reports", + "schema": { + "example": false, + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Crew overview", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayPeriodOverviewDto" + } + } + } + }, + "404": { + "description": "Pay period not found" + } + }, + "summary": "Supervisor crew overview for a given pay period", + "tags": [ + "pay-periods" + ] + } + }, + "/pay-periods/overview/{year}/{periodNumber}": { + "get": { + "operationId": "PayPeriodsController_getOverviewByYear", + "parameters": [ + { + "name": "year", + "required": true, + "in": "path", + "schema": { + "example": 2024, + "type": "number" + } + }, + { + "name": "periodNumber", + "required": true, + "in": "path", + "description": "1..26", + "schema": { + "example": 1, + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "Pay period overview found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PayPeriodOverviewDto" + } + } + } + }, + "404": { + "description": "Pay period not found" + } + }, + "summary": "Detailed view of a pay period by year + number", + "tags": [ + "pay-periods" + ] + } + }, + "/timesheets": { + "get": { + "operationId": "TimesheetController_getTimesheetByIds", + "parameters": [ + { + "name": "employee_email", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "year", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "period_number", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Timesheet" + ] + } + }, + "/shift": { + "get": { + "operationId": "ShiftController_getShiftsByIds", + "parameters": [ + { + "name": "shift_ids", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Shift" + ] + }, + "patch": { + "operationId": "ShiftController_updateBatch", + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Shift" + ] + } + }, + "/shift/{timesheet_id}": { + "post": { + "operationId": "ShiftController_createBatch", + "parameters": [ + { + "name": "timesheet_id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "Shift" + ] + } + }, + "/shift/{shift_id}": { + "delete": { + "operationId": "ShiftController_remove", + "parameters": [ + { + "name": "shift_id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Shift" + ] + } + }, "/preferences/{email}": { "patch": { "operationId": "PreferencesController_updatePreferences", @@ -562,139 +915,6 @@ "SchedulePresets" ] } - }, - "/shift": { - "get": { - "operationId": "ShiftController_getShiftsByIds", - "parameters": [ - { - "name": "shift_ids", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Shift" - ] - }, - "patch": { - "operationId": "ShiftController_updateBatch", - "parameters": [], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Shift" - ] - } - }, - "/shift/{timesheet_id}": { - "post": { - "operationId": "ShiftController_createBatch", - "parameters": [ - { - "name": "timesheet_id", - "required": true, - "in": "path", - "schema": { - "type": "number" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "Shift" - ] - } - }, - "/shift/{shift_id}": { - "delete": { - "operationId": "ShiftController_remove", - "parameters": [ - { - "name": "shift_id", - "required": true, - "in": "path", - "schema": { - "type": "number" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Shift" - ] - } - }, - "/timesheets": { - "get": { - "operationId": "TimesheetController_getTimesheetByIds", - "parameters": [ - { - "name": "employee_email", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "year", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "period_number", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Timesheet" - ] - } } }, "info": { @@ -1018,6 +1238,168 @@ } } }, + "PayPeriodDto": { + "type": "object", + "properties": { + "pay_period_no": { + "type": "number", + "example": 1, + "description": "numéro cyclique de la période entre 1 et 26" + }, + "period_start": { + "type": "string", + "example": "2023-12-17", + "format": "date" + }, + "period_end": { + "type": "string", + "example": "2023-12-30", + "format": "date" + }, + "payday": { + "type": "string", + "example": "2023-01-04", + "format": "date" + }, + "pay_year": { + "type": "number", + "example": 2023 + }, + "label": { + "type": "string", + "example": "2023-12-17 → 2023-12-30" + } + }, + "required": [ + "pay_period_no", + "period_start", + "period_end", + "payday", + "pay_year", + "label" + ] + }, + "PayPeriodBundleDto": { + "type": "object", + "properties": { + "current": { + "description": "Current pay period (resolved from date)", + "allOf": [ + { + "$ref": "#/components/schemas/PayPeriodDto" + } + ] + }, + "periods": { + "description": "All pay periods", + "type": "array", + "items": { + "$ref": "#/components/schemas/PayPeriodDto" + } + } + }, + "required": [ + "current", + "periods" + ] + }, + "EmployeePeriodOverviewDto": { + "type": "object", + "properties": { + "employee_name": { + "type": "string", + "example": "Alex Dupont", + "description": "Nom complet de lemployé" + }, + "regular_hours": { + "type": "number", + "example": 40, + "description": "pay-period`s regular hours" + }, + "other_hours": { + "type": "object", + "example": 0, + "description": "pay-period`s other hours" + }, + "expenses": { + "type": "number", + "example": 420.69, + "description": "pay-period`s total expenses ($)" + }, + "mileage": { + "type": "number", + "example": 40, + "description": "pay-period total mileages (km)" + }, + "is_approved": { + "type": "boolean", + "example": true, + "description": "Tous les timesheets de la période sont approuvés pour cet employé" + } + }, + "required": [ + "employee_name", + "regular_hours", + "other_hours", + "expenses", + "mileage", + "is_approved" + ] + }, + "PayPeriodOverviewDto": { + "type": "object", + "properties": { + "pay_period_no": { + "type": "number", + "example": 1, + "description": "Period number (1–26)" + }, + "pay_year": { + "type": "number", + "example": 2023, + "description": "Calendar year of the period" + }, + "period_start": { + "type": "string", + "example": "2023-12-17", + "format": "date", + "description": "Period start date (YYYY-MM-DD)" + }, + "period_end": { + "type": "string", + "example": "2023-12-30", + "format": "date", + "description": "Period end date (YYYY-MM-DD)" + }, + "payday": { + "type": "string", + "example": "2023-12-30", + "format": "date", + "description": "Period pay day(YYYY-MM-DD)" + }, + "label": { + "type": "string", + "example": "2023-12-17 → 2023-12-30", + "description": "Human-readable label" + }, + "employees_overview": { + "description": "Per-employee overview for the period", + "type": "array", + "items": { + "$ref": "#/components/schemas/EmployeePeriodOverviewDto" + } + } + }, + "required": [ + "pay_period_no", + "pay_year", + "period_start", + "period_end", + "payday", + "label", + "employees_overview" + ] + }, "PreferencesDto": { "type": "object", "properties": {} diff --git a/src/modules/pay-periods/controllers/pay-periods.controller.ts b/src/modules/pay-periods/controllers/pay-periods.controller.ts index c12f810..61026c6 100644 --- a/src/modules/pay-periods/controllers/pay-periods.controller.ts +++ b/src/modules/pay-periods/controllers/pay-periods.controller.ts @@ -5,7 +5,7 @@ import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto"; import { PayPeriodsQueryService } from "../services/pay-periods-query.service"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { Roles as RoleEnum } from '.prisma/client'; -import { PayPeriodsCommandService } from "../services/pay-periods-command.service"; +// import { PayPeriodsCommandService } from "../services/pay-periods-command.service"; import { PayPeriodBundleDto } from "../dtos/bundle-pay-period.dto"; import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto"; @@ -15,7 +15,7 @@ export class PayPeriodsController { constructor( private readonly queryService: PayPeriodsQueryService, - private readonly commandService: PayPeriodsCommandService, + // private readonly commandService: PayPeriodsCommandService, ) {} @Get('current-and-all') @@ -51,13 +51,13 @@ export class PayPeriodsController { return this.queryService.findOneByYearPeriod(year, period_no); } - @Patch("crew/bulk-approval") - //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR) - @ApiOperation({ summary: "Approve all selected timesheets in the period" }) - @ApiResponse({ status: 200, description: "Pay period approved" }) - async bulkApproval(@Body() dto: BulkCrewApprovalDto) { - return this.commandService.bulkApproveCrew(dto); - } + // @Patch("crew/bulk-approval") + // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR) + // @ApiOperation({ summary: "Approve all selected timesheets in the period" }) + // @ApiResponse({ status: 200, description: "Pay period approved" }) + // async bulkApproval(@Body() dto: BulkCrewApprovalDto) { + // return this.commandService.bulkApproveCrew(dto); + // } @Get(':year/:periodNumber/:email') //@RolesAllowed(RoleEnum.SUPERVISOR) diff --git a/src/modules/pay-periods/pay-periods.module.ts b/src/modules/pay-periods/pay-periods.module.ts index 6ba5c63..c614179 100644 --- a/src/modules/pay-periods/pay-periods.module.ts +++ b/src/modules/pay-periods/pay-periods.module.ts @@ -1,7 +1,6 @@ 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 { SharedModule } from "../shared/shared.module"; @@ -12,14 +11,10 @@ import { BusinessLogicsModule } from "../business-logics/business-logics.module" imports: [PrismaModule, TimesheetsModule, SharedModule, BusinessLogicsModule], providers: [ PayPeriodsQueryService, - PayPeriodsCommandService, PrismaService, ], controllers: [PayPeriodsController], - exports: [ - PayPeriodsQueryService, - PayPeriodsCommandService, - ] + exports: [ PayPeriodsQueryService ], }) export class PayperiodsModule {} \ No newline at end of file diff --git a/src/modules/pay-periods/services/pay-periods-command.service.ts b/src/modules/pay-periods/services/pay-periods-command.service.ts index a1a4b5a..960e1ef 100644 --- a/src/modules/pay-periods/services/pay-periods-command.service.ts +++ b/src/modules/pay-periods/services/pay-periods-command.service.ts @@ -1,71 +1,71 @@ -import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common"; -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"; +// import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common"; +// 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: TimesheetApprovalService, - private readonly query: PayPeriodsQueryService, - ) {} +// @Injectable() +// export class PayPeriodsCommandService { +// constructor( +// private readonly prisma: PrismaService, +// private readonly timesheets_approval: TimesheetApprovalService, +// private readonly query: PayPeriodsQueryService, +// ) {} - //function to approve pay-periods according to selected crew members - async bulkApproveCrew(dto:BulkCrewApprovalDto): Promise<{updated: number}> { - const { supervisor_email, include_subtree, items } = dto; - if(!items?.length) throw new BadRequestException('no items to process'); +// //function to approve pay-periods according to selected crew members +// async bulkApproveCrew(dto:BulkCrewApprovalDto): Promise<{updated: number}> { +// const { supervisor_email, include_subtree, items } = dto; +// if(!items?.length) throw new BadRequestException('no items to process'); - //fetch and validate supervisor status - const supervisor = await this.query.getSupervisor(supervisor_email); - if(!supervisor) throw new NotFoundException('No employee record linked to current user'); - if(!supervisor.is_supervisor) throw new ForbiddenException('Employee is not a supervisor'); +// //fetch and validate supervisor status +// const supervisor = await this.query.getSupervisor(supervisor_email); +// if(!supervisor) throw new NotFoundException('No employee record linked to current user'); +// if(!supervisor.is_supervisor) throw new ForbiddenException('Employee is not a supervisor'); - //fetches emails of crew members linked to supervisor - const crew_emails = await this.query.resolveCrewEmails(supervisor.id, include_subtree); +// //fetches emails of crew members linked to supervisor +// const crew_emails = await this.query.resolveCrewEmails(supervisor.id, include_subtree); - for(const item of items) { - if(!crew_emails.has(item.employee_email)) { - throw new ForbiddenException(`Employee ${item.employee_email} not in supervisor crew`); - } - } +// for(const item of items) { +// if(!crew_emails.has(item.employee_email)) { +// throw new ForbiddenException(`Employee ${item.employee_email} not in supervisor crew`); +// } +// } - const period_cache = new Map(); - const getPeriod = async (y:number, no: number) => { - const key = `${y}-${no}`; - if(!period_cache.has(key)) return period_cache.get(key)!; - const period = await this.query.getPeriodWindow(y,no); - if(!period) throw new NotFoundException(`Pay period ${y}-${no} not found`); - period_cache.set(key, period); - return period; - }; +// const period_cache = new Map(); +// const getPeriod = async (y:number, no: number) => { +// const key = `${y}-${no}`; +// if(!period_cache.has(key)) return period_cache.get(key)!; +// const period = await this.query.getPeriodWindow(y,no); +// if(!period) throw new NotFoundException(`Pay period ${y}-${no} not found`); +// period_cache.set(key, period); +// return period; +// }; - let updated = 0; +// let updated = 0; - await this.prisma.$transaction(async (transaction) => { - for(const item of items) { - const { period_start, period_end } = await getPeriod(item.pay_year, item.period_no); +// await this.prisma.$transaction(async (transaction) => { +// for(const item of items) { +// const { period_start, period_end } = await getPeriod(item.pay_year, item.period_no); - const t_sheets = await transaction.timesheets.findMany({ - where: { - employee: { user: { email: item.employee_email } }, - OR: [ - {shift : { some: { date: { gte: period_start, lte: period_end } } } }, - {expense: { some: { date: { gte: period_start, lte: period_end } } } }, - ], - }, - select: { id: true }, - }); +// const t_sheets = await transaction.timesheets.findMany({ +// where: { +// employee: { user: { email: item.employee_email } }, +// OR: [ +// {shift : { some: { date: { gte: period_start, lte: period_end } } } }, +// {expense: { some: { date: { gte: period_start, lte: period_end } } } }, +// ], +// }, +// select: { id: true }, +// }); - for(const { id } of t_sheets) { - await this.timesheets_approval.updateApprovalWithTransaction(transaction, id, item.approve); - updated++; - } +// for(const { id } of t_sheets) { +// await this.timesheets_approval.updateApprovalWithTransaction(transaction, id, item.approve); +// updated++; +// } - } - }); - return {updated}; - } -} \ No newline at end of file +// } +// }); +// return {updated}; +// } +// } \ No newline at end of file