250 lines
9.0 KiB
JavaScript
250 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, "SILLY", `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, "SILLY", `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", //TODO:AIO Resolve this for rome 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: bodyshop.pbs_configuration.appostingaccount === "wip" ? cc.dms_wip_acctnumber : cc.dms_acctnumber,
|
|
ControlNumber: bodyshop.pbs_configuration.apcontrol === "ro" ? bill.job.ro_number : 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, "SILLY", `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;
|
|
}
|