134 lines
5.2 KiB
JavaScript
134 lines
5.2 KiB
JavaScript
'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,
|
|
};
|