feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Cashiering Checkpoint
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
const CreateRRLogEvent = require("./rr-logger-event");
|
||||
const { rrCombinedSearch, rrGetAdvisors, buildClientAndOpts } = require("./rr-lookup");
|
||||
const { QueryJobData } = require("./rr-job-helpers");
|
||||
const { exportJobToRR } = require("./rr-job-export");
|
||||
const { exportJobToRR, finalizeRRRepairOrder } = require("./rr-job-export");
|
||||
const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default;
|
||||
const { createRRCustomer } = require("./rr-customers");
|
||||
const { ensureRRServiceVehicle } = require("./rr-service-vehicles");
|
||||
@@ -317,7 +317,7 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
}
|
||||
});
|
||||
|
||||
// ================= Fortellis-style two-step export =================
|
||||
// ================= Fortellis-style two-step export (RR only) =================
|
||||
// 1) Stage export -> search (Full Name + VIN) -> emit rr-select-customer
|
||||
socket.on("rr-export-job", async ({ jobid, jobId, txEnvelope } = {}) => {
|
||||
const rid = resolveJobId(jobid || jobId, { jobId, jobid }, null);
|
||||
@@ -385,7 +385,7 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
}
|
||||
});
|
||||
|
||||
// 2) Selection (or create) -> ensure vehicle -> export
|
||||
// 2) Selection (or create) -> ensure vehicle -> CREATE RO (do not mark exported)
|
||||
socket.on("rr-selected-customer", async ({ jobid, jobId, selectedCustomerId, custNo, create } = {}, ack) => {
|
||||
const rid = resolveJobId(jobid || jobId, { jobid, jobId }, null);
|
||||
let bodyshop = null;
|
||||
@@ -515,7 +515,6 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
client,
|
||||
routing,
|
||||
bodyshop,
|
||||
// Normalize for any internal checks:
|
||||
selectedCustomerNo: effectiveCustNo,
|
||||
custNo: effectiveCustNo,
|
||||
customerNo: effectiveCustNo,
|
||||
@@ -532,7 +531,6 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
const advisorNo = readAdvisorNo({ txEnvelope }, cachedAdvisor);
|
||||
if (!advisorNo) {
|
||||
CreateRRLogEvent(socket, "ERROR", `Advisor is required (advisorNo)`);
|
||||
// Failure log (no advisor)
|
||||
await insertRRFailedExportLog({
|
||||
socket,
|
||||
jobId: rid,
|
||||
@@ -552,8 +550,8 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
// Export
|
||||
CreateRRLogEvent(socket, "DEBUG", `{4} Performing RR export`);
|
||||
// CREATE/UPDATE (first step only)
|
||||
CreateRRLogEvent(socket, "DEBUG", `{4} Performing RR create/update (step 1)`);
|
||||
const result = await exportJobToRR({
|
||||
bodyshop,
|
||||
job,
|
||||
@@ -563,22 +561,60 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
socket
|
||||
});
|
||||
|
||||
if (result?.success) {
|
||||
CreateRRLogEvent(socket, "DEBUG", `{5} Export success`, { roStatus: result.roStatus });
|
||||
// Cache raw export result + pending RO number for finalize
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.ExportResult,
|
||||
result || {},
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
// ✅ Mark exported + success log (with metadata)
|
||||
await markRRExportSuccess({
|
||||
socket,
|
||||
jobId: rid,
|
||||
job,
|
||||
bodyshop,
|
||||
result
|
||||
if (result?.success) {
|
||||
const data = result?.data || {};
|
||||
|
||||
// Prefer explicit return from export function; then fall back to fields
|
||||
const dmsRoNo =
|
||||
result?.roNo ?? data?.dmsRoNo ?? data?.DMSRoNo ?? data?.roStatus?.dmsRoNo ?? data?.roStatus?.DMSRoNo ?? null;
|
||||
|
||||
const outsdRoNo =
|
||||
data?.outsdRoNo ??
|
||||
data?.OutsdRoNo ??
|
||||
data?.roStatus?.outsdRoNo ??
|
||||
data?.roStatus?.OutsdRoNo ??
|
||||
job?.ro_number ??
|
||||
job?.id ??
|
||||
null;
|
||||
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.PendingRO,
|
||||
{
|
||||
outsdRoNo,
|
||||
dmsRoNo,
|
||||
customerNo: String(effectiveCustNo),
|
||||
advisorNo: String(advisorNo),
|
||||
vin: job?.v_vin || null
|
||||
},
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
CreateRRLogEvent(socket, "INFO", `{5} RO created. Waiting for cashiering.`, {
|
||||
dmsRoNo: dmsRoNo || null,
|
||||
outsdRoNo: outsdRoNo || null
|
||||
});
|
||||
|
||||
socket.emit("export-success", { vendor: "rr", jobId: rid, roStatus: result.roStatus });
|
||||
ack?.({ ok: true, result });
|
||||
// Tell FE to prompt for "Finished/Close"
|
||||
socket.emit("rr-cashiering-required", { jobId: rid, dmsRoNo, outsdRoNo });
|
||||
|
||||
// Still emit info result if you want
|
||||
socket.emit("rr-export-job:result", { jobId: rid, bodyshopId: bodyshop?.id, result });
|
||||
|
||||
// ACK but indicate it's pending finalize
|
||||
ack?.({ ok: true, pendingFinalize: true, dmsRoNo, outsdRoNo, result });
|
||||
} else {
|
||||
// NEW: classify vendor status for a friendly FE message
|
||||
// classify & fail (no finalize)
|
||||
const vendorStatusCode = Number(
|
||||
result?.roStatus?.statusCode ?? result?.roStatus?.StatusCode ?? result?.statusBlocks?.transaction?.statusCode
|
||||
);
|
||||
@@ -587,12 +623,11 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
message: result?.roStatus?.message ?? result?.roStatus?.Message ?? result?.error ?? "RR export failed"
|
||||
});
|
||||
|
||||
CreateRRLogEvent(socket, "ERROR", `Export failed`, {
|
||||
CreateRRLogEvent(socket, "ERROR", `Export failed (step 1)`, {
|
||||
roStatus: result?.roStatus,
|
||||
classification: cls
|
||||
});
|
||||
|
||||
// ❌ Failure log (with classification + bits of response)
|
||||
await insertRRFailedExportLog({
|
||||
socket,
|
||||
jobId: rid,
|
||||
@@ -609,8 +644,6 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
error: cls?.friendlyMessage || result?.error || "RR export failed",
|
||||
...cls
|
||||
});
|
||||
// Optional: a user-focused channel if you want to show inline banners
|
||||
socket.emit("rr-user-notice", { jobId: rid, ...cls });
|
||||
|
||||
ack?.({
|
||||
ok: false,
|
||||
@@ -619,15 +652,6 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
classification: cls
|
||||
});
|
||||
}
|
||||
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.ExportResult,
|
||||
result || {},
|
||||
defaultRRTTL
|
||||
);
|
||||
socket.emit("rr-export-job:result", { jobId: rid, bodyshopId: bodyshop?.id, result });
|
||||
} catch (error) {
|
||||
const cls = classifyRRVendorError(error);
|
||||
|
||||
@@ -640,9 +664,7 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
jobid: rid
|
||||
});
|
||||
|
||||
// ❌ Failure log for thrown error path
|
||||
try {
|
||||
// Load bodyshop/job if not loaded yet (best-effort)
|
||||
if (!bodyshop || !job) {
|
||||
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
|
||||
bodyshop = bodyshop || (await getBodyshopForSocket({ bodyshopId, socket }));
|
||||
@@ -651,7 +673,7 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
(await redisHelpers.getSessionTransactionData(socket.id, getTransactionType(rid), RRCacheEnums.JobData));
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
//
|
||||
}
|
||||
|
||||
await insertRRFailedExportLog({
|
||||
@@ -670,10 +692,155 @@ function registerRREvents({ socket, redisHelpers }) {
|
||||
error: error.message,
|
||||
...cls
|
||||
});
|
||||
// Optional UX hook for inline banners/toasts
|
||||
socket.emit("rr-user-notice", { jobId: rid, ...cls });
|
||||
} catch {
|
||||
/* ignore */
|
||||
//
|
||||
}
|
||||
|
||||
ack?.({ ok: false, error: cls.friendlyMessage || error.message, classification: cls });
|
||||
}
|
||||
});
|
||||
|
||||
// 3) Finalize -> updateRepairOrder(finalUpdate: "Y") -> mark exported
|
||||
socket.on("rr-finalize-repair-order", async ({ jobid, jobId } = {}, ack) => {
|
||||
const rid = resolveJobId(jobid || jobId, { jobid, jobId }, null);
|
||||
let bodyshop = null;
|
||||
let job = null;
|
||||
|
||||
try {
|
||||
if (!rid) throw new Error("jobid required for finalize");
|
||||
|
||||
const ns = getTransactionType(rid);
|
||||
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
|
||||
bodyshop = await getBodyshopForSocket({ bodyshopId, socket });
|
||||
|
||||
job = await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.JobData);
|
||||
if (!job) job = await QueryJobData({ redisHelpers }, rid);
|
||||
|
||||
const pending = await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.PendingRO);
|
||||
const advisorNo = await redisHelpers.getSessionTransactionData(socket.id, ns, RRCacheEnums.AdvisorNo);
|
||||
const selectedCustomerNo = await redisHelpers.getSessionTransactionData(
|
||||
socket.id,
|
||||
ns,
|
||||
RRCacheEnums.SelectedCustomer
|
||||
);
|
||||
|
||||
if (!advisorNo) throw new Error("Advisor missing in session");
|
||||
if (!selectedCustomerNo) throw new Error("Customer number missing in session");
|
||||
|
||||
// Prefer cached outsdRoNo; fall back to our deterministic external number
|
||||
const outsdRoNo = pending?.outsdRoNo ?? job?.ro_number ?? job?.id ?? null;
|
||||
// Prefer DMS RO for update, but finalize() will safely fall back to Outsd if missing
|
||||
const dmsRoNo = pending?.dmsRoNo ?? pending?.roNo ?? null;
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `{6} Finalizing RR RO`, {
|
||||
jobId: rid,
|
||||
outsdRoNo,
|
||||
dmsRoNo,
|
||||
advisorNo,
|
||||
customerNo: selectedCustomerNo
|
||||
});
|
||||
|
||||
const finalizeResult = await finalizeRRRepairOrder({
|
||||
bodyshop,
|
||||
job,
|
||||
advisorNo: String(advisorNo),
|
||||
customerNo: String(selectedCustomerNo),
|
||||
roNo: dmsRoNo, // ✅ RR requires roNo; finalize() will fall back to outsdRoNo if this is absent
|
||||
vin: pending?.vin,
|
||||
socket
|
||||
});
|
||||
|
||||
if (finalizeResult?.success) {
|
||||
CreateRRLogEvent(socket, "INFO", `{7} Finalize success; marking exported`, { dmsRoNo, outsdRoNo });
|
||||
|
||||
// ✅ Mark exported + success log
|
||||
await markRRExportSuccess({
|
||||
socket,
|
||||
jobId: rid,
|
||||
job,
|
||||
bodyshop,
|
||||
result: finalizeResult
|
||||
});
|
||||
|
||||
// Clean pending key
|
||||
try {
|
||||
await redisHelpers.setSessionTransactionData(socket.id, ns, RRCacheEnums.PendingRO, null, 1);
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
|
||||
socket.emit("export-success", { vendor: "rr", jobId: rid, roStatus: finalizeResult?.roStatus });
|
||||
ack?.({ ok: true, result: finalizeResult });
|
||||
} else {
|
||||
const vendorStatusCode = Number(
|
||||
finalizeResult?.roStatus?.statusCode ??
|
||||
finalizeResult?.roStatus?.StatusCode ??
|
||||
finalizeResult?.statusBlocks?.transaction?.statusCode
|
||||
);
|
||||
const cls = classifyRRVendorError({
|
||||
code: vendorStatusCode,
|
||||
message:
|
||||
finalizeResult?.roStatus?.message ??
|
||||
finalizeResult?.roStatus?.Message ??
|
||||
finalizeResult?.error ??
|
||||
"RR finalize failed"
|
||||
});
|
||||
|
||||
await insertRRFailedExportLog({
|
||||
socket,
|
||||
jobId: rid,
|
||||
job,
|
||||
bodyshop,
|
||||
error: new Error(cls.friendlyMessage || finalizeResult?.error || "RR finalize failed"),
|
||||
classification: cls,
|
||||
result: finalizeResult
|
||||
});
|
||||
|
||||
socket.emit("export-failed", {
|
||||
vendor: "rr",
|
||||
jobId: rid,
|
||||
error: cls?.friendlyMessage || finalizeResult?.error || "RR finalize failed",
|
||||
...cls
|
||||
});
|
||||
ack?.({ ok: false, error: cls.friendlyMessage || "RR finalize failed", classification: cls });
|
||||
}
|
||||
} catch (error) {
|
||||
const cls = classifyRRVendorError(error);
|
||||
CreateRRLogEvent(socket, "ERROR", `Error during RR finalize`, {
|
||||
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 });
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
|
||||
ack?.({ ok: false, error: cls.friendlyMessage || error.message, classification: cls });
|
||||
|
||||
Reference in New Issue
Block a user