diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index f089187..d32a67f 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -3,7 +3,7 @@ "paths": { "/": { "get": { - "operationId": "ShiftsOverviewController_getSummary", + "operationId": "AppController_getHello", "parameters": [], "responses": { "200": { @@ -11,7 +11,7 @@ } }, "tags": [ - "ShiftsOverview" + "App" ] } }, @@ -835,24 +835,11 @@ ] }, "get": { - "operationId": "ShiftsController_findAll", + "operationId": "ShiftsController_getSummary", "parameters": [], "responses": { - "201": { - "description": "List of shifts found", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CreateShiftDto" - } - } - } - } - }, - "400": { - "description": "List of shifts not found" + "200": { + "description": "" } }, "security": [ @@ -860,7 +847,6 @@ "access-token": [] } ], - "summary": "Find all shifts", "tags": [ "Shifts" ] @@ -1017,17 +1003,22 @@ ] } }, - "/export.csv": { + "/shifts/export.csv": { "get": { - "operationId": "ShiftsOverviewController_exportCsv", + "operationId": "ShiftsController_exportCsv", "parameters": [], "responses": { "200": { "description": "" } }, + "security": [ + { + "access-token": [] + } + ], "tags": [ - "ShiftsOverview" + "Shifts" ] } }, diff --git a/src/app.module.ts b/src/app.module.ts index b1d5850..c1a20e8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -21,6 +21,7 @@ import { ScheduleModule } from '@nestjs/schedule'; import { ShiftsModule } from './modules/shifts/shifts.module'; import { TimesheetsModule } from './modules/timesheets/timesheets.module'; import { UsersModule } from './modules/users-management/users.module'; +import { ConfigModule } from '@nestjs/config'; @Module({ imports: [ @@ -28,6 +29,7 @@ import { UsersModule } from './modules/users-management/users.module'; AuthenticationModule, BankCodesModule, BusinessLogicsModule, + ConfigModule.forRoot({isGlobal: true}), CsvExportModule, CustomersModule, EmployeesModule, diff --git a/src/modules/archival/controllers/expenses-archive.controller.ts b/src/modules/archival/controllers/expenses-archive.controller.ts index 1e522bd..2ad0519 100644 --- a/src/modules/archival/controllers/expenses-archive.controller.ts +++ b/src/modules/archival/controllers/expenses-archive.controller.ts @@ -2,13 +2,13 @@ import { UseGuards, Controller, Get, Param, ParseIntPipe, NotFoundException } fr import { ApiTags, ApiOperation, ApiResponse } from "@nestjs/swagger"; import { ExpensesArchive,Roles as RoleEnum } from "@prisma/client"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; -import { ExpensesService } from "src/modules/expenses/services/expenses.service"; +import { ExpensesQueryService } from "src/modules/expenses/services/expenses-query.service"; @ApiTags('Expense Archives') // @UseGuards() @Controller('archives/expenses') export class ExpensesArchiveController { - constructor(private readonly expensesService: ExpensesService) {} + constructor(private readonly expensesService: ExpensesQueryService) {} @Get() @RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR) diff --git a/src/modules/archival/controllers/shifts-archive.controller.ts b/src/modules/archival/controllers/shifts-archive.controller.ts index 5666646..26af4a6 100644 --- a/src/modules/archival/controllers/shifts-archive.controller.ts +++ b/src/modules/archival/controllers/shifts-archive.controller.ts @@ -2,13 +2,13 @@ import { Get, Param, ParseIntPipe, NotFoundException, Controller, UseGuards } fr import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { ShiftsArchive, Roles as RoleEnum } from "@prisma/client"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; -import { ShiftsService } from "src/modules/shifts/services/shifts.service"; +import { ShiftsQueryService } from "src/modules/shifts/services/shifts-query.service"; @ApiTags('Shift Archives') // @UseGuards() @Controller('archives/shifts') export class ShiftsArchiveController { - constructor(private readonly shiftsService:ShiftsService) {} + constructor(private readonly shiftsService:ShiftsQueryService) {} @Get() @RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR) diff --git a/src/modules/archival/controllers/timesheets-archive.controller.ts b/src/modules/archival/controllers/timesheets-archive.controller.ts index e2e1d20..f41b679 100644 --- a/src/modules/archival/controllers/timesheets-archive.controller.ts +++ b/src/modules/archival/controllers/timesheets-archive.controller.ts @@ -2,13 +2,13 @@ import { Controller, Get, NotFoundException, Param, ParseIntPipe, UseGuards } fr import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { TimesheetsArchive, Roles as RoleEnum } from '@prisma/client'; -import { TimesheetsService } from "src/modules/timesheets/services/timesheets.service"; +import { TimesheetsQueryService } from "src/modules/timesheets/services/timesheets-query.service"; @ApiTags('Timesheet Archives') // @UseGuards() @Controller('archives/timesheets') export class TimesheetsArchiveController { - constructor(private readonly timesheetsService: TimesheetsService) {} + constructor(private readonly timesheetsService: TimesheetsQueryService) {} @Get() @RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR) diff --git a/src/modules/archival/services/archival.service.ts b/src/modules/archival/services/archival.service.ts index db91238..bf7a36d 100644 --- a/src/modules/archival/services/archival.service.ts +++ b/src/modules/archival/services/archival.service.ts @@ -1,18 +1,18 @@ import { Injectable, Logger } from "@nestjs/common"; import { Cron } from "@nestjs/schedule"; -import { ExpensesService } from "src/modules/expenses/services/expenses.service"; +import { ExpensesQueryService } from "src/modules/expenses/services/expenses-query.service"; import { LeaveRequestsService } from "src/modules/leave-requests/services/leave-requests.service"; -import { ShiftsService } from "src/modules/shifts/services/shifts.service"; -import { TimesheetsService } from "src/modules/timesheets/services/timesheets.service"; +import { ShiftsQueryService } from "src/modules/shifts/services/shifts-query.service"; +import { TimesheetsQueryService } from "src/modules/timesheets/services/timesheets-query.service"; @Injectable() export class ArchivalService { private readonly logger = new Logger(ArchivalService.name); constructor( - private readonly timesheetsService: TimesheetsService, - private readonly expensesService: ExpensesService, - private readonly shiftsService: ShiftsService, + private readonly timesheetsService: TimesheetsQueryService, + private readonly expensesService: ExpensesQueryService, + private readonly shiftsService: ShiftsQueryService, private readonly leaveRequestsService: LeaveRequestsService, ) {} diff --git a/src/modules/authentication/auth.module.ts b/src/modules/authentication/auth.module.ts index 784e22d..bfe92a7 100644 --- a/src/modules/authentication/auth.module.ts +++ b/src/modules/authentication/auth.module.ts @@ -4,7 +4,7 @@ import { AuthentikAuthService } from './services/authentik-auth.service'; import { UsersModule } from '../users-management/users.module'; import { AuthController } from './controllers/auth.controller'; import { AuthentikStrategy } from './strategies/authentik.strategy'; -import { ExpressSessionSerializer } from './services/express-session.serializer'; +import { ExpressSessionSerializer } from './serializers/express-session.serializer'; @Module({ diff --git a/src/modules/authentication/services/express-session.serializer.ts b/src/modules/authentication/serializers/express-session.serializer.ts similarity index 97% rename from src/modules/authentication/services/express-session.serializer.ts rename to src/modules/authentication/serializers/express-session.serializer.ts index 3508ced..c3d87c0 100644 --- a/src/modules/authentication/services/express-session.serializer.ts +++ b/src/modules/authentication/serializers/express-session.serializer.ts @@ -1,18 +1,18 @@ -import { PassportSerializer } from '@nestjs/passport'; -import { Injectable, UnauthorizedException } from '@nestjs/common'; - -@Injectable() -export class ExpressSessionSerializer extends PassportSerializer { - serializeUser(user: any, done: (err: any, user: any) => void): any { - if (!user){ - done(new UnauthorizedException('Serialize user error'), user); - } - done(null, user); - } - deserializeUser(payload: any, done: (err: any, payload: string) => void): any { - if (!payload){ - done(new UnauthorizedException('Deserialize user error'), payload); - } - done(null, payload); - } +import { PassportSerializer } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; + +@Injectable() +export class ExpressSessionSerializer extends PassportSerializer { + serializeUser(user: any, done: (err: any, user: any) => void): any { + if (!user){ + done(new UnauthorizedException('Serialize user error'), user); + } + done(null, user); + } + deserializeUser(payload: any, done: (err: any, payload: string) => void): any { + if (!payload){ + done(new UnauthorizedException('Deserialize user error'), payload); + } + done(null, payload); + } } \ No newline at end of file diff --git a/src/modules/bank-codes/bank-codes.module.ts b/src/modules/bank-codes/bank-codes.module.ts index 0030ec7..80ec6e3 100644 --- a/src/modules/bank-codes/bank-codes.module.ts +++ b/src/modules/bank-codes/bank-codes.module.ts @@ -1,7 +1,7 @@ import { Module } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; import { BankCodesControllers } from "./controllers/bank-codes.controller"; -import { BankCodesService } from "./services/bank-codes.services"; +import { BankCodesService } from "./services/bank-codes.service"; @Module({ controllers: [BankCodesControllers], diff --git a/src/modules/bank-codes/controllers/bank-codes.controller.ts b/src/modules/bank-codes/controllers/bank-codes.controller.ts index f8a44fa..cb36ee2 100644 --- a/src/modules/bank-codes/controllers/bank-codes.controller.ts +++ b/src/modules/bank-codes/controllers/bank-codes.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from "@nestjs/common"; -import { BankCodesService } from "../services/bank-codes.services"; -import { CreateBankCodeDto } from "../dtos/create-bank-codes"; -import { UpdateBankCodeDto } from "../dtos/update-bank-codes"; +import { BankCodesService } from "../services/bank-codes.service"; +import { CreateBankCodeDto } from "../dtos/create-bank-code.dto"; +import { UpdateBankCodeDto } from "../dtos/update-bank-code.dto"; import { ApiBadRequestResponse, ApiNotFoundResponse, ApiOperation, ApiResponse } from "@nestjs/swagger"; @Controller('bank-codes') diff --git a/src/modules/bank-codes/dtos/create-bank-codes.ts b/src/modules/bank-codes/dtos/create-bank-code.dto.ts similarity index 100% rename from src/modules/bank-codes/dtos/create-bank-codes.ts rename to src/modules/bank-codes/dtos/create-bank-code.dto.ts diff --git a/src/modules/bank-codes/dtos/update-bank-codes.ts b/src/modules/bank-codes/dtos/update-bank-code.dto.ts similarity index 66% rename from src/modules/bank-codes/dtos/update-bank-codes.ts rename to src/modules/bank-codes/dtos/update-bank-code.dto.ts index 658ba81..4033484 100644 --- a/src/modules/bank-codes/dtos/update-bank-codes.ts +++ b/src/modules/bank-codes/dtos/update-bank-code.dto.ts @@ -1,4 +1,4 @@ import { PartialType } from "@nestjs/swagger"; -import { CreateBankCodeDto } from "./create-bank-codes"; +import { CreateBankCodeDto } from "./create-bank-code.dto"; export class UpdateBankCodeDto extends PartialType(CreateBankCodeDto) {} \ No newline at end of file diff --git a/src/modules/bank-codes/services/bank-codes.services.ts b/src/modules/bank-codes/services/bank-codes.service.ts similarity index 88% rename from src/modules/bank-codes/services/bank-codes.services.ts rename to src/modules/bank-codes/services/bank-codes.service.ts index 71031de..7e4c3e5 100644 --- a/src/modules/bank-codes/services/bank-codes.services.ts +++ b/src/modules/bank-codes/services/bank-codes.service.ts @@ -1,8 +1,8 @@ import { Injectable, NotFoundException } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; -import { CreateBankCodeDto } from "../dtos/create-bank-codes"; +import { CreateBankCodeDto } from "../dtos/create-bank-code.dto"; import { BankCodes } from "@prisma/client"; -import { UpdateBankCodeDto } from "../dtos/update-bank-codes"; +import { UpdateBankCodeDto } from "../dtos/update-bank-code.dto"; @Injectable() export class BankCodesService { diff --git a/src/modules/expenses/controllers/expenses.controller.ts b/src/modules/expenses/controllers/expenses.controller.ts index 458dc72..4626f86 100644 --- a/src/modules/expenses/controllers/expenses.controller.ts +++ b/src/modules/expenses/controllers/expenses.controller.ts @@ -1,12 +1,12 @@ import { Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, Query, UseGuards, UsePipes, ValidationPipe } from "@nestjs/common"; -import { ExpensesService } from "../services/expenses.service"; +import { ExpensesQueryService } from "../services/expenses-query.service"; 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.dto"; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; -import { ExpensesApprovalService } from "../services/expenses-command.service"; +import { ExpensesCommandService } from "../services/expenses-command.service"; import { SearchExpensesDto } from "../dtos/search-expense.dto"; @ApiTags('Expenses') @@ -15,8 +15,8 @@ import { SearchExpensesDto } from "../dtos/search-expense.dto"; @Controller('Expenses') export class ExpensesController { constructor( - private readonly expensesService: ExpensesService, - private readonly expensesApprovalService: ExpensesApprovalService, + private readonly expensesService: ExpensesQueryService, + private readonly expensesApprovalService: ExpensesCommandService, ) {} @Post() diff --git a/src/modules/expenses/expenses.module.ts b/src/modules/expenses/expenses.module.ts index 489109a..04c3965 100644 --- a/src/modules/expenses/expenses.module.ts +++ b/src/modules/expenses/expenses.module.ts @@ -1,14 +1,14 @@ import { ExpensesController } from "./controllers/expenses.controller"; import { Module } from "@nestjs/common"; -import { ExpensesService } from "./services/expenses.service"; +import { ExpensesQueryService } from "./services/expenses-query.service"; import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module"; -import { ExpensesApprovalService } from "./services/expenses-command.service"; +import { ExpensesCommandService } from "./services/expenses-command.service"; @Module({ imports: [BusinessLogicsModule], controllers: [ExpensesController], - providers: [ExpensesService, ExpensesApprovalService], - exports: [ ExpensesService ], + providers: [ExpensesQueryService, ExpensesCommandService], + exports: [ ExpensesQueryService ], }) export class ExpensesModule {} \ No newline at end of file diff --git a/src/modules/expenses/services/expenses-command.service.ts b/src/modules/expenses/services/expenses-command.service.ts index 412cf1c..f5b8283 100644 --- a/src/modules/expenses/services/expenses-command.service.ts +++ b/src/modules/expenses/services/expenses-command.service.ts @@ -4,7 +4,7 @@ import { BaseApprovalService } from "src/common/shared/base-approval.service"; import { PrismaService } from "src/prisma/prisma.service"; @Injectable() -export class ExpensesApprovalService extends BaseApprovalService { +export class ExpensesCommandService extends BaseApprovalService { constructor(prisma: PrismaService) { super(prisma); } protected get delegate() { diff --git a/src/modules/expenses/services/expenses.service.ts b/src/modules/expenses/services/expenses-query.service.ts similarity index 99% rename from src/modules/expenses/services/expenses.service.ts rename to src/modules/expenses/services/expenses-query.service.ts index 9d1515c..707f737 100644 --- a/src/modules/expenses/services/expenses.service.ts +++ b/src/modules/expenses/services/expenses-query.service.ts @@ -8,7 +8,7 @@ import { SearchExpensesDto } from "../dtos/search-expense.dto"; import { buildPrismaWhere } from "src/common/shared/build-prisma-where"; @Injectable() -export class ExpensesService { +export class ExpensesQueryService { constructor( private readonly prisma: PrismaService, private readonly mileageService: MileageService, diff --git a/src/modules/leave-requests/controllers/leave-requests.controller.ts b/src/modules/leave-requests/controllers/leave-requests.controller.ts index d4bc92a..cfa6a3c 100644 --- a/src/modules/leave-requests/controllers/leave-requests.controller.ts +++ b/src/modules/leave-requests/controllers/leave-requests.controller.ts @@ -1,12 +1,12 @@ 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 { CreateLeaveRequestsDto } from "../dtos/create-leave-request.dto"; import { LeaveRequests } from "@prisma/client"; -import { UpdateLeaveRequestsDto } from "../dtos/update-leave-requests.dto"; +import { UpdateLeaveRequestsDto } from "../dtos/update-leave-request.dto"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { LeaveApprovalStatus, Roles as RoleEnum } from '.prisma/client'; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; -import { SearchLeaveRequestsDto } from "../dtos/search-leave-requests.dto"; +import { SearchLeaveRequestsDto } from "../dtos/search-leave-request.dto"; @ApiTags('Leave Requests') @ApiBearerAuth('access-token') diff --git a/src/modules/leave-requests/dtos/create-leave-requests.dto.ts b/src/modules/leave-requests/dtos/create-leave-request.dto.ts similarity index 100% rename from src/modules/leave-requests/dtos/create-leave-requests.dto.ts rename to src/modules/leave-requests/dtos/create-leave-request.dto.ts diff --git a/src/modules/leave-requests/dtos/search-leave-requests.dto.ts b/src/modules/leave-requests/dtos/search-leave-request.dto.ts similarity index 100% rename from src/modules/leave-requests/dtos/search-leave-requests.dto.ts rename to src/modules/leave-requests/dtos/search-leave-request.dto.ts diff --git a/src/modules/leave-requests/dtos/update-leave-requests.dto.ts b/src/modules/leave-requests/dtos/update-leave-request.dto.ts similarity index 64% rename from src/modules/leave-requests/dtos/update-leave-requests.dto.ts rename to src/modules/leave-requests/dtos/update-leave-request.dto.ts index 0955fe9..ec4bb86 100644 --- a/src/modules/leave-requests/dtos/update-leave-requests.dto.ts +++ b/src/modules/leave-requests/dtos/update-leave-request.dto.ts @@ -1,4 +1,4 @@ import { PartialType } from "@nestjs/swagger"; -import { CreateLeaveRequestsDto } from "./create-leave-requests.dto"; +import { CreateLeaveRequestsDto } from "./create-leave-request.dto"; export class UpdateLeaveRequestsDto extends PartialType(CreateLeaveRequestsDto){} \ 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 0be337e..c3131fb 100644 --- a/src/modules/leave-requests/services/leave-requests.service.ts +++ b/src/modules/leave-requests/services/leave-requests.service.ts @@ -1,12 +1,12 @@ import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; -import { CreateLeaveRequestsDto } from "../dtos/create-leave-requests.dto"; +import { CreateLeaveRequestsDto } from "../dtos/create-leave-request.dto"; import { LeaveRequests, LeaveRequestsArchive } from "@prisma/client"; -import { UpdateLeaveRequestsDto } from "../dtos/update-leave-requests.dto"; +import { UpdateLeaveRequestsDto } from "../dtos/update-leave-request.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 { SearchLeaveRequestsDto } from "../dtos/search-leave-request.dto"; import { buildPrismaWhere } from "src/common/shared/build-prisma-where"; @Injectable() diff --git a/src/modules/notifications/dtos/notifications.types.ts b/src/modules/notifications/dtos/notification.types.ts similarity index 100% rename from src/modules/notifications/dtos/notifications.types.ts rename to src/modules/notifications/dtos/notification.types.ts diff --git a/src/modules/notifications/services/notifications.service.ts b/src/modules/notifications/services/notifications.service.ts index ffcc90c..c9b40c0 100644 --- a/src/modules/notifications/services/notifications.service.ts +++ b/src/modules/notifications/services/notifications.service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from "@nestjs/common"; import { Subject } from "rxjs"; -import { NotificationCard } from "../dtos/notifications.types"; +import { NotificationCard } from "../dtos/notification.types"; @Injectable() export class NotificationsService { diff --git a/src/modules/oauth-sessions/controllers/oauth-sessions.controller.ts b/src/modules/oauth-sessions/controllers/oauth-sessions.controller.ts index cc32f22..64b3eb7 100644 --- a/src/modules/oauth-sessions/controllers/oauth-sessions.controller.ts +++ b/src/modules/oauth-sessions/controllers/oauth-sessions.controller.ts @@ -3,9 +3,9 @@ import { OAuthSessions } from '@prisma/client'; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { Roles as RoleEnum } from '.prisma/client'; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { CreateOauthSessionDto } from '../dtos/create-oauth-sessions.dto'; +import { CreateOauthSessionDto } from '../dtos/create-oauth-session.dto'; import { OauthSessionsService } from '../services/oauth-sessions.service'; -import { UpdateOauthSessionDto } from '../dtos/update-oauth-sessions.dto'; +import { UpdateOauthSessionDto } from '../dtos/update-oauth-session.dto'; @ApiTags('OAuth Sessions') @ApiBearerAuth('sessions') diff --git a/src/modules/oauth-sessions/dtos/create-oauth-sessions.dto.ts b/src/modules/oauth-sessions/dtos/create-oauth-session.dto.ts similarity index 100% rename from src/modules/oauth-sessions/dtos/create-oauth-sessions.dto.ts rename to src/modules/oauth-sessions/dtos/create-oauth-session.dto.ts diff --git a/src/modules/oauth-sessions/dtos/update-oauth-sessions.dto.ts b/src/modules/oauth-sessions/dtos/update-oauth-session.dto.ts similarity index 65% rename from src/modules/oauth-sessions/dtos/update-oauth-sessions.dto.ts rename to src/modules/oauth-sessions/dtos/update-oauth-session.dto.ts index 5a2abef..697efd7 100644 --- a/src/modules/oauth-sessions/dtos/update-oauth-sessions.dto.ts +++ b/src/modules/oauth-sessions/dtos/update-oauth-session.dto.ts @@ -1,4 +1,4 @@ import { PartialType } from "@nestjs/swagger"; -import { CreateOauthSessionDto } from "./create-oauth-sessions.dto"; +import { CreateOauthSessionDto } from "./create-oauth-session.dto"; export class UpdateOauthSessionDto extends PartialType(CreateOauthSessionDto) {} diff --git a/src/modules/oauth-sessions/services/oauth-sessions.service.ts b/src/modules/oauth-sessions/services/oauth-sessions.service.ts index 353f128..c307682 100644 --- a/src/modules/oauth-sessions/services/oauth-sessions.service.ts +++ b/src/modules/oauth-sessions/services/oauth-sessions.service.ts @@ -1,8 +1,8 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; -import { CreateOauthSessionDto } from '../dtos/create-oauth-sessions.dto'; +import { CreateOauthSessionDto } from '../dtos/create-oauth-session.dto'; import { OAuthSessions } from '@prisma/client'; -import { UpdateOauthSessionDto } from '../dtos/update-oauth-sessions.dto'; +import { UpdateOauthSessionDto } from '../dtos/update-oauth-session.dto'; @Injectable() export class OauthSessionsService { diff --git a/src/modules/pay-periods/controllers/pay-periods.controller.ts b/src/modules/pay-periods/controllers/pay-periods.controller.ts index 14a8288..589b0a3 100644 --- a/src/modules/pay-periods/controllers/pay-periods.controller.ts +++ b/src/modules/pay-periods/controllers/pay-periods.controller.ts @@ -1,6 +1,5 @@ import { Controller, ForbiddenException, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Query } from "@nestjs/common"; import { ApiNotFoundResponse, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "@nestjs/swagger"; -import { PayPeriodsService } from "../services/pay-periods.service"; import { PayPeriodDto } from "../dtos/pay-period.dto"; import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto"; import { PayPeriodsQueryService } from "../services/pay-periods-query.service"; @@ -10,23 +9,22 @@ import { Req } from '@nestjs/common'; import { Request } from 'express'; import { PayPeriodsCommandService } from "../services/pay-periods-command.service"; import { REPL_MODE_STRICT } from "repl"; -import { PayPeriodBundleDto } from "../dtos/bundle-pay-periods.dto"; +import { PayPeriodBundleDto } from "../dtos/bundle-pay-period.dto"; @ApiTags('pay-periods') @Controller('pay-periods') export class PayPeriodsController { constructor( - private readonly payPeriodsService: PayPeriodsService, - private readonly queryService: PayPeriodsQueryService, - private readonly commandService: PayPeriodsCommandService, + private readonly queryService: PayPeriodsQueryService, + private readonly commandService: PayPeriodsCommandService, ) {} @Get() @ApiOperation({ summary: 'Find all pay period' }) @ApiResponse({status: 200,description: 'List of pay period found', type: PayPeriodDto, isArray: true }) async findAll(): Promise { - return this.payPeriodsService.findAll(); + return this.queryService.findAll(); } @Get('bundle/current-and-all') @@ -35,8 +33,8 @@ export class PayPeriodsController { @ApiResponse({status: 200, description:'Find current and all pay periods', type: PayPeriodBundleDto}) async getCurrentAndAll(@Query('date') date?: string): Promise { const [current, periods] = await Promise.all([ - this.payPeriodsService.findCurrent(date), - this.payPeriodsService.findAll(), + this.queryService.findCurrent(date), + this.queryService.findAll(), ]); return { current, periods }; } @@ -46,7 +44,7 @@ export class PayPeriodsController { @ApiResponse({ status: 200, description: "Pay period found for the selected date", type: PayPeriodDto }) @ApiNotFoundResponse({ description: "Pay period not found for the selected date" }) async findByDate(@Param("date") date: string) { - return this.payPeriodsService.findByDate(date); + return this.queryService.findByDate(date); } @Get(":year/:periodNumber") @@ -59,7 +57,7 @@ export class PayPeriodsController { @Param("year", ParseIntPipe) year: number, @Param("periodNumber", ParseIntPipe) periodNumber: number, ) { - return this.payPeriodsService.findOneByYearPeriod(year, periodNumber); + return this.queryService.findOneByYearPeriod(year, periodNumber); } @Patch(":year/:periodNumber/approval") diff --git a/src/modules/pay-periods/dtos/bundle-pay-periods.dto.ts b/src/modules/pay-periods/dtos/bundle-pay-period.dto.ts similarity index 100% rename from src/modules/pay-periods/dtos/bundle-pay-periods.dto.ts rename to src/modules/pay-periods/dtos/bundle-pay-period.dto.ts diff --git a/src/modules/pay-periods/pay-periods.module.ts b/src/modules/pay-periods/pay-periods.module.ts index af3157c..fc400d3 100644 --- a/src/modules/pay-periods/pay-periods.module.ts +++ b/src/modules/pay-periods/pay-periods.module.ts @@ -1,29 +1,28 @@ import { PrismaModule } from "src/prisma/prisma.module"; -import { PayPeriodsService } from "./services/pay-periods.service"; 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/services/timesheets-command.service"; -import { ExpensesApprovalService } from "../expenses/services/expenses-command.service"; -import { ShiftsApprovalService } from "../shifts/services/shifts-command.service"; +import { ExpensesCommandService } from "../expenses/services/expenses-command.service"; +import { ShiftsCommandService } from "../shifts/services/shifts-command.service"; @Module({ imports: [PrismaModule, TimesheetsModule], providers: [ - PayPeriodsService, + PayPeriodsQueryService, PayPeriodsQueryService, PayPeriodsCommandService, TimesheetsCommandService, - ExpensesApprovalService, - ShiftsApprovalService, + ExpensesCommandService, + ShiftsCommandService, ], controllers: [PayPeriodsController], exports: [ PayPeriodsQueryService, PayPeriodsCommandService, - PayPeriodsService, + PayPeriodsQueryService, ] }) diff --git a/src/modules/pay-periods/services/pay-periods-query.service.ts b/src/modules/pay-periods/services/pay-periods-query.service.ts index 7d81f83..ba71102 100644 --- a/src/modules/pay-periods/services/pay-periods-query.service.ts +++ b/src/modules/pay-periods/services/pay-periods-query.service.ts @@ -3,11 +3,15 @@ import { PrismaService } from "src/prisma/prisma.service"; import { computeHours } from "src/common/utils/date-utils"; import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto"; import { EmployeePeriodOverviewDto } from "../dtos/overview-employee-period.dto"; -import { computePeriod } from "../utils/pay-year.util"; +import { computePeriod, listPayYear, payYearOfDate } from "../utils/pay-year.util"; +import { PayPeriodDto } from "../dtos/pay-period.dto"; +import { mapPayPeriodToDto } from "../mappers/pay-periods.mapper"; @Injectable() export class PayPeriodsQueryService { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + ) {} async getOverview(periodNumber: number): Promise { const period = await this.prisma.payPeriods.findFirst({ @@ -177,52 +181,107 @@ export class PayPeriodsQueryService { } - async getCrewOverview(year: number, periodNumber: number, userId: string, includeSubtree: boolean): Promise { - // 1) Trouver la période - const period = await this.prisma.payPeriods.findFirst({ where: { year, period_number: periodNumber } }); - if (!period) throw new NotFoundException(`Pay period ${year}-${periodNumber} not found`); + async getCrewOverview(year: number, periodNumber: number, userId: string, includeSubtree: boolean): Promise { + // 1) Trouver la période + const period = await this.prisma.payPeriods.findFirst({ where: { year, period_number: periodNumber } }); + if (!period) throw new NotFoundException(`Pay period ${year}-${periodNumber} not found`); - // 2) Résoudre l'employé superviseur depuis l'utilisateur courant (Users.id -> Employees) - const supervisor = await this.prisma.employees.findUnique({ - where: { user_id: userId }, - select: { id: true }, - }); - if (!supervisor) throw new ForbiddenException('No employee record linked to current user'); + // 2) Résoudre l'employé superviseur depuis l'utilisateur courant (Users.id -> Employees) + const supervisor = await this.prisma.employees.findUnique({ + where: { user_id: userId }, + select: { id: true }, + }); + if (!supervisor) throw new ForbiddenException('No employee record linked to current user'); - // 3) Récupérer la liste des employés du crew (directs ou sous-arbo complète) - const crew = await this.resolveCrew(supervisor.id, includeSubtree); // [{ id, first_name, last_name }] - const crewIds = crew.map(c => c.id); - // seed names map for employés sans données - const seedNames = new Map(crew.map(c => [c.id, `${c.first_name} ${c.last_name}`.trim()])); + // 3) Récupérer la liste des employés du crew (directs ou sous-arbo complète) + const crew = await this.resolveCrew(supervisor.id, includeSubtree); // [{ id, first_name, last_name }] + const crewIds = crew.map(c => c.id); + // seed names map for employés sans données + const seedNames = new Map(crew.map(c => [c.id, `${c.first_name} ${c.last_name}`.trim()])); - // 4) Construire l’overview filtré par ce crew - return this.buildOverview(period, { restrictEmployeeIds: crewIds, seedNames }); - } + // 4) Construire l’overview filtré par ce crew + return this.buildOverview(period, { restrictEmployeeIds: crewIds, seedNames }); +} - private async resolveCrew(supervisorId: number, includeSubtree: boolean): Promise> { - const result: Array<{ id: number; first_name: string; last_name: string }> = []; +private async resolveCrew(supervisorId: number, includeSubtree: boolean): Promise> { + const result: Array<{ id: number; first_name: string; last_name: string }> = []; - // niveau 1 (directs) - let frontier = await this.prisma.employees.findMany({ - where: { supervisor_id: supervisorId }, + // niveau 1 (directs) + let frontier = await this.prisma.employees.findMany({ + where: { supervisor_id: supervisorId }, + select: { id: true, user: { select: { first_name: true, last_name: true } } }, + }); + result.push(...frontier.map(e => ({ id: e.id, first_name: e.user.first_name, last_name: e.user.last_name }))); + + if (!includeSubtree) return result; + + // BFS pour les niveaux suivants + while (frontier.length) { + const parentIds = frontier.map(e => e.id); + const next = await this.prisma.employees.findMany({ + where: { supervisor_id: { in: parentIds } }, select: { id: true, user: { select: { first_name: true, last_name: true } } }, }); - result.push(...frontier.map(e => ({ id: e.id, first_name: e.user.first_name, last_name: e.user.last_name }))); + if (next.length === 0) break; + result.push(...next.map(e => ({ id: e.id, first_name: e.user.first_name, last_name: e.user.last_name }))); + frontier = next; + } - if (!includeSubtree) return result; + return result; +} - // BFS pour les niveaux suivants - while (frontier.length) { - const parentIds = frontier.map(e => e.id); - const next = await this.prisma.employees.findMany({ - where: { supervisor_id: { in: parentIds } }, - select: { id: true, user: { select: { first_name: true, last_name: true } } }, + + async findAll(): Promise { + const currentPayYear = payYearOfDate(new Date()); + return listPayYear(currentPayYear).map(period =>({ + period_number: period.period_number, + year: period.year, + start_date: period.start_date, + end_date: period.end_date, + label: period.label, + })); + } + + async findOne(periodNumber: number): Promise { + const row = await this.prisma.payPeriods.findFirst({ + where: { period_number: periodNumber }, + orderBy: { year: "desc" }, }); - if (next.length === 0) break; - result.push(...next.map(e => ({ id: e.id, first_name: e.user.first_name, last_name: e.user.last_name }))); - frontier = next; - } + if (!row) throw new NotFoundException(`Pay period #${periodNumber} not found`); + return mapPayPeriodToDto(row); + } - return result; + async findOneByYearPeriod(year: number, periodNumber: number): Promise { + const row = await this.prisma.payPeriods.findFirst({ + where: { year, period_number: periodNumber }, + }); + if(row) return mapPayPeriodToDto(row); + + // fallback for outside of view periods + const p = computePeriod(year, periodNumber); + return {period_number: p.period_number, year: p.year, start_date: p.start_date, end_date: p.end_date, label: p.label} + } + + //function to cherry pick a Date to find a period + async findByDate(date: string): Promise { + const dt = new Date(date); + const row = await this.prisma.payPeriods.findFirst({ + where: { start_date: { lte: dt }, end_date: { gte: dt } }, + }); + if(row) return mapPayPeriodToDto(row); + + //fallback for outwside view periods + const payYear = payYearOfDate(date); + const periods = listPayYear(payYear); + const hit = periods.find(p => date >= p.start_date && date <= p.end_date); + if(!hit) throw new NotFoundException(`No period found for ${date}`); + + return { period_number: hit.period_number, year: hit.year, start_date: hit.start_date, end_date:hit.end_date, label: hit.label} + + } + + async findCurrent(date?: string): Promise { + const isoDay = date ?? new Date().toISOString().slice(0,10); + return this.findByDate(isoDay); } } diff --git a/src/modules/pay-periods/services/pay-periods.service.ts b/src/modules/pay-periods/services/pay-periods.service.ts deleted file mode 100644 index 120f7b4..0000000 --- a/src/modules/pay-periods/services/pay-periods.service.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Injectable, NotFoundException } from "@nestjs/common"; -import { PrismaService } from "src/prisma/prisma.service"; -import { PayPeriodsCommandService } from "./pay-periods-command.service"; -import { mapMany, mapPayPeriodToDto } from "../mappers/pay-periods.mapper"; -import { PayPeriodDto } from "../dtos/pay-period.dto"; -import { computePeriod, listPayYear, payYearOfDate } from "../utils/pay-year.util"; - - -@Injectable() -export class PayPeriodsService { - constructor(private readonly prisma: PrismaService) {} - - async findAll(): Promise { - const currentPayYear = payYearOfDate(new Date()); - return listPayYear(currentPayYear).map(period =>({ - period_number: period.period_number, - year: period.year, - start_date: period.start_date, - end_date: period.end_date, - label: period.label, - })); - } - - async findOne(periodNumber: number): Promise { - const row = await this.prisma.payPeriods.findFirst({ - where: { period_number: periodNumber }, - orderBy: { year: "desc" }, - }); - if (!row) throw new NotFoundException(`Pay period #${periodNumber} not found`); - return mapPayPeriodToDto(row); - } - - async findOneByYearPeriod(year: number, periodNumber: number): Promise { - const row = await this.prisma.payPeriods.findFirst({ - where: { year, period_number: periodNumber }, - }); - if(row) return mapPayPeriodToDto(row); - - // fallback for outside of view periods - const p = computePeriod(year, periodNumber); - return {period_number: p.period_number, year: p.year, start_date: p.start_date, end_date: p.end_date, label: p.label} - } - - //function to cherry pick a Date to find a period - async findByDate(date: string): Promise { - const dt = new Date(date); - const row = await this.prisma.payPeriods.findFirst({ - where: { start_date: { lte: dt }, end_date: { gte: dt } }, - }); - if(row) return mapPayPeriodToDto(row); - - //fallback for outwside view periods - const payYear = payYearOfDate(date); - const periods = listPayYear(payYear); - const hit = periods.find(p => date >= p.start_date && date <= p.end_date); - if(!hit) throw new NotFoundException(`No period found for ${date}`); - - return { period_number: hit.period_number, year: hit.year, start_date: hit.start_date, end_date:hit.end_date, label: hit.label} - - } - - async findCurrent(date?: string): Promise { - const isoDay = date ?? new Date().toISOString().slice(0,10); - return this.findByDate(isoDay); - } -} \ No newline at end of file diff --git a/src/modules/pay-periods/utils/pay-year.util.ts b/src/modules/pay-periods/utils/pay-year.util.ts index 57cc675..68193b6 100644 --- a/src/modules/pay-periods/utils/pay-year.util.ts +++ b/src/modules/pay-periods/utils/pay-year.util.ts @@ -1,6 +1,7 @@ export const ANCHOR_ISO = '2023-12-17'; // ancre date const PERIOD_DAYS = 14; const PERIODS_PER_YEAR = 26; +const MS_PER_DAY = 86_400_000; const toUTCDate = (iso: string | Date) => { const d = typeof iso === 'string' ? new Date(iso + 'T00:00:00.000Z') : iso; @@ -10,18 +11,20 @@ export const toDateString = (d: Date) => d.toISOString().slice(0, 10); const ANCHOR = toUTCDate(ANCHOR_ISO); -export function payYearOfDate(date: string | Date): number { +export function payYearOfDate(date: string | Date, anchorISO = ANCHOR_ISO): number { + const ANCHOR = toUTCDate(anchorISO); const d = toUTCDate(date); - const days = Math.floor((+d - +ANCHOR) / 86400000); + const days = Math.floor((+d - +ANCHOR) / MS_PER_DAY); const cycles = Math.floor(days / (PERIODS_PER_YEAR * PERIOD_DAYS)); return ANCHOR.getUTCFullYear() + 1 + cycles; } //compute labels for periods -export function computePeriod(payYear: number, periodNumber: number) { +export function computePeriod(payYear: number, periodNumber: number, anchorISO = ANCHOR_ISO) { + const ANCHOR = toUTCDate(anchorISO); const cycles = payYear - (ANCHOR.getUTCFullYear() + 1); const offsetPeriods = cycles * PERIODS_PER_YEAR + (periodNumber - 1); - const start = new Date(+ANCHOR + offsetPeriods * PERIOD_DAYS * 86400000); - const end = new Date(+start + (PERIOD_DAYS - 1) * 86400000); + const start = new Date(+ANCHOR + offsetPeriods * PERIOD_DAYS * MS_PER_DAY); + const end = new Date(+start + (PERIOD_DAYS - 1) * MS_PER_DAY); return { period_number: periodNumber, year: payYear, @@ -33,6 +36,6 @@ export function computePeriod(payYear: number, periodNumber: number) { } //list of all 26 periods for a full year -export function listPayYear(payYear: number) { - return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(payYear, i + 1)); +export function listPayYear(payYear: number, anchorISO = ANCHOR_ISO) { + return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(payYear, i + 1, anchorISO)); } diff --git a/src/modules/shifts/controllers/shifts-overview.controller.ts b/src/modules/shifts/controllers/shifts-overview.controller.ts deleted file mode 100644 index 7a56602..0000000 --- a/src/modules/shifts/controllers/shifts-overview.controller.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Controller, Get, Header, Query } from "@nestjs/common"; -import { OverviewRow, ShiftsOverviewService } from "../services/shifts-query.service"; -import { GetShiftsOverviewDto } from "../dtos/get-shifts-overview.dto"; - -@Controller() -export class ShiftsOverviewController { - constructor(private readonly shiftsValidationService: ShiftsOverviewService) {} - - @Get() - async getSummary( @Query() query: GetShiftsOverviewDto): Promise { - 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.period_id); - - //CSV Headers - const header = [ - 'fullName', - 'supervisor', - 'totalRegularHrs', - 'totalEveningHrs', - 'totalOvertimeHrs', - 'totalExpenses', - 'totalMileage', - 'isValidated' - ].join(',') + '\n'; - - //CSV rows - const body = rows.map(r => { - const esc = (str: string) => `"${str.replace(/"/g, '""')}"`; - - return [ - esc(r.fullName), - esc(r.supervisor), - r.totalRegularHrs.toFixed(2), - r.totalEveningHrs.toFixed(2), - r.totalOvertimeHrs.toFixed(2), - r.totalExpenses.toFixed(2), - r.totalMileage.toFixed(2), - r.isValidated, - ].join(','); - }).join('\n'); - - return Buffer.from('\uFEFF' + header + body, 'utf8'); - } - -} \ No newline at end of file diff --git a/src/modules/shifts/controllers/shifts.controller.ts b/src/modules/shifts/controllers/shifts.controller.ts index 0a3bc5f..033616d 100644 --- a/src/modules/shifts/controllers/shifts.controller.ts +++ b/src/modules/shifts/controllers/shifts.controller.ts @@ -1,13 +1,14 @@ -import { Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, Query, UseGuards, UsePipes, ValidationPipe } from "@nestjs/common"; -import { ShiftsService } from "../services/shifts.service"; +import { Body, Controller, Delete, Get, Header, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, Query, UseGuards, UsePipes, ValidationPipe } from "@nestjs/common"; import { Shifts } from "@prisma/client"; 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 { ShiftsApprovalService } from "../services/shifts-command.service"; -import { SearchShiftsDto } from "../dtos/search-shifts.dto"; +import { ShiftsCommandService } from "../services/shifts-command.service"; +import { SearchShiftsDto } from "../dtos/search-shift.dto"; +import { OverviewRow, ShiftsQueryService } from "../services/shifts-query.service"; +import { GetShiftsOverviewDto } from "../dtos/get-shift-overview.dto"; @ApiTags('Shifts') @ApiBearerAuth('access-token') @@ -15,8 +16,9 @@ import { SearchShiftsDto } from "../dtos/search-shifts.dto"; @Controller('shifts') export class ShiftsController { constructor( - private readonly shiftsService: ShiftsService, - private readonly shiftsApprovalService: ShiftsApprovalService, + private readonly shiftsService: ShiftsQueryService, + private readonly shiftsApprovalService: ShiftsCommandService, + private readonly shiftsValidationService: ShiftsQueryService, ){} @Post() @@ -71,4 +73,46 @@ export class ShiftsController { return this.shiftsApprovalService.updateApproval(id, isApproved); } + @Get() + async getSummary( @Query() query: GetShiftsOverviewDto): Promise { + 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.period_id); + + //CSV Headers + const header = [ + 'fullName', + 'supervisor', + 'totalRegularHrs', + 'totalEveningHrs', + 'totalOvertimeHrs', + 'totalExpenses', + 'totalMileage', + 'isValidated' + ].join(',') + '\n'; + + //CSV rows + const body = rows.map(r => { + const esc = (str: string) => `"${str.replace(/"/g, '""')}"`; + + return [ + esc(r.fullName), + esc(r.supervisor), + r.totalRegularHrs.toFixed(2), + r.totalEveningHrs.toFixed(2), + r.totalOvertimeHrs.toFixed(2), + r.totalExpenses.toFixed(2), + r.totalMileage.toFixed(2), + r.isValidated, + ].join(','); + }).join('\n'); + + return Buffer.from('\uFEFF' + header + body, 'utf8'); + } + } \ No newline at end of file diff --git a/src/modules/shifts/dtos/get-shifts-overview.dto.ts b/src/modules/shifts/dtos/get-shift-overview.dto.ts similarity index 100% rename from src/modules/shifts/dtos/get-shifts-overview.dto.ts rename to src/modules/shifts/dtos/get-shift-overview.dto.ts diff --git a/src/modules/shifts/dtos/search-shifts.dto.ts b/src/modules/shifts/dtos/search-shift.dto.ts similarity index 100% rename from src/modules/shifts/dtos/search-shifts.dto.ts rename to src/modules/shifts/dtos/search-shift.dto.ts diff --git a/src/modules/shifts/services/shifts-command.service.ts b/src/modules/shifts/services/shifts-command.service.ts index d95d3b1..e222490 100644 --- a/src/modules/shifts/services/shifts-command.service.ts +++ b/src/modules/shifts/services/shifts-command.service.ts @@ -4,7 +4,7 @@ import { BaseApprovalService } from "src/common/shared/base-approval.service"; import { PrismaService } from "src/prisma/prisma.service"; @Injectable() -export class ShiftsApprovalService extends BaseApprovalService { +export class ShiftsCommandService extends BaseApprovalService { constructor(prisma: PrismaService) { super(prisma); } protected get delegate() { diff --git a/src/modules/shifts/services/shifts-query.service.ts b/src/modules/shifts/services/shifts-query.service.ts index 4098140..f2f8186 100644 --- a/src/modules/shifts/services/shifts-query.service.ts +++ b/src/modules/shifts/services/shifts-query.service.ts @@ -1,6 +1,14 @@ import { Injectable, NotFoundException } from "@nestjs/common"; -import { computeHours } from "src/common/utils/date-utils"; import { PrismaService } from "src/prisma/prisma.service"; +import { CreateShiftDto } from "../dtos/create-shift.dto"; +import { Shifts, ShiftsArchive } from "@prisma/client"; +import { UpdateShiftsDto } from "../dtos/update-shift.dto"; +import { buildPrismaWhere } from "src/common/shared/build-prisma-where"; +import { SearchShiftsDto } from "../dtos/search-shift.dto"; +import { NotificationsService } from "src/modules/notifications/services/notifications.service"; +import { computeHours, hoursBetweenSameDay } from "src/common/utils/date-utils"; + +const DAILY_LIMIT_HOURS = Number(process.env.DAILY_LIMIT_HOURS ?? 8); export interface OverviewRow { fullName: string; @@ -14,10 +22,98 @@ export interface OverviewRow { } @Injectable() -export class ShiftsOverviewService { - constructor(private readonly prisma: PrismaService) {} +export class ShiftsQueryService { + constructor( + private readonly prisma: PrismaService, + private readonly notifs: NotificationsService, + ) {} - async getSummary(period_id: number): Promise { + async create(dto: CreateShiftDto): Promise { + const { timesheet_id, bank_code_id, date, start_time, end_time } = dto; + + //shift creation + const shift = await this.prisma.shifts.create({ + data: { timesheet_id, bank_code_id, date, start_time, end_time }, + include: { timesheet: { include: { employee: { include: { user: true } } } }, + bank_code: true, + }, + }); + + //fetches all shifts of the same day to check for daily overtime + const sameDayShifts = await this.prisma.shifts.findMany({ + where: { timesheet_id, date }, + select: { id: true, date: true, start_time: true, end_time: true }, + }); + + //sums hours of the day + const totalHours = sameDayShifts.reduce((sum, s) => { + return sum + hoursBetweenSameDay(s.date, s.start_time, s.end_time); + }, 0 ); + + //Notify if total hours > 8 for a single day + if(totalHours > DAILY_LIMIT_HOURS ) { + const userId = String(shift.timesheet.employee.user.id); + const dateLabel = new Date(date).toLocaleDateString('fr-CA'); + this.notifs.notify(userId, { + type: 'shift.overtime.daily', + severity: 'warn', + message: `Tu viens de dépasser ${DAILY_LIMIT_HOURS.toFixed(2)}h pour la journée du ${dateLabel} (total: ${totalHours.toFixed(2)}h).`, + ts: new Date().toISOString(), + meta: { + timesheet_id, + date: new Date(date).toISOString(), + totalHours, + threshold: DAILY_LIMIT_HOURS, + lastShiftId: shift.id + }, + }); + } + return shift; + } + + async findAll(filters: SearchShiftsDto): Promise { + const where = buildPrismaWhere(filters); + const shifts = await this.prisma.shifts.findMany({ where }) + return shifts; + } + + async findOne(id: number): Promise { + const shift = await this.prisma.shifts.findUnique({ + where: { id }, + include: { timesheet: { include: { employee: { include: { user: true } } } }, + bank_code: true, + }, + }); + if(!shift) { + throw new NotFoundException(`Shift #${id} not found`); + } + return shift; + } + + async update(id: number, dto: UpdateShiftsDto): Promise { + await this.findOne(id); + const { timesheet_id, bank_code_id, date,start_time,end_time} = dto; + return this.prisma.shifts.update({ + where: { id }, + data: { + ...(timesheet_id !== undefined && { timesheet_id }), + ...(bank_code_id !== undefined && { bank_code_id }), + ...(date !== undefined && { date }), + ...(start_time !== undefined && { start_time }), + ...(end_time !== undefined && { end_time }), + }, + include: { timesheet: { include: { employee: { include: { user: true } } } }, + bank_code: true, + }, + }); + } + + async remove(id: number): Promise { + await this.findOne(id); + return this.prisma.shifts.delete({ where: { id } }); + } + + async getSummary(period_id: number): Promise { //fetch pay-period to display const period = await this.prisma.payPeriods.findUnique({ where: { period_number: period_id }, @@ -116,4 +212,58 @@ export class ShiftsOverviewService { return Array.from(mapRow.values()).sort((a,b) => a.fullName.localeCompare(b.fullName)); } -} + //archivation functions ****************************************************** + + async archiveOld(): Promise { + //fetches archived timesheet's Ids + const archivedTimesheets = await this.prisma.timesheetsArchive.findMany({ + select: { timesheet_id: true }, + }); + + const timesheetIds = archivedTimesheets.map(sheet => sheet.timesheet_id); + if(timesheetIds.length === 0) { + return; + } + + // copy/delete transaction + await this.prisma.$transaction(async transaction => { + //fetches shifts to move to archive + const shiftsToArchive = await transaction.shifts.findMany({ + where: { timesheet_id: { in: timesheetIds } }, + }); + if(shiftsToArchive.length === 0) { + return; + } + + //copies sent to archive table + await transaction.shiftsArchive.createMany({ + data: shiftsToArchive.map(shift => ({ + shift_id: shift.id, + timesheet_id: shift.timesheet_id, + bank_code_id: shift.bank_code_id, + description: shift.description ?? undefined, + date: shift.date, + start_time: shift.start_time, + end_time: shift.end_time, + })), + }); + + //delete from shifts table + await transaction.shifts.deleteMany({ + where: { id: { in: shiftsToArchive.map(shift => shift.id) } }, + }) + + }) + } + + //fetches all archived timesheets + async findAllArchived(): Promise { + return this.prisma.shiftsArchive.findMany(); + } + + //fetches an archived timesheet + async findOneArchived(id: number): Promise { + return this.prisma.shiftsArchive.findUniqueOrThrow({ where: { id } }); + } + +} \ No newline at end of file diff --git a/src/modules/shifts/services/shifts.service.ts b/src/modules/shifts/services/shifts.service.ts deleted file mode 100644 index fca99ca..0000000 --- a/src/modules/shifts/services/shifts.service.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { Injectable, NotFoundException } from "@nestjs/common"; -import { PrismaService } from "src/prisma/prisma.service"; -import { CreateShiftDto } from "../dtos/create-shift.dto"; -import { Shifts, ShiftsArchive } from "@prisma/client"; -import { UpdateShiftsDto } from "../dtos/update-shift.dto"; -import { buildPrismaWhere } from "src/common/shared/build-prisma-where"; -import { SearchShiftsDto } from "../dtos/search-shifts.dto"; -import { NotificationsService } from "src/modules/notifications/services/notifications.service"; -import { hoursBetweenSameDay } from "src/common/utils/date-utils"; - -const DAILY_LIMIT_HOURS = Number(process.env.DAILY_LIMIT_HOURS ?? 8); - -@Injectable() -export class ShiftsService { - constructor( - private readonly prisma: PrismaService, - private readonly notifs: NotificationsService, - ) {} - - async create(dto: CreateShiftDto): Promise { - const { timesheet_id, bank_code_id, date, start_time, end_time } = dto; - - //shift creation - const shift = await this.prisma.shifts.create({ - data: { timesheet_id, bank_code_id, date, start_time, end_time }, - include: { timesheet: { include: { employee: { include: { user: true } } } }, - bank_code: true, - }, - }); - - //fetches all shifts of the same day to check for daily overtime - const sameDayShifts = await this.prisma.shifts.findMany({ - where: { timesheet_id, date }, - select: { id: true, date: true, start_time: true, end_time: true }, - }); - - //sums hours of the day - const totalHours = sameDayShifts.reduce((sum, s) => { - return sum + hoursBetweenSameDay(s.date, s.start_time, s.end_time); - }, 0 ); - - //Notify if total hours > 8 for a single day - if(totalHours > DAILY_LIMIT_HOURS ) { - const userId = String(shift.timesheet.employee.user.id); - const dateLabel = new Date(date).toLocaleDateString('fr-CA'); - this.notifs.notify(userId, { - type: 'shift.overtime.daily', - severity: 'warn', - message: `Tu viens de dépasser ${DAILY_LIMIT_HOURS.toFixed(2)}h pour la journée du ${dateLabel} (total: ${totalHours.toFixed(2)}h).`, - ts: new Date().toISOString(), - meta: { - timesheet_id, - date: new Date(date).toISOString(), - totalHours, - threshold: DAILY_LIMIT_HOURS, - lastShiftId: shift.id - }, - }); - } - return shift; - } - - async findAll(filters: SearchShiftsDto): Promise { - const where = buildPrismaWhere(filters); - const shifts = await this.prisma.shifts.findMany({ where }) - return shifts; - } - - async findOne(id: number): Promise { - const shift = await this.prisma.shifts.findUnique({ - where: { id }, - include: { timesheet: { include: { employee: { include: { user: true } } } }, - bank_code: true, - }, - }); - if(!shift) { - throw new NotFoundException(`Shift #${id} not found`); - } - return shift; - } - - async update(id: number, dto: UpdateShiftsDto): Promise { - await this.findOne(id); - const { timesheet_id, bank_code_id, date,start_time,end_time} = dto; - return this.prisma.shifts.update({ - where: { id }, - data: { - ...(timesheet_id !== undefined && { timesheet_id }), - ...(bank_code_id !== undefined && { bank_code_id }), - ...(date !== undefined && { date }), - ...(start_time !== undefined && { start_time }), - ...(end_time !== undefined && { end_time }), - }, - include: { timesheet: { include: { employee: { include: { user: true } } } }, - bank_code: true, - }, - }); - } - - async remove(id: number): Promise { - await this.findOne(id); - return this.prisma.shifts.delete({ where: { id } }); - } - - //archivation functions ****************************************************** - - async archiveOld(): Promise { - //fetches archived timesheet's Ids - const archivedTimesheets = await this.prisma.timesheetsArchive.findMany({ - select: { timesheet_id: true }, - }); - - const timesheetIds = archivedTimesheets.map(sheet => sheet.timesheet_id); - if(timesheetIds.length === 0) { - return; - } - - // copy/delete transaction - await this.prisma.$transaction(async transaction => { - //fetches shifts to move to archive - const shiftsToArchive = await transaction.shifts.findMany({ - where: { timesheet_id: { in: timesheetIds } }, - }); - if(shiftsToArchive.length === 0) { - return; - } - - //copies sent to archive table - await transaction.shiftsArchive.createMany({ - data: shiftsToArchive.map(shift => ({ - shift_id: shift.id, - timesheet_id: shift.timesheet_id, - bank_code_id: shift.bank_code_id, - description: shift.description ?? undefined, - date: shift.date, - start_time: shift.start_time, - end_time: shift.end_time, - })), - }); - - //delete from shifts table - await transaction.shifts.deleteMany({ - where: { id: { in: shiftsToArchive.map(shift => shift.id) } }, - }) - - }) - } - - //fetches all archived timesheets - async findAllArchived(): Promise { - return this.prisma.shiftsArchive.findMany(); - } - - //fetches an archived timesheet - async findOneArchived(id: number): Promise { - return this.prisma.shiftsArchive.findUniqueOrThrow({ where: { id } }); - } - -} \ No newline at end of file diff --git a/src/modules/shifts/shifts.module.ts b/src/modules/shifts/shifts.module.ts index eb3ad29..7c0e3ef 100644 --- a/src/modules/shifts/shifts.module.ts +++ b/src/modules/shifts/shifts.module.ts @@ -1,16 +1,14 @@ import { Module } from '@nestjs/common'; import { ShiftsController } from './controllers/shifts.controller'; -import { ShiftsService } from './services/shifts.service'; import { BusinessLogicsModule } from 'src/modules/business-logics/business-logics.module'; -import { ShiftsOverviewController } from './controllers/shifts-overview.controller'; -import { ShiftsOverviewService } from './services/shifts-query.service'; -import { ShiftsApprovalService } from './services/shifts-command.service'; +import { ShiftsCommandService } from './services/shifts-command.service'; import { NotificationsModule } from '../notifications/notifications.module'; +import { ShiftsQueryService } from './services/shifts-query.service'; @Module({ imports: [BusinessLogicsModule, NotificationsModule], - controllers: [ShiftsController, ShiftsOverviewController], - providers: [ShiftsService, ShiftsOverviewService, ShiftsApprovalService], - exports: [ShiftsService, ShiftsOverviewService, ShiftsApprovalService], + controllers: [ShiftsController], + providers: [ShiftsQueryService, ShiftsCommandService], + exports: [ShiftsQueryService, ShiftsCommandService], }) export class ShiftsModule {} diff --git a/src/modules/timesheets/controllers/timesheets.controller.ts b/src/modules/timesheets/controllers/timesheets.controller.ts index 35463e4..04913cb 100644 --- a/src/modules/timesheets/controllers/timesheets.controller.ts +++ b/src/modules/timesheets/controllers/timesheets.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, Query, UseGuards, UsePipes, ValidationPipe } from '@nestjs/common'; -import { TimesheetsService } from '../services/timesheets.service'; +import { TimesheetsQueryService } from '../services/timesheets-query.service'; import { CreateTimesheetDto } from '../dtos/create-timesheet.dto'; import { Timesheets } from '@prisma/client'; import { UpdateTimesheetDto } from '../dtos/update-timesheet.dto'; @@ -7,7 +7,7 @@ import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { Roles as RoleEnum } from '.prisma/client'; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { TimesheetsCommandService } from '../services/timesheets-command.service'; -import { SearchTimesheetDto } from '../dtos/search-timesheets.dto'; +import { SearchTimesheetDto } from '../dtos/search-timesheet.dto'; @ApiTags('Timesheets') @ApiBearerAuth('access-token') @@ -15,7 +15,7 @@ import { SearchTimesheetDto } from '../dtos/search-timesheets.dto'; @Controller('timesheets') export class TimesheetsController { constructor( - private readonly timesheetsService: TimesheetsService, + private readonly timesheetsService: TimesheetsQueryService, private readonly timesheetsCommandService: TimesheetsCommandService, ) {} diff --git a/src/modules/timesheets/dtos/search-timesheets.dto.ts b/src/modules/timesheets/dtos/search-timesheet.dto.ts similarity index 100% rename from src/modules/timesheets/dtos/search-timesheets.dto.ts rename to src/modules/timesheets/dtos/search-timesheet.dto.ts diff --git a/src/modules/timesheets/services/timesheets.service.ts b/src/modules/timesheets/services/timesheets-query.service.ts similarity index 98% rename from src/modules/timesheets/services/timesheets.service.ts rename to src/modules/timesheets/services/timesheets-query.service.ts index 97cfda5..82e0fae 100644 --- a/src/modules/timesheets/services/timesheets.service.ts +++ b/src/modules/timesheets/services/timesheets-query.service.ts @@ -6,10 +6,10 @@ 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'; +import { SearchTimesheetDto } from '../dtos/search-timesheet.dto'; @Injectable() -export class TimesheetsService { +export class TimesheetsQueryService { constructor( private readonly prisma: PrismaService, private readonly overtime: OvertimeService, diff --git a/src/modules/timesheets/timesheets.module.ts b/src/modules/timesheets/timesheets.module.ts index 5f3ca84..cbfc001 100644 --- a/src/modules/timesheets/timesheets.module.ts +++ b/src/modules/timesheets/timesheets.module.ts @@ -1,20 +1,20 @@ import { Module } from '@nestjs/common'; import { TimesheetsController } from './controllers/timesheets.controller'; -import { TimesheetsService } from './services/timesheets.service'; +import { TimesheetsQueryService } from './services/timesheets-query.service'; import { BusinessLogicsModule } from 'src/modules/business-logics/business-logics.module'; import { TimesheetsCommandService } from './services/timesheets-command.service'; -import { ShiftsApprovalService } from '../shifts/services/shifts-command.service'; -import { ExpensesApprovalService } from '../expenses/services/expenses-command.service'; +import { ShiftsCommandService } from '../shifts/services/shifts-command.service'; +import { ExpensesCommandService } from '../expenses/services/expenses-command.service'; @Module({ imports: [BusinessLogicsModule], controllers: [TimesheetsController], providers: [ - TimesheetsService, + TimesheetsQueryService, TimesheetsCommandService, - ShiftsApprovalService, - ExpensesApprovalService + ShiftsCommandService, + ExpensesCommandService ], - exports: [TimesheetsService], + exports: [TimesheetsQueryService], }) export class TimesheetsModule {}