feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Expanded Logs / Formatting change

This commit is contained in:
Dave
2025-11-12 17:01:54 -05:00
parent 556cd993b9
commit 90f653c0b7
13 changed files with 444 additions and 254 deletions

View File

@@ -6,10 +6,7 @@ const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").defa
const { createRRCustomer } = require("./rr-customers");
const { ensureRRServiceVehicle } = require("./rr-service-vehicles");
const { classifyRRVendorError } = require("./rr-errors");
// NEW: export logs (success/failure) parity with Fortellis/PBS
const { markRRExportSuccess, insertRRFailedExportLog } = require("./rr-export-logs");
const {
makeVehicleSearchPayloadFromJob,
ownersFromVinBlocks,
@@ -19,7 +16,6 @@ const {
defaultRRTTL,
RRCacheEnums
} = require("./rr-utils");
const { GraphQLClient } = require("graphql-request");
const queries = require("../graphql-client/queries");
@@ -36,9 +32,8 @@ const ADVISORS_CACHE_TTL = 7 * 24 * 60 * 60; // seconds
* @param job
* @returns {*|null}
*/
function resolveJobId(explicit, payload, job) {
return explicit || payload?.jobId || payload?.jobid || job?.id || job?.jobId || job?.jobid || null;
}
const resolveJobId = (explicit, payload, job) =>
explicit || payload?.jobId || payload?.jobid || job?.id || job?.jobId || job?.jobid || null;
/**
* Resolve VIN from tx/job shapes
@@ -46,18 +41,15 @@ function resolveJobId(explicit, payload, job) {
* @param job
* @returns {*|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 || null;
}
const resolveVin = ({ tx, job }) => tx?.jobData?.vin || job?.v_vin || null;
/**
* Sort vehicle owners first in the list, preserving original order otherwise.
* @param list
* @returns {*}
*/
function sortVehicleOwnerFirst(list) {
return list
const sortVehicleOwnerFirst = (list) =>
list
.map((v, i) => ({ v, i }))
.sort((a, b) => {
const ao = a.v?.isVehicleOwner ? 1 : 0;
@@ -66,15 +58,13 @@ function sortVehicleOwnerFirst(list) {
return a.i - b.i;
})
.map(({ v }) => v);
}
/**
* NEW: merge candidates coming from multiple queries (name + vin) by custNo.
* - keeps first non-empty name
* - preserves/ORs vinOwner/isVehicleOwner
* - keeps first non-empty address
* Merge customer candidates by custNo, combining isVehicleOwner flags and filling missing fields.
* @param items
* @returns {any[]}
*/
function mergeByCustNo(items = []) {
const mergeByCustNo = (items = []) => {
const byId = new Map();
for (const c of items) {
const id = (c?.custNo || "").trim();
@@ -93,7 +83,7 @@ function mergeByCustNo(items = []) {
}
}
return Array.from(byId.values());
}
};
/**
* Get session data or socket fallback
@@ -101,7 +91,7 @@ function mergeByCustNo(items = []) {
* @param socket
* @returns {Promise<{bodyshopId: *, email: *, sess: null}>}
*/
async function getSessionOrSocket(redisHelpers, socket) {
const getSessionOrSocket = async (redisHelpers, socket) => {
let sess = null;
try {
sess = await redisHelpers.getSessionData(socket.id);
@@ -112,7 +102,7 @@ async function getSessionOrSocket(redisHelpers, socket) {
const email = sess?.email ?? socket.user?.email;
if (!bodyshopId) throw new Error("No bodyshopId (session/socket)");
return { bodyshopId, email, sess };
}
};
/**
* Fetch bodyshop data for socket
@@ -120,7 +110,7 @@ async function getSessionOrSocket(redisHelpers, socket) {
* @param socket
* @returns {Promise<{id: string, intellipay_config: {payment_map: {amex: string}}}|{id: string, intellipay_config: null}|*>}
*/
async function getBodyshopForSocket({ bodyshopId, socket }) {
const getBodyshopForSocket = async ({ bodyshopId, socket }) => {
const endpoint = process.env.GRAPHQL_ENDPOINT;
if (!endpoint) throw new Error("GRAPHQL_ENDPOINT not configured");
const token = (socket?.data && socket.data.authToken) || (socket?.handshake?.auth && socket.handshake.auth.token);
@@ -131,12 +121,16 @@ async function getBodyshopForSocket({ bodyshopId, socket }) {
const bodyshop = res?.bodyshops_by_pk;
if (!bodyshop) throw new Error(`Bodyshop not found: ${bodyshopId}`);
return bodyshop;
}
};
/**
* Build advisors cache namespace + field (per bodyshop + routing + department)
* Build advisors cache namespace and field
* @param bodyshopId
* @param routing
* @param departmentType
* @returns {{ns: string, field: string}}
*/
function buildAdvisorsCacheNS({ bodyshopId, routing, departmentType = "B" }) {
const buildAdvisorsCacheNS = ({ bodyshopId, routing, departmentType = "B" }) => {
const dealer = routing?.dealerNumber || "unknown";
const store = routing?.storeNumber || "none";
const area = routing?.areaNumber || "none";
@@ -145,12 +139,17 @@ function buildAdvisorsCacheNS({ bodyshopId, routing, departmentType = "B" }) {
ns: `rr:advisors:${bodyshopId}:${dealer}:${store}:${area}`,
field: `dept:${dept}`
};
}
};
/**
* VIN + Full Name merge (export flow)
* Run multi-query customer search (Full Name + VIN) and merge results.
* @param bodyshop
* @param job
* @param socket
* @param redisHelpers
* @returns {Promise<*|*[]>}
*/
async function rrMultiCustomerSearch({ bodyshop, job, socket, redisHelpers }) {
const rrMultiCustomerSearch = async ({ bodyshop, job, socket, redisHelpers }) => {
const queriesList = [];
// 1) Full Name (preferred)
@@ -207,11 +206,14 @@ async function rrMultiCustomerSearch({ bodyshop, job, socket, redisHelpers }) {
const deduped = mergeByCustNo(merged);
return sortVehicleOwnerFirst(deduped);
}
};
// ---------------- register handlers ----------------
function registerRREvents({ socket, redisHelpers }) {
// ---------- Lookup passthrough ----------
/**
* Register RR socket events
* @param socket
* @param redisHelpers
*/
const registerRREvents = ({ socket, redisHelpers }) => {
socket.on("rr-lookup-combined", async ({ jobid, params } = {}, cb) => {
try {
const { bodyshopId } = await getSessionOrSocket(redisHelpers, socket);
@@ -242,7 +244,6 @@ function registerRREvents({ socket, redisHelpers }) {
}
});
// ---------- Advisors (cached) ----------
socket.on("rr-get-advisors", async (args = {}, ack) => {
const refresh = !!args?.refresh;
const requestedDept = (args?.departmentType || "B").toUpperCase();
@@ -317,8 +318,6 @@ function registerRREvents({ socket, redisHelpers }) {
}
});
// ================= 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);
try {
@@ -385,7 +384,6 @@ function registerRREvents({ socket, redisHelpers }) {
}
});
// 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;
@@ -701,7 +699,6 @@ function registerRREvents({ socket, redisHelpers }) {
}
});
// 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;
@@ -847,7 +844,6 @@ function registerRREvents({ socket, redisHelpers }) {
}
});
// ---------- Allocations (parity) ----------
socket.on("rr-calculate-allocations", async (jobid, cb) => {
try {
CreateRRLogEvent(socket, "DEBUG", "rr-calculate-allocations: begin", { jobid });
@@ -860,6 +856,6 @@ function registerRREvents({ socket, redisHelpers }) {
cb?.({ ok: false, error: e.message });
}
});
}
};
module.exports = registerRREvents;