import { weekStartSunday, toStringFromDate, toDateFromString } from "src/common/utils/date-utils"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { expense_select, timesheet_select } from "src/time-and-attendance/utils/selects.utils"; import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service"; import { Injectable } from "@nestjs/common"; import { Result } from "src/common/errors/result-error.factory"; import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper"; 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"; @Injectable() export class ExpenseUpdateService { constructor( private readonly prisma: PrismaPostgresService, private readonly emailResolver: EmailToIdResolver, private readonly typeResolver: BankCodesResolver, private readonly payPeriodEventService: PayPeriodEventService, ) { } async updateExpense( dto: ExpenseDto, requesterEmail: string, targetEmail?: string ): Promise> { try { // check if requester has access to timesheet_approval if (!!targetEmail && requesterEmail !== targetEmail) { const requester = await this.prisma.users.findFirst({ where: { email: requesterEmail, }, select: { user_module_access: true, } }) if (!requester) return { success: false, error: 'INVALID_USER' } if (!requester.user_module_access?.timesheets_approval) return { success: false, error: 'ACCESS_DENIED' } } const accountEmail = targetEmail ?? requesterEmail; const employee_id = await this.emailResolver.findIdByEmail(accountEmail); if (!employee_id.success) return { success: false, error: employee_id.error }; const normed_expense = await normalizeAndParseExpenseDto(dto); if (!normed_expense.success) return { success: false, error: normed_expense.error } const type = await this.typeResolver.findBankCodeIDByType(dto.type); if (!type.success) return { success: false, error: 'INVALID_EXPENSE_TYPE' } const new_timesheet_start_date = weekStartSunday(toDateFromString(dto.date)); const timesheet = await this.prisma.timesheets.findFirst({ where: { start_date: new_timesheet_start_date, employee_id: employee_id.data }, select: timesheet_select, }); if (!timesheet) return { success: false, error: `TIMESHEET_NOT_FOUND` } const data = { ...normed_expense.data, bank_code_id: type.data, is_approved: dto.is_approved, }; if (!data) return { success: false, error: `INVALID_EXPENSE` } const expense = await this.prisma.expenses.update({ where: { id: dto.id, timesheet_id: timesheet.id }, data, select: expense_select, }); if (!expense) return { success: false, error: 'INVALID_EXPENSE' } const updated: ExpenseDto = { ...expense, type: expense.bank_code.type, date: toStringFromDate(expense.date), amount: expense.amount?.toNumber(), mileage: expense.mileage?.toNumber(), supervisor_comment: expense.supervisor_comment ?? undefined, }; // notify timesheet-approval observers of changes, but only if it came // from timesheet and not timesheet-approval (no targetEmail) if (!targetEmail) { this.payPeriodEventService.emit({ employee_email: accountEmail, event_type: 'expense', action: 'update', date: updated.date, }); } return { success: true, data: updated }; } catch (error) { console.error(error); return { success: false, error: 'EXPENSE_NOT_FOUND' }; } } }