feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Refresh button inside VIN Enforced, add TOo Many OPen RO Handlers
This commit is contained in:
@@ -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 }) {
|
||||
<Table
|
||||
title={() => (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
{/* Open RO limit banner (from parent flag) */}
|
||||
{dmsType === "rr" && rrOpenRoLimit && (
|
||||
<Alert
|
||||
type="error"
|
||||
showIcon
|
||||
message="Open RO limit reached in Reynolds"
|
||||
description={
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
<div>
|
||||
Reynolds has reached the maximum number of open Repair Orders for this Customer. Close or finalize
|
||||
an RO in Reynolds, then click <strong>Finished</strong> to continue.
|
||||
</div>
|
||||
<div>
|
||||
<Button type="primary" danger onClick={onRrOpenRoFinished}>
|
||||
Finished
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||
<Button onClick={onUseSelected} disabled={!selectedCustomer}>
|
||||
<Button onClick={onUseSelected} disabled={!selectedCustomer || (dmsType === "rr" && rrOpenRoLimit)}>
|
||||
{t("jobs.actions.dms.useselected")}
|
||||
</Button>
|
||||
|
||||
@@ -359,12 +405,24 @@ export function DmsCustomerSelector({ bodyshop, jobid }) {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* NEW: VIN ownership enforced with Refresh */}
|
||||
{dmsType === "rr" && rrHasVinOwner && (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="VIN ownership enforced"
|
||||
description="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."
|
||||
description={
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12 }}>
|
||||
<div>
|
||||
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.
|
||||
</div>
|
||||
<Button onClick={refreshRrSearch} loading={refreshing}>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
</Col>
|
||||
<Col md={24} lg={14}>
|
||||
<DmsPostForm socket={activeSocket} jobId={jobId} job={data?.jobs_by_pk} logsRef={logsRef} />
|
||||
<DmsPostForm socket={activeSocket} job={data?.jobs_by_pk} logsRef={logsRef} />
|
||||
</Col>
|
||||
|
||||
<DmsCustomerSelector jobid={jobId} />
|
||||
|
||||
<DmsCustomerSelector jobid={jobId} rrOpenRoLimit={rrOpenRoLimit} onRrOpenRoFinished={clearRrOpenRoLimit} />
|
||||
<Col span={24}>
|
||||
<div ref={logsRef}>
|
||||
<Card
|
||||
@@ -316,9 +328,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
<Button
|
||||
onClick={() => {
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user