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"); var builder = require("xmlbuilder2"); const QbXmlUtils = require("./qbxml-utils"); const moment = require("moment-timezone"); const logger = require("../../utils/logger"); require("dotenv").config({ path: path.resolve( process.cwd(), `.env.${process.env.NODE_ENV || "development"}` ), }); exports.default = async (req, res) => { const BearerToken = req.headers.authorization; const { bills: billsToQuery } = req.body; const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { headers: { Authorization: BearerToken, }, }); 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, 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 } } : {}), 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"; } };