Assist_Design/docs/plans/2026-03-05-invoice-optimization-design.md
barsa 4ebfc4c254 docs: add invoice optimization design plan
Remove N+1 subscription invoice scanning, clean up billing service layer,
and simplify InvoicesList component to single-purpose.
2026-03-05 16:27:58 +09:00

3.7 KiB

Invoice System Optimization Design

Date: 2026-03-05 Status: Approved

Problem

Subscription invoice fetching is slow due to an N+1 query pattern. When viewing a subscription's invoices, the BFF:

  1. Fetches ALL client invoices page by page (full table scan)
  2. For each invoice, makes an individual GetInvoice WHMCS API call to get line items
  3. Filters in-memory by item.serviceId === subscriptionId

For a client with 19 invoices: 1 list call + 19 individual detail calls = 20 WHMCS API calls with batching delays.

Decision

Remove subscription-specific invoice fetching entirely. Most billing portals (Stripe, AWS, DigitalOcean) don't offer per-subscription invoice lists. The WHMCS GetInvoices API doesn't support filtering by service/subscription ID, making this fundamentally expensive.

Instead:

  • Subscription detail page shows billing summary (already available on subscription object) + link to main invoices page
  • Main invoices page uses efficient single GetInvoices call (already working well)
  • Individual invoice detail already shows which subscription each line item belongs to

Additionally, clean up the billing service layer by removing the pass-through BillingOrchestrator.

Changes

Files to DELETE

  1. apps/bff/src/modules/billing/services/billing-orchestrator.service.ts - zero-logic pass-through

BFF Files to EDIT

  1. subscriptions-orchestrator.service.ts - Remove:

    • getSubscriptionInvoices(), tryGetCachedInvoices(), fetchAllRelatedInvoices(), paginateInvoices(), cacheInvoiceResults() (~150 lines)
    • Related type imports (InvoiceItem, InvoiceList)
    • WhmcsInvoiceService dependency (if no other method uses it)
  2. subscriptions.controller.ts - Remove:

    • GET :id/invoices endpoint
    • SubscriptionInvoiceQueryDto, InvoiceListDto DTOs
    • invoiceListSchema, InvoiceList, Validation imports
    • subscriptionInvoiceQuerySchema
  3. whmcs-invoice.service.ts - Remove:

    • getInvoicesWithItems() method (~70 lines)
    • chunkArray, sleep imports (if unused after removal)
  4. whmcs-cache.service.ts - Remove:

    • subscriptionInvoices + subscriptionInvoicesAll cache configs
    • getSubscriptionInvoices(), setSubscriptionInvoices(), getSubscriptionInvoicesAll(), setSubscriptionInvoicesAll()
    • buildSubscriptionInvoicesKey(), buildSubscriptionInvoicesAllKey()
    • Subscription invoice patterns in invalidation methods
  5. billing.controller.ts - Replace BillingOrchestrator with direct WhmcsPaymentService + WhmcsSsoService injection

  6. billing.module.ts - Remove BillingOrchestrator from providers/exports

Portal Files to EDIT

  1. InvoiceList.tsx - Remove dual-mode logic:

    • Remove subscriptionId, showFilters props
    • Remove useSubscriptionInvoices import
    • Remove useInvoicesData() helper, use useInvoices() directly
    • Remove isSubscriptionMode conditionals
    • Simplify InvoicesFilterBar (remove conditional spread)
  2. SubscriptionDetail.tsx - Replace <InvoicesList> with billing link:

    • Remove InvoicesList and InvoiceListSkeleton imports
    • Replace billing history section with "View all invoices" link to /account/billing
  3. app/account/subscriptions/[id]/loading.tsx - Remove InvoiceListSkeleton

  4. useSubscriptions.ts - Remove useSubscriptionInvoices() hook

  5. core/api/index.ts - Remove subscriptions.invoices query key

Impact

  • Eliminates 20+ WHMCS API calls per subscription detail view
  • Removes ~300 lines of scanning/caching/batching code
  • Simplifies InvoicesList to single-purpose component
  • Removes unused service abstraction layer (BillingOrchestrator)
  • No feature regression: invoice data still accessible via main billing page