← back to Tk10630 Sku Suffix Canary

lib.mjs

65 lines

// Pure transform + detection logic shared by enumerate / apply / canary.
// No I/O, no side effects on import.

// Fabricated DW codes to purge. Hollywood Wallcoverings & Phillipe Romano are DW
// BRANDS, not external vendors — a scraper (dw-sku-registry mintNewPrefix) minted
// DW-vendor-style SKUs = "DW" + 2-4 letters + number. Within the Hollywood line the
// REAL codes are always non-DW (XWH-, NOC-, HWC-, FLS-, XJP-, HLW-, DUR-, ...), so
// ANY DW-prefixed base is fabricated. Observed families: DWHD, DWHW2, DWHV, DWYX,
// DWKK, DWPR, DWGB, DWCC, DWPN, DWPP, DWHQ. (Narrow /^DWH[DW]/ missed DWHV/DWYX/... .)
export const FABRICATED = /^DW[A-Z]{1,4}\d?-\d/i;

// A legit ORIGINAL private-label code: alpha prefix + digits, e.g. XWH-52359, NOC-105.
const ORIG = /^([A-Z]{2,5})-?(\d{2,})$/i;

function normCode(s) {
  const m = s.match(ORIG);
  return m ? `${m[1].toUpperCase()}-${m[2]}` : s.toUpperCase();
}

// Derive a product's ORIGINAL line code (prefix-agnostic), ignoring fabricated codes.
// Returns a string base, null (no code), or {conflict:[...]} (variants disagree).
export function deriveBase(p) {
  const cand = new Set();
  for (const v of p.variants.nodes) {
    const m = (v.sku || '').match(/^(.+?)-(sample|yard|roll)$/i);
    const raw = m ? m[1] : (v.sku || '');
    if (raw && !FABRICATED.test(raw) && ORIG.test(raw)) cand.add(normCode(raw));
  }
  if (cand.size === 1) return [...cand][0];
  if (cand.size > 1) return { conflict: [...cand] };
  const hm = p.handle.match(/-([a-z]{2,5})-(\d{2,})$/i);
  if (hm) { const b = `${hm[1].toUpperCase()}-${hm[2]}`; if (!FABRICATED.test(b)) return b; }
  if (p.pn?.value) { const m = p.pn.value.match(/^([A-Z]{2,5})-?(\d{2,})$/i); if (m) { const b = `${m[1].toUpperCase()}-${m[2]}`; if (!FABRICATED.test(b)) return b; } }
  return null; // no NON-fabricated original code recoverable → skip/flag (needs staging reconciliation)
}

// Which purchase-option suffix a variant should carry.
export function suffixFor(v) {
  const opt = (v.selectedOptions?.find(o => /purchase option/i.test(o.name))?.value || v.title || '').toLowerCase();
  const cur = (v.sku || '').toLowerCase();
  if (/sample/.test(opt) || /-sample$/.test(cur)) return 'sample';
  if (/roll/.test(opt)   || /-roll$/.test(cur))   return 'roll';
  if (/yard/.test(opt)   || /per yard/.test(opt) || /-yard$/.test(cur)) return 'yard';
  return 'yard';
}

// Compute the change set for one product given its canonical base.
export function planProduct(p, base) {
  const varChanges = [];
  for (const v of p.variants.nodes) {
    const suf = suffixFor(v);
    const target = `${base}-${suf}`;
    if ((v.sku || '') !== target)
      varChanges.push({ variantId: v.id, invItemId: v.inventoryItem.id, from: v.sku, to: target, option: suf });
  }
  const mfChanges = [];
  // Three dw_sku metafield namespaces carry the fabricated codes: global, dwc, custom.
  for (const [ns, mf] of [['global', p.mfG], ['dwc', p.mfD], ['custom', p.mfC]]) {
    if (mf === undefined) continue;           // namespace not fetched for this pass
    const cur = mf?.value || null;
    if (cur !== base) mfChanges.push({ namespace: ns, key: 'dw_sku', ownerId: p.id, from: cur, to: base });
  }
  return { varChanges, mfChanges };
}