'use strict'; const { AppParamError, AppError } = require('../helpers/app_error'), { Errors, UserTypes, InvoiceStatus } = require('../helpers/constants'), { toFixedNumberString } = require('../helpers/utils'), moment = require('moment'), mongoose = require('mongoose'), mongoUtil = require('../helpers/mongo'), env = require('../helpers/env'), { Invoice, LogPayment } = require('../model'), ObjectId = require('mongodb').ObjectId; async function getLogPayments_get(req, res) { const invoiceId = req?.query?.invoiceId; const puid = req.userInfo?.puid; const invoice = await Invoice.findOne({ _id: invoiceId, byPuid: puid }); if (!invoice) AppParamError.throw(Errors.INVOICE_NOT_FOUND); const userId = req.uid; const option = { invoice: invoice }; const isClientRole = req.ut === UserTypes.CLIENT; if (isClientRole) option['client'] = userId; const logPayments = await LogPayment.find(option).populate({ path: 'client', select: 'name email address phone' }).populate({ path: 'createdBy', select: 'name email address phone' }); res.json(logPayments); } async function insertLogPayment(logPaymentBody, puid, createdBy, session) { const invoiceId = logPaymentBody?.invoiceId; const clientId = logPaymentBody?.clientId; const amount = logPaymentBody?.amount; const invoice = await Invoice.findOne({ 'clients.billTo': clientId, _id: invoiceId, byPuid: puid }); if (!invoice) AppParamError.throw(Errors.INVOICE_NOT_FOUND); // Prevent further payments if the invoice is already in these following states if (invoice.status === InvoiceStatus.Paid) AppError.throw(Errors.INVOICE_ALREADY_PAID); if (invoice.status === InvoiceStatus.Cancelled) AppError.throw(Errors.INVOICE_CANCELLED); if (invoice.status === InvoiceStatus.Draft) AppError.throw(Errors.INVOICE_DRAFT); if (invoice.status === InvoiceStatus.Void) AppError.throw(Errors.INVOICE_VOID); const client = invoice?.clients?.find((client) => client?.billTo == clientId); if (!client) AppParamError.throw(Errors.CLIENT_NOT_FOUND); // Calculate total amountDue const amountPaid = client?.amountPaid ? Number(client?.amountPaid) + Number(amount) : Number(amount); const totalExcludingTax = Number(client?.subTotal) * (1 - Number(client?.discount) / 100); const total = toFixedNumberString(totalExcludingTax + totalExcludingTax * (Number(client?.taxRate) / 100)); const amountDue = Number(total) - amountPaid; if (env.INV_OVERPAID_THRESHOLD && amountPaid > (Number(total) * (1 + (env.INV_OVERPAID_THRESHOLD * 1E-2)))) AppError.create(Errors.CLIENT_OVER_PAID); // Update the specific client's payment details client.amountPaid = toFixedNumberString(amountPaid); client.amountDue = toFixedNumberString(amountDue); // Check if the invoice is fully paid based on all clients let totalAmountDue = 0; for (const client of invoice.clients) { const clientTotalExcludingTax = Number(client?.subTotal) * (1 - Number(client?.discount) / 100); const clientTotal = toFixedNumberString(clientTotalExcludingTax + clientTotalExcludingTax * (Number(client?.taxRate) / 100)); const clientAmountPaid = Number(client?.amountPaid || 0); totalAmountDue += Number(clientTotal) - clientAmountPaid; } if (totalAmountDue <= 0) { invoice.status = InvoiceStatus.Paid; invoice.paidAt = moment.utc().toDate(); // Ensure all clients' `amountDue` is set to 0 invoice.clients.forEach((client) => { client.amountDue = toFixedNumberString(0); }); } const createdLogPayment = new LogPayment({ ...logPaymentBody, invoice: invoiceId, client: clientId, amount: toFixedNumberString(amount), amountDue: toFixedNumberString(amountDue), createdBy }); const logPayment = await createdLogPayment.save({ session }); await invoice.save({ session }); return logPayment; } async function createLogPayment_post(req, res) { const logPaymentBody = req?.body; const puid = req.userInfo?.puid; if (!puid || !ObjectId.isValid(puid)) AppParamError.throw(Errors.INVALID_PUID); const createdBy = req.uid; if (!createdBy) AppParamError.throw(Errors.INVALID_CREATED_BY_USER_ID); let logPayment; await mongoUtil.runInTransaction(async (session) => { logPayment = await insertLogPayment(logPaymentBody, puid, createdBy, session); }); res.json(logPayment); } async function createLogPayments_post(req, res) { const logPayments = []; const logPaymentsBody = req.body; const puid = req.userInfo?.puid; if (!puid || !ObjectId.isValid(puid)) AppParamError.throw(Errors.INVALID_PUID); const createdBy = req.uid; if (!createdBy) AppParamError.throw(Errors.INVALID_CREATED_BY_USER_ID); const session = await mongoose.startSession({ readPreference: { mode: "primary" } }); try { session && session.startTransaction(); for (const logPayment of logPaymentsBody) { const newLogPayment = await insertLogPayment(logPayment, puid, createdBy, session); logPayments.push(newLogPayment); } session && await mongoUtil.commitWithRetry(session); } catch (error) { session && session.abortTransaction(); throw error; } finally { session && session.endSession(); } res.json(logPayments); } module.exports = { getLogPayments_get, createLogPayment_post, createLogPayments_post, };