const GraphQLClient = require("graphql-request").GraphQLClient; const path = require("path"); 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; require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); 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) => { 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: bill.billlines.map((il) => generateBillLine(il, bodyshop.md_responsibility_centers, bill.job.class) ) } } } } }; 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"; } };