diff --git a/client/src/components/dms-customer-selector/dms-customer-selector.component.jsx b/client/src/components/dms-customer-selector/dms-customer-selector.component.jsx
index 7e731d559..225332756 100644
--- a/client/src/components/dms-customer-selector/dms-customer-selector.component.jsx
+++ b/client/src/components/dms-customer-selector/dms-customer-selector.component.jsx
@@ -58,12 +58,13 @@ function rrAddressToString(addr) {
return parts.join(", ");
}
-export function DmsCustomerSelector({ bodyshop, jobid }) {
+export function DmsCustomerSelector({ bodyshop, jobid, rrOpenRoLimit = false, onRrOpenRoFinished }) {
const { t } = useTranslation();
const [customerList, setcustomerList] = useState([]);
const [open, setOpen] = useState(false);
const [selectedCustomer, setSelectedCustomer] = useState(null);
const [dmsType, setDmsType] = useState("cdk");
+ const [refreshing, setRefreshing] = useState(false);
const {
treatments: { Fortellis }
@@ -95,6 +96,7 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
setcustomerList(normalized);
const firstOwner = normalized.find((r) => r.vinOwner)?.custNo;
setSelectedCustomer(firstOwner ? String(firstOwner) : null);
+ setRefreshing(false); // stop any in-flight refresh spinner
};
wsssocket.on("rr-select-customer", handleRrSelectCustomer);
@@ -183,7 +185,6 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
const generic = bodyshop.cdk_configuration?.generic_customer_number || null;
if (dmsType === "rr") {
- // Not rendered in RR anymore
return;
} else if (Fortellis.treatment === "on") {
setOpen(false);
@@ -220,7 +221,30 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
setSelectedCustomer(null);
};
- // ---------- Columns ----------
+ // NEW: trigger a re-run of the RR combined search
+ const refreshRrSearch = () => {
+ if (dmsType !== "rr") return;
+ setRefreshing(true);
+
+ // Safety timeout so the spinner can't hang forever
+ const to = setTimeout(() => {
+ setRefreshing(false);
+ }, 12000);
+
+ // Stop spinner on either outcome
+ const stop = () => {
+ clearTimeout(to);
+ setRefreshing(false);
+ wsssocket.off("export-failed", stop);
+ wsssocket.off("rr-select-customer", stop);
+ };
+ wsssocket.once("rr-select-customer", stop);
+ wsssocket.once("export-failed", stop);
+
+ // This re-runs the name+VIN multi-search and emits rr-select-customer
+ wsssocket.emit("rr-export-job", { jobId: jobid });
+ };
+
const fortellisColumns = [
{ title: t("jobs.fields.dms.id"), dataIndex: "customerId", key: "id" },
{
@@ -342,8 +366,30 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
(
+ {/* Open RO limit banner (from parent flag) */}
+ {dmsType === "rr" && rrOpenRoLimit && (
+
+
+ Reynolds has reached the maximum number of open Repair Orders for this Customer. Close or finalize
+ an RO in Reynolds, then click Finished to continue.
+
+
+
+ Finished
+
+
+
+ }
+ />
+ )}
+
-
+
{t("jobs.actions.dms.useselected")}
@@ -359,12 +405,24 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
+ {/* NEW: VIN ownership enforced with Refresh */}
{dmsType === "rr" && rrHasVinOwner && (
+
+ This VIN is already assigned in Reynolds. Only the VIN owner is selectable here. To use a
+ different customer, please change the vehicle ownership in Reynolds first, then return to complete
+ the export.
+
+
+ Refresh
+
+
+ }
/>
)}
diff --git a/client/src/pages/dms/dms.container.jsx b/client/src/pages/dms/dms.container.jsx
index 00d3fa586..03f159068 100644
--- a/client/src/pages/dms/dms.container.jsx
+++ b/client/src/pages/dms/dms.container.jsx
@@ -79,29 +79,41 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
});
const logsRef = useRef(null);
+ // NEW: RR “open RO limit” UX hold
+ const [rrOpenRoLimit, setRrOpenRoLimit] = useState(false);
+ const clearRrOpenRoLimit = () => setRrOpenRoLimit(false);
+
const handleExportFailed = (payload = {}) => {
const { title, friendlyMessage, error, severity, errorCode, vendorStatusCode } = payload;
- // Prefer server-provided nice text; otherwise generic fallback
const msg =
friendlyMessage ||
error ||
t("dms.errors.exportfailedgeneric", "We couldn't complete the export. Please try again.");
- // Title defaults by DMS
const vendorTitle = title || (dms === "rr" ? "Reynolds" : "DMS");
- // Severity: warn for known soft-stops like 507, else error
- const sev =
- severity || (vendorStatusCode === 507 || (errorCode || "").includes("MAX_OPEN_ROS") ? "warning" : "error");
+ // Detect the specific RR “max open ROs” case
+ const isRrOpenRoLimit =
+ dms === "rr" &&
+ (vendorStatusCode === 507 ||
+ /MAX_OPEN_ROS/i.test(String(errorCode || "")) ||
+ /maximum number of open repair orders/i.test(String(msg || "").toLowerCase()));
- const notifyKind = sev === "warning" && typeof notification.warning === "function" ? "warning" : "error";
+ // Soft/warn default for known cases
+ const sev = severity || (isRrOpenRoLimit ? "warning" : "error");
- notification[notifyKind]({
- message: vendorTitle,
- description: msg,
- duration: 10
- });
+ // Show toast for *other* failures; for the open RO limit, switch to blocking banner UX instead.
+ if (!isRrOpenRoLimit) {
+ const notifyKind = sev === "warning" && typeof notification.warning === "function" ? "warning" : "error";
+ notification[notifyKind]({
+ message: vendorTitle,
+ description: msg,
+ duration: 10
+ });
+ } else {
+ setRrOpenRoLimit(true);
+ }
// Mirror to the on-screen log card
setLogs((prev) => [
@@ -110,7 +122,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
timestamp: new Date(),
level: (sev || "error").toUpperCase(),
message: `${vendorTitle}: ${msg}`,
- meta: { errorCode, vendorStatusCode, raw: payload }
+ meta: { errorCode, vendorStatusCode, raw: payload, blockedByOpenRoLimit: !!isRrOpenRoLimit }
}
]);
};
@@ -154,7 +166,8 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
const handleLogEvent = (payload) => setLogs((prev) => [...prev, payload]);
const handleExportSuccess = (payload) => {
- const jobId = payload?.jobId ?? payload; // RR sends object; legacy sends raw id notification.success({ message: t("jobs.successes.exported") });
+ const jobId = payload?.jobId ?? payload; // RR sends object; legacy sends raw id
+ notification.success({ message: t("jobs.successes.exported") });
insertAuditTrail({
jobid: jobId,
operation: AuditTrailMapping.jobexported(),
@@ -283,11 +296,10 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
/>
-
+
-
-
+
{
setLogs([]);
- // Reconnect appropriate socket
if (dms === "rr" || Fortellis.treatment === "on") {
- // wsssocket is managed by provider; emit a ping
wsssocket.emit("set-log-level", logLevel);
} else {
socket.disconnect();