diff --git a/client/src/components/dms-allocations-summary/rr-dms-allocations-summary.component.jsx b/client/src/components/dms-allocations-summary/rr-dms-allocations-summary.component.jsx
index c04f2a62a..a1cfd3f10 100644
--- a/client/src/components/dms-allocations-summary/rr-dms-allocations-summary.component.jsx
+++ b/client/src/components/dms-allocations-summary/rr-dms-allocations-summary.component.jsx
@@ -18,7 +18,21 @@ export default connect(mapStateToProps, mapDispatchToProps)(RrAllocationsSummary
/**
* Normalize job allocations into a flat list for display / preview building.
* @param ack
- * @returns {{center: *, sale, partsSale, laborTaxableSale, laborNonTaxableSale, extrasSale, cost, profitCenter, costCenter}[]|*[]}
+ * @returns {{
+ * center: *,
+ * sale: *,
+ * partsSale: *,
+ * partsTaxableSale: *,
+ * partsNonTaxableSale: *,
+ * laborTaxableSale: *,
+ * laborNonTaxableSale: *,
+ * extrasSale: *,
+ * extrasTaxableSale: *,
+ * extrasNonTaxableSale: *,
+ * cost: *,
+ * profitCenter: *,
+ * costCenter: *
+ * }[]|*[]}
*/
function normalizeJobAllocations(ack) {
if (!ack || !Array.isArray(ack.jobAllocations)) return [];
@@ -31,9 +45,13 @@ function normalizeJobAllocations(ack) {
// bucketed sales used to build split ROGOG/ROLABOR
partsSale: row.partsSale || null,
+ partsTaxableSale: row.partsTaxableSale || null,
+ partsNonTaxableSale: row.partsNonTaxableSale || null,
laborTaxableSale: row.laborTaxableSale || null,
laborNonTaxableSale: row.laborNonTaxableSale || null,
extrasSale: row.extrasSale || null,
+ extrasTaxableSale: row.extrasTaxableSale || null,
+ extrasNonTaxableSale: row.extrasNonTaxableSale || null,
cost: row.cost || null,
profitCenter: row.profitCenter || null,
@@ -111,9 +129,12 @@ export function RrAllocationsSummary({ socket, bodyshop, jobId, title, onAllocat
}, [fetchAllocations]);
const segmentLabelMap = {
- partsExtras: "Parts/Extras",
- laborTaxable: "Taxable Labor",
- laborNonTaxable: "Non-Taxable Labor"
+ partsTaxable: "Parts Taxable",
+ partsNonTaxable: "Parts Non-Taxable",
+ extrasTaxable: "Extras Taxable",
+ extrasNonTaxable: "Extras Non-Taxable",
+ laborTaxable: "Labor Taxable",
+ laborNonTaxable: "Labor Non-Taxable"
};
const roggRows = useMemo(() => {
@@ -149,7 +170,7 @@ export function RrAllocationsSummary({ socket, bodyshop, jobId, title, onAllocat
});
});
return rows;
- }, [roggPreview, opCode]);
+ }, [roggPreview, opCode, segmentLabelMap]);
const rolaborRows = useMemo(() => {
if (!rolaborPreview || !Array.isArray(rolaborPreview.ops)) return [];
@@ -231,7 +252,8 @@ export function RrAllocationsSummary({ socket, bodyshop, jobId, title, onAllocat
<>
OpCode: {effectiveOpCode}. Only centers with RR GOG mapping (rr_gogcode & rr_item_type)
- are included. Totals below reflect exactly what will be sent in ROGOG.
+ are included. Totals below reflect exactly what will be sent in ROGOG, with parts, extras, and labor split
+ into taxable / non-taxable segments.
(v == null ? 0 : Number(v) || 0);
+
+ const federalTaxRate = toNumber(job.federal_tax_rate);
+ const stateTaxRate = toNumber(job.state_tax_rate);
+ const localTaxRate = toNumber(job.local_tax_rate);
+
+ const hasFederalRate = federalTaxRate > 0;
+ const hasState = stateTaxRate > 0;
+ const hasLocal = localTaxRate > 0;
+
+ // "hasFederal" kept for backwards compatibility / logging (Canada only)
+ const hasFederal = isImex && hasFederalRate;
+
+ // Canada: if ANY of federal / state / local > 0, treat the job as
+ // "everything taxable by default", then let line-level flags override
+ // for parts where applicable.
+ const globalAllTaxCanada = isImex && (hasFederalRate || hasState || hasLocal);
+
+ const hasAnySalesTax = hasFederalRate || hasState || hasLocal;
+
+ // Parts tax rate map (PAA/PAC/…)
+ let partTaxRates = job.part_tax_rates || job.parts_tax_rates || {};
+ if (typeof partTaxRates === "string") {
+ try {
+ partTaxRates = JSON.parse(partTaxRates);
+ } catch {
+ partTaxRates = {};
+ }
+ }
+ if (!partTaxRates || typeof partTaxRates !== "object") {
+ partTaxRates = {};
+ }
+
+ const tax_lbr_rt = toNumber(job.tax_lbr_rt); // labour
+ const tax_paint_mat_rt = toNumber(job.tax_paint_mat_rt || job.tax_paint_mt_rate); // MAPA
+ const tax_shop_mat_rt = toNumber(job.tax_shop_mat_rt); // MASH
+ const tax_tow_rt = toNumber(job.tax_tow_rt); // towing
+ const tax_sub_rt = toNumber(job.tax_sub_rt); // sublet (rarely used directly)
+
+ const hasAnyPartsWithTax = Object.values(partTaxRates).some(
+ (entry) => entry && entry.prt_tax_in && toNumber(entry.prt_tax_rt) > 0
+ );
+
+ const hasAnyTax =
+ hasAnySalesTax ||
+ tax_lbr_rt > 0 ||
+ tax_paint_mat_rt > 0 ||
+ tax_shop_mat_rt > 0 ||
+ tax_tow_rt > 0 ||
+ tax_sub_rt > 0 ||
+ hasAnyPartsWithTax;
+
+ return {
+ isImex,
+ isRome,
+
+ federalTaxRate,
+ stateTaxRate,
+ localTaxRate,
+
+ hasFederal,
+ hasState,
+ hasLocal,
+ hasAnySalesTax,
+ globalAllTaxCanada,
+
+ partTaxRates,
+ tax_lbr_rt,
+ tax_paint_mat_rt,
+ tax_shop_mat_rt,
+ tax_tow_rt,
+ tax_sub_rt,
+ hasAnyPartsWithTax,
+ hasAnyTax
+ };
+}
+
+/**
+ * Resolve the "PA" / part-type code (PAA/PAC/…) from a job line.
+ */
+function resolvePartType(line = {}) {
+ return line.part_type || line.partType || line.pa_code || line.pa || null;
}
/**
* Decide if a *part* line is taxable vs non-taxable for RR.
- * For now we mirror the same flag; this can be extended with CAD-specific
- * logic (federal_tax_rate, parts_tax_rate, prt_tax_in, etc.) later.
+ *
+ * Rules:
+ * - Canada (IMEX):
+ * - If ANY of federal_tax_rate / state_tax_rate / local_tax_rate > 0
+ * => everything is taxable by default (globalAllTaxCanada),
+ * unless tax_part is explicitly false.
+ * - Otherwise, use part_tax_rates[part_type] (prt_tax_in && prt_tax_rt > 0),
+ * with tax_part as final override.
+ * - US (ROME):
+ * - Use part_tax_rates[part_type] (prt_tax_in && prt_tax_rt > 0),
+ * with tax_part as final override.
+ *
+ * - line.tax_part is treated as the *final* check:
+ * - tax_part === false => always non-taxable.
+ * - tax_part === true => always taxable, even if we have no table entry.
*/
-function isPartTaxable(line) {
- return line.tax_part;
+function isPartTaxable(line = {}, taxCtx) {
+ if (!taxCtx) return !!line.tax_part;
+
+ const { globalAllTaxCanada, partTaxRates } = taxCtx;
+
+ // Explicit per-line override to *not* tax.
+ if (typeof line.tax_part === "boolean" && line.tax_part === false) {
+ return false;
+ }
+
+ // Canada: any federal/state/local tax rate set => all parts taxable,
+ // unless explicitly turned off above.
+ if (globalAllTaxCanada) {
+ return true;
+ }
+
+ let taxable = false;
+
+ const partType = resolvePartType(line);
+ if (partType && partTaxRates && partTaxRates[partType]) {
+ const entry = partTaxRates[partType];
+ const rate = Number(entry?.prt_tax_rt || 0);
+ const indicator = !!entry?.prt_tax_in;
+ taxable = indicator && rate > 0;
+ }
+
+ // tax_part === true is treated as "final yes" even if we didn't find
+ // a matching part_tax_rate entry.
+ if (typeof line.tax_part === "boolean" && line.tax_part === true) {
+ taxable = true;
+ }
+
+ return taxable;
+}
+
+/**
+ * Decide if *labour* for this job is taxable.
+ *
+ * - Canada (IMEX):
+ * - If ANY of federal_tax_rate / state_tax_rate / local_tax_rate > 0
+ * (globalAllTaxCanada) => all labour is taxable.
+ * - Else if tax_lbr_rt > 0 => labour taxable.
+ * - Else => non-taxable.
+ * - US (ROME):
+ * - tax_lbr_rt > 0 => labour taxable, otherwise not.
+ */
+function isLaborTaxable(_line, taxCtx) {
+ if (!taxCtx) return false;
+ const { isImex, globalAllTaxCanada, tax_lbr_rt } = taxCtx;
+
+ if (isImex && globalAllTaxCanada) return true;
+ return tax_lbr_rt > 0;
+}
+
+/**
+ * Taxability helpers for "extras" buckets.
+ * These are all job-level decisions; there are no per-line flags for them
+ * in the data we currently work with.
+ *
+ * Canada: if globalAllTaxCanada is true, we treat these as taxable.
+ */
+function isMapaTaxable(taxCtx) {
+ if (!taxCtx) return false;
+ const { isImex, globalAllTaxCanada, tax_paint_mat_rt } = taxCtx;
+ if (isImex && globalAllTaxCanada) return true;
+ return tax_paint_mat_rt > 0;
+}
+
+function isMashTaxable(taxCtx) {
+ if (!taxCtx) return false;
+ const { isImex, globalAllTaxCanada, tax_shop_mat_rt } = taxCtx;
+ if (isImex && globalAllTaxCanada) return true;
+ return tax_shop_mat_rt > 0;
+}
+
+function isTowTaxable(taxCtx) {
+ if (!taxCtx) return false;
+ const { isImex, globalAllTaxCanada, tax_tow_rt } = taxCtx;
+ if (isImex && globalAllTaxCanada) return true;
+ return tax_tow_rt > 0;
+}
+
+/**
+ * Helper to push an "extra" (MAPA/MASH/towing/PAO/etc) amount into the
+ * appropriate taxable / non-taxable buckets for a given center.
+ */
+function addExtras(bucket, dineroAmount, isTaxable) {
+ if (!bucket || !dineroAmount || typeof dineroAmount.add !== "function") return;
+ bucket.extrasSale = bucket.extrasSale.add(dineroAmount);
+ if (isTaxable) {
+ bucket.extrasTaxableSale = bucket.extrasTaxableSale.add(dineroAmount);
+ } else {
+ bucket.extrasNonTaxableSale = bucket.extrasNonTaxableSale.add(dineroAmount);
+ }
}
/**
* Build profitCenterHash from joblines (parts + labor) and detect MAPA/MASH presence.
* Now stores *buckets* instead of a single Dinero per center.
*/
-function buildProfitCenterHash(job, debugLog) {
+function buildProfitCenterHash(job, debugLog, taxContext) {
let hasMapaLine = false;
let hasMashLine = false;
@@ -231,7 +435,7 @@ function buildProfitCenterHash(job, debugLog) {
amount = amount.add(discount);
}
- const taxable = isPartTaxable(val);
+ const taxable = isPartTaxable(val, taxContext);
if (taxable) {
bucket.partsTaxableSale = bucket.partsTaxableSale.add(amount);
@@ -254,7 +458,7 @@ function buildProfitCenterHash(job, debugLog) {
amount: Math.round(rate * 100)
}).multiply(val.mod_lb_hrs);
- if (isLaborTaxable(val)) {
+ if (isLaborTaxable(val, taxContext)) {
bucket.laborTaxableSale = bucket.laborTaxableSale.add(laborAmount);
} else {
bucket.laborNonTaxableSale = bucket.laborNonTaxableSale.add(laborAmount);
@@ -274,7 +478,9 @@ function buildProfitCenterHash(job, debugLog) {
partsNonTaxable: summarizeMoney(b.partsNonTaxableSale),
laborTaxable: summarizeMoney(b.laborTaxableSale),
laborNonTaxable: summarizeMoney(b.laborNonTaxableSale),
- extras: summarizeMoney(b.extrasSale)
+ extras: summarizeMoney(b.extrasSale),
+ extrasTaxable: summarizeMoney(b.extrasTaxableSale),
+ extrasNonTaxable: summarizeMoney(b.extrasNonTaxableSale)
}))
});
@@ -353,39 +559,48 @@ function applyMapaMashManualLines({
profitCenterHash,
hasMapaLine,
hasMashLine,
- debugLog
+ debugLog,
+ taxContext
}) {
- // MAPA
+ // MAPA (paint materials)
if (!hasMapaLine && job.job_totals.rates.mapa.total.amount > 0) {
const mapaAccountName = selectedDmsAllocationConfig.profits.MAPA;
const mapaAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === mapaAccountName);
if (mapaAccount) {
+ const amount = Dinero(job.job_totals.rates.mapa.total);
+ const taxable = isMapaTaxable(taxContext);
+
debugLog("Adding MAPA Line Manually", {
mapaAccountName,
- amount: summarizeMoney(Dinero(job.job_totals.rates.mapa.total))
+ amount: summarizeMoney(amount),
+ taxable
});
const bucket = ensureCenterBucket(profitCenterHash, mapaAccountName);
- bucket.extrasSale = bucket.extrasSale.add(Dinero(job.job_totals.rates.mapa.total));
+ addExtras(bucket, amount, taxable);
} else {
debugLog("NO MAPA ACCOUNT FOUND!!", { mapaAccountName });
}
}
- // MASH
+ // MASH (shop materials)
if (!hasMashLine && job.job_totals.rates.mash.total.amount > 0) {
const mashAccountName = selectedDmsAllocationConfig.profits.MASH;
const mashAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === mashAccountName);
if (mashAccount) {
+ const amount = Dinero(job.job_totals.rates.mash.total);
+ const taxable = isMashTaxable(taxContext);
+
debugLog("Adding MASH Line Manually", {
mashAccountName,
- amount: summarizeMoney(Dinero(job.job_totals.rates.mash.total))
+ amount: summarizeMoney(amount),
+ taxable
});
const bucket = ensureCenterBucket(profitCenterHash, mashAccountName);
- bucket.extrasSale = bucket.extrasSale.add(Dinero(job.job_totals.rates.mash.total));
+ addExtras(bucket, amount, taxable);
} else {
debugLog("NO MASH ACCOUNT FOUND!!", { mashAccountName });
}
@@ -467,9 +682,17 @@ function applyMaterialsCosting({ job, bodyshop, selectedDmsAllocationConfig, cos
/**
* Apply non-tax extras (PVRT, towing, storage, PAO).
- * Extras go into the extrasSale bucket.
+ * Extras go into the extrasSale bucket (split taxable / non-taxable).
*/
-function applyExtras({ job, bodyshop, selectedDmsAllocationConfig, profitCenterHash, taxAllocations, debugLog }) {
+function applyExtras({
+ job,
+ bodyshop,
+ selectedDmsAllocationConfig,
+ profitCenterHash,
+ taxAllocations,
+ debugLog,
+ taxContext
+}) {
const { ca_bc_pvrt } = job;
// BC PVRT -> state tax
@@ -485,17 +708,19 @@ function applyExtras({ job, bodyshop, selectedDmsAllocationConfig, profitCenterH
const towAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === towAccountName);
if (towAccount) {
+ const amount = Dinero({
+ amount: Math.round((job.towing_payable || 0) * 100)
+ });
+ const taxable = isTowTaxable(taxContext);
+
debugLog("Adding towing_payable to TOW account", {
towAccountName,
- towing_payable: job.towing_payable
+ towing_payable: job.towing_payable,
+ taxable
});
const bucket = ensureCenterBucket(profitCenterHash, towAccountName);
- bucket.extrasSale = bucket.extrasSale.add(
- Dinero({
- amount: Math.round((job.towing_payable || 0) * 100)
- })
- );
+ addExtras(bucket, amount, taxable);
} else {
debugLog("NO TOW ACCOUNT FOUND!!", { towAccountName });
}
@@ -507,17 +732,19 @@ function applyExtras({ job, bodyshop, selectedDmsAllocationConfig, profitCenterH
const towAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === storageAccountName);
if (towAccount) {
+ const amount = Dinero({
+ amount: Math.round((job.storage_payable || 0) * 100)
+ });
+ const taxable = isTowTaxable(taxContext);
+
debugLog("Adding storage_payable to TOW account", {
storageAccountName,
- storage_payable: job.storage_payable
+ storage_payable: job.storage_payable,
+ taxable
});
const bucket = ensureCenterBucket(profitCenterHash, storageAccountName);
- bucket.extrasSale = bucket.extrasSale.add(
- Dinero({
- amount: Math.round((job.storage_payable || 0) * 100)
- })
- );
+ addExtras(bucket, amount, taxable);
} else {
debugLog("NO STORAGE/TOW ACCOUNT FOUND!!", { storageAccountName });
}
@@ -529,17 +756,19 @@ function applyExtras({ job, bodyshop, selectedDmsAllocationConfig, profitCenterH
const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === otherAccountName);
if (otherAccount) {
+ const amount = Dinero({
+ amount: Math.round((job.adjustment_bottom_line || 0) * 100)
+ });
+ const taxable = !!(taxContext && taxContext.hasAnyTax);
+
debugLog("Adding adjustment_bottom_line to PAO", {
otherAccountName,
- adjustment_bottom_line: job.adjustment_bottom_line
+ adjustment_bottom_line: job.adjustment_bottom_line,
+ taxable
});
const bucket = ensureCenterBucket(profitCenterHash, otherAccountName);
- bucket.extrasSale = bucket.extrasSale.add(
- Dinero({
- amount: Math.round((job.adjustment_bottom_line || 0) * 100)
- })
- );
+ addExtras(bucket, amount, taxable);
} else {
debugLog("NO PAO ACCOUNT FOUND!!", { otherAccountName });
}
@@ -558,7 +787,8 @@ function applyRomeProfileAdjustments({
selectedDmsAllocationConfig,
profitCenterHash,
debugLog,
- connectionData
+ connectionData,
+ taxContext
}) {
// Only relevant for Rome instances
if (!InstanceManager({ rome: true })) return profitCenterHash;
@@ -576,6 +806,8 @@ function applyRomeProfileAdjustments({
rateKeys: Object.keys(rateMap)
});
+ const extrasTaxable = !!(taxContext && taxContext.hasAnyTax);
+
// Parts adjustments
Object.keys(partsAdjustments).forEach((key) => {
const accountName = selectedDmsAllocationConfig.profits[key];
@@ -585,12 +817,13 @@ function applyRomeProfileAdjustments({
const bucket = ensureCenterBucket(profitCenterHash, accountName);
const adjMoney = Dinero(partsAdjustments[key]);
- bucket.extrasSale = bucket.extrasSale.add(adjMoney);
+ addExtras(bucket, adjMoney, extrasTaxable);
debugLog("Added parts adjustment", {
key,
accountName,
- adjustment: summarizeMoney(adjMoney)
+ adjustment: summarizeMoney(adjMoney),
+ taxable: extrasTaxable
});
} else {
CreateRRLogEvent(
@@ -619,12 +852,13 @@ function applyRomeProfileAdjustments({
// Note: we intentionally use `rate.adjustments` here to mirror CDK behaviour
const adjMoney = Dinero(rate.adjustments);
- bucket.extrasSale = bucket.extrasSale.add(adjMoney);
+ addExtras(bucket, adjMoney, extrasTaxable);
debugLog("Added rate adjustment", {
key,
accountName,
- adjustment: summarizeMoney(adjMoney)
+ adjustment: summarizeMoney(adjMoney),
+ taxable: extrasTaxable
});
} else {
CreateRRLogEvent(
@@ -651,6 +885,8 @@ function applyRomeProfileAdjustments({
* laborTaxableSale,
* laborNonTaxableSale,
* extrasSale,
+ * extrasTaxableSale,
+ * extrasNonTaxableSale,
* totalSale,
* cost,
* profitCenter,
@@ -663,10 +899,8 @@ function buildJobAllocations(bodyshop, profitCenterHash, costCenterHash, debugLo
const jobAllocations = centers.map((center) => {
const bucket = profitCenterHash[center] || emptyCenterBucket();
- const totalSale = bucket.partsSale
- .add(bucket.laborTaxableSale)
- .add(bucket.laborNonTaxableSale)
- .add(bucket.extrasSale);
+ const extrasSale = bucket.extrasSale;
+ const totalSale = bucket.partsSale.add(bucket.laborTaxableSale).add(bucket.laborNonTaxableSale).add(extrasSale);
const profitCenter = bodyshop.md_responsibility_centers.profits.find((c) => c.name === center);
const costCenter = bodyshop.md_responsibility_centers.costs.find((c) => c.name === center);
@@ -684,7 +918,9 @@ function buildJobAllocations(bodyshop, profitCenterHash, costCenterHash, debugLo
laborNonTaxableSale: bucket.laborNonTaxableSale,
// Extras
- extrasSale: bucket.extrasSale,
+ extrasSale,
+ extrasTaxableSale: bucket.extrasTaxableSale,
+ extrasNonTaxableSale: bucket.extrasNonTaxableSale,
totalSale,
@@ -705,6 +941,8 @@ function buildJobAllocations(bodyshop, profitCenterHash, costCenterHash, debugLo
laborTaxable: summarizeMoney(row.laborTaxableSale),
laborNonTaxable: summarizeMoney(row.laborNonTaxableSale),
extras: summarizeMoney(row.extrasSale),
+ extrasTaxable: summarizeMoney(row.extrasTaxableSale),
+ extrasNonTaxable: summarizeMoney(row.extrasNonTaxableSale),
totalSale: summarizeMoney(row.totalSale),
cost: summarizeMoney(row.cost)
}))
@@ -812,12 +1050,35 @@ function calculateAllocations(connectionData, job) {
timetickets: Array.isArray(job.timetickets) ? job.timetickets.length : 0
});
+ const taxContext = buildTaxContext(job);
+ debugLog("Tax context initialised", {
+ isImex: taxContext.isImex,
+ isRome: taxContext.isRome,
+ federalTaxRate: taxContext.federalTaxRate,
+ stateTaxRate: taxContext.stateTaxRate,
+ localTaxRate: taxContext.localTaxRate,
+ hasFederal: taxContext.hasFederal,
+ hasState: taxContext.hasState,
+ hasLocal: taxContext.hasLocal,
+ globalAllTaxCanada: taxContext.globalAllTaxCanada,
+ tax_lbr_rt: taxContext.tax_lbr_rt,
+ tax_paint_mat_rt: taxContext.tax_paint_mat_rt,
+ tax_shop_mat_rt: taxContext.tax_shop_mat_rt,
+ tax_tow_rt: taxContext.tax_tow_rt,
+ hasAnyPartsWithTax: taxContext.hasAnyPartsWithTax,
+ hasAnyTax: taxContext.hasAnyTax
+ });
+
// 1) Tax allocations
let taxAllocations = buildTaxAllocations(bodyshop, job);
debugLog("Initial taxAllocations", summarizeTaxAllocations(taxAllocations));
// 2) Profit centers from job lines + MAPA/MASH detection
- const { profitCenterHash: initialProfitHash, hasMapaLine, hasMashLine } = buildProfitCenterHash(job, debugLog);
+ const {
+ profitCenterHash: initialProfitHash,
+ hasMapaLine,
+ hasMashLine
+ } = buildProfitCenterHash(job, debugLog, taxContext);
// 3) DMS allocation config
const selectedDmsAllocationConfig =
@@ -842,7 +1103,8 @@ function calculateAllocations(connectionData, job) {
profitCenterHash: initialProfitHash,
hasMapaLine,
hasMashLine,
- debugLog
+ debugLog,
+ taxContext
});
// 6) Materials costing (MAPA/MASH cost side)
@@ -861,7 +1123,8 @@ function calculateAllocations(connectionData, job) {
selectedDmsAllocationConfig,
profitCenterHash,
taxAllocations,
- debugLog
+ debugLog,
+ taxContext
}));
// 8) Rome-only profile-level adjustments
@@ -871,7 +1134,8 @@ function calculateAllocations(connectionData, job) {
selectedDmsAllocationConfig,
profitCenterHash,
debugLog,
- connectionData
+ connectionData,
+ taxContext
});
debugLog("profitCenterHash before jobAllocations build", {
@@ -882,7 +1146,9 @@ function calculateAllocations(connectionData, job) {
partsNonTaxable: summarizeMoney(b.partsNonTaxableSale),
laborTaxable: summarizeMoney(b.laborTaxableSale),
laborNonTaxable: summarizeMoney(b.laborNonTaxableSale),
- extras: summarizeMoney(b.extrasSale)
+ extras: summarizeMoney(b.extrasSale),
+ extrasTaxable: summarizeMoney(b.extrasTaxableSale),
+ extrasNonTaxable: summarizeMoney(b.extrasNonTaxableSale)
}))
});
debugLog("costCenterHash before jobAllocations build", {
diff --git a/server/rr/rr-job-helpers.js b/server/rr/rr-job-helpers.js
index d9fc523a8..1d415bb72 100644
--- a/server/rr/rr-job-helpers.js
+++ b/server/rr/rr-job-helpers.js
@@ -56,7 +56,7 @@ const asN2 = (dineroLike) => {
* Build RO.GOG structure for the reynolds-rome-client `createRepairOrder` payload
* from allocations.
*
- * Supports the new allocation shape:
+ * Supports the allocation shape:
* {
* center,
* partsSale,
@@ -65,18 +65,21 @@ const asN2 = (dineroLike) => {
* laborTaxableSale,
* laborNonTaxableSale,
* extrasSale,
+ * extrasTaxableSale,
+ * extrasNonTaxableSale,
* totalSale,
* cost,
* profitCenter,
* costCenter
* }
*
- * For each center, we can emit up to 5 GOG *segments*:
- * - taxable parts (CustTxblNTxblFlag="T")
- * - non-taxable parts (CustTxblNTxblFlag="N")
- * - extras (uses profitCenter.rr_cust_txbl_flag)
- * - taxable labor (CustTxblNTxblFlag="T")
- * - non-tax labor (CustTxblNTxblFlag="N")
+ * For each center, we can emit up to 6 GOG *segments*:
+ * - taxable parts (CustTxblNTxblFlag="T")
+ * - non-taxable parts (CustTxblNTxblFlag="N")
+ * - taxable extras (CustTxblNTxblFlag="T")
+ * - non-taxable extras (CustTxblNTxblFlag="N")
+ * - taxable labor (CustTxblNTxblFlag="T")
+ * - non-taxable labor (CustTxblNTxblFlag="N")
*
* IMPORTANT:
* Each segment becomes its OWN JobNo / AllGogOpCodeInfo, with exactly one
@@ -153,7 +156,8 @@ const buildRogogFromAllocations = (allocations, { opCode, payType = "Cust", roNo
const partsTaxableCents = toCents(alloc.partsTaxableSale);
const partsNonTaxableCents = toCents(alloc.partsNonTaxableSale);
- const extrasCents = toCents(alloc.extrasSale);
+ const extrasTaxableCents = toCents(alloc.extrasTaxableSale);
+ const extrasNonTaxableCents = toCents(alloc.extrasNonTaxableSale);
const laborTaxableCents = toCents(alloc.laborTaxableSale);
const laborNonTaxableCents = toCents(alloc.laborNonTaxableSale);
const costCents = toCents(alloc.cost);
@@ -178,16 +182,25 @@ const buildRogogFromAllocations = (allocations, { opCode, payType = "Cust", roNo
});
}
- // 3) Extras segment (respect center's default tax flag)
- if (extrasCents !== 0) {
+ // 3) Taxable extras -> "T"
+ if (extrasTaxableCents !== 0) {
segments.push({
- kind: "extras",
- saleCents: extrasCents,
- txFlag: pc.rr_cust_txbl_flag || "N"
+ kind: "extrasTaxable",
+ saleCents: extrasTaxableCents,
+ txFlag: "T"
});
}
- // 4) Taxable labor segment -> "T"
+ // 4) Non-taxable extras -> "N"
+ if (extrasNonTaxableCents !== 0) {
+ segments.push({
+ kind: "extrasNonTaxable",
+ saleCents: extrasNonTaxableCents,
+ txFlag: "N"
+ });
+ }
+
+ // 5) Taxable labor segment -> "T"
if (laborTaxableCents !== 0) {
segments.push({
kind: "laborTaxable",
@@ -196,7 +209,7 @@ const buildRogogFromAllocations = (allocations, { opCode, payType = "Cust", roNo
});
}
- // 5) Non-taxable labor segment -> "N"
+ // 6) Non-tax labor segment -> "N"
if (laborNonTaxableCents !== 0) {
segments.push({
kind: "laborNonTaxable",
@@ -356,10 +369,8 @@ const QueryJobData = async (ctx = {}, jobId) => {
* @param advisorNo
* @param story
* @param makeOverride
- * @param bodyshop
* @param allocations
* @param {string} [opCode] - RR OpCode for this RO (global default / override)
- * @param {string} [taxCode] - RR tax code for header tax (e.g. state/prov code)
* @returns {Object}
*/
const buildRRRepairOrderPayload = ({
@@ -370,7 +381,6 @@ const buildRRRepairOrderPayload = ({
makeOverride,
allocations,
opCode
- // taxCode
} = {}) => {
const customerNo = selectedCustomer?.customerNo
? String(selectedCustomer.customerNo).trim()
@@ -421,7 +431,6 @@ const buildRRRepairOrderPayload = ({
if (haveAllocations) {
const effectiveOpCode = (opCode && String(opCode).trim()) || null;
- // const effectiveTaxCode = (taxCode && String(taxCode).trim()) || null;
if (effectiveOpCode) {
// Build RO.GOG and RO.LABOR in the new normalized shape