feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
// File: server/rr/rr-register-socket-events.js
|
||||
// File: server/web-sockets/rr-register-socket-events.js
|
||||
// RR events aligned to Fortellis flow with Fortellis-style logging via CreateRRLogEvent
|
||||
|
||||
const CreateRRLogEvent = require("../rr/rr-logger-event");
|
||||
const { rrCombinedSearch, rrGetAdvisors, rrGetParts } = require("../rr/rr-lookup");
|
||||
const { rrCombinedSearch, rrGetAdvisors, rrGetParts, buildClientAndOpts } = require("../rr/rr-lookup");
|
||||
const { QueryJobData } = require("../rr/rr-job-helpers");
|
||||
const { exportJobToRR } = require("../rr/rr-job-export");
|
||||
const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default;
|
||||
@@ -26,6 +26,11 @@ function resolveJobId(explicit, payload, job) {
|
||||
return explicit || payload?.jobId || payload?.jobid || job?.id || job?.jobId || job?.jobid || null;
|
||||
}
|
||||
|
||||
function resolveVin({ tx, job }) {
|
||||
// Prefer cached tx vin (if we made one), then common job shapes (v_vin for our schema)
|
||||
return tx?.jobData?.vin || job?.v_vin || job?.vehicle?.vin || job?.vin || job?.vehicleVin || null;
|
||||
}
|
||||
|
||||
function sortVehicleOwnerFirst(list) {
|
||||
return list
|
||||
.map((v, i) => ({ v, i }))
|
||||
@@ -65,14 +70,10 @@ async function getBodyshopForSocket({ bodyshopId, socket }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* VIN + Full Name merge (export flow):
|
||||
* - Name query from job first/last or company
|
||||
* - VIN query from job VIN
|
||||
* - Cache VIN raw blocks (res.data) under RRCacheEnums.VINCandidates
|
||||
* - Mark isVehicleOwner only if candidate.custNo is in the VIN owners set (exact match)
|
||||
* VIN + Full Name merge (export flow)
|
||||
*/
|
||||
async function rrMultiCustomerSearch({ bodyshop, job, socket, redisHelpers }) {
|
||||
const queries = [];
|
||||
const queriesList = [];
|
||||
|
||||
// 1) Full Name (preferred)
|
||||
const firstName = job?.ownr_fn && String(job.ownr_fn).trim();
|
||||
@@ -80,29 +81,28 @@ async function rrMultiCustomerSearch({ bodyshop, job, socket, redisHelpers }) {
|
||||
const company = job?.ownr_co_nm && String(job.ownr_co_nm).trim();
|
||||
|
||||
if (firstName || lastName) {
|
||||
queries.push({
|
||||
queriesList.push({
|
||||
q: { kind: "name", name: { fname: firstName || undefined, lname: lastName || undefined }, maxResults: 50 },
|
||||
fromVin: false
|
||||
});
|
||||
} else if (company) {
|
||||
queries.push({ q: { kind: "name", name: { name: company }, maxResults: 50 }, fromVin: false });
|
||||
queriesList.push({ q: { kind: "name", name: { name: company }, maxResults: 50 }, fromVin: false });
|
||||
}
|
||||
|
||||
// 2) VIN (owner association)
|
||||
const vehQ = makeVehicleSearchPayloadFromJob(job);
|
||||
if (vehQ && vehQ.kind === "vin") queries.push({ q: vehQ, fromVin: true });
|
||||
if (vehQ && vehQ.kind === "vin") queriesList.push({ q: vehQ, fromVin: true });
|
||||
|
||||
if (!queries.length) return [];
|
||||
if (!queriesList.length) return [];
|
||||
|
||||
let ownersSet = null;
|
||||
const merged = [];
|
||||
|
||||
for (const { q, fromVin } of queries) {
|
||||
for (const { q, fromVin } of queriesList) {
|
||||
try {
|
||||
CreateRRLogEvent(socket, "DEBUG", `{RR-SEARCH} Executing ${q.kind} query`, { q });
|
||||
const res = await rrCombinedSearch(bodyshop, q);
|
||||
|
||||
// If VIN query, compute ownersSet & cache raw blocks
|
||||
if (fromVin) {
|
||||
const blocks = Array.isArray(res?.data) ? res.data : [];
|
||||
ownersSet = ownersFromVinBlocks(blocks, job?.v_vin);
|
||||
@@ -114,9 +114,7 @@ async function rrMultiCustomerSearch({ bodyshop, job, socket, redisHelpers }) {
|
||||
blocks,
|
||||
defaultRRTTL
|
||||
);
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const norm = normalizeCustomerCandidates(res, { ownersSet });
|
||||
@@ -143,7 +141,7 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
|
||||
if ((params?.kind || "").toLowerCase() === "vin") {
|
||||
const blocks = Array.isArray(res?.data) ? res.data : [];
|
||||
ownersSet = ownersFromVinBlocks(blocks); // no job VIN filter in ad-hoc lookup
|
||||
ownersSet = ownersFromVinBlocks(blocks);
|
||||
}
|
||||
|
||||
const normalized = sortVehicleOwnerFirst(normalizeCustomerCandidates(res, { ownersSet }));
|
||||
@@ -257,9 +255,7 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
});
|
||||
try {
|
||||
socket.emit("export-failed", { vendor: "rr", jobId: rid, error: error.message });
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -288,6 +284,7 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
|
||||
const bodyshop = await getBodyshopForSocket({ bodyshopId, socket });
|
||||
|
||||
// Create customer (if requested or none chosen)
|
||||
if (create === true || !selectedCustNo) {
|
||||
CreateRRLogEvent(socket, "DEBUG", `{3.1} Creating RR customer`);
|
||||
const created = await createRRCustomer({ bodyshop, job, socket });
|
||||
@@ -296,25 +293,108 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
CreateRRLogEvent(socket, "DEBUG", `{3.2} Created customer`, { custNo: selectedCustNo });
|
||||
}
|
||||
|
||||
// VIN owner pre-check
|
||||
try {
|
||||
const vehQ = makeVehicleSearchPayloadFromJob(job);
|
||||
if (vehQ && vehQ.kind === "vin" && job?.v_vin) {
|
||||
const resVin = await rrCombinedSearch(bodyshop, vehQ);
|
||||
const blocksVin = Array.isArray(resVin?.data) ? resVin.data : [];
|
||||
try {
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.VINCandidates,
|
||||
blocksVin,
|
||||
defaultRRTTL
|
||||
);
|
||||
} catch {}
|
||||
const ownersSet = ownersFromVinBlocks(blocksVin, job.v_vin);
|
||||
if (ownersSet && ownersSet.size) {
|
||||
const sel = String(selectedCustNo);
|
||||
if (!ownersSet.has(sel)) {
|
||||
const [existingOwner] = Array.from(ownersSet).map(String);
|
||||
CreateRRLogEvent(socket, "DEBUG", `{3.2a} VIN exists; switching to VIN owner`, {
|
||||
vin: job.v_vin,
|
||||
selected: sel,
|
||||
existingOwner
|
||||
});
|
||||
try {
|
||||
socket.emit("rr-vin-owner-mismatch", {
|
||||
ts: Date.now(),
|
||||
vin: job.v_vin,
|
||||
selectedCustomerNo: sel,
|
||||
existingOwner,
|
||||
message:
|
||||
"VIN already exists in RR under a different customer. Using the VIN's owner to continue the export."
|
||||
});
|
||||
} catch {}
|
||||
selectedCustNo = existingOwner;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
CreateRRLogEvent(socket, "WARN", `VIN owner pre-check failed; continuing with selected customer`, {
|
||||
error: e?.message
|
||||
});
|
||||
}
|
||||
|
||||
// Cache final/effective customer selection
|
||||
const effectiveCustNo = String(selectedCustNo);
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.SelectedCustomer,
|
||||
String(selectedCustNo),
|
||||
effectiveCustNo,
|
||||
defaultRRTTL
|
||||
);
|
||||
CreateRRLogEvent(socket, "DEBUG", `{3.3} Cached selected customer`, { custNo: String(selectedCustNo) });
|
||||
CreateRRLogEvent(socket, "DEBUG", `{3.3} Cached selected customer`, { custNo: effectiveCustNo });
|
||||
|
||||
// Ensure service vehicle exists and is owned by selected customer (uses cached VIN blocks when present)
|
||||
const ensureVeh = await ensureRRServiceVehicle({
|
||||
// Build client & routing
|
||||
const { client, opts } = await buildClientAndOpts(bodyshop);
|
||||
const routing = opts?.routing || client?.opts?.routing || null;
|
||||
if (!routing?.dealerNumber) throw new Error("ensureRRServiceVehicle: routing.dealerNumber required");
|
||||
|
||||
// Reconstruct a lightweight tx object (so resolveVin can use the same shape we logged at {1.2})
|
||||
const tx = {
|
||||
jobData: {
|
||||
...job,
|
||||
vin: job?.v_vin || job?.vin || job?.vehicleVin || undefined
|
||||
},
|
||||
txEnvelope
|
||||
};
|
||||
|
||||
const vin = resolveVin({ tx, job });
|
||||
if (!vin) {
|
||||
CreateRRLogEvent(socket, "ERROR", "{3.x} No VIN found for ensureRRServiceVehicle", { jobid: rid });
|
||||
throw new Error("ensureRRServiceVehicle: vin required");
|
||||
}
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", "{3.2} ensureRRServiceVehicle: starting", {
|
||||
jobid: rid,
|
||||
selectedCustomerNo: effectiveCustNo,
|
||||
vin,
|
||||
dealerNumber: routing.dealerNumber,
|
||||
storeNumber: routing.storeNumber,
|
||||
areaNumber: routing.areaNumber
|
||||
});
|
||||
|
||||
const ensured = await ensureRRServiceVehicle({
|
||||
client,
|
||||
routing,
|
||||
bodyshop,
|
||||
custNo: String(selectedCustNo),
|
||||
// Normalize for any internal checks:
|
||||
selectedCustomerNo: effectiveCustNo,
|
||||
custNo: effectiveCustNo,
|
||||
customerNo: effectiveCustNo,
|
||||
vin,
|
||||
job,
|
||||
socket,
|
||||
redisHelpers
|
||||
});
|
||||
CreateRRLogEvent(socket, "DEBUG", `{3.4} ensureRRServiceVehicle`, ensureVeh);
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", "{3.4} ensureRRServiceVehicle: done", ensured);
|
||||
|
||||
// Advisor no
|
||||
const cachedAdvisor = await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.AdvisorNo);
|
||||
const advisorNo = readAdvisorNo({ txEnvelope }, cachedAdvisor);
|
||||
if (!advisorNo) {
|
||||
@@ -330,11 +410,12 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
// Export
|
||||
CreateRRLogEvent(socket, "DEBUG", `{4} Performing RR export`);
|
||||
const result = await exportJobToRR({
|
||||
bodyshop,
|
||||
job,
|
||||
selectedCustomer: { customerNo: String(selectedCustNo), custNo: String(selectedCustNo) },
|
||||
selectedCustomer: { customerNo: effectiveCustNo, custNo: effectiveCustNo },
|
||||
advisorNo: String(advisorNo),
|
||||
existing: txEnvelope?.existing,
|
||||
socket
|
||||
@@ -371,9 +452,7 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
});
|
||||
try {
|
||||
socket.emit("export-failed", { vendor: "rr", jobId: rid, error: error.message });
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
} catch {}
|
||||
ack?.({ ok: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user