From c97213bc9626ec53be3024c59bc336eef141128c Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 24 Mar 2026 18:12:04 -0700 Subject: [PATCH 1/2] IO-3599 Taxable Amount Signed-off-by: Allan Carr --- server/job/job-totals-USA.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index a8bd632cc..cdf5ec3b0 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -116,6 +116,32 @@ async function TotalsServerSide(req, res) { ret.totals.ttl_tax_adjustment = Dinero({ amount: Math.round(ttlTaxDifference * 100) }); ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_tax_adjustment); ret.totals.net_repairs = ret.totals.net_repairs.add(ret.totals.ttl_tax_adjustment); + + if (Math.abs(totalUsTaxes) === 0) { + const laborRates = Object.values(job.cieca_pfl) + .map((v) => v.lbr_taxp) + .filter((v) => v != null); + const materialRates = Object.values(job.materials) + .map((v) => v.mat_taxp) + .filter((v) => v != null); + const partsRates = Object.values(job.parts_tax_rates) + .map((v) => { + const field = v.prt_tax_rt ?? v.part_tax_rt; + if (field == null) return null; + const raw = typeof field === "object" ? field.parsedValue : field; + return raw != null ? raw * 100 : null; + }) + .filter((v) => v != null); + const taxRate = Math.max(...laborRates, ...materialRates, ...partsRates); + + const totalTaxes = ret.totals.taxableAmounts.total.multiply(taxRate > 1 ? taxRate / 100 : taxRate); + ret.totals.taxableAmounts.total = ret.totals.taxableAmounts.total + .multiply(emsTaxTotal) + .divide(totalTaxes.toUnit()); + } else { + ret.totals.taxableAmounts.total = ret.totals.taxableAmounts.total.multiply(emsTaxTotal).divide(totalUsTaxes); + } + logger.log("job-totals-USA-ttl-tax-adj", "debug", null, job.id, { adjAmount: ttlTaxDifference }); @@ -979,6 +1005,8 @@ function CalculateTaxesTotals(job, otherTotals) { } }); + taxableAmounts.total = Object.values(taxableAmounts).reduce((acc, amount) => acc.add(amount), Dinero({ amount: 0 })); + // console.log("*** Taxable Amounts***"); // console.table(JSON.parse(JSON.stringify(taxableAmounts))); @@ -1174,6 +1202,7 @@ function CalculateTaxesTotals(job, otherTotals) { let ret = { subtotal: subtotal, + taxableAmounts: taxableAmounts, federal_tax: subtotal .percentage((job.federal_tax_rate || 0) * 100) .add(otherTotals.additional.pvrt.percentage((job.federal_tax_rate || 0) * 100)), From cc623b7cbbbe45c11b3a77a54f0edfc621d3477c Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 24 Mar 2026 20:12:26 -0700 Subject: [PATCH 2/2] IO-3627 Courtesy Car Create RO Signed-off-by: Allan Carr --- .../contract-convert-to-ro.component.jsx | 218 ++++++++++-------- server/job/job-totals-USA.js | 37 ++- 2 files changed, 154 insertions(+), 101 deletions(-) diff --git a/client/src/components/contract-convert-to-ro/contract-convert-to-ro.component.jsx b/client/src/components/contract-convert-to-ro/contract-convert-to-ro.component.jsx index 015b70bbb..d66facf6a 100644 --- a/client/src/components/contract-convert-to-ro/contract-convert-to-ro.component.jsx +++ b/client/src/components/contract-convert-to-ro/contract-convert-to-ro.component.jsx @@ -10,6 +10,7 @@ import { createStructuredSelector } from "reselect"; import { INSERT_NEW_JOB } from "../../graphql/jobs.queries"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; +import InstanceRenderManager from "../../utils/instanceRenderMgr.js"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser @@ -156,104 +157,127 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled joblines: { data: billingLines }, - parts_tax_rates: { - PAA: { - prt_type: "PAA", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + ...InstanceRenderManager({ + imex: { + parts_tax_rates: { + PAA: { + prt_type: "PAA", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + PAC: { + prt_type: "PAC", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + PAL: { + prt_type: "PAL", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + PAM: { + prt_type: "PAM", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + PAN: { + prt_type: "PAN", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + PAR: { + prt_type: "PAR", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + PAS: { + prt_type: "PAS", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + CCDR: { + prt_type: "CCDR", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + CCF: { + prt_type: "CCF", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + CCM: { + prt_type: "CCM", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + CCC: { + prt_type: "CCC", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + }, + CCD: { + prt_type: "CCD", + prt_discp: 0, + prt_mktyp: false, + prt_mkupp: 0, + prt_tax_in: true, + prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + } + } }, - PAC: { - prt_type: "PAC", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 - }, - PAL: { - prt_type: "PAL", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 - }, - PAM: { - prt_type: "PAM", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 - }, - PAN: { - prt_type: "PAN", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 - }, - PAR: { - prt_type: "PAR", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 - }, - PAS: { - prt_type: "PAS", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 - }, - CCDR: { - prt_type: "CCDR", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 - }, - CCF: { - prt_type: "CCF", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 - }, - CCM: { - prt_type: "CCM", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 - }, - CCC: { - prt_type: "CCC", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 - }, - CCD: { - prt_type: "CCD", - prt_discp: 0, - prt_mktyp: false, - prt_mkupp: 0, - prt_tax_in: true, - prt_tax_rt: bodyshop.bill_tax_rates.state_tax_rate / 100 + rome: { + cieca_pft: { + ...bodyshop.md_responsibility_centers.taxes.tax_ty1, + ...bodyshop.md_responsibility_centers.taxes.tax_ty2, + ...bodyshop.md_responsibility_centers.taxes.tax_ty3, + ...bodyshop.md_responsibility_centers.taxes.tax_ty4, + ...bodyshop.md_responsibility_centers.taxes.tax_ty5 + }, + materials: bodyshop.md_responsibility_centers.cieca_pfm, + cieca_pfl: bodyshop.md_responsibility_centers.cieca_pfl, + parts_tax_rates: bodyshop.md_responsibility_centers.parts_tax_rates, + tax_tow_rt: bodyshop.md_responsibility_centers.tax_tow_rt, + tax_str_rt: bodyshop.md_responsibility_centers.tax_str_rt, + tax_paint_mat_rt: bodyshop.md_responsibility_centers.tax_paint_mat_rt, + tax_shop_mat_rt: bodyshop.md_responsibility_centers.tax_shop_mat_rt, + tax_sub_rt: bodyshop.md_responsibility_centers.tax_sub_rt, + tax_lbr_rt: bodyshop.md_responsibility_centers.tax_lbr_rt, + tax_levies_rt: bodyshop.md_responsibility_centers.tax_levies_rt } - } + }) }; if (currentUser?.email) { @@ -287,7 +311,7 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled notification.success({ title: t("jobs.successes.created"), onClick: () => { - history.push(`/manage/jobs/${result.data.insert_jobs.returning[0].id}`); + history(`/manage/jobs/${result.data.insert_jobs.returning[0].id}`); } }); } diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index a8bd632cc..be4accfff 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -885,6 +885,8 @@ function CalculateTaxesTotals(job, otherTotals) { STOR: Dinero() }; + const pfp = job.parts_tax_rates; + //For each line, determine if it's taxable, and if it is, add the line amount to the taxable amounts total. job.joblines .filter((jl) => !jl.removed) @@ -916,7 +918,17 @@ function CalculateTaxesTotals(job, otherTotals) { }) .multiply(val.part_qty || 0) .add(discMarkupAmount); - taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add(partAmount); + if (taxableAmounts[typeOfPart]) { + taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add(partAmount); + } else { + const isTaxableCustomType = + IsTrueOrYes(pfp?.[typeOfPart]?.prt_tax_in) || pfp?.[typeOfPart]?.prt_tax_in === true; + + if (isTaxableCustomType) { + if (!taxableAmounts[typeOfPart]) taxableAmounts[typeOfPart] = Dinero(); + taxableAmounts[typeOfPart] = taxableAmounts[typeOfPart].add(partAmount); + } + } } }); @@ -969,13 +981,15 @@ function CalculateTaxesTotals(job, otherTotals) { }) ); - const pfp = job.parts_tax_rates; - //For any profile level markups/discounts, add them in now as well. Object.keys(otherTotals.parts.adjustments).forEach((key) => { const adjustmentAmount = otherTotals.parts.adjustments[key]; if (adjustmentAmount.getAmount() !== 0 && pfp[key]?.prt_tax_in) { - taxableAmounts[key] = taxableAmounts[key].add(adjustmentAmount); + if (taxableAmounts[key]) { + taxableAmounts[key] = taxableAmounts[key].add(adjustmentAmount); + } else { + taxableAmounts[key] = Dinero().add(adjustmentAmount); + } } }); @@ -1072,6 +1086,21 @@ function CalculateTaxesTotals(job, otherTotals) { ); } } + } else if (pfp[key]) { + //Custom part types (e.g. CC*) should flow through taxableAmountsByTier too. + let assignedToTier = false; + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfp[key][`prt_tx_in${tyCounter}`])) { + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add( + taxableAmounts[key] + ); + assignedToTier = true; + } + } + + if (!assignedToTier && (IsTrueOrYes(pfp[key].prt_tax_in) || pfp[key].prt_tax_in === true)) { + taxableAmountsByTier.ty1Tax = taxableAmountsByTier.ty1Tax.add(taxableAmounts[key]); + } } } catch (error) { logger.log("job-totals-USA Key with issue", "warn", null, job.id, {