agmission/Development/server/workers/invoice_worker.js

109 lines
3.8 KiB
JavaScript

'use strict';
const cron = require('node-cron'),
debug = require('debug')('agm:invoice_worker'),
env = require('../helpers/env.js'),
isProd = env.PRODUCTION,
dbConn = require('../helpers/db/connect.js')(),
// dbConn = require('../helpers/db/connect-remote.js')(),
models = require('../model'),
{ InvoiceStatus } = require('../helpers/constants'),
utils = require('../helpers/utils.js'),
{ isEqual, cloneDeep } = require('lodash'),
moment = require('moment');
process
.on('uncaughtException', function (err) {
debug(err);
process.exit(1);
})
.on('unhandledRejection', (reason, p) => {
debug(reason, 'Unhandled Rejection at Promise', p);
});
// Checking on invoices and update (i.e.: status to automatically transit into the next status such as Draft to Open) them accordlingly
const processInvoices = {
schedule: isProd ? '01 10 * * *' : `*/15 * * * * *`,
status: 0,
name: "processInvoices"
};
const processInvoicesTask = cron.schedule(processInvoices.schedule, async () => {
// Check and only proceed when is idle and the db connection is connected
if (!dbConn || dbConn.readyState !== 1 || processInvoices.status)
return;
let result;
try {
processInvoices.status = 1;
const currDateUTC = moment.utc().endOf('day'); // Shift today to the end of day for easily checking with other dates counting by day
const bulkUpdateOps = [];
//1. Retreive the eligible invoices, by batch, for processing then loop through them while retreiving which is not a proper way.
const invoices = await models.Invoice.find({
$or: [{ status: InvoiceStatus.Draft, openDate: { $lte: currDateUTC } }, { status: InvoiceStatus.Open, dueDate: { $gte: currDateUTC } }]
}, 'status openDate dueDate clients', { lean: true }).limit(100);
let invUpdateOrg, invUpdate, openDate, dueDate;
for (const invoice of invoices) {
invUpdate = { ...invoice.status };
invUpdateOrg = cloneDeep(invUpdate);
openDate = moment.utc(invoice.openDate);
// Case 1: invoice status is draft and current date >= open date => set status = open
if (invoice.status == InvoiceStatus.Draft && currDateUTC >= openDate) {
invUpdate.status = InvoiceStatus.Open;
}
dueDate = moment.utc(invoice.dueDate);
// dueDate.setDate(dueDate.getDate() + 1); // Old logic - Add a day to compare with end of duedate
if (invoice.status === InvoiceStatus.Open && dueDate >= currDateUTC)
// Case 2: Check Open invoice to transit to Paid or Uncollectible (should bases on a number of days param)
if (invoice.status == InvoiceStatus.Open) {
const clients = invoice.clients;
let isPaid = true;
// Check on all clients's amountDue to decide whether the invoice was fully paid
for (const client of clients) {
if (client.amountDue == undefined || Number(client.amountDue) > 0) {
isPaid = false;
break;
}
}
if (isPaid) {
invUpdate.status = InvoiceStatus.Paid;
}
// TODO: when a parameter to decide the invoice as Uncollectible, check and update here
// else { invoice.status = InvoiceStatus.Uncollectible;}
}
if (!isEqual(invUpdateOrg, invUpdate)) {
const invUpdateDoc = { 'updateOne': { 'filter': { '_id': invoice._id }, 'update': invUpdate } };
bulkUpdateOps.push(invUpdateDoc);
}
}
if (!utils.isEmptyArray(bulkUpdateOps)) {
result = await models.Invoice.bulkWrite(bulkUpdateOps);
}
return result;
} catch (error) {
debug(error);
} finally {
if (result) {
debug('Done. processInvoicesTask:', result);
}
processInvoices.status = 0;
}
},
{
scheduled: true,
timezone: "Etc/UTC",
name: processInvoices.name,
runOnInit: true
});