Wiki · NetSuite expansion

NS inventory reconciliation

Weekly cycle counts, threshold-gated auto-adjust, HITL for the outliers, audit trail. The unsexy infrastructure that keeps quantityonhand honest. One STUB: scanner autoflow — today it's still paper and keyboard.

Mixed · 9 real, 1 STUB
What this is

Cycle counts, not annual freeze

The NS inventory reconciliation flow runs a rotating cycle count: every warehouse zone is counted once every 30 days; every SKU lands on the list at least once per month. We never do annual full freezes — the warehouse can't go dark for a day, and cycle counts catch shrink + bin-error far faster anyway.

The discipline came in after R49 — senior architect review flagged inventory drift as the silent budget-killer. Pre-cycle-counts, we'd discover quantityonhand drift only when a SO short-picked. Post-cycle-counts, variance is caught in the same week.

Diagram: ns-inventory-reconciliation.html. The flow has 10 phases. One is STUB: physical-count autoflow via barcode scanner. Today, warehouse staff walk the zone with a paper tally sheet or type counts into the NS UI mobile app. There is no scanner integration.

When to use it

Trigger conditions

Threshold

Auto-adjust if |variance_pct| ≤ 2% AND |variance_value| ≤ $100. Outside that band, HITL fires. Both bars must hold — a 0.5% variance on a $50K SKU is still a $250 swing and gets HITL.

Worked example

Zone B count — Monday morning

Scenario

Monday 06:00. The weekly cron picks Zone B (cold dock, 47 SKUs). Floor team (Marcus + temp) walks the zone with the paper tally sheet (STUB — no scanner). Counts entered into NS UI by 09:30. The variance engine runs at 09:35.

46 SKUs match within 2% / $100 — auto-adjusted, NS push queued, D1 mirror updated by 09:38. One outlier: SKU 21044 — Right Start Foods Egg Patty shows physical 184 cases, on-hand 142 cases — +29.6%, $1,470 value. HITL fires. Card lands at /proposed-actions.html with the variance, the prior-30-day txn list, and a suggested reason ("partial pallet not booked on PO receipt 3 days ago, likely receiving timing"). Mike taps Approve at 10:14. NS adjustment posted by 10:16. reflexion_log row written; events.inventory.adjusted fires. Customer health watcher consumes the event for the affected customer's allocation outlook.

Step-by-step what happens

The ten beats

  1. 01

    Cycle count scheduled

    Weekly cron per warehouse zone selects a count list. Cron rotates zones so every SKU is counted within 30 days. The list is materialized into cycle_count_queue with status='pending'.

    Cron 0 6 * * 1
    Rotation 4 zones / 30 days
  2. 02

    Physical count — STUB autoflow

    Warehouse staff walks the zone and records counts. Today: paper tally sheet or NS UI mobile keyboard entry. Barcode scanner integration is on the long-term roadmap but not wired — no scanner hardware deployed, no scanner-to-NS path. This is the single highest leverage automation gap in inventory ops.

    STUB scanner autoflow
    Today paper + keyboard
  3. 03

    Variance detection

    Diff physical_qty vs inventory_balance.quantityonhand per item × location. Compute variance_pct and variance_abs (absolute units and dollar value at current cost).

    Reads inventory_balance, items.unit_cost
    Writes cycle_count_results
  4. 04

    Threshold branch — auto or HITL

    If |variance_pct| ≤ 2% AND |variance_value| ≤ $100, route to auto-adjust. Otherwise route to HITL. Threshold configurable in guardrails.

    Reads guardrails
  5. 05

    HITL stage — for outliers

    A proposed_actions row is inserted with action_type='inventory_adjustment'. Payload contains item_code, location, on_hand, physical_qty, variance, suggested_reason. The suggested_reason field looks at recent transactions for hints (PO receipt timing, recent damage flag, etc.).

    Writes proposed_actions
    Risk 2-3 depending on $ value
  6. 06

    Mike approves at /proposed-actions.html

    Admin reviews variance and reason. X-Edit-Token required for confirm. Approval flips proposed_actions.status='approved' via the R560 atomic claim.

    Writes proposed_actions.status
    Atomic R560 claim
  7. 07

    NS push — inventoryadjustment

    Approval drops a row in ns_pending_pushes targeting record_type='inventoryadjustment'. PushMutexDO drains and writes via the NS RESTlet OAuth1. Retry policy: 3 attempts, exponential backoff.

    Writes NS inventoryadjustment
    Retry 3 attempts
  8. 08

    D1 mirror update

    inventory_balance.quantityonhand reflects the new value. Reservation rows recomputed. Sync engine confirms NS parity on the next 5-min sync.

    Writes inventory_balance
    Confirm next sync
  9. 09

    Audit trail

    reflexion_log row written with reason + delta + approved_by. events.inventory.adjusted fires onto the event ledger. Customer health watcher, anomaly detector, and timeline UI all consume this event.

    Writes reflexion_log, events
  10. 10

    Variance report

    The admin-dashboard tile surfaces last 30 days of variances by zone, item, and value. Patterns inform the next cron schedule — if Zone B keeps producing outliers, the cron raises its weight.

    Reads cycle_count_results
    Surfaces /admin-dashboard
Outcomes

What's different after a count

Variance
Resolved
auto or HITL
Audit trail
Written
reflexion + event
NS ↔ D1
In sync
5-min warm tier
Allocation
Accurate
SO short-picks ↓
Failure modes

What can go wrong

Scanner STUB — count error risk

Paper tallies introduce transcription errors. Mitigation: floor lead reviews the tally before NS entry. Detection: HITL rate > 5% of count items indicates systemic miscounting. Long-term fix: scanner integration with NS UI mobile.

Count happens during active picking

If picking happens mid-count, physical_qty becomes a moving target. Mitigation: zone-level pick hold for the count window. Detection: variance + pick-list timestamps that overlap the count window.

NS push fails — D1 ahead of NS

D1 has the adjustment, NS doesn't. The reconciliation cron at 0 */15 * * * catches this within 15 min and re-enqueues. Manual recovery: POST /admin/ns-push/retry?action_id=<id>.

Auto-adjust threshold drift

If many counts cluster just under the threshold, real drift hides. Detection: variance histogram on the admin-dashboard tile. Recovery: tighten the threshold or trigger ad-hoc HITL review.

Related

Adjacent flows + diagrams

For developers

Code paths + invariants

ConcernWhere
Cycle count cronwrangler.toml — 0 6 * * 1
Count queuecycle_count_queue (D1)
Variance enginesrc/index.ts cycle_count handler
Thresholdsguardrails.inventory_variance_pct, _value
HITL action_typeproposed_actions.action_type='inventory_adjustment'
NS push recordrecord_type='inventoryadjustment'
Event emittedevents.event_type='inventory.adjusted'
Audit rowreflexion_log entity_type='inventory_adjustment'
STUB — scannerno scanner hardware; paper/keyboard only
// Threshold branch (simplified) const withinPct = Math.abs(variance_pct) <= 0.02; const withinValue = Math.abs(variance_value) <= 100; if (withinPct && withinValue) { autoAdjust(item, location, physical_qty); } else { stageProposedAction({ action_type: 'inventory_adjustment', risk_level: variance_value > 1000 ? 3 : 2, payload: { item, location, on_hand, physical_qty, variance, suggested_reason } }); }