const { buildRRRepairOrderPayload } = require("./rr-job-helpers"); const { buildClientAndOpts } = require("./rr-lookup"); const { ensureRRServiceVehicle } = require("./rr-service-vehicles"); const RRLogger = require("./rr-logger"); /** * Export a job to Reynolds & Reynolds as a Repair Order (create or update). * @param args * @returns {Promise<{success, data: *, roStatus: *, statusBlocks, xml: *, parsed: any, customerNo: string, svId: null, roNo: *}>} */ const exportJobToRR = async (args) => { const { bodyshop, job, advisorNo, selectedCustomer, existing, socket } = args || {}; const log = RRLogger(socket, { ns: "rr-export" }); 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; log("info", "RR service vehicle ensured", { created: svRes?.created, svId }); } catch (e) { log("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 rrRes = roNoForUpdate ? await client.updateRepairOrder({ ...payload, roNo: String(roNoForUpdate) }, finalOpts) // ✅ use roNo on update : await client.createRepairOrder(payload, finalOpts); const data = rrRes?.data || null; const roStatus = data?.roStatus || null; // Extract canonical roNo you'll need for finalize step const roNo = data?.dmsRoNo ?? data?.outsdRoNo ?? roStatus?.dmsRoNo ?? roStatus?.DMSRoNo ?? roStatus?.outsdRoNo ?? roStatus?.OutsdRoNo ?? null; return { success: rrRes?.success === true || roStatus?.status === "Success", data, roStatus, statusBlocks: rrRes?.statusBlocks || [], xml: rrRes?.xml, parsed: rrRes?.parsed, customerNo: String(selected), svId, roNo }; }; /** * Finalize an RR Repair Order by sending finalUpdate: "Y". * @param args * @returns {Promise<{success, data: *, roStatus: *, statusBlocks, xml: *, parsed: any}>} */ const finalizeRRRepairOrder = async (args) => { const { bodyshop, job, advisorNo, customerNo, roNo, vin, socket } = args || {}; const log = RRLogger(socket, { ns: "rr-finalize" }); 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" } }; log("info", "RR finalize updateRepairOrder", { 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 || [], xml: rrRes?.xml, parsed: rrRes?.parsed }; }; module.exports = { exportJobToRR, finalizeRRRepairOrder };