refactor(module): rename and refactor services
This commit is contained in:
parent
6139d335c3
commit
4c880e47bf
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
@ -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<Expenses> {
|
||||
export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
||||
constructor(prisma: PrismaService) { super(prisma); }
|
||||
|
||||
protected get delegate() {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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){}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,14 +9,13 @@ 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,
|
||||
) {}
|
||||
|
|
@ -26,7 +24,7 @@ export class PayPeriodsController {
|
|||
@ApiOperation({ summary: 'Find all pay period' })
|
||||
@ApiResponse({status: 200,description: 'List of pay period found', type: PayPeriodDto, isArray: true })
|
||||
async findAll(): Promise<PayPeriodDto[]> {
|
||||
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<PayPeriodBundleDto> {
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
]
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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<PayPeriodOverviewDto> {
|
||||
const period = await this.prisma.payPeriods.findFirst({
|
||||
|
|
@ -197,9 +201,9 @@ export class PayPeriodsQueryService {
|
|||
|
||||
// 4) Construire l’overview filtré par ce crew
|
||||
return this.buildOverview(period, { restrictEmployeeIds: crewIds, seedNames });
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveCrew(supervisorId: number, includeSubtree: boolean): Promise<Array<{ id: number; first_name: string; last_name: string }>> {
|
||||
private async resolveCrew(supervisorId: number, includeSubtree: boolean): Promise<Array<{ id: number; first_name: string; last_name: string }>> {
|
||||
const result: Array<{ id: number; first_name: string; last_name: string }> = [];
|
||||
|
||||
// niveau 1 (directs)
|
||||
|
|
@ -224,5 +228,60 @@ export class PayPeriodsQueryService {
|
|||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
async findAll(): Promise<PayPeriodDto[]> {
|
||||
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<PayPeriodDto> {
|
||||
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<PayPeriodDto> {
|
||||
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<PayPeriodDto> {
|
||||
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<PayPeriodDto> {
|
||||
const isoDay = date ?? new Date().toISOString().slice(0,10);
|
||||
return this.findByDate(isoDay);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PayPeriodDto[]> {
|
||||
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<PayPeriodDto> {
|
||||
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<PayPeriodDto> {
|
||||
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<PayPeriodDto> {
|
||||
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<PayPeriodDto> {
|
||||
const isoDay = date ?? new Date().toISOString().slice(0,10);
|
||||
return this.findByDate(isoDay);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<OverviewRow[]> {
|
||||
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<Buffer>{
|
||||
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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<OverviewRow[]> {
|
||||
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<Buffer>{
|
||||
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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Shifts> {
|
||||
export class ShiftsCommandService extends BaseApprovalService<Shifts> {
|
||||
constructor(prisma: PrismaService) { super(prisma); }
|
||||
|
||||
protected get delegate() {
|
||||
|
|
|
|||
|
|
@ -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,8 +22,96 @@ 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 create(dto: CreateShiftDto): Promise<Shifts> {
|
||||
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 <Shifts[]> {
|
||||
const where = buildPrismaWhere(filters);
|
||||
const shifts = await this.prisma.shifts.findMany({ where })
|
||||
return shifts;
|
||||
}
|
||||
|
||||
async findOne(id: number): Promise<Shifts> {
|
||||
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<Shifts> {
|
||||
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<Shifts> {
|
||||
await this.findOne(id);
|
||||
return this.prisma.shifts.delete({ where: { id } });
|
||||
}
|
||||
|
||||
async getSummary(period_id: number): Promise<OverviewRow[]> {
|
||||
//fetch pay-period to display
|
||||
|
|
@ -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<void> {
|
||||
//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<ShiftsArchive[]> {
|
||||
return this.prisma.shiftsArchive.findMany();
|
||||
}
|
||||
|
||||
//fetches an archived timesheet
|
||||
async findOneArchived(id: number): Promise<ShiftsArchive> {
|
||||
return this.prisma.shiftsArchive.findUniqueOrThrow({ where: { id } });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Shifts> {
|
||||
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 <Shifts[]> {
|
||||
const where = buildPrismaWhere(filters);
|
||||
const shifts = await this.prisma.shifts.findMany({ where })
|
||||
return shifts;
|
||||
}
|
||||
|
||||
async findOne(id: number): Promise<Shifts> {
|
||||
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<Shifts> {
|
||||
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<Shifts> {
|
||||
await this.findOne(id);
|
||||
return this.prisma.shifts.delete({ where: { id } });
|
||||
}
|
||||
|
||||
//archivation functions ******************************************************
|
||||
|
||||
async archiveOld(): Promise<void> {
|
||||
//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<ShiftsArchive[]> {
|
||||
return this.prisma.shiftsArchive.findMany();
|
||||
}
|
||||
|
||||
//fetches an archived timesheet
|
||||
async findOneArchived(id: number): Promise<ShiftsArchive> {
|
||||
return this.prisma.shiftsArchive.findUniqueOrThrow({ where: { id } });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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 {}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user