Amounts less then 0 (ie all credits) would not get pushed to transactionObject. Expand to be both sides of zero to allow for credits within transactionObject
286 lines
9.0 KiB
JavaScript
286 lines
9.0 KiB
JavaScript
const path = require("path");
|
|
require("dotenv").config({
|
|
path: path.resolve(
|
|
process.cwd(),
|
|
`.env.${process.env.NODE_ENV || "development"}`
|
|
),
|
|
});
|
|
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
|
|
const queries = require("../../graphql-client/queries");
|
|
const CdkBase = require("../../web-sockets/web-socket");
|
|
const moment = require("moment");
|
|
const Dinero = require("dinero.js");
|
|
const AxiosLib = require("axios").default;
|
|
const axios = AxiosLib.create();
|
|
const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants");
|
|
const { CheckForErrors } = require("./pbs-job-export");
|
|
const uuid = require("uuid").v4;
|
|
axios.interceptors.request.use((x) => {
|
|
const socket = x.socket;
|
|
|
|
const headers = {
|
|
...x.headers.common,
|
|
...x.headers[x.method],
|
|
...x.headers,
|
|
};
|
|
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
|
|
x.url
|
|
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
|
|
console.log(printable);
|
|
|
|
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
|
|
|
|
return x;
|
|
});
|
|
|
|
axios.interceptors.response.use((x) => {
|
|
const socket = x.config.socket;
|
|
|
|
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(
|
|
x.data
|
|
)}`;
|
|
console.log(printable);
|
|
CdkBase.createJsonEvent(
|
|
socket,
|
|
"TRACE",
|
|
`Raw Response: ${printable}`,
|
|
x.data
|
|
);
|
|
|
|
return x;
|
|
});
|
|
|
|
async function PbsCalculateAllocationsAp(socket, billids) {
|
|
try {
|
|
CdkBase.createLogEvent(
|
|
socket,
|
|
"DEBUG",
|
|
`Received request to calculate allocations for ${billids}`
|
|
);
|
|
const { bills, bodyshops } = await QueryBillData(socket, billids);
|
|
const bodyshop = bodyshops[0];
|
|
socket.bodyshop = bodyshop;
|
|
socket.bills = bills;
|
|
|
|
//Each bill will enter it's own top level transaction.
|
|
|
|
const transactionlist = [];
|
|
if (bills.length === 0) {
|
|
CdkBase.createLogEvent(
|
|
socket,
|
|
"ERROR",
|
|
`No bills found for export. Ensure they have not already been exported and try again.`
|
|
);
|
|
}
|
|
bills.forEach((bill) => {
|
|
//Keep the allocations at the bill level.
|
|
|
|
const transactionObject = {
|
|
SerialNumber: socket.bodyshop.pbs_serialnumber,
|
|
billid: bill.id,
|
|
Posting: {
|
|
Reference: bill.invoice_number,
|
|
JournalCode: socket.txEnvelope ? socket.txEnvelope.journal : null,
|
|
TransactionDate: moment().tz(socket.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z",
|
|
//Description: "Bulk AP posting.",
|
|
//AdditionalInfo: "String",
|
|
Source: "ImEX Online",
|
|
Lines: [], //socket.apAllocations,
|
|
},
|
|
};
|
|
|
|
const billHash = {
|
|
[bodyshop.md_responsibility_centers.taxes.federal_itc.name]: {
|
|
Account:
|
|
bodyshop.md_responsibility_centers.taxes.federal_itc.dms_acctnumber,
|
|
ControlNumber: bill.vendor.dmsid,
|
|
Amount: Dinero(),
|
|
// Comment: "String",
|
|
AdditionalInfo: bill.vendor.name,
|
|
InvoiceNumber: bill.invoice_number,
|
|
InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString(),
|
|
},
|
|
[bodyshop.md_responsibility_centers.taxes.state.name]: {
|
|
Account:
|
|
bodyshop.md_responsibility_centers.taxes.state.dms_acctnumber,
|
|
ControlNumber: bill.vendor.dmsid,
|
|
Amount: Dinero(),
|
|
// Comment: "String",
|
|
AdditionalInfo: bill.vendor.name,
|
|
InvoiceNumber: bill.invoice_number,
|
|
InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString(),
|
|
},
|
|
};
|
|
|
|
bill.billlines.forEach((bl) => {
|
|
let lineDinero = Dinero({
|
|
amount: Math.round((bl.actual_cost || 0) * 100),
|
|
})
|
|
.multiply(bl.quantity)
|
|
.multiply(bill.is_credit_memo ? -1 : 1);
|
|
const cc = getCostAccount(bl, bodyshop.md_responsibility_centers);
|
|
|
|
if (!billHash[cc.name]) {
|
|
billHash[cc.name] = {
|
|
Account: cc.dms_acctnumber,
|
|
ControlNumber: bill.vendor.dmsid,
|
|
Amount: Dinero(),
|
|
// Comment: "String",
|
|
AdditionalInfo: bill.vendor.name,
|
|
InvoiceNumber: bill.invoice_number,
|
|
InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString(),
|
|
};
|
|
}
|
|
|
|
//Add the line amount.
|
|
billHash[cc.name] = {
|
|
...billHash[cc.name],
|
|
Amount: billHash[cc.name].Amount.add(lineDinero),
|
|
};
|
|
|
|
//Does the line have taxes?
|
|
if (bl.applicable_taxes.federal) {
|
|
billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name] =
|
|
{
|
|
...billHash[
|
|
bodyshop.md_responsibility_centers.taxes.federal_itc.name
|
|
],
|
|
Amount: billHash[
|
|
bodyshop.md_responsibility_centers.taxes.federal_itc.name
|
|
].Amount.add(lineDinero.percentage(bill.federal_tax_rate || 0)),
|
|
};
|
|
}
|
|
if (bl.applicable_taxes.state) {
|
|
billHash[bodyshop.md_responsibility_centers.taxes.state.name] = {
|
|
...billHash[bodyshop.md_responsibility_centers.taxes.state.name],
|
|
Amount: billHash[
|
|
bodyshop.md_responsibility_centers.taxes.state.name
|
|
].Amount.add(lineDinero.percentage(bill.state_tax_rate || 0)),
|
|
};
|
|
}
|
|
//End tax check
|
|
});
|
|
|
|
let APAmount = Dinero();
|
|
Object.keys(billHash).map((key) => {
|
|
if (billHash[key].Amount.getAmount() > 0 || billHash[key].Amount.getAmount() < 0) {
|
|
transactionObject.Posting.Lines.push({
|
|
...billHash[key],
|
|
Amount: billHash[key].Amount.toFormat("0.00"),
|
|
});
|
|
APAmount = APAmount.add(billHash[key].Amount); //Calculate the total expense for the bill iteratively to create the corresponding credit to AP.
|
|
}
|
|
});
|
|
|
|
transactionObject.Posting.Lines.push({
|
|
Account: bodyshop.md_responsibility_centers.ap.dms_acctnumber,
|
|
ControlNumber: bill.vendor.dmsid,
|
|
Amount: APAmount.multiply(-1).toFormat("0.00"),
|
|
// Comment: "String",
|
|
AdditionalInfo: bill.vendor.name,
|
|
InvoiceNumber: bill.invoice_number,
|
|
InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString(),
|
|
});
|
|
|
|
transactionlist.push(transactionObject);
|
|
});
|
|
|
|
return transactionlist;
|
|
} catch (error) {
|
|
CdkBase.createLogEvent(
|
|
socket,
|
|
"ERROR",
|
|
`Error encountered in PbsCalculateAllocationsAp. ${error}`
|
|
);
|
|
}
|
|
}
|
|
|
|
exports.PbsCalculateAllocationsAp = PbsCalculateAllocationsAp;
|
|
|
|
async function QueryBillData(socket, billids) {
|
|
CdkBase.createLogEvent(
|
|
socket,
|
|
"DEBUG",
|
|
`Querying bill data for id(s) ${billids}`
|
|
);
|
|
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
|
const result = await client
|
|
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
|
.request(queries.GET_PBS_AP_ALLOCATIONS, { billids: billids });
|
|
CdkBase.createLogEvent(
|
|
socket,
|
|
"TRACE",
|
|
`Bill data query result ${JSON.stringify(result, null, 2)}`
|
|
);
|
|
|
|
return result;
|
|
}
|
|
|
|
//@returns the account object.
|
|
function getCostAccount(billline, respcenters) {
|
|
if (!billline.cost_center) return null;
|
|
|
|
const acctName = respcenters.defaults.costs[billline.cost_center];
|
|
|
|
return respcenters.costs.find((c) => c.name === acctName);
|
|
}
|
|
|
|
exports.PbsExportAp = async function (socket, { billids, txEnvelope }) {
|
|
CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`);
|
|
|
|
//apAllocations has the same shap as the lines key for the accounting posting to PBS.
|
|
socket.apAllocations = await PbsCalculateAllocationsAp(socket, billids);
|
|
socket.txEnvelope = txEnvelope;
|
|
for (const allocation of socket.apAllocations) {
|
|
const { billid, ...restAllocation } = allocation;
|
|
const { data: AccountPostingChange } = await axios.post(
|
|
PBS_ENDPOINTS.AccountingPostingChange,
|
|
restAllocation,
|
|
{ auth: PBS_CREDENTIALS, socket }
|
|
);
|
|
|
|
CheckForErrors(socket, AccountPostingChange);
|
|
|
|
if (AccountPostingChange.WasSuccessful) {
|
|
CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
|
|
await MarkApExported(socket, [billid]);
|
|
|
|
socket.emit("ap-export-success", billid);
|
|
} else {
|
|
CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`);
|
|
socket.emit("ap-export-failure", {
|
|
billid,
|
|
error: AccountPostingChange.Message,
|
|
});
|
|
}
|
|
}
|
|
socket.emit("ap-export-complete");
|
|
};
|
|
|
|
async function MarkApExported(socket, billids) {
|
|
CdkBase.createLogEvent(
|
|
socket,
|
|
"DEBUG",
|
|
`Marking bills as exported for id ${billids}`
|
|
);
|
|
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
|
const result = await client
|
|
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
|
.request(queries.MARK_BILLS_EXPORTED, {
|
|
billids,
|
|
bill: {
|
|
exported: true,
|
|
exported_at: new Date(),
|
|
},
|
|
logs: socket.bills.map((bill) => ({
|
|
bodyshopid: socket.bodyshop.id,
|
|
billid: bill.id,
|
|
successful: true,
|
|
useremail: socket.user.email,
|
|
})),
|
|
});
|
|
|
|
return result;
|
|
}
|