Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
2.4 KiB
Recurring Service-Period Invoice Linkage
F241 defines the first explicit linkage contract between a persisted recurring service-period record and the canonical invoice detail row that billed it.
This checkpoint stays additive:
- it does not yet switch due selection or runtime billing onto persisted rows
- it does not yet define backfill or compare-mode policy for historical billed coverage
- it does define the record shape, storage fields, and lifecycle effect once a billed detail row is known
Linkage Shape
Persisted records now carry optional invoiceLinkage metadata:
invoiceIdinvoiceChargeIdinvoiceChargeDetailIdlinkedAt
Those fields point at the already-authoritative invoice persistence surface:
invoice_charges.item_idremains the parent charge identityinvoice_charge_details.item_detail_idremains the canonical recurring detail identity- the persisted service-period row stores both ids so billed history can be traced without reconstructing joins from schedule math alone
Lifecycle Effect
Invoice linkage is the moment a future service-period record becomes billed history.
- a successfully linked record transitions to
lifecycleState = billed - unlinked future rows stay in generated / edited / skipped / locked states
- already linked rows may only be changed later through the narrow corrective boundary
invoice_linkage_repair
shared/billingClients/recurringServicePeriodInvoiceLinkage.ts is the v1 helper that applies this transition explicitly and rejects conflicting relinks outside that repair flow.
Physical Storage
server/migrations/20260318143000_add_invoice_linkage_to_recurring_service_periods.cjs adds additive linkage columns on recurring_service_periods:
invoice_idinvoice_charge_idinvoice_charge_detail_idinvoice_linked_at
The first integrity rules are:
- linkage columns are all-null or all-present together
invoice_charge_detail_idis unique per tenant across the ledger- linked rows must already be in
billedstate
Deliberate Boundary
This checkpoint still does not define:
- the due-selection query contract that reads persisted rows at invoice time
- parity comparison between derived timing and persisted schedules
- billed-history backfill for already-generated historical invoices
- richer repair, replay, or administrative reconciliation flows
Those remain sequenced behind F242-F244.