From ac4fcf169496a018ade67327636b75b27301ea7a Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 2 Oct 2025 21:22:35 -0700 Subject: [PATCH] IO-3368 QB Bill Accumulator Signed-off-by: Allan Carr --- .../shop-info/shop-info.general.component.jsx | 10 +++- server/accounting/qbo/qbo-payables.js | 56 ++++++++++++++----- server/accounting/qbxml/qbxml-payables.js | 26 ++++++++- 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/client/src/components/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx index 165e133a2..97df97c80 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -425,7 +425,15 @@ export function ShopInfoGeneral({ form, bodyshop }) { ] : []) ] - : []) + : []), + + + ]} null}> diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index 9f0aacc42..a21e8ebc0 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -205,21 +205,49 @@ async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) { async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) { const { accounts, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req, bill.job.shopid); + let lines; + if (bodyshop.accountingconfig.accumulatePayableLines === true) { + lines = Object.values( + bill.billlines.reduce((acc, il) => { + const { cost_center, actual_cost, quantity = 1 } = il; - const lines = bill.billlines.map((il) => - generateBillLine( - il, - accounts, - bill.job.class, - bodyshop.md_responsibility_centers.sales_tax_codes, - classes, - taxCodes, - bodyshop.md_responsibility_centers.costs, - bodyshop.accountingconfig, - bodyshop.region_config - ) - ); + if (!acc[cost_center]) { + acc[cost_center] = { ...il, actual_cost: 0, quantity: 1 }; + } + acc[cost_center].actual_cost += Math.round(actual_cost * quantity * 100); + + return acc; + }, {}) + ).map((il) => { + il.actual_cost /= 100; + return generateBillLine( + il, + accounts, + bill.job.class, + bodyshop.md_responsibility_centers.sales_tax_codes, + classes, + taxCodes, + bodyshop.md_responsibility_centers.costs, + bodyshop.accountingconfig, + bodyshop.region_config + ); + }); + } else { + lines = bill.billlines.map((il) => + generateBillLine( + il, + accounts, + bill.job.class, + bodyshop.md_responsibility_centers.sales_tax_codes, + classes, + taxCodes, + bodyshop.md_responsibility_centers.costs, + bodyshop.accountingconfig, + bodyshop.region_config + ) + ); + } //QB USA with GST //This was required for the No. 1 Collision Group. if ( @@ -241,7 +269,7 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) Amount: Dinero({ amount: Math.round( bill.billlines.reduce((acc, val) => { - return acc + (val.applicable_taxes?.federal ? (val.actual_cost * val.quantity ?? 0) : 0); + return acc + (val.applicable_taxes?.federal ? val.actual_cost * val.quantity || 0 : 0); }, 0) * 100 ) }) diff --git a/server/accounting/qbxml/qbxml-payables.js b/server/accounting/qbxml/qbxml-payables.js index 9e658ecea..2b5efaf18 100644 --- a/server/accounting/qbxml/qbxml-payables.js +++ b/server/accounting/qbxml/qbxml-payables.js @@ -46,6 +46,28 @@ exports.default = async (req, res) => { }; const generateBill = (bill, bodyshop) => { + let lines; + if (bodyshop.accountingconfig.accumulatePayableLines === true) { + lines = Object.values( + bill.billlines.reduce((acc, il) => { + const { cost_center, actual_cost, quantity = 1 } = il; + + if (!acc[cost_center]) { + acc[cost_center] = { ...il, actual_cost: 0, quantity: 1 }; + } + + acc[cost_center].actual_cost += Math.round(actual_cost * quantity * 100); + + return acc; + }, {}) + ).map((il) => { + il.actual_cost /= 100; + return generateBillLine(il, bodyshop.md_responsibility_centers, bill.job.class); + }); + } else { + lines = bill.billlines.map((il) => generateBillLine(il, bodyshop.md_responsibility_centers, bill.job.class)); + } + const billQbxmlObj = { QBXML: { QBXMLMsgsRq: { @@ -67,9 +89,7 @@ const generateBill = (bill, bodyshop) => { }), RefNumber: bill.invoice_number, Memo: `RO ${bill.job.ro_number || ""}`, - ExpenseLineAdd: bill.billlines.map((il) => - generateBillLine(il, bodyshop.md_responsibility_centers, bill.job.class) - ) + ExpenseLineAdd: lines } } }