--- status: Accepted date: 2026-09-30 applyTo: - "Fuchs_Database/dbo/Functions/fds__fn_IntermediateIsAllocatedToOther.sql" - "Fuchs_Database/dbo/Stored Procedures/fds__setInvoiceFinal.sql" - "Fuchs_Database/dbo/Stored Procedures/fds__prepInvoice.sql" - "Fuchs_Database/dbo/Stored Procedures/fds__prepStorno_recreate.sql" - "Fuchs_Database/dbo/Stored Procedures/fds__createStorno_*.sql" - "Fuchs_Database/Scripts/2026-09-30_backfill_intermediate_invoice_allocations.sql" supersededBy: "" --- # 0034 — An Abschlagsrechnung credit follows the live invoice ## Context A Schlussrechnung credits the Abschlagsrechnungen (intermediate invoices, `InvoiceType = 'i'`) already paid for the same service requests via `IntInvoiceAllocation` lines. Finalising it records the claim in `fds__invoice_details.AllocatedTo_InvId` of each credited intermediate so that `fds__prepInvoice` does not offer the same Abschlagsrechnung again. Until 2026-09-30 that claim was never actually written (the editor dropped `reference_InvId`, the MERGE aborted on the NULL). The moment it is written, the question "what happens to the claim when the crediting invoice is cancelled?" becomes real: a Storno (simple/copy) or a Storno + recreate leaves `AllocatedTo_InvId` pointing at the cancelled invoice, and the next invoice for the same service requests would silently bill the customer again for money already paid. ## Decision - **A claim counts only while the crediting invoice is alive.** An `AllocatedTo_InvId` that points at a cancelled invoice (`IsCanceled = 1`), at a non-existent invoice, or at the invoice that the one being finalised replaces (`CanceledInvoice` line → `@Replaces`) is void: the Abschlagsrechnung is offered again by `fds__prepInvoice` / `fds__prepStorno_recreate` and may be taken over by the next finalisation. The single source of truth for "already credited elsewhere" is `[dbo].[fds__fn_IntermediateIsAllocatedToOther]`; `fds__setInvoiceFinal`'s `existing` CTE is its set-based twin and must apply the same rule. - **The link is not cleared on cancellation.** Cancellation has several paths (storno simple/copy, storno + recreate, the retired `fds__setInvoiceCancelled`); rather than releasing the link in each, the readers ignore dead links. The last claimant therefore stays visible for auditing. - **A Stornorechnung (`InvoiceType = 'c'`) never claims.** `fds__createStorno_copy` mirrors the cancelled invoice's `IntInvoiceAllocation` lines (with their references) so the storno document shows them, but finalising the storno must not move the allocation onto it. - **Storno + recreate carries the references.** `fds__prepStorno_recreate` emits its credit lines with the same type as `fds__prepInvoice` (`IntInvoiceAllocation`, not `PartialInvoice`) and the credited invoice in `reference_InvId`, and its `CanceledInvoice` line references the replaced invoice — otherwise the replacement neither takes over the credit nor records what it replaces. ## Consequences - The recreate flow now produces `Replaces_InvId` / `DateCancelled` on the replaced invoice via the regular `fds__setInvoiceFinal` bookkeeping (it did not before: no reference reached the proc). - A second recreate (cancel the replacement, recreate again) keeps the credit because the claim on the replaced invoice is treated as void during the replacement's finalisation. - The one-off backfill script records the *earliest* live claimant; it is consistent with this rule. - An intermediate that is itself cancelled is not offered (unchanged). ## Alternatives considered - **Release `AllocatedTo_InvId` when the crediting invoice is cancelled** — rejected: four write paths to keep in sync, and the audit trail of who last credited the Abschlagsrechnung is lost. - **Keep `PartialInvoice` for recreated credit lines and teach `fds__setInvoiceFinal` both types** — rejected: one line type per meaning; `PartialInvoice` remains the Abschlagsbetrag charge line of an intermediate invoice.