feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint

This commit is contained in:
Dave
2025-11-13 14:31:55 -05:00
parent 09ea6dff2b
commit 9c2c0b665d
9 changed files with 75 additions and 64 deletions

View File

@@ -37,8 +37,8 @@ export function DmsCustomerSelector(props) {
const rrProps = { const rrProps = {
rrOpenRoLimit: rrOptions.openRoLimit, rrOpenRoLimit: rrOptions.openRoLimit,
onRrOpenRoFinished: rrOptions.onOpenRoFinished, onRrOpenRoFinished: rrOptions.onOpenRoFinished,
rrCashierPending: rrOptions.cashierPending, rrValidationPending: rrOptions.validationPending,
onRrCashierFinished: rrOptions.onCashierFinished onValidationFinished: rrOptions.onValidationFinished
}; };
return <RRCustomerSelector {...base} {...rrProps} />; return <RRCustomerSelector {...base} {...rrProps} />;
} }

View File

@@ -49,8 +49,8 @@ export default function RRCustomerSelector({
socket, socket,
rrOpenRoLimit = false, rrOpenRoLimit = false,
onRrOpenRoFinished, onRrOpenRoFinished,
rrCashierPending = false, rrValidationPending = false,
onRrCashierFinished onValidationFinished
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -58,10 +58,10 @@ export default function RRCustomerSelector({
const [selectedCustomer, setSelectedCustomer] = useState(null); const [selectedCustomer, setSelectedCustomer] = useState(null);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
// Show dialog automatically when cashiering is pending // Show dialog automatically when validation is pending
useEffect(() => { useEffect(() => {
if (rrCashierPending) setOpen(true); if (rrValidationPending) setOpen(true);
}, [rrCashierPending]); }, [rrValidationPending]);
// Listen for RR customer selection list // Listen for RR customer selection list
useEffect(() => { useEffect(() => {
@@ -196,22 +196,21 @@ export default function RRCustomerSelector({
/> />
)} )}
{/* Cashiering step banner */} {/* Validation step banner */}
{rrCashierPending && ( {rrValidationPending && (
<Alert <Alert
type="info" type="info"
showIcon showIcon
message="Complete cashiering in Reynolds" message="Complete Validation in Reynolds"
description={ description={
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}> <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<div> <div>
We created the Repair Order in Reynolds. Please complete the cashiering/closeout steps in We created the Repair Order. Please validate the totals and taxes in the DMS system. When done,
Reynolds. When done, click <strong>Finished/Close</strong> to finalize and mark this export as click <strong>Finished</strong> to finalize and mark this export as complete.
complete.
</div> </div>
<div> <div>
<Space> <Space>
<Button type="primary" onClick={onRrCashierFinished}> <Button type="primary" onClick={onValidationFinished}>
Finished / Close Finished / Close
</Button> </Button>
</Space> </Space>

View File

@@ -43,7 +43,7 @@ const DMS_SOCKET_EVENTS = {
[DMS_MAP.reynolds]: { [DMS_MAP.reynolds]: {
log: "rr-log-event", log: "rr-log-event",
partialResult: "rr-export-job:result", partialResult: "rr-export-job:result",
cashierNeeded: "rr-cashiering-required", validationNeeded: "rr-validation-required",
exportSuccess: "export-success", exportSuccess: "export-success",
exportFailed: "export-failed" exportFailed: "export-failed"
}, },
@@ -104,7 +104,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
const [rrOpenRoLimit, setRrOpenRoLimit] = useState(false); const [rrOpenRoLimit, setRrOpenRoLimit] = useState(false);
const clearRrOpenRoLimit = () => setRrOpenRoLimit(false); const clearRrOpenRoLimit = () => setRrOpenRoLimit(false);
const [rrCashierPending, setRrCashierPending] = useState(false); const [rrValidationPending, setrrValidationPending] = useState(false);
const { loading, error, data } = useQuery(QUERY_JOB_EXPORT_DMS, { const { loading, error, data } = useQuery(QUERY_JOB_EXPORT_DMS, {
variables: { id: jobId }, variables: { id: jobId },
@@ -259,8 +259,8 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
const jobIdResolved = payload?.jobId ?? payload; const jobIdResolved = payload?.jobId ?? payload;
notification.success({ message: t("jobs.successes.exported") }); notification.success({ message: t("jobs.successes.exported") });
// Clear RR cashier flag if any // Clear RR Validation flag if any
setRrCashierPending(false); setrrValidationPending(false);
insertAuditTrail({ insertAuditTrail({
jobid: jobIdResolved, jobid: jobIdResolved,
@@ -276,40 +276,41 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
// RR-only extras // RR-only extras
const onPartialResult = () => { const onPartialResult = () => {
setRrCashierPending(true); setrrValidationPending(true);
setLogs((prev) => [ setLogs((prev) => [
...prev, ...prev,
{ {
timestamp: new Date(), timestamp: new Date(),
level: "INFO", level: "INFO",
message: message:
"Repair Order created in Reynolds. Complete cashiering in Reynolds, then click Finished/Close to finalize." "Repair Order created in Reynolds. Complete validation in Reynolds, then click Finished/Close to finalize."
} }
]); ]);
notification.info({ notification.info({
message: "Reynolds RO created", message: "Reynolds RO created",
description: description:
"Complete cashiering in Reynolds, then click Finished/Close to finalize and mark this export complete.", "Complete validation in Reynolds, then click Finished/Close to finalize and mark this export complete.",
duration: 8 duration: 8
}); });
}; };
const onCashierRequired = (payload) => { const onValidationRequired = (payload) => {
setRrCashierPending(true); setrrValidationPending(true);
setLogs((prev) => [ setLogs((prev) => [
...prev, ...prev,
{ {
timestamp: new Date(), timestamp: new Date(),
level: "INFO", level: "INFO",
message: message:
"Repair Order created in Reynolds. Complete cashiering in Reynolds, then click Finished/Close to finalize.", "Repair Order created in Reynolds. Complete validation in Reynolds, then click Finished/Close to finalize.",
meta: { payload } meta: { payload }
} }
]); ]);
}; };
if (mode === DMS_MAP.reynolds && channels.partialResult) activeSocket.on(channels.partialResult, onPartialResult); if (mode === DMS_MAP.reynolds && channels.partialResult) activeSocket.on(channels.partialResult, onPartialResult);
if (mode === DMS_MAP.reynolds && channels.cashierNeeded) activeSocket.on(channels.cashierNeeded, onCashierRequired); if (mode === DMS_MAP.reynolds && channels.validationNeeded)
activeSocket.on(channels.validationrNeeded, onValidationRequired);
return () => { return () => {
activeSocket.off("connect", onConnect); activeSocket.off("connect", onConnect);
@@ -323,8 +324,8 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
if (mode === DMS_MAP.reynolds && channels.partialResult) if (mode === DMS_MAP.reynolds && channels.partialResult)
activeSocket.off(channels.partialResult, onPartialResult); activeSocket.off(channels.partialResult, onPartialResult);
if (mode === DMS_MAP.reynolds && channels.cashierNeeded) if (mode === DMS_MAP.reynolds && channels.validationNeeded)
activeSocket.off(channels.cashierNeeded, onCashierRequired); activeSocket.off(channels.validationNeeded, onValidationRequired);
// Only tear down legacy socket listeners; don't disconnect WSS from here // Only tear down legacy socket listeners; don't disconnect WSS from here
if (!isWssMode(mode)) { if (!isWssMode(mode)) {
@@ -335,7 +336,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
}, [mode, activeSocket, channels, logLevel, notification, t, insertAuditTrail, history]); }, [mode, activeSocket, channels, logLevel, notification, t, insertAuditTrail, history]);
// RR finalize callback (unchanged public behavior) // RR finalize callback (unchanged public behavior)
const handleRrCashierFinished = () => { const handleRrValidationFinished = () => {
if (!jobId) return; if (!jobId) return;
if (!isWssMode(mode)) return; // RR is WSS-only if (!isWssMode(mode)) return; // RR is WSS-only
activeSocket.emit("rr-finalize-repair-order", { jobId }, (ack) => { activeSocket.emit("rr-finalize-repair-order", { jobId }, (ack) => {
@@ -385,8 +386,8 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
rrOptions={{ rrOptions={{
openRoLimit: rrOpenRoLimit, openRoLimit: rrOpenRoLimit,
onOpenRoFinished: clearRrOpenRoLimit, onOpenRoFinished: clearRrOpenRoLimit,
cashierPending: rrCashierPending, validationPending: rrValidationPending,
onCashierFinished: handleRrCashierFinished onValidationFinished: handleRrValidationFinished
}} }}
/> />

View File

@@ -1,6 +1,7 @@
const { RRClient } = require("./lib/index.cjs"); const { RRClient } = require("./lib/index.cjs");
const { getRRConfigFromBodyshop } = require("./rr-config"); const { getRRConfigFromBodyshop } = require("./rr-config");
const RRLogger = require("./rr-logger"); const RRLogger = require("./rr-logger");
const InstanceManager = require("../utils/instanceMgr").default;
/** /**
* Country code map for normalization * Country code map for normalization
@@ -189,7 +190,7 @@ const buildCustomerPayloadFromJob = (job, overrides = {}) => {
firstName: firstName || undefined, firstName: firstName || undefined,
lastName: lastName || undefined, lastName: lastName || undefined,
customerName: companyName || undefined, customerName: companyName || undefined,
createdBy: overrides.createdBy || "ImEX Online", createdBy: overrides.createdBy || InstanceManager({ imex: "ImEX Online", rome: "Rome Online" }),
customerType: overrides.customerType || "R", // Retail default customerType: overrides.customerType || "R", // Retail default
addresses, addresses,
phones, phones,

View File

@@ -122,6 +122,7 @@ const markRRExportSuccess = async ({ socket, jobId, job, bodyshop, result, metaE
const insertRRFailedExportLog = async ({ socket, jobId, job, bodyshop, error, classification, result }) => { const insertRRFailedExportLog = async ({ socket, jobId, job, bodyshop, error, classification, result }) => {
const endpoint = process.env.GRAPHQL_ENDPOINT; const endpoint = process.env.GRAPHQL_ENDPOINT;
if (!endpoint) throw new Error("GRAPHQL_ENDPOINT not configured"); if (!endpoint) throw new Error("GRAPHQL_ENDPOINT not configured");
const token = getAuthToken(socket); const token = getAuthToken(socket);
if (!token) throw new Error("Auth token missing on socket"); if (!token) throw new Error("Auth token missing on socket");

View File

@@ -72,14 +72,7 @@ const exportJobToRR = async (args) => {
const roStatus = data?.roStatus || null; const roStatus = data?.roStatus || null;
// Extract canonical roNo you'll need for finalize step // Extract canonical roNo you'll need for finalize step
const roNo = const roNo = data?.dmsRoNo ?? data?.outsdRoNo ?? roStatus?.dmsRoNo ?? null;
data?.dmsRoNo ??
data?.outsdRoNo ??
roStatus?.dmsRoNo ??
roStatus?.DMSRoNo ??
roStatus?.outsdRoNo ??
roStatus?.OutsdRoNo ??
null;
return { return {
success: rrRes?.success === true || roStatus?.status === "Success", success: rrRes?.success === true || roStatus?.status === "Success",

View File

@@ -194,10 +194,13 @@ const normalizeVehicleCandidates = (res) => {
for (const sv of serv) { for (const sv of serv) {
const v = sv?.Vehicle || {}; const v = sv?.Vehicle || {};
const vin = v?.Vin || v?.VIN || v?.vin; const vin = v?.Vin || v?.VIN || v?.vin;
if (!vin) continue; if (!vin) continue;
const year = v?.VehicleYr || v?.ModelYear || v?.Year; const year = v?.VehicleYr || v?.ModelYear || v?.Year;
const make = v?.VehicleMake || v?.MakeName || v?.Make; const make = v?.VehicleMake || v?.MakeName || v?.Make;
const model = v?.MdlNo || v?.ModelDesc || v?.Model; const model = v?.MdlNo || v?.ModelDesc || v?.Model;
const label = [year, make, model, vin].filter(Boolean).join(" "); const label = [year, make, model, vin].filter(Boolean).join(" ");
out.push({ vin, year, make, model, label, _blk: blk }); out.push({ vin, year, make, model, label, _blk: blk });
} }

View File

@@ -32,8 +32,7 @@ const ADVISORS_CACHE_TTL = 7 * 24 * 60 * 60; // seconds
* @param job * @param job
* @returns {*|null} * @returns {*|null}
*/ */
const resolveJobId = (explicit, payload, job) => const resolveJobId = (explicit, payload, job) => explicit || payload?.jobId || job?.id || null;
explicit || payload?.jobId || payload?.jobid || job?.id || job?.jobId || job?.jobid || null;
/** /**
* Resolve VIN from tx/job shapes * Resolve VIN from tx/job shapes
@@ -218,6 +217,7 @@ const registerRREvents = ({ socket, redisHelpers }) => {
try { try {
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket); const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
const bodyshop = await getBodyshopForSocket({ bodyshopId, socket }); const bodyshop = await getBodyshopForSocket({ bodyshopId, socket });
CreateRRLogEvent(socket, "DEBUG", "rr-lookup-combined: begin", { jobid, params }); CreateRRLogEvent(socket, "DEBUG", "rr-lookup-combined: begin", { jobid, params });
const res = await rrCombinedSearch(bodyshop, params || {}); const res = await rrCombinedSearch(bodyshop, params || {});
@@ -230,13 +230,14 @@ const registerRREvents = ({ socket, redisHelpers }) => {
const normalized = sortVehicleOwnerFirst(normalizeCustomerCandidates(res, { ownersSet })); const normalized = sortVehicleOwnerFirst(normalizeCustomerCandidates(res, { ownersSet }));
const rid = resolveJobId(jobid, { jobid }, null); const rid = resolveJobId(jobid, { jobid }, null);
const decorated = normalized.map((c) => (c.vinOwner != null ? c : { ...c, vinOwner: !!c.isVehicleOwner })); const decorated = normalized.map((c) => (c.vinOwner != null ? c : { ...c, vinOwner: !!c.isVehicleOwner }));
cb?.({ jobid: rid, data: decorated }); cb?.({ jobid: rid, data: decorated });
socket.emit("rr-select-customer", decorated); socket.emit("rr-select-customer", decorated);
CreateRRLogEvent(socket, "DEBUG", "rr-lookup-combined: emitted rr-select-customer", { CreateRRLogEvent(socket, "DEBUG", "rr-lookup-combined: emitted rr-select-customer", {
count: decorated.length count: decorated.length,
res
}); });
} catch (e) { } catch (e) {
CreateRRLogEvent(socket, "ERROR", "RR combined lookup error", { error: e.message, jobid }); CreateRRLogEvent(socket, "ERROR", "RR combined lookup error", { error: e.message, jobid });
@@ -320,8 +321,10 @@ const registerRREvents = ({ socket, redisHelpers }) => {
socket.on("rr-export-job", async ({ jobid, jobId, txEnvelope } = {}) => { socket.on("rr-export-job", async ({ jobid, jobId, txEnvelope } = {}) => {
const rid = resolveJobId(jobid || jobId, { jobId, jobid }, null); const rid = resolveJobId(jobid || jobId, { jobId, jobid }, null);
try { try {
if (!rid) throw new Error("RR export: jobid required"); if (!rid) throw new Error("RR export: jobid required");
CreateRRLogEvent(socket, "DEBUG", `{1} Received RR export request`, { jobid: rid }); CreateRRLogEvent(socket, "DEBUG", `{1} Received RR export request`, { jobid: rid });
await redisHelpers.setSessionTransactionData( await redisHelpers.setSessionTransactionData(
@@ -331,6 +334,7 @@ const registerRREvents = ({ socket, redisHelpers }) => {
txEnvelope || {}, txEnvelope || {},
defaultRRTTL defaultRRTTL
); );
CreateRRLogEvent(socket, "DEBUG", `{1.1} Cached txEnvelope`, { hasTxEnvelope: !!txEnvelope }); CreateRRLogEvent(socket, "DEBUG", `{1.1} Cached txEnvelope`, { hasTxEnvelope: !!txEnvelope });
const job = await QueryJobData({ redisHelpers }, rid); const job = await QueryJobData({ redisHelpers }, rid);
@@ -341,12 +345,14 @@ const registerRREvents = ({ socket, redisHelpers }) => {
job, job,
defaultRRTTL defaultRRTTL
); );
CreateRRLogEvent(socket, "DEBUG", `{1.2} Cached JobData`, { vin: job?.v_vin, ro: job?.ro_number }); CreateRRLogEvent(socket, "DEBUG", `{1.2} Cached JobData`, { vin: job?.v_vin, ro: job?.ro_number });
const adv = readAdvisorNo( const adv = readAdvisorNo(
{ txEnvelope }, { txEnvelope },
await redisHelpers.getSessionTransactionData(socket.id, getTransactionType(rid), RRCacheEnums.AdvisorNo) await redisHelpers.getSessionTransactionData(socket.id, getTransactionType(rid), RRCacheEnums.AdvisorNo)
); );
if (adv) { if (adv) {
await redisHelpers.setSessionTransactionData( await redisHelpers.setSessionTransactionData(
socket.id, socket.id,
@@ -355,6 +361,7 @@ const registerRREvents = ({ socket, redisHelpers }) => {
String(adv), String(adv),
defaultRRTTL defaultRRTTL
); );
CreateRRLogEvent(socket, "DEBUG", `{1.3} Cached advisorNo`, { advisorNo: String(adv) }); CreateRRLogEvent(socket, "DEBUG", `{1.3} Cached advisorNo`, { advisorNo: String(adv) });
} }
@@ -362,9 +369,10 @@ const registerRREvents = ({ socket, redisHelpers }) => {
const bodyshop = await getBodyshopForSocket({ bodyshopId, socket }); const bodyshop = await getBodyshopForSocket({ bodyshopId, socket });
CreateRRLogEvent(socket, "DEBUG", `{2} Running multi-search (Full Name + VIN)`); CreateRRLogEvent(socket, "DEBUG", `{2} Running multi-search (Full Name + VIN)`);
const candidates = await rrMultiCustomerSearch({ bodyshop, job, socket, redisHelpers });
const candidates = await rrMultiCustomerSearch({ bodyshop, job, socket, redisHelpers });
const decorated = candidates.map((c) => (c.vinOwner != null ? c : { ...c, vinOwner: !!c.isVehicleOwner })); const decorated = candidates.map((c) => (c.vinOwner != null ? c : { ...c, vinOwner: !!c.isVehicleOwner }));
socket.emit("rr-select-customer", decorated); socket.emit("rr-select-customer", decorated);
CreateRRLogEvent(socket, "DEBUG", `{2.1} Emitted rr-select-customer`, { CreateRRLogEvent(socket, "DEBUG", `{2.1} Emitted rr-select-customer`, {
count: decorated.length, count: decorated.length,
@@ -376,6 +384,7 @@ const registerRREvents = ({ socket, redisHelpers }) => {
stack: error.stack, stack: error.stack,
jobid: rid jobid: rid
}); });
try { try {
socket.emit("export-failed", { vendor: "rr", jobId: rid, error: error.message }); socket.emit("export-failed", { vendor: "rr", jobId: rid, error: error.message });
} catch { } catch {
@@ -399,24 +408,31 @@ const registerRREvents = ({ socket, redisHelpers }) => {
}); });
const ns = getTransactionType(rid); const ns = getTransactionType(rid);
let selectedCustNo = let selectedCustNo =
(custNo && String(custNo)) || (custNo && String(custNo)) ||
(selectedCustomerId && String(selectedCustomerId)) || (selectedCustomerId && String(selectedCustomerId)) ||
(await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.SelectedCustomer)); (await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.SelectedCustomer));
job = await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.JobData); job = await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.JobData);
const txEnvelope = (await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.txEnvelope)) || {}; const txEnvelope = (await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.txEnvelope)) || {};
if (!job) throw new Error("Staged JobData not found (run rr-export-job first)."); if (!job) throw new Error("Staged JobData not found (run rr-export-job first).");
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket); const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
bodyshop = await getBodyshopForSocket({ bodyshopId, socket }); bodyshop = await getBodyshopForSocket({ bodyshopId, socket });
// Create customer (if requested or none chosen) // Create customer (if requested or none chosen)
if (create === true || !selectedCustNo) { if (create === true || !selectedCustNo) {
CreateRRLogEvent(socket, "DEBUG", `{3.1} Creating RR customer`); CreateRRLogEvent(socket, "DEBUG", `{3.1} Creating RR customer`);
const created = await createRRCustomer({ bodyshop, job, socket }); const created = await createRRCustomer({ bodyshop, job, socket });
selectedCustNo = String(created?.customerNo); selectedCustNo = String(created?.customerNo);
if (!selectedCustNo) throw new Error("RR create customer returned no custNo"); if (!selectedCustNo) throw new Error("RR create customer returned no custNo");
CreateRRLogEvent(socket, "DEBUG", `{3.2} Created customer`, { custNo: selectedCustNo }); CreateRRLogEvent(socket, "DEBUG", `{3.2} Created customer`, { custNo: selectedCustNo });
} }
@@ -426,6 +442,7 @@ const registerRREvents = ({ socket, redisHelpers }) => {
if (vehQ && vehQ.kind === "vin" && job?.v_vin) { if (vehQ && vehQ.kind === "vin" && job?.v_vin) {
const resVin = await rrCombinedSearch(bodyshop, vehQ); const resVin = await rrCombinedSearch(bodyshop, vehQ);
const blocksVin = Array.isArray(resVin?.data) ? resVin.data : Array.isArray(resVin) ? resVin : []; const blocksVin = Array.isArray(resVin?.data) ? resVin.data : Array.isArray(resVin) ? resVin : [];
try { try {
await redisHelpers.setSessionTransactionData( await redisHelpers.setSessionTransactionData(
socket.id, socket.id,
@@ -437,9 +454,12 @@ const registerRREvents = ({ socket, redisHelpers }) => {
} catch { } catch {
// //
} }
const ownersSet = ownersFromVinBlocks(blocksVin, job.v_vin); const ownersSet = ownersFromVinBlocks(blocksVin, job.v_vin);
if (ownersSet?.size) { if (ownersSet?.size) {
const sel = String(selectedCustNo); const sel = String(selectedCustNo);
if (!ownersSet.has(sel)) { if (!ownersSet.has(sel)) {
const [existingOwner] = Array.from(ownersSet).map(String); const [existingOwner] = Array.from(ownersSet).map(String);
CreateRRLogEvent(socket, "DEBUG", `{3.2a} VIN exists; switching to VIN owner`, { CreateRRLogEvent(socket, "DEBUG", `{3.2a} VIN exists; switching to VIN owner`, {
@@ -471,6 +491,7 @@ const registerRREvents = ({ socket, redisHelpers }) => {
// Cache final/effective customer selection // Cache final/effective customer selection
const effectiveCustNo = String(selectedCustNo); const effectiveCustNo = String(selectedCustNo);
await redisHelpers.setSessionTransactionData( await redisHelpers.setSessionTransactionData(
socket.id, socket.id,
ns, ns,
@@ -478,6 +499,7 @@ const registerRREvents = ({ socket, redisHelpers }) => {
effectiveCustNo, effectiveCustNo,
defaultRRTTL defaultRRTTL
); );
CreateRRLogEvent(socket, "DEBUG", `{3.3} Cached selected customer`, { custNo: effectiveCustNo }); CreateRRLogEvent(socket, "DEBUG", `{3.3} Cached selected customer`, { custNo: effectiveCustNo });
// Build client & routing // Build client & routing
@@ -489,12 +511,13 @@ const registerRREvents = ({ socket, redisHelpers }) => {
const tx = { const tx = {
jobData: { jobData: {
...job, ...job,
vin: job?.v_vin || job?.vin || job?.vehicleVin || undefined vin: job?.v_vin
}, },
txEnvelope txEnvelope
}; };
const vin = resolveVin({ tx, job }); const vin = resolveVin({ tx, job });
if (!vin) { if (!vin) {
CreateRRLogEvent(socket, "ERROR", "{3.x} No VIN found for ensureRRServiceVehicle", { jobid: rid }); CreateRRLogEvent(socket, "ERROR", "{3.x} No VIN found for ensureRRServiceVehicle", { jobid: rid });
throw new Error("ensureRRServiceVehicle: vin required"); throw new Error("ensureRRServiceVehicle: vin required");
@@ -524,9 +547,9 @@ const registerRREvents = ({ socket, redisHelpers }) => {
CreateRRLogEvent(socket, "DEBUG", "{3.4} ensureRRServiceVehicle: done", ensured); CreateRRLogEvent(socket, "DEBUG", "{3.4} ensureRRServiceVehicle: done", ensured);
// Advisor no
const cachedAdvisor = await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.AdvisorNo); const cachedAdvisor = await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.AdvisorNo);
const advisorNo = readAdvisorNo({ txEnvelope }, cachedAdvisor); const advisorNo = readAdvisorNo({ txEnvelope }, cachedAdvisor);
if (!advisorNo) { if (!advisorNo) {
CreateRRLogEvent(socket, "ERROR", `Advisor is required (advisorNo)`); CreateRRLogEvent(socket, "ERROR", `Advisor is required (advisorNo)`);
await insertRRFailedExportLog({ await insertRRFailedExportLog({
@@ -540,6 +563,7 @@ const registerRREvents = ({ socket, redisHelpers }) => {
socket.emit("export-failed", { vendor: "rr", jobId: rid, error: "Advisor is required (advisorNo)." }); socket.emit("export-failed", { vendor: "rr", jobId: rid, error: "Advisor is required (advisorNo)." });
return ack?.({ ok: false, error: "Advisor is required (advisorNo)." }); return ack?.({ ok: false, error: "Advisor is required (advisorNo)." });
} }
await redisHelpers.setSessionTransactionData( await redisHelpers.setSessionTransactionData(
socket.id, socket.id,
ns, ns,
@@ -572,17 +596,9 @@ const registerRREvents = ({ socket, redisHelpers }) => {
const data = result?.data || {}; const data = result?.data || {};
// Prefer explicit return from export function; then fall back to fields // Prefer explicit return from export function; then fall back to fields
const dmsRoNo = const dmsRoNo = result?.roNo ?? data?.dmsRoNo ?? null;
result?.roNo ?? data?.dmsRoNo ?? data?.DMSRoNo ?? data?.roStatus?.dmsRoNo ?? data?.roStatus?.DMSRoNo ?? null;
const outsdRoNo = const outsdRoNo = data?.outsdRoNo ?? job?.ro_number ?? job?.id ?? null;
data?.outsdRoNo ??
data?.OutsdRoNo ??
data?.roStatus?.outsdRoNo ??
data?.roStatus?.OutsdRoNo ??
job?.ro_number ??
job?.id ??
null;
await redisHelpers.setSessionTransactionData( await redisHelpers.setSessionTransactionData(
socket.id, socket.id,
@@ -598,13 +614,13 @@ const registerRREvents = ({ socket, redisHelpers }) => {
defaultRRTTL defaultRRTTL
); );
CreateRRLogEvent(socket, "INFO", `{5} RO created. Waiting for cashiering.`, { CreateRRLogEvent(socket, "INFO", `{5} RO created. Waiting for validation.`, {
dmsRoNo: dmsRoNo || null, dmsRoNo: dmsRoNo || null,
outsdRoNo: outsdRoNo || null outsdRoNo: outsdRoNo || null
}); });
// Tell FE to prompt for "Finished/Close" // Tell FE to prompt for "Finished/Close"
socket.emit("rr-cashiering-required", { jobId: rid, dmsRoNo, outsdRoNo }); socket.emit("rr-validation-required", { jobId: rid, dmsRoNo, outsdRoNo });
// Still emit info result if you want // Still emit info result if you want
socket.emit("rr-export-job:result", { jobId: rid, bodyshopId: bodyshop?.id, result }); socket.emit("rr-export-job:result", { jobId: rid, bodyshopId: bodyshop?.id, result });
@@ -616,6 +632,7 @@ const registerRREvents = ({ socket, redisHelpers }) => {
const vendorStatusCode = Number( const vendorStatusCode = Number(
result?.roStatus?.statusCode ?? result?.roStatus?.StatusCode ?? result?.statusBlocks?.transaction?.statusCode result?.roStatus?.statusCode ?? result?.roStatus?.StatusCode ?? result?.statusBlocks?.transaction?.statusCode
); );
const cls = classifyRRVendorError({ const cls = classifyRRVendorError({
code: vendorStatusCode, code: vendorStatusCode,
message: result?.roStatus?.message ?? result?.roStatus?.Message ?? result?.error ?? "RR export failed" message: result?.roStatus?.message ?? result?.roStatus?.Message ?? result?.error ?? "RR export failed"

View File

@@ -61,7 +61,6 @@ const normalizeCustomerCandidates = (res, { ownersSet = null } = {}) => {
const chosen = arr.find((a) => (a?.Type || a?.type || "").toString().toUpperCase() === "P") || arr[0]; 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 line1 = chosen?.Addr1 ?? chosen?.AddressLine1 ?? chosen?.Line1 ?? chosen?.Street1 ?? undefined;
const line2 = chosen?.Addr2 ?? chosen?.AddressLine2 ?? chosen?.Line2 ?? chosen?.Street2 ?? undefined; const line2 = chosen?.Addr2 ?? chosen?.AddressLine2 ?? chosen?.Line2 ?? chosen?.Street2 ?? undefined;
const city = chosen?.City ?? chosen?.city ?? undefined; const city = chosen?.City ?? chosen?.city ?? undefined;
@@ -152,10 +151,7 @@ const readAdvisorNo = (payload, cached) => {
const tx = payload?.txEnvelope || payload?.envelope || {}; const tx = payload?.txEnvelope || payload?.envelope || {};
const get = (v) => (v != null && String(v).trim() !== "" ? String(v).trim() : null); const get = (v) => (v != null && String(v).trim() !== "" ? String(v).trim() : null);
return get(tx?.advisorNo) || get(payload?.advisorNo) || get(cached) || null;
let value = get(tx?.advisorNo) || get(payload?.advisorNo) || get(cached) || null;
return value;
}; };
/** /**
@@ -170,7 +166,7 @@ const RRCacheEnums = {
VINCandidates: "RR.VINCandidates", VINCandidates: "RR.VINCandidates",
SelectedVin: "RR.SelectedVin", SelectedVin: "RR.SelectedVin",
ExportResult: "RR.ExportResult", ExportResult: "RR.ExportResult",
PendingRO: "RR.PendingRO" // NEW: cache created RO to finalize later PendingRO: "RR.PendingRO"
}; };
/** /**