feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint - Remove old attempt at Reynolds Integration in favor of new library.
This commit is contained in:
@@ -1,158 +1,68 @@
|
||||
/**
|
||||
* @file rr-job-export.js
|
||||
* @description End-to-end export of a Hasura "job" to Reynolds & Reynolds (Rome).
|
||||
* Orchestrates Customer (insert/update), optional Vehicle insert, and RO (create/update),
|
||||
* mirroring behavior of PBS/Fortellis exporters for parity.
|
||||
*/
|
||||
const { withClient } = require("./withClient");
|
||||
|
||||
const RRLogger = require("./rr-logger");
|
||||
const { RrApiError } = require("./rr-error");
|
||||
async function exportJobToRR({ bodyshopId, job, logger }) {
|
||||
return withClient(bodyshopId, logger, async (client, routing) => {
|
||||
// 1) Upsert Customer
|
||||
const custPayload = mapJobToCustomer(job);
|
||||
const custRes = job.customer?.nameRecId
|
||||
? await client.updateCustomer(custPayload, { routing })
|
||||
: await client.insertCustomer(custPayload, { routing });
|
||||
|
||||
const customerApi = require("./rr-customer");
|
||||
const roApi = require("./rr-repair-orders");
|
||||
const { MakeRRCall } = require("./rr-helpers"); // for optional vehicle insert
|
||||
const { mapServiceVehicle } = require("./rr-mappers");
|
||||
const customerNo = custRes?.data?.dmsRecKey || job.customer?.customerNo;
|
||||
if (!customerNo) throw new Error("Failed to resolve customerNo from RR response.");
|
||||
|
||||
/**
|
||||
* Decide if we should CREATE or UPDATE an entity in Rome based on external IDs
|
||||
*/
|
||||
function decideAction({ customer, vehicle, job }) {
|
||||
const hasCustId = !!(customer?.external_id || customer?.rr_customer_id);
|
||||
const hasVehId = !!(vehicle?.external_id || vehicle?.rr_vehicle_id);
|
||||
const hasRoId = !!(job?.external_id || job?.rr_repair_order_id || job?.dms_repair_order_id);
|
||||
|
||||
return {
|
||||
customerAction: hasCustId ? "update" : "insert",
|
||||
vehicleAction: hasVehId ? "skip" : "insert", // Rome often generates vehicle IDs on RO create; we insert only if we have enough data and no id
|
||||
repairOrderAction: hasRoId ? "update" : "create"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a stage result to a consistent structure.
|
||||
*/
|
||||
function stageOk(name, extra = {}) {
|
||||
return { stage: name, success: true, ...extra };
|
||||
}
|
||||
function stageFail(name, error) {
|
||||
return { stage: name, success: false, error: error?.message || String(error) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a job into Rome (Customer → Vehicle → RepairOrder).
|
||||
* @param {Socket} socket - logging context (may be null in batch)
|
||||
* @param {Object} job - Hasura job object (must include customer, vehicle, lines, totals)
|
||||
* @param {Object} bodyshopConfig - per-shop RR config (dealer/store/branch + creds)
|
||||
* @param {Object} options - { insertVehicleIfMissing: boolean }
|
||||
* @returns {Promise<Object>} normalized result
|
||||
*/
|
||||
async function exportJobToRR(socket, job, bodyshopConfig, options = {}) {
|
||||
const { customer = {}, vehicle = {} } = job || {};
|
||||
const { insertVehicleIfMissing = true } = options;
|
||||
|
||||
const actions = decideAction({ customer, vehicle, job });
|
||||
|
||||
const stages = [];
|
||||
const summary = {
|
||||
dms: "Rome",
|
||||
jobid: job?.id,
|
||||
ro_action: actions.repairOrderAction,
|
||||
customer_action: actions.customerAction,
|
||||
vehicle_action: insertVehicleIfMissing ? actions.vehicleAction : "skip"
|
||||
};
|
||||
|
||||
RRLogger(socket, "info", `RR Export start`, summary);
|
||||
|
||||
// ---- 1) Customer ----
|
||||
try {
|
||||
if (actions.customerAction === "insert") {
|
||||
const res = await customerApi.insertCustomer(socket, customer, bodyshopConfig);
|
||||
stages.push(stageOk("customer.insert"));
|
||||
summary.customer_xml = res.xml;
|
||||
} else {
|
||||
const res = await customerApi.updateCustomer(socket, customer, bodyshopConfig);
|
||||
stages.push(stageOk("customer.update"));
|
||||
summary.customer_xml = res.xml;
|
||||
// 2) Ensure Service Vehicle (optional, if VIN present)
|
||||
if (job?.vehicle?.vin) {
|
||||
await client.insertServiceVehicle(
|
||||
{
|
||||
vin: job.vehicle.vin,
|
||||
vehicleServInfo: { customerNo }
|
||||
},
|
||||
{ routing }
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
stages.push(stageFail(`customer.${actions.customerAction}`, error));
|
||||
RRLogger(socket, "error", `RR customer ${actions.customerAction} failed`, {
|
||||
jobid: job?.id,
|
||||
error: error.message
|
||||
});
|
||||
throw new RrApiError(`Customer ${actions.customerAction} failed: ${error.message}`, "RR_CUSTOMER_ERROR");
|
||||
}
|
||||
|
||||
// ---- 2) Vehicle (optional explicit insert) ----
|
||||
if (insertVehicleIfMissing && actions.vehicleAction === "insert") {
|
||||
try {
|
||||
// Only insert when we have at least VIN or plate+state/year
|
||||
const hasMinimumIdentity = !!(vehicle?.vin || (vehicle?.license_plate && vehicle?.license_state));
|
||||
if (hasMinimumIdentity) {
|
||||
const data = mapServiceVehicle(vehicle, customer, bodyshopConfig);
|
||||
const xml = await MakeRRCall({
|
||||
action: "InsertServiceVehicle",
|
||||
body: { template: "InsertServiceVehicle", data },
|
||||
socket,
|
||||
dealerConfig: bodyshopConfig,
|
||||
jobid: job?.id
|
||||
});
|
||||
stages.push(stageOk("vehicle.insert"));
|
||||
summary.vehicle_xml = xml;
|
||||
} else {
|
||||
stages.push(stageOk("vehicle.skip", { reason: "insufficient_identity" }));
|
||||
}
|
||||
} catch (error) {
|
||||
stages.push(stageFail("vehicle.insert", error));
|
||||
RRLogger(socket, "error", `RR vehicle insert failed`, {
|
||||
jobid: job?.id,
|
||||
error: error.message
|
||||
});
|
||||
// Non-fatal for the overall export — many flows let RO creation create/associate vehicle.
|
||||
}
|
||||
} else {
|
||||
stages.push(stageOk("vehicle.skip", { reason: actions.vehicleAction === "skip" ? "already_has_id" : "disabled" }));
|
||||
}
|
||||
// 3) Create RO
|
||||
const roHeader = {
|
||||
customerNo,
|
||||
departmentType: "B",
|
||||
vin: job?.vehicle?.vin,
|
||||
outsdRoNo: job?.roExternal || job?.id,
|
||||
advisorNo: job?.advisorNo,
|
||||
mileageIn: job?.mileageIn
|
||||
};
|
||||
|
||||
// ---- 3) Repair Order ----
|
||||
try {
|
||||
let res;
|
||||
if (actions.repairOrderAction === "create") {
|
||||
res = await roApi.createRepairOrder(socket, job, bodyshopConfig);
|
||||
stages.push(stageOk("ro.create"));
|
||||
} else {
|
||||
res = await roApi.updateRepairOrder(socket, job, bodyshopConfig);
|
||||
stages.push(stageOk("ro.update"));
|
||||
}
|
||||
summary.ro_xml = res.xml;
|
||||
} catch (error) {
|
||||
stages.push(stageFail(`ro.${actions.repairOrderAction}`, error));
|
||||
RRLogger(socket, "error", `RR RO ${actions.repairOrderAction} failed`, {
|
||||
jobid: job?.id,
|
||||
error: error.message
|
||||
});
|
||||
throw new RrApiError(`RepairOrder ${actions.repairOrderAction} failed: ${error.message}`, "RR_RO_ERROR");
|
||||
}
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
...summary,
|
||||
stages
|
||||
};
|
||||
|
||||
RRLogger(socket, "info", `RR Export finished`, {
|
||||
jobid: job?.id,
|
||||
result: {
|
||||
success: result.success,
|
||||
customer_action: summary.customer_action,
|
||||
vehicle_action: summary.vehicle_action,
|
||||
ro_action: summary.ro_action
|
||||
}
|
||||
const roBody = mapJobToRO(job); // extend if you want lines/tax/etc
|
||||
const roRes = await client.createRepairOrder({ ...roHeader, ...roBody }, { routing });
|
||||
return roRes?.data;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
exportJobToRR
|
||||
};
|
||||
function mapJobToCustomer(job) {
|
||||
const c = job?.customer || {};
|
||||
return {
|
||||
nameRecId: c.nameRecId,
|
||||
firstName: c.firstName || c.given_name,
|
||||
lastName: c.lastName || c.family_name,
|
||||
phone: c.phone || c.mobile,
|
||||
email: c.email,
|
||||
address: {
|
||||
line1: c.address1,
|
||||
line2: c.address2,
|
||||
city: c.city,
|
||||
state: c.province || c.state,
|
||||
postalCode: c.postal || c.zip
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function mapJobToRO(job) {
|
||||
return {
|
||||
// rolabor: [...],
|
||||
// roparts: [...],
|
||||
// estimate: {...},
|
||||
// tax: {...}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { exportJobToRR };
|
||||
|
||||
Reference in New Issue
Block a user