From 6671db1724e209aa1213dbb176f88dac62395f3c Mon Sep 17 00:00:00 2001 From: Dave Date: Tue, 14 Oct 2025 14:26:15 -0400 Subject: [PATCH] feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint --- .../dms-allocations-summary.component.jsx | 31 ++-- .../dms-customer-selector.component.jsx | 136 +++++++++++++---- .../dms-post-form/dms-post-form.component.jsx | 48 +++--- client/src/pages/dms/dms.container.jsx | 142 +++++++++++------- 4 files changed, 242 insertions(+), 115 deletions(-) diff --git a/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx b/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx index 1cd3281f7..ca8814ae9 100644 --- a/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx +++ b/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx @@ -9,6 +9,7 @@ import { SyncOutlined } from "@ant-design/icons"; import { pageLimit } from "../../utils/config"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { useSocket } from "../../contexts/SocketIO/useSocket"; +import { determineDmsType } from "../../utils/determineDMSType"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser @@ -33,13 +34,23 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) { }); const { socket: wsssocket } = useSocket(); - useEffect(() => { - if (Fortellis.treatment === "on") { + const dms = determineDmsType(bodyshop); + + const fetchAllocations = () => { + // ✅ RR takes precedence over Fortellis + if (dms === "rr") { + wsssocket.emit("rr-calculate-allocations", jobId, (ack) => { + setAllocationsSummary(ack); + socket.allocationsSummary = ack; + }); + } else if (Fortellis.treatment === "on") { + // Fortellis path (unchanged) wsssocket.emit("fortellis-calculate-allocations", jobId, (ack) => { setAllocationsSummary(ack); socket.allocationsSummary = ack; }); } else { + // Default to CDK path if (socket.connected) { socket.emit("cdk-calculate-allocations", jobId, (ack) => { setAllocationsSummary(ack); @@ -47,7 +58,11 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) { }); } } - }, [socket, socket.connected, jobId]); + }; + + useEffect(() => { + fetchAllocations(); + }, [socket, socket.connected, jobId, dms, Fortellis?.treatment]); const columns = [ { @@ -91,15 +106,7 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) { { - if (Fortellis.treatment === "on") { - socket.emit("fortellis-calculate-allocations", jobId, (ack) => setAllocationsSummary(ack)); - } else { - socket.emit("cdk-calculate-allocations", jobId, (ack) => setAllocationsSummary(ack)); - } - }} - > + } 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 3da4ca38f..4ab9a6097 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 @@ -8,13 +8,12 @@ import { useSocket } from "../../contexts/SocketIO/useSocket"; import { socket } from "../../pages/dms/dms.container"; import { selectBodyshop } from "../../redux/user/user.selectors"; import { alphaSort } from "../../utils/sorters"; +import { determineDmsType } from "../../utils/determineDMSType"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -const mapDispatchToProps = () => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); +const mapDispatchToProps = () => ({}); export default connect(mapStateToProps, mapDispatchToProps)(DmsCustomerSelector); export function DmsCustomerSelector({ bodyshop, jobid }) { @@ -34,27 +33,43 @@ export function DmsCustomerSelector({ bodyshop, jobid }) { const { socket: wsssocket } = useSocket(); + const dms = determineDmsType(bodyshop); + const bodyshopId = bodyshop?.id || bodyshop?.bodyshopid || bodyshop?.uuid; + useEffect(() => { + // ✅ RR takes precedence over Fortellis + if (dms === "rr") { + const handleRrSelectCustomer = (list) => { + setOpen(true); + setDmsType("rr"); + setcustomerList(Array.isArray(list) ? list : []); + }; + wsssocket.on("rr-select-customer", handleRrSelectCustomer); + return () => { + wsssocket.off("rr-select-customer", handleRrSelectCustomer); + }; + } + if (Fortellis.treatment === "on") { - const handleFortellisSelectCustomer = (customerList) => { + const handleFortellisSelectCustomer = (list) => { setOpen(true); setDmsType("cdk"); - setcustomerList(customerList); + setcustomerList(Array.isArray(list) ? list : []); }; wsssocket.on("fortellis-select-customer", handleFortellisSelectCustomer); return () => { wsssocket.off("fortellis-select-customer", handleFortellisSelectCustomer); }; } else { - const handleCdkSelectCustomer = (customerList) => { + const handleCdkSelectCustomer = (list) => { setOpen(true); setDmsType("cdk"); - setcustomerList(customerList); + setcustomerList(Array.isArray(list) ? list : []); }; - const handlePbsSelectCustomer = (customerList) => { + const handlePbsSelectCustomer = (list) => { setOpen(true); setDmsType("pbs"); - setcustomerList(customerList); + setcustomerList(Array.isArray(list) ? list : []); }; socket.on("cdk-select-customer", handleCdkSelectCustomer); socket.on("pbs-select-customer", handlePbsSelectCustomer); @@ -63,12 +78,14 @@ export function DmsCustomerSelector({ bodyshop, jobid }) { socket.off("pbs-select-customer", handlePbsSelectCustomer); }; } - }, []); + }, [dms, Fortellis?.treatment, wsssocket]); const onUseSelected = () => { setOpen(false); - if (Fortellis.treatment === "on") { - wsssocket.emit(`fortellis-selected-customer`, { selectedCustomerId: selectedCustomer, jobid }); + if (dmsType === "rr") { + wsssocket.emit("rr-selected-customer", { bodyshopId, selectedCustomerId: selectedCustomer, jobid }); + } else if (Fortellis.treatment === "on") { + wsssocket.emit("fortellis-selected-customer", { selectedCustomerId: selectedCustomer, jobid }); } else { socket.emit(`${dmsType}-selected-customer`, selectedCustomer); } @@ -77,23 +94,24 @@ export function DmsCustomerSelector({ bodyshop, jobid }) { const onUseGeneric = () => { setOpen(false); + const generic = bodyshop.cdk_configuration?.generic_customer_number || null; - if (Fortellis.treatment === "on") { - wsssocket.emit(`fortellis-selected-customer`, { - selectedCustomerId: bodyshop.cdk_configuration.generic_customer_number, - jobid - }); + if (dmsType === "rr") { + wsssocket.emit("rr-selected-customer", { bodyshopId, selectedCustomerId: generic, jobid }); + } else if (Fortellis.treatment === "on") { + wsssocket.emit("fortellis-selected-customer", { selectedCustomerId: generic, jobid }); } else { - socket.emit(`${dmsType}-selected-customer`, bodyshop.cdk_configuration.generic_customer_number); + socket.emit(`${dmsType}-selected-customer`, generic); } setSelectedCustomer(null); }; const onCreateNew = () => { setOpen(false); - - if (Fortellis.treatment === "on") { - wsssocket.emit(`fortellis-selected-customer`, { selectedCustomerId: null, jobid }); + if (dmsType === "rr") { + wsssocket.emit("rr-selected-customer", { bodyshopId, selectedCustomerId: null, jobid }); + } else if (Fortellis.treatment === "on") { + wsssocket.emit("fortellis-selected-customer", { selectedCustomerId: null, jobid }); } else { socket.emit(`${dmsType}-selected-customer`, null); } @@ -126,13 +144,13 @@ export function DmsCustomerSelector({ bodyshop, jobid }) { }, { title: t("jobs.fields.dms.address"), - key: "address", render: (record) => - `${record.postalAddress?.addressLine1} ${record.postalAddress?.addressLine2 ? `, ${record.postalAddress?.addressLine2}` : ""}, - ${record.postalAddress?.city} ${record.postalAddress?.state} ${record.postalAddress?.postalCode} ${ - record.postalAddress?.country - }` + `${record.postalAddress?.addressLine1 || ""}${ + record.postalAddress?.addressLine2 ? `, ${record.postalAddress.addressLine2}` : "" + }, ${record.postalAddress?.city || ""} ${record.postalAddress?.state || ""} ${ + record.postalAddress?.postalCode || "" + } ${record.postalAddress?.country || ""}` } ]; @@ -154,10 +172,8 @@ export function DmsCustomerSelector({ bodyshop, jobid }) { key: "name1", sorter: (a, b) => alphaSort(a.name1?.fullName, b.name1?.fullName) }, - { title: t("jobs.fields.dms.address"), - //dataIndex: ["name2", "fullName"], key: "address", render: (record) => `${record.address?.addressLine && record.address.addressLine[0]}, ${record.address?.city} ${ @@ -178,7 +194,6 @@ export function DmsCustomerSelector({ bodyshop, jobid }) { sorter: (a, b) => alphaSort(a.LastName, b.LastName), render: (text, record) => `${record.FirstName || ""} ${record.LastName || ""}` }, - { title: t("jobs.fields.dms.address"), key: "address", @@ -186,7 +201,57 @@ export function DmsCustomerSelector({ bodyshop, jobid }) { } ]; + // NEW: RR columns (aligned with RR CombinedSearch-style payloads; falls back gracefully) + const rrColumns = [ + { + title: t("jobs.fields.dms.id"), + dataIndex: "CustomerId", + key: "CustomerId" + }, + { + title: t("jobs.fields.dms.name1"), + key: "CustomerName", + sorter: (a, b) => + alphaSort( + (a.CustomerName?.FirstName || "") + " " + (a.CustomerName?.LastName || ""), + (b.CustomerName?.FirstName || "") + " " + (b.CustomerName?.LastName || "") + ), + render: (record) => `${record.CustomerName?.FirstName || ""} ${record.CustomerName?.LastName || ""}`.trim() + }, + { + title: t("jobs.fields.dms.address"), + key: "Address", + render: (record) => { + const a = record.PostalAddress || record.Address || {}; + const l1 = a.AddressLine1 || a.Line1 || ""; + const l2 = a.AddressLine2 || a.Line2 || ""; + const city = a.City || ""; + const st = a.State || a.StateProvince || ""; + const pc = a.PostalCode || ""; + const ctry = a.Country || ""; + return `${l1}${l2 ? `, ${l2}` : ""}, ${city} ${st} ${pc} ${ctry}`.trim(); + } + } + ]; + if (!open) return null; + + const columns = + dmsType === "rr" + ? rrColumns + : dmsType === "cdk" + ? Fortellis.treatment === "on" + ? fortellisColumns + : cdkColumns + : pbsColumns; + + const rowKeyFn = + dmsType === "rr" + ? (record) => record.CustomerId || record.customerId + : dmsType === "cdk" + ? (record) => record.id?.value || record.customerId + : (record) => record.ContactId; + return ( )} pagination={{ position: "top" }} - columns={dmsType === "cdk" ? (Fortellis.treatment === "on" ? fortellisColumns : cdkColumns) : pbsColumns} - rowKey={(record) => (dmsType === "cdk" ? record.id?.value || record.customerId : record.ContactId)} + columns={columns} + rowKey={rowKeyFn} dataSource={customerList} - //onChange={handleTableChange} rowSelection={{ onSelect: (record) => { - setSelectedCustomer(dmsType === "cdk" ? record.id?.value || record.customerId : record.ContactId); + const key = + dmsType === "rr" + ? record.CustomerId || record.customerId + : dmsType === "cdk" + ? record.id?.value || record.customerId + : record.ContactId; + setSelectedCustomer(key); }, type: "radio", selectedRowKeys: [selectedCustomer] diff --git a/client/src/components/dms-post-form/dms-post-form.component.jsx b/client/src/components/dms-post-form/dms-post-form.component.jsx index 127dc4a4b..7b4b60750 100644 --- a/client/src/components/dms-post-form/dms-post-form.component.jsx +++ b/client/src/components/dms-post-form/dms-post-form.component.jsx @@ -62,35 +62,45 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) { return { ...cdkPayer, dms_acctnumber: cdkPayer.dms_acctnumber, - controlnumber: job && job[cdkPayer.control_type] + controlnumber: job?.[cdkPayer.control_type] }; }) }); }; const handleFinish = (values) => { - //TODO: Add this as a split instead. - if (Fortellis.treatment === "on") { - wsssocket.emit("fortellis-export-job", { - jobid: job.id, - txEnvelope: { - ...values, - SubscriptionID: bodyshop.cdk_dealerid - } - }); - } else { - socket.emit(`${determineDmsType(bodyshop)}-export-job`, { + const dms = determineDmsType(bodyshop); + + // 1) RR takes precedence regardless of Fortellis split + if (dms === "rr") { + wsssocket.emit("rr-export-job", { + bodyshopId: bodyshop?.id || bodyshop?.bodyshopid || bodyshop?.uuid, jobid: job.id, + job, txEnvelope: values }); - } - console.log(logsRef); - if (logsRef) { - console.log("executing", logsRef); - logsRef.curent && - logsRef.current.scrollIntoView({ - behavior: "smooth" + } else { + // 2) Fallback to existing behavior + // TODO: Add this as a split instead. + if (Fortellis.treatment === "on") { + wsssocket.emit("fortellis-export-job", { + jobid: job.id, + txEnvelope: { + ...values, + SubscriptionID: bodyshop.cdk_dealerid + } }); + } else { + socket.emit(`${dms}-export-job`, { + jobid: job.id, + txEnvelope: values + }); + } + } + + // Keep existing auto-scroll-to-logs behavior (fixing "curent" typo) + if (logsRef?.current) { + logsRef.current.scrollIntoView({ behavior: "smooth" }); } }; diff --git a/client/src/pages/dms/dms.container.jsx b/client/src/pages/dms/dms.container.jsx index 60f710dab..409e52980 100644 --- a/client/src/pages/dms/dms.container.jsx +++ b/client/src/pages/dms/dms.container.jsx @@ -37,21 +37,20 @@ const mapDispatchToProps = (dispatch) => ({ export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer); -export const socket = SocketIO( - import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "", // for dev testing, - { - path: "/ws", - withCredentials: true, - auth: async (callback) => { - const token = auth.currentUser && (await auth.currentUser.getIdToken()); - callback({ token }); - } +// Legacy /ws socket (CDK/PBS) +export const socket = SocketIO(import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "", { + path: "/ws", + withCredentials: true, + auth: async (callback) => { + const token = auth.currentUser && (await auth.currentUser.getIdToken()); + callback({ token }); } -); +}); export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, insertAuditTrail }) { const { t } = useTranslation(); - const [logLevel, setLogLevel] = useState(determineDmsType(bodyshop) === "pbs" ? "INFO" : "DEBUG"); + const dms = determineDmsType(bodyshop); + const [logLevel, setLogLevel] = useState(dms === "pbs" ? "INFO" : "DEBUG"); const history = useNavigate(); const [logs, setLogs] = useState([]); const search = queryString.parse(useLocation().search); @@ -64,6 +63,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse names: ["Fortellis"], splitKey: bodyshop.imexshopid }); + // New unified wss socket (Fortellis, RR) const { socket: wsssocket } = useSocket(); const { loading, error, data } = useQuery(QUERY_JOB_EXPORT_DMS, { @@ -95,19 +95,59 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse }, [t, setBreadcrumbs, setSelectedHeader]); useEffect(() => { + // ✅ RR uses the new wss socket and takes precedence over Fortellis flag + if (dms === "rr") { + // set log level on connect and immediately + wsssocket.emit("set-log-level", logLevel); + const handleConnect = () => wsssocket.emit("set-log-level", logLevel); + const handleReconnect = () => + setLogs((prev) => [ + ...prev, + { timestamp: new Date(), level: "warn", message: "Reconnected to RR Export Service" } + ]); + const handleConnectError = (err) => { + console.log(`connect_error due to ${err}`, err); + notification.error({ message: err.message }); + }; + + const handleLogEvent = (payload) => setLogs((prev) => [...prev, payload]); + const handleExportSuccess = (payload) => { + notification.success({ message: t("jobs.successes.exported") }); + insertAuditTrail({ + jobid: payload, + operation: AuditTrailMapping.jobexported(), + type: "jobexported" + }); + history("/manage/accounting/receivables"); + }; + const handleRrExportResult = (payload) => handleExportSuccess(payload); + + wsssocket.on("connect", handleConnect); + wsssocket.on("reconnect", handleReconnect); + wsssocket.on("connect_error", handleConnectError); + + // RR channels (over wss) + wsssocket.on("rr-log-event", handleLogEvent); + wsssocket.on("export-success", handleExportSuccess); + wsssocket.on("rr-export-job:result", handleRrExportResult); + + return () => { + wsssocket.off("connect", handleConnect); + wsssocket.off("reconnect", handleReconnect); + wsssocket.off("connect_error", handleConnectError); + wsssocket.off("rr-log-event", handleLogEvent); + wsssocket.off("export-success", handleExportSuccess); + wsssocket.off("rr-export-job:result", handleRrExportResult); + }; + } + + // Fortellis / CDK behavior (when not RR) if (Fortellis.treatment === "on") { wsssocket.emit("set-log-level", logLevel); - const handleLogEvent = (payload) => { - setLogs((logs) => { - return [...logs, payload]; - }); - }; - + const handleLogEvent = (payload) => setLogs((prev) => [...prev, payload]); const handleExportSuccess = (payload) => { - notification.success({ - message: t("jobs.successes.exported") - }); + notification.success({ message: t("jobs.successes.exported") }); insertAuditTrail({ jobid: payload, operation: AuditTrailMapping.jobexported(), @@ -116,6 +156,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse history("/manage/accounting/receivables"); }; + // Fortellis logs (wss) wsssocket.on("fortellis-log-event", handleLogEvent); wsssocket.on("export-success", handleExportSuccess); @@ -124,32 +165,21 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse wsssocket.off("export-success", handleExportSuccess); }; } else { + // CDK/PBS via legacy /ws socket socket.on("connect", () => socket.emit("set-log-level", logLevel)); socket.on("reconnect", () => { - setLogs((logs) => { - return [ - ...logs, - { - timestamp: new Date(), - level: "warn", - message: "Reconnected to CDK Export Service" - } - ]; - }); + setLogs((prev) => [ + ...prev, + { timestamp: new Date(), level: "warn", message: "Reconnected to CDK Export Service" } + ]); }); socket.on("connect_error", (err) => { console.log(`connect_error due to ${err}`, err); notification.error({ message: err.message }); }); - socket.on("log-event", (payload) => { - setLogs((logs) => { - return [...logs, payload]; - }); - }); + socket.on("log-event", (payload) => setLogs((prev) => [...prev, payload])); socket.on("export-success", (payload) => { - notification.success({ - message: t("jobs.successes.exported") - }); + notification.success({ message: t("jobs.successes.exported") }); insertAuditTrail({ jobid: payload, operation: AuditTrailMapping.jobexported(), @@ -164,7 +194,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse socket.disconnect(); }; } - }, []); + }, [dms, Fortellis?.treatment, logLevel, history, insertAuditTrail, notification, t, wsssocket]); if (loading) return ; if (error) return ; @@ -176,15 +206,14 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse return (
- {Fortellis.treatment === "on" && ( - - )} + +
{ setLogLevel(value); - socket.emit("set-log-level", value); + // Send to the active socket type + if (dms === "rr" || Fortellis.treatment === "on") { + wsssocket.emit("set-log-level", value); + } else { + socket.emit("set-log-level", value); + } }} > DEBUG @@ -231,8 +265,14 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse