From 6a521c0f468c070e234b1e0fc3153adac8dc0147 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 16 Jan 2026 16:37:29 -0800 Subject: [PATCH 1/2] IO-3498 QBO Fix for Returned Data from oauthClient Signed-off-by: Allan Carr --- server/accounting/qbo/qbo-payables.js | 83 ++++++------ server/accounting/qbo/qbo-payments.js | 94 ++++---------- server/accounting/qbo/qbo-receivables.js | 157 ++++++++--------------- 3 files changed, 118 insertions(+), 216 deletions(-) diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index e9391c21d..64a1db7eb 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -87,17 +87,17 @@ exports.default = async (req, res) => { } catch (error) { logger.log("qbo-paybles-create-error", "ERROR", req.user.email, null, { error: - (error?.authResponse && error.authResponse.body) || - error.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") || + error?.authResponse?.body || + error?.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") || error?.message }); ret.push({ billid: bill.id, success: false, errorMessage: - (error && error.authResponse && error.authResponse.body) || - error.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") || - (error && error.message) + error?.authResponse?.body || + error?.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") || + error?.message }); //Add the export log error. @@ -108,9 +108,7 @@ exports.default = async (req, res) => { bodyshopid: bodyshop.id, billid: bill.id, successful: false, - message: JSON.stringify([ - (error && error.authResponse && error.authResponse.body) || (error && error.message) - ]), + message: JSON.stringify([error?.authResponse?.body || error?.message]), useremail: req.user.email } ] @@ -153,7 +151,7 @@ async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) { email: req.user.email }); setNewRefreshToken(req.user.email, result); - return result.json?.QueryResponse?.Vendor[0]; + return result.json?.QueryResponse?.Vendor?.[0]; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { method: "QueryVendorRecord", @@ -190,7 +188,7 @@ async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) { if (result.status >= 400) { throw new Error(JSON.stringify(result.json.Fault)); } - if (result.status === 200) return result?.json; + if (result.status === 200) return result?.json.Vendor; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { method: "InsertVendorRecord", @@ -248,12 +246,7 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) } //QB USA with GST //This was required for the No. 1 Collision Group. - if ( - bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo && - bodyshop.accountingconfig.qbo_usa && - bodyshop.region_config.includes("CA_") - ) { + if (bodyshop.accountingconfig?.qbo && bodyshop.accountingconfig?.qbo_usa && bodyshop.region_config.includes("CA_")) { lines.push({ DetailType: "AccountBasedExpenseLineDetail", @@ -276,7 +269,8 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) }); } - const billQbo = { + let billQbo, VendorCredit; + const billObject = { VendorRef: { value: vendor.Id }, @@ -298,22 +292,30 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) DocNumber: bill.invoice_number, //...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}), ...(!( - bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo && - bodyshop.accountingconfig.qbo_usa && + bodyshop.accountingconfig?.qbo && + bodyshop.accountingconfig?.qbo_usa && bodyshop.region_config.includes("CA_") ) ? { GlobalTaxCalculation: "TaxExcluded" } : {}), - ...(bodyshop.accountingconfig.qbo_departmentid && - bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && { - DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } - }), + ...(bodyshop.accountingconfig.qbo_departmentid?.trim() !== "" && { + DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } + }), PrivateNote: `RO ${bill.job.ro_number || ""}`, Line: lines }; + + if (bill.is_credit_memo) { + VendorCredit = billObject; + } else { + billQbo = billObject; + } + + const logKey = bill.is_credit_memo ? "VendorCredit" : "billQbo"; + const logValue = bill.is_credit_memo ? VendorCredit : billQbo; + logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, { - billQbo + [logKey]: logValue }); try { const result = await oauthClient.makeApiCall({ @@ -322,7 +324,7 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) headers: { "Content-Type": "application/json" }, - body: JSON.stringify(billQbo) + body: JSON.stringify(bill.is_credit_memo ? VendorCredit : billQbo) }); logger.LogIntegrationCall({ platform: "QBO", @@ -450,30 +452,19 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) { email: req.user.email }); const taxCodeMapping = {}; - - taxCodes.json && - taxCodes.json.QueryResponse && - taxCodes.json.QueryResponse.TaxCode && - taxCodes.json.QueryResponse.TaxCode.forEach((t) => { - taxCodeMapping[t.Name] = t.Id; - }); + taxCodes.json?.QueryResponse?.TaxCode?.forEach((t) => { + taxCodeMapping[t.Name] = t.Id; + }); const accountMapping = {}; - - accounts.json && - accounts.json.QueryResponse && - accounts.json.QueryResponse.Account && - accounts.json.QueryResponse.Account.forEach((t) => { - accountMapping[t.FullyQualifiedName] = t.Id; - }); + accounts.json?.QueryResponse?.Account?.forEach((t) => { + accountMapping[t.FullyQualifiedName] = t.Id; + }); const classMapping = {}; - classes.json && - classes.json.QueryResponse && - classes.json.QueryResponse.Class && - classes.json.QueryResponse.Class.forEach((t) => { - classMapping[t.Name] = t.Id; - }); + classes.json?.QueryResponse?.Class?.forEach((t) => { + classMapping[t.Name] = t.Id; + }); return { accounts: accountMapping, diff --git a/server/accounting/qbo/qbo-payments.js b/server/accounting/qbo/qbo-payments.js index be9a28ab1..f4c5d6c52 100644 --- a/server/accounting/qbo/qbo-payments.js +++ b/server/accounting/qbo/qbo-payments.js @@ -145,7 +145,7 @@ exports.default = async (req, res) => { ret.push({ paymentid: payment.id, success: true }); } catch (error) { logger.log("qbo-payment-create-error", "ERROR", req.user.email, null, { - error: (error && error.authResponse && error.authResponse.body) || (error && error.message) + error: error?.authResponse?.body || error?.message }); //Add the export log error. if (elgen) { @@ -155,9 +155,7 @@ exports.default = async (req, res) => { bodyshopid: bodyshop.id, paymentid: payment.id, successful: false, - message: JSON.stringify([ - (error && error.authResponse && error.authResponse.body) || (error && error.message) - ]), + message: JSON.stringify([error?.authResponse?.body || error?.message]), useremail: req.user.email } ] @@ -167,7 +165,7 @@ exports.default = async (req, res) => { ret.push({ paymentid: payment.id, success: false, - errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message) + errorMessage: error?.authResponse?.body || error?.message }); } } @@ -201,9 +199,7 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef) CustomerRef: { value: parentRef.Id }, - TxnDate: moment(payment.date) //.tz(bodyshop.timezone) - .format("YYYY-MM-DD"), - //DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"), + TxnDate: moment(payment.date).format("YYYY-MM-DD"), DocNumber: payment.paymentnum, TotalAmt: Dinero({ amount: Math.round(payment.amount * 100) @@ -211,19 +207,13 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef) PaymentMethodRef: { value: paymentMethods[payment.type] }, - PrivateNote: payment.memo - ? payment.memo.length > 4000 - ? payment.memo.substring(0, 4000).trim() - : payment.memo.trim() - : "", + PrivateNote: payment.memo?.substring(0, 4000)?.trim() ?? "", PaymentRefNum: payment.transactionid, - ...(invoices && invoices.length === 1 && invoices[0] + ...(invoices?.length === 1 && invoices[0] ? { Line: [ { - Amount: Dinero({ - amount: Math.round(payment.amount * 100) - }).toFormat(DineroQbFormat), + Amount: Dinero({ amount: Math.round(payment.amount * 100) }).toFormat(DineroQbFormat), LinkedTxn: [ { TxnId: invoices[0].Id, @@ -260,7 +250,7 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef) if (result.status >= 400) { throw new Error(JSON.stringify(result.json.Fault)); } - if (result.status === 200) return result?.json; + if (result.status === 200) return result?.json.Customer; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, { method: "InsertPayment", @@ -273,11 +263,7 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef) async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditMemo, parentTierRef, bodyshopid) { const invoice = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * From Invoice where DocNumber like '${ro_number}%'` - ), + url: urlBuilder(qbo_realmId, "query", `select * From Invoice where DocNumber like '${ro_number}%'`), method: "POST", headers: { "Content-Type": "application/json" @@ -292,11 +278,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM email: req.user.email }); const paymentMethods = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * From PaymentMethod` - ), + url: urlBuilder(qbo_realmId, "query", `select * From PaymentMethod`), method: "POST", headers: { "Content-Type": "application/json" @@ -322,12 +304,9 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM const paymentMethodMapping = {}; - paymentMethods.json && - paymentMethods.json.QueryResponse && - paymentMethods.json.QueryResponse.PaymentMethod && - paymentMethods.json.QueryResponse.PaymentMethod.forEach((t) => { - paymentMethodMapping[t.Name] = t.Id; - }); + paymentMethods.json?.QueryResponse?.PaymentMethod?.forEach((t) => { + paymentMethodMapping[t.Name] = t.Id; + }); // const accountMapping = {}; @@ -347,11 +326,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM if (isCreditMemo) { const taxCodes = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * From TaxCode` - ), + url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`), method: "POST", headers: { "Content-Type": "application/json" @@ -366,11 +341,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM email: req.user.email }); const items = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * From Item` - ), + url: urlBuilder(qbo_realmId, "query", `select * From Item`), method: "POST", headers: { "Content-Type": "application/json" @@ -388,20 +359,15 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM const itemMapping = {}; - items.json && - items.json.QueryResponse && - items.json.QueryResponse.Item && - items.json.QueryResponse.Item.forEach((t) => { - itemMapping[t.Name] = t.Id; - }); + items.json?.QueryResponse?.Item?.forEach((t) => { + itemMapping[t.Name] = t.Id; + }); const taxCodeMapping = {}; - taxCodes.json && - taxCodes.json.QueryResponse && - taxCodes.json.QueryResponse.TaxCode && - taxCodes.json.QueryResponse.TaxCode.forEach((t) => { - taxCodeMapping[t.Name] = t.Id; - }); + taxCodes.json?.QueryResponse?.TaxCode?.forEach((t) => { + taxCodeMapping[t.Name] = t.Id; + }); + ret = { ...ret, items: itemMapping, @@ -413,12 +379,10 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM ...ret, paymentMethods: paymentMethodMapping, invoices: - invoice.json && - invoice.json.QueryResponse && - invoice.json.QueryResponse.Invoice && + invoice.json?.QueryResponse?.Invoice && (parentTierRef - ? [invoice.json.QueryResponse.Invoice.find((x) => x.CustomerRef.value === parentTierRef.Id)] - : [invoice.json.QueryResponse.Invoice[0]]) + ? [invoice.json?.QueryResponse?.Invoice.find((x) => x.CustomerRef?.value === parentTierRef?.Id)] + : [invoice.json?.QueryResponse?.Invoice?.[0]]) }; } @@ -433,7 +397,7 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe payment.job.shopid ); - if (invoices && invoices.length !== 1) { + if (invoices?.length !== 1) { throw new Error(`More than 1 invoice with DocNumber ${payment.ro_number} found.`); } @@ -441,11 +405,9 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe CustomerRef: { value: parentRef.Id }, - TxnDate: moment(payment.date) - //.tz(bodyshop.timezone) - .format("YYYY-MM-DD"), + TxnDate: moment(payment.date).format("YYYY-MM-DD"), DocNumber: payment.paymentnum, - ...(invoices && invoices[0] ? { InvoiceRef: { value: invoices[0].Id } } : {}), + ...(invoices?.[0] ? { InvoiceRef: { value: invoices[0].Id } } : {}), PaymentRefNum: payment.transactionid, Line: [ { diff --git a/server/accounting/qbo/qbo-receivables.js b/server/accounting/qbo/qbo-receivables.js index 10c662c74..bc97961cc 100644 --- a/server/accounting/qbo/qbo-receivables.js +++ b/server/accounting/qbo/qbo-receivables.js @@ -103,7 +103,7 @@ exports.default = async (req, res) => { if (!req.body.custDataOnly) { await InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, jobTier); - if (job.qb_multiple_payers && job.qb_multiple_payers.length > 0) { + if (job.qb_multiple_payers?.length > 0) { for (const [index, payer] of job.qb_multiple_payers.entries()) { //do the thing. @@ -150,23 +150,21 @@ exports.default = async (req, res) => { // //No error. Mark the job exported & insert export log. if (elgen) { - await client - .setHeaders({ Authorization: BearerToken }) - .request(queries.QBO_MARK_JOB_EXPORTED, { - jobId: job.id, - job: { - status: bodyshop.md_ro_statuses.default_exported || "Exported*", - date_exported: moment().tz(bodyshop.timezone) - }, - logs: [ - { - bodyshopid: bodyshop.id, - jobid: job.id, - successful: true, - useremail: req.user.email - } - ] - }); + await client.setHeaders({ Authorization: BearerToken }).request(queries.QBO_MARK_JOB_EXPORTED, { + jobId: job.id, + job: { + status: bodyshop.md_ro_statuses.default_exported || "Exported*", + date_exported: moment().tz(bodyshop.timezone) + }, + logs: [ + { + bodyshopid: bodyshop.id, + jobid: job.id, + successful: true, + useremail: req.user.email + } + ] + }); } } ret.push({ jobid: job.id, success: true }); @@ -193,9 +191,7 @@ exports.default = async (req, res) => { bodyshopid: bodyshop.id, jobid: job.id, successful: false, - message: JSON.stringify([ - (error && error.authResponse && error.authResponse.body) || (error && error.message) - ]), + message: JSON.stringify([error?.authResponse?.body || error?.message]), useremail: req.user.email } ] @@ -232,18 +228,13 @@ async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) { platform: "QBO", method: "POST", name: "QueryCustomer", - status: result.response?.status, + status: result.status, bodyshopid: job.shopid, jobid: job.id, email: req.user.email }); setNewRefreshToken(req.user.email, result); - return ( - result.json && - result.json.QueryResponse && - result.json.QueryResponse.Customer && - result.json.QueryResponse.Customer[0] - ); + return result.json?.QueryResponse?.Customer?.[0]; } catch (error) { logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { error, @@ -287,13 +278,13 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) { platform: "QBO", method: "POST", name: "InsertCustomer", - status: result.response.status, + status: result.status, bodyshopid: job.shopid, jobid: job.id, email: req.user.email }); setNewRefreshToken(req.user.email, result); - return result && result.json.Customer; + return result.json?.Customer; } catch (error) { logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { error, @@ -322,18 +313,13 @@ async function QueryOwner(oauthClient, qbo_realmId, req, job, parentTierRef) { platform: "QBO", method: "POST", name: "QueryCustomer", - status: result.response?.status, + status: result.status, bodyshopid: job.shopid, jobid: job.id, email: req.user.email }); setNewRefreshToken(req.user.email, result); - return ( - result.json && - result.json.QueryResponse && - result.json.QueryResponse.Customer && - result.json.QueryResponse.Customer.find((x) => x.ParentRef?.value === parentTierRef?.Id) - ); + return result.json?.QueryResponse?.Customer?.find((x) => x.ParentRef?.value === parentTierRef?.Id); } exports.QueryOwner = QueryOwner; @@ -373,13 +359,13 @@ async function InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, pare platform: "QBO", method: "POST", name: "InsertCustomer", - status: result.response?.status, + status: result.status, bodyshopid: job.shopid, jobid: job.id, email: req.user.email }); setNewRefreshToken(req.user.email, result); - return result && result.json.Customer; + return result.json?.Customer; } catch (error) { logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { error, @@ -407,20 +393,14 @@ async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) { platform: "QBO", method: "POST", name: "QueryCustomer", - status: result.response?.status, + status: result.status, bodyshopid: job.shopid, jobid: job.id, email: req.user.email }); setNewRefreshToken(req.user.email, result); - return ( - result.json && - result.json.QueryResponse && - result.json.QueryResponse.Customer && - (parentTierRef - ? result.json.QueryResponse.Customer.find((x) => x.ParentRef.value === parentTierRef.Id) - : result.json.QueryResponse.Customer[0]) - ); + const customers = result.json?.QueryResponse?.Customer; + return customers && (parentTierRef ? customers.find((x) => x.ParentRef.value === parentTierRef.Id) : customers[0]); } exports.QueryJob = QueryJob; @@ -464,7 +444,7 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) { if (result.status >= 400) { throw new Error(JSON.stringify(result.json.Fault)); } - if (result.status === 200) return result?.json; + if (result.status === 200) return result?.json.Customer; } catch (error) { logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { method: "InsertOwner", @@ -479,13 +459,7 @@ exports.InsertJob = InsertJob; async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) { const items = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * - From Item - where active = true maxresults 1000` - ), + url: urlBuilder(qbo_realmId, "query", `select * From Item where active = true maxresults 1000`), method: "POST", headers: { "Content-Type": "application/json" @@ -502,13 +476,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) { }); setNewRefreshToken(req.user.email, items); const taxCodes = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * - From TaxCode - where active = true` - ), + url: urlBuilder(qbo_realmId, "query", `select * From TaxCode where active = true`), method: "POST", headers: { "Content-Type": "application/json" @@ -524,12 +492,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) { email: req.user.email }); const classes = await oauthClient.makeApiCall({ - url: urlBuilder( - qbo_realmId, - "query", - `select * - From Class` - ), + url: urlBuilder(qbo_realmId, "query", `select * From Class`), method: "POST", headers: { "Content-Type": "application/json" @@ -544,31 +507,21 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) { jobid: jobid, email: req.user.email }); - const taxCodeMapping = {}; - taxCodes.json && - taxCodes.json.QueryResponse && - taxCodes.json.QueryResponse.TaxCode && - taxCodes.json.QueryResponse.TaxCode.forEach((t) => { - taxCodeMapping[t.Name] = t.Id; - }); + const taxCodeMapping = {}; + taxCodes.json?.QueryResponse?.TaxCode?.forEach((t) => { + taxCodeMapping[t.Name] = t.Id; + }); const itemMapping = {}; - - items.json && - items.json.QueryResponse && - items.json.QueryResponse.Item && - items.json.QueryResponse.Item.forEach((t) => { - itemMapping[t.Name] = t.Id; - }); + items.json?.QueryResponse?.Item?.forEach((t) => { + itemMapping[t.Name] = t.Id; + }); const classMapping = {}; - classes.json && - classes.json.QueryResponse && - classes.json.QueryResponse.Class && - classes.json.QueryResponse.Class.forEach((t) => { - classMapping[t.Name] = t.Id; - }); + classes.json?.QueryResponse?.Class?.forEach((t) => { + classMapping[t.Name] = t.Id; + }); return { items: itemMapping, @@ -601,12 +554,11 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren } ${job.v_vin || ""} ${job.plate_no || ""} `.trim() }, CustomerRef: { - value: parentTierRef.Id + value: parentTierRef?.Id }, - ...(bodyshop.accountingconfig.qbo_departmentid && - bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && { - DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } - }), + ...(bodyshop.accountingconfig.qbo_departmentid?.trim() !== "" && { + DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } + }), CustomField: [ ...(bodyshop.accountingconfig.ReceivableCustomField1 ? [ @@ -636,9 +588,8 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren ] : []) ], - ...(bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo && - bodyshop.accountingconfig.qbo_usa && { + ...(bodyshop.accountingconfig?.qbo && + bodyshop.accountingconfig?.qbo_usa && { TxnTaxDetail: { TxnTaxCodeRef: { value: taxCodes[bodyshop.md_responsibility_centers.taxes.state.accountitem] @@ -732,10 +683,9 @@ async function InsertInvoiceMultiPayerInvoice( CustomerRef: { value: parentTierRef.Id }, - ...(bodyshop.accountingconfig.qbo_departmentid && - bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && { - DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } - }), + ...(bodyshop.accountingconfig.qbo_departmentid?.trim() !== "" && { + DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } + }), CustomField: [ ...(bodyshop.accountingconfig.ReceivableCustomField1 ? [ @@ -765,9 +715,8 @@ async function InsertInvoiceMultiPayerInvoice( ] : []) ], - ...(bodyshop.accountingconfig && - bodyshop.accountingconfig.qbo && - bodyshop.accountingconfig.qbo_usa && + ...(bodyshop.accountingconfig?.qbo && + bodyshop.accountingconfig?.qbo_usa && bodyshop.region_config.includes("CA_") && { TxnTaxDetail: { TxnTaxCodeRef: { From 334077a39d6e0cfd2cae66f807fd52bf0c99290e Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 16 Jan 2026 17:07:27 -0800 Subject: [PATCH 2/2] IO-3498 Remove setNewRefreshToken and replace forEach with map Signed-off-by: Allan Carr --- server/accounting/qbo/qbo-callback.js | 11 -------- server/accounting/qbo/qbo-payables.js | 28 ++++++++----------- server/accounting/qbo/qbo-payments.js | 27 ++++++------------ server/accounting/qbo/qbo-receivables.js | 35 +++++++++--------------- 4 files changed, 32 insertions(+), 69 deletions(-) diff --git a/server/accounting/qbo/qbo-callback.js b/server/accounting/qbo/qbo-callback.js index c69aaabb0..a22c7f5cb 100644 --- a/server/accounting/qbo/qbo-callback.js +++ b/server/accounting/qbo/qbo-callback.js @@ -57,14 +57,3 @@ exports.refresh = async (oauthClient, req) => { }); } }; - -exports.setNewRefreshToken = async (email, apiResponse) => { - // Deprecated - tokens are now auto-updated in the oauthClient and the token isn't pushed back from QBO API calls anymore - - // logger.log("qbo-token-updated", "DEBUG", email, null, {apiResponse: apiResponse}); - - // await client.request(queries.SET_QBO_AUTH, { - // email, - // qbo_auth: { ...apiResponse.token, createdAt: Date.now() } - // }); -}; diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index 64a1db7eb..2876fd9a3 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -6,7 +6,7 @@ const Dinero = require("dinero.js"); const DineroQbFormat = require("../accounting-constants").DineroQbFormat; const apiGqlClient = require("../../graphql-client/graphql-client").client; const queries = require("../../graphql-client/queries"); -const { refresh: refreshOauthToken, setNewRefreshToken } = require("./qbo-callback"); +const { refresh: refreshOauthToken } = require("./qbo-callback"); const OAuthClient = require("intuit-oauth"); const moment = require("moment-timezone"); const findTaxCode = require("../qb-receivables-lines").findTaxCode; @@ -150,7 +150,7 @@ async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) { bodyshopid: bill.job.shopid, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + return result.json?.QueryResponse?.Vendor?.[0]; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { @@ -184,7 +184,7 @@ async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) { bodyshopid: bill.job.shopid, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + if (result.status >= 400) { throw new Error(JSON.stringify(result.json.Fault)); } @@ -335,7 +335,7 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) bodyshopid: bill.job.shopid, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + if (result.status >= 400) { throw new Error(JSON.stringify(result.json.Fault)); } @@ -420,7 +420,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) { bodyshopid, email: req.user.email }); - setNewRefreshToken(req.user.email, accounts); + const taxCodes = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`), method: "POST", @@ -451,20 +451,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) { bodyshopid, email: req.user.email }); - const taxCodeMapping = {}; - taxCodes.json?.QueryResponse?.TaxCode?.forEach((t) => { - taxCodeMapping[t.Name] = t.Id; - }); - const accountMapping = {}; - accounts.json?.QueryResponse?.Account?.forEach((t) => { - accountMapping[t.FullyQualifiedName] = t.Id; - }); + const taxCodeMapping = Object.fromEntries((taxCodes.json?.QueryResponse?.TaxCode || []).map((t) => [t.Name, t.Id])); - const classMapping = {}; - classes.json?.QueryResponse?.Class?.forEach((t) => { - classMapping[t.Name] = t.Id; - }); + const accountMapping = Object.fromEntries( + (accounts.json?.QueryResponse?.Account || []).map((t) => [t.FullyQualifiedName, t.Id]) + ); + + const classMapping = Object.fromEntries((classes.json?.QueryResponse?.Class || []).map((c) => [c.Name, c.Id])); return { accounts: accountMapping, diff --git a/server/accounting/qbo/qbo-payments.js b/server/accounting/qbo/qbo-payments.js index f4c5d6c52..47cdc9c97 100644 --- a/server/accounting/qbo/qbo-payments.js +++ b/server/accounting/qbo/qbo-payments.js @@ -3,7 +3,7 @@ const Dinero = require("dinero.js"); const apiGqlClient = require("../../graphql-client/graphql-client").client; const queries = require("../../graphql-client/queries"); -const { refresh: refreshOauthToken, setNewRefreshToken } = require("./qbo-callback"); +const { refresh: refreshOauthToken } = require("./qbo-callback"); const OAuthClient = require("intuit-oauth"); const moment = require("moment-timezone"); const { @@ -246,7 +246,7 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef) bodyshopid: payment.job.shopid, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + if (result.status >= 400) { throw new Error(JSON.stringify(result.json.Fault)); } @@ -292,7 +292,6 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM bodyshopid, email: req.user.email }); - setNewRefreshToken(req.user.email, paymentMethods); // const classes = await oauthClient.makeApiCall({ // url: urlBuilder(qbo_realmId, "query", `select * From Class`), @@ -302,11 +301,9 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM // }, // }); - const paymentMethodMapping = {}; - - paymentMethods.json?.QueryResponse?.PaymentMethod?.forEach((t) => { - paymentMethodMapping[t.Name] = t.Id; - }); + const paymentMethodMapping = Object.fromEntries( + (paymentMethods.json?.QueryResponse?.PaymentMethod || []).map((t) => [t.Name, t.Id]) + ); // const accountMapping = {}; @@ -355,18 +352,10 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM bodyshopid, email: req.user.email }); - setNewRefreshToken(req.user.email, items); - const itemMapping = {}; + const taxCodeMapping = Object.fromEntries((taxCodes.json?.QueryResponse?.TaxCode || []).map((t) => [t.Name, t.Id])); - items.json?.QueryResponse?.Item?.forEach((t) => { - itemMapping[t.Name] = t.Id; - }); - const taxCodeMapping = {}; - - taxCodes.json?.QueryResponse?.TaxCode?.forEach((t) => { - taxCodeMapping[t.Name] = t.Id; - }); + const itemMapping = Object.fromEntries((items.json?.QueryResponse?.Item || []).map((item) => [item.Name, item.Id])); ret = { ...ret, @@ -456,7 +445,7 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe bodyshopid: req.user.bodyshopid, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + if (result.status >= 400) { throw new Error(JSON.stringify(result.json.Fault)); } diff --git a/server/accounting/qbo/qbo-receivables.js b/server/accounting/qbo/qbo-receivables.js index bc97961cc..ef64fcdd8 100644 --- a/server/accounting/qbo/qbo-receivables.js +++ b/server/accounting/qbo/qbo-receivables.js @@ -4,7 +4,7 @@ const StandardizeName = require("./qbo").StandardizeName; const logger = require("../../utils/logger"); const apiGqlClient = require("../../graphql-client/graphql-client").client; const queries = require("../../graphql-client/queries"); -const { refresh: refreshOauthToken, setNewRefreshToken } = require("./qbo-callback"); +const { refresh: refreshOauthToken } = require("./qbo-callback"); const OAuthClient = require("intuit-oauth"); const CreateInvoiceLines = require("../qb-receivables-lines").default; const moment = require("moment-timezone"); @@ -233,7 +233,7 @@ async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) { jobid: job.id, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + return result.json?.QueryResponse?.Customer?.[0]; } catch (error) { logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { @@ -283,7 +283,7 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) { jobid: job.id, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + return result.json?.Customer; } catch (error) { logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { @@ -318,7 +318,7 @@ async function QueryOwner(oauthClient, qbo_realmId, req, job, parentTierRef) { jobid: job.id, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + return result.json?.QueryResponse?.Customer?.find((x) => x.ParentRef?.value === parentTierRef?.Id); } @@ -364,7 +364,7 @@ async function InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, pare jobid: job.id, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + return result.json?.Customer; } catch (error) { logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { @@ -398,7 +398,7 @@ async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) { jobid: job.id, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + const customers = result.json?.QueryResponse?.Customer; return customers && (parentTierRef ? customers.find((x) => x.ParentRef.value === parentTierRef.Id) : customers[0]); } @@ -440,7 +440,7 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) { jobid: job.id, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + if (result.status >= 400) { throw new Error(JSON.stringify(result.json.Fault)); } @@ -474,7 +474,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) { jobid: jobid, email: req.user.email }); - setNewRefreshToken(req.user.email, items); + const taxCodes = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From TaxCode where active = true`), method: "POST", @@ -508,20 +508,11 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) { email: req.user.email }); - const taxCodeMapping = {}; - taxCodes.json?.QueryResponse?.TaxCode?.forEach((t) => { - taxCodeMapping[t.Name] = t.Id; - }); + const taxCodeMapping = Object.fromEntries((taxCodes.json?.QueryResponse?.TaxCode || []).map((t) => [t.Name, t.Id])); - const itemMapping = {}; - items.json?.QueryResponse?.Item?.forEach((t) => { - itemMapping[t.Name] = t.Id; - }); + const itemMapping = Object.fromEntries((items.json?.QueryResponse?.Item || []).map((item) => [item.Name, item.Id])); - const classMapping = {}; - classes.json?.QueryResponse?.Class?.forEach((t) => { - classMapping[t.Name] = t.Id; - }); + const classMapping = Object.fromEntries((classes.json?.QueryResponse?.Class || []).map((c) => [c.Name, c.Id])); return { items: itemMapping, @@ -630,7 +621,7 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren jobid: job.id, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + if (result.status >= 400) { throw new Error(JSON.stringify(result.json.Fault)); } @@ -758,7 +749,7 @@ async function InsertInvoiceMultiPayerInvoice( jobid: job.id, email: req.user.email }); - setNewRefreshToken(req.user.email, result); + if (result.status >= 400) { throw new Error(JSON.stringify(result.json.Fault)); }