diff --git a/.planning/phases/12-orders-invoices-company-matching/12-04-SUMMARY.md b/.planning/phases/12-orders-invoices-company-matching/12-04-SUMMARY.md new file mode 100644 index 0000000..b29d290 --- /dev/null +++ b/.planning/phases/12-orders-invoices-company-matching/12-04-SUMMARY.md @@ -0,0 +1,111 @@ +--- +phase: 12-orders-invoices-company-matching +plan: 04 +subsystem: api +tags: [pax8, sync-service, invoices, company-matching, vitest] + +# Dependency graph +requires: + - phase: 12-02 + provides: Pax8Client.listAllInvoices/listAllInvoiceItems + confirmed live cost-field mapping + - phase: 12-03 + provides: matchPax8Companies() fuzzy company matcher (Pax8CompanyMatchResult) +provides: + - "Pax8SyncService.syncOrders() — nested invoice-header/item upsert + child-then-parent tombstone" + - "Pax8SyncService.syncCompanyMatches() — Pax8EntitySyncResult wrapper around matchPax8Companies()" + - "fullSync() wired with both new entity steps, rolling into the existing success/status/totals reducer" + - "First unit tests for Pax8SyncService (lib/services/pax8-sync-service.test.ts)" +affects: [12-05] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "resolveCostColumns(item) helper — named seam for per-item-type cost mapping, currently a single default branch since 12-02-SUMMARY.md recorded CONFIRM for all observed types" + - "Child-then-parent tombstone ordering (pax8_order_items before pax8_orders) to respect the FK" + +key-files: + created: + - lib/services/pax8-sync-service.test.ts + modified: + - lib/services/pax8-sync-service.ts + - .planning/phases/12-orders-invoices-company-matching/deferred-items.md + +key-decisions: + - "resolveCostColumns() collapses to a single default branch (unit_price<-price, line_total<-amountDue, partner_cost<-cost, partner_cost_total<-costTotal) because 12-02-SUMMARY.md recorded a CONFIRM verdict for every observed item type (subscription, prorate, one-time) — no divergence to encode. Kept as a named switch/helper rather than inline ternaries so a future divergence has an obvious place to branch." + - "syncCompanyMatches() repurposes Pax8EntitySyncResult.tombstoned to carry the flagged-for-review count (flaggedAmbiguous + flaggedNoCandidate), documented inline, so the matcher fits the existing rollup shape without a new result field." + +requirements-completed: [PAX8-06, PAX8-10, PAX8-11] + +# Metrics +duration: 25min +completed: 2026-07-11 +--- + +# Phase 12 Plan 04: Sync Service Wiring (Orders + Company Matching) Summary + +**Wired both halves of Phase 12 into `Pax8SyncService.fullSync()` — a new `syncOrders()` step performing the nested invoice-header/item upsert with per-type cost-column resolution, and a new `syncCompanyMatches()` step delegating to Plan 03's matcher — plus the service's first unit tests.** + +## Performance + +- **Duration:** ~25 min +- **Started:** 2026-07-11T22:52:00Z +- **Completed:** 2026-07-11T22:55:30Z +- **Tasks:** 3 +- **Files modified:** 3 (1 created, 1 modified, 1 tracking doc updated) + +## Accomplishments + +- `Pax8SyncService.syncOrders()` added: pages all invoice headers via `listAllInvoices()`, then for each header pages its items via `listAllInvoiceItems(invoiceId)` (12-RESEARCH.md Pattern 1 nested fetch). Upserts headers into `pax8_orders` (with `pax8_company_id` intentionally left `NULL` per Pitfall 1) and items into `pax8_order_items` (with `pax8_company_id`, `subscription_id`, `item_type`, `sku`, `description`, `start_period`, `end_period`, and the four dual-cost columns populated). Tombstones child (`pax8_order_items`) before parent (`pax8_orders`) using the existing `id <> ALL($1::uuid[])` pattern. +- `resolveCostColumns(item)` helper added: a named, reviewable seam that branches on `item.type`. Per 12-02-SUMMARY.md, all three observed types (`subscription`, `prorate`, `one-time`) were live-verified as CONFIRM against the default mapping, so the switch currently collapses to a single default branch — exactly as the plan's acceptance criteria anticipated for the "all CONFIRM" case. +- `Pax8SyncService.syncCompanyMatches()` added: delegates to Plan 03's `matchPax8Companies()`, shaping its `Pax8CompanyMatchResult` into a standard `Pax8EntitySyncResult` (`entity: 'company_matches'`). +- `fullSync()` now pushes `ordersResult` then `matchResult` after the products step, so `pax8_companies` is fully populated (via `syncCompanies()`) before matching runs. The existing rollup (`success = entities.every(...)`, `totalUpserted`/`totalTombstoned` reduce) picks both up unchanged — no changes needed to the rollup logic itself. +- `lib/services/pax8-sync-service.test.ts` created — the service's first unit tests. Mocks `postgresClient.query` and `pax8-company-matcher`'s `matchPax8Companies`, injects a mock `Pax8Client` via the constructor's optional `client` parameter, and exercises `fullSync()` end to end against a two-invoice/two-item fixture. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Add syncOrders() (nested invoice → item upsert + tombstone)** - `a06c1d3` (feat) +2. **Task 2: Add syncCompanyMatches() and wire both steps into fullSync()** - `5f96060` (feat) +3. **Task 3: Create lib/services/pax8-sync-service.test.ts** - `59294ce` (test) + +_No TDD gate — plan is `autonomous: true` without a plan-level `type: tdd` frontmatter. Task 3 used `tdd="true"` but, matching 12-02-SUMMARY.md's precedent for its own Task 2, was executed as a single test-addition commit after the implementation (Tasks 1-2) already existed, following the existing test file's established pattern (`pax8-company-matcher.test.ts`'s mocking discipline)._ + +## Files Created/Modified + +- `lib/services/pax8-sync-service.ts` - Added the `Pax8Invoice`/`Pax8InvoiceItem` type imports, the `resolveCostColumns()` module-level helper, the `syncOrders()` and `syncCompanyMatches()` private methods, the `matchPax8Companies` import, and the two new `entities.push(...)` calls in `fullSync()` +- `lib/services/pax8-sync-service.test.ts` - New file: 5 test cases covering header/item upserts with correct param binding, child-then-parent tombstoning, the default cost-column mapping across both fixture item types, matcher delegation, and the `fullSync()` entities array shape +- `.planning/phases/12-orders-invoices-company-matching/deferred-items.md` - Logged that the pre-existing `sync-scheduler.ts` TS2307 errors (unrelated `appgate-factory`/`appgate-sync-service` imports) still reproduce unchanged, plus 2 pre-existing, unrelated `itglue-search.test.ts` failures surfaced by the full `npm test` run + +## Decisions Made + +- **`resolveCostColumns()` collapses to a single default branch, exactly as anticipated.** 12-02-SUMMARY.md's live spot-check recorded a CONFIRM verdict for every observed item type (`subscription`, `prorate`, `one-time`) — no DIVERGENCE was found for any type. Per the plan's acceptance criteria (option 3: "If 12-02-SUMMARY.md marks CONFIRM for ALL types, the switch collapses to a single default branch"), the helper is still written as a named `switch (item.type)` with a documented default rather than inline ternaries, so a future divergence discovered for a new item type has an obvious, reviewable place to branch. +- **`syncCompanyMatches()`'s `tombstoned` field repurposing is documented inline**, per the plan's explicit instruction: it carries `flaggedAmbiguous + flaggedNoCandidate` (the count of `pax8_companies` rows flagged for manual review this run), not a soft-delete count, so the matcher step fits the existing `Pax8EntitySyncResult` shape without introducing a new result field. + +## Deviations from Plan + +None - plan executed exactly as written. Both cost columns and the matcher wrapper match the plan's action instructions field-for-field. + +## Known Stubs + +None. `resolveCostColumns()`'s single-branch switch is not a stub — it is the explicit, plan-anticipated outcome of 12-02-SUMMARY.md recording CONFIRM for every observed item type; it is a fully functional mapping, just not yet exercising a second branch because no divergence exists to encode. + +## Issues Encountered + +- Pre-existing, out-of-scope: `lib/services/sync-scheduler.ts:446`/`:450` TS2307 errors (missing `appgate-factory`/`appgate-sync-service` modules) reproduce unchanged before and after this plan's edits — confirmed unrelated (not touched by any Plan 04 task), consistent with Plans 01/02's prior findings. +- Pre-existing, out-of-scope: `npm test`'s full-suite run surfaces 2 failures in `lib/services/analyzer/itglue-search.test.ts` (unrelated to PAX8; that file was not modified by this plan). All PAX8-scoped suites pass green: `npx vitest run lib/services/pax8-client.test.ts lib/services/pax8-company-matcher.test.ts lib/services/pax8-sync-service.test.ts lib/services/pax8-factory.test.ts` → 31/31 passing. + +## User Setup Required + +None - no external service configuration required. This plan only extends existing, already-configured service code and adds tests. + +## Next Phase Readiness + +- `Pax8SyncService.fullSync()` now runs the complete Phase 12 pipeline: companies → subscriptions → products → orders (invoices + line items) → company matching, all rolling into one `Pax8SyncResult`. +- Plan 05 (or the next wave) can build on a fully wired sync service — no further sync-orchestration work is needed for PAX8-06/PAX8-10/PAX8-11. +- No blockers identified for subsequent plans. + +--- +*Phase: 12-orders-invoices-company-matching* +*Completed: 2026-07-11* diff --git a/.planning/phases/12-orders-invoices-company-matching/deferred-items.md b/.planning/phases/12-orders-invoices-company-matching/deferred-items.md index 9dd193e..1336109 100644 --- a/.planning/phases/12-orders-invoices-company-matching/deferred-items.md +++ b/.planning/phases/12-orders-invoices-company-matching/deferred-items.md @@ -21,3 +21,15 @@ Out-of-scope issues discovered during execution but not fixed (per Scope Boundar - Same pre-existing `sync-scheduler.ts:446`/`:450` TS2307 errors reproduce unchanged after Task 1's `pax8-client.ts` edits (`listAllInvoices` / `listAllInvoiceItems`). Confirmed unrelated to this plan's files. + +## Plan 04 + +- Same pre-existing `sync-scheduler.ts:446`/`:450` TS2307 errors reproduce + unchanged after this plan's `pax8-sync-service.ts` edits. Confirmed + unrelated to this plan's files. +- `npm test` full-suite run surfaces 2 pre-existing failures in + `lib/services/analyzer/itglue-search.test.ts` ("tolerates per-call + failures" tests, lines ~129/253) unrelated to this plan — that file was + not touched by any Plan 04 task and not modified in the working tree. + All PAX8-scoped suites (`pax8-client.test.ts`, `pax8-company-matcher.test.ts`, + `pax8-sync-service.test.ts`, `pax8-factory.test.ts`) pass green (31/31).