Checkpoint

This commit is contained in:
Dave
2025-11-17 17:30:52 -05:00
parent 43dc760c95
commit e20ef4374c
3 changed files with 533 additions and 14 deletions

View File

@@ -1,14 +1,30 @@
// server/rr/rr-job-exports.js
const { buildRRRepairOrderPayload } = require("./rr-job-helpers");
const { buildClientAndOpts } = require("./rr-lookup");
const CreateRRLogEvent = require("./rr-logger-event");
const { extractRrResponsibilityCenters } = require("./rr-responsibility-centers");
const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default;
/**
* Step 1: Export a job to Reynolds & Reynolds as a *new* Repair Order.
*
* This is the "create" phase only:
* - We always call createRepairOrder
* - We always call client.createRepairOrder(payload, opts)
* - Any follow-up / finalUpdate is handled by finalizeRRRepairOrder
*
* When in RR mode (bodyshop.rr_dealerid truthy), we also:
* - Extract responsibility center config (for logging / debugging)
* - Run CdkCalculateAllocations to produce the allocations array
* (with profitCenter/costCenter + rr_gogcode / rr_item_type / rr_cust_txbl_flag)
* - Derive a default RR OpCode + TaxCode (if configured)
* - Pass bodyshop, allocations, opCode, taxCode into buildRRRepairOrderPayload
* so it can create the payload fields used by reynolds-rome-client:
* - header fields (CustNo, AdvNo, DeptType, Vin, etc.)
* - RO.rolabor (ops[])
* - RO.rogg (ops[])
* - RO.tax (TaxCodeInfo)
*
* @param args
* @returns {Promise<{success, data: *, roStatus: *, statusBlocks, customerNo: string, svId: string|null, roNo: *}>}
*/
@@ -43,13 +59,96 @@ const exportJobToRR = async (args) => {
const story = txEnvelope?.story ? String(txEnvelope.story).trim() : null;
const makeOverride = txEnvelope?.makeOverride ? String(txEnvelope.makeOverride).trim() : null;
// Build RO payload for create
// RR-only extras
let rrCentersConfig = null;
let allocations = null;
let opCode = null;
let taxCode = null;
if (bodyshop.rr_dealerid) {
// 1) Responsibility center config (for visibility / debugging)
try {
rrCentersConfig = extractRrResponsibilityCenters(bodyshop);
CreateRRLogEvent(socket, "SILLY", "RR responsibility centers resolved", {
hasCenters: !!bodyshop.md_responsibility_centers,
profitCenters: Object.keys(rrCentersConfig?.profitsByName || {}),
costCenters: Object.keys(rrCentersConfig?.costsByName || {}),
dmsCostDefaults: rrCentersConfig?.dmsCostDefaults || {},
dmsProfitDefaults: rrCentersConfig?.dmsProfitDefaults || {}
});
} catch (e) {
CreateRRLogEvent(socket, "ERROR", "Failed to resolve RR responsibility centers", {
message: e?.message,
stack: e?.stack
});
}
// 2) Allocations (sales + cost by center, with rr_* metadata already attached)
try {
allocations = await CdkCalculateAllocations(socket, job.id);
CreateRRLogEvent(socket, "SILLY", "RR allocations resolved", {
hasAllocations: Array.isArray(allocations),
count: Array.isArray(allocations) ? allocations.length : 0
});
} catch (e) {
CreateRRLogEvent(socket, "ERROR", "Failed to calculate RR allocations", {
message: e?.message,
stack: e?.stack
});
allocations = null; // We still proceed with a header-only RO if this fails.
}
// 3) OpCode (global, but overridable)
// - baseOpCode can come from bodyshop or rrCentersConfig (you'll map it in onboarding)
// - txEnvelope can carry an explicit override field (opCode/opcode/op_code)
const baseOpCode =
bodyshop.rr_default_opcode ||
bodyshop.rr_opcode ||
rrCentersConfig?.defaultOpCode ||
rrCentersConfig?.rrDefaultOpCode ||
"51DOZ"; // TODO Change / implement default handling policy
const opCodeOverride = txEnvelope?.opCode || txEnvelope?.opcode || txEnvelope?.op_code || null;
if (opCodeOverride || baseOpCode) {
opCode = String(opCodeOverride || baseOpCode).trim() || null;
}
// 4) TaxCode (for header-level tax, e.g. state/prov tax)
const baseTaxCode =
bodyshop.rr_default_taxcode ||
bodyshop.rr_tax_code ||
rrCentersConfig?.defaultTaxCode ||
rrCentersConfig?.rrDefaultTaxCode ||
"TEST"; // TODO Change / implement default handling policy
if (baseTaxCode) {
taxCode = String(baseTaxCode).trim() || null;
}
CreateRRLogEvent(socket, "SILLY", "RR op/tax config resolved", {
opCode,
taxCode
});
}
// Build RO payload for create.
//
// NOTE:
// - bodyshop + allocations + opCode + taxCode are used only to build the
// value object expected by reynolds-rome-client (header + rogg + rolabor + tax).
const payload = buildRRRepairOrderPayload({
bodyshop,
job,
selectedCustomer: { customerNo: String(selected), custNo: String(selected) },
advisorNo: String(advisorNo),
story,
makeOverride
makeOverride,
allocations,
opCode,
taxCode
});
const response = await client.createRepairOrder(payload, finalOpts);
@@ -81,6 +180,9 @@ const exportJobToRR = async (args) => {
* Step 2: Finalize an RR Repair Order by sending finalUpdate: "Y".
* This is the *update* phase.
*
* We intentionally do NOT send Rogog/Rolabor here — all of that is pushed on
* create; finalize is just a header-level update (FinalUpdate + estimateType).
*
* @param args
* @returns {Promise<{success, data: *, roStatus: *, statusBlocks}>}
*/
@@ -94,7 +196,9 @@ const finalizeRRRepairOrder = async (args) => {
// 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");
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);