feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint
This commit is contained in:
@@ -1,97 +1,123 @@
|
||||
/**
|
||||
* RR WSDL / SOAP XML Transport Layer (thin wrapper)
|
||||
* -------------------------------------------------
|
||||
* Delegates to rr-helpers.MakeRRCall (which handles:
|
||||
* - fetching dealer config from DB via resolveRRConfig
|
||||
* - rendering Mustache XML templates
|
||||
* - building SOAP envelope + headers
|
||||
* - axios POST + retries
|
||||
*
|
||||
* Use this when you prefer the "action + variables" style and (optionally)
|
||||
* want a parsed Body node back instead of raw XML.
|
||||
* @file rr-wsdl.js
|
||||
* @description Lightweight service description + utilities for the Rome (R&R) SOAP actions.
|
||||
* - Maps actions to SOAPAction headers (from rr-constants)
|
||||
* - Maps actions to Mustache template filenames (xml-templates/*.xml)
|
||||
* - Provides verification helpers to ensure templates exist
|
||||
* - Provides normalized SOAP headers used by the transport
|
||||
*/
|
||||
|
||||
const { XMLParser } = require("fast-xml-parser");
|
||||
const logger = require("../utils/logger");
|
||||
const { MakeRRCall, resolveRRConfig, renderXmlTemplate } = require("./rr-helpers");
|
||||
const path = require("path");
|
||||
const fs = require("fs/promises");
|
||||
const { RR_ACTIONS, RR_SOAP_HEADERS } = require("./rr-constants");
|
||||
|
||||
// Map friendly action names to template filenames (no envelope here; helpers add it)
|
||||
const RR_ACTION_MAP = {
|
||||
CustomerInsert: { file: "InsertCustomer.xml" },
|
||||
CustomerUpdate: { file: "UpdateCustomer.xml" },
|
||||
ServiceVehicleInsert: { file: "InsertServiceVehicle.xml" },
|
||||
CombinedSearch: { file: "CombinedSearch.xml" },
|
||||
GetParts: { file: "GetParts.xml" },
|
||||
GetAdvisors: { file: "GetAdvisors.xml" },
|
||||
CreateRepairOrder: { file: "CreateRepairOrder.xml" },
|
||||
UpdateRepairOrder: { file: "UpdateRepairOrder.xml" }
|
||||
};
|
||||
// ---- Action <-> Template wiring ----
|
||||
// Keep action names consistent with rr-helpers / rr-lookup / rr-repair-orders / rr-customer
|
||||
const ACTION_TEMPLATES = Object.freeze({
|
||||
InsertCustomer: "InsertCustomer",
|
||||
UpdateCustomer: "UpdateCustomer",
|
||||
InsertServiceVehicle: "InsertServiceVehicle",
|
||||
CreateRepairOrder: "CreateRepairOrder",
|
||||
UpdateRepairOrder: "UpdateRepairOrder",
|
||||
GetAdvisors: "GetAdvisors",
|
||||
GetParts: "GetParts",
|
||||
CombinedSearch: "CombinedSearch"
|
||||
});
|
||||
|
||||
/**
|
||||
* Optionally render just the body XML for a given action (no SOAP envelope).
|
||||
* Mostly useful for diagnostics/tests.
|
||||
* Get the SOAPAction string for a known action.
|
||||
* Throws if action is unknown.
|
||||
*/
|
||||
async function buildRRXml(action, variables = {}) {
|
||||
const entry = RR_ACTION_MAP[action];
|
||||
if (!entry) throw new Error(`Unknown RR action: ${action}`);
|
||||
const templateName = entry.file.replace(/\.xml$/i, "");
|
||||
return renderXmlTemplate(templateName, variables);
|
||||
function getSoapAction(action) {
|
||||
const entry = RR_ACTIONS[action];
|
||||
if (!entry) {
|
||||
const known = Object.keys(RR_ACTIONS).join(", ");
|
||||
throw new Error(`Unknown RR action "${action}". Known: ${known}`);
|
||||
}
|
||||
return entry.soapAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an RR SOAP request using helpers (action + variables).
|
||||
* @param {object} opts
|
||||
* @param {string} opts.action One of RR_ACTION_MAP keys (and RR_ACTIONS in rr-constants)
|
||||
* @param {object} opts.variables Mustache variables for the body template
|
||||
* @param {object} opts.socket Socket/req for context (bodyshopId + auth)
|
||||
* @param {boolean} [opts.raw=false] If true, returns raw XML string
|
||||
* @param {number} [opts.retries=1] Transient retry attempts (5xx/network)
|
||||
* @returns {Promise<string|object>} Raw XML (raw=true) or parsed Body node
|
||||
* Get the template filename (without extension) for a known action.
|
||||
* e.g., "CreateRepairOrder" -> "CreateRepairOrder"
|
||||
*/
|
||||
async function sendRRRequest({ action, variables = {}, socket, raw = false, retries = 1 }) {
|
||||
const entry = RR_ACTION_MAP[action];
|
||||
if (!entry) throw new Error(`Unknown RR action: ${action}`);
|
||||
function getTemplateForAction(action) {
|
||||
const tpl = ACTION_TEMPLATES[action];
|
||||
if (!tpl) {
|
||||
const known = Object.keys(ACTION_TEMPLATES).join(", ");
|
||||
throw new Error(`No template mapping for RR action "${action}". Known: ${known}`);
|
||||
}
|
||||
return tpl;
|
||||
}
|
||||
|
||||
const templateName = entry.file.replace(/\.xml$/i, "");
|
||||
const dealerConfig = await resolveRRConfig(socket);
|
||||
/**
|
||||
* Build headers for a SOAP request, including SOAPAction.
|
||||
* Consumers: rr-helpers (transport).
|
||||
*/
|
||||
function buildSoapHeadersForAction(action) {
|
||||
return {
|
||||
...RR_SOAP_HEADERS,
|
||||
SOAPAction: getSoapAction(action)
|
||||
};
|
||||
}
|
||||
|
||||
// Let MakeRRCall render + envelope + post
|
||||
const xml = await MakeRRCall({
|
||||
/**
|
||||
* List all known actions with their SOAPAction + template.
|
||||
* Useful for diagnostics (e.g., /rr/actions route).
|
||||
*/
|
||||
function listActions() {
|
||||
return Object.keys(ACTION_TEMPLATES).map((action) => ({
|
||||
action,
|
||||
body: { template: templateName, data: variables },
|
||||
socket,
|
||||
dealerConfig,
|
||||
retries
|
||||
});
|
||||
soapAction: getSoapAction(action),
|
||||
template: getTemplateForAction(action)
|
||||
}));
|
||||
}
|
||||
|
||||
if (raw) return xml;
|
||||
/**
|
||||
* Verify that every required template exists in xml-templates/.
|
||||
* Returns an array of issues; empty array means all good.
|
||||
*/
|
||||
async function verifyTemplatesExist() {
|
||||
const issues = [];
|
||||
const baseDir = path.join(__dirname, "xml-templates");
|
||||
|
||||
try {
|
||||
const parser = new XMLParser({ ignoreAttributes: false });
|
||||
const parsed = parser.parse(xml);
|
||||
for (const [action, tpl] of Object.entries(ACTION_TEMPLATES)) {
|
||||
const filePath = path.join(baseDir, `${tpl}.xml`);
|
||||
try {
|
||||
const stat = await fs.stat(filePath);
|
||||
if (!stat.isFile()) {
|
||||
issues.push({ action, template: tpl, error: "Not a regular file" });
|
||||
}
|
||||
} catch {
|
||||
issues.push({ action, template: tpl, error: `Missing file: ${filePath}` });
|
||||
}
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
// Try several common namespace variants for Envelope/Body
|
||||
const bodyNode =
|
||||
parsed?.Envelope?.Body ||
|
||||
parsed?.["soapenv:Envelope"]?.["soapenv:Body"] ||
|
||||
parsed?.["SOAP-ENV:Envelope"]?.["SOAP-ENV:Body"] ||
|
||||
parsed?.["S:Envelope"]?.["S:Body"] ||
|
||||
parsed;
|
||||
|
||||
return bodyNode;
|
||||
} catch (err) {
|
||||
logger.log("rr-wsdl-parse-error", "ERROR", "RR", null, {
|
||||
action,
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
});
|
||||
// If parsing fails, return raw so caller can inspect
|
||||
return xml;
|
||||
/**
|
||||
* Quick assert that throws if any template is missing.
|
||||
* You can call this once during boot and log the result.
|
||||
*/
|
||||
async function assertTemplates() {
|
||||
const issues = await verifyTemplatesExist();
|
||||
if (issues.length) {
|
||||
const msg =
|
||||
"RR xml-templates verification failed:\n" +
|
||||
issues.map((i) => ` - ${i.action} -> ${i.template}.xml :: ${i.error}`).join("\n");
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendRRRequest,
|
||||
buildRRXml,
|
||||
RR_ACTION_MAP
|
||||
// Maps / helpers
|
||||
ACTION_TEMPLATES,
|
||||
listActions,
|
||||
getSoapAction,
|
||||
getTemplateForAction,
|
||||
buildSoapHeadersForAction,
|
||||
|
||||
// Verification
|
||||
verifyTemplatesExist,
|
||||
assertTemplates
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user