Skip to main content

Commercial Operations Phased Spec

This is the executable spec sheet for commercial operations in apps/admin.

Use this document to implement, review, and ship the commercialization program end to end.

Related strategy doc:

1. Purpose

Build a reliable commercial operating system that supports:

  1. safe auto-dispatch of eligible leads
  2. delivery-based billing (not sales-outcome billing)
  3. manual Stripe-first invoicing at the current stage
  4. auditable reconciliation between delivery events and invoices

2. Scope And Constraints

In scope

  1. Delivery ledger and finance-ready references.
  2. Monthly invoice workflow with manual Stripe issuance.
  3. Intake-triggered auto-dispatch with exception handling.
  4. Operator UI for drafts, issued invoices, and reconciliation status.
  5. Guardrails, tests, and rollout controls.

Out of scope (for this spec)

  1. Full Stripe API automation for invoice creation and payment sync.
  2. Outcome-based pricing or pay-on-sale contracts.
  3. Installer self-service portal.
  4. Advanced pricing optimization models.

3. Canonical Commercial Rules

  1. Billable event is assignment transition to sent.
  2. Cycle-cap usage defaults from that same delivery event but remains independently releasable.
  3. Exclusive product maps to lead path recommended_installer and 1 installer assignment.
  4. Shared product maps to lead path compare_quotes and up to 3 installer assignments.
  5. Unit price is snapped at delivery event creation and does not mutate retroactively.
  6. Credits are only for objective operational defects, not conversion outcomes.

4. Source Of Truth Boundaries

  1. App ledger is source of truth for delivery eligibility and amounts.
  2. Stripe is source of truth for invoice issuance and payment collection artifacts.
  3. App stores Stripe references for reconciliation and audit:
    • stripeCustomerId
    • stripeInvoiceId
    • optional invoice URL
    • optional payment external reference

5. Data Model Spec

Target file:

  • apps/admin/convex/schema.ts

5.1 New table: installerPricingPlans

Fields:

  1. installerId: Id<"installers">
  2. effectiveFrom: number
  3. effectiveTo?: number
  4. exclusivePriceCents: number
  5. sharedPriceCents: number
  6. currency: "AUD" (literal for now)
  7. createdAt: number
  8. lastUpdatedAt: number

Indexes:

  1. by_installer_effective_from on installerId, effectiveFrom
  2. by_effective_window on effectiveFrom, effectiveTo

5.2 New table: deliveryEvents

Fields:

  1. assignmentId: Id<"routingAssignments">
  2. leadId: Id<"leads">
  3. installerId: Id<"installers">
  4. deliveredAt: number
  5. productType: "exclusive" | "shared"
  6. unitPriceCents: number
  7. currency: "AUD"
  8. eventStatus: "billable" | "credited" | "void"
  9. creditReasonCode?: string
  10. invoiceId?: Id<"invoices">
  11. stripeInvoiceId?: string
  12. createdAt: number
  13. lastUpdatedAt: number

Indexes:

  1. by_assignment on assignmentId (enforce idempotency in code)
  2. by_installer_delivered_at on installerId, deliveredAt
  3. by_status_delivered_at on eventStatus, deliveredAt
  4. by_invoice on invoiceId

5.3 New table: invoices

Fields:

  1. installerId: Id<"installers">
  2. periodStart: number
  3. periodEnd: number
  4. status: "draft" | "issued" | "paid" | "partially_paid" | "void"
  5. subtotalCents: number
  6. creditsCents: number
  7. totalCents: number
  8. balanceCents: number
  9. currency: "AUD"
  10. issuedAt?: number
  11. dueAt?: number
  12. paidAt?: number
  13. stripeInvoiceId?: string
  14. stripeInvoiceUrl?: string
  15. notes?: string
  16. createdAt: number
  17. lastUpdatedAt: number

Indexes:

  1. by_installer_period on installerId, periodStart
  2. by_status_period on status, periodStart
  3. by_stripe_invoice_id on stripeInvoiceId

5.4 New table: invoiceLineItems

Fields:

  1. invoiceId: Id<"invoices">
  2. deliveryEventId: Id<"deliveryEvents">
  3. description: string
  4. quantity: number
  5. unitPriceCents: number
  6. lineTotalCents: number
  7. createdAt: number

Indexes:

  1. by_invoice on invoiceId
  2. by_delivery_event on deliveryEventId

5.5 New table: invoiceAdjustments

Fields:

  1. invoiceId: Id<"invoices">
  2. type: "credit" | "debit"
  3. reasonCode: string
  4. amountCents: number
  5. notes?: string
  6. actorUserId: Id<"users">
  7. actorEmail: string
  8. createdAt: number

Indexes:

  1. by_invoice_created_at on invoiceId, createdAt

5.6 New table: payments

Fields:

  1. invoiceId: Id<"invoices">
  2. amountCents: number
  3. receivedAt: number
  4. method: string
  5. reference?: string
  6. externalPaymentRef?: string
  7. createdAt: number

Indexes:

  1. by_invoice_received_at on invoiceId, receivedAt

6. Backend Function Spec

Primary target modules:

  1. apps/admin/convex/routing.ts
  2. apps/admin/convex/leads.ts
  3. apps/admin/convex/http.ts
  4. new apps/admin/convex/billing.ts
  5. optional apps/admin/convex/internal/billingInternal.ts

6.1 Delivery event emission

Trigger point:

  1. Assignment status transition to sent.

Rules:

  1. One delivery event per assignment (by_assignment uniqueness guard in mutation logic).
  2. Determine product type from assignment selectedPath.
  3. Resolve active pricing plan by installer and delivery timestamp.
  4. Snapshot unitPriceCents and currency.

6.2 Auto-dispatch internal entrypoint

Implement an internal mutation callable from intake flow without interactive admin session.

Rules:

  1. Reuse same eligibility and capacity logic as existing routing behavior.
  2. If dispatch fails, write explicit failure record and notify ops.
  3. Do not silently swallow dispatch exceptions.

6.3 Intake-trigger dispatch wiring

After successful lead insert in apps/admin/convex/leads.ts:

  1. attempt auto-dispatch when policy allows
  2. otherwise push lead to manual exception queue path

6.4 Billing operations API

Public/admin functions:

  1. previewInvoicePeriod({ periodStart, periodEnd, installerId? })
  2. generateInvoiceDrafts({ periodStart, periodEnd, installerIds? })
  3. markInvoiceIssued({ invoiceId, stripeInvoiceId, stripeInvoiceUrl?, dueAt })
  4. recordInvoicePayment({ invoiceId, amountCents, receivedAt, method, reference? })
  5. addInvoiceAdjustment({ invoiceId, type, reasonCode, amountCents, notes? })
  6. listInvoices({ status?, installerId?, periodStart?, periodEnd? })
  7. listDeliveryEvents({ installerId?, status?, periodStart?, periodEnd? })
  8. exportInstallerInvoicePayload({ invoiceId })

7. Admin UI Spec

7.1 New surfaces

  1. Billing workspace route (suggested /billing under admin app).
  2. Invoice draft list and issue action.
  3. Issued invoice detail with Stripe reference fields.
  4. Delivery events table with finance status, cap status, filters, and status chips.

7.2 Existing surface updates

  1. Installer detail: pricing plan management.
  2. Lead detail / assignment history: billable event status, cap status, and linked invoice reference.
  3. Dashboard cards: cap used this period, invoiced amount, outstanding balance.

7.3 Required UI states

  1. empty state
  2. loading state
  3. partial failure state for bulk invoice generation
  4. validation error state for missing Stripe reference on issue

8. Phased Delivery Plan

Phase 0: Manual Stripe Operating Baseline

Objective:

  1. Start billing operations without Stripe API automation.

Deliverables:

  1. Delivery events table + writer.
  2. Draft invoice generation in app.
  3. Export payload for manual Stripe invoice creation.
  4. Capture stripeInvoiceId back into app.

Acceptance criteria:

  1. Month-end can run with no spreadsheets required for event aggregation.
  2. Every issued invoice has app record and Stripe reference.
  3. At least one dry-run monthly close succeeds in staging/dev data.

Phase A: Ledger Hardening

Objective:

  1. Make delivery and invoice records immutable where needed and auditable.

Deliverables:

  1. finance-safe invariants on issue/finalization.
  2. adjustment and payment write paths.
  3. reconciliation status query.

Acceptance criteria:

  1. No modification of draft line composition after issue except adjustments.
  2. Reconciliation view reports mismatch and missing Stripe refs clearly.

Phase B: App-Assisted Invoice Ops

Objective:

  1. Reduce operator effort for issuing and reconciling invoices.

Deliverables:

  1. invoice issue workflow with validation.
  2. payment/partial payment updates.
  3. arrears and overdue dashboard metrics.

Acceptance criteria:

  1. Operator can execute full period close from admin UI.
  2. Invoice status lifecycle transitions are validated and auditable.

Phase C: Auto-Dispatch Defaulting

Objective:

  1. Shift from manual routing-first to policy auto-dispatch-first.

Deliverables:

  1. intake-triggered internal auto-dispatch.
  2. exception queue for failed or ineligible leads.
  3. monitoring for dispatch latency and failure rate.

Acceptance criteria:

  1. Eligible new leads are dispatched without operator action.
  2. Failures are visible and actionable within one queue view.

Phase D: Scale Controls And Nice-To-Haves

Objective:

  1. Improve manager ergonomics and operational throughput.

Deliverables:

  1. quick-win management features from readiness addendum.
  2. bulk installer operations and saved views.
  3. escalation automation and auth hardening later in phase.

Acceptance criteria:

  1. Day-to-day manager workflows require fewer manual steps.
  2. escalation and permission controls remain safe under growth.

9. Validation And Test Spec

Backend tests

  1. Delivery event idempotency test (single event per assignment).
  2. Price snapshot correctness test at effective date boundaries.
  3. Invoice draft generation totals test.
  4. Adjustment and payment balance math test.
  5. Intake auto-dispatch success/failure path tests.

UI validation

  1. Invoice generation flow (draft to issued).
  2. Manual Stripe reference capture flow.
  3. Reconciliation status display correctness.
  4. Delivery event filtering and export behavior.

Operational dry runs

  1. Simulated month close in non-prod with seeded leads and assignments.
  2. Manual Stripe invoice entry for sample installers.
  3. Reconciliation check after marking payments.

10. Rollback And Safety Plan

  1. Feature flag intake auto-dispatch path.
  2. Keep manual routing controls active during Phase C rollout.
  3. If delivery event writer fails, block invoice generation and raise ops alert.
  4. Never delete delivery or invoice rows in rollback; use status transitions and adjustments.

11. Monthly Operating Runbook (Manual Stripe)

Week 1 cadence:

  1. Review previous period invoice statuses and outstanding balances.
  2. Confirm pricing plan changes for current period.

Month-end close:

  1. Run draft generation for period.
  2. Review outliers and credits.
  3. Export per-installer payload.
  4. Create Stripe invoices manually.
  5. Save Stripe invoice references in app.
  6. Mark invoices issued in app.

Weekly reconciliation:

  1. Check paid/partial/overdue status.
  2. Record payments in app.
  3. Resolve mismatches between app balance and Stripe state.

12. Open Decisions (Must Be Locked Before Build Starts)

  1. exact invoice period boundaries and timezone rules
  2. invoice numbering convention (app-generated vs Stripe-only)
  3. GST display and tax line strategy
  4. credit reason code taxonomy
  5. Stripe role ownership (creator, approver, reconciler)

13. Dependency Map

  1. Phase 0 depends on finalized pricing policy and credit reason taxonomy.
  2. Phase A depends on Phase 0 delivery event and invoice draft records existing.
  3. Phase B depends on Phase A ledger immutability and reconciliation queries.
  4. Phase C depends on stable assignment and delivery event writes from earlier phases.
  5. Phase D can run partially in parallel, but escalation automation should wait until Phase C is stable.

14. Go/No-Go Checklist (Before Starting Build)

All items must be explicitly decided and written before implementation starts:

  1. Invoice period boundaries and timezone source.
  2. GST behavior and whether amounts are tax-inclusive or tax-exclusive in app totals.
  3. Credit reason code list and approval policy.
  4. Stripe account ownership model and operator permissions.
  5. Which roles can issue invoices, add credits, and record payments.
  6. Manual Stripe workflow cadence and owner for weekly reconciliation.
  7. Naming and status conventions approved for new billing tables and UI labels.

15. Phase 0 First Sprint Plan (Execution Starter)

Use this as the first implementation sprint to avoid over-scoping.

Sprint goal

  1. Produce billable delivery ledger and export-ready draft invoice payloads.

Sprint scope

  1. Add installerPricingPlans, deliveryEvents, and invoices schema tables.
  2. Write delivery events on assignment transition to sent with idempotency.
  3. Add billing draft generation and list queries.
  4. Add export payload query for manual Stripe invoice creation.
  5. Add minimal admin UI for draft invoice list and Stripe reference capture.

Sprint out of scope

  1. Full payment reconciliation UI.
  2. Credit/debit adjustment workflow.
  3. Auto-dispatch default behavior.

Sprint acceptance checks

  1. For a test period, generated draft totals match delivery event sums.
  2. No duplicate delivery events are created for repeated sent transitions.
  3. Stripe invoice id can be captured and persisted to invoice records.
  4. Export payload is sufficient for manual Stripe invoice creation.

16. AI Execution Protocol

Use this section as the build contract for AI-assisted implementation.

  1. Treat this file as implementation spec.
  2. Treat readiness doc as strategy context.
  3. Implement one phase at a time.
  4. Do not start next phase until acceptance criteria for current phase are met.
  5. For each phase:
    • create task checklist
    • implement schema and function changes
    • implement UI changes
    • run validations/tests
    • update planning docs to reflect actual completion

17. Progress Tracking Template

Use this simple tracker directly in PRs or daily notes:

  1. Phase:
  2. Objective:
  3. Completed this cycle:
  4. Validation run:
  5. Blockers:
  6. Next step: