--- status: Accepted date: 2026-09-30 applyTo: - "Fuchs_Database/dbo/Functions/fds__fn_invoiceBackdate.sql" - "Fuchs_Database/dbo/Stored Procedures/fds__setInvoiceFinal.sql" - "Fuchs/code/InvoiceDates.cs" - "Fuchs/Services/ERechnungMapper.cs" - "Fuchs/Services/InvoiceService.cs" - "Fuchs/Controllers/IntranetController.InvoiceDraft.cs" - "Fuchs/Controllers/IntranetController.Requests.cs" - "Fuchs/js/intranet/modules/fis.inv_shared.js" supersededBy: "" --- # 0032 — Invoices may be backdated to the last working day of the previous month (admins only) ## Context Work done at the end of a month is often invoiced in the first days of the next month. The customer wants those invoices dated to the previous month. The invoice date was always the moment of finalisation (`fds__setInvoiceFinal`: `DateFinalized = GETUTCDATE()`), and the invoice number is drawn from the year of that date (`fds__newInvoiceId(Year(...))`). Two invoice dates also existed side by side: the PDF printed `DateCreated` (draft creation) as "Rechnungsdatum", while the embedded ZUGFeRD/XRechnung issue date (BT-2) used `DateFinalized` — a visible date and an e-invoice date that could disagree. ## Decision - **One invoice date.** PDF "Rechnungsdatum" and e-invoice issue date both come from `InvoiceDates.IssueDate`: `DateFinalized` (converted from the stored UTC to the German calendar day), falling back to `DateCreated` for drafts. - **Backdating option.** When finalising, a user with `fds_inv > 2` may date the invoice to the **last working day (Mon–Fri) of the previous month** — a fixed date, not a free choice. Public holidays are not excluded (possible later extension). - **Only while no invoice exists after that date.** Invoice numbers must ascend with invoice dates, so the option is offered and accepted only while no invoice has been finalized after the backdate day (a 2 h margin covers the UTC storage). - **Single source of truth:** `[dbo].[fds__fn_invoiceBackdate](@authuser)` returns the allowed date or NULL (authorisation + eligibility). `inv/dpreview` returns it so the preview dialog shows the per-invoice checkbox; `req/sconf` passes `@backdate = 1`, and `fds__setInvoiceFinal` re-evaluates the function and `THROW`s (60001) when it is no longer allowed — nothing is finalised then. - **Serialized finalisation.** `fds__setInvoiceFinal` runs check + numbering `UPDATE` in one transaction under an exclusive, transaction-owned app lock (`sp_getapplock`, resource `fds__setInvoiceFinal`) taken by every finalisation, so a concurrent normal finalisation cannot slip between the eligibility check and the backdated write and break the date/number order. - `req/sconf` maps only SQL error 60001 to the "no longer possible, create without backdating" message. A 0-row result (invoice already final/sent, e.g. a double submit) is reported as "already finalised" — never as a backdate rejection, which would invite a duplicate invoice. - A backdated invoice is stored with `DateFinalized = 12:00` (UTC, stays on the same German day), its number comes from that day's year, and `DueDate` is recomputed from the payment term anchored on that day. ## Consequences - The PDF of every newly finalised invoice now shows the finalisation date instead of the draft creation date (intended: it now matches the e-invoice and the number sequence). - In January the option yields a December date and therefore a number from the previous year's range. - Lists, reports, DATEV and reminders follow the same invoice date — see ADR 0035. - Telemetry: `fuchs.invoices.backdated` (outcome ok / rejected / already_final / error); successful backdating logs EventId `InvoiceBackdated`. ## Alternatives considered - **Free date picker for admins** — rejected: breaks the date/number order too easily; the need is exactly "previous month's last working day". - **Global admin switch "backdate everything until the first invoice of the month"** — rejected by the customer in favour of an explicit per-invoice checkbox.