Files
Fuchs_Intranet/Fuchs/Docs/Decisions/0006-backend-authoritative-draft-editing.md
T
Stefan 49e3ed2673 Add unit tests for Fuchs_DataService and related components
- Introduced comprehensive unit tests for the Fuchs_DataService library, covering DATEV header formatting, CSV/XML generation, and FdsMfrClient construction.
- Implemented tests for FdsMfr.UpdateNeed parsing and FdsShared utility helpers, ensuring correct functionality and stability.
- Added tests for FdsConfig and FdsMfrClient to validate configuration resolution and client construction.

Document decisions on backend-authoritative invoice and reminder handling

- Created ADR 0008 to clarify that all invoice types and reminder stages are backend-authoritative during drafting and previewing.
- Established that all calculations and settings must be processed server-side, ensuring consistency between online editor and PDF outputs.

Define irreversible mutations for set-price modes in invoices

- Documented ADR 0009 to specify that the "Set mit Preis" and "Nur Set mit Preis" operations are irreversible mutations affecting service request blocks.
- Clarified that these operations are not display toggles but actual data changes, ensuring clear expectations for invoice handling.

Transition MFR ERP sync to in-process execution within the web app

- Created ADR 0010 to outline the migration of Fuchs_DataService from a standalone service to an in-process library within the Fuchs web application.
- Updated configuration and logging management to be handled by the host application, streamlining the sync process.

Add publish profile and periodic hosted service for job scheduling

- Introduced a publish profile for deployment to a specified folder.
- Implemented PeriodicHostedService to manage multiple independent jobs, including the MFR ERP sync, with configurable execution intervals.

Add dotnet-tools.json for EF Core CLI tools

- Included dotnet-tools.json to manage the version of dotnet-ef for Entity Framework Core migrations and commands.
2026-07-16 13:34:23 +02:00

91 lines
5.1 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-07-10
applyTo:
- "Fuchs/Services/InvoiceDraft*"
- "Fuchs/Services/IInvoiceDraft*"
- "Fuchs/code/InvoiceDraftSession.cs"
- "Fuchs/code/InvoiceDraftCalculator.cs"
- "Fuchs/Notifications/DraftPreviewHub.cs"
- "Fuchs/Notifications/*DraftNotifier*"
- "Fuchs/Controllers/IntranetController.InvoiceDraft.cs"
- "Fuchs/js/intranet/**"
supersededBy: ""
---
# 0006 — Invoice draft editing is backend-authoritative over an in-memory cache
## Context
The invoice editor was deliberately **stateless**: the browser held the working
model, computed totals/VAT client-side (`invSumUpdate` in `fis.inv_shared.js`) and
re-posted the whole `invc` JSON on every preview/save. `EVAL_live_invoice_editing.md`
(2026) recommended keeping it that way and **against** a server-cached, SignalR-driven
model, because the real-time/co-editing benefits were weak for a single back-office
editor.
The product owner has since decided the trade-off differently and prioritised a
**single source of truth in the backend** with server-computed sums, server-side
plausibility/consistency checks, in-place PDF preview without re-upload, an automatic
change history, and an explicit discard. This decision records that reversal and the
architecture chosen to implement it.
## Decision
While a user edits an invoice draft, the authoritative state lives **server-side** in
an in-memory `InvoiceDraftSession` (`Fuchs/code/InvoiceDraftSession.cs`), held by the
singleton `IInvoiceDraftCache` and orchestrated by the scoped `IInvoiceDraftService`
(`InvoiceDraftEditService`). The browser is a pure view/input layer.
- **Truth & calculation on the server.** `InvoiceDraftCalculator` is the pure,
unit-tested port of the former client-side math (`quantChange` + `setVat` +
`invSumUpdate`), including per-line net/VAT/service-value multiplication
(`RecomputeLineValues`), the §13b reverse-charge rule and VAT-per-rate grouping. The
browser performs **no arithmetic whatsoever** — not even a single line's
`net = qty × price` — it only renders the server's `req`/`sums`.
- **Commands are ordinary POSTs; signals are SignalR.** The editor posts single edits
to `inv/dpatch` (and `dopen`/`dstate`/`dpreview`/`dsave`/`dhistory`/`ddiscard`/`dclose`).
The server mutates the session, recomputes, validates, bumps a version, and pings the
editing browser (`draftReady`) to re-fetch `inv/dstate`. See
[0007](0007-targeted-draft-signalr-groups.md) for the targeted-signal transport.
- **Cache-only until Zwischenspeichern/Finalise.** Opening builds the session (from a
brand-new payload or by reloading a DB draft); edits touch only the cache. `dsave`
flushes the session to the DB by reusing the existing
`IInvoiceService.RegisterInvoiceAsync`**no new persistence path** — and reports
success/failure through the existing `IEventService` (ADR 0001). Finalise continues
through `req/sconf`.
- **Preview from cache.** `inv/dpreview` renders the draft PDF straight from the session
(synthesised registration), with no client upload.
- **Automatic change history.** Every applied patch appends a `ChangeHistoryEntry`
(cache-only, never persisted); `inv/dhistory` exposes it for the "Änderungshistorie"
dialog.
- **Idle lifecycle with user warning.** `InvoiceDraftExpiryService` warns the editing
browser before a session's idle TTL lapses (`draftExpiring`) and, on eviction, tells
it to close the editor with a reason (`draftClosed`). TTL and warning lead are under
`Fuchs:DraftEditing`.
## Consequences
- The server is now **stateful for in-progress drafts**. This is acceptable for a
single-instance deployment; **scale-out requires sticky sessions or a distributed
cache/SignalR backplane** — none exist today, so this is a documented limitation, not
a silent assumption.
- New editor interactions must be modelled as a **delta** applied server-side (add a
case in `InvoiceDraftEditService.ApplyDelta` + calculator handling), never as a new
client-side calculation. Do not reintroduce client-side totals.
- `FdsInvoiceData` stays a pure data holder; `InvoiceDraftSession` is likewise a data
holder, with all logic in the service/calculator (mirrors the existing service split).
- Reminders (Mahnungen) are intended to follow the identical pattern as a second phase;
this decision covers invoices first (the pilot) and applies to the reminder mirror
when built.
- `EVAL_live_invoice_editing.md` and `INVOICE_LIFECYCLE.md` §4/§10 (the "stateless
editor" invariant) are superseded by this decision for the draft-editing flow and have
been annotated accordingly.
## Alternatives considered
- **Keep the stateless editor** (the prior recommendation): rejected by the product
owner in favour of a backend single source of truth.
- **Full bidirectional SignalR hub for commands too**: rejected — edits as POSTs reuse
the existing controller/auth pattern and avoid a command reconnect/replay protocol; the
hub carries only coordination signals.
- **Write-through to the DB on every edit**: rejected — conflicts with the
"Zwischenspeichern = persist the cache" semantics and adds DB load; the cache is the
truth until an explicit save/finalise.