hotfix/2026-02-27 - Reynolds Estimate amounts on second call, + RR Docs in _ref

This commit is contained in:
Dave
2026-02-27 16:14:35 -05:00
parent e03546d989
commit 51af6f084d
11 changed files with 1684 additions and 48 deletions

View File

@@ -262,7 +262,7 @@ const updateRRRepairOrderWithFullData = async (args) => {
CreateRRLogEvent(socket, "INFO", "RR allocations resolved for update", {
hasAllocations: allocations.length > 0,
count: allocations.length,
allocationsPreview: allocations.slice(0, 2).map(a => ({
allocationsPreview: allocations.slice(0, 2).map((a) => ({
type: a?.type,
code: a?.code,
laborSale: a?.laborSale,
@@ -322,11 +322,11 @@ const updateRRRepairOrderWithFullData = async (args) => {
// Add roNo for linking to existing RO
payload.roNo = String(roNo);
payload.outsdRoNo = job?.ro_number || job?.id || undefined;
// Keep rolabor - it's needed to register the job/OpCode accounts in Reynolds
// Without this, Reynolds won't recognize the OpCode when we send rogg operations
// The rolabor section tells Reynolds "these jobs exist" even with minimal data
CreateRRLogEvent(socket, "INFO", "Sending full data for early RO (using create with roNo)", {
roNo: String(roNo),
hasRolabor: !!payload.rolabor,

View File

@@ -52,6 +52,122 @@ const asN2 = (dineroLike) => {
return amount.toFixed(2);
};
/**
* Normalize various "money-like" shapes to integer cents.
* Supports:
* - Dinero instances (getAmount / toUnit)
* - { cents }
* - { amount, precision }
* - plain numbers (treated as units, e.g. dollars)
* - numeric strings (treated as units, e.g. "123.45")
* @param value
* @returns {number}
*/
const toMoneyCents = (value) => {
if (value == null || value === "") return 0;
if (typeof value.getAmount === "function") {
return value.getAmount();
}
if (typeof value.toUnit === "function") {
const unit = value.toUnit();
return Number.isFinite(unit) ? Math.round(unit * 100) : 0;
}
if (typeof value.cents === "number") {
return value.cents;
}
if (typeof value.amount === "number") {
const precision = typeof value.precision === "number" ? value.precision : 2;
if (precision === 2) return value.amount;
const factor = Math.pow(10, 2 - precision);
return Math.round(value.amount * factor);
}
if (typeof value === "number") {
return Math.round(value * 100);
}
if (typeof value === "string") {
const parsed = Number.parseFloat(value);
return Number.isFinite(parsed) ? Math.round(parsed * 100) : 0;
}
return 0;
};
const asN2FromCents = (cents) => asN2({ amount: Number.isFinite(cents) ? cents : 0, precision: 2 });
/**
* Build RR estimate block from allocation totals.
* @param {Array} allocations
* @returns {{parts: string, labor: string, total: string}|null}
*/
const buildEstimateFromAllocations = (allocations) => {
if (!Array.isArray(allocations) || allocations.length === 0) return null;
const totals = allocations.reduce(
(acc, alloc) => {
acc.parts += toMoneyCents(alloc?.partsSale);
acc.labor += toMoneyCents(alloc?.laborTaxableSale);
acc.labor += toMoneyCents(alloc?.laborNonTaxableSale);
acc.total += toMoneyCents(alloc?.totalSale);
return acc;
},
{ parts: 0, labor: 0, total: 0 }
);
// If totalSale wasn't provided, keep total coherent with parts + labor.
if (!totals.total) {
totals.total = totals.parts + totals.labor;
}
return {
parts: asN2FromCents(totals.parts),
labor: asN2FromCents(totals.labor),
total: asN2FromCents(totals.total)
};
};
/**
* Build RR estimate block from precomputed job totals.
* @param job
* @returns {{parts: string, labor: string, total: string}|null}
*/
const buildEstimateFromJobTotals = (job) => {
const totals = job?.job_totals;
if (!totals) return null;
const partsCents = toMoneyCents(totals?.parts?.parts?.total) + toMoneyCents(totals?.parts?.sublets?.total);
const laborCents = toMoneyCents(totals?.rates?.rates_subtotal ?? totals?.rates?.subtotal);
let totalCents = toMoneyCents(totals?.totals?.subtotal);
if (!totalCents) {
totalCents = partsCents + laborCents;
}
// If we truly have no numbers from totals, omit estimate entirely.
if (!partsCents && !laborCents && !totalCents) return null;
return {
parts: asN2FromCents(partsCents),
labor: asN2FromCents(laborCents),
total: asN2FromCents(totalCents)
};
};
/**
* Build RR estimate block from the best available source.
* @param job
* @param allocations
* @returns {{parts: string, labor: string, total: string}|null}
*/
const buildRREstimate = ({ job, allocations } = {}) => {
return buildEstimateFromAllocations(allocations) || buildEstimateFromJobTotals(job);
};
/**
* Build RO.GOG structure for the reynolds-rome-client `createRepairOrder` payload
* from allocations.
@@ -103,44 +219,6 @@ const buildRogogFromAllocations = (allocations, { opCode, payType = "Cust", roNo
const ops = [];
/**
* Normalize various "money-like" shapes to integer cents.
* Supports:
* - Dinero instances (getAmount / toUnit)
* - { cents }
* - { amount, precision }
* - plain numbers (treated as units, e.g. dollars)
*/
const toCents = (value) => {
if (!value) return 0;
if (typeof value.getAmount === "function") {
return value.getAmount();
}
if (typeof value.toUnit === "function") {
const unit = value.toUnit();
return Number.isFinite(unit) ? Math.round(unit * 100) : 0;
}
if (typeof value.cents === "number") {
return value.cents;
}
if (typeof value.amount === "number") {
const precision = typeof value.precision === "number" ? value.precision : 2;
if (precision === 2) return value.amount;
const factor = Math.pow(10, 2 - precision);
return Math.round(value.amount * factor);
}
if (typeof value === "number") {
return Math.round(value * 100);
}
return 0;
};
const asMoneyLike = (amountCents) => ({
amount: amountCents || 0,
precision: 2
@@ -154,13 +232,13 @@ const buildRogogFromAllocations = (allocations, { opCode, payType = "Cust", roNo
// Only centers configured for RR GOG are included
if (!breakOut || !itemType) continue;
const partsTaxableCents = toCents(alloc.partsTaxableSale);
const partsNonTaxableCents = toCents(alloc.partsNonTaxableSale);
const extrasTaxableCents = toCents(alloc.extrasTaxableSale);
const extrasNonTaxableCents = toCents(alloc.extrasNonTaxableSale);
const laborTaxableCents = toCents(alloc.laborTaxableSale);
const laborNonTaxableCents = toCents(alloc.laborNonTaxableSale);
const costCents = toCents(alloc.cost);
const partsTaxableCents = toMoneyCents(alloc.partsTaxableSale);
const partsNonTaxableCents = toMoneyCents(alloc.partsNonTaxableSale);
const extrasTaxableCents = toMoneyCents(alloc.extrasTaxableSale);
const extrasNonTaxableCents = toMoneyCents(alloc.extrasNonTaxableSale);
const laborTaxableCents = toMoneyCents(alloc.laborTaxableSale);
const laborNonTaxableCents = toMoneyCents(alloc.laborNonTaxableSale);
const costCents = toMoneyCents(alloc.cost);
const segments = [];
@@ -418,6 +496,11 @@ const buildRRRepairOrderPayload = ({
mileageIn: job.kmin
};
const estimate = buildRREstimate({ job, allocations });
if (estimate) {
payload.estimate = estimate;
}
if (story) {
payload.roComment = String(story).trim();
}