Checkpoint
This commit is contained in:
@@ -34,6 +34,225 @@ const blocksFromCombinedSearchResult = (res) => {
|
||||
return Array.isArray(data) ? data : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a Dinero.js object or number into an "N2" string ("123.45").
|
||||
* @param value
|
||||
* @returns {string}
|
||||
*/
|
||||
const asN2 = (dineroLike) => {
|
||||
if (!dineroLike) return "0.00";
|
||||
|
||||
// Handle Dinero v1/v2-ish or raw objects
|
||||
if (typeof dineroLike.toUnit === "function") {
|
||||
return dineroLike.toUnit().toFixed(2);
|
||||
}
|
||||
|
||||
const precision = dineroLike.precision ?? 2;
|
||||
const amount = (dineroLike.amount ?? 0) / Math.pow(10, precision);
|
||||
return amount.toFixed(2);
|
||||
};
|
||||
|
||||
/**
|
||||
* Build RO.GOG structure for the reynolds-rome-client `createRepairOrder` payload
|
||||
* from CDK allocations.
|
||||
* @param {Array} allocations
|
||||
* @param {Object} opts
|
||||
* @param {string} opts.opCode - RR OpCode for the job (global, overridable)
|
||||
* @param {string} [opts.payType="Cust"] - PayType (always "Cust" per Marc)
|
||||
* @param {string} [opts.roNo] - Optional RoNo to echo on <Rogog RoNo="">
|
||||
* @returns {null|{roNo?: string, ops: Array}}
|
||||
*/
|
||||
const buildRogogFromAllocations = (allocations, { opCode, payType = "Cust", roNo } = {}) => {
|
||||
if (!Array.isArray(allocations) || !allocations.length || !opCode) return null;
|
||||
|
||||
const ops = [];
|
||||
|
||||
for (const alloc of allocations) {
|
||||
const pc = alloc?.profitCenter || {};
|
||||
const breakOut = pc.rr_gogcode;
|
||||
const itemType = pc.rr_item_type;
|
||||
|
||||
// Only centers that have been configured for RR GOG are included
|
||||
if (!breakOut || !itemType) continue;
|
||||
|
||||
const saleN2 = asN2(alloc.sale);
|
||||
const costN2 = asN2(alloc.cost);
|
||||
|
||||
const itemDesc = pc.accountdesc || pc.accountname || alloc.center || "";
|
||||
const jobNo = String(ops.length + 1); // 1-based JobNo
|
||||
|
||||
ops.push({
|
||||
opCode,
|
||||
jobNo,
|
||||
lines: [
|
||||
{
|
||||
breakOut,
|
||||
itemType,
|
||||
itemDesc,
|
||||
custQty: "1.0",
|
||||
// warrQty: "0.0",
|
||||
// intrQty: "0.0",
|
||||
custPayTypeFlag: "C",
|
||||
// warrPayTypeFlag: "W",
|
||||
// intrPayTypeFlag: "I",
|
||||
custTxblNtxblFlag: pc.rr_cust_txbl_flag || "T",
|
||||
// warrTxblNtxblFlag: "N",
|
||||
// intrTxblNtxblFlag: "N",
|
||||
amount: {
|
||||
payType,
|
||||
amtType: "Unit",
|
||||
custPrice: saleN2,
|
||||
dlrCost: costN2
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (!ops.length) return null;
|
||||
|
||||
return {
|
||||
roNo,
|
||||
ops
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Build RO.ROLABOR structure for the reynolds-rome-client `createRepairOrder` payload
|
||||
* from an already-built RO.GOG structure.
|
||||
* @param {Object} rogg - result of buildRogogFromAllocations
|
||||
* @param {Object} opts
|
||||
* @param {string} [opts.payType="Cust"]
|
||||
* @returns {null|{ops: Array}}
|
||||
*/
|
||||
const buildRolaborFromRogog = (rogg, { payType = "Cust" } = {}) => {
|
||||
if (!rogg || !Array.isArray(rogg.ops)) return null;
|
||||
|
||||
const ops = rogg.ops.map((op) => {
|
||||
const firstLine = op.lines?.[0] || {};
|
||||
|
||||
return {
|
||||
opCode: op.opCode,
|
||||
jobNo: op.jobNo,
|
||||
custPayTypeFlag: firstLine.custPayTypeFlag || "C",
|
||||
// warrPayTypeFlag: firstLine.warrPayTypeFlag || "W",
|
||||
// intrPayTypeFlag: firstLine.intrPayTypeFlag || "I",
|
||||
custTxblNtxblFlag: firstLine.custTxblNtxblFlag || "N",
|
||||
// warrTxblNtxblFlag: firstLine.warrTxblNtxblFlag || "N",
|
||||
// intrTxblNtxblFlag: firstLine.intrTxblNtxblFlag || "N",
|
||||
// vlrCode: undefined,
|
||||
bill: {
|
||||
payType,
|
||||
jobTotalHrs: "0",
|
||||
billTime: "0",
|
||||
billRate: "0"
|
||||
},
|
||||
amount: {
|
||||
payType,
|
||||
amtType: "Job",
|
||||
custPrice: "0",
|
||||
totalAmt: "0"
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if (!ops.length) return null;
|
||||
|
||||
return { ops };
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a header-level TaxCodeInfo payload from allocations (e.g. PROVINCIAL SALES TAX line).
|
||||
*
|
||||
* Shape returned matches what `buildCreateRepairOrder` expects for:
|
||||
*
|
||||
* payload.tax = {
|
||||
* payType,
|
||||
* taxCode,
|
||||
* txblGrossAmt,
|
||||
* grossTaxAmt
|
||||
* }
|
||||
*
|
||||
* NOTE: We are currently NOT wiring this into the payload (see buildRRRepairOrderPayload)
|
||||
* so that TaxCodeInfo is suppressed in the XML, but we keep this helper around for
|
||||
* future use.
|
||||
*
|
||||
* @param {Array} allocations
|
||||
* @param {Object} opts
|
||||
* @param {string} opts.taxCode - RR tax code (configured per dealer)
|
||||
* @param {string} [opts.payType="Cust"]
|
||||
* @returns {null|{payType, taxCode, txblGrossAmt, grossTaxAmt}}
|
||||
*/
|
||||
const buildTaxFromAllocations = (allocations, { taxCode, payType = "Cust" } = {}) => {
|
||||
if (!taxCode || !Array.isArray(allocations) || !allocations.length) return null;
|
||||
|
||||
const taxAlloc = allocations.find((a) => a && a.tax);
|
||||
if (!taxAlloc || !taxAlloc.sale) return null;
|
||||
|
||||
const grossTaxNum = parseFloat(asN2(taxAlloc.sale));
|
||||
if (!Number.isFinite(grossTaxNum)) return null;
|
||||
|
||||
const rate = typeof taxAlloc.profitCenter?.rate === "number" ? taxAlloc.profitCenter.rate : null;
|
||||
|
||||
let taxableGrossNum = grossTaxNum;
|
||||
if (rate && rate > 0) {
|
||||
const r = rate / 100;
|
||||
taxableGrossNum = grossTaxNum / r;
|
||||
}
|
||||
|
||||
return {
|
||||
payType,
|
||||
taxCode,
|
||||
txblGrossAmt: taxableGrossNum.toFixed(2),
|
||||
grossTaxAmt: grossTaxNum.toFixed(2)
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a minimal Rolabor structure in the new normalized shape.
|
||||
*
|
||||
* Useful for tests or for scenarios where you want a single zero-dollar
|
||||
* Rolabor op but don't have GOG data. Shape matches payload.rolabor for the
|
||||
* reynolds-rome-client builders.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {string} opts.opCode
|
||||
* @param {number|string} [opts.jobNo=1]
|
||||
* @param {string} [opts.payType="Cust"]
|
||||
* @returns {null|{ops: Array}}
|
||||
*/
|
||||
const buildRolaborSkeleton = ({ opCode, jobNo = 1, payType = "Cust" } = {}) => {
|
||||
if (!opCode) return null;
|
||||
|
||||
return {
|
||||
ops: [
|
||||
{
|
||||
opCode,
|
||||
jobNo: String(jobNo),
|
||||
custPayTypeFlag: "C",
|
||||
warrPayTypeFlag: "W",
|
||||
intrPayTypeFlag: "I",
|
||||
custTxblNtxblFlag: "N",
|
||||
warrTxblNtxblFlag: "N",
|
||||
intrTxblNtxblFlag: "N",
|
||||
vlrCode: undefined,
|
||||
bill: {
|
||||
payType,
|
||||
jobTotalHrs: "0",
|
||||
billTime: "0",
|
||||
billRate: "0"
|
||||
},
|
||||
amount: {
|
||||
payType,
|
||||
amtType: "Job",
|
||||
custPrice: "0",
|
||||
totalAmt: "0"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
// ---------- Public API ----------
|
||||
|
||||
/**
|
||||
@@ -63,25 +282,38 @@ const QueryJobData = async (ctx = {}, jobId) => {
|
||||
|
||||
/**
|
||||
* Build Repair Order payload for RR from job and customer data.
|
||||
* @param {Object} args
|
||||
* @param job
|
||||
* @param selectedCustomer
|
||||
* @param advisorNo
|
||||
* @param story
|
||||
* @param makeOverride
|
||||
* @returns {{outsdRoNo: string, repairOrderNumber: string, departmentType: string, vin: string, customerNo: string, advisorNo: string, mileageIn: *|null}}
|
||||
* @param bodyshop
|
||||
* @param allocations
|
||||
* @param {string} [opCode] - RR OpCode for this RO (global default / override)
|
||||
* @param {string} [taxCode] - RR tax code for header tax (e.g. state/prov code)
|
||||
* @returns {Object}
|
||||
*/
|
||||
const buildRRRepairOrderPayload = ({ job, selectedCustomer, advisorNo, story }) => {
|
||||
const buildRRRepairOrderPayload = ({
|
||||
job,
|
||||
selectedCustomer,
|
||||
advisorNo,
|
||||
story,
|
||||
makeOverride,
|
||||
allocations,
|
||||
opCode,
|
||||
taxCode
|
||||
} = {}) => {
|
||||
const customerNo = selectedCustomer?.customerNo
|
||||
? String(selectedCustomer.customerNo).trim()
|
||||
: selectedCustomer?.custNo
|
||||
? String(selectedCustomer.custNo).trim()
|
||||
: null;
|
||||
|
||||
if (!customerNo) throw new Error("No RR customer selected (customerNo/CustNo missing)");
|
||||
if (!customerNo) throw new Error("No RR customer selected (customerNo/custNo missing)");
|
||||
|
||||
const adv = advisorNo != null && String(advisorNo).trim() !== "" ? String(advisorNo).trim() : null;
|
||||
|
||||
if (!adv) throw new Error("advisorNo is required for RR export");
|
||||
if (!adv) throw new Error("advisorNo is required for RR export");
|
||||
|
||||
const vinRaw = job?.v_vin;
|
||||
const vin =
|
||||
@@ -98,9 +330,9 @@ const buildRRRepairOrderPayload = ({ job, selectedCustomer, advisorNo, story })
|
||||
|
||||
const roStr = String(ro);
|
||||
|
||||
const output = {
|
||||
// Base payload shape expected by reynolds-rome-client (buildCreateRepairOrder)
|
||||
const payload = {
|
||||
outsdRoNo: roStr,
|
||||
repairOrderNumber: roStr,
|
||||
departmentType: "B",
|
||||
vin,
|
||||
customerNo: String(customerNo),
|
||||
@@ -109,10 +341,57 @@ const buildRRRepairOrderPayload = ({ job, selectedCustomer, advisorNo, story })
|
||||
};
|
||||
|
||||
if (story) {
|
||||
output.roComment = String(story).trim();
|
||||
payload.roComment = String(story).trim();
|
||||
}
|
||||
|
||||
return output;
|
||||
if (makeOverride) {
|
||||
// Passed through so the template can override DMS Make if needed
|
||||
payload.makeOverride = String(makeOverride).trim();
|
||||
}
|
||||
|
||||
const haveAllocations = Array.isArray(allocations) && allocations.length > 0;
|
||||
|
||||
if (haveAllocations) {
|
||||
const effectiveOpCode = (opCode && String(opCode).trim()) || null;
|
||||
const effectiveTaxCode = (taxCode && String(taxCode).trim()) || null;
|
||||
|
||||
if (effectiveOpCode) {
|
||||
// Build RO.GOG and RO.LABOR in the new normalized shape
|
||||
const rogg = buildRogogFromAllocations(allocations, {
|
||||
opCode: effectiveOpCode,
|
||||
payType: "Cust"
|
||||
});
|
||||
|
||||
if (rogg) {
|
||||
payload.rogg = rogg;
|
||||
|
||||
const rolabor = buildRolaborFromRogog(rogg, { payType: "Cust" });
|
||||
if (rolabor) {
|
||||
payload.rolabor = rolabor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- TAX HEADER TEMPORARILY DISABLED ---
|
||||
// We intentionally do NOT attach payload.tax right now so that the Mustache
|
||||
// section that renders <TaxCodeInfo> stays false and no TaxCodeInfo is sent.
|
||||
//
|
||||
// Keeping this commented-out for future enablement once RR confirms header
|
||||
// tax handling behaviour.
|
||||
//
|
||||
// if (effectiveTaxCode) {
|
||||
// const taxInfo = buildTaxFromAllocations(allocations, {
|
||||
// taxCode: effectiveTaxCode,
|
||||
// payType: "Cust"
|
||||
// });
|
||||
//
|
||||
// if (taxInfo) {
|
||||
// payload.tax = taxInfo;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -220,5 +499,10 @@ module.exports = {
|
||||
makeCustomerSearchPayloadFromJob,
|
||||
makeVehicleSearchPayloadFromJob,
|
||||
normalizeCustomerCandidates,
|
||||
normalizeVehicleCandidates
|
||||
normalizeVehicleCandidates,
|
||||
// exporting these so you can unit-test them directly if you want
|
||||
buildRogogFromAllocations,
|
||||
buildTaxFromAllocations,
|
||||
buildRolaborSkeleton,
|
||||
buildRolaborFromRogog
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user