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 { getSessionData, getMultipleSessionData, setMultipleSessionData } = require("../../../server"); const uuid = require("uuid").v4; axios.interceptors.request.use( async (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); // Use await properly here for the async operation await CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data); return x; // Return the modified request }, (error) => { return Promise.reject(error); // Proper error handling } ); axios.interceptors.response.use( async (x) => { const socket = x.config.socket; const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`; console.log(printable); // Use await properly here for the async operation await CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data); return x; // Return the modified response }, (error) => { return Promise.reject(error); // Proper error handling } ); async function PbsCalculateAllocationsAp(socket, billids) { try { await CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`); const { bills, bodyshops } = await QueryBillData(socket, billids); const bodyshop = bodyshops[0]; await setMultipleSessionData(socket.id, { bills, bodyshop }); const txEnvelope = await getSessionData(socket.id, "txEnvelope"); //Each bill will enter it's own top level transaction. const transactionlist = []; if (bills.length === 0) { await 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: bodyshop.pbs_serialnumber, billid: bill.id, Posting: { //Description: "Bulk AP posting.", //AdditionalInfo: "String", Reference: bill.invoice_number, JournalCode: txEnvelope?.journal, TransactionDate: moment().tz(bodyshop.timezone).toISOString(), Source: "ImEX Online", // TODO: Resolve this for Rome Online. Lines: [] // Will be populated with allocation data, } }; 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) { 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) { await CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`); } } exports.PbsCalculateAllocationsAp = PbsCalculateAllocationsAp; async function QueryBillData(socket, billids) { await 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 }); await 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 }) { await CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`); const apAllocations = await PbsCalculateAllocationsAp(socket, billids); await setMultipleSessionData(socket.id, { apAllocations, txEnvelope }); for (const allocation of apAllocations) { const { billid, ...restAllocation } = allocation; const { data: AccountPostingChange } = await axios.post(PBS_ENDPOINTS.AccountingPostingChange, restAllocation, { auth: PBS_CREDENTIALS, socket }); CheckForErrors(socket, AccountPostingChange).catch((err) => console.error(`Error running CheckingForErrors in pbs-ap-allocations`) ); if (AccountPostingChange.WasSuccessful) { await CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`); await MarkApExported(socket, [billid]); socket.emit("ap-export-success", billid); } else { await CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`); socket.emit("ap-export-failure", { billid, error: AccountPostingChange.Message }); } } socket.emit("ap-export-complete"); }; async function MarkApExported(socket, billids) { const { bills, bodyshop } = await getMultipleSessionData(socket.id, ["bills", "bodyshop"]); await CdkBase.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); return await client .setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) .request(queries.MARK_BILLS_EXPORTED, { billids, bill: { exported: true, exported_at: new Date() }, logs: bills.map((bill) => ({ bodyshopid: bodyshop.id, billid: bill.id, successful: true, useremail: socket.user.email })) }); }