diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index cb414b8..c8b8500 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -253,16 +253,16 @@ ] } }, - "/employees/{id}": { + "/employees/{email}": { "get": { "operationId": "EmployeesController_findOne", "parameters": [ { - "name": "id", + "name": "email", "required": true, "in": "path", "schema": { - "type": "number" + "type": "string" } } ], @@ -295,10 +295,10 @@ "operationId": "EmployeesController_remove", "parameters": [ { - "name": "id", + "name": "email", "required": true, "in": "path", - "description": "Identifier of the employee to delete", + "description": "Email of the employee to delete", "schema": { "type": "number" } @@ -326,10 +326,10 @@ "operationId": "EmployeesController_updateOrArchiveOrRestore", "parameters": [ { - "name": "id", + "name": "email", "required": true, "in": "path", - "description": "Identifier of the employee", + "description": "Email of the employee", "schema": { "type": "number" } diff --git a/prisma/migrations/20250818193726_minor_naming_fix/migration.sql b/prisma/migrations/20250818193726_minor_naming_fix/migration.sql new file mode 100644 index 0000000..2307a37 --- /dev/null +++ b/prisma/migrations/20250818193726_minor_naming_fix/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - You are about to drop the column `first_Work_Day` on the `employees_archive` table. All the data in the column will be lost. + - Added the required column `first_work_day` to the `employees_archive` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "public"."employees_archive" DROP COLUMN "first_Work_Day", +ADD COLUMN "first_work_day" DATE NOT NULL; diff --git a/prisma/mock-seeds-scripts/05-employees-archive.ts b/prisma/mock-seeds-scripts/05-employees-archive.ts index c030196..08e457c 100644 --- a/prisma/mock-seeds-scripts/05-employees-archive.ts +++ b/prisma/mock-seeds-scripts/05-employees-archive.ts @@ -24,7 +24,7 @@ async function main() { last_name: e.user.last_name, external_payroll_id: e.external_payroll_id, company_code: e.company_code, - first_Work_Day: e.first_work_day, + first_work_day: e.first_work_day, last_work_day: daysAgo(30), supervisor_id: e.supervisor_id ?? null, job_title: e.job_title, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 3eed529..58524d1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -69,7 +69,7 @@ model EmployeesArchive { is_supervisor Boolean external_payroll_id Int company_code Int - first_Work_Day DateTime @db.Date + first_work_day DateTime @db.Date last_work_day DateTime @db.Date supervisor_id Int? supervisor Employees? @relation("EmployeeSupervisorToArchive", fields: [supervisor_id], references: [id]) diff --git a/src/common/shared/base-approval.service.ts b/src/common/shared/base-approval.service.ts index d3e3931..a624fe7 100644 --- a/src/common/shared/base-approval.service.ts +++ b/src/common/shared/base-approval.service.ts @@ -1,26 +1,52 @@ import { NotFoundException } from "@nestjs/common"; +import { Prisma } from "@prisma/client"; +import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library"; import { PrismaService } from "src/prisma/prisma.service"; + +type UpdatableDelegate = { + update(args: { + where: { id: number }, + data: { is_approved: boolean }, + }): Promise; +}; + //abstract class for approving or rejecting a shift, expense, timesheet or pay-period export abstract class BaseApprovalService { protected constructor(protected readonly prisma: PrismaService) {} //returns the corresponding Prisma delegate - protected abstract get delegate(): { - update(args: {where: {id: number }; - data: { is_approved: boolean } - }): Promise; - }; + protected abstract get delegate(): UpdatableDelegate; + + protected abstract delegateFor(transaction: Prisma.TransactionClient): UpdatableDelegate; //standard update Aproval async updateApproval(id: number, isApproved: boolean): Promise { - const entity = await this.delegate.update({ - where: { id }, - data: { is_approved: isApproved }, - }); - - if(!entity) throw new NotFoundException(`Entity #${id} not found`); - - return entity; + try{ + return await this.delegate.update({ + where: { id }, + data: { is_approved: isApproved }, + }); + }catch (error: any) { + if (error instanceof PrismaClientKnownRequestError && error.code === "P2025") { + throw new NotFoundException(`Entity #${id} not found`); + } + throw error; + } } -} \ No newline at end of file + + //approval with transaction to avoid many requests + async updateApprovalWithTx(transaction: Prisma.TransactionClient, id: number, isApproved: boolean): Promise { + try { + return await this.delegateFor(transaction).update({ + where: { id }, + data: { is_approved: isApproved }, + }); + } catch (error: any ){ + if(error instanceof PrismaClientKnownRequestError && error.code === 'P2025') { + throw new NotFoundException(`Entity #${id} not found`); + } + throw error; + } + } +} \ No newline at end of file diff --git a/src/modules/employees/controllers/employees.controller.ts b/src/modules/employees/controllers/employees.controller.ts index 8faea6c..e46d2cc 100644 --- a/src/modules/employees/controllers/employees.controller.ts +++ b/src/modules/employees/controllers/employees.controller.ts @@ -42,13 +42,13 @@ export class EmployeesController { return this.employeesService.findListEmployees(); } - @Get(':id') + @Get(':email') //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR,RoleEnum.ACCOUNTING ) @ApiOperation({summary: 'Find employee' }) @ApiResponse({ status: 200, description: 'Employee found', type: CreateEmployeeDto }) @ApiResponse({ status: 400, description: 'Employee not found' }) - findOne(@Param('id', ParseIntPipe) id: number): Promise { - return this.employeesService.findOne(id); + findOne(@Param('email', ParseIntPipe) email: string): Promise { + return this.employeesService.findOne(email); } @Get('profile/:email') @@ -60,31 +60,31 @@ export class EmployeesController { return this.employeesService.findOneProfile(email); } - @Delete(':id') + @Delete(':email') //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR ) @ApiOperation({summary: 'Delete employee' }) - @ApiParam({ name: 'id', type: Number, description: 'Identifier of the employee to delete' }) + @ApiParam({ name: 'email', type: Number, description: 'Email of the employee to delete' }) @ApiResponse({ status: 204, description: 'Employee deleted' }) @ApiResponse({ status: 404, description: 'Employee not found' }) - remove(@Param('id', ParseIntPipe) id: number): Promise { - return this.employeesService.remove(id); + remove(@Param('email', ParseIntPipe) email: string): Promise { + return this.employeesService.remove(email); } - @Patch(':id') + @Patch(':email') //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR) @ApiBearerAuth('access-token') @ApiOperation({ summary: 'Update, archive or restore an employee' }) - @ApiParam({ name: 'id', type: Number, description: 'Identifier of the employee' }) + @ApiParam({ name: 'email', type: Number, description: 'Email of the employee' }) @ApiResponse({ status: 200, description: 'Employee updated or restored', type: CreateEmployeeDto }) @ApiResponse({ status: 202, description: 'Employee archived successfully', type: CreateEmployeeDto }) @ApiResponse({ status: 404, description: 'Employee not found in active or archive' }) - async updateOrArchiveOrRestore(@Param('id') id: string, @Body() dto: UpdateEmployeeDto,) { + async updateOrArchiveOrRestore(@Param('email') email: string, @Body() dto: UpdateEmployeeDto,) { // if last_work_day is set => archive the employee // else if employee is archived and first_work_day or last_work_day = null => restore //otherwise => standard update - const result = await this.employeesService.patchEmployee(+id, dto); + const result = await this.employeesService.patchEmployee(email, dto); if(!result) { - throw new NotFoundException(`Employee #${ id } not found in active or archive.`) + throw new NotFoundException(`Employee with email: ${ email } is not found in active or archive.`) } return result; } diff --git a/src/modules/employees/services/employees.service.ts b/src/modules/employees/services/employees.service.ts index 0315812..57181da 100644 --- a/src/modules/employees/services/employees.service.ts +++ b/src/modules/employees/services/employees.service.ts @@ -4,7 +4,6 @@ import { CreateEmployeeDto } from '../dtos/create-employee.dto'; import { UpdateEmployeeDto } from '../dtos/update-employee.dto'; import { Employees, EmployeesArchive, Users } from '@prisma/client'; import { EmployeeListItemDto } from '../dtos/list-employee.dto'; -import { Roles as RoleEnum } from '@prisma/client'; import { EmployeeProfileItemDto } from '../dtos/profil-employee.dto'; function toDateOrNull(v?: string | null): Date | null { @@ -100,13 +99,15 @@ export class EmployeesService { ); } - async findOne(id: number): Promise { - const emp = await this.prisma.employees.findUnique({ - where: { id }, + async findOne(email: string): Promise { + const emp = await this.prisma.employees.findFirst({ + where: { user: { email } }, include: { user: true }, }); + + //add search for archived employees if (!emp) { - throw new NotFoundException(`Employee #${id} not found`); + throw new NotFoundException(`Employee with email: ${email} not found`); } return emp; } @@ -156,16 +157,15 @@ export class EmployeesService { }; } -async update( - id: number, + async update( + email: string, dto: UpdateEmployeeDto, ): Promise { - const emp = await this.findOne(id); + const emp = await this.findOne(email); const { first_name, last_name, - email, phone_number, residence, external_payroll_id, @@ -173,23 +173,32 @@ async update( job_title, first_work_day, last_work_day, - is_supervisor + is_supervisor, + email: newEmail, } = dto; return this.prisma.$transaction(async (tx) => { - await tx.users.update({ - where: { id: emp.user_id }, - data: { - ...(first_name !== undefined && { first_name }), - ...(last_name !== undefined && { last_name }), - ...(email !== undefined && { email }), - ...(phone_number !== undefined && { phone_number }), - ...(residence !== undefined && { residence }), - }, - }); + if( + first_name !== undefined || + last_name !== undefined || + newEmail !== undefined || + phone_number !== undefined || + residence !== undefined + ){ + await tx.users.update({ + where: { id: emp.user_id }, + data: { + ...(first_name !== undefined && { first_name }), + ...(last_name !== undefined && { last_name }), + ...(email !== undefined && { email }), + ...(phone_number !== undefined && { phone_number }), + ...(residence !== undefined && { residence }), + }, + }); + } - return tx.employees.update({ - where: { id }, + const updated = await tx.employees.update({ + where: { id: emp.id }, data: { ...(external_payroll_id !== undefined && { external_payroll_id }), ...(company_code !== undefined && { company_code }), @@ -199,36 +208,51 @@ async update( ...(is_supervisor !== undefined && { is_supervisor }), }, }); + return updated; }); } - async remove(id: number): Promise { - await this.findOne(id); - return this.prisma.employees.delete({ where: { id } }); + async remove(email: string): Promise { + + const emp = await this.findOne(email); + + return this.prisma.$transaction(async (tx) => { + await tx.employees.updateMany({ + where: { supervisor_id: emp.id }, + data: { supervisor_id: null }, + }); + const deletedEmployee = await tx.employees.delete({ + where: {id: emp.id }, + }); + await tx.users.delete({ + where: { id: emp.user_id }, + }); + return deletedEmployee; + }); } //archivation functions ****************************************************** -async patchEmployee(id: number, dto: UpdateEmployeeDto): Promise { +async patchEmployee(email: string, dto: UpdateEmployeeDto): Promise { // 1) Tenter sur employés actifs - const existing = await this.prisma.employees.findUnique({ - where: { id }, + const active = await this.prisma.employees.findFirst({ + where: { user: { email } }, include: { user: true }, }); - if (existing) { + if (active) { // Archivage : si on reçoit un last_work_day défini et que l'employé n’est pas déjà terminé - if (dto.last_work_day !== undefined && existing.last_work_day == null && dto.last_work_day !== null) { - return this.archiveOnTermination(existing, dto); + if (dto.last_work_day !== undefined && active.last_work_day == null && dto.last_work_day !== null) { + return this.archiveOnTermination(active, dto); } // Sinon, update standard (split Users/Employees) const { first_name, last_name, - email, + email: newEmail, phone_number, residence, external_payroll_id, @@ -238,54 +262,59 @@ async patchEmployee(id: number, dto: UpdateEmployeeDto): Promise { - const willUpdateUser = - first_name !== undefined || - last_name !== undefined || - email !== undefined || + if( + first_name !== undefined || + last_name !== undefined || + newEmail !== undefined || phone_number !== undefined || - residence !== undefined; - - if (willUpdateUser) { + residence !== undefined + ) { await tx.users.update({ - where: { id: existing.user_id }, + where: { id: active.user_id }, data: { - ...(first_name !== undefined ? { first_name } : {}), - ...(last_name !== undefined ? { last_name } : {}), - ...(email !== undefined ? { email } : {}), - ...(phone_number !== undefined ? { phone_number } : {}), - ...(residence !== undefined ? { residence } : {}), - ...(is_supervisor !== undefined ? { is_supervisor }: {}), + ...(first_name !== undefined ? { first_name } : {}), + ...(last_name !== undefined ? { last_name } : {}), + ...(email !== undefined ? { email: newEmail }: {}), + ...(phone_number !== undefined ? { phone_number } : {}), + ...(residence !== undefined ? { residence } : {}), }, }); + } - await tx.employees.update({ - where: { id }, + const updated = await tx.employees.update({ + where: { id: active.id }, data: { - ...(external_payroll_id !== undefined ? { external_payroll_id } : {}), - ...(company_code !== undefined ? { company_code } : {}), - ...(job_title !== undefined ? { job_title } : {}), - ...(fw !== undefined ? { first_work_day: fw } : {}), - ...(lw !== undefined ? { last_work_day: lw } : {}), - ...(supervisor_id !== undefined ? { supervisor_id } : {}), + ...(external_payroll_id !== undefined ? { external_payroll_id } : {}), + ...(company_code !== undefined ? { company_code } : {}), + ...(job_title !== undefined ? { job_title } : {}), + ...(first_work_d !== undefined ? { first_work_day: first_work_d } : {}), + ...(last_work_d !== undefined ? { last_work_day: last_work_d } : {}), + ...(is_supervisor !== undefined ? { is_supervisor } : {}), + ...(supervisor_id !== undefined ? { supervisor_id } : {}), }, + include: { user: true }, }); + + return updated; }); - return this.prisma.employees.findUnique({ where: { id } }); + return this.prisma.employees.findFirst({ where: { user: {email} } }); } + const user = await this.prisma.users.findUnique({where: {email}}); + if(!user) return null; // 2) Pas trouvé en actifs → regarder en archive (pour restauration) const archived = await this.prisma.employeesArchive.findFirst({ - where: { employee_id: id }, + where: { user_id: user.id }, include: { user: true }, }); @@ -296,51 +325,57 @@ async patchEmployee(id: number, dto: UpdateEmployeeDto): Promise { + private async archiveOnTermination(active: Employees & {user: Users }, dto: UpdateEmployeeDto): Promise { + const last_work_d = toDateOrNull(dto.last_work_day!); + if(!last_work_d) throw new Error('invalide last_work_day for archive'); return this.prisma.$transaction(async transaction => { - //archive insertion - const archived = await transaction.employeesArchive.create({ - data: { - employee_id: existing.id, - user_id: existing.user_id, - first_name: existing.first_name, - last_name: existing.last_name, - external_payroll_id: existing.external_payroll_id, - company_code: existing.company_code, - job_title: existing.job_title, - first_Work_Day: existing.first_Work_Day, - last_work_day: existing.last_work_day, - supervisor_id: existing.supervisor_id ?? null, - is_supervisor: existing.is_supervisor, - }, - }); + //detach crew from supervisor if employee is a supervisor + await transaction.employees.updateMany({ + where: { supervisor_id: active.id }, + data: { supervisor_id: null }, + }) + const archived = await transaction.employeesArchive.create({ + data: { + employee_id: active.id, + user_id: active.user_id, + first_name: active.user.first_name, + last_name: active.user.last_name, + external_payroll_id: active.external_payroll_id, + company_code: active.company_code, + job_title: active.job_title, + first_work_day: active.first_work_day, + last_work_day: last_work_d, + supervisor_id: active.supervisor_id ?? null, + is_supervisor: active.is_supervisor, + }, + include: { user: true} + }); //delete from employees table - await transaction.employees.delete({ where: { id: existing.id } }); + await transaction.employees.delete({ where: { id: active.id } }); //return archived employee - return archived + return archived }); } //transfers the employee from archive to the employees table - private async restoreEmployee(archived: any, dto: UpdateEmployeeDto): Promise { + private async restoreEmployee(archived: EmployeesArchive & { user:Users }, dto: UpdateEmployeeDto): Promise { + const first_work_d = toDateOrUndefined(dto.first_work_day); return this.prisma.$transaction(async transaction => { //restores the archived employee into the employees table const restored = await transaction.employees.create({ data: { - id: archived.employee_id, user_id: archived.user_id, - external_payroll_id: dto.external_payroll_id ?? archived.external_payroll_id, - company_code: dto.company_code ?? archived.company_code, - job_title: dto.job_title ?? archived.job_title, - first_work_day: dto.first_work_day ?? archived.first_Work_Day, + external_payroll_id: archived.external_payroll_id, + company_code: archived.company_code, + job_title: archived.job_title, + first_work_day: archived.first_work_day, last_work_day: null, - supervisor_id: dto.supervisor_id ?? archived.supervisor_id, + is_supervisor: archived.is_supervisor ?? false, }, }); //deleting archived entry by id diff --git a/src/modules/pay-periods/services/pay-periods-command.service.ts b/src/modules/pay-periods/services/pay-periods-command.service.ts index c7e2a0a..f24b768 100644 --- a/src/modules/pay-periods/services/pay-periods-command.service.ts +++ b/src/modules/pay-periods/services/pay-periods-command.service.ts @@ -11,11 +11,11 @@ export class PayPeriodsCommandService { async approvalPayPeriod(year: number , periodNumber: number): Promise { const period = await this.prisma.payPeriods.findFirst({ - where: { period_number: periodNumber }, + where: { year, period_number: periodNumber}, }); - if (!period) throw new NotFoundException(`PayPeriod #${periodNumber} not found`); + if (!period) throw new NotFoundException(`PayPeriod #${year}-${periodNumber} not found`); - //fetches timesheet of selected period if the timesheet as atleast 1 shift or 1 expense + //fetches timesheet of selected period if the timesheet has atleast 1 shift or 1 expense const timesheetList = await this.prisma.timesheets.findMany({ where: { OR: [ @@ -31,11 +31,14 @@ export class PayPeriodsCommandService { }, ], }, + select: { id: true }, }); //approval of both timesheet (cascading to the approval of related shifts and expenses) - for(const timesheet of timesheetList) { - await this.timesheetsApproval.updateApproval(timesheet.id, true); - } + await this.prisma.$transaction(async (transaction)=> { + for(const {id} of timesheetList) { + await this.timesheetsApproval.updateApprovalWithTx(transaction,id, true); + } + }) } } \ No newline at end of file diff --git a/src/modules/pay-periods/services/pay-periods-query.service.ts b/src/modules/pay-periods/services/pay-periods-query.service.ts index 964c0ff..c3e7196 100644 --- a/src/modules/pay-periods/services/pay-periods-query.service.ts +++ b/src/modules/pay-periods/services/pay-periods-query.service.ts @@ -23,19 +23,19 @@ export class PayPeriodsQueryService { } async getOverviewByYearPeriod(year: number, periodNumber: number): Promise { - const p = computePeriod(year, periodNumber); + const period = computePeriod(year, periodNumber); return this.buildOverview({ - start_date: p.start_date, - end_date : p.end_date, - period_number: p.period_number, - year: p.year, - label:p.label, + start_date: period.start_date, + end_date : period.end_date, + period_number: period.period_number, + year: period.year, + label:period.label, } as any); } private async buildOverview( period: { start_date: string | Date; end_date: string | Date; period_number: number; year: number; label: string; }, - opts?: { restrictEmployeeIds?: number[]; seedNames?: Map }, + options?: { filteredEmployeeIds?: number[]; seedNames?: Map }, ): Promise { const toDateString = (d: Date) => d.toISOString().slice(0, 10); const toMoney = (v: any) => (typeof v === "object" && "toNumber" in v ? v.toNumber() : (v as number)); @@ -48,7 +48,8 @@ export class PayPeriodsQueryService { ? period.end_date : new Date(`${period.end_date}T00:00:00.000Z`); - const whereEmployee = opts?.restrictEmployeeIds?.length ? { employee_id: { in: opts.restrictEmployeeIds } }: {}; + //restrictEmployeeIds = filter for shifts and expenses by employees + const whereEmployee = options?.filteredEmployeeIds?.length ? { employee_id: { in: options.filteredEmployeeIds } }: {}; // SHIFTS (filtered by crew) const shifts = await this.prisma.shifts.findMany({ @@ -99,8 +100,8 @@ export class PayPeriodsQueryService { const byEmployee = new Map(); // seed for employee without data - if (opts?.seedNames) { - for (const [id, name] of opts.seedNames.entries()) { + if (options?.seedNames) { + for (const [id, name] of options.seedNames.entries()) { byEmployee.set(id, { employee_id: id, employee_name: name, @@ -132,37 +133,37 @@ export class PayPeriodsQueryService { return byEmployee.get(id)!; }; - for (const s of shifts) { - const e = s.timesheet.employee; - const name = `${e.user.first_name} ${e.user.last_name}`.trim(); - const rec = ensure(e.id, name); + for (const shift of shifts) { + const employee = shift.timesheet.employee; + const name = `${employee.user.first_name} ${employee.user.last_name}`.trim(); + const rec = ensure(employee.id, name); - const hours = computeHours(s.start_time, s.end_time); - const cat = (s.bank_code?.categorie || "REGULAR").toUpperCase(); - switch (cat) { + const hours = computeHours(shift.start_time, shift.end_time); + const categorie = (shift.bank_code?.categorie || "REGULAR").toUpperCase(); + switch (categorie) { case "EVENING": rec.evening_hours += hours; break; case "EMERGENCY": case "URGENT": rec.emergency_hours += hours; break; case "OVERTIME": rec.overtime_hours += hours; break; default: rec.regular_hours += hours; break; } - rec.is_approved = rec.is_approved && s.timesheet.is_approved; + rec.is_approved = rec.is_approved && shift.timesheet.is_approved; } - for (const ex of expenses) { - const e = ex.timesheet.employee; - const name = `${e.user.first_name} ${e.user.last_name}`.trim(); - const rec = ensure(e.id, name); + for (const expense of expenses) { + const exp = expense.timesheet.employee; + const name = `${exp.user.first_name} ${exp.user.last_name}`.trim(); + const record = ensure(exp.id, name); - const amount = toMoney(ex.amount); - rec.expenses += amount; + const amount = toMoney(expense.amount); + record.expenses += amount; - const cat = (ex.bank_code?.categorie || "").toUpperCase(); - const rate = ex.bank_code?.modifier ?? 0; - if (cat === "MILEAGE" && rate > 0) { - rec.mileage += amount / rate; + const categorie = (expense.bank_code?.categorie || "").toUpperCase(); + const rate = expense.bank_code?.modifier ?? 0; + if (categorie === "MILEAGE" && rate > 0) { + record.mileage += amount / rate; } - rec.is_approved = rec.is_approved && ex.timesheet.is_approved; + record.is_approved = record.is_approved && expense.timesheet.is_approved; } const employees_overview = Array.from(byEmployee.values()).sort((a, b) => @@ -199,7 +200,7 @@ export class PayPeriodsQueryService { const seedNames = new Map(crew.map(c => [c.id, `${c.first_name} ${c.last_name}`.trim()])); // 4) overview build - return this.buildOverview(period, { restrictEmployeeIds: crewIds, seedNames }); + return this.buildOverview(period, { filteredEmployeeIds: crewIds, seedNames }); } private async resolveCrew(supervisorId: number, includeSubtree: boolean): Promise> { diff --git a/src/modules/timesheets/services/timesheets-command.service.ts b/src/modules/timesheets/services/timesheets-command.service.ts index 5f32ecb..9720491 100644 --- a/src/modules/timesheets/services/timesheets-command.service.ts +++ b/src/modules/timesheets/services/timesheets-command.service.ts @@ -1,26 +1,35 @@ import { Injectable } from "@nestjs/common"; -import { Timesheets } from "@prisma/client"; +import { Prisma, Timesheets } from "@prisma/client"; import { BaseApprovalService } from "src/common/shared/base-approval.service"; import { PrismaService } from "src/prisma/prisma.service"; @Injectable() export class TimesheetsCommandService extends BaseApprovalService{ constructor(prisma: PrismaService) {super(prisma);} - + protected get delegate() { return this.prisma.timesheets; } + protected delegateFor(transaction: Prisma.TransactionClient) { + return transaction.timesheets; + } - async updateApproval(timesheetId: number, isApproved: boolean): Promise { - const timesheet = await super.updateApproval(timesheetId, isApproved); + async updateApproval(id: number, isApproved: boolean): Promise { + return this.prisma.$transaction((transaction) => + this.updateApprovalWithTx(transaction, id, isApproved), + ); + } - await this.prisma.shifts.updateMany({ + async cascadeApprovalWithtx(transaction: Prisma.TransactionClient, timesheetId: number, isApproved: boolean): Promise { + const timesheet = await this.updateApprovalWithTx(transaction, timesheetId, isApproved); + + await transaction.shifts.updateMany({ where: { timesheet_id: timesheetId }, data: { is_approved: isApproved }, }); - await this.prisma.expenses.updateMany({ + await transaction.expenses.updateManyAndReturn({ where: { timesheet_id: timesheetId }, data: { is_approved: isApproved }, });