feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint
This commit is contained in:
@@ -46,7 +46,7 @@ async function ensureRRServiceVehicle({ bodyshop, custNo, job, overrides = {}, s
|
||||
// Optional: first try a combined query by VIN to detect existing SV
|
||||
try {
|
||||
const queryRes = await client.combinedSearch(
|
||||
{ vin: job?.v_vin, maxRecs: 1 },
|
||||
{ vin: job?.v_vin, maxRecs: 1, kind: "vin" },
|
||||
{
|
||||
...opts,
|
||||
envelope: {
|
||||
|
||||
@@ -5,17 +5,29 @@ const { exportJobToRR } = require("../rr/rr-job-export");
|
||||
const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default;
|
||||
const { createRRCustomer } = require("../rr/rr-customers");
|
||||
|
||||
const { buildClientAndOpts } = require("../rr/rr-lookup");
|
||||
const { GraphQLClient } = require("graphql-request");
|
||||
const queries = require("../graphql-client/queries");
|
||||
|
||||
// ---------------- utils ----------------
|
||||
// Use the same namespacing/TTL approach as Fortellis
|
||||
const { getTransactionType, defaultFortellisTTL } = require("../fortellis/fortellis-helpers");
|
||||
|
||||
// ---------------- cache keys (RR) ----------------
|
||||
const RRCacheEnums = {
|
||||
txEnvelope: "RR.txEnvelope",
|
||||
JobData: "RR.JobData",
|
||||
SelectedCustomer: "RR.SelectedCustomer",
|
||||
AdvisorNo: "RR.AdvisorNo",
|
||||
// Vehicle keys reserved for future multi-vehicle UX parity
|
||||
VINCandidates: "RR.VINCandidates",
|
||||
SelectedVin: "RR.SelectedVin",
|
||||
ExportResult: "RR.ExportResult"
|
||||
};
|
||||
const defaultRRTTL = defaultFortellisTTL || 60 * 60; // fallback 1h
|
||||
|
||||
// ---------------- utils ----------------
|
||||
function resolveJobId(explicit, payload, job) {
|
||||
return explicit || payload?.jobId || payload?.jobid || job?.id || job?.jobId || job?.jobid || null;
|
||||
}
|
||||
|
||||
// ---- local helpers (avoid no-undef) ----
|
||||
const digitsOnly = (s) => String(s || "").replace(/\D/g, "");
|
||||
|
||||
const makeVehicleSearchPayloadFromJob = (job) => {
|
||||
@@ -42,8 +54,8 @@ const makeCustomerSearchPayloadFromJob = (job) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
// Normalize to the RR table shape expected by FE (custNo + name)
|
||||
const normalizeCustomerCandidates = (res) => {
|
||||
// CombinedSearch may return array or { data: [...] }
|
||||
const blocks = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : [];
|
||||
const out = [];
|
||||
for (const blk of blocks) {
|
||||
@@ -58,7 +70,7 @@ const normalizeCustomerCandidates = (res) => {
|
||||
const name = (personal || company || "").trim();
|
||||
|
||||
for (const custNo of custNos) {
|
||||
out.push({ custNo, name: name || `Customer ${custNo}` });
|
||||
out.push({ custNo: String(custNo), name: name || `Customer ${custNo}` });
|
||||
}
|
||||
}
|
||||
const seen = new Set();
|
||||
@@ -99,32 +111,41 @@ async function getBodyshopForSocket({ bodyshopId, socket }) {
|
||||
return bodyshop;
|
||||
}
|
||||
|
||||
// ---------------- register handlers ----------------
|
||||
function readAdvisorNo(payload, cached) {
|
||||
const v =
|
||||
(payload?.txEnvelope?.advisorNo != null && String(payload.txEnvelope.advisorNo)) ||
|
||||
(payload?.advisorNo != null && String(payload.advisorNo)) ||
|
||||
(payload?.advNo != null && String(payload.advNo)) ||
|
||||
(cached != null && String(cached)) ||
|
||||
null;
|
||||
return v && v.trim() !== "" ? v : null;
|
||||
}
|
||||
|
||||
function registerRREvents({ socket, redisHelpers }) {
|
||||
// RRLogger returns a log(level, message, ctx) function
|
||||
// ---------------- register handlers ----------------
|
||||
function registerRREvents({ socket, redisHelpers /*, ioHelpers, logger*/ }) {
|
||||
const log = RRLogger(socket);
|
||||
|
||||
// Lookups (mirrors Fortellis shape/flow)
|
||||
// --------- Lookups (customer search → open table) ---------
|
||||
socket.on("rr-lookup-combined", async ({ jobid, params } = {}, cb) => {
|
||||
try {
|
||||
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
|
||||
const bodyshop = await getBodyshopForSocket({ bodyshopId, socket });
|
||||
|
||||
const res = await rrCombinedSearch(bodyshop, params || {});
|
||||
const data = res?.data ?? res;
|
||||
const normalized = normalizeCustomerCandidates(res);
|
||||
|
||||
cb?.({ jobid: resolveJobId(jobid, { jobid }, null), data });
|
||||
const rid = resolveJobId(jobid, { jobid }, null);
|
||||
cb?.({ jobid: rid, data: normalized });
|
||||
|
||||
// Push to FE to open the table; keep payload as the raw array (FE maps columns itself)
|
||||
socket.emit("rr-select-customer", Array.isArray(data) ? data : data?.customers || []);
|
||||
// FE expects { custNo, name }
|
||||
socket.emit("rr-select-customer", normalized);
|
||||
} catch (e) {
|
||||
log("error", `RR combined lookup error: ${e.message}`, { jobid });
|
||||
cb?.({ jobid, error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Advisors
|
||||
// --------- Advisors ---------
|
||||
socket.on("rr-get-advisors", async (args = {}, ack) => {
|
||||
try {
|
||||
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
|
||||
@@ -138,7 +159,7 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
}
|
||||
});
|
||||
|
||||
// Parts
|
||||
// --------- Parts ---------
|
||||
socket.on("rr-get-parts", async (args = {}, ack) => {
|
||||
try {
|
||||
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
|
||||
@@ -152,31 +173,54 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
}
|
||||
});
|
||||
|
||||
// Persist customer selection (or flag create-new)
|
||||
socket.on("rr-selected-customer", async (selected, ack) => {
|
||||
// --------- Persist customer selection / create-intent (table-first UX) ---------
|
||||
socket.on("rr-selected-customer", async ({ jobId, custNo, create } = {}, ack) => {
|
||||
const ns = getTransactionType(jobId || "unknown");
|
||||
try {
|
||||
await getSessionOrSocket(redisHelpers, socket);
|
||||
const tx = (await redisHelpers.getSessionTransactionData(socket.id)) || {};
|
||||
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
|
||||
const bodyshop = await getBodyshopForSocket({ bodyshopId, socket });
|
||||
if (!jobId) throw new Error("jobId required");
|
||||
|
||||
// Signal create-new intent
|
||||
if (!selected || selected?.create === true || selected?.__new === true) {
|
||||
await redisHelpers.setSessionTransactionData(socket.id, { ...tx, rrCreateCustomer: true });
|
||||
log("info", "rr-selected-customer:new-customer-intent");
|
||||
socket.emit("rr-customer-create-required");
|
||||
return ack?.({ ok: true, action: "create" });
|
||||
// If caller passed a selection, just persist it.
|
||||
if (custNo && create !== true) {
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.SelectedCustomer,
|
||||
String(custNo),
|
||||
defaultRRTTL
|
||||
);
|
||||
log("info", "rr-selected-customer", { jobId, custNo: String(custNo) });
|
||||
return ack?.({ ok: true });
|
||||
}
|
||||
|
||||
await redisHelpers.setSessionTransactionData(socket.id, { ...tx, rrSelectedCustomer: selected });
|
||||
log("info", "rr-selected-customer", { selected });
|
||||
ack?.({ ok: true });
|
||||
// No custNo (or create: true) => create immediately from JobData (Fortellis parity)
|
||||
const job = await QueryJobData({ redisHelpers }, jobId);
|
||||
const created = await createRRCustomer({ bodyshop, job, socket });
|
||||
const newCustNo = String(
|
||||
created?.custNo || created?.customerNo || created?.CustomerNo || created?.dmsRecKey || ""
|
||||
);
|
||||
if (!newCustNo) throw new Error("RR create customer returned no custNo");
|
||||
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.SelectedCustomer,
|
||||
newCustNo,
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
log("info", "rr-create-customer:success", { jobId, custNo: newCustNo });
|
||||
return ack?.({ ok: true, custNo: newCustNo });
|
||||
} catch (err) {
|
||||
log("error", err?.message || "select customer failed", { err });
|
||||
ack?.({ ok: false, error: err?.message || "select customer failed" });
|
||||
log("error", err?.message || "select/create customer failed", { err, jobId });
|
||||
return ack?.({ ok: false, error: err?.message || "select/create customer failed" });
|
||||
}
|
||||
});
|
||||
|
||||
// Optional explicit create-customer from UI form
|
||||
// --------- Optional explicit create-customer (form) ---------
|
||||
socket.on("rr-create-customer", async ({ jobId, fields } = {}, ack) => {
|
||||
const ns = getTransactionType(jobId || "unknown");
|
||||
try {
|
||||
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
|
||||
const bodyshop = await getBodyshopForSocket({ bodyshopId, socket });
|
||||
@@ -184,67 +228,82 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
if (!jobId) throw new Error("jobId required");
|
||||
const job = await QueryJobData({ redisHelpers }, jobId);
|
||||
|
||||
const { custNo } = await createRRCustomer({ bodyshop, job, overrides: fields || {}, socket });
|
||||
const created = await createRRCustomer({ bodyshop, job, overrides: fields || {}, socket });
|
||||
const custNo = String(created?.custNo || created?.customerNo || created?.CustomerNo || created?.dmsRecKey || "");
|
||||
if (!custNo) throw new Error("RR create customer returned no custNo");
|
||||
|
||||
const tx = (await redisHelpers.getSessionTransactionData(socket.id)) || {};
|
||||
await redisHelpers.setSessionTransactionData(socket.id, {
|
||||
...tx,
|
||||
rrSelectedCustomer: { custNo },
|
||||
rrCreateCustomer: false
|
||||
});
|
||||
await redisHelpers.setSessionTransactionData(socket.id, ns, RRCacheEnums.SelectedCustomer, custNo, defaultRRTTL);
|
||||
await redisHelpers.setSessionTransactionData(socket.id, ns, "RR.CreateCustomerIntent", false, defaultRRTTL);
|
||||
|
||||
log("info", "rr-create-customer:success", { custNo });
|
||||
log("info", "rr-create-customer:success", { jobId, custNo });
|
||||
socket.emit("rr-customer-created", { custNo });
|
||||
ack?.({ ok: true, custNo });
|
||||
} catch (err) {
|
||||
log("error", err?.message || "create customer failed", { err });
|
||||
log("error", err?.message || "create customer failed", { err, jobId });
|
||||
ack?.({ ok: false, error: err?.message || "create customer failed" });
|
||||
}
|
||||
});
|
||||
|
||||
// Vehicle selection helpers
|
||||
socket.on("rr-selected-vehicle", async (selected, ack) => {
|
||||
// --------- Vehicle selection hooks (reserved for future parity) ---------
|
||||
socket.on("rr-selected-vehicle", async ({ jobId, vin } = {}, ack) => {
|
||||
const ns = getTransactionType(jobId || "unknown");
|
||||
try {
|
||||
await getSessionOrSocket(redisHelpers, socket);
|
||||
if (!selected?.vin) throw new Error("selected vehicle must include vin");
|
||||
const tx = (await redisHelpers.getSessionTransactionData(socket.id)) || {};
|
||||
await redisHelpers.setSessionTransactionData(socket.id, { ...tx, rrSelectedVehicle: selected });
|
||||
log("info", "rr-selected-vehicle", { vin: selected.vin });
|
||||
if (!vin) throw new Error("vin required");
|
||||
await redisHelpers.setSessionTransactionData(socket.id, ns, RRCacheEnums.SelectedVin, String(vin), defaultRRTTL);
|
||||
log("info", "rr-selected-vehicle", { jobId, vin: String(vin) });
|
||||
ack?.({ ok: true });
|
||||
} catch (err) {
|
||||
log("error", err?.message || "select vehicle failed", { err });
|
||||
log("error", err?.message || "select vehicle failed", { err, jobId });
|
||||
ack?.({ ok: false, error: err?.message || "select vehicle failed" });
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("rr-create-vehicle", async (vehicle, ack) => {
|
||||
socket.on("rr-create-vehicle", async ({ jobId, vehicle } = {}, ack) => {
|
||||
const ns = getTransactionType(jobId || "unknown");
|
||||
try {
|
||||
await getSessionOrSocket(redisHelpers, socket);
|
||||
if (!vehicle?.vin) throw new Error("vehicle.vin required");
|
||||
const tx = (await redisHelpers.getSessionTransactionData(socket.id)) || {};
|
||||
await redisHelpers.setSessionTransactionData(socket.id, { ...tx, rrSelectedVehicle: vehicle });
|
||||
log("info", "rr-create-vehicle", { vin: vehicle.vin });
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.SelectedVin,
|
||||
String(vehicle.vin),
|
||||
defaultRRTTL
|
||||
);
|
||||
log("info", "rr-create-vehicle", { jobId, vin: String(vehicle.vin) });
|
||||
ack?.({ ok: true });
|
||||
} catch (err) {
|
||||
log("error", err?.message || "create vehicle failed", { err });
|
||||
log("error", err?.message || "create vehicle failed", { err, jobId });
|
||||
ack?.({ ok: false, error: err?.message || "create vehicle failed" });
|
||||
}
|
||||
});
|
||||
|
||||
// --------- Main export (Fortellis-style staging) ---------
|
||||
socket.on("rr-export-job", async (payload = {}) => {
|
||||
const log = RRLogger(socket, { ns: "rr" });
|
||||
const _log = RRLogger(socket, { ns: "rr" });
|
||||
|
||||
try {
|
||||
// -------- 1) Resolve job --------
|
||||
// 1) Resolve job
|
||||
let job = payload.job || payload.txEnvelope?.job;
|
||||
const jobId = payload.jobId || payload.jobid || payload.txEnvelope?.jobId || job?.id;
|
||||
|
||||
if (!job) {
|
||||
if (!jobId) throw new Error("RR export: job or jobId required");
|
||||
job = await QueryJobData({ redisHelpers }, jobId);
|
||||
}
|
||||
const ns = getTransactionType(job.id);
|
||||
|
||||
// -------- 2) Resolve bodyshop (+ full row via GraphQL) --------
|
||||
// Persist txEnvelope + job
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.txEnvelope,
|
||||
payload.txEnvelope || {},
|
||||
defaultRRTTL
|
||||
);
|
||||
await redisHelpers.setSessionTransactionData(socket.id, ns, RRCacheEnums.JobData, job, defaultRRTTL);
|
||||
|
||||
// 2) Resolve bodyshop
|
||||
let bodyshopId = payload.bodyshopId || payload.bodyshopid || payload.bodyshopUUID || job?.bodyshop?.id;
|
||||
if (!bodyshopId) {
|
||||
const sess = await getSessionOrSocket(redisHelpers, socket);
|
||||
@@ -257,109 +316,116 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
? job.bodyshop
|
||||
: await getBodyshopForSocket({ bodyshopId, socket });
|
||||
|
||||
// -------- 3) Resolve advisor number from the posting form --------
|
||||
const tx = (await redisHelpers.getSessionTransactionData(socket.id)) || {};
|
||||
const advisorNo = payload.advisorNo || payload.advNo || payload.txEnvelope?.advisorNo || tx.rrAdvisorNo || null;
|
||||
|
||||
if (!advisorNo || String(advisorNo).trim() === "") {
|
||||
// 3) Resolve advisor number (from form or cache)
|
||||
const cachedAdvisor = await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.AdvisorNo);
|
||||
const advisorNo = readAdvisorNo(payload, cachedAdvisor);
|
||||
if (!advisorNo) {
|
||||
socket.emit("export-failed", { vendor: "rr", jobId, error: "Advisor is required (advisorNo)." });
|
||||
return;
|
||||
}
|
||||
// Persist for subsequent steps if useful
|
||||
await redisHelpers.setSessionTransactionData(socket.id, { ...tx, rrAdvisorNo: String(advisorNo) });
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.AdvisorNo,
|
||||
String(advisorNo),
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
// -------- 4) Resolve selected customer (payload → tx) --------
|
||||
let selectedCustomer = null;
|
||||
// 4) Resolve selected customer (payload → cache)
|
||||
let selectedCust = null;
|
||||
|
||||
// from payload
|
||||
if (payload.selectedCustomer) {
|
||||
if (typeof payload.selectedCustomer === "object" && payload.selectedCustomer.custNo) {
|
||||
selectedCustomer = { custNo: String(payload.selectedCustomer.custNo) };
|
||||
selectedCust = { custNo: String(payload.selectedCustomer.custNo) };
|
||||
} else if (typeof payload.selectedCustomer === "string" || typeof payload.selectedCustomer === "number") {
|
||||
selectedCustomer = { custNo: String(payload.selectedCustomer) };
|
||||
selectedCust = { custNo: String(payload.selectedCustomer) };
|
||||
}
|
||||
if (selectedCust?.custNo) {
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.SelectedCustomer,
|
||||
selectedCust.custNo,
|
||||
defaultRRTTL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// from tx
|
||||
if (!selectedCustomer && tx.rrSelectedCustomer) {
|
||||
if (typeof tx.rrSelectedCustomer === "object" && tx.rrSelectedCustomer.custNo) {
|
||||
selectedCustomer = { custNo: String(tx.rrSelectedCustomer.custNo) };
|
||||
} else {
|
||||
selectedCustomer = { custNo: String(tx.rrSelectedCustomer) };
|
||||
}
|
||||
if (!selectedCust) {
|
||||
const cached = await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.SelectedCustomer);
|
||||
if (cached) selectedCust = { custNo: String(cached) };
|
||||
}
|
||||
|
||||
// Flags
|
||||
const forceCreate = payload.forceCreate === true || tx.rrCreateCustomer === true;
|
||||
const forceCreate = payload.forceCreate === true;
|
||||
const autoCreateOnNoMatch = payload.autoCreateOnNoMatch !== false; // default TRUE
|
||||
|
||||
// -------- 5) If no selection & not "forceCreate", try auto-search first --------
|
||||
if (!selectedCustomer && !forceCreate) {
|
||||
// 5) If no selection & not "forceCreate", try auto-search
|
||||
if (!selectedCust && !forceCreate) {
|
||||
const customerQuery = makeCustomerSearchPayloadFromJob(job);
|
||||
const vehicleQuery = makeVehicleSearchPayloadFromJob(job);
|
||||
const query = customerQuery || vehicleQuery;
|
||||
|
||||
if (query) {
|
||||
log("info", "rr-export-job:customer-preselect-search", { query, jobId });
|
||||
_log("info", "rr-export-job:customer-preselect-search", { query, jobId });
|
||||
const searchRes = await rrCombinedSearch(bodyshop, query);
|
||||
const candidates = normalizeCustomerCandidates(searchRes);
|
||||
|
||||
if (candidates.length === 1) {
|
||||
// auto-pick single hit
|
||||
selectedCustomer = { custNo: String(candidates[0].custNo) };
|
||||
await redisHelpers.setSessionTransactionData(socket.id, {
|
||||
...tx,
|
||||
rrSelectedCustomer: selectedCustomer.custNo,
|
||||
rrCreateCustomer: false
|
||||
});
|
||||
log("info", "rr-export-job:auto-selected-customer", { jobId, custNo: selectedCustomer.custNo });
|
||||
selectedCust = { custNo: String(candidates[0].custNo) };
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.SelectedCustomer,
|
||||
selectedCust.custNo,
|
||||
defaultRRTTL
|
||||
);
|
||||
_log("info", "rr-export-job:auto-selected-customer", { jobId, custNo: selectedCust.custNo });
|
||||
} else if (candidates.length > 1) {
|
||||
// multiple matches → ask UI to pick and STOP here
|
||||
const table = candidates.map((c) => ({
|
||||
CustomerId: c.custNo,
|
||||
customerId: c.custNo,
|
||||
CustomerName: { FirstName: c.name || String(c.custNo), LastName: "" }
|
||||
}));
|
||||
socket.emit("rr-select-customer", table);
|
||||
socket.emit("rr-log-event", { level: "info", message: "RR: customer selection required", ts: Date.now() });
|
||||
// multiple matches → table and stop
|
||||
socket.emit("rr-select-customer", candidates); // FE expects [{custNo,name}]
|
||||
socket.emit("rr-log-event", {
|
||||
level: "info",
|
||||
message: "RR: customer selection required",
|
||||
ts: Date.now()
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
// 0 matches → auto-create by default
|
||||
if (autoCreateOnNoMatch) {
|
||||
const { createRRCustomer } = require("../rr/rr-customers");
|
||||
const created = await createRRCustomer({ bodyshop, job, socket });
|
||||
const custNo = created?.custNo || created?.customerNo || created?.CustomerNo || created?.dmsRecKey;
|
||||
if (!custNo) throw new Error("RR create customer returned no custNo");
|
||||
selectedCustomer = { custNo: String(custNo) };
|
||||
await redisHelpers.setSessionTransactionData(socket.id, {
|
||||
...tx,
|
||||
rrSelectedCustomer: selectedCustomer.custNo,
|
||||
rrCreateCustomer: false
|
||||
});
|
||||
log("info", "rr-export-job:auto-created-customer", { jobId, custNo: selectedCustomer.custNo });
|
||||
selectedCust = { custNo: String(custNo) };
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.SelectedCustomer,
|
||||
selectedCust.custNo,
|
||||
defaultRRTTL
|
||||
);
|
||||
_log("info", "rr-export-job:auto-created-customer", { jobId, custNo: selectedCust.custNo });
|
||||
} else {
|
||||
await redisHelpers.setSessionTransactionData(socket.id, { ...tx, rrCreateCustomer: true });
|
||||
socket.emit("rr-customer-create-required");
|
||||
socket.emit("rr-log-event", { level: "info", message: "RR: create customer required", ts: Date.now() });
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no usable query → fall back to create or UI prompt
|
||||
// no usable query → create or prompt
|
||||
if (autoCreateOnNoMatch) {
|
||||
const { createRRCustomer } = require("../rr/rr-customers");
|
||||
const created = await createRRCustomer({ bodyshop, job, socket });
|
||||
const custNo = created?.custNo || created?.customerNo || created?.CustomerNo || created?.dmsRecKey;
|
||||
if (!custNo) throw new Error("RR create customer returned no custNo");
|
||||
selectedCustomer = { custNo: String(custNo) };
|
||||
await redisHelpers.setSessionTransactionData(socket.id, {
|
||||
...tx,
|
||||
rrSelectedCustomer: selectedCustomer.custNo,
|
||||
rrCreateCustomer: false
|
||||
});
|
||||
log("info", "rr-export-job:auto-created-customer(no-query)", { jobId, custNo: selectedCustomer.custNo });
|
||||
selectedCust = { custNo: String(custNo) };
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.SelectedCustomer,
|
||||
selectedCust.custNo,
|
||||
defaultRRTTL
|
||||
);
|
||||
_log("info", "rr-export-job:auto-created-customer(no-query)", { jobId, custNo: selectedCust.custNo });
|
||||
} else {
|
||||
await redisHelpers.setSessionTransactionData(socket.id, { ...tx, rrCreateCustomer: true });
|
||||
socket.emit("rr-customer-create-required");
|
||||
socket.emit("rr-log-event", { level: "info", message: "RR: create customer required", ts: Date.now() });
|
||||
return;
|
||||
@@ -367,29 +433,14 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
}
|
||||
}
|
||||
|
||||
// -------- 6) If still not selected & creation is allowed, create now --------
|
||||
if (!selectedCustomer && (forceCreate || autoCreateOnNoMatch)) {
|
||||
const { createRRCustomer } = require("../rr/rr-customers");
|
||||
const created = await createRRCustomer({ bodyshop, job, socket });
|
||||
const custNo = created?.custNo || created?.customerNo || created?.CustomerNo || created?.dmsRecKey;
|
||||
if (!custNo) throw new Error("RR create customer returned no custNo");
|
||||
selectedCustomer = { custNo: String(custNo) };
|
||||
await redisHelpers.setSessionTransactionData(socket.id, {
|
||||
...tx,
|
||||
rrSelectedCustomer: selectedCustomer.custNo,
|
||||
rrCreateCustomer: false
|
||||
});
|
||||
log("info", "rr-export-job:customer-created", { jobId, custNo: selectedCustomer.custNo });
|
||||
}
|
||||
if (!selectedCust?.custNo) throw new Error("RR export: selected customer missing custNo");
|
||||
|
||||
if (!selectedCustomer?.custNo) throw new Error("RR export: selected customer missing custNo");
|
||||
|
||||
// -------- 7) Perform export (ensure SV + create/update RO) --------
|
||||
// 6) Perform export (ensure SV + create/update RO inside exportJobToRR)
|
||||
const result = await exportJobToRR({
|
||||
bodyshop,
|
||||
job,
|
||||
selectedCustomer,
|
||||
advisorNo,
|
||||
selectedCustomer: selectedCust,
|
||||
advisorNo: String(advisorNo),
|
||||
existing: payload.existing,
|
||||
socket
|
||||
});
|
||||
@@ -405,10 +456,17 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
});
|
||||
}
|
||||
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.ExportResult,
|
||||
result || {},
|
||||
defaultRRTTL
|
||||
);
|
||||
socket.emit("rr-export-job:result", { jobId, bodyshopId, result });
|
||||
} catch (error) {
|
||||
const jobId = payload.jobId || payload.jobid || payload.txEnvelope?.jobId || payload?.job?.id;
|
||||
log("error", `Error during RR export: ${error.message}`, { jobId, stack: error.stack });
|
||||
_log("error", `Error during RR export: ${error.message}`, { jobId, stack: error.stack });
|
||||
try {
|
||||
socket.emit("export-failed", { vendor: "rr", jobId, error: error.message });
|
||||
} catch {
|
||||
@@ -417,7 +475,7 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
}
|
||||
});
|
||||
|
||||
// Allocations (RR reuses CDK calculator)
|
||||
// --------- Allocations (reuse CDK calculator) ---------
|
||||
socket.on("rr-calculate-allocations", async (jobid, cb) => {
|
||||
try {
|
||||
const allocations = await CdkCalculateAllocations(socket, jobid);
|
||||
|
||||
Reference in New Issue
Block a user