const path = require("path"); const DineroQbFormat = require("../accounting-constants").DineroQbFormat; const queries = require("../../graphql-client/queries"); const Dinero = require("dinero.js"); const moment = require("moment-timezone"); const builder = require("xmlbuilder2"); const QbXmlUtils = require("./qbxml-utils"); const CreateInvoiceLines = require("../qb-receivables-lines").default; 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"}`) }); Dinero.globalRoundingMode = "HALF_EVEN"; const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils; exports.default = async (req, res) => { const { jobIds } = req.body; const BearerToken = req.BearerToken; const client = req.userGraphQLClient; try { logger.log("qbxml-receivables-create", "DEBUG", req?.user?.email, null, { jobIds: req?.body?.jobIds }); const result = await client .setHeaders({ Authorization: BearerToken }) .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { ids: jobIds }); const { jobs, bodyshops } = result; const QbXmlToExecute = []; const bodyshop = bodyshops[0]; jobs.map((jobs_by_pk) => { //Is this a two tier, or 3 tier setup? const isThreeTier = bodyshop.accountingconfig.tiers === 3; const twoTierPref = bodyshop.accountingconfig.twotierpref; //This is the Insurance Company tier IF 3 tier is selected. if (isThreeTier) { QbXmlToExecute.push({ id: jobs_by_pk.id, okStatusCodes: ["0", "3100"], qbxml: generateSourceCustomerQbxml(jobs_by_pk, bodyshop) // Create the source customer. }); } //If 3 tier, this should be the customer. //If 2 tier, this should be based on the pref. QbXmlToExecute.push({ id: jobs_by_pk.id, okStatusCodes: ["0", "3100"], qbxml: generateJobQbxml(jobs_by_pk, bodyshop, isThreeTier, 2, twoTierPref) }); //This is always going to be the job. QbXmlToExecute.push({ id: jobs_by_pk.id, okStatusCodes: ["0", "3100"], qbxml: generateJobQbxml(jobs_by_pk, bodyshop, isThreeTier, 3, twoTierPref) }); if (!req.body.custDataOnly) { //Generate the actual invoice. QbXmlToExecute.push({ id: jobs_by_pk.id, okStatusCodes: ["0"], qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop, isThreeTier, twoTierPref) }); } }); res.status(200).json(QbXmlToExecute); } catch (error) { logger.log("qbxml-receivables-error", "error", req?.user?.email, null, { jobIds: req.body?.jobIds, error: error.message, stack: error.stack }); 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.trim() // 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.ownr_zip, // }, } } } } }; 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; }; exports.generateSourceCustomerQbxml = generateSourceCustomerQbxml; const generateJobQbxml = (jobs_by_pk, bodyshop, isThreeTier, tierLevel, twoTierPref) => { let Name; let ParentRefName; if (tierLevel === 2) { Name = generateOwnerTier(jobs_by_pk, isThreeTier, twoTierPref); ParentRefName = isThreeTier ? generateSourceTier(jobs_by_pk) : null; } else if (tierLevel === 3) { Name = generateJobTier(jobs_by_pk); ParentRefName = isThreeTier ? `${generateSourceTier(jobs_by_pk)}:${generateOwnerTier(jobs_by_pk)}` : generateOwnerTier(jobs_by_pk, isThreeTier, twoTierPref); } const jobQbxmlObj = { QBXML: { QBXMLMsgsRq: { "@onError": "continueOnError", CustomerAddRq: { CustomerAdd: { Name: Name, ParentRef: ParentRefName ? { FullName: ParentRefName } : null, ...(tierLevel === 3 ? { 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.ownr_zip }, ShipAddress: { 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.ownr_zip }, Email: jobs_by_pk.ownr_ea } : {}) } } } } }; var jobQbxml_partial = builder .create(jobQbxmlObj, { version: "1.30", encoding: "UTF-8", headless: true }) .end({ pretty: true }); const jobQbxml_Full = QbXmlUtils.addQbxmlHeader(jobQbxml_partial); return jobQbxml_Full; }; exports.generateJobQbxml = generateJobQbxml; const generateInvoiceQbxml = (jobs_by_pk, bodyshop, isThreeTier, twoTierPref) => { //Build the Invoice XML file. const InvoiceLineAdd = CreateInvoiceLines({ bodyshop, jobs_by_pk }); 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, isThreeTier, twoTierPref)}:${generateJobTier(jobs_by_pk)}` ).trim() }, ...(jobs_by_pk.class ? { ClassRef: { FullName: jobs_by_pk.class } } : {}), ARAccountRef: { FullName: bodyshop.md_responsibility_centers.ar.accountname }, TxnDate: moment(jobs_by_pk.date_invoiced).tz(bodyshop.timezone).format("YYYY-MM-DD"), RefNumber: jobs_by_pk.ro_number, BillAddress: { Addr1: jobs_by_pk.ownr_co_nm ? jobs_by_pk.ownr_co_nm.substring(0, 30).trim() : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`.substring(0, 30).trim()}`, Addr2: jobs_by_pk.ownr_addr1, Addr3: jobs_by_pk.ownr_addr2, City: jobs_by_pk.ownr_city, State: jobs_by_pk.ownr_st, PostalCode: jobs_by_pk.ownr_zip }, ShipAddress: { Addr1: jobs_by_pk.ownr_co_nm ? jobs_by_pk.ownr_co_nm.substring(0, 30).trim() : `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`.substring(0, 30).trim()}`, Addr2: jobs_by_pk.ownr_addr1, Addr3: jobs_by_pk.ownr_addr2, City: jobs_by_pk.ownr_city, State: jobs_by_pk.ownr_st, PostalCode: jobs_by_pk.ownr_zip }, PONumber: jobs_by_pk.clm_no, ...InstanceManager({ rome: { ItemSalesTaxRef: { FullName: bodyshop.md_responsibility_centers.taxes.invoiceexemptcode } } }), IsToBePrinted: bodyshop.accountingconfig.printlater, ...(jobs_by_pk.ownr_ea ? { IsToBeEmailed: bodyshop.accountingconfig.emaillater } : {}), 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", // }, // }; // };