Files
Fuchs_Intranet/Fuchs/Docs/Decisions/0034-abschlagsrechnung-credit-follows-the-live-invoice.md
T
Stefan c741535aec
Playwright Tests / test (push) Canceled after 0s
feat: Implement mfr truncation fix and enhance invoice handling
- Added a new SQL script to address the "String or binary data would be truncated" error in mfr sync by widening the Note fields in service objects and companies to NVARCHAR(MAX).
- Updated the table types and associated stored procedures to accommodate the changes, ensuring compatibility with existing data.
- Queued a re-sync for previously rejected rows due to truncation issues.
- Created application logins for fuchs_fds and fuchs_web with placeholder passwords for initial setup.
- Introduced functions for invoice backdating and invoice date retrieval to streamline invoice processing.
- Developed a password generation function with customizable parameters for length and character types.
- Added a random number generation function to support password creation.
2026-09-30 16:56:08 +02:00

3.9 KiB

status, date, applyTo, supersededBy
status date applyTo supersededBy
Accepted 2026-09-30
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

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.