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("xmlbuilder"); const QbXmlUtils = require("./qbxml-utils"); require("dotenv").config({ path: path.resolve( process.cwd(), `.env.${process.env.NODE_ENV || "development"}` ), }); const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils; exports.default = async (req, res) => { const BearerToken = req.headers.authorization; const { jobId } = req.body; const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { headers: { Authorization: BearerToken, }, }); try { const result = await client .setHeaders({ Authorization: BearerToken }) .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { id: jobId }); const { jobs_by_pk } = result; const { bodyshop } = jobs_by_pk; const QbXmlToExecute = []; //Is this a two tier, or 3 tier setup? const isThreeTier = bodyshop.accountingconfig.tiers === 3; const twoTierPref = bodyshop.accountingconfig.twotierpref; if (isThreeTier) { QbXmlToExecute.push({ id: jobId, okStatusCodes: ["0", "3100"], qbxml: generateSourceCustomerQbxml(jobs_by_pk, bodyshop), // Create the source customer. }); } QbXmlToExecute.push({ id: jobId, okStatusCodes: ["0", "3100"], qbxml: generateJobQbxml( jobs_by_pk, bodyshop, isThreeTier, 2, twoTierPref ), }); QbXmlToExecute.push({ id: jobId, okStatusCodes: ["0", "3100"], qbxml: generateJobQbxml( jobs_by_pk, bodyshop, isThreeTier, 3, twoTierPref ), }); //Generate the actual invoice. QbXmlToExecute.push({ id: jobId, okStatusCodes: ["0"], qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop), }); res.status(200).json(QbXmlToExecute); } catch (error) { console.log("error", error); res.status(400).send(JSON.stringify(error)); } }; const generateSourceCustomerQbxml = (jobs_by_pk, bodyshop) => { const customerQbxmlObj = { QBXML: { QBXMLMsgsRq: { "@onError": "continueOnError", CustomerAddRq: { CustomerAdd: { Name: jobs_by_pk.ins_co_nm, BillAddress: { Addr1: jobs_by_pk.ownr_addr1, Addr2: jobs_by_pk.ownr_addr2, City: jobs_by_pk.ownr_city, State: jobs_by_pk.ownr_st, PostalCode: jobs_by_pk.ownrzip, }, }, }, }, }, }; var customerQbxml_partial = builder .create(customerQbxmlObj, { version: "1.30", encoding: "UTF-8", headless: true, }) .end({ pretty: true }); const customerQbxml_Full = QbXmlUtils.addQbxmlHeader(customerQbxml_partial); return customerQbxml_Full; }; const generateJobQbxml = ( jobs_by_pk, bodyshop, isThreeTier, tierLevel, twoTierPref ) => { let Name; let ParentRefName; if (tierLevel === 2) { Name = generateOwnerTier(jobs_by_pk); ParentRefName = isThreeTier ? generateSourceTier(jobs_by_pk) : null; } else if (tierLevel === 3) { Name = generateJobTier(jobs_by_pk); ParentRefName = isThreeTier ? `${jobs_by_pk.ins_co_nm}:${generateOwnerTier(jobs_by_pk)}` : generateOwnerTier(jobs_by_pk); } const jobQbxmlObj = { QBXML: { QBXMLMsgsRq: { "@onError": "continueOnError", CustomerAddRq: { CustomerAdd: { Name: Name, ParentRef: ParentRefName ? { FullName: ParentRefName, } : null, }, }, }, }, }; var jobQbxml_partial = builder .create(jobQbxmlObj, { version: "1.30", encoding: "UTF-8", headless: true, }) .end({ pretty: true }); const jobQbxml_Full = QbXmlUtils.addQbxmlHeader(jobQbxml_partial); console.log("jobQbxml_Full", jobQbxml_Full); return jobQbxml_Full; }; const generateInvoiceQbxml = (jobs_by_pk, bodyshop) => { //Build the Invoice XML file. const InvoiceLineAdd = []; const invoice_allocation = jobs_by_pk.invoice_allocation; Object.keys(invoice_allocation.partsAllocations).forEach( (partsAllocationKey) => { if ( !!!invoice_allocation.partsAllocations[partsAllocationKey].allocations ) return; invoice_allocation.partsAllocations[ partsAllocationKey ].allocations.forEach((alloc) => { InvoiceLineAdd.push( generateInvoiceLine( jobs_by_pk, alloc, bodyshop.md_responsibility_centers ) ); }); } ); Object.keys(invoice_allocation.labMatAllocations).forEach((AllocationKey) => { if (!!!invoice_allocation.labMatAllocations[AllocationKey].allocations) return; invoice_allocation.labMatAllocations[AllocationKey].allocations.forEach( (alloc) => { InvoiceLineAdd.push( generateInvoiceLine( jobs_by_pk, alloc, bodyshop.md_responsibility_centers ) ); } ); }); //Add tax lines const job_totals = JSON.parse(jobs_by_pk.job_totals); const federal_tax = Dinero(job_totals.totals.federal_tax); const state_tax = Dinero(job_totals.totals.state_tax); const local_tax = Dinero(job_totals.totals.local_tax); if (federal_tax.getAmount() > 0) { InvoiceLineAdd.push({ ItemRef: { FullName: bodyshop.md_responsibility_centers.taxes.federal.accountitem, }, Desc: bodyshop.md_responsibility_centers.taxes.federal.accountdesc, Amount: federal_tax.toFormat(DineroQbFormat), }); } if (state_tax.getAmount() > 0) { InvoiceLineAdd.push({ ItemRef: { FullName: bodyshop.md_responsibility_centers.taxes.state.accountitem, }, Desc: bodyshop.md_responsibility_centers.taxes.state.accountdesc, Amount: state_tax.toFormat(DineroQbFormat), }); } if (local_tax.getAmount() > 0) { InvoiceLineAdd.push({ ItemRef: { FullName: bodyshop.md_responsibility_centers.taxes.local.accountitem, }, Desc: bodyshop.md_responsibility_centers.taxes.local.accountdesc, Amount: local_tax.toFormat(DineroQbFormat), }); } const invoiceQbxmlObj = { QBXML: { QBXMLMsgsRq: { "@onError": "stopOnError", InvoiceAddRq: { InvoiceAdd: { CustomerRef: { FullName: bodyshop.accountingconfig.tiers === 3 ? `${generateSourceTier(jobs_by_pk)}:${generateOwnerTier( jobs_by_pk )}:${generateJobTier(jobs_by_pk)}` : `${generateOwnerTier(jobs_by_pk)}:${generateJobTier( jobs_by_pk )}`, }, TxnDate: new Date(), RefNumber: jobs_by_pk.ro_number, BillAddress: { Addr1: jobs_by_pk.ownr_addr1, Addr2: jobs_by_pk.ownr_addr2, City: jobs_by_pk.ownr_city, State: jobs_by_pk.ownr_st, PostalCode: jobs_by_pk.ownrzip, }, PONumber: jobs_by_pk.clm_no, InvoiceLineAdd: InvoiceLineAdd, }, }, }, }, }; var invoiceQbxml_partial = builder .create(invoiceQbxmlObj, { version: "1.30", encoding: "UTF-8", headless: true, }) .end({ pretty: true }); const invoiceQbxml_Full = QbXmlUtils.addQbxmlHeader(invoiceQbxml_partial); return invoiceQbxml_Full; }; const generateInvoiceLine = (job, allocation, responsibilityCenters) => { const { amount, center } = allocation; const DineroAmount = Dinero(amount); const account = responsibilityCenters.profits.find( (i) => i.name.toLowerCase() === center.toLowerCase() ); if (!!!account) { throw new Error( `A matching account does not exist for the allocation. Center: ${center}` ); } return { ItemRef: { FullName: account.accountitem }, Desc: account.accountdesc, Quantity: 1, //Rate: 100, Amount: DineroAmount.toFormat(DineroQbFormat), SalesTaxCodeRef: { FullName: "E", }, }; };