- syncCompanyMatches() delegates to matchPax8Companies() (Plan 03),
shaping its result into the standard Pax8EntitySyncResult
- fullSync() now pushes ordersResult then matchResult after products,
so pax8_companies is fully populated before matching runs
- Both steps roll up into the existing success/status/totals reducer
and sync_history record unchanged
- Adds Pax8SyncService.syncOrders(): pages all invoice headers, then
per-header pages its items (12-RESEARCH.md Pattern 1 nested fetch)
- resolveCostColumns() branches on item.type per 12-02-SUMMARY.md's
live spot-check verdicts (all types CONFIRM -> single default branch,
kept as a named seam for future divergence)
- pax8_orders.pax8_company_id stays NULL (Pitfall 1); per-company data
lives on pax8_order_items.pax8_company_id
- Tombstones child (pax8_order_items) before parent (pax8_orders) to
respect the FK, using the existing id <> ALL($1::uuid[]) pattern
- fullSync() orchestrates companies -> subscriptions -> referenced-only
products, each with independent try/catch returning Pax8EntitySyncResult
- Referenced product catalog resolved in a single pass: listAllProducts()
fetched once, filtered in-memory to subscription-referenced ids (D-01/D-02)
- Each entity's UUID-array tombstone soft-deletes rows PAX8 no longer
returns (is_deleted=true, deleted_at set), skipped when zero ids seen
- sync_history row (entity_type='pax8', sync_type='full') tracks
started/completed/failed with base columns only
- No PAX8 writes: only Pax8Client's read methods are called