diff --git a/Fuchs/Docs/EVAL_live_invoice_editing.md b/Fuchs/Docs/EVAL_live_invoice_editing.md new file mode 100644 index 0000000..7038470 --- /dev/null +++ b/Fuchs/Docs/EVAL_live_invoice_editing.md @@ -0,0 +1,98 @@ +# Evaluation — Backend-cached invoice editing over SignalR + +**Idea (as proposed):** hold invoices that users are editing in a **server-side +cache**, keep a **SignalR / WebSocket** connection open, apply each front-end +change **in the backend**, and **push the recomputed state back** to the browser. +The backend becomes the single source of truth for the in-progress invoice. + +This note evaluates that against the current design and recommends a path. + +--- + +## 1. How invoice editing works today + +- **Stateless.** The browser holds the editor state. It posts the whole `invc` + JSON to `req/sprep|sedit|save`; the server computes/persists and returns a PDF + preview (image collection). No per-user editing state lives on the server. +- Totals/§13b/§35a and now **set pricing** are computed from posted data; the + invoice **total comes from the registration balance**, not summed lines. +- Auth is cookie-based (`OCORECookieAuthenticationEvents`), SQL-first data access, + controller is stateless and DI-scoped. + +**Implication:** the server is horizontally scalable and crash-tolerant for +editing — there is nothing to lose if a node restarts mid-edit. + +--- + +## 2. What the proposal would buy + +| Benefit | Real for Fuchs? | +|---|---| +| Server-authoritative calculation (one place for pricing/VAT/set rules) | **Partly already true** — the PDF/totals are server-computed; the editor only previews. The duplicated logic is the *live* line math in JS. | +| Real-time multi-user co-editing | **Low value** — invoices are edited by one back-office user at a time; concurrent editing of the same draft is rare. | +| Live validation / instant recompute without full round-trips | **Some value** — smoother UX than re-posting the whole `invc` for each tweak. | +| Reduced payload (deltas vs whole invoice) | Marginal — invoices are small. | + +--- + +## 3. Costs and risks + +- **Server-side edit state.** Per-user/per-draft cache with lifetime management + (idle expiry, explicit discard, max size), or the server leaks memory. Needs a + distributed cache if scaled out (Redis), or **sticky sessions** for SignalR. +- **Concurrency/locking.** Two tabs or two users on the same draft → need + optimistic concurrency / locking semantics that don't exist today. +- **Connection lifecycle.** Reconnect, replay, and "lost update" handling; + offline/flaky networks; auth on the socket (cookie works, but token refresh and + disconnect-on-logout must be handled). +- **Scaling.** SignalR with multiple instances needs a backplane (Redis/Azure + SignalR). Today the app has none. +- **Big rewrite of the editor.** The 1,200-line `fis.inv_shared.js` becomes an + event-driven client of server state — a substantial, risky rewrite of working + code, with new failure modes (desync between optimistic UI and server truth). +- **Testing surface** grows (connection states, races) far beyond the current + request/response model. + +For a single-tenant back-office app with one editor at a time, this is a lot of +**accidental complexity** for modest UX gains. + +--- + +## 4. Recommendation + +**Do not do a full SignalR rewrite now.** It optimises a problem (real-time +collaboration, server-held edit state) the business doesn't strongly have, while +adding stateful-server, scaling, and reconnection complexity to an app that is +currently simple and robust. + +Prefer an **incremental, lower-risk path** that captures most of the value: + +1. **Single source of pricing truth (highest value, do first).** + Add a stateless endpoint `inv/calc` that takes the `invc` JSON and returns the + computed lines + totals + set-mode resolution using the **same backend code** + (`InvoiceSetPricing`, VAT, §13b/§35a). The editor calls it on change (debounced) + to recompute, instead of duplicating the math in JS. This removes the + front/back duplication — the actual pain — **without** sockets or server state. + It also makes the interplay fully unit-testable (the endpoint is pure). + +2. **Keep preview as-is** (post → PDF image) but allow it to reuse `inv/calc`. + +3. **If/when live UX is still wanted**, add SignalR **only as a transport** on top + of the stateless calc (push recompute results), keeping persistence stateless. + Defer server-held draft state until genuine multi-user co-editing is required. + +4. **If server-held drafts are truly needed**, scope a pilot: one entity (invoice + draft), `IDistributedCache`-backed, explicit acquire/release lock, idle TTL, + and a reconnect/replay protocol — behind a feature flag, measured against the + current flow before rollout. + +### Why this fits the codebase +It aligns with the just-completed DI service layer: the calc lives in +`IInvoiceService`/`InvoiceSetPricing` (already tested), reused by both the preview +and a future socket. We get "changes computed by the backend, reflected in the +frontend" — the stated goal — **without** turning a stateless, scalable web app +into a stateful real-time system prematurely. + +**Suggested next step:** implement `inv/calc` (item 1) and migrate the editor's +line math to it; revisit SignalR only if real-time/co-editing becomes a real +requirement.