feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint

This commit is contained in:
Dave
2025-11-04 11:19:20 -05:00
parent 65e26ed5c9
commit 3d24d44274
23 changed files with 812 additions and 701 deletions

View File

@@ -1,24 +1,145 @@
const { withClient } = require("./withClient");
// server/rr/rr-lookup.js
// Reynolds & Reynolds lookup helpers that adapt our bodyshop record to the RR client
async function getAdvisors({ bodyshopId, ...criteria }) {
return withClient(bodyshopId, async (client, routing) => {
const res = await client.getAdvisors(criteria, { routing });
return res;
const { RRClient } = require("./lib/index.cjs");
const { getRRConfigFromBodyshop } = require("./rr-config");
/**
* Build an RR client + common opts from a bodyshop row
*/
function buildClientAndOpts(bodyshop) {
const cfg = getRRConfigFromBodyshop(bodyshop);
const client = new RRClient({
baseUrl: cfg.baseUrl,
username: cfg.username,
password: cfg.password,
timeoutMs: cfg.timeoutMs,
retries: cfg.retries
// optional debug logger already inside lib; leave defaults
});
// Common CallOptions for all ops; routing is CRITICAL for Destination block
const opts = {
routing: cfg.routing,
envelope: {
// You can override these per-call if needed
sender: {
component: "Rome",
task: "CVC",
referenceId: "Query",
creator: "RCI",
senderName: "RCI"
}
// bodId/creationDateTime auto-filled by the client if omitted
}
};
return { client, opts };
}
async function getParts({ bodyshopId, ...criteria }) {
return withClient(bodyshopId, async (client, routing) => {
const res = await client.getParts(criteria, { routing });
return res;
});
/**
* Normalize the combined-search arguments into the RR shape.
* We infer `kind` if not provided, based on the first detectable field.
*/
function toCombinedSearchPayload(args = {}) {
const q = { ...args };
let kind = (q.kind || "").toString().trim().toLowerCase();
if (!kind) {
if (q.phone) kind = "phone";
else if (q.license) kind = "license";
else if (q.vin) kind = "vin";
else if (q.nameRecId || q.custId) kind = "nameRecId";
else if (q.name && (q.name.fname || q.name.lname || q.name.mname || q.name.name)) kind = "name";
else if (q.stkNo || q.stock) kind = "stkNo";
}
// Map loose aliases into the RR builders expected fields
const payload = { maxResults: q.maxResults || q.maxRecs || 50, kind };
switch (kind) {
case "phone":
payload.phone = q.phone;
break;
case "license":
payload.license = q.license;
break;
case "vin":
payload.vin = q.vin;
break;
case "namerecid":
payload.nameRecId = q.nameRecId || q.custId;
break;
case "name":
payload.name = q.name; // { fname, lname, mname } or { name }
break;
case "stkno":
payload.stkNo = q.stkNo || q.stock;
break;
default:
// Let the RR builder throw the canonical “Unsupported CombinedSearch kind”
payload.kind = q.kind; // may be undefined; RR lib will validate
}
// Optional vehicle narrowing; the RR builder defaults to ANY/ANY/ANY if omitted
if (q.make || q.model || q.year) {
payload.make = q.make;
payload.model = q.model;
payload.year = q.year;
}
return payload;
}
async function combinedSearch({ bodyshopId, ...query }) {
return withClient(bodyshopId, async (client, routing) => {
const res = await client.combinedSearch(query, { routing });
return res;
});
/**
* Combined customer/service/vehicle search
* @param bodyshop - bodyshop row (must include rr_dealerid & rr_configuration with store/branch)
* @param args - search inputs (phone | license | vin | nameRecId | name | stkNo)
*/
async function rrCombinedSearch(bodyshop, args = {}) {
const { client, opts } = buildClientAndOpts(bodyshop);
const payload = toCombinedSearchPayload(args);
const res = await client.combinedSearch(payload, opts);
return res?.data ?? res; // lib returns { success, data, ... }
}
module.exports = { getAdvisors, getParts, combinedSearch };
/**
* Advisors lookup
* @param bodyshop
* @param args - { department: 'B'|'S'|'P'|string, advisorNumber?: string }
*/
async function rrGetAdvisors(bodyshop, args = {}) {
const { client, opts } = buildClientAndOpts(bodyshop);
// Allow friendly department values
const dep = (args.department || "").toString().toUpperCase();
const department =
dep === "BODY" || dep === "BODYSHOP" ? "B" : dep === "SERVICE" ? "S" : dep === "PARTS" ? "P" : dep || "B";
const payload = {
department,
advisorNumber: args.advisorNumber ? String(args.advisorNumber) : undefined
};
const res = await client.getAdvisors(payload, opts);
return res?.data ?? res;
}
/**
* Parts on an internal RO
* @param bodyshop
* @param args - { roNumber: string } (ERA/DMS internal RO number)
*/
async function rrGetParts(bodyshop, args = {}) {
const { client, opts } = buildClientAndOpts(bodyshop);
const payload = { roNumber: String(args.roNumber || "").trim() };
const res = await client.getParts(payload, opts);
return res?.data ?? res;
}
module.exports = {
rrCombinedSearch,
rrGetAdvisors,
rrGetParts,
buildClientAndOpts
};