feature/IO-3558-Reynolds-Part-2 - Initial
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
const CreateRRLogEvent = require("./rr-logger-event");
|
||||
const { rrCombinedSearch, rrGetAdvisors, buildClientAndOpts } = require("./rr-lookup");
|
||||
const { QueryJobData, buildRogogFromAllocations, buildRolaborFromRogog } = require("./rr-job-helpers");
|
||||
const { exportJobToRR, finalizeRRRepairOrder } = require("./rr-job-export");
|
||||
const {
|
||||
exportJobToRR,
|
||||
createMinimalRRRepairOrder,
|
||||
updateRRRepairOrderWithFullData,
|
||||
finalizeRRRepairOrder
|
||||
} = require("./rr-job-export");
|
||||
const RRCalculateAllocations = require("./rr-calculate-allocations").default;
|
||||
const { createRRCustomer } = require("./rr-customers");
|
||||
const { ensureRRServiceVehicle } = require("./rr-service-vehicles");
|
||||
@@ -124,13 +129,15 @@ const getBodyshopForSocket = async ({ bodyshopId, socket }) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* GraphQL mutation to set job.dms_id
|
||||
* GraphQL mutation to set job.dms_id, dms_customer_id, and dms_advisor_id
|
||||
* @param socket
|
||||
* @param jobId
|
||||
* @param dmsId
|
||||
* @param dmsCustomerId
|
||||
* @param dmsAdvisorId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const setJobDmsIdForSocket = async ({ socket, jobId, dmsId }) => {
|
||||
const setJobDmsIdForSocket = async ({ socket, jobId, dmsId, dmsCustomerId, dmsAdvisorId }) => {
|
||||
if (!jobId || !dmsId) {
|
||||
CreateRRLogEvent(socket, "WARN", "setJobDmsIdForSocket called without jobId or dmsId", {
|
||||
jobId,
|
||||
@@ -149,16 +156,25 @@ const setJobDmsIdForSocket = async ({ socket, jobId, dmsId }) => {
|
||||
const client = new GraphQLClient(endpoint, {});
|
||||
await client
|
||||
.setHeaders({ Authorization: `Bearer ${token}` })
|
||||
.request(queries.SET_JOB_DMS_ID, { id: jobId, dms_id: String(dmsId) });
|
||||
.request(queries.SET_JOB_DMS_ID, {
|
||||
id: jobId,
|
||||
dms_id: String(dmsId),
|
||||
dms_customer_id: dmsCustomerId ? String(dmsCustomerId) : null,
|
||||
dms_advisor_id: dmsAdvisorId ? String(dmsAdvisorId) : null
|
||||
});
|
||||
|
||||
CreateRRLogEvent(socket, "INFO", "Linked job.dms_id to RR RO", {
|
||||
jobId,
|
||||
dmsId: String(dmsId)
|
||||
dmsId: String(dmsId),
|
||||
dmsCustomerId,
|
||||
dmsAdvisorId
|
||||
});
|
||||
} catch (err) {
|
||||
CreateRRLogEvent(socket, "ERROR", "Failed to set job.dms_id after RR create/update", {
|
||||
jobId,
|
||||
dmsId,
|
||||
dmsCustomerId,
|
||||
dmsAdvisorId,
|
||||
message: err?.message || String(err),
|
||||
stack: err?.stack
|
||||
});
|
||||
@@ -373,7 +389,501 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("rr-export-job", async ({ jobid, jobId, txEnvelope } = {}) => {
|
||||
/**
|
||||
* NEW: Early RO Creation Event
|
||||
* Creates a minimal RO from convert button or admin page with customer selection,
|
||||
* advisor, mileage, and optional story/overrides.
|
||||
*/
|
||||
socket.on("rr-create-early-ro", async ({ jobid, jobId, txEnvelope } = {}) => {
|
||||
const rid = resolveJobId(jobid || jobId, { jobId, jobid }, null);
|
||||
|
||||
try {
|
||||
if (!rid) throw new Error("RR early create: jobid required");
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-1} Received RR early RO creation request`, { jobid: rid });
|
||||
|
||||
// Cache txEnvelope (contains advisor, mileage, story, overrides)
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(rid),
|
||||
RRCacheEnums.txEnvelope,
|
||||
txEnvelope || {},
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-1.1} Cached txEnvelope`, { hasTxEnvelope: !!txEnvelope });
|
||||
|
||||
const job = await QueryJobData({ redisHelpers }, rid);
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(rid),
|
||||
RRCacheEnums.JobData,
|
||||
job,
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-1.2} Cached JobData`, { vin: job?.v_vin, ro: job?.ro_number });
|
||||
|
||||
const adv = readAdvisorNo(
|
||||
{ txEnvelope },
|
||||
await redisHelpers.getSessionTransactionData(socket.id, getTransactionType(rid), RRCacheEnums.AdvisorNo)
|
||||
);
|
||||
|
||||
if (adv) {
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(rid),
|
||||
RRCacheEnums.AdvisorNo,
|
||||
String(adv),
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-1.3} Cached advisorNo`, { advisorNo: String(adv) });
|
||||
}
|
||||
|
||||
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
|
||||
const bodyshop = await getBodyshopForSocket({ bodyshopId, socket });
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-2} Running multi-search (Full Name + VIN)`);
|
||||
|
||||
const candidates = await rrMultiCustomerSearch({ bodyshop, job, socket, redisHelpers });
|
||||
const decorated = candidates.map((c) => (c.vinOwner != null ? c : { ...c, vinOwner: !!c.isVehicleOwner }));
|
||||
|
||||
socket.emit("rr-select-customer", decorated);
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-2.1} Emitted rr-select-customer for early RO`, {
|
||||
count: decorated.length,
|
||||
anyOwner: decorated.some((c) => c.vinOwner || c.isVehicleOwner)
|
||||
});
|
||||
} catch (error) {
|
||||
CreateRRLogEvent(socket, "ERROR", `Error during RR early RO creation (prepare)`, {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
jobid: rid
|
||||
});
|
||||
|
||||
try {
|
||||
socket.emit("export-failed", { vendor: "rr", jobId: rid, error: error.message });
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* NEW: Early RO Customer Selected Event
|
||||
* Handles customer selection for early RO creation and creates minimal RO.
|
||||
*/
|
||||
socket.on("rr-early-customer-selected", async ({ jobid, jobId, selectedCustomerId, custNo, create } = {}, ack) => {
|
||||
const rid = resolveJobId(jobid || jobId, { jobid, jobId }, null);
|
||||
let bodyshop = null;
|
||||
let job = null;
|
||||
let createdCustomer = false;
|
||||
|
||||
try {
|
||||
if (!rid) throw new Error("jobid required");
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-3} rr-early-customer-selected`, {
|
||||
jobid: rid,
|
||||
custNo,
|
||||
selectedCustomerId,
|
||||
create: !!create
|
||||
});
|
||||
|
||||
const ns = getTransactionType(rid);
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-3.0a} Raw parameters received`, {
|
||||
custNo: custNo,
|
||||
custNoType: typeof custNo,
|
||||
selectedCustomerId: selectedCustomerId,
|
||||
create: create
|
||||
});
|
||||
|
||||
let selectedCustNo =
|
||||
(custNo && String(custNo)) ||
|
||||
(selectedCustomerId && String(selectedCustomerId)) ||
|
||||
(await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.SelectedCustomer));
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-3.0b} After initial resolution`, {
|
||||
selectedCustNo,
|
||||
selectedCustNoType: typeof selectedCustNo
|
||||
});
|
||||
|
||||
// Filter out invalid values
|
||||
if (selectedCustNo === "undefined" || selectedCustNo === "null" || (selectedCustNo && selectedCustNo.trim() === "")) {
|
||||
selectedCustNo = null;
|
||||
}
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-3.0} Resolved customer selection`, {
|
||||
selectedCustNo,
|
||||
willCreateNew: create === true || !selectedCustNo
|
||||
});
|
||||
|
||||
job = await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.JobData);
|
||||
|
||||
const txEnvelope = (await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.txEnvelope)) || {};
|
||||
|
||||
if (!job) throw new Error("Staged JobData not found (run rr-create-early-ro first).");
|
||||
|
||||
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
|
||||
|
||||
bodyshop = await getBodyshopForSocket({ bodyshopId, socket });
|
||||
|
||||
// Create customer (if requested or none chosen)
|
||||
if (create === true || !selectedCustNo) {
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-3.1} Creating RR customer`);
|
||||
|
||||
const created = await createRRCustomer({ bodyshop, job, socket });
|
||||
selectedCustNo = String(created?.customerNo || "");
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-3.2} Created customer`, {
|
||||
custNo: selectedCustNo,
|
||||
createdCustomerNo: created?.customerNo
|
||||
});
|
||||
|
||||
if (!selectedCustNo || selectedCustNo === "undefined" || selectedCustNo.trim() === "") {
|
||||
throw new Error("RR create customer returned no valid custNo");
|
||||
}
|
||||
|
||||
createdCustomer = true;
|
||||
}
|
||||
|
||||
// VIN owner pre-check
|
||||
try {
|
||||
const vehQ = makeVehicleSearchPayloadFromJob(job);
|
||||
if (vehQ && vehQ.kind === "vin" && job?.v_vin) {
|
||||
const vinResponse = await rrCombinedSearch(bodyshop, vehQ);
|
||||
|
||||
CreateRRLogEvent(socket, "SILLY", `VIN owner pre-check response (early RO)`, { response: vinResponse });
|
||||
|
||||
const vinBlocks = Array.isArray(vinResponse?.data) ? vinResponse.data : [];
|
||||
|
||||
try {
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.VINCandidates,
|
||||
vinBlocks,
|
||||
defaultRRTTL
|
||||
);
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
|
||||
const ownersSet = ownersFromVinBlocks(vinBlocks, job.v_vin);
|
||||
|
||||
if (ownersSet?.size) {
|
||||
const sel = String(selectedCustNo);
|
||||
|
||||
if (!ownersSet.has(sel)) {
|
||||
const [existingOwner] = Array.from(ownersSet).map(String);
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-3.2a} VIN exists; switching to VIN owner`, {
|
||||
vin: job.v_vin,
|
||||
selected: sel,
|
||||
existingOwner
|
||||
});
|
||||
selectedCustNo = existingOwner;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
CreateRRLogEvent(socket, "WARN", `VIN owner pre-check failed; continuing with selected customer (early RO)`, {
|
||||
error: e?.message
|
||||
});
|
||||
}
|
||||
|
||||
// Cache final/effective customer selection
|
||||
const effectiveCustNo = String(selectedCustNo);
|
||||
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.SelectedCustomer,
|
||||
effectiveCustNo,
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-3.3} Cached selected customer`, { custNo: effectiveCustNo });
|
||||
|
||||
// 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
|
||||
const tx = {
|
||||
jobData: {
|
||||
...job,
|
||||
vin: job?.v_vin
|
||||
},
|
||||
txEnvelope
|
||||
};
|
||||
|
||||
const vin = resolveVin({ tx, job });
|
||||
|
||||
if (!vin) {
|
||||
CreateRRLogEvent(socket, "ERROR", "{EARLY-3.x} No VIN found for ensureRRServiceVehicle", { jobid: rid });
|
||||
throw new Error("ensureRRServiceVehicle: vin required");
|
||||
}
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", "{EARLY-3.4} ensureRRServiceVehicle: starting", {
|
||||
jobid: rid,
|
||||
selectedCustomerNo: effectiveCustNo,
|
||||
vin,
|
||||
dealerNumber: routing.dealerNumber,
|
||||
storeNumber: routing.storeNumber,
|
||||
areaNumber: routing.areaNumber
|
||||
});
|
||||
|
||||
const ensured = await ensureRRServiceVehicle({
|
||||
client,
|
||||
routing,
|
||||
bodyshop,
|
||||
selectedCustomerNo: effectiveCustNo,
|
||||
custNo: effectiveCustNo,
|
||||
customerNo: effectiveCustNo,
|
||||
vin,
|
||||
job,
|
||||
socket,
|
||||
redisHelpers
|
||||
});
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", "{EARLY-3.5} ensureRRServiceVehicle: done", ensured);
|
||||
|
||||
const cachedAdvisor = await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.AdvisorNo);
|
||||
const advisorNo = readAdvisorNo({ txEnvelope }, cachedAdvisor);
|
||||
|
||||
if (!advisorNo) {
|
||||
CreateRRLogEvent(socket, "ERROR", `Advisor is required (advisorNo) for early RO`);
|
||||
await insertRRFailedExportLog({
|
||||
socket,
|
||||
jobId: rid,
|
||||
job,
|
||||
bodyshop,
|
||||
error: new Error("Advisor is required (advisorNo)."),
|
||||
classification: { errorCode: "RR_MISSING_ADVISOR", friendlyMessage: "Advisor is required." }
|
||||
});
|
||||
socket.emit("export-failed", { vendor: "rr", jobId: rid, error: "Advisor is required (advisorNo)." });
|
||||
return ack?.({ ok: false, error: "Advisor is required (advisorNo)." });
|
||||
}
|
||||
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.AdvisorNo,
|
||||
String(advisorNo),
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
// CREATE MINIMAL RO (early creation)
|
||||
CreateRRLogEvent(socket, "DEBUG", `{EARLY-4} Creating minimal RR RO`);
|
||||
const result = await createMinimalRRRepairOrder({
|
||||
bodyshop,
|
||||
job,
|
||||
selectedCustomer: { customerNo: effectiveCustNo, custNo: effectiveCustNo },
|
||||
advisorNo: String(advisorNo),
|
||||
txEnvelope,
|
||||
socket,
|
||||
svId: ensured?.svId || null
|
||||
});
|
||||
|
||||
// Cache raw export result + pending RO number
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.ExportResult,
|
||||
result || {},
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
if (result?.success) {
|
||||
const data = result?.data || {};
|
||||
|
||||
// Prefer explicit return from export function; then fall back to fields
|
||||
const dmsRoNo = result?.roNo ?? data?.dmsRoNo ?? null;
|
||||
|
||||
const outsdRoNo = data?.outsdRoNo ?? job?.ro_number ?? job?.id ?? null;
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", "Early RO created - checking dmsRoNo", {
|
||||
dmsRoNo,
|
||||
resultRoNo: result?.roNo,
|
||||
dataRoNo: data?.dmsRoNo,
|
||||
jobId: rid
|
||||
});
|
||||
|
||||
// ✅ Persist DMS RO number, customer ID, and advisor ID on the job
|
||||
if (dmsRoNo) {
|
||||
CreateRRLogEvent(socket, "DEBUG", "Calling setJobDmsIdForSocket", {
|
||||
jobId: rid,
|
||||
dmsId: dmsRoNo,
|
||||
customerId: effectiveCustNo,
|
||||
advisorId: String(advisorNo)
|
||||
});
|
||||
await setJobDmsIdForSocket({
|
||||
socket,
|
||||
jobId: rid,
|
||||
dmsId: dmsRoNo,
|
||||
dmsCustomerId: effectiveCustNo,
|
||||
dmsAdvisorId: String(advisorNo)
|
||||
});
|
||||
} else {
|
||||
CreateRRLogEvent(socket, "WARN", "RR early RO creation succeeded but no DMS RO number was returned", {
|
||||
jobId: rid,
|
||||
resultPreview: {
|
||||
roNo: result?.roNo,
|
||||
data: {
|
||||
dmsRoNo: data?.dmsRoNo,
|
||||
outsdRoNo: data?.outsdRoNo
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.PendingRO,
|
||||
{
|
||||
outsdRoNo,
|
||||
dmsRoNo,
|
||||
customerNo: String(effectiveCustNo),
|
||||
advisorNo: String(advisorNo),
|
||||
vin: job?.v_vin || null,
|
||||
earlyRoCreated: true // Flag to indicate this was an early RO
|
||||
},
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
CreateRRLogEvent(socket, "INFO", `{EARLY-5} Minimal RO created successfully`, {
|
||||
dmsRoNo: dmsRoNo || null,
|
||||
outsdRoNo: outsdRoNo || null
|
||||
});
|
||||
|
||||
// Mark success in export logs
|
||||
await markRRExportSuccess({
|
||||
socket,
|
||||
jobId: rid,
|
||||
job,
|
||||
bodyshop,
|
||||
result,
|
||||
isEarlyRo: true
|
||||
});
|
||||
|
||||
// Tell FE that early RO was created
|
||||
socket.emit("rr-early-ro-created", { jobId: rid, dmsRoNo, outsdRoNo });
|
||||
|
||||
// Emit result
|
||||
socket.emit("rr-create-early-ro:result", { jobId: rid, bodyshopId: bodyshop?.id, result });
|
||||
|
||||
// ACK with RO details
|
||||
ack?.({
|
||||
ok: true,
|
||||
dmsRoNo,
|
||||
outsdRoNo,
|
||||
result,
|
||||
custNo: String(effectiveCustNo),
|
||||
createdCustomer,
|
||||
earlyRoCreated: true
|
||||
});
|
||||
} else {
|
||||
// classify & fail
|
||||
const tx = result?.statusBlocks?.transaction;
|
||||
|
||||
const vendorStatusCode = Number(
|
||||
result?.roStatus?.statusCode ?? result?.roStatus?.StatusCode ?? tx?.statusCode ?? tx?.StatusCode
|
||||
);
|
||||
|
||||
const vendorMessage =
|
||||
result?.roStatus?.message ??
|
||||
result?.roStatus?.Message ??
|
||||
tx?.message ??
|
||||
tx?.Message ??
|
||||
result?.error ??
|
||||
"RR early RO creation failed";
|
||||
|
||||
const cls = classifyRRVendorError({
|
||||
code: vendorStatusCode,
|
||||
message: vendorMessage
|
||||
});
|
||||
|
||||
CreateRRLogEvent(socket, "ERROR", `Early RO creation failed`, {
|
||||
roStatus: result?.roStatus,
|
||||
statusBlocks: result?.statusBlocks,
|
||||
classification: cls
|
||||
});
|
||||
|
||||
await insertRRFailedExportLog({
|
||||
socket,
|
||||
jobId: rid,
|
||||
job,
|
||||
bodyshop,
|
||||
error: new Error(cls.friendlyMessage || result?.error || "RR early RO creation failed"),
|
||||
classification: cls,
|
||||
result
|
||||
});
|
||||
|
||||
socket.emit("export-failed", {
|
||||
vendor: "rr",
|
||||
jobId: rid,
|
||||
error: cls?.friendlyMessage || result?.error || "RR early RO creation failed",
|
||||
...cls
|
||||
});
|
||||
|
||||
ack?.({
|
||||
ok: false,
|
||||
error: cls.friendlyMessage || result?.error || "RR early RO creation failed",
|
||||
result,
|
||||
classification: cls
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
const cls = classifyRRVendorError(error);
|
||||
|
||||
CreateRRLogEvent(socket, "ERROR", `Error during RR early RO creation (customer-selected)`, {
|
||||
error: error.message,
|
||||
vendorStatusCode: cls.vendorStatusCode,
|
||||
code: cls.errorCode,
|
||||
friendly: cls.friendlyMessage,
|
||||
stack: error.stack,
|
||||
jobid: rid
|
||||
});
|
||||
|
||||
try {
|
||||
if (!bodyshop || !job) {
|
||||
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
|
||||
bodyshop = bodyshop || (await getBodyshopForSocket({ bodyshopId, socket }));
|
||||
job =
|
||||
job ||
|
||||
(await redisHelpers.getSessionTransactionData(socket.id, getTransactionType(rid), RRCacheEnums.JobData));
|
||||
}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
|
||||
await insertRRFailedExportLog({
|
||||
socket,
|
||||
jobId: rid,
|
||||
job,
|
||||
bodyshop,
|
||||
error,
|
||||
classification: cls
|
||||
});
|
||||
|
||||
try {
|
||||
socket.emit("export-failed", {
|
||||
vendor: "rr",
|
||||
jobId: rid,
|
||||
error: error.message,
|
||||
...cls
|
||||
});
|
||||
socket.emit("rr-user-notice", { jobId: rid, ...cls });
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
|
||||
ack?.({ ok: false, error: cls.friendlyMessage || error.message, classification: cls });
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("rr-export-job", async ({ jobid, jobId, txEnvelope } = {}, ack) => {
|
||||
const rid = resolveJobId(jobid || jobId, { jobId, jobid }, null);
|
||||
|
||||
try {
|
||||
@@ -422,6 +932,139 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
|
||||
const bodyshop = await getBodyshopForSocket({ bodyshopId, socket });
|
||||
|
||||
// Check if this job already has an early RO - if so, use stored IDs and skip customer search
|
||||
const hasEarlyRO = !!job?.dms_id;
|
||||
|
||||
if (hasEarlyRO) {
|
||||
CreateRRLogEvent(socket, "DEBUG", `{2} Early RO exists - using stored customer/advisor`, {
|
||||
dms_id: job.dms_id,
|
||||
dms_customer_id: job.dms_customer_id,
|
||||
dms_advisor_id: job.dms_advisor_id
|
||||
});
|
||||
|
||||
// Cache the stored customer/advisor IDs for the next step
|
||||
if (job.dms_customer_id) {
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(rid),
|
||||
RRCacheEnums.SelectedCustomer,
|
||||
String(job.dms_customer_id),
|
||||
defaultRRTTL
|
||||
);
|
||||
}
|
||||
if (job.dms_advisor_id) {
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(rid),
|
||||
RRCacheEnums.AdvisorNo,
|
||||
String(job.dms_advisor_id),
|
||||
defaultRRTTL
|
||||
);
|
||||
}
|
||||
|
||||
// Emit empty customer list to frontend (won't show modal)
|
||||
socket.emit("rr-select-customer", []);
|
||||
|
||||
// Continue directly with the export by calling the selected customer handler logic inline
|
||||
// This is essentially the same as if user selected the stored customer
|
||||
const selectedCustNo = job.dms_customer_id;
|
||||
|
||||
if (!selectedCustNo) {
|
||||
throw new Error("Early RO exists but no customer ID stored");
|
||||
}
|
||||
|
||||
// Continue with ensureRRServiceVehicle and export (same as rr-selected-customer handler)
|
||||
const { client, opts } = await buildClientAndOpts(bodyshop);
|
||||
const routing = opts?.routing || client?.opts?.routing || null;
|
||||
if (!routing?.dealerNumber) throw new Error("ensureRRServiceVehicle: routing.dealerNumber required");
|
||||
|
||||
const tx = {
|
||||
jobData: {
|
||||
...job,
|
||||
vin: job?.v_vin
|
||||
},
|
||||
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");
|
||||
}
|
||||
|
||||
const ensured = await ensureRRServiceVehicle({
|
||||
client,
|
||||
routing,
|
||||
bodyshop,
|
||||
selectedCustomerNo: String(selectedCustNo),
|
||||
custNo: String(selectedCustNo),
|
||||
customerNo: String(selectedCustNo),
|
||||
vin,
|
||||
job,
|
||||
socket,
|
||||
redisHelpers
|
||||
});
|
||||
|
||||
const advisorNo = job.dms_advisor_id || readAdvisorNo({ txEnvelope }, await redisHelpers.getSessionTransactionData(socket.id, getTransactionType(rid), RRCacheEnums.AdvisorNo));
|
||||
|
||||
if (!advisorNo) {
|
||||
throw new Error("Advisor is required (advisorNo).");
|
||||
}
|
||||
|
||||
// UPDATE existing RO with full data
|
||||
CreateRRLogEvent(socket, "DEBUG", `{4} Updating existing RR RO with full data`, { dmsRoNo: job.dms_id });
|
||||
const result = await updateRRRepairOrderWithFullData({
|
||||
bodyshop,
|
||||
job,
|
||||
selectedCustomer: { customerNo: String(selectedCustNo), custNo: String(selectedCustNo) },
|
||||
advisorNo: String(advisorNo),
|
||||
txEnvelope,
|
||||
socket,
|
||||
svId: ensured?.svId || null,
|
||||
roNo: job.dms_id
|
||||
});
|
||||
|
||||
if (!result?.success) {
|
||||
throw new Error(result?.roStatus?.message || "Failed to update RR Repair Order");
|
||||
}
|
||||
|
||||
const dmsRoNo = result?.roNo ?? result?.data?.dmsRoNo ?? job.dms_id;
|
||||
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(rid),
|
||||
RRCacheEnums.ExportResult,
|
||||
result || {},
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(rid),
|
||||
RRCacheEnums.PendingRO,
|
||||
{
|
||||
outsdRoNo: result?.data?.outsdRoNo ?? job?.ro_number ?? job?.id ?? null,
|
||||
dmsRoNo,
|
||||
customerNo: String(selectedCustNo),
|
||||
advisorNo: String(advisorNo),
|
||||
vin: job?.v_vin || null,
|
||||
isUpdate: true
|
||||
},
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
CreateRRLogEvent(socket, "INFO", `RR Repair Order updated successfully`, {
|
||||
dmsRoNo,
|
||||
jobId: rid
|
||||
});
|
||||
|
||||
// For early RO flow, only emit validation-required (not export-job:result)
|
||||
// since the export is not complete yet - we're just waiting for validation
|
||||
socket.emit("rr-validation-required", { dmsRoNo, jobId: rid });
|
||||
|
||||
return ack?.({ ok: true, skipCustomerSelection: true, dmsRoNo });
|
||||
}
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `{2} Running multi-search (Full Name + VIN)`);
|
||||
|
||||
const candidates = await rrMultiCustomerSearch({ bodyshop, job, socket, redisHelpers });
|
||||
@@ -620,17 +1263,59 @@ const registerRREvents = ({ socket, redisHelpers }) => {
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
// CREATE/UPDATE (first step only)
|
||||
CreateRRLogEvent(socket, "DEBUG", `{4} Performing RR create/update (step 1)`);
|
||||
const result = await exportJobToRR({
|
||||
bodyshop,
|
||||
job,
|
||||
selectedCustomer: { customerNo: effectiveCustNo, custNo: effectiveCustNo },
|
||||
advisorNo: String(advisorNo),
|
||||
txEnvelope,
|
||||
socket,
|
||||
svId: ensured?.svId || null
|
||||
});
|
||||
// Check if this job already has an early RO created (check job.dms_id)
|
||||
// If so, we'll use stored customer/advisor IDs and do a full data UPDATE instead of CREATE
|
||||
const existingDmsId = job?.dms_id || null;
|
||||
const shouldUpdate = !!existingDmsId;
|
||||
|
||||
// When updating an early RO, use stored customer/advisor IDs
|
||||
let finalEffectiveCustNo = effectiveCustNo;
|
||||
let finalAdvisorNo = advisorNo;
|
||||
|
||||
if (shouldUpdate && job?.dms_customer_id) {
|
||||
CreateRRLogEvent(socket, "DEBUG", `Using stored customer ID from early RO`, {
|
||||
storedCustomerId: job.dms_customer_id,
|
||||
originalCustomerId: effectiveCustNo
|
||||
});
|
||||
finalEffectiveCustNo = String(job.dms_customer_id);
|
||||
}
|
||||
|
||||
if (shouldUpdate && job?.dms_advisor_id) {
|
||||
CreateRRLogEvent(socket, "DEBUG", `Using stored advisor ID from early RO`, {
|
||||
storedAdvisorId: job.dms_advisor_id,
|
||||
originalAdvisorId: advisorNo
|
||||
});
|
||||
finalAdvisorNo = String(job.dms_advisor_id);
|
||||
}
|
||||
|
||||
let result;
|
||||
|
||||
if (shouldUpdate) {
|
||||
// UPDATE existing RO with full data
|
||||
CreateRRLogEvent(socket, "DEBUG", `{4} Updating existing RR RO with full data`, { dmsRoNo: existingDmsId });
|
||||
result = await updateRRRepairOrderWithFullData({
|
||||
bodyshop,
|
||||
job,
|
||||
selectedCustomer: { customerNo: finalEffectiveCustNo, custNo: finalEffectiveCustNo },
|
||||
advisorNo: String(finalAdvisorNo),
|
||||
txEnvelope,
|
||||
socket,
|
||||
svId: ensured?.svId || null,
|
||||
roNo: existingDmsId
|
||||
});
|
||||
} else {
|
||||
// CREATE new RO (legacy flow - full data on first create)
|
||||
CreateRRLogEvent(socket, "DEBUG", `{4} Performing RR create (step 1 - full data)`);
|
||||
result = await exportJobToRR({
|
||||
bodyshop,
|
||||
job,
|
||||
selectedCustomer: { customerNo: finalEffectiveCustNo, custNo: finalEffectiveCustNo },
|
||||
advisorNo: String(finalAdvisorNo),
|
||||
txEnvelope,
|
||||
socket,
|
||||
svId: ensured?.svId || null
|
||||
});
|
||||
}
|
||||
|
||||
// Cache raw export result + pending RO number for finalize
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
|
||||
Reference in New Issue
Block a user