Files
Fuchs_Intranet/Fuchs/Docs/Decisions/0032-invoice-backdating-to-previous-month.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

69 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 = <day> 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.