WFDASH-WEBAPP/scripts
SJCoetz db1e7d6f76 perf(quotes): server-side tab filter, slim expand, combined counts + bonus bugfix
Same shape as the orders refactor \u2014 applied to the quotes page,
with a fix for a long-standing tab-count bug along the way.

# Measured against the live PB (8,466 quotes, 4,514 orders, 25,612 items)

Before:
  - 22 parallel per-status getList(1, 1) queries for the badge
    counts: 2.6s wall time (no quotes.status index)
  - 64 KB JSON response for 20 quotes with full deep-expand
    (45+ fields per quote, including unbounded email_body)
  - Page footer shows 'Page 1 of N' where N is the global count
    \u2014 'Completed' tab had 7,849 quotes = 'Page 1 of 393'

After (no PB index, just code):
  - 1 combined counts query: 138ms (19x faster)
  - 5.5 KB JSON response (12x smaller)
  - Server-side tab filter: page 1 of N reflects the tab's set
  - Cancelled tab badge now correctly counts rejected+cancelled

# Server-side read model

- getQuotesDashboardData in src/lib/server/quotes-service.ts
  - tab-scoped list + global counts in one round-trip
  - tab param drives a server-side filter (status IN (...) for
    workflow tabs, relation filter for delivery_note, is_template
    for templates, excludeTemplate for 'all')
  - pagination reflects the active tab's matching set, not the
    global ~8.5k quote count

- getQuoteStatusTabCounts: one combined getFullList with all
  17 counted statuses ORed (fields: 'status'), deduped in memory.
  Replaces 22 separate per-status scans. Cached 60s on the server
  with bypassCache option for future realtime refetches.

- /api/v1/quotes/list thin route: validates tab against
  QUOTE_TAB_ORDER, threads tab + nocache params to the service.

# Slim wire payload

- fields param on the orders list: just the columns the page reads
  + per-expand limits (quote.quote_number doesn't need
  email_body, company.company doesn't need its address field, etc.)
- Dropped the customer.company 3-level expand (the page never read
  expand.customer.expand.company \u2014 only expand.company.company,
  which is a direct relation from quote)
- searchQuotesWithItems: same fields added, with
  fetchQuoteItemsForQuotes now accepting fields

# Bonus bug: 'Cancelled' tab badge was always empty

The old getQuoteStatusCounts mapped both 'cancelled' AND 'rejected'
to the 'completed' count, so the page's 'Cancelled' tab badge
showed 0 even when 60+ rejected quotes existed. The page's
tabFilters separately grouped them under 'cancelled', so a quote
that was rejected showed up in the list but not the count.

The new QUOTE_TAB_STATUSES in the service layer is the canonical
mapping (cancelled absorbs both statuses). Page constants and
service constants now derive from the same source \u2014 no more drift.

# Index migration

scripts/migrations/2026-08-04-add-quotes-status-index.ts. Idempotent.
The live PB has no quotes.status index today (verified by hitting
/api/collections/quotes). With the index, the combined counts
query drops from 138ms to sub-10ms on the live instance.

# Tests

6 new tests in quotes-service.test.ts:
  - QUOTE_TAB_STATUSES contract (cancelled absorbs rejected,
    no empty status lists unless special-cased)
  - getQuoteStatusTabCounts buckets correctly into tabs
  - Cache hit returns the same counts on second call
  - bypassCache: true sees new state
  - getQuotesDashboardData end-to-end: tab-scoped items +
    global counts
  - Regression guard for the cancelled/rejected bug

Mock infrastructure upgrade: the quotes-service mock's where
function is now a proper recursive-descent filter parser (handles
nested || inside parens, empty quoted values). getList mock now
returns totalPages.

Full suite: 1312/1312 pass.

Co-authored-by: Orca <help@stably.ai>
2026-08-04 19:01:31 +02:00
..
dev feat(sprint): add new sprint pricing workflow and supporting code 2026-07-03 07:29:43 +02:00
migrations perf(quotes): server-side tab filter, slim expand, combined counts + bonus bugfix 2026-08-04 19:01:31 +02:00
smoke chore: add logos, opencode config and build assets 2026-07-27 08:21:20 +02:00
utils refactor: switch hardcoded UI colors to semantic theme tokens 2026-07-07 17:22:03 +02:00
2026-08-03-load-husqvarna-cart.ts chore(scripts): one-time stock-order loaders + source screenshots 2026-08-03 17:33:28 +02:00
2026-08-03-load-yg1-quote-soq078608.ts chore(scripts): one-time stock-order loaders + source screenshots 2026-08-03 17:33:28 +02:00
2026-08-03-merge-yg1-drafts.ts chore(scripts): one-time stock-order loaders + source screenshots 2026-08-03 17:33:28 +02:00
audit-pb-fields.py chore: add logos, opencode config and build assets 2026-07-27 08:21:20 +02:00
backfill-po-items.mjs refactor: switch hardcoded UI colors to semantic theme tokens 2026-07-07 17:22:03 +02:00
build_v5_workflow.py feat: add quote email intake and atomic numbering 2026-06-22 13:58:24 +02:00
deploy_v5_to_n8n.py feat: add quote email intake and atomic numbering 2026-06-22 13:58:24 +02:00
import-data.ts
mrbxnim4-telegram-cloud-photo-size-4-6016860144018328845-y.jpg feat(invoicing): add QuickBooks invoicing flow and print functionality 2026-07-09 09:44:55 +02:00
n8n-v2-error.ts
n8n-v2-main.ts
quotes.pb.js refactor: switch hardcoded UI colors to semantic theme tokens 2026-07-07 17:22:03 +02:00
seed-gielie-quotes.py feat(sprint): add new sprint pricing workflow and supporting code 2026-07-03 07:29:43 +02:00
setup-pocketbase.ts refactor: drop order-level status, shift to item-driven workflow 2026-06-29 13:06:01 +02:00
verify_v5.py feat: add quote email intake and atomic numbering 2026-06-22 13:58:24 +02:00