165 lines
5.6 KiB
JavaScript
165 lines
5.6 KiB
JavaScript
const { buildRRRepairOrderPayload } = require("./rr-job-helpers");
|
|
const { buildClientAndOpts } = require("./rr-lookup");
|
|
const { ensureRRServiceVehicle } = require("./rr-service-vehicles");
|
|
const CreateRRLogEvent = require("./rr-logger-event");
|
|
|
|
/**
|
|
* Export a job to Reynolds & Reynolds as a Repair Order (create or update).
|
|
* @param args
|
|
* @returns {Promise<{success, data: *, roStatus: *, statusBlocks,customerNo: string, svId: null, roNo: *}>}
|
|
*/
|
|
const exportJobToRR = async (args) => {
|
|
const { bodyshop, job, advisorNo, selectedCustomer, existing, socket } = args || {};
|
|
|
|
if (!bodyshop) throw new Error("exportJobToRR: bodyshop is required");
|
|
if (!job) throw new Error("exportJobToRR: job is required");
|
|
if (advisorNo == null || String(advisorNo).trim() === "") {
|
|
throw new Error("exportJobToRR: advisorNo is required for RR");
|
|
}
|
|
|
|
// Resolve customer number (accept multiple shapes)
|
|
const selected = selectedCustomer?.customerNo || selectedCustomer?.custNo;
|
|
if (!selected) throw new Error("exportJobToRR: selectedCustomer.custNo/customerNo is required");
|
|
|
|
const { client, opts } = buildClientAndOpts(bodyshop);
|
|
const finalOpts = {
|
|
...opts,
|
|
envelope: {
|
|
...(opts?.envelope || {}),
|
|
sender: {
|
|
...(opts?.envelope?.sender || {}),
|
|
task: "BSMRO",
|
|
// If we have an existing RO number we'll be updating, otherwise inserting
|
|
referenceId: existing?.roNo || existing?.dmsRoNo || existing?.dmsRepairOrderId ? "Update" : "Insert"
|
|
}
|
|
}
|
|
};
|
|
|
|
// Ensure service vehicle for create flows (best-effort)
|
|
let svId = null;
|
|
if (!(existing?.roNo || existing?.dmsRoNo || existing?.dmsRepairOrderId)) {
|
|
try {
|
|
const svRes = await ensureRRServiceVehicle({
|
|
bodyshop,
|
|
job,
|
|
overrides: {},
|
|
customerNo: String(selected),
|
|
socket
|
|
});
|
|
svId = svRes?.svId || null;
|
|
CreateRRLogEvent(socket, "INFO", "RR service vehicle ensured", { created: svRes?.created, svId });
|
|
} catch (e) {
|
|
CreateRRLogEvent(socket, "WARN", "RR ensure service vehicle failed; continuing", { error: e?.message });
|
|
}
|
|
}
|
|
|
|
// Build RO payload for create/update
|
|
const payload = buildRRRepairOrderPayload({
|
|
job,
|
|
selectedCustomer: { customerNo: String(selected), custNo: String(selected) },
|
|
advisorNo: String(advisorNo)
|
|
});
|
|
|
|
// Canonical update key is "roNo" (prefer DMS RO number); accept fallbacks from "existing"
|
|
const roNoForUpdate = existing?.roNo || existing?.dmsRoNo || existing?.dmsRepairOrderId || null;
|
|
|
|
const response = roNoForUpdate
|
|
? await client.updateRepairOrder({ ...payload, roNo: String(roNoForUpdate) }, finalOpts) // ✅ use roNo on update
|
|
: await client.createRepairOrder(payload, finalOpts);
|
|
|
|
CreateRRLogEvent(socket, "INFO", `RR raw Repair Order ${roNoForUpdate ? "updated" : "created"}`, {
|
|
response
|
|
});
|
|
|
|
const data = response?.data || null;
|
|
const roStatus = data?.roStatus || null;
|
|
|
|
// Extract canonical roNo you'll need for finalize step
|
|
const roNo = data?.dmsRoNo ?? data?.outsdRoNo ?? roStatus?.dmsRoNo ?? null;
|
|
|
|
return {
|
|
success: response?.success === true || roStatus?.status === "Success",
|
|
data,
|
|
roStatus,
|
|
statusBlocks: response?.statusBlocks || [],
|
|
customerNo: String(selected),
|
|
svId,
|
|
roNo
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Finalize an RR Repair Order by sending finalUpdate: "Y".
|
|
* @param args
|
|
* @returns {Promise<{success, data: *, roStatus: *, statusBlocks}>}
|
|
*/
|
|
const finalizeRRRepairOrder = async (args) => {
|
|
const { bodyshop, job, advisorNo, customerNo, roNo, vin, socket } = args || {};
|
|
|
|
if (!bodyshop) throw new Error("finalizeRRRepairOrder: bodyshop is required");
|
|
if (!job) throw new Error("finalizeRRRepairOrder: job is required");
|
|
if (!advisorNo) throw new Error("finalizeRRRepairOrder: advisorNo is required");
|
|
if (!customerNo) throw new Error("finalizeRRRepairOrder: customerNo is required");
|
|
|
|
// The external (Outsd) RO is our deterministic fallback and correlation id.
|
|
const externalRo = job?.ro_number ?? job?.id;
|
|
if (externalRo == null) throw new Error("finalizeRRRepairOrder: outsdRoNo (job.ro_number/id) is required");
|
|
|
|
// Prefer DMS RO for update; fall back to external when DMS RO isn't known
|
|
const roNoToSend = roNo ? String(roNo) : String(externalRo);
|
|
|
|
const { client, opts } = buildClientAndOpts(bodyshop);
|
|
const finalOpts = {
|
|
...opts,
|
|
envelope: {
|
|
...(opts?.envelope || {}),
|
|
sender: {
|
|
...(opts?.envelope?.sender || {}),
|
|
task: "BSMRO",
|
|
referenceId: "Update"
|
|
}
|
|
}
|
|
};
|
|
|
|
const cleanVin =
|
|
(job?.v_vin || vin || "")
|
|
.toString()
|
|
.replace(/[^A-Za-z0-9]/g, "")
|
|
.toUpperCase()
|
|
.slice(0, 17) || undefined;
|
|
|
|
// IMPORTANT: include "roNo" on updates (RR requires it). Also send outsdRoNo for correlation.
|
|
const payload = {
|
|
roNo: roNoToSend, // ✅ REQUIRED BY RR on update
|
|
outsdRoNo: String(externalRo),
|
|
finalUpdate: "Y",
|
|
departmentType: "B",
|
|
customerNo: String(customerNo),
|
|
advisorNo: String(advisorNo),
|
|
vin: cleanVin,
|
|
mileageIn: job?.kmin,
|
|
mileageOut: job?.kmout,
|
|
estimate: { estimateType: "Final" }
|
|
};
|
|
|
|
CreateRRLogEvent(socket, "INFO", "Finalizing RR Repair Order", {
|
|
roNo: roNoToSend,
|
|
outsdRoNo: String(externalRo),
|
|
customerNo: String(customerNo),
|
|
advisorNo: String(advisorNo)
|
|
});
|
|
|
|
const rrRes = await client.updateRepairOrder(payload, finalOpts);
|
|
const data = rrRes?.data || null;
|
|
const roStatus = data?.roStatus || null;
|
|
|
|
return {
|
|
success: rrRes?.success === true || roStatus?.status === "Success",
|
|
data,
|
|
roStatus,
|
|
statusBlocks: rrRes?.statusBlocks || []
|
|
};
|
|
};
|
|
|
|
module.exports = { exportJobToRR, finalizeRRRepairOrder };
|