← 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,
  };
}