refactor(module): rename and refactor services

This commit is contained in:
Matthieu Haineault 2025-08-11 14:58:03 -04:00
parent 6139d335c3
commit 4c880e47bf
46 changed files with 419 additions and 451 deletions

View File

@ -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"
]
}
},

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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,
) {}

View File

@ -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({

View File

@ -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],

View File

@ -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')

View File

@ -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) {}

View File

@ -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 {

View File

@ -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()

View File

@ -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 {}

View File

@ -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() {

View File

@ -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,

View File

@ -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')

View File

@ -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){}

View File

@ -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()

View File

@ -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 {

View File

@ -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')

View File

@ -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) {}

View File

@ -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 {

View File

@ -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<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")

View File

@ -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,
]
})

View File

@ -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({
@ -177,52 +181,107 @@ export class PayPeriodsQueryService {
}
async getCrewOverview(year: number, periodNumber: number, userId: string, includeSubtree: boolean): Promise<PayPeriodOverviewDto> {
// 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<PayPeriodOverviewDto> {
// 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<number, string>(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<number, string>(crew.map(c => [c.id, `${c.first_name} ${c.last_name}`.trim()]));
// 4) Construire loverview filtré par ce crew
return this.buildOverview(period, { restrictEmployeeIds: crewIds, seedNames });
}
// 4) Construire loverview 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 }>> {
const result: 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)
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<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 (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<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);
}
}

View File

@ -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);
}
}

View File

@ -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));
}

View File

@ -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');
}
}

View File

@ -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');
}
}

View File

@ -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() {

View File

@ -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<OverviewRow[]> {
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
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<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 } });
}
}

View File

@ -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 } });
}
}

View File

@ -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 {}

View File

@ -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,
) {}

View File

@ -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,

View File

@ -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 {}