Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
369 lines
82 KiB
Markdown
369 lines
82 KiB
Markdown
# Scratchpad — Recurring Invoicing Hard Cutover
|
||
|
||
- Plan slug: `recurring-invoicing-hard-cutover`
|
||
- Created: `2026-03-18`
|
||
|
||
## What This Is
|
||
|
||
Working notes for the hard-cutover plan that removes recurring invoice bridge assumptions after service-period-driven recurring execution already exists.
|
||
|
||
## Decisions
|
||
|
||
- (2026-03-18) Treat `client_billing_cycles` as a legitimate client cadence concept, not as recurring invoice execution identity.
|
||
- (2026-03-18) Treat `billing_cycle_id` as historical/optional metadata only in the target state; it should not remain a first-class recurring contract.
|
||
- (2026-03-18) Do not support mixed recurring schema states in steady-state application code.
|
||
- (2026-03-18) Prefer explicit hard failures and repair actions over fallback compatibility rows when recurring service-period materialization is missing.
|
||
- (2026-03-18) This plan is a hard-cutover follow-on to the broader service-period-first plan and the softer service-driven invoicing cutover plan.
|
||
- (2026-03-18) Keep the hard-cutover architecture notes separate from the softer service-driven runbook so the steady-state model is explicit and testable.
|
||
- (2026-03-18) Recurring invoice linkage should derive candidate obligations from canonical persisted detail metadata (`config_id`, service-period window, due position, invoice window) plus contract-line or assignment identity, never from invoice-header `billing_cycle_id`.
|
||
- (2026-03-18) Prepayment classification should use explicit invoice-kind state (`is_prepayment`) rather than the absence of a recurring bridge field; bridge-less recurring invoices and prepayments are separate concepts.
|
||
- (2026-03-18) Persisted recurring execution windows are already canonical recurring truth, so billing-engine validation for selector-input recurring runs must not round-trip back through `client_billing_cycles` or auto-create cycle rows just to validate the window.
|
||
- (2026-03-18) Direct `selector_input` preview/generate requests must normalize and validate against persisted `recurring_service_periods`; treating the caller-provided window as trusted input leaves a gap where mutated windows can bypass canonical service-period authority.
|
||
- (2026-03-18) Client billing schedule edits should regenerate future client-cadence `recurring_service_periods` after the last billed boundary while leaving `client_billing_cycles` available only for schedule administration and historical context.
|
||
- (2026-03-18) Bucket allowance periods should resolve from canonical `recurring_service_periods` for both client cadence and contract cadence; `client_billing_cycles` should not determine bucket rollover windows once recurring service periods exist.
|
||
- (2026-03-18) Hourly and usage recurring charge queries should always use canonical service-period bounds when a persisted recurring selection is present, even if the invoice window is later or shifted metadata.
|
||
- (2026-03-18) Live accounting export lines should emit canonical recurring detail periods when present and otherwise stay periodless financial documents; invoice-header billing periods remain historical read-side metadata only, not live recurring provenance.
|
||
- (2026-03-18) Ready recurring work should use canonical selection actions only; the dashboard must not keep bridge-only row menus such as “Delete Cycle” that disappear when `billingCycleId` is null.
|
||
|
||
## Agent Findings
|
||
|
||
- Documentation slice completed:
|
||
- Added [ARCHITECTURE.md](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/ee/docs/plans/2026-03-18-recurring-invoicing-hard-cutover/ARCHITECTURE.md) to define the hard-cutover invariant, the limited role of `client_billing_cycles`, the required-schema posture, and the `invoices.billing_cycle_id` deprecation posture.
|
||
- Added [RUNBOOK.md](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/ee/docs/plans/2026-03-18-recurring-invoicing-hard-cutover/RUNBOOK.md) to describe the final recurring mental model, missing-materialization diagnosis, and canonical reverse/delete repair expectations.
|
||
- Added [recurringInvoicingHardCutover.runbook.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts) to lock T072/T073/T074 against regressions.
|
||
- Due-work cutover slice completed:
|
||
- Updated [billingAndTax.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingAndTax.ts) so ready recurring work comes only from persisted `recurring_service_periods`, with client-cadence materialization gaps surfaced separately as repair records instead of compatibility due rows.
|
||
- Updated [recurringRunExecutionIdentity.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/shared/billingClients/recurringRunExecutionIdentity.ts) to add canonical `client_cadence_window` identity keyed by schedule/period/window instead of `billingCycleId`.
|
||
- Updated [recurringDueWork.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/shared/billingClients/recurringDueWork.ts) so client-cadence due-work rows can be built bridge-free while still carrying optional `billingCycleId` metadata for display.
|
||
- Updated [billingEngine.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/lib/billing/billingEngine.ts) to stop treating missing recurring-service-period relations as an acceptable mixed-schema fallback.
|
||
- Added/updated focused tests:
|
||
- [recurringDueWork.domain.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringDueWork.domain.test.ts)
|
||
- [recurringDueWorkReader.integration.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringDueWorkReader.integration.test.ts)
|
||
- [recurringDueWorkReader.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringDueWorkReader.static.test.ts)
|
||
- Recurring run-orchestration slice completed:
|
||
- Updated [recurringBillingRunActions.shared.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/recurringBillingRunActions.shared.ts) so recurring run targets always carry canonical `selectorInput` plus execution-window identity; client-cadence target mapping now starts from canonical due-work rows instead of billing periods.
|
||
- Updated [recurringBillingRunActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/recurringBillingRunActions.ts) so recurring batch selection reads only `getAvailableRecurringDueWork(...)`, recurring runs no longer accept raw `billingCycleIds`, and execution always delegates through `generateInvoiceForSelectionInput(...)`.
|
||
- Updated [AutomaticInvoices.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/AutomaticInvoices.tsx) so ready-table, preview, and PO-overage generate flows submit canonical recurring targets for both bridged client rows and bridge-free contract rows.
|
||
- Updated recurring workflow metadata in [recurringBillingRunEventBuilders.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/shared/workflow/streams/domainEventBuilders/recurringBillingRunEventBuilders.ts) and [billingEventSchemas.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/shared/workflow/runtime/schemas/billingEventSchemas.ts) so run payloads use `client_cadence_window`, `contract_cadence_window`, or `mixed_execution_windows`, rather than treating billing-cycle windows as the default live recurring mode.
|
||
- Added/updated focused tests:
|
||
- [recurringBillingRunActions.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringBillingRunActions.test.ts)
|
||
- [recurringBillingRunActions.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringBillingRunActions.static.test.ts)
|
||
- [recurringBillingRunWorkflowEvents.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringBillingRunWorkflowEvents.test.ts)
|
||
- [recurringBillingRunWindowIdentity.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringBillingRunWindowIdentity.test.ts)
|
||
- [automaticInvoices.recurringDueWork.ui.test.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx)
|
||
- [contractPurchaseOrderSupport.ui.test.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsx)
|
||
- Billing-calculation and duplicate-detection slice completed:
|
||
- Updated [invoiceGeneration.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/invoiceGeneration.ts) so selector-input billing always executes through `calculateBillingForExecutionWindow(...)`, no longer runs rollout-era legacy-vs-canonical comparison mode, and checks client-cadence duplicates via canonical `recurring_service_periods` schedule/period linkage before any legacy billing-cycle fallback.
|
||
- Updated [billingEngine.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/lib/billing/billingEngine.ts) to remove the unused billing-cycle parameter from persisted recurring due-selection loading.
|
||
- Added/updated focused tests:
|
||
- [invoiceGeneration.recurringSelection.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.recurringSelection.test.ts)
|
||
- [invoiceGeneration.duplicate.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.duplicate.test.ts)
|
||
- [invoiceGeneration.duplicate.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.duplicate.static.test.ts)
|
||
- [invoiceGeneration.selectorInputGenerate.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.selectorInputGenerate.test.ts)
|
||
- [invoiceGeneration.preview.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.preview.test.ts)
|
||
- [invoiceGeneration.emptyResult.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.emptyResult.test.ts)
|
||
- [invoiceGeneration.zeroDollarFinalization.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.zeroDollarFinalization.test.ts)
|
||
- Billing-engine recurring execution slice completed:
|
||
- Updated [billingEngine.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/lib/billing/billingEngine.ts) so selector-input recurring due-work loading and execution-window billing stay on canonical persisted service-period selections, bypass `validateBillingPeriod(...)` for `recurringTimingSelectionSource = "persisted"`, and never call `getClientContractLinesAndCycle(...)` or `getBillingCycle(...)` on the live recurring execution path.
|
||
- This keeps client cadence source rules relevant only before execution, when recurring service periods are materialized; the runtime billing engine now treats the persisted service-period window as the recurring source of truth.
|
||
- Added/updated focused tests:
|
||
- [billingEngine.timing.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/billingEngine.timing.test.ts)
|
||
- [billingEngine.recurringExecution.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/billingEngine.recurringExecution.static.test.ts)
|
||
- Selector-input window-validation slice completed:
|
||
- Updated [invoiceGeneration.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/invoiceGeneration.ts) so `normalizeRecurringSelectorInput(...)` canonicalizes client-cadence and contract-cadence selector input by looking up persisted `recurring_service_periods`, rejects windows that do not match materialized service periods, and wraps generation-side normalization failures with execution-identity diagnostics.
|
||
- Added focused preview/generate regressions:
|
||
- [invoiceGeneration.preview.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.preview.test.ts)
|
||
- [invoiceGeneration.selectorInputGenerate.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.selectorInputGenerate.test.ts)
|
||
- Accounting export service-period slice completed:
|
||
- Updated [accountingExportInvoiceSelector.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/services/accountingExportInvoiceSelector.ts) so live export lines only emit canonical recurring detail periods when invoice charge detail rows exist and otherwise fall back to periodless `financial_document_fallback` output instead of preserving invoice-header recurring provenance.
|
||
- Kept `invoice_header_fallback` confined to passive historical payload/read-side compatibility; live export selection no longer emits mixed canonical-vs-header recurring provenance.
|
||
- Added/updated focused tests:
|
||
- [accountingExportInvoiceSelector.servicePeriods.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/accounting/accountingExportInvoiceSelector.servicePeriods.test.ts)
|
||
- [accountingExportInvoiceSelector.servicePeriods.wiring.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/tests/accountingExportInvoiceSelector.servicePeriods.wiring.test.ts)
|
||
- [authoritativeRecurringReaders.servicePeriods.wiring.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/tests/authoritativeRecurringReaders.servicePeriods.wiring.test.ts)
|
||
- [invoiceSelection.integration.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/integration/accounting/invoiceSelection.integration.test.ts)
|
||
- Recurring dashboard copy/actions slice completed:
|
||
- Updated [AutomaticInvoices.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/AutomaticInvoices.tsx) so ready recurring rows no longer render a bridge-only delete-cycle menu, unbridged rows share the same service-period-backed badge and selection actions as bridged rows, and reverse/delete dialog copy now describes client-cycle data only as optional bridge metadata instead of the primary recurring object.
|
||
- Extended [automaticInvoices.recurringDueWork.ui.test.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx) with a regression proving ready service-period rows no longer expose bridge-only row menus while history row actions and service-period-backed copy remain intact.
|
||
|
||
- Billing UI/actions sweep:
|
||
- `AutomaticInvoices`, due-work selection, recurring run target selection, history, reversal/delete, accounting export, and some authoring/storage helpers still preserve bridge-first recurring behavior.
|
||
- Notable files:
|
||
- [AutomaticInvoices.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/AutomaticInvoices.tsx)
|
||
- [billingAndTax.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingAndTax.ts)
|
||
- [recurringBillingRunActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/recurringBillingRunActions.ts)
|
||
- [recurringBillingRunActions.shared.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/recurringBillingRunActions.shared.ts)
|
||
- [billingCycleActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingCycleActions.ts)
|
||
- [accountingExportInvoiceSelector.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/services/accountingExportInvoiceSelector.ts)
|
||
- API/contracts sweep:
|
||
- recurring preview/generate request contracts, invoice DTOs, recurring shared types, and invoice list/read classification still preserve `billing_cycle_id` as a recurring concept.
|
||
- Notable files:
|
||
- [ApiInvoiceController.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/controllers/ApiInvoiceController.ts)
|
||
- [invoiceSchemas.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/schemas/invoiceSchemas.ts)
|
||
- [InvoiceService.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/services/InvoiceService.ts)
|
||
- [recurringTiming.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/types/src/interfaces/recurringTiming.interfaces.ts)
|
||
- [invoice.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/types/src/interfaces/invoice.interfaces.ts)
|
||
- Runtime/history sweep:
|
||
- billing engine, invoice generation, invoice linkage, bucket usage, schedule changes, jobs, and migrations still preserve bridge-era assumptions.
|
||
- Notable files:
|
||
- [billingEngine.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/lib/billing/billingEngine.ts)
|
||
- [invoiceGeneration.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/invoiceGeneration.ts)
|
||
- [invoiceService.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/services/invoiceService.ts)
|
||
- [bucketUsageService.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/services/bucketUsageService.ts)
|
||
- [billingScheduleActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingScheduleActions.ts)
|
||
- [20241130164200_add_billing_cycle_id_to_invoices.cjs](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/migrations/20241130164200_add_billing_cycle_id_to_invoices.cjs)
|
||
- Recurring invoice-linkage slice completed:
|
||
- [invoiceService.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/services/invoiceService.ts) now builds recurring linkage candidates from canonical config and invoice-window data, then matches `recurring_service_periods` across explicit `contract_line` and `client_contract_line` obligations without any `invoice.billing_cycle_id` branch.
|
||
- The linkage helper no longer suppresses missing-relation errors as a rollout-era fallback; required recurring linkage relations are now assumed to exist in steady state.
|
||
- Added persistence coverage in [invoiceService.fixedPersistence.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceService.fixedPersistence.test.ts) for both client-cadence and contract-cadence rows with null `billing_cycle_id`.
|
||
- Added static guards in [recurringInvoiceLinkage.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringInvoiceLinkage.static.test.ts) to lock out `billing_cycle_id`-driven widening and mixed-schema fallback logic.
|
||
- Invoice-kind classification slice completed:
|
||
- [invoiceModification.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/invoiceModification.ts) now classifies prepayment handling from explicit invoice kind (`is_prepayment`) and negative totals, rather than treating `billing_cycle_id = null` as a proxy.
|
||
- [creditActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/creditActions.ts) now persists `is_prepayment: true` when creating prepayment invoices so finalization and later reads use the same explicit kind signal.
|
||
- Added behavior and static coverage in [invoiceFinalization.kindClassification.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceFinalization.kindClassification.test.ts), plus a prepayment persistence assertion in [prepaymentInvoice.periodPolicy.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts).
|
||
- Client schedule-regeneration slice completed:
|
||
- Added [clientCadenceScheduleRegeneration.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/clientCadenceScheduleRegeneration.ts) to derive the last billed client boundary, load client-cadence obligations, and regenerate future `recurring_service_periods` with canonical `billing_schedule_changed` / `backfill_materialization` provenance instead of mutating future `client_billing_cycles`.
|
||
- Updated [billingScheduleActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingScheduleActions.ts) and [billingCycleAnchorActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingCycleAnchorActions.ts) so both schedule-edit entrypoints call the new regeneration helper after updating client schedule settings.
|
||
- Added focused unit coverage in [updateClientBillingSchedule.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/updateClientBillingSchedule.test.ts) and rewrote the DB-backed schedule expectations in [clientBillingCycleAnchors.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/infrastructure/billing/invoices/clientBillingCycleAnchors.test.ts) to assert recurring-service-period regeneration and preserved schedule-administration cycles instead of future-cycle deactivation.
|
||
- Bucket recurring-period slice completed:
|
||
- Updated [bucketUsageService.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/services/bucketUsageService.ts) so current and previous bucket allowance periods resolve from canonical `recurring_service_periods` by obligation and schedule key, with no live `client_billing_cycles` lookup in the recurring bucket path.
|
||
- Replaced the old client-cycle-based unit coverage in [bucketUsageService.periods.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/bucketUsageService.periods.test.ts) with client-cadence and contract-cadence tests that prove rollover uses canonical recurring service periods and never touches `client_billing_cycles`.
|
||
- Hourly/usage service-window verification slice completed:
|
||
- Confirmed [billingEngine.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/lib/billing/billingEngine.ts) already uses `servicePeriodStartExclusive` / `servicePeriodEndExclusive` to query `time_entries` and `usage_tracking` for persisted recurring selections; no product-code change was required for the hard-cutover behavior.
|
||
- Extended [billingEngine.endExclusiveQueries.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/billingEngine.endExclusiveQueries.test.ts) with persisted-recurring hourly and usage regressions proving canonical service windows still drive the queries when they differ from the invoice window.
|
||
- Cadence-authoring cleanup slice completed:
|
||
- Updated [recurringAuthoringPolicy.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/shared/billingClients/recurringAuthoringPolicy.ts) so shared normalization no longer hides a client-cadence default; callers must provide an explicit authoring default or reuse stored cadence.
|
||
- Updated [billingSettingsActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingSettingsActions.ts), [packages/types billing.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/types/src/interfaces/billing.interfaces.ts), and [server billing.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/interfaces/billing.interfaces.ts) so billing settings expose explicit client defaults and `mixed_enabled` metadata without blocking contract cadence updates behind rollout-era throws.
|
||
- Removed the dead rollout validator wrappers from [contractLineSchemas.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/schemas/contractLineSchemas.ts) and [financialSchemas.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/schemas/financialSchemas.ts), leaving cadence-owner defaults only at explicit UI/action/service boundaries.
|
||
- Aligned [contractLineRepository.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/repositories/contractLineRepository.ts) with the package-side repository by removing live `contract_template_line_terms` reads from template detail and template-clone paths; only the cleanup delete in [contractTemplate.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/models/contractTemplate.ts) still references that table.
|
||
- Extended focused wiring/static coverage:
|
||
- [cadenceOwnerRollout.actions.wiring.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/tests/cadenceOwnerRollout.actions.wiring.test.ts)
|
||
- [contractWizardCadenceOwner.wiring.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/tests/contractWizardCadenceOwner.wiring.test.ts)
|
||
- [recurrenceStorageModel.contract.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurrenceStorageModel.contract.test.ts)
|
||
- Service-period troubleshooting slice completed:
|
||
- Added [recurringServicePeriodActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/recurringServicePeriodActions.ts) to load persisted recurring service-period management views and preview operator regeneration/history-repair flows behind dedicated recurring-service-period permissions.
|
||
- Added [RecurringServicePeriodsTab.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/RecurringServicePeriodsTab.tsx), and wired it into [BillingDashboard.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/BillingDashboard.tsx) plus [billingTabsConfig.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/billingTabsConfig.ts), so operators can inspect generated, edited, billed, and corrective service-period state by schedule key.
|
||
- Updated [AutomaticInvoices.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/AutomaticInvoices.tsx) to render `materializationGaps` as explicit repair-required panels with deep links into the Service Periods tab, instead of silently dropping the gap or pretending it is ready invoice work.
|
||
- Added [20260318194500_add_recurring_service_period_permissions.cjs](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/migrations/20260318194500_add_recurring_service_period_permissions.cjs) so admins/managers receive the recurring service-period `view`, `manage_future`, `regenerate`, and `correct_history` permissions required by the new operator surface.
|
||
- Added/updated focused tests:
|
||
- [recurringServicePeriodActions.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringServicePeriodActions.test.ts)
|
||
- [recurringServicePeriodsTab.ui.test.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringServicePeriodsTab.ui.test.tsx)
|
||
- [automaticInvoices.recurringDueWork.ui.test.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx)
|
||
- Required-schema and wording cleanup slice completed:
|
||
- Updated [recurringInvoicingHardCutover.runbook.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts) to lock the hard-cutover architecture’s required-schema posture, including the expectation that recurring code never catches missing table/column errors to recreate bridge behavior.
|
||
- Renamed the client-cadence materialization-gap reader in [billingAndTax.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingAndTax.ts) away from `compatibilityPeriods`, and removed the dead `mergeRecurringDueWorkRows(...)` helper from [recurringDueWork.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/shared/billingClients/recurringDueWork.ts), so the steady-state due-work path no longer carries bridge-era “compatibility” terminology or helpers.
|
||
- Workflow/event diagnostics audit slice completed:
|
||
- Added [recurringBillingRunEventDiagnostics.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringBillingRunEventDiagnostics.static.test.ts) to lock out `billingCycleId`, `billing_cycle_id`, `billing_cycle_window`, and similar bridge-era fields from recurring workflow/job diagnostic contracts.
|
||
- Updated [generateInvoiceHandler.recurringExecutionIdentity.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/jobs/generateInvoiceHandler.recurringExecutionIdentity.test.ts) so the recurring invoice job payload contract is explicitly selector-input plus canonical execution-window identity, with no raw `billingCycleId` requirement.
|
||
- Updated [recurringBillingRunWorkflowEvents.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringBillingRunWorkflowEvents.test.ts) to prove recurring run failure events identify runs with `selectionKey`, `retryKey`, `selectionMode`, `windowIdentity`, and `executionWindowKinds`, and do not emit bridge-specific fields.
|
||
- Invoice-query summary cleanup slice completed:
|
||
- Updated [invoiceQueries.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/invoiceQueries.ts) so invoice list, client, and contract summary readers derive `service_period_start` / `service_period_end` through `invoice_charges -> invoice_charge_details` joins, rather than stale direct-detail assumptions or bridge-era summary naming.
|
||
- Updated [invoiceQueries.recurringDetailRead.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceQueries.recurringDetailRead.test.ts) with a static guard that the summary queries use canonical charge-detail joins and no longer describe their service-period projection as a compatibility range.
|
||
- API schema audit slice completed:
|
||
- Updated [financialSchemas.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/schemas/financialSchemas.ts) so financial invoice write requests no longer expose `billing_cycle_id`, the dead `billingCycleInvoiceRequestSchema` is removed, and `calculateBillingSchema` now rejects raw cycle-ID bridge input while preserving billing-cycle fields only on schedule-administration schemas and invoice read metadata.
|
||
- Updated [ApiFinancialController.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/controllers/ApiFinancialController.ts) and [FinancialService.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/services/FinancialService.ts) so `/api/v1/financial/billing/calculate` executes through canonical execution-window billing instead of forwarding a `billing_cycle_id` handle into the legacy cycle-based engine path.
|
||
- Added [financialRecurringSchemaAudit.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/api/financialRecurringSchemaAudit.static.test.ts) to lock T070 against non-schedule API schemas reintroducing billing-cycle recurring request contracts.
|
||
- Shared interface separation slice completed:
|
||
- Updated [recurringTiming.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/types/src/interfaces/recurringTiming.interfaces.ts) so canonical `IRecurringInvoiceWindow` no longer carries a dead `billingCycleId` bridge field.
|
||
- Updated [packages/types billing.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/types/src/interfaces/billing.interfaces.ts) and [server billing.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/interfaces/billing.interfaces.ts) to remove the unused `IBillingCycleInvoiceRequest` interface now that cycle-ID invoice request schemas are no longer part of the hard-cutover recurring contract surface.
|
||
- Added [recurringInterfaceSeparation.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringInterfaceSeparation.static.test.ts) to lock T071 against recurring execution models or shared/server billing interface files growing cycle-bridge request models back in.
|
||
- Historical read-side strategy slice completed:
|
||
- Updated [ARCHITECTURE.md](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/ee/docs/plans/2026-03-18-recurring-invoicing-hard-cutover/ARCHITECTURE.md) with a dedicated historical read-side strategy section defining `canonical_recurring`, `financial_document_fallback`, and `missing_source_context` as read-only migration states, not live recurring execution modes.
|
||
- Updated [RUNBOOK.md](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/ee/docs/plans/2026-03-18-recurring-invoicing-hard-cutover/RUNBOOK.md) with operator guidance for historically incomplete linkage so old invoices remain readable without synthesizing due work or rebuilding execution identity from `billing_cycle_id`.
|
||
- Extended [recurringInvoicingHardCutover.runbook.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts) to lock that fallback posture against regressions, alongside the existing read-side unit coverage in [invoiceModel.servicePeriods.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceModel.servicePeriods.test.ts) and [creditActions.servicePeriods.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/creditActions.servicePeriods.test.ts).
|
||
- Legacy recurring test cleanup slice completed:
|
||
- Updated [billingInvoiceTiming.integration.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/integration/billingInvoiceTiming.integration.test.ts) so the remaining client-cadence happy-path/history/reversal assertions (`T051`, `T087`, `T088`, `T090`) use canonical selector input and `client_cadence_window` expectations instead of raw `billingCycleId` generation or `billing_cycle_window` compatibility assertions.
|
||
- Added [legacyRecurringIntegrationTests.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/legacyRecurringIntegrationTests.static.test.ts) to lock T081 against bridge-era compatibility titles and `billing_cycle_window` assertions creeping back into the recurring timing integration harness.
|
||
- Canonical recurring regression slice completed:
|
||
- Updated [billingInvoiceTiming.integration.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/integration/billingInvoiceTiming.integration.test.ts) so the contract-cadence and client-cadence happy-path integration cases are now explicit checklist regressions `T077` and `T076`, both exercising due-work selection, canonical preview, selector-input generation, and recurring history with `contract_cadence_window` / `client_cadence_window` identity rather than raw billing-cycle execution.
|
||
- Invoice-kind regression slice completed:
|
||
- Updated [invoiceFinalization.kindClassification.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceFinalization.kindClassification.test.ts) so the explicit-kind regression cases are aligned to checklist items `T046` and `T047`, covering both bridge-less recurring finalization and true prepayment credit behavior after the explicit `is_prepayment` cutover.
|
||
- Revalidated [prepaymentInvoice.periodPolicy.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts) alongside that suite to keep the prepayment financial-document policy anchored to the same explicit kind model.
|
||
- Preview/generate request rejection tests completed:
|
||
- Revalidated [invoiceRecurringSelectorInput.schema.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/api/invoiceRecurringSelectorInput.schema.test.ts) and [invoiceService.recurringSelectorInput.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/api/invoiceService.recurringSelectorInput.test.ts), which already lock checklist items `T014` and `T015` at both schema and service-routing layers for recurring preview/generate rejection of raw `billing_cycle_id` selectors.
|
||
- Canonical preview/generate success tests completed:
|
||
- Updated [billingInvoiceTiming.integration.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/integration/billingInvoiceTiming.integration.test.ts) so the canonical happy-path integrations now explicitly cover `T016/T018/T076` for client cadence and `T017/T019/T077` for contract cadence, proving both preview and selector-input generation succeed without requiring a live billing-cycle bridge.
|
||
|
||
## Discoveries / Constraints
|
||
|
||
- (2026-03-18) The repo already had an in-progress service-driven runbook/test update in the worktree; the hard-cutover docs were added in separate files to avoid mixing coexistence guidance with the post-bridge model.
|
||
- (2026-03-18) The due-work reader still needs later UI/run-action follow-up because some surfaces continue to use `billingCycleId` for row actions even though due-work identity is now canonical.
|
||
- (2026-03-18) `invoiceModification.ts` now classifies prepayment behavior from `is_prepayment` rather than null/non-null `billing_cycle_id`; historical rows that predate the explicit flag may still need later read-side or backfill consideration.
|
||
- (2026-03-18) `billingAndTax.ts` now sources ready recurring work only from `recurring_service_periods`, but the separate repair-gap surface still needs downstream UI handling so operators can act on missing materialization without relying on compatibility rows.
|
||
- (2026-03-18) recurring run selection, `AutomaticInvoices`, and recurring preview/generate API requests now use canonical selector-input targets, but shared type unions and some read-side/history surfaces still carry legacy `billing_cycle_id` or `billing_cycle_window` semantics.
|
||
- (2026-03-18) `generateInvoice(...)`, `previewInvoice(...)`, and PO-overage selection now normalize legacy billing-cycle entrypoints onto canonical client-cadence selector input before duplicate detection or billing calculation. `billingCycleId` remains only as optional metadata on the normalized selector while request-shape cleanup is still pending.
|
||
- (2026-03-18) recurring preview/generate API schemas now reject top-level `billing_cycle_id` and `billing_cycle_window` selector inputs. The remaining bridge-only fields live in shared type unions and read-side/history surfaces, not in recurring preview/generate request handling.
|
||
- (2026-03-18) Legitimate client billing schedule administration still has separate schedule-specific schemas and actions (`financialSchemas.ts`, `billingScheduleActions.ts`, `billingCycleAnchorActions.ts`) after recurring preview/generate stopped accepting `billing_cycle_id`.
|
||
- (2026-03-18) Live recurring history now loads through recurring-invoice-first readers and action labels. A deprecated billing-cycle alias remains only to keep the already-dirty `billingInvoiceTiming.integration.test.ts` harness compiling until that separate worktree change is cleaned up.
|
||
- (2026-03-18) `InvoiceService.ts` list/detail classification now uses canonical recurring summary data. `billing_cycle_id` remains only as optional historical metadata or explicit include-side context.
|
||
- (2026-03-19) `InvoiceService.ts` still had one stale read-side bridge seam in its historical billing-cycle include path: it was joining `client_billing_cycles` on legacy `cycle_id` / `period_start` / `period_end` column names. The hard-cutover read-side tests now lock the current `billing_cycle_id` / `period_start_date` / `period_end_date` metadata path instead.
|
||
- (2026-03-19) Recurring reverse/delete actions still contained one bridge-era cleanup branch that deactivated or deleted `client_billing_cycles` after invoice deletion. That mutation is now removed so client billing schedule rows stay cadence-administration/history records rather than recurring runtime objects.
|
||
- (2026-03-18) one API service path still appears to reference old cycle column names (`cycle_id`, `period_start`, `period_end`) and should be rechecked during cleanup.
|
||
- (2026-03-18) Bucket recurring period resolution now derives from canonical `recurring_service_periods` windows and only falls back to plan-anchor arithmetic when no current service period exists.
|
||
- (2026-03-18) Live accounting export selection no longer emits invoice-header recurring provenance. Canonical recurring detail rows remain authoritative, while invoices without canonical detail export as periodless financial documents; historical stored payloads may still carry older fallback metadata.
|
||
- (2026-03-18) Shared recurring identity types now drop bridge-only `billingCycleId` and `hasBillingCycleBridge` fields, but read-side invoice history types still legitimately expose `billing_cycle_window` and bridge metadata for historical context.
|
||
- (2026-03-18) `generateInvoiceHandler` and `scheduleRecurringWindowInvoiceGeneration` are now selector-input-only. The old `scheduleInvoiceGeneration(...)` helper is left as an explicit hard failure if something still tries to schedule recurring work from a raw `billingCycleId`.
|
||
- (2026-03-18) DB-backed verification for `billingInvoiceTiming.integration.test.ts` could not run locally because PostgreSQL was unavailable on `127.0.0.1:5438` / `::1:5438`. The targeted tests were skipped before any test body executed.
|
||
- (2026-03-18) Recurring preview/generation diagnostics now emit only `executionIdentityKey` for live recurring failures. `billingCycleId` still exists as passive persistence or read-side metadata, but it is no longer part of the recurring preview/generate error contract.
|
||
- (2026-03-18) Invoice API list/detail contracts now distinguish client recurring invoices as `client_cadence_window`, not `billing_cycle_window`. `billing_cycle_id` remains available on invoice DTOs only as optional historical metadata or explicit include-side context, not as the recurring classifier.
|
||
- (2026-03-18) `recurring_projection` was dead compatibility metadata in the clean runtime paths. Canonical recurring reads now key directly off `recurring_detail_periods`; only the already-dirty `billingInvoiceTiming.integration.test.ts` still references the removed field and was intentionally left untouched until that file is clean.
|
||
- (2026-03-18) Live recurring history now loads through `getRecurringInvoiceHistoryPaginated(...)` and labels the surface as recurring invoice history, not invoiced billing cycles. A deprecated `getInvoicedBillingCyclesPaginated(...)` alias remains only so the already-dirty `billingInvoiceTiming.integration.test.ts` harness can be cleaned up separately without mixing in its unrelated edits.
|
||
- (2026-03-18) `invoiceService.ts` no longer uses `invoice.billing_cycle_id` to choose recurring linkage candidates. The next remaining bridge-like misclassification seam is still in [invoiceModification.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/invoiceModification.ts), where null/non-null `billing_cycle_id` is still used for prepayment logic (`F046`/`F047`).
|
||
- (2026-03-18) `invoiceModification.ts` now classifies prepayment behavior from `is_prepayment`, and `creditActions.ts` now persists that flag for new prepayment invoices. Historical rows that predate the explicit flag may still need later read-side or backfill consideration if they relied on the old null-bridge proxy.
|
||
- (2026-03-18) The schedule-edit actions still read `client_billing_cycles` only to determine the last invoiced historical boundary. That boundary is now passive historical input to recurring-service-period regeneration; future schedule changes no longer deactivate or redefine recurring work by mutating future cycle rows.
|
||
- (2026-03-18) The DB-backed schedule-management suite now imports action files directly instead of `@alga-psa/billing/actions`, because the broader action index pulls optional jobs infrastructure that is not resolvable in this isolated test harness.
|
||
- (2026-03-18) DB-backed verification for `clientBillingCycleAnchors.test.ts` is currently blocked locally because PostgreSQL is unavailable on `127.0.0.1:5438` / `::1:5438`. The rewritten tests load and compile, but the shared test context aborts before any test body executes.
|
||
- (2026-03-18) `bucketUsageService.ts` no longer queries `client_billing_cycles` on the recurring bucket path. Bucket period resolution now derives current and rollover windows from `recurring_service_periods` and falls back to plan-anchor arithmetic only when no canonical service period exists.
|
||
- (2026-03-18) `billingEngine.ts` already had the correct service-window behavior for hourly and usage recurring execution. `F056` was completed by locking that behavior with explicit persisted-selection query regressions instead of changing runtime logic.
|
||
- (2026-03-18) DB-backed verification for `invoiceSelection.integration.test.ts` is also currently blocked locally because PostgreSQL is unavailable on `127.0.0.1:5438` / `::1:5438`. The updated coexistence assertions compile, but the shared test context aborts before any integration test body executes.
|
||
- (2026-03-19) Shared recurring authoring defaults are now explicit at product boundaries only. The remaining non-test `cadence_owner ?? 'client'` callsites are all UI defaults (`ContractWizard`, template wizard, and contract-line configuration components), while shared/action/repository code now requires either `defaultCadenceOwner` or stored cadence reuse.
|
||
- (2026-03-19) The last live `contract_template_line_terms` runtime reads were in [server/src/lib/repositories/contractLineRepository.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/repositories/contractLineRepository.ts); they are now removed. The only remaining non-test reference is the cleanup delete in [contractTemplate.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/models/contractTemplate.ts), which is intentionally retained for template cleanup.
|
||
- (2026-03-19) `getAvailableRecurringDueWork(...)` was already returning `materializationGaps`, but [AutomaticInvoices.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/AutomaticInvoices.tsx) was discarding them. The hard-cutover operator story now depends on surfacing those gaps directly and linking to the Service Periods tab by schedule key.
|
||
- (2026-03-19) The new Service Periods tab uses schedule-key lookup rather than client-cycle identity. That keeps troubleshooting aligned with persisted recurring-service-period authority and avoids reintroducing `billing_cycle_id` as the operator handle for recurring repair.
|
||
- (2026-03-19) The remaining steady-state “compatibility” wording in recurring due-work code had collapsed to an internal gap-reader name plus an unused merge helper. Removing both let the hard-cutover docs and runtime code tell the same story: missing service periods are repair work, not a coexistence mode.
|
||
- (2026-03-19) The recurring run builders, workflow event schemas, and job handler contracts were already canonical; the remaining gap was regression coverage. The new tests now lock that those diagnostics stay keyed by canonical execution-window identity and never grow bridge-only payload fields back.
|
||
- (2026-03-19) `invoiceQueries.ts` still had one remaining bridge-era seam in its summary readers: they projected service-period ranges with stale `invoice_charge_details` assumptions and a “compatibility summary range” comment. That is now cleaned up and locked to canonical charge-detail joins.
|
||
- (2026-03-19) The remaining API-schema bridge seam was the legacy financial billing-calculation route. `ApiInvoiceController` was already selector-input-only for recurring preview/generate, but `ApiFinancialController` still accepted `billing_cycle_id` and routed through `BillingEngine.calculateBilling(...)`; that path now uses canonical execution-window dates only.
|
||
- (2026-03-19) Financial invoice write schemas now treat `billing_cycle_id` as read-side metadata only. The hard-cutover API audit does not remove historical invoice list filters or response metadata, but it does remove writable/request-side cycle bridge fields outside legitimate client billing schedule administration schemas.
|
||
- (2026-03-19) The remaining package/shared interface leak was dead bridge data on canonical recurring invoice-window types plus an unused `IBillingCycleInvoiceRequest` interface duplicated in package/server billing interfaces. Those are now removed; passive cycle metadata still exists on read/display models such as recurring due-work rows where F009 explicitly kept it for optional context.
|
||
- (2026-03-19) The code already had the historical-read fallback states needed for incomplete recurring linkage (`financial_document_fallback` and `missing_source_context`), but the hard-cutover architecture/runbook did not explain that posture. `F075` is now documented as an explicit read-side strategy: keep old invoices readable without inventing new recurring work or rebuilding live identity from bridge metadata.
|
||
- (2026-03-19) The highest-signal leftover bridge-era tests were in the already-dirty [billingInvoiceTiming.integration.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/integration/billingInvoiceTiming.integration.test.ts) harness. They were rewritten surgically in place to avoid disturbing the unrelated worktree edits, and local DB-backed execution is still blocked by the missing PostgreSQL instance on `127.0.0.1:5438` / `::1:5438`.
|
||
- (2026-03-19) The same timing integration harness now carries the canonical end-to-end regression names for client and contract recurring happy paths (`T076` / `T077`). They collect successfully under Vitest, but local execution remains blocked before test bodies run because PostgreSQL is unavailable on `127.0.0.1:5438` / `::1:5438`.
|
||
- (2026-03-19) The remaining explicit-kind coverage already existed in focused unit tests; `F079` was completed by aligning those regressions to checklist items `T046` and `T047` and rerunning them. No additional product-code change was required for this final feature slice.
|
||
|
||
## Commands / Runbooks
|
||
|
||
- `rg -n "billing_cycle_id|client_billing_cycles|getAvailableBillingPeriods|missing_service_period_materialization|isMissingRecurringDueWorkRelation" packages/billing/src server/src/lib/api shared -g '!**/*.test.*'`
|
||
- `rg -n "prepayment|billing_cycle_id" packages/billing/src/actions packages/billing/src/services -g '!**/*.test.*'`
|
||
- `rg -n "recurring_projection|hasBillingCycleBridge|billingCycleId" packages/types/src server/src/interfaces server/src/lib/api -g '!**/*.test.*'`
|
||
- `rg -n "billing cycle" packages/billing/src/components packages/billing/src/actions -g '!**/*.test.*'`
|
||
- `cd server && pnpm exec vitest run src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/recurringDueWork.domain.test.ts src/test/unit/billing/recurringDueWorkReader.integration.test.ts src/test/unit/billing/recurringDueWorkReader.static.test.ts src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/recurringServicePeriodDueSelection.domain.test.ts src/test/unit/billing/recurringTiming.domain.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/recurringBillingRunActions.test.ts src/test/unit/billing/recurringBillingRunActions.static.test.ts src/test/unit/billing/recurringBillingRunWorkflowEvents.test.ts src/test/unit/billing/recurringBillingRunWindowIdentity.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsx --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/invoiceGeneration.recurringSelection.test.ts src/test/unit/billing/invoiceGeneration.selectorInputGenerate.test.ts src/test/unit/billing/invoiceGeneration.preview.test.ts src/test/unit/billing/invoiceGeneration.emptyResult.test.ts src/test/unit/billing/invoiceGeneration.duplicate.test.ts src/test/unit/billing/invoiceGeneration.duplicate.static.test.ts src/test/unit/billing/invoiceGeneration.zeroDollarFinalization.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/recurringIdentityTypes.static.test.ts src/test/unit/billing/recurringTiming.domain.test.ts src/test/unit/billing/recurringServicePeriodDueSelection.domain.test.ts src/test/unit/billing/recurringDueWork.domain.test.ts src/test/unit/billing/recurringDueWorkReader.integration.test.ts src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsx src/test/unit/jobs/generateInvoiceHandler.recurringExecutionIdentity.test.ts src/test/unit/billing/invoiceGeneration.recurringSelection.test.ts src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T321|T322/T328" --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/invoiceGeneration.selectorInputGenerate.test.ts src/test/unit/billing/invoiceGeneration.preview.test.ts src/test/unit/billing/invoiceGeneration.duplicate.test.ts src/test/unit/billing/invoiceGeneration.duplicate.static.test.ts src/test/unit/billing/invoiceGeneration.zeroDollarFinalization.test.ts src/test/unit/billing/invoiceGeneration.emptyResult.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/api/invoiceRecurringList.contract.test.ts src/test/unit/api/invoiceRecurringSelectorInput.schema.test.ts src/test/unit/api/invoiceService.recurringSelectorInput.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/api/invoiceService.recurringDetailProjection.test.ts src/test/unit/api/invoiceResponseSchema.compatibility.test.ts src/test/unit/billing/invoiceModel.servicePeriods.test.ts src/test/unit/billing/invoiceQueries.recurringDetailRead.test.ts src/test/unit/billing/manualInvoiceActions.viewing.test.ts src/test/unit/api/invoiceService.deleteRecurringDetailGuard.test.ts src/test/unit/invoiceWorkflowEvents.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/integration/api/invoiceService.recurringCoexistence.integration.test.ts src/test/integration/accounting/invoiceSelection.integration.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsx src/test/unit/billing/recurringInvoiceHistory.static.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/invoiceService.fixedPersistence.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/recurringInvoiceLinkage.static.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/invoiceFinalization.kindClassification.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/billingEngine.recurringExecution.static.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/invoiceGeneration.preview.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/invoiceGeneration.selectorInputGenerate.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/updateClientBillingSchedule.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/infrastructure/billing/invoices/clientBillingCycleAnchors.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/bucketUsageService.periods.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/billingEngine.endExclusiveQueries.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/accounting/accountingExportInvoiceSelector.servicePeriods.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run ../packages/billing/tests/accountingExportInvoiceSelector.servicePeriods.wiring.test.ts ../packages/billing/tests/authoritativeRecurringReaders.servicePeriods.wiring.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/integration/accounting/invoiceSelection.integration.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/recurringInvoiceHistory.static.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/recurringAuthoringPolicy.domain.test.ts src/test/unit/billing/recurrenceStorageModel.contract.test.ts src/test/unit/api/contractLineCadenceOwner.schema.test.ts src/test/unit/api/contractLineService.cadenceOwner.test.ts src/test/unit/api/defaultBillingSettings.cadenceOwner.schema.test.ts ../packages/billing/tests/cadenceOwnerRollout.actions.wiring.test.ts ../packages/billing/tests/contractWizardCadenceOwner.wiring.test.ts ../packages/billing/tests/templateWizardCadenceOwner.wiring.test.ts ../packages/billing/tests/contractLinePresetRecurringAuthoring.wiring.test.ts ../packages/billing/tests/recurringAuthoringValidation.wiring.test.ts ../packages/billing/tests/fixedContractLineConfiguration.cadenceOwner.ui.test.tsx ../packages/billing/tests/contractLinePresetCadenceOwner.actions.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run ../packages/billing/src/actions/billingSettingsActions.cadenceOwnerDefaultsWiring.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/recurringServicePeriodActions.test.ts src/test/unit/billing/recurringServicePeriodsTab.ui.test.tsx src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts src/test/unit/billing/recurringDueWorkReader.static.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/jobs/generateInvoiceHandler.recurringExecutionIdentity.test.ts src/test/unit/billing/recurringBillingRunWorkflowEvents.test.ts src/test/unit/billing/recurringBillingRunEventDiagnostics.static.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/invoiceQueries.recurringDetailRead.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/api/financialRecurringSchemaAudit.static.test.ts src/test/unit/api/invoiceRecurringSelectorInput.schema.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec tsc -p tsconfig.json --noEmit` (started for this slice; full run was still in progress during checklist updates)
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/recurringIdentityTypes.static.test.ts src/test/unit/billing/recurringInterfaceSeparation.static.test.ts src/test/unit/api/financialRecurringSchemaAudit.static.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts src/test/unit/billing/invoiceModel.servicePeriods.test.ts src/test/unit/billing/creditActions.servicePeriods.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/legacyRecurringIntegrationTests.static.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T051|T087|T088|T090" --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T051|T076|T077|T088|T090" --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/billing/invoiceFinalization.kindClassification.test.ts src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/api/invoiceRecurringSelectorInput.schema.test.ts src/test/unit/api/invoiceService.recurringSelectorInput.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T016|T017|T018|T019|T076|T077" --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/unit/api/invoiceService.billingCycleMetadata.static.test.ts src/test/unit/api/invoiceRecurringList.contract.test.ts src/test/unit/api/invoiceService.recurringDetailProjection.test.ts src/test/unit/billing/invoiceGeneration.preview.test.ts src/test/unit/billing/recurringInvoiceReversal.static.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/integration/api/invoiceService.recurringCoexistence.integration.test.ts --coverage.enabled=false`
|
||
- `cd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T029|T030|T033|T034|T049|T050|T078|T079|T080|T082|T084|T085|T087" --coverage.enabled=false`
|
||
|
||
## Completed Items
|
||
|
||
- (2026-03-18) F001 implemented by defining canonical service-period or execution-window identity as the only recurring execution identity in `ARCHITECTURE.md`.
|
||
- (2026-03-18) F002 implemented by documenting `client_billing_cycles` as cadence/source-rule infrastructure and optional historical context only.
|
||
- (2026-03-18) F074 implemented by documenting `invoices.billing_cycle_id` as passive historical metadata only in recurring code until later physical removal.
|
||
- (2026-03-18) F080 implemented by adding hard-cutover architecture and operator runbook guidance for the final recurring model.
|
||
- (2026-03-18) T072/T073/T074 implemented with a focused documentation regression test for the final model, client-cycle role, and `billing_cycle_id` deprecation posture.
|
||
- (2026-03-18) F003/F004/F005/F006 implemented by removing recurring due-work compatibility merges, removing recurring mixed-schema fallback guards from the reader/engine, and sourcing ready recurring work only from persisted service-period rows.
|
||
- (2026-03-18) F007/F008/F009 implemented by introducing canonical client-cadence execution identity keyed by schedule/period/window and treating `billingCycleId` only as optional read-side metadata on due-work rows.
|
||
- (2026-03-18) T001/T002/T003/T004/T005/T006 implemented with static, integration, and unit coverage for canonical due-work sourcing and bridge-free client-cadence row identity.
|
||
- (2026-03-18) F010/F011/F012/F013 implemented by removing raw-cycle recurring run entrypoints, selecting recurring runs from canonical due-work rows only, and making the recurring run executor invoke selector-input invoice generation for all cadence owners.
|
||
- (2026-03-18) F014/F015 implemented at the run-target boundary by removing required `billingCycleId` from recurring run target contracts and making client-cadence run identity schedule/period/window keyed end to end.
|
||
- (2026-03-18) T007/T008/T023/T024/T026/T027/T028 implemented with unit, integration, static, and UI coverage for bridge-free execution identity keys, canonical recurring run selection, and selector-input-only recurring run execution.
|
||
- (2026-03-18) F016 implemented by removing the billing-cycle-vs-execution-window branch from selector-input billing calculation helpers and using execution-window-first billing for recurring selector inputs and compatibility wrappers alike.
|
||
- (2026-03-18) F017 implemented by deleting the rollout-era recurring comparison-mode branch from `invoiceGeneration.ts`.
|
||
- (2026-03-18) F018/F019/F020 implemented by normalizing legacy billing-cycle preview/generate entrypoints onto canonical client-cadence selector identity, deleting the remaining `invoices.billing_cycle_id` duplicate fallback, and keeping rerun or retry behavior keyed off canonical execution identity even when `billing_cycle_id` is retained as passive metadata.
|
||
- (2026-03-18) F021/F022 implemented by making recurring preview/generate API schemas require canonical `selector_input` and reject `billing_cycle_id` request shapes or `billing_cycle_window` selector inputs.
|
||
- (2026-03-18) F023/F024 implemented by proving the `AutomaticInvoices` preview/generate actions submit canonical selector input for both client-cadence and contract-cadence due rows, even when passive billing-cycle metadata is still displayed on the row.
|
||
- (2026-03-18) F025/F026 implemented by removing API service/controller routing through `generateInvoice(...)` or `previewInvoice(...)`, renaming the generate controller path away from billing-cycle framing, and making recurring API routes depend only on selector-input actions.
|
||
- (2026-03-18) F027 confirmed by keeping billing-cycle-specific schedule administration surfaces separate from recurring execution APIs; no recurring preview/generate contract still depends on those schedule-only endpoints.
|
||
- (2026-03-18) T020/T021/T022 implemented with client-cadence, contract-cadence, and static source coverage showing duplicate detection uses canonical recurring linkage before any legacy billing-cycle fallback.
|
||
- (2026-03-18) F028/F029 implemented by removing bridge-only `billingCycleId` and `hasBillingCycleBridge` fields from shared recurring identity interfaces, dropping shared `billing_cycle_window` builders, converting shared client due-work builders to canonical schedule/period selector input, and updating recurring jobs to require canonical selector-input payloads.
|
||
- (2026-03-18) T009 implemented with a static contract test for the shared recurring type file plus domain/UI/job coverage proving client-cadence and contract-cadence shared builders still work after the bridge fields were removed.
|
||
- (2026-03-18) F030 implemented by removing `billingCycleId` from recurring preview/generation error payloads and duplicate errors so live recurring diagnostics key only on canonical `executionIdentityKey`.
|
||
- (2026-03-18) F031/F032/F033/F034 implemented by renaming client recurring invoice DTO execution-window kind to `client_cadence_window`, keeping `billing_cycle_id` only as optional metadata, and cutting `InvoiceService` list/detail projection plus filter classification over to canonical recurring summary data without any `invoices.billing_cycle_id` fallback.
|
||
- (2026-03-18) F035/F036 implemented by removing `recurring_projection` from invoice charge types, schema contracts, invoice model hydration, and workflow-event provenance so canonical recurring detail reads depend only on `recurring_detail_periods` plus summary parent period fields.
|
||
- (2026-03-18) T010 implemented with a UI regression proving `AutomaticInvoices` can render, preview, and generate a client-cadence row whose `billingCycleId` metadata is null while still sending canonical selector-input targets.
|
||
- (2026-03-18) F037/F038 implemented by renaming the live recurring history reader to `getRecurringInvoiceHistoryPaginated(...)`, removing the remaining billing-cycle cadence fallback from the history query, and updating `AutomaticInvoices` copy from billing-cycle framing to recurring-invoice-history framing.
|
||
- (2026-03-18) T011/T035/T036/T037 implemented with ready-row UI coverage for an unbridged contract-cadence row, recurring-history UI coverage for row actions and service-period/execution-window copy, and a static guard that the live history surface no longer imports or labels itself as invoiced billing cycles.
|
||
- (2026-03-18) F039/F040/F041 implemented by making recurring reverse/delete wrappers repair recurring service periods through `hardDeleteInvoice(...)` and `releaseRecurringServicePeriodInvoiceLinkageForInvoice(...)` without mutating `client_billing_cycles` as the recurring primary object.
|
||
- (2026-03-18) T012/T013 implemented by extending the existing client-cadence `AutomaticInvoices` UI regression so the same proof covers both preview and generate actions submitting canonical selector input with no `billing_cycle_id`.
|
||
- (2026-03-18) F042/F043/F044/F045 implemented by making `invoiceService.ts` derive recurring linkage candidates from canonical config/window identity, matching both `contract_line` and `client_contract_line` obligations without consulting `invoice.billing_cycle_id`, and deleting the mixed-schema missing-relation fallback guard from recurring linkage persistence.
|
||
- (2026-03-18) T031/T032 implemented with static source guards proving recurring invoice linkage no longer widens or narrows from `invoice.billing_cycle_id` and no longer suppresses missing-relation fallback errors.
|
||
- (2026-03-18) F046/F047/F048 implemented by classifying invoice finalization behavior from explicit `is_prepayment` plus negative totals, persisting `is_prepayment: true` on prepayment creation, and proving bridge-less recurring invoices with null `billing_cycle_id` no longer get routed through prepayment credit logic.
|
||
- (2026-03-18) T048 implemented with a static source guard proving invoice finalization no longer keys recurring or prepayment classification from null/non-null `billing_cycle_id`.
|
||
- (2026-03-18) F049/F050/F051 implemented by making persisted recurring execution-window billing bypass cycle validation, loading contract lines directly for the execution window, and preventing selector-input recurring runs from auto-loading or auto-creating `client_billing_cycles` during live execution.
|
||
- (2026-03-18) T051 implemented with static and unit coverage proving the billing engine’s persisted recurring path no longer routes through `getClientContractLinesAndCycle(...)`, `validateBillingPeriod(...)`, or `getBillingCycle(...)` to execute recurring work.
|
||
- (2026-03-18) Added T089 because the original checklist lacked a focused validation test for direct selector-input windows drifting away from persisted recurring service periods; preview and generate both now reject that mismatch explicitly.
|
||
- (2026-03-18) F052 implemented by normalizing client-cadence and contract-cadence selector input against persisted `recurring_service_periods`, so recurring preview/generate validate canonical service-period windows instead of trusting caller-supplied windows or legacy cycle semantics.
|
||
- (2026-03-18) T089 implemented with preview/generate regressions proving selector-input recurring actions reject execution windows that do not match materialized recurring service periods and still surface canonical execution-identity diagnostics.
|
||
- (2026-03-18) F053/F054 implemented by routing both client billing schedule edit actions through canonical client-cadence service-period regeneration, keeping `client_billing_cycles` available for schedule administration while removing the old future-cycle invalidation behavior from recurring execution.
|
||
- (2026-03-18) T052/T053 implemented with focused unit coverage for schedule-driven `recurring_service_periods` regeneration plus a DB-backed schedule suite rewrite that now asserts preserved client-cycle administration and canonical recurring regeneration semantics, although the DB-backed run is currently blocked locally by the missing PostgreSQL test instance.
|
||
- (2026-03-18) F055 implemented by deriving bucket allowance periods and rollover lookups from canonical `recurring_service_periods` schedule windows instead of preferring `client_billing_cycles`.
|
||
- (2026-03-18) T054/T055 implemented with client-cadence and contract-cadence bucket-service unit coverage proving recurring bucket resolution uses canonical service periods and never hits `client_billing_cycles`.
|
||
- (2026-03-18) F056 confirmed by locking the existing billing-engine behavior that filters hourly `time_entries` and usage `usage_tracking` against canonical persisted service windows rather than invoice-window or client-cycle boundaries.
|
||
- (2026-03-18) T056/T057 implemented with focused billing-engine regressions proving persisted recurring hourly and usage execution still query inside the canonical service window when it differs from the invoice window.
|
||
- (2026-03-18) F057/F058/F059 implemented by cutting live accounting export selection over to canonical recurring detail periods only, removing invoice-header recurring provenance emission from the live selector, and treating invoices without canonical recurring detail as periodless financial documents.
|
||
- (2026-03-18) T058/T059/T060/T061 implemented with integration, unit, and static coverage for client-cadence and contract-cadence canonical export periods plus the legacy no-detail case that now exports without live recurring fallback provenance.
|
||
- (2026-03-18) F060/F061 implemented by removing the ready-table’s bridge-only delete-cycle menu, making bridge-free rows use the same service-period-backed badge and canonical selection flow as other recurring rows, and updating recurring reverse/delete copy to talk about optional bridge metadata instead of treating billing cycles as the primary object.
|
||
- (2026-03-19) F062/F063/F064 implemented by moving client-cadence defaults to explicit UI/action/service boundaries, removing rollout-era cadence-owner schema wrappers and hidden shared defaults, and deleting the last live `contract_template_line_terms` fallback reads from the server-side contract-line repository.
|
||
- (2026-03-19) T062/T063/T064 implemented with focused wiring/schema/storage-model coverage proving authoring defaults stay explicit at UX boundaries, shared/runtime helpers no longer hide client fallback semantics, and normal read/write paths no longer use old template-term fallback tables.
|
||
- (2026-03-19) F065/F066 implemented by keeping the Service Periods tab wired into the billing dashboard as the canonical troubleshooting surface, introducing dedicated recurring-service-period permissions and management actions, and surfacing missing-materialization gaps in `AutomaticInvoices` as explicit repair links instead of compatibility due rows.
|
||
- (2026-03-19) T065/T066 implemented with UI/action coverage proving the service-period management surface remains reachable from the billing dashboard and that missing materialization renders as a repair-required panel linked to canonical service-period review rather than as ready-to-generate invoice work.
|
||
- (2026-03-19) F067/F068 implemented by explicitly locking the hard-cutover required-schema posture in docs coverage and removing the last bridge-era “compatibility” naming/helper from the recurring due-work repair path.
|
||
- (2026-03-19) F069/F070 implemented by locking recurring job/workflow diagnostics to canonical execution-window identity and proving static schema/builder contracts no longer emit bridge-only recurring payload fields.
|
||
- (2026-03-19) T025/T067/T068 implemented with job-contract, workflow-payload, and static-schema coverage for selector-input-only recurring job payloads and bridge-free recurring run diagnostics.
|
||
- (2026-03-19) F071/T069 implemented by moving invoice summary service-period projections onto canonical `invoice_charges` + `invoice_charge_details` joins and adding a static guard against bridge-era summary assumptions.
|
||
- (2026-03-19) F072/T070 implemented by removing `billing_cycle_id` from non-schedule financial API write/calculation schemas, routing financial billing calculation through canonical execution-window billing, deleting the dead billing-cycle invoice request schema, and adding a static audit that only client billing schedule schemas still expose billing-cycle request contracts.
|
||
- (2026-03-19) F073/T071 implemented by removing dead cycle-bridge fields from canonical recurring invoice-window interfaces, deleting the unused shared/server `IBillingCycleInvoiceRequest` model, and adding a static guard that recurring execution interfaces stay separate from client billing cycle request models.
|
||
- (2026-03-19) F075 implemented by documenting the hard-cutover migration/read-side strategy for historically incomplete recurring linkage, explicitly constraining fallback to `financial_document_fallback` / `missing_source_context` read models while keeping live recurring execution bridge-free.
|
||
- (2026-03-19) F076/T081 implemented by rewriting the remaining client-cadence timing integration assertions away from raw cycle-ID generation and `billing_cycle_window` expectations, then adding a static guard that the legacy compatibility framing stays removed.
|
||
- (2026-03-19) F077/F078 and T076/T077 implemented by aligning the DB-backed client- and contract-cadence happy-path timing integrations with the checklist regressions, so both end-to-end recurring flows now validate canonical selector-input preview/generate/history behavior under `client_cadence_window` and `contract_cadence_window`.
|
||
- (2026-03-19) F079 and T046/T047 implemented by aligning the explicit invoice-kind regression suite to the checklist and rerunning it, proving bridge-less recurring invoices are not misclassified as prepayments while true prepayment behavior still issues credit correctly.
|
||
- (2026-03-19) T038/T039/T040/T041/T042/T043/T044/T045/T075/T088 implemented by fixing `InvoiceService` historical billing-cycle metadata reads to current `client_billing_cycles` columns, adding read-side classification/detail/static coverage for passive bridge metadata versus canonical recurring summary data, and tightening recurring preview/generation diagnostic tests so `billingCycleId` never reappears as the primary recurring error key.
|
||
- (2026-03-19) T029/T030/T033/T034/T049/T050/T078/T079/T080/T082/T084/T085/T087 implemented by aligning the DB-backed recurring timing suite to canonical selector-input linkage, mandatory service-period materialization, bridge-free engine queries, null-bridge client-cadence persistence, and history/reverse/reissue behavior after the last billing-cycle cleanup branches were removed. Local execution of those DB-backed tests remains blocked until PostgreSQL is available on `127.0.0.1:5438`.
|
||
- (2026-03-19) T083/T086 implemented by relabeling the DB-backed client billing schedule regeneration suite around the hard-cutover acceptance criteria: client billing schedule records remain administrable, future recurring service periods regenerate canonically, and historical invoice context remains intact. Local execution remains blocked until PostgreSQL is available on `127.0.0.1:5438`.
|
||
|
||
## Links / References
|
||
|
||
- Broad architecture:
|
||
- [PRD.md](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/PRD.md)
|
||
- Softer cutover plan:
|
||
- [PRD.md](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/ee/docs/plans/2026-03-18-service-driven-invoicing-cutover/PRD.md)
|
||
|
||
## Open Questions
|
||
|
||
- Should there be a later physical schema removal plan for `invoices.billing_cycle_id`, or is passive historical retention enough?
|
||
- How much read-side fallback for historically incomplete recurring linkage is acceptable after live recurring compatibility branches are removed?
|