← back to Sanderson Onboard
tk10873/lib.mjs
108 lines
// lib.mjs — pure pricing + classification helpers for the Zoffany Option-A draft.
// READ-ONLY logic; NO SHOPIFY/DB WRITES. Extracted so test_optionA.mjs can exercise
// them without a DB. Every function here is deterministic and side-effect-free.
export const SAMPLE_PRICE = 4.25;
// Retail rule (Option-A contract, verified on real zoffany_catalog rows):
// retail = trade / 0.65 / 0.85, rounded to the cent.
// trade 191 -> 345.70, 208 -> 376.47, 177 -> 320.36 (exact).
export function retailFromTrade(trade) {
const t = Number(trade);
if (!Number.isFinite(t) || t <= 0) return null;
return Math.round((t / 0.65 / 0.85) * 100) / 100;
}
// Round any numeric to cents; null-safe.
export function toCents(v) {
const n = Number(v);
if (!Number.isFinite(n)) return null;
return Math.round(n * 100) / 100;
}
// A staged retail is "usable" only if it is a positive number.
export function hasStagedPrice(row) {
const r = Number(row && row.price_retail);
return Number.isFinite(r) && r > 0;
}
// Classify a joined live+staging plan row into exactly one action bucket.
// REPRICE — live product HAS a sellable variant AND staging has price_retail>0.
// SELLABLE_ADD — live product MISSING a sellable variant AND staging has a usable price.
// HELD_NEEDS_PRICE — cannot price (no staging join, or joined but price_retail null/0),
// OR the missing-sellable outlier with no usable staged price.
// `row` fields expected: has_product_variant (bool), joined (bool), price_retail (num|null).
export function classify(row) {
const priced = hasStagedPrice(row);
const hasSellable = !!row.has_product_variant;
if (!hasSellable) {
// The DWWC-502880 outlier: needs a sellable variant added.
return priced ? 'SELLABLE_ADD' : 'HELD_NEEDS_PRICE';
}
// Has a sellable variant already -> this is a REPRICE candidate, IF we can price it.
return priced ? 'REPRICE' : 'HELD_NEEDS_PRICE';
}
// Compute the proposed sellable price for a plan row given its action.
// REPRICE / SELLABLE_ADD -> the staged price_retail (the authoritative retail).
// HELD_NEEDS_PRICE -> null (cannot price).
export function proposedSellablePrice(row, action) {
if (action === 'REPRICE' || action === 'SELLABLE_ADD') {
return hasStagedPrice(row) ? toCents(row.price_retail) : null;
}
return null;
}
// Build the full per-product plan row from a joined DB record.
export function buildPlanRow(rec) {
const row = {
mfr_sku: rec.mfr_sku,
live_dw_sku: rec.live_dw_sku ?? null,
staged_dw_sku: rec.staged_dw_sku ?? null,
joined: rec.joined,
has_product_variant: !!rec.has_product_variant,
has_sample_variant: !!rec.has_sample_variant,
variant_count: rec.variant_count ?? null,
price_trade: rec.price_trade == null ? null : Number(rec.price_trade),
price_retail: rec.price_retail == null ? null : Number(rec.price_retail),
our_price: rec.our_price == null ? null : Number(rec.our_price),
tariff: rec.tariff == null ? null : Number(rec.tariff),
};
const action = classify(row);
const proposed = proposedSellablePrice(row, action);
let note = '';
if (action === 'HELD_NEEDS_PRICE') {
if (!row.joined) {
note = 'no staging join — held for price harvest';
} else if (row.our_price > 0) {
// Joined, price_retail null, but our_price is populated. On these rows our_price
// == trade/0.65/0.85 exactly. HELD is the conservative default; whether our_price
// is an authoritative retail source is a Steve CLARIFY (do NOT auto-price off it).
note = `joins staging, price_retail null but our_price=${toCents(row.our_price)} present — HELD (CLARIFY: use our_price as retail?)`;
} else {
note = 'joins staging but price_retail null/0 — held for price harvest';
}
} else if (action === 'SELLABLE_ADD') {
note = 'live product missing sellable variant — add sellable at staged retail';
} else {
// REPRICE — note when reconcile vs trade/0.65/0.85 is off (informational)
if (row.price_trade > 0) {
const calc = retailFromTrade(row.price_trade);
if (calc != null && Math.abs(calc - toCents(row.price_retail)) > 0.02) {
note = `retail ${toCents(row.price_retail)} deviates from trade/0.65/0.85=${calc}`;
} else {
note = 'reprice to staged retail';
}
} else {
note = 'reprice to staged retail (retail-harvested, no trade)';
}
}
return {
...row,
action,
proposed_sellable_price: proposed,
sample_price: SAMPLE_PRICE,
note,
};
}