const DineroQbFormat = require("../accounting-constants").DineroQbFormat; const queries = require("../../graphql-client/queries"); const Dinero = require("dinero.js"); const builder = require("xmlbuilder2"); const QbXmlUtils = require("./qbxml-utils"); const moment = require("moment-timezone"); const logger = require("../../utils/logger"); const InstanceManager = require("../../utils/instanceMgr").default; exports.default = async (req, res) => { const { bills: billsToQuery } = req.body; const BearerToken = req.BearerToken; const client = req.userGraphQLClient; try { logger.log("qbxml-payable-create", "DEBUG", req.user.email, req.body.billsToQuery); const result = await client .setHeaders({ Authorization: BearerToken }) .request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, { bills: billsToQuery }); const { bills, bodyshops } = result; const bodyshop = bodyshops[0]; const QbXmlToExecute = []; bills.map((i) => { QbXmlToExecute.push({ id: i.id, okStatusCodes: ["0"], qbxml: generateBill(i, bodyshop) }); }); //For each invoice. res.status(200).json(QbXmlToExecute); } catch (error) { logger.log("qbxml-payable-error", "ERROR", req?.user?.email, null, { billsToQuery: req?.body?.billsToQuery, error: error?.message, stack: error?.stack }); res.status(400).send(JSON.stringify(error)); } }; const generateBill = (bill, bodyshop) => { let lines; if (bodyshop.accountingconfig.accumulatePayableLines === true) { lines = Object.values( bill.billlines.reduce((acc, il) => { const { cost_center, actual_cost, quantity = 1 } = il; if (!acc[cost_center]) { acc[cost_center] = { ...il, actual_cost: 0, quantity: 1 }; } acc[cost_center].actual_cost += Math.round(actual_cost * quantity * 100); return acc; }, {}) ).map((il) => { il.actual_cost /= 100; return generateBillLine(il, bodyshop.md_responsibility_centers, bill.job.class); }); } else { lines = bill.billlines.map((il) => generateBillLine(il, bodyshop.md_responsibility_centers, bill.job.class)); } const billQbxmlObj = { QBXML: { QBXMLMsgsRq: { "@onError": "continueOnError", [`${bill.is_credit_memo ? "VendorCreditAddRq" : "BillAddRq"}`]: { [`${bill.is_credit_memo ? "VendorCreditAdd" : "BillAdd"}`]: { VendorRef: { FullName: bill.vendor.name }, TxnDate: moment(bill.date) //.tz(bill.job.bodyshop.timezone) .format("YYYY-MM-DD"), ...(!bill.is_credit_memo && bill.vendor.due_date && { DueDate: moment(bill.date) // .tz(bill.job.bodyshop.timezone) .add(bill.vendor.due_date, "days") .format("YYYY-MM-DD") }), RefNumber: bill.invoice_number, Memo: `RO ${bill.job.ro_number || ""}`, ExpenseLineAdd: lines } } } } }; var billQbxml_partial = builder .create(billQbxmlObj, { version: "1.30", encoding: "UTF-8", headless: true }) .end({ pretty: true }); const billQbxml_Full = QbXmlUtils.addQbxmlHeader(billQbxml_partial); return billQbxml_Full; }; const generateBillLine = (billLine, responsibilityCenters, jobClass) => { return { AccountRef: { FullName: responsibilityCenters.costs.find((c) => c.name === billLine.cost_center).accountname }, Amount: Dinero({ amount: Math.round(billLine.actual_cost * 100) }) .multiply(billLine.quantity || 1) .toFormat(DineroQbFormat), ...(jobClass ? { ClassRef: { FullName: jobClass } } : {}), ...InstanceManager({ imex: { SalesTaxCodeRef: { FullName: findTaxCode(billLine, responsibilityCenters.sales_tax_codes) } } }) }; }; const findTaxCode = (billLine, taxcode) => { const { applicable_taxes: { local, state, federal } } = billLine.applicable_taxes === null ? { ...billLine, applicable_taxes: { local: false, state: false, federal: false } } : billLine; const t = taxcode.filter((t) => !!t.local === !!local && !!t.state === !!state && !!t.federal === !!federal); if (t.length === 1) { return t[0].code; } else if (t.length > 1) { return "Multiple Tax Codes Match"; } else { return "No Tax Code Matches"; } };