From 646cb58e98db118df3ed465cc1f7dd1d6815a381 Mon Sep 17 00:00:00 2001 From: Nicolas Drolet Date: Tue, 20 Jan 2026 16:28:17 -0500 Subject: [PATCH 1/2] feat(pay-period): begin integration of SSE route for live event-based feedback on timesheet update --- .../expenses/expenses.module.ts | 2 + .../pay-period/dtos/pay-period-event.dto.ts | 5 +++ .../pay-period/pay-periods.controller.ts | 16 ++++++-- .../pay-period/pay-periods.module.ts | 2 + .../services/pay-period-event.service.ts | 16 ++++++++ .../shifts/services/shifts-create.service.ts | 17 +++++++- .../shifts/services/shifts-delete.service.ts | 41 +++++++++++++++++-- .../shifts/services/shifts-update.service.ts | 13 +++++- .../shifts/shift.controller.ts | 23 ++++++++--- .../shifts/shifts.module.ts | 2 + 10 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 src/time-and-attendance/pay-period/dtos/pay-period-event.dto.ts create mode 100644 src/time-and-attendance/pay-period/services/pay-period-event.service.ts diff --git a/src/time-and-attendance/expenses/expenses.module.ts b/src/time-and-attendance/expenses/expenses.module.ts index 99870cd..c0206a7 100644 --- a/src/time-and-attendance/expenses/expenses.module.ts +++ b/src/time-and-attendance/expenses/expenses.module.ts @@ -6,6 +6,7 @@ import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper"; import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper"; import { ExpenseDeleteService } from "src/time-and-attendance/expenses/services/expense-delete.service"; import { ExpenseCreateService } from "src/time-and-attendance/expenses/services/expense-create.service"; +import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service"; @Module({ controllers: [ExpenseController], @@ -16,6 +17,7 @@ import { ExpenseCreateService } from "src/time-and-attendance/expenses/services/ EmailToIdResolver, BankCodesResolver, EmployeeTimesheetResolver, + PayPeriodEventService, ], }) diff --git a/src/time-and-attendance/pay-period/dtos/pay-period-event.dto.ts b/src/time-and-attendance/pay-period/dtos/pay-period-event.dto.ts new file mode 100644 index 0000000..4bf21a0 --- /dev/null +++ b/src/time-and-attendance/pay-period/dtos/pay-period-event.dto.ts @@ -0,0 +1,5 @@ +export class PayPeriodEvent { + employee_email: string; + event_type: 'expense' | 'shift'; + action: 'create' | 'update' | 'delete'; +} \ No newline at end of file diff --git a/src/time-and-attendance/pay-period/pay-periods.controller.ts b/src/time-and-attendance/pay-period/pay-periods.controller.ts index 41f7ddf..b8b7dab 100644 --- a/src/time-and-attendance/pay-period/pay-periods.controller.ts +++ b/src/time-and-attendance/pay-period/pay-periods.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Param, ParseIntPipe, Patch } from "@nestjs/common"; +import { Body, Controller, Get, Param, MessageEvent, ParseIntPipe, Patch, Sse } from "@nestjs/common"; import { PayPeriodOverviewDto } from "./dtos/overview-pay-period.dto"; import { PayPeriodsQueryService } from "./services/pay-periods-query.service"; import { PayPeriodsCommandService } from "./services/pay-periods-command.service"; @@ -6,6 +6,8 @@ import { Result } from "src/common/errors/result-error.factory"; import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators"; import { Modules as ModulesEnum } from ".prisma/client"; import { GetOverviewService } from "src/time-and-attendance/pay-period/services/pay-periods-build-overview.service"; +import { map, Observable } from "rxjs"; +import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service"; @Controller('pay-periods') export class PayPeriodsController { @@ -14,8 +16,14 @@ export class PayPeriodsController { private readonly queryService: PayPeriodsQueryService, private readonly getOverviewService: GetOverviewService, private readonly commandService: PayPeriodsCommandService, + private readonly payPeriodEventService: PayPeriodEventService, ) { } + @Sse("subscribe") + sse(): Observable { + return this.payPeriodEventService.stream().pipe(map(event => ({ data: event, }))); + } + @Get("date/:date") @ModuleAccessAllowed(ModulesEnum.timesheets) async findByDate(@Param("date") date: string) { @@ -38,9 +46,9 @@ export class PayPeriodsController { @Body('timesheet_ids') timesheet_ids: number[], @Body('is_approved') is_approved: boolean, ): Promise> { - if (!email) return {success: false, error: 'EMAIL_REQUIRED'}; - if (!timesheet_ids || timesheet_ids.length < 1) return {success: false, error: 'TIMESHEET_ID_REQUIRED'}; - if (is_approved === null) return {success: false, error: 'APPROVAL_STATUS_REQUIRED'} + if (!email) return { success: false, error: 'EMAIL_REQUIRED' }; + if (!timesheet_ids || timesheet_ids.length < 1) return { success: false, error: 'TIMESHEET_ID_REQUIRED' }; + if (is_approved === null) return { success: false, error: 'APPROVAL_STATUS_REQUIRED' } return this.commandService.bulkApproveEmployee(email, timesheet_ids, is_approved); } diff --git a/src/time-and-attendance/pay-period/pay-periods.module.ts b/src/time-and-attendance/pay-period/pay-periods.module.ts index 3e375db..eaba3ce 100644 --- a/src/time-and-attendance/pay-period/pay-periods.module.ts +++ b/src/time-and-attendance/pay-period/pay-periods.module.ts @@ -5,6 +5,7 @@ import { PayPeriodsCommandService } from "src/time-and-attendance/pay-period/ser import { TimesheetsModule } from "src/time-and-attendance/timesheets/timesheets.module"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { GetOverviewService } from "src/time-and-attendance/pay-period/services/pay-periods-build-overview.service"; +import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service"; @Module({ imports:[TimesheetsModule], @@ -13,6 +14,7 @@ import { GetOverviewService } from "src/time-and-attendance/pay-period/services/ PayPeriodsQueryService, PayPeriodsCommandService, GetOverviewService, + PayPeriodEventService, EmailToIdResolver, ], }) diff --git a/src/time-and-attendance/pay-period/services/pay-period-event.service.ts b/src/time-and-attendance/pay-period/services/pay-period-event.service.ts new file mode 100644 index 0000000..f895103 --- /dev/null +++ b/src/time-and-attendance/pay-period/services/pay-period-event.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from "@nestjs/common"; +import { Observable, Subject } from "rxjs"; +import { PayPeriodEvent } from "src/time-and-attendance/pay-period/dtos/pay-period-event.dto"; + +@Injectable() +export class PayPeriodEventService { + private readonly pay_period_events$ = new Subject(); + + emit(event: PayPeriodEvent) { + this.pay_period_events$.next(event); + } + + stream(): Observable { + return this.pay_period_events$.asObservable(); + } +} \ No newline at end of file diff --git a/src/time-and-attendance/shifts/services/shifts-create.service.ts b/src/time-and-attendance/shifts/services/shifts-create.service.ts index 9482525..9ae63d7 100644 --- a/src/time-and-attendance/shifts/services/shifts-create.service.ts +++ b/src/time-and-attendance/shifts/services/shifts-create.service.ts @@ -11,6 +11,8 @@ import { VacationService } from "src/time-and-attendance/domains/services/vacati import { BankedHoursService } from "src/time-and-attendance/domains/services/banking-hours.service"; import { SickLeaveService } from "src/time-and-attendance/domains/services/sick-leave.service"; import { paid_time_off_types } from "src/time-and-attendance/paid-time-off/paid-time-off.dto"; +import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service"; +import { Modules } from "@prisma/client"; @Injectable() export class ShiftsCreateService { @@ -21,15 +23,16 @@ export class ShiftsCreateService { private readonly vacationService: VacationService, private readonly bankingService: BankedHoursService, private readonly sickService: SickLeaveService, + private readonly payPeriodEventService: PayPeriodEventService, ) { } //_________________________________________________________________ // CREATE WRAPPER FUNCTION FOR ONE OR MANY INPUT //_________________________________________________________________ - async createOneOrManyShifts(email: string, shifts: ShiftDto[]): Promise> { + async createOneOrManyShifts(email: string, shifts: ShiftDto[], is_from_timesheet: boolean = true): Promise> { try { //verify if array is empty or not - if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'No data received' }; + if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'NO_DATA_RECEIVED' }; //verify if email is valid or not const employee_id = await this.emailResolver.findIdByEmail(email); @@ -57,6 +60,15 @@ export class ShiftsCreateService { //verify if shifts were created and returns an array of errors if needed if (created_shifts.length === 0) return { success: false, error: errors.join(' | ') || 'No shift created' }; + // push to event service to notify timesheet-approval subscribers of change + if (is_from_timesheet) { + this.payPeriodEventService.emit({ + employee_email: email, + event_type: 'shift', + action: 'create' + }) + } + // returns array of created shifts return { success: true, data: true } } catch (error) { @@ -150,6 +162,7 @@ export class ShiftsCreateService { comment: dto.comment ?? '', }, }); + //builds an object to return for display in the frontend const shift: ShiftDto = { id: created_shift.id, diff --git a/src/time-and-attendance/shifts/services/shifts-delete.service.ts b/src/time-and-attendance/shifts/services/shifts-delete.service.ts index a0c161b..cdf8878 100644 --- a/src/time-and-attendance/shifts/services/shifts-delete.service.ts +++ b/src/time-and-attendance/shifts/services/shifts-delete.service.ts @@ -1,14 +1,18 @@ import { Injectable } from "@nestjs/common"; import { Result } from "src/common/errors/result-error.factory"; +import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { computeHours } from "src/common/utils/date-utils"; import { PrismaService } from "src/prisma/prisma.service"; import { PaidTimeOFfBankHoursService } from "src/time-and-attendance/paid-time-off/paid-time-off.service"; +import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service"; @Injectable() export class ShiftsDeleteService { constructor( private readonly prisma: PrismaService, private readonly paidTimeOffService: PaidTimeOFfBankHoursService, + private readonly emailResolver: EmailToIdResolver, + private readonly payPeriodEventService: PayPeriodEventService, ) { } //_________________________________________________________________ // DELETE @@ -16,8 +20,29 @@ export class ShiftsDeleteService { //finds shifts using shit_ids //ajust paid-time-off banks //blocs deletion if approved - async deleteShift(shift_id: number): Promise> { + async deleteShift(shift_id: number, email: string, is_from_timesheet: boolean = true): Promise> { try { + + //verify if email is valid or not + const employee_id = await this.emailResolver.findIdByEmail(email); + if (!employee_id.success) return { success: false, error: employee_id.error }; + + // check if shift actually belongs to employee + const shift = await this.prisma.shifts.findUnique({ + where: { id: shift_id }, + select: { + timesheet: { + select: { + employee_id: true, + } + } + } + }); + + if (!shift || shift.timesheet.employee_id !== employee_id.data) + return { success: false, error: 'SHIFT_NOT_FOUND'} + + // return deletion result return await this.prisma.$transaction(async (tx) => { const shift = await tx.shifts.findUnique({ where: { id: shift_id }, @@ -31,9 +56,9 @@ export class ShiftsDeleteService { bank_code: { select: { type: true } }, }, }); - + if (!shift) return { success: false, error: `SHIFT_NOT_FOUND` }; - if (shift.is_approved) return { success: false, error: 'APPROUVED_SHIFT' }; + if (shift.is_approved) return { success: false, error: 'APPROVED_SHIFT' }; //call to ajust paid_time_off hour banks await this.paidTimeOffService.updatePaidTimeoffBankHoursWhenShiftDelete( @@ -43,6 +68,16 @@ export class ShiftsDeleteService { shift.timesheet.employee_id ); await tx.shifts.delete({ where: { id: shift_id } }); + + // push to event service to notify timesheet-approval subscribers of change + if (is_from_timesheet) { + this.payPeriodEventService.emit({ + employee_email: email, + event_type: 'shift', + action: 'delete' + }) + } + return { success: true, data: shift.id }; }); } catch (error) { diff --git a/src/time-and-attendance/shifts/services/shifts-update.service.ts b/src/time-and-attendance/shifts/services/shifts-update.service.ts index 2c9f0fc..75c1b09 100644 --- a/src/time-and-attendance/shifts/services/shifts-update.service.ts +++ b/src/time-and-attendance/shifts/services/shifts-update.service.ts @@ -12,6 +12,7 @@ import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { PaidTimeOFfBankHoursService } from "src/time-and-attendance/paid-time-off/paid-time-off.service"; import { paid_time_off_types } from "src/time-and-attendance/paid-time-off/paid-time-off.dto"; +import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service"; @Injectable() export class ShiftsUpdateService { @@ -21,9 +22,10 @@ export class ShiftsUpdateService { private readonly timesheetResolver: EmployeeTimesheetResolver, private readonly emailResolver: EmailToIdResolver, private readonly paidTimeOffService: PaidTimeOFfBankHoursService, + private readonly payPeriodEventService: PayPeriodEventService, ) { } - async updateOneOrManyShifts(shifts: ShiftDto[], email: string): Promise> { + async updateOneOrManyShifts(shifts: ShiftDto[], email: string, is_from_timesheet: boolean = true): Promise> { try { //verify if array is empty or not if (!Array.isArray(shifts) || shifts.length === 0) return { success: false, error: 'No data received' }; @@ -54,6 +56,15 @@ export class ShiftsUpdateService { //verify if shifts were updated and returns an array of errors if needed if (updated_shifts.length === 0) return { success: false, error: errors.join(' | ') || 'No shift updated' }; + // push to event service to notify timesheet-approval subscribers of change + if (is_from_timesheet) { + this.payPeriodEventService.emit({ + employee_email: email, + event_type: 'shift', + action: 'create' + }) + } + // returns array of updated shifts return { success: true, data: true } } catch (error) { diff --git a/src/time-and-attendance/shifts/shift.controller.ts b/src/time-and-attendance/shifts/shift.controller.ts index abc4c0b..dc0ff25 100644 --- a/src/time-and-attendance/shifts/shift.controller.ts +++ b/src/time-and-attendance/shifts/shift.controller.ts @@ -26,8 +26,8 @@ export class ShiftController { @Post('create/:email') @ModuleAccessAllowed(ModulesEnum.timesheets_approval) - createBatchByTimesheetsApproval(@Param('email') email:string, @Body() dtos: ShiftDto[]): Promise> { - return this.create_service.createOneOrManyShifts(email,dtos); + createBatchByTimesheetsApproval(@Param('email') email: string, @Body() dtos: ShiftDto[]): Promise> { + return this.create_service.createOneOrManyShifts(email, dtos, false); } @Patch('update') @@ -36,10 +36,21 @@ export class ShiftController { return this.update_service.updateOneOrManyShifts(dtos, email); } - @Delete(':shift_id') - @ModuleAccessAllowed(ModulesEnum.timesheets) - remove(@Param('shift_id') shift_id: number): Promise> { - return this.delete_service.deleteShift(shift_id); + @Patch('update/:email') + @ModuleAccessAllowed(ModulesEnum.timesheets_approval) + updateBatchByTimesheetApproval(@Param('email') email: string, @Body() dtos: ShiftDto[]): Promise> { + return this.update_service.updateOneOrManyShifts(dtos, email, false); } + @Delete(':shift_id') + @ModuleAccessAllowed(ModulesEnum.timesheets) + remove(@Access('email') email: string, @Param('shift_id') shift_id: number): Promise> { + return this.delete_service.deleteShift(shift_id, email); + } + + @Delete(':shift_id/:email') + @ModuleAccessAllowed(ModulesEnum.timesheets) + removeByTimesheetApproval(@Param('shift_id') shift_id: number, @Param('email') email: string): Promise> { + return this.delete_service.deleteShift(shift_id, email, false); + } } diff --git a/src/time-and-attendance/shifts/shifts.module.ts b/src/time-and-attendance/shifts/shifts.module.ts index b6c090e..5d979e0 100644 --- a/src/time-and-attendance/shifts/shifts.module.ts +++ b/src/time-and-attendance/shifts/shifts.module.ts @@ -9,6 +9,7 @@ import { VacationService } from 'src/time-and-attendance/domains/services/vacati import { BankedHoursService } from 'src/time-and-attendance/domains/services/banking-hours.service'; import { PaidTimeOffModule } from 'src/time-and-attendance/paid-time-off/paid-time-off.module'; import { PaidTimeOFfBankHoursService } from 'src/time-and-attendance/paid-time-off/paid-time-off.service'; +import { PayPeriodEventService } from 'src/time-and-attendance/pay-period/services/pay-period-event.service'; @Module({ imports: [PaidTimeOffModule], @@ -20,6 +21,7 @@ import { PaidTimeOFfBankHoursService } from 'src/time-and-attendance/paid-time-o VacationService, BankedHoursService, PaidTimeOFfBankHoursService, + PayPeriodEventService, ], exports: [ ShiftsCreateService, From 68883b84e7f53e6669477f4b8cef810a4f37a3ae Mon Sep 17 00:00:00 2001 From: Nic D Date: Wed, 21 Jan 2026 10:21:58 -0500 Subject: [PATCH 2/2] feat(pay-period): add SSE for timesheet-approval when employees update timesheet and a user is on timesheet-approval --- .../expenses/expense.controller.ts | 7 +++- .../services/expense-create.service.ts | 11 +++++- .../services/expense-delete.service.ts | 37 ++++++++++++++++++- .../services/expense-update.service.ts | 13 +++++++ .../pay-period/dtos/pay-period-event.dto.ts | 2 +- .../schedule-presets.module.ts | 2 + .../schedule-presets-apply.service.ts | 10 +++++ .../shifts/services/shifts-update.service.ts | 2 +- .../time-and-attendance.module.ts | 2 + 9 files changed, 79 insertions(+), 7 deletions(-) diff --git a/src/time-and-attendance/expenses/expense.controller.ts b/src/time-and-attendance/expenses/expense.controller.ts index 737228e..d910ad4 100644 --- a/src/time-and-attendance/expenses/expense.controller.ts +++ b/src/time-and-attendance/expenses/expense.controller.ts @@ -35,8 +35,11 @@ export class ExpenseController { @Delete('delete/:expense_id') @ModuleAccessAllowed(ModulesEnum.timesheets) - remove(@Param('expense_id') expense_id: number): Promise> { - return this.deleteService.deleteExpense(expense_id); + remove( + @Param('expense_id') expense_id: number, + @Access('email') email: string, + ): Promise> { + return this.deleteService.deleteExpense(expense_id, email); } } diff --git a/src/time-and-attendance/expenses/services/expense-create.service.ts b/src/time-and-attendance/expenses/services/expense-create.service.ts index 6222c83..298c58c 100644 --- a/src/time-and-attendance/expenses/services/expense-create.service.ts +++ b/src/time-and-attendance/expenses/services/expense-create.service.ts @@ -6,6 +6,7 @@ import { toStringFromDate, weekStartSunday } from "src/common/utils/date-utils"; import { PrismaService } from "src/prisma/prisma.service"; import { ExpenseDto } from "src/time-and-attendance/expenses/expense-create.dto"; import { normalizeAndParseExpenseDto } from "src/time-and-attendance/expenses/expense.utils"; +import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service"; import { expense_select } from "src/time-and-attendance/utils/selects.utils"; @Injectable() @@ -14,6 +15,7 @@ export class ExpenseCreateService { private readonly prisma: PrismaService, private readonly emailResolver: EmailToIdResolver, private readonly typeResolver: BankCodesResolver, + private readonly payPeriodEventService: PayPeriodEventService, ) { } //_________________________________________________________________ @@ -64,8 +66,15 @@ export class ExpenseCreateService { attachment: expense.attachment ?? undefined, supervisor_comment: expense.supervisor_comment ?? undefined, }; - return { success: true, data: created }; + // notify timesheet approval observers of changes + this.payPeriodEventService.emit({ + employee_email: email, + event_type: 'expense', + action: 'create', + }); + + return { success: true, data: created }; } catch (error) { return { success: false, error: 'INVALID_EXPENSE' }; } diff --git a/src/time-and-attendance/expenses/services/expense-delete.service.ts b/src/time-and-attendance/expenses/services/expense-delete.service.ts index 9ad7dd8..01c3bfc 100644 --- a/src/time-and-attendance/expenses/services/expense-delete.service.ts +++ b/src/time-and-attendance/expenses/services/expense-delete.service.ts @@ -1,15 +1,40 @@ import { Injectable } from "@nestjs/common"; import { Result } from "src/common/errors/result-error.factory"; +import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { PrismaService } from "src/prisma/prisma.service"; +import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service"; @Injectable() export class ExpenseDeleteService { - constructor(private readonly prisma: PrismaService) { } + constructor( + private readonly prisma: PrismaService, + private readonly payPeriodEventService: PayPeriodEventService, + private readonly emailResolver: EmailToIdResolver, + ){} //_________________________________________________________________ // DELETE //_________________________________________________________________ - async deleteExpense(expense_id: number): Promise> { + async deleteExpense(expense_id: number, email: string): Promise> { + // get employee id of employee who made delete request + const employee = await this.emailResolver.findIdByEmail(email); + + if (!employee.success) return employee; + + // confirm ownership of expense to employee who made request + const expense = await this.prisma.expenses.findUnique({ + where: { id: expense_id}, + select: { + timesheet: { + select: { + employee_id: true, + } + } + } + }); + + if (!expense || expense.timesheet.employee_id !== employee.data) return { success: false, error: 'EXPENSE_NOT_FOUND'}; + try { await this.prisma.$transaction(async (tx) => { const expense = await tx.expenses.findUnique({ @@ -21,6 +46,14 @@ export class ExpenseDeleteService { await tx.expenses.delete({ where: { id: expense.id } }); return { success: true, data: expense.id }; }); + + // notify timesheet-approval observers of changes + this.payPeriodEventService.emit({ + employee_email: email, + event_type: 'expense', + action: 'delete', + }); + return { success: true, data: expense_id }; } catch (error) { return { success: false, error: `EXPENSE_NOT_FOUND` }; diff --git a/src/time-and-attendance/expenses/services/expense-update.service.ts b/src/time-and-attendance/expenses/services/expense-update.service.ts index 3f2e1e5..a5af26a 100644 --- a/src/time-and-attendance/expenses/services/expense-update.service.ts +++ b/src/time-and-attendance/expenses/services/expense-update.service.ts @@ -8,6 +8,7 @@ import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper"; import { ExpenseDto } from "src/time-and-attendance/expenses/expense-create.dto"; import { Prisma } from "@prisma/client"; import { normalizeAndParseExpenseDto } from "src/time-and-attendance/expenses/expense.utils"; +import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service"; @Injectable() export class ExpenseUpdateService { @@ -15,6 +16,7 @@ export class ExpenseUpdateService { private readonly prisma: PrismaService, private readonly emailResolver: EmailToIdResolver, private readonly typeResolver: BankCodesResolver, + private readonly payPeriodEventService: PayPeriodEventService, ) { } //_________________________________________________________________ // UPDATE @@ -66,6 +68,17 @@ export class ExpenseUpdateService { attachment: expense.attachment ?? undefined, supervisor_comment: expense.supervisor_comment ?? undefined, }; + + // notify timesheet-approval observers of changes, but only if it came + // from timesheet and not timesheet-approval (no employee_email) + if (!employee_email) { + this.payPeriodEventService.emit({ + employee_email: email, + event_type: 'expense', + action: 'update', + }); + } + return { success: true, data: updated }; } catch (error) { return { success: false, error: 'EXPENSE_NOT_FOUND' }; diff --git a/src/time-and-attendance/pay-period/dtos/pay-period-event.dto.ts b/src/time-and-attendance/pay-period/dtos/pay-period-event.dto.ts index 4bf21a0..89faff2 100644 --- a/src/time-and-attendance/pay-period/dtos/pay-period-event.dto.ts +++ b/src/time-and-attendance/pay-period/dtos/pay-period-event.dto.ts @@ -1,5 +1,5 @@ export class PayPeriodEvent { employee_email: string; - event_type: 'expense' | 'shift'; + event_type: 'expense' | 'shift' | 'preset'; action: 'create' | 'update' | 'delete'; } \ No newline at end of file diff --git a/src/time-and-attendance/schedule-presets/schedule-presets.module.ts b/src/time-and-attendance/schedule-presets/schedule-presets.module.ts index 56beb0a..04085d6 100644 --- a/src/time-and-attendance/schedule-presets/schedule-presets.module.ts +++ b/src/time-and-attendance/schedule-presets/schedule-presets.module.ts @@ -13,6 +13,7 @@ import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shi import { VacationService } from "src/time-and-attendance/domains/services/vacation.service"; import { SickLeaveService } from "src/time-and-attendance/domains/services/sick-leave.service"; import { BankedHoursService } from "src/time-and-attendance/domains/services/banking-hours.service"; +import { PayPeriodEventService } from "../pay-period/services/pay-period-event.service"; @@ -30,6 +31,7 @@ import { BankedHoursService } from "src/time-and-attendance/domains/services/ban VacationService, SickLeaveService, BankedHoursService, + PayPeriodEventService, ], exports: [ SchedulePresetsGetService, diff --git a/src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service.ts b/src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service.ts index 668418a..97ad01c 100644 --- a/src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service.ts +++ b/src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service.ts @@ -11,6 +11,7 @@ import { timesheet_select } from "src/time-and-attendance/utils/selects.utils"; import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto"; import { WEEKDAY_MAP } from "src/time-and-attendance/schedule-presets/schedule-presets.dto"; import { $Enums, Prisma, SchedulePresetShifts } from "@prisma/client"; +import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service"; @Injectable() @@ -20,6 +21,7 @@ export class SchedulePresetsApplyService { private readonly emailResolver: EmailToIdResolver, private readonly shiftService: ShiftsCreateService, private readonly typeResolver: BankCodesResolver, + private readonly payPeriodEventService: PayPeriodEventService, ) { } async applyPresetToTimesheet(email: string, timesheet_id: number): Promise> { @@ -119,6 +121,14 @@ export class SchedulePresetsApplyService { await this.shiftService.createShift(employee_id.data, created_shift.data); } + + // notify timesheet-approval observers of changes + this.payPeriodEventService.emit({ + employee_email: email, + event_type: 'preset', + action: 'create', + }) + return { success: true, data: true }; } diff --git a/src/time-and-attendance/shifts/services/shifts-update.service.ts b/src/time-and-attendance/shifts/services/shifts-update.service.ts index 75c1b09..dcaaf3d 100644 --- a/src/time-and-attendance/shifts/services/shifts-update.service.ts +++ b/src/time-and-attendance/shifts/services/shifts-update.service.ts @@ -61,7 +61,7 @@ export class ShiftsUpdateService { this.payPeriodEventService.emit({ employee_email: email, event_type: 'shift', - action: 'create' + action: 'update' }) } diff --git a/src/time-and-attendance/time-and-attendance.module.ts b/src/time-and-attendance/time-and-attendance.module.ts index c5d1420..7b7696c 100644 --- a/src/time-and-attendance/time-and-attendance.module.ts +++ b/src/time-and-attendance/time-and-attendance.module.ts @@ -43,6 +43,7 @@ import { SchedulePresetDeleteService } from "src/time-and-attendance/schedule-pr import { SchedulePresetUpdateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-update.service"; import { SchedulePresetsCreateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-create.service"; import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service"; +import { PayPeriodEventService } from "./pay-period/services/pay-period-event.service"; @Module({ @@ -90,6 +91,7 @@ import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-pr VacationService, BankedHoursService, PaidTimeOFfBankHoursService, + PayPeriodEventService, ], exports: [TimesheetApprovalService], }) export class TimeAndAttendanceModule { }; \ No newline at end of file