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
new file mode 100644
index 000000000..bfc388678
--- /dev/null
+++ b/client/src/components/dms-allocations-summary/rr-dms-allocations-summary.component.jsx
@@ -0,0 +1,521 @@
+import { Alert, Button, Card, Table, Tabs, Typography } from "antd";
+import { SyncOutlined } from "@ant-design/icons";
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import Dinero from "dinero.js";
+
+import { selectBodyshop } from "../../redux/user/user.selectors";
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop
+});
+const mapDispatchToProps = () => ({});
+
+export default connect(mapStateToProps, mapDispatchToProps)(RrAllocationsSummary);
+
+function normalizeDineroJson(d) {
+ if (!d) return null;
+
+ // If it's already a Dinero instance, leave it to the caller
+ if (typeof d.toUnit === "function") return d;
+
+ // New server shape: { cents: 54144, currency?: "USD" }
+ if (typeof d.cents === "number") {
+ return {
+ amount: d.cents,
+ precision: 2,
+ currency: d.currency || "USD"
+ };
+ }
+
+ // Classic Dinero JSON: { amount, precision, currency }
+ if (typeof d.amount === "number") {
+ return {
+ amount: d.amount,
+ precision: d.precision ?? 2,
+ currency: d.currency || "USD"
+ };
+ }
+
+ return null;
+}
+
+/**
+ * Convert a Dinero-like object into an "N2" string ("123.45").
+ * Works with real Dinero instances or plain JSON objects
+ * that have { amount, precision }.
+ */
+function dineroToN2(dineroLike) {
+ if (!dineroLike) return "0.00";
+
+ // If it's an actual Dinero instance
+ if (typeof dineroLike.toUnit === "function") {
+ return dineroLike.toUnit().toFixed(2);
+ }
+
+ const normalized = normalizeDineroJson(dineroLike);
+ if (!normalized) return "0.00";
+
+ const { amount, precision = 2 } = normalized;
+ const value = amount / Math.pow(10, precision);
+ return value.toFixed(2);
+}
+
+/**
+ * Normalize job allocations into a flat list for display / preview building.
+ * @param ack
+ * @returns {{center: *, sale, partsSale, laborTaxableSale, laborNonTaxableSale, extrasSale, cost, profitCenter, costCenter}[]|*[]}
+ */
+function normalizeJobAllocations(ack) {
+ if (!ack || !Array.isArray(ack.jobAllocations)) return [];
+
+ return ack.jobAllocations.map((row) => ({
+ center: row.center,
+
+ // legacy "sale" (total) if we ever want to show it again
+ sale: row.sale || row.totalSale || null,
+
+ // bucketed sales used to build split ROGOG/ROLABOR
+ partsSale: row.partsSale || null,
+ laborTaxableSale: row.laborTaxableSale || null,
+ laborNonTaxableSale: row.laborNonTaxableSale || null,
+ extrasSale: row.extrasSale || null,
+
+ cost: row.cost || null,
+ profitCenter: row.profitCenter || null,
+ costCenter: row.costCenter || null
+ }));
+}
+
+/**
+ * Build a minimal ROGOG preview from job allocation rows.
+ * Mirrors the backend buildRogogFromAllocations logic:
+ * - parts+extras segment
+ * - taxable labor segment
+ * - non-taxable labor segment
+ * Each segment becomes its *own* JobNo with a single GOG line.
+ */
+function buildRogogPreviewFromJobRows(jobRows, opCode) {
+ if (!Array.isArray(jobRows) || !jobRows.length || !opCode) return null;
+
+ const ops = [];
+
+ const toDinero = (d) => {
+ if (!d) return Dinero();
+ if (typeof d.toUnit === "function") return d;
+
+ const normalized = normalizeDineroJson(d);
+ return normalized ? Dinero(normalized) : Dinero();
+ };
+
+ const cents = (d) => toDinero(d).getAmount();
+
+ const segmentLabelMap = {
+ partsExtras: "Parts/Extras",
+ laborTaxable: "Taxable Labor",
+ laborNonTaxable: "Non-Taxable Labor"
+ };
+
+ for (const row of jobRows) {
+ const pc = row.profitCenter || {};
+ const breakOut = pc.rr_gogcode;
+ const itemType = pc.rr_item_type;
+
+ // Only centers configured for RR GOG should appear
+ if (!breakOut || !itemType) continue;
+
+ // Bucketed sales (Dinero-like objects coming from the backend)
+ const partsSale = toDinero(row.partsSale);
+ const extrasSale = toDinero(row.extrasSale);
+ const laborTaxableSale = toDinero(row.laborTaxableSale);
+ const laborNonTaxableSale = toDinero(row.laborNonTaxableSale);
+ const costMoney = toDinero(row.cost);
+
+ const partsExtrasSale = partsSale.add(extrasSale);
+
+ const segments = [];
+
+ // 1) Parts + extras segment (uses center's default tax flag)
+ if (!partsExtrasSale.isZero()) {
+ segments.push({
+ kind: "partsExtras",
+ sale: partsExtrasSale,
+ txFlag: pc.rr_cust_txbl_flag || "T"
+ });
+ }
+
+ // 2) Taxable labor -> always "T"
+ if (!laborTaxableSale.isZero()) {
+ segments.push({
+ kind: "laborTaxable",
+ sale: laborTaxableSale,
+ txFlag: "T"
+ });
+ }
+
+ // 3) Non-taxable labor -> always "N"
+ if (!laborNonTaxableSale.isZero()) {
+ segments.push({
+ kind: "laborNonTaxable",
+ sale: laborNonTaxableSale,
+ txFlag: "N"
+ });
+ }
+
+ if (!segments.length) continue;
+
+ // Proportionally split cost across segments (same logic as backend)
+ const totalCostCents = cents(costMoney);
+ const totalSaleCents = segments.reduce((sum, seg) => sum + cents(seg.sale), 0);
+
+ let remainingCostCents = totalCostCents;
+
+ segments.forEach((seg, idx) => {
+ let costCents = 0;
+
+ if (totalCostCents > 0 && totalSaleCents > 0) {
+ if (idx === segments.length - 1) {
+ // Last segment gets the remainder to avoid rounding drift
+ costCents = remainingCostCents;
+ } else {
+ const segSaleCents = cents(seg.sale);
+ costCents = Math.round((segSaleCents / totalSaleCents) * totalCostCents);
+ remainingCostCents -= costCents;
+ }
+ }
+
+ seg.costCents = costCents;
+ });
+
+ const itemDescBase = pc.accountdesc || pc.accountname || row.center || "";
+
+ // π Each segment becomes its own JobNo with a single GOG line
+ segments.forEach((seg, segIndex) => {
+ const jobNo = String(ops.length + 1); // 1-based, global across all centers
+ const segmentCount = segments.length;
+ const segmentKind = seg.kind;
+ const segmentLabel = segmentLabelMap[segmentKind] || segmentKind;
+
+ // If there is a split, annotate the description so itβs obvious which segment this is
+ const displayDesc = segmentCount > 1 ? `${itemDescBase} (${segmentLabel})` : itemDescBase;
+
+ const saleN2 = dineroToN2(seg.sale);
+ const costN2 = dineroToN2({
+ amount: seg.costCents || 0,
+ precision: 2
+ });
+
+ ops.push({
+ opCode,
+ jobNo,
+ segmentKind,
+ segmentIndex: segIndex,
+ segmentCount,
+ lines: [
+ {
+ breakOut,
+ itemType,
+ itemDesc: displayDesc,
+ custQty: "1.0",
+ custPayTypeFlag: "C",
+ // canonical property name used on the server
+ custTxblNTxblFlag: seg.txFlag || "T",
+ // legacy alias used by the table
+ custTxblNtxblFlag: seg.txFlag || "T",
+ amount: {
+ payType: "Cust",
+ amtType: "Unit",
+ custPrice: saleN2,
+ dlrCost: costN2
+ }
+ }
+ ]
+ });
+ });
+ }
+
+ if (!ops.length) return null;
+
+ return {
+ roNo: null, // preview only
+ ops
+ };
+}
+
+/**
+ * Build a minimal ROLABOR preview from a ROGOG preview.
+ * Mirrors server-side buildRolaborFromRogog.
+ */
+function buildRolaborPreviewFromRogog(rogg) {
+ if (!rogg || !Array.isArray(rogg.ops)) return null;
+
+ const ops = rogg.ops.map((op) => {
+ const firstLine = op.lines?.[0] || {};
+
+ // Prefer the server-side property name, but fall back to legacy
+ const txFlag = firstLine.custTxblNTxblFlag ?? firstLine.custTxblNtxblFlag ?? "N";
+ const payFlag = firstLine.custPayTypeFlag || "C";
+
+ return {
+ opCode: op.opCode,
+ jobNo: op.jobNo,
+ custPayTypeFlag: payFlag,
+ // this is what the table uses
+ custTxblNtxblFlag: txFlag,
+ bill: {
+ payType: "Cust",
+ jobTotalHrs: "0",
+ billTime: "0",
+ billRate: "0"
+ },
+ amount: {
+ payType: "Cust",
+ amtType: "Job",
+ custPrice: "0",
+ totalAmt: "0"
+ }
+ };
+ });
+
+ if (!ops.length) return null;
+ return { ops };
+}
+
+/**
+ * RR-specific DMS Allocations Summary
+ * Focused on what we actually send to RR:
+ * - ROGOG (split by taxable / non-taxable segments)
+ * - ROLABOR shell
+ */
+export function RrAllocationsSummary({ socket, bodyshop, jobId, title }) {
+ const { t } = useTranslation();
+ const [jobRows, setJobRows] = useState([]);
+ const [error, setError] = useState(null);
+
+ const fetchAllocations = useCallback(() => {
+ if (!socket || !jobId) return;
+
+ try {
+ socket.emit("rr-calculate-allocations", jobId, (ack) => {
+ if (ack && ack.ok === false) {
+ setJobRows([]);
+ setError(ack.error || t("dms.labels.allocations_error"));
+ if (socket) {
+ socket.allocationsSummary = [];
+ socket.rrAllocationsRaw = ack;
+ }
+ return;
+ }
+
+ const jobAllocRows = normalizeJobAllocations(ack);
+
+ setJobRows(jobAllocRows);
+ setError(null);
+
+ if (socket) {
+ socket.allocationsSummary = jobAllocRows;
+ socket.rrAllocationsRaw = ack;
+ }
+ });
+ } catch {
+ setJobRows([]);
+ setError(t("dms.labels.allocations_error"));
+ if (socket) {
+ socket.allocationsSummary = [];
+ }
+ }
+ }, [socket, jobId, t]);
+
+ useEffect(() => {
+ fetchAllocations();
+ }, [fetchAllocations]);
+
+ const opCode = bodyshop?.rr_configuration?.baseOpCode || "28TOZ";
+
+ const roggPreview = useMemo(() => buildRogogPreviewFromJobRows(jobRows, opCode), [jobRows, opCode]);
+ const rolaborPreview = useMemo(() => buildRolaborPreviewFromRogog(roggPreview), [roggPreview]);
+
+ const roggRows = useMemo(() => {
+ if (!roggPreview || !Array.isArray(roggPreview.ops)) return [];
+ const rows = [];
+ roggPreview.ops.forEach((op) => {
+ (op.lines || []).forEach((line, idx) => {
+ rows.push({
+ key: `${op.jobNo}-${idx}`,
+ opCode: op.opCode,
+ jobNo: op.jobNo,
+ breakOut: line.breakOut,
+ itemType: line.itemType,
+ itemDesc: line.itemDesc,
+ custQty: line.custQty,
+ custPayTypeFlag: line.custPayTypeFlag,
+ custTxblNtxblFlag: line.custTxblNtxblFlag,
+ custPrice: line.amount?.custPrice,
+ dlrCost: line.amount?.dlrCost,
+ // segment metadata for visual styling
+ segmentKind: op.segmentKind,
+ segmentCount: op.segmentCount
+ });
+ });
+ });
+ return rows;
+ }, [roggPreview]);
+
+ const rolaborRows = useMemo(() => {
+ if (!rolaborPreview || !Array.isArray(rolaborPreview.ops)) return [];
+ return rolaborPreview.ops.map((op, idx) => ({
+ key: `${op.jobNo}-${idx}`,
+ opCode: op.opCode,
+ jobNo: op.jobNo,
+ custPayTypeFlag: op.custPayTypeFlag,
+ custTxblNtxblFlag: op.custTxblNtxblFlag,
+ payType: op.bill?.payType,
+ amtType: op.amount?.amtType,
+ custPrice: op.amount?.custPrice,
+ totalAmt: op.amount?.totalAmt
+ }));
+ }, [rolaborPreview]);
+
+ // Totals for ROGOG (sum custPrice + dlrCost over all lines)
+ const roggTotals = useMemo(() => {
+ if (!roggPreview || !Array.isArray(roggPreview.ops)) {
+ return { totalCustPrice: "0.00", totalDlrCost: "0.00" };
+ }
+
+ let totalCustCents = 0;
+ let totalCostCents = 0;
+
+ roggPreview.ops.forEach((op) => {
+ (op.lines || []).forEach((line) => {
+ const cp = parseFloat(line.amount?.custPrice || "0");
+ if (!Number.isNaN(cp)) {
+ totalCustCents += Math.round(cp * 100);
+ }
+
+ const dc = parseFloat(line.amount?.dlrCost || "0");
+ if (!Number.isNaN(dc)) {
+ totalCostCents += Math.round(dc * 100);
+ }
+ });
+ });
+
+ return {
+ totalCustPrice: (totalCustCents / 100).toFixed(2),
+ totalDlrCost: (totalCostCents / 100).toFixed(2)
+ };
+ }, [roggPreview]);
+
+ const roggColumns = [
+ { title: "JobNo", dataIndex: "jobNo", key: "jobNo" },
+ { title: "OpCode", dataIndex: "opCode", key: "opCode" },
+ { title: "BreakOut", dataIndex: "breakOut", key: "breakOut" },
+ { title: "ItemType", dataIndex: "itemType", key: "itemType" },
+ { title: "ItemDesc", dataIndex: "itemDesc", key: "itemDesc" },
+ { title: "CustQty", dataIndex: "custQty", key: "custQty" },
+ { title: "CustTxblFlag", dataIndex: "custTxblNtxblFlag", key: "custTxblNtxblFlag" },
+ { title: "CustPrice", dataIndex: "custPrice", key: "custPrice" },
+ { title: "DlrCost", dataIndex: "dlrCost", key: "dlrCost" }
+ ];
+
+ const rolaborColumns = [
+ { title: "JobNo", dataIndex: "jobNo", key: "jobNo" },
+ { title: "OpCode", dataIndex: "opCode", key: "opCode" },
+ { title: "CustPayType", dataIndex: "custPayTypeFlag", key: "custPayTypeFlag" },
+ { title: "CustTxblFlag", dataIndex: "custTxblNtxblFlag", key: "custTxblNtxblFlag" },
+ { title: "PayType", dataIndex: "payType", key: "payType" },
+ { title: "AmtType", dataIndex: "amtType", key: "amtType" },
+ { title: "CustPrice", dataIndex: "custPrice", key: "custPrice" },
+ { title: "TotalAmt", dataIndex: "totalAmt", key: "totalAmt" }
+ ];
+
+ const tabItems = [
+ {
+ key: "rogog",
+ label: "ROGOG Preview",
+ children: (
+ <>
+
+ OpCode: {opCode}. Only centers with RR GOG mapping (rr_gogcode & rr_item_type) are
+ included. Totals below reflect exactly what will be sent in ROGOG.
+
+
{
+ if (
+ record.segmentCount > 1 &&
+ (record.segmentKind === "laborTaxable" || record.segmentKind === "laborNonTaxable")
+ ) {
+ return "rr-allocations-tax-split-row";
+ }
+ if (record.segmentCount > 1) {
+ return "rr-allocations-split-row";
+ }
+ return "";
+ }}
+ summary={() => (
+
+
+ {t("general.labels.totals")}
+
+
+
+
+
+
+
+ {roggTotals.totalCustPrice}
+ {roggTotals.totalDlrCost}
+
+ )}
+ />
+ >
+ )
+ },
+ {
+ key: "rolabor",
+ label: "ROLABOR Preview",
+ children: (
+ <>
+
+ This mirrors the shell that would be sent for ROLABOR when all financials are carried in GOG.
+
+
+ >
+ )
+ }
+ ];
+
+ return (
+
+
+
+ }
+ >
+ {bodyshop.pbs_configuration?.disablebillwip && (
+
+ )}
+
+ {error && }
+
+
+
+ );
+}
diff --git a/client/src/components/dms-log-events/dms-log-events.component.jsx b/client/src/components/dms-log-events/dms-log-events.component.jsx
index 25f0981cf..d71f3633f 100644
--- a/client/src/components/dms-log-events/dms-log-events.component.jsx
+++ b/client/src/components/dms-log-events/dms-log-events.component.jsx
@@ -13,7 +13,14 @@ const mapDispatchToProps = () => ({});
export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents);
-export function DmsLogEvents({ logs, detailsOpen, detailsNonce, isDarkMode, colorizeJson = false }) {
+export function DmsLogEvents({
+ logs,
+ detailsOpen,
+ detailsNonce,
+ isDarkMode,
+ colorizeJson = false,
+ showDetails = true
+}) {
const [openSet, setOpenSet] = useState(() => new Set());
// Inject JSON highlight styles once (only when colorize is enabled)
@@ -54,8 +61,10 @@ export function DmsLogEvents({ logs, detailsOpen, detailsNonce, isDarkMode, colo
() =>
(logs || []).map((raw, idx) => {
const { level, message, timestamp, meta } = normalizeLog(raw);
- const hasMeta = !isEmpty(meta);
- const isOpen = openSet.has(idx);
+
+ // Only treat meta as "present" when we are allowed to show details
+ const hasMeta = !isEmpty(meta) && showDetails;
+ const isOpen = hasMeta && openSet.has(idx);
return {
key: idx,
@@ -101,7 +110,7 @@ export function DmsLogEvents({ logs, detailsOpen, detailsNonce, isDarkMode, colo
)
};
}),
- [logs, openSet, colorizeJson]
+ [logs, openSet, colorizeJson, isDarkMode, showDetails]
);
return ;
diff --git a/client/src/pages/dms/dms.container.jsx b/client/src/pages/dms/dms.container.jsx
index 313e29376..a5144c0f2 100644
--- a/client/src/pages/dms/dms.container.jsx
+++ b/client/src/pages/dms/dms.container.jsx
@@ -26,6 +26,7 @@ import DmsPostForm from "../../components/dms-post-form/dms-post-form.component"
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
import DmsCustomerSelector from "../../components/dms-customer-selector/dms-customer-selector.component";
import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms-allocations-summary.component";
+import RrAllocationsSummary from "../../components/dms-allocations-summary/rr-dms-allocations-summary.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -82,6 +83,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
// Compute a single normalized mode and pick the proper socket
const mode = getDmsMode(bodyshop, Fortellis.treatment); // "rr" | "fortellis" | "cdk" | "pbs" | "none"
+ const isRrMode = mode === DMS_MAP.reynolds;
const { socket: wsssocket } = useSocket();
const activeSocket = useMemo(() => (isWssMode(mode) ? wsssocket : legacySocket), [mode, wsssocket]);
@@ -134,7 +136,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
[mode]
);
- const transportLabel = isWssMode(mode) ? "App Socket (WSS)" : "Legacy Socket (WS)";
+ const transportLabel = isWssMode(mode) ? "(WSS)" : "(WS)";
const bannerMessage = `Posting to ${providerLabel} | ${transportLabel} | ${
isConnected ? "Connected" : "Disconnected"
@@ -148,10 +150,10 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
errText ||
t("dms.errors.exportfailedgeneric", "We couldn't complete the export. Please try again.");
- const vendorTitle = title || (mode === DMS_MAP.reynolds ? "Reynolds" : "DMS");
+ const vendorTitle = title || (isRrMode ? "Reynolds" : "DMS");
const isRrOpenRoLimit =
- mode === DMS_MAP.reynolds &&
+ isRrMode &&
(vendorStatusCode === 507 ||
/MAX_OPEN_ROS/i.test(String(errorCode || "")) ||
/maximum number of open repair orders/i.test(String(msg || "").toLowerCase()));
@@ -187,8 +189,8 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
});
setSelectedHeader("dms");
setBreadcrumbs([
- { link: "/manage/accounting/receivables", label: t("titles.bc.accounting-receivables") },
- { link: "/manage/dms", label: t("titles.bc.dms") }
+ { link: "/manage/accounting/receivables", label: t("titles.bc.accounting-receivables") }
+ // { link: "/manage/dms", label: t("titles.bc.dms") }
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
@@ -218,7 +220,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
{
timestamp: new Date(),
level: "warn",
- message: `Reconnected to ${mode === DMS_MAP.reynolds ? "RR" : mode === DMS_MAP.fortellis ? "Fortellis" : "DMS"} Export Service`
+ message: `Reconnected to ${isRrMode ? "RR" : mode === DMS_MAP.fortellis ? "Fortellis" : "DMS"} Export Service`
}
]);
};
@@ -235,22 +237,17 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
activeSocket.on("connect_error", onConnectError);
// Logs
- const onLog =
- mode === DMS_MAP.reynolds
- ? (payload = {}) => {
- const normalized = {
- timestamp: payload.timestamp
- ? new Date(payload.timestamp)
- : payload.ts
- ? new Date(payload.ts)
- : new Date(),
- level: (payload.level || "INFO").toUpperCase(),
- message: payload.message || payload.msg || "",
- meta: payload.meta ?? payload.ctx ?? payload.details ?? null
- };
- setLogs((prev) => [...prev, normalized]);
- }
- : (payload) => setLogs((prev) => [...prev, payload]);
+ const onLog = isRrMode
+ ? (payload = {}) => {
+ const normalized = {
+ timestamp: payload.timestamp ? new Date(payload.timestamp) : payload.ts ? new Date(payload.ts) : new Date(),
+ level: (payload.level || "INFO").toUpperCase(),
+ message: payload.message || payload.msg || "",
+ meta: payload.meta ?? payload.ctx ?? payload.details ?? null
+ };
+ setLogs((prev) => [...prev, normalized]);
+ }
+ : (payload) => setLogs((prev) => [...prev, payload]);
if (channels.log) activeSocket.on(channels.log, onLog);
@@ -308,9 +305,8 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
]);
};
- if (mode === DMS_MAP.reynolds && channels.partialResult) activeSocket.on(channels.partialResult, onPartialResult);
- if (mode === DMS_MAP.reynolds && channels.validationNeeded)
- activeSocket.on(channels.validationNeeded, onValidationRequired);
+ if (isRrMode && channels.partialResult) activeSocket.on(channels.partialResult, onPartialResult);
+ if (isRrMode && channels.validationNeeded) activeSocket.on(channels.validationNeeded, onValidationRequired);
return () => {
activeSocket.off("connect", onConnect);
@@ -322,10 +318,8 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
if (channels.exportSuccess) activeSocket.off(channels.exportSuccess, onExportSuccess);
if (channels.exportFailed) activeSocket.off(channels.exportFailed, handleExportFailed);
- if (mode === DMS_MAP.reynolds && channels.partialResult)
- activeSocket.off(channels.partialResult, onPartialResult);
- if (mode === DMS_MAP.reynolds && channels.validationNeeded)
- activeSocket.off(channels.validationNeeded, onValidationRequired);
+ if (isRrMode && channels.partialResult) activeSocket.off(channels.partialResult, onPartialResult);
+ if (isRrMode && channels.validationNeeded) activeSocket.off(channels.validationNeeded, onValidationRequired);
// Only tear down legacy socket listeners; don't disconnect WSS from here
if (!isWssMode(mode)) {
@@ -359,19 +353,38 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
-
- {`${data?.jobs_by_pk && data.jobs_by_pk.ro_number}`}
- {` | ${OwnerNameDisplayFunction(data.jobs_by_pk)} | ${data.jobs_by_pk.v_model_yr || ""} ${data.jobs_by_pk.v_make_desc || ""} ${data.jobs_by_pk.v_model_desc || ""}`}
-
- }
- socket={activeSocket}
- jobId={jobId}
- mode={mode}
- />
+ {!isRrMode ? (
+
+ {`${data?.jobs_by_pk && data.jobs_by_pk.ro_number}`}
+ {` | ${OwnerNameDisplayFunction(data.jobs_by_pk)} | ${data.jobs_by_pk.v_model_yr || ""} ${
+ data.jobs_by_pk.v_make_desc || ""
+ } ${data.jobs_by_pk.v_model_desc || ""}`}
+
+ }
+ socket={activeSocket}
+ jobId={jobId}
+ mode={mode}
+ />
+ ) : (
+
+
+ {data?.jobs_by_pk && data.jobs_by_pk.ro_number}
+
+ {` | ${OwnerNameDisplayFunction(data.jobs_by_pk)} | ${
+ data.jobs_by_pk.v_model_yr || ""
+ } ${data.jobs_by_pk.v_make_desc || ""} ${data.jobs_by_pk.v_model_desc || ""}`}
+
+ }
+ socket={activeSocket}
+ jobId={jobId}
+ />
+ )}
@@ -397,13 +410,18 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
title={t("jobs.labels.dms.logs")}
extra={
-
-
+ {isRrMode && (
+ <>
+
+
+ >
+ )}
+