// import { BaseApprovalService } from "src/common/shared/base-approval.service"; // import { Expenses, Prisma } from "@prisma/client"; // import { PrismaService } from "src/prisma/prisma.service"; // import { UpsertExpenseDto } from "../dtos/upsert-expense.dto"; // import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils"; // import { ExpenseResponse, UpsertAction } from "../types and interfaces/expenses.types.interfaces"; // import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils"; // import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-timesheet.utils"; // import { // BadRequestException, // Injectable, // NotFoundException // } from "@nestjs/common"; // import { // assertAndTrimComment, // computeAmountDecimal, // computeMileageAmount, // mapDbExpenseToDayResponse, // normalizeType, // parseAttachmentId // } from "../utils/expenses.utils"; // @Injectable() // export class ExpensesCommandService extends BaseApprovalService { // constructor( // prisma: PrismaService, // private readonly bankCodesResolver: BankCodesResolver, // private readonly timesheetsResolver: EmployeeTimesheetResolver, // private readonly emailResolver: EmailToIdResolver, // ) { super(prisma); } // //_____________________________________________________________________________________________ // // APPROVAL TX-DELEGATE METHODS // //_____________________________________________________________________________________________ // protected get delegate() { // return this.prisma.expenses; // } // protected delegateFor(transaction: Prisma.TransactionClient){ // return transaction.expenses; // } // async updateApproval(id: number, isApproved: boolean): Promise { // return this.prisma.$transaction((transaction) => // this.updateApprovalWithTransaction(transaction, id, isApproved), // ); // } // //_____________________________________________________________________________________________ // // MASTER CRUD FUNCTION // //_____________________________________________________________________________________________ // readonly upsertExpensesByDate = async (email: string, date: string, dto: UpsertExpenseDto, // ): Promise<{ action:UpsertAction; day: ExpenseResponse[] }> => { // //validates if there is an existing expense, at least 1 old or new // const { old_expense, new_expense } = dto ?? {}; // if(!old_expense && !new_expense) throw new BadRequestException('At least one expense must be provided'); // //validate date format // const date_only = toDateOnly(date); // if(Number.isNaN(date_only.getTime())) throw new BadRequestException('Invalid date format (expected: YYYY-MM-DD)'); // //resolve employee_id by email // const employee_id = await this.emailResolver.findIdByEmail(email); // //make sure a timesheet existes // const timesheet_id = await this.timesheetsResolver.findTimesheetIdByEmail(email, date_only); // if(!timesheet_id) throw new NotFoundException(`no timesheet found for employee #${employee_id}`) // const {id} = timesheet_id; // return this.prisma.$transaction(async (tx) => { // const loadDay = async (): Promise => { // const rows = await tx.expenses.findMany({ // where: { // timesheet_id: id, // date: date_only, // }, // include: { // bank_code: { // select: { // type: true, // }, // }, // }, // orderBy: [{ date: 'asc' }, { id: 'asc' }], // }); // return rows.map((r) => // mapDbExpenseToDayResponse({ // date: r.date, // amount: r.amount ?? 0, // mileage: r.mileage ?? 0, // comment: r.comment, // is_approved: r.is_approved, // bank_code: r.bank_code, // })); // }; // const normalizePayload = async (payload: { // type: string; // amount?: number; // mileage?: number; // comment: string; // attachment?: string | number; // }): Promise<{ // type: string; // bank_code_id: number; // amount: Prisma.Decimal; // mileage: number | null; // comment: string; // attachment: number | null; // }> => { // const type = normalizeType(payload.type); // const comment = assertAndTrimComment(payload.comment); // const attachment = parseAttachmentId(payload.attachment); // const { id: bank_code_id, modifier } = await this.bankCodesResolver.findByType(type); // let amount = computeAmountDecimal(type, payload, modifier); // let mileage: number | null = null; // if (type === 'MILEAGE') { // mileage = Number(payload.mileage ?? 0); // if (!(mileage > 0)) { // throw new BadRequestException('Mileage required and must be > 0 for type MILEAGE'); // } // const amountNumber = computeMileageAmount(mileage, modifier); // amount = new Prisma.Decimal(amountNumber); // } else { // if (!(typeof payload.amount === 'number' && payload.amount >= 0)) { // throw new BadRequestException('Amount required for non-MILEAGE expense'); // } // amount = new Prisma.Decimal(payload.amount); // } // if (attachment !== null) { // const attachment_row = await tx.attachments.findUnique({ // where: { id: attachment }, // select: { status: true }, // }); // if (!attachment_row || attachment_row.status !== 'ACTIVE') { // throw new BadRequestException('Attachment not found or inactive'); // } // } // return { // type, // bank_code_id, // amount, // mileage, // comment, // attachment // }; // }; // const findExactOld = async (norm: { // bank_code_id: number; // amount: Prisma.Decimal; // mileage: number | null; // comment: string; // attachment: number | null; // }) => { // return tx.expenses.findFirst({ // where: { // timesheet_id: id, // date: date_only, // bank_code_id: norm.bank_code_id, // amount: norm.amount, // comment: norm.comment, // attachment: norm.attachment, // ...(norm.mileage !== null ? { mileage: norm.mileage } : { mileage: null }), // }, // select: { id: true }, // }); // }; // let action : UpsertAction; // //_____________________________________________________________________________________________ // // DELETE // //_____________________________________________________________________________________________ // if(old_expense && !new_expense) { // const old_norm = await normalizePayload(old_expense); // const existing = await findExactOld(old_norm); // if(!existing) { // throw new NotFoundException({ // error_code: 'EXPENSE_STALE', // message: 'The expense was modified or deleted by someone else', // }); // } // await tx.expenses.delete({where: { id: existing.id } }); // action = 'delete'; // } // //_____________________________________________________________________________________________ // // CREATE // //_____________________________________________________________________________________________ // else if (!old_expense && new_expense) { // const new_exp = await normalizePayload(new_expense); // await tx.expenses.create({ // data: { // timesheet_id: id, // date: date_only, // bank_code_id: new_exp.bank_code_id, // amount: new_exp.amount, // mileage: new_exp.mileage, // comment: new_exp.comment, // attachment: new_exp.attachment, // is_approved: false, // }, // }); // action = 'create'; // } // //_____________________________________________________________________________________________ // // UPDATE // //_____________________________________________________________________________________________ // else if(old_expense && new_expense) { // const old_norm = await normalizePayload(old_expense); // const existing = await findExactOld(old_norm); // if(!existing) { // throw new NotFoundException({ // error_code: 'EXPENSE_STALE', // message: 'The expense was modified or deleted by someone else', // }); // } // const new_exp = await normalizePayload(new_expense); // await tx.expenses.update({ // where: { id: existing.id }, // data: { // bank_code_id: new_exp.bank_code_id, // amount: new_exp.amount, // mileage: new_exp.mileage, // comment: new_exp.comment, // attachment: new_exp.attachment, // }, // }); // action = 'update'; // } // else { // throw new BadRequestException('Invalid upsert combination'); // } // const day = await loadDay(); // return { action, day }; // }); // } // }