From fa250f10a20a1aa83d7d0cf8ab9fe8cb0338fe90 Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 7 Nov 2025 15:28:23 -0500 Subject: [PATCH] feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - rr-Utils hardening --- .../dms-customer-selector.component.jsx | 42 +++++++- server/rr/rr-utils.js | 101 ++++++++++++++---- 2 files changed, 117 insertions(+), 26 deletions(-) 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 dcfbad6d4..4ae308f57 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 @@ -28,11 +28,38 @@ function normalizeRrList(list) { (custNo ? String(custNo) : ""); if (!custNo) return null; const vinOwner = !!(row.vinOwner ?? row.isVehicleOwner); - return { custNo: String(custNo), name, vinOwner }; + + // Pass through address from backend if present; tolerate various shapes + const address = + row.address && typeof row.address === "object" + ? { + line1: row.address.line1 ?? row.address.addr1 ?? row.address.Address1 ?? undefined, + line2: row.address.line2 ?? row.address.addr2 ?? row.address.Address2 ?? undefined, + city: row.address.city ?? undefined, + state: row.address.state ?? row.address.stateOrProvince ?? undefined, + postalCode: row.address.postalCode ?? row.address.zip ?? undefined, + country: row.address.country ?? row.address.countryCode ?? undefined + } + : undefined; + + return { custNo: String(custNo), name, vinOwner, address }; }) .filter(Boolean); } +// Small formatter used by the RR Address column render +function rrAddressToString(addr) { + if (!addr) return ""; + const parts = [ + addr.line1, + addr.line2, + [addr.city, addr.state].filter(Boolean).join(" "), + addr.postalCode, + addr.country + ].filter(Boolean); + return parts.join(", "); +} + export function DmsCustomerSelector({ bodyshop, jobid }) { const { t } = useTranslation(); const [customerList, setcustomerList] = useState([]); @@ -278,6 +305,12 @@ export function DmsCustomerSelector({ bodyshop, jobid }) { const rrColumns = [ { title: t("jobs.fields.dms.id"), dataIndex: "custNo", key: "custNo" }, + { + title: t("jobs.fields.dms.vinowner"), + dataIndex: "vinOwner", + key: "vinOwner", + render: (_t, r) => + }, { title: t("jobs.fields.dms.name1"), dataIndex: "name", @@ -285,10 +318,9 @@ export function DmsCustomerSelector({ bodyshop, jobid }) { sorter: (a, b) => alphaSort(a?.name, b?.name) }, { - title: t("jobs.fields.dms.vinowner"), - dataIndex: "vinOwner", - key: "vinOwner", - render: (_t, r) => + title: t("jobs.fields.dms.address"), + key: "address", + render: (record) => rrAddressToString(record.address) } ]; diff --git a/server/rr/rr-utils.js b/server/rr/rr-utils.js index 0569b20f7..b6f73cdf4 100644 --- a/server/rr/rr-utils.js +++ b/server/rr/rr-utils.js @@ -45,8 +45,7 @@ const makeVehicleSearchPayloadFromJob = (job) => { }; /** - * Normalize customer candidates from VIN blocks - * Adds `vinOwner` (and keeps `isVehicleOwner` for backward compat). + * Normalize customer candidates from VIN/name blocks, including address + owner flag * @param res * @param ownersSet * @returns {any[]} @@ -54,6 +53,51 @@ const makeVehicleSearchPayloadFromJob = (job) => { const normalizeCustomerCandidates = (res, { ownersSet = null } = {}) => { const blocks = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : []; const out = []; + + const pickAddr = (addrArr) => { + const arr = Array.isArray(addrArr) ? addrArr : addrArr ? [addrArr] : []; + if (!arr.length) return null; + + const chosen = arr.find((a) => (a?.Type || a?.type || "").toString().toUpperCase() === "P") || arr[0]; + + // NEW: include County + const line1 = chosen?.Addr1 ?? chosen?.AddressLine1 ?? chosen?.Line1 ?? chosen?.Street1 ?? undefined; + const line2 = chosen?.Addr2 ?? chosen?.AddressLine2 ?? chosen?.Line2 ?? chosen?.Street2 ?? undefined; + const city = chosen?.City ?? chosen?.city ?? undefined; + const state = chosen?.State ?? chosen?.StateOrProvince ?? chosen?.state ?? undefined; + const postalCode = chosen?.Zip ?? chosen?.PostalCode ?? chosen?.zip ?? undefined; + const country = chosen?.Country ?? chosen?.CountryCode ?? chosen?.country ?? undefined; + const county = chosen?.County ?? chosen?.county ?? undefined; // << added + + // instrumentation (kept minimal; County is now expected) + if ((process.env.RR_DEBUG_ADDR ?? "1") !== "0") { + const allowed = new Set([ + "Type", + "Addr1", + "AddressLine1", + "Line1", + "Street1", + "Addr2", + "AddressLine2", + "Line2", + "Street2", + "City", + "State", + "StateOrProvince", + "Zip", + "PostalCode", + "Country", + "CountryCode", + "County" // << allow County + ]); + const unknown = Object.keys(chosen || {}).filter((k) => !allowed.has(k)); + if (unknown.length) console.log("[RR:normCandidates] Unexpected address keys seen:", unknown); + } + + if (!line1 && !city && !state && !postalCode && !country && !county) return null; + return { line1, line2, city, state, postalCode, country, county }; + }; + for (const blk of blocks) { const serv = Array.isArray(blk?.ServVehicle) ? blk.ServVehicle : []; const custNos = serv.map((sv) => sv?.VehicleServInfo?.CustomerNo).filter(Boolean); @@ -61,35 +105,49 @@ const normalizeCustomerCandidates = (res, { ownersSet = null } = {}) => { const nci = blk?.NameContactId; const ind = nci?.NameId?.IndName; const bus = nci?.NameId?.BusName; - const personal = [ind?.FirstName || ind?.FName, ind?.LastName || ind?.LName].filter(Boolean).join(" ").trim(); - const company = bus?.CompanyName || bus?.BName; + + const personal = [ind?.FirstName ?? ind?.FName, ind?.LastName ?? ind?.LName].filter(Boolean).join(" ").trim(); + const company = bus?.CompanyName ?? bus?.BName; const name = (personal || company || "").trim(); + const address = pickAddr(nci?.Address); + for (const custNo of custNos) { const cno = String(custNo).trim(); - const isOwner = !!(ownersSet && ownersSet.has(cno)); + if (!cno) continue; + const item = { custNo: cno, name: name || `Customer ${cno}`, - vinOwner: isOwner, - isVehicleOwner: isOwner // legacy key kept for any older FE code + address: address || undefined }; + + if (ownersSet && ownersSet.has(cno)) { + item.isVehicleOwner = true; + item.vinOwner = true; + } + out.push(item); } } - // Dedup by custNo, keep vinOwner/isVehicleOwner if any - const seen = new Map(); + + const byId = new Map(); for (const c of out) { const key = (c.custNo || "").trim(); if (!key) continue; - const prev = seen.get(key); - if (!prev) { - seen.set(key, c); - } else if ((c.vinOwner || c.isVehicleOwner) && !(prev.vinOwner || prev.isVehicleOwner)) { - seen.set(key, { ...prev, vinOwner: true, isVehicleOwner: true }); + const prev = byId.get(key); + if (!prev) byId.set(key, c); + else { + byId.set(key, { + ...prev, + isVehicleOwner: prev.isVehicleOwner || c.isVehicleOwner, + vinOwner: prev.vinOwner || c.vinOwner, + address: prev.address || c.address + }); } } - return Array.from(seen.values()); + + return Array.from(byId.values()); }; /** @@ -99,12 +157,13 @@ const normalizeCustomerCandidates = (res, { ownersSet = null } = {}) => { * @returns {string|null} */ const readAdvisorNo = (payload, cached) => { - const v = - (payload?.txEnvelope?.advisorNo != null && String(payload.txEnvelope.advisorNo)) || - (payload?.advisorNo != null && String(payload.advisorNo)) || - (cached != null && String(cached)) || - null; - return v && v.trim() !== "" ? v : null; + const tx = payload?.txEnvelope || payload?.envelope || {}; + + const get = (v) => (v != null && String(v).trim() !== "" ? String(v).trim() : null); + + let value = get(tx?.advisorNo) || get(payload?.advisorNo) || get(cached) || null; + + return value; }; /**