Files
Fuchs_Intranet/Fuchs/Docs/INVOICE_SET_PRICING.md
T
Stefan c358fdbdb2 Invoice set pricing: 3 display modes (backend contract + PDF + tests)
Sets (mfr__items Type='set') can be shown three ways on the invoice:
- SetPrice (default): set line priced, member items shown without price
- ItemPrices: member items priced, set line as a heading without price
- SetOnly: only the set line (priced), members removed

- InvoiceSetPricing (new): the authoritative, unit-tested transformation
  (SetDisplayMode + Build) that both sides agree on; set price always equals the
  sum of members. Mode is read from InvoiceOptions ("setmode:<mode>").
- FuchsPdf.ApplyInvoice renders through it: lines flagged ShowPrice=false print
  blank price/total cells; set headers are emphasised. Invoices without sets are
  unchanged. Totals come from the registration balance, so modes are purely
  presentational and never change the sum.
- InvoiceSetPricingTests (+14): all three modes, set-price = member sum, header
  total fallback, no-set pass-through, option parsing.
- Docs/INVOICE_SET_PRICING.md documents the front-end contract (the editor sets
  the mode token + tags set header/member items); the back-end does the rest.

Front-end editor wiring is specified in the doc but intentionally not shipped
blind (cannot validate the running editor here).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:42:44 +02:00

3.3 KiB

Invoice "Set" Pricing — Design & Front-/Back-end Contract

Customer requirement: items declared as a set in [dbo].[mfr__items] ([Type] = 'set') should normally be shown as a single set price on the invoice instead of being broken up into their member items and summed.

Three display modes (switchable in the invoice editor):

Mode Set line Member items Use as
SetPrice (default) shown with price shown without price the new default
ItemPrices shown as a heading without price shown with price the previous behaviour
SetOnly shown with price removed compact

Totals are unaffected. The invoice total is taken from the registration balance (InvoiceBalance / InvoiceBalance_net), not by summing the rendered lines, so switching modes is purely presentational. The set price always equals the sum of its members (computed as a fallback when the set header carries no own price).

Back-end (implemented + unit-tested)

  • Fuchs/code/InvoiceSetPricing.cs — the authoritative transformation: SetDisplayMode + Build(items, mode) → ordered InvoiceSetLines, each with ShowPrice and IsSetHeader. ModeFromInvoiceOptions(...) reads the mode from the invoice options. Fully covered by Fuchs.Tests/InvoiceSetPricingTests.cs.
  • FuchsPdf.ApplyInvoice renders through InvoiceSetPricing.Build: lines with ShowPrice == false render blank price/total cells (not 0,00 €), and the set header line is rendered emphasised. Invoices without sets pass through unchanged.

Front-end contract (to wire in the invoice editor)

The editor already knows the set structure (from fds__prepInvoice) and assembles the invc JSON. To drive the modes it must, when sending the invoice:

  1. Mode — add a token to inv.InvoiceOptions (CSV, alongside §13b):

    • setmode:setprice (default — may be omitted), setmode:itemprices, or setmode:setonly. A 3-way switch in the editor sets this token.
  2. Item flags — in each service request's items[]:

    • the set header item: type: "set", id: "<setId>", and total_net = the set price (or 0 to let the back-end sum the members);
    • each member item: setId: "<setId>" (matching the header's id).
    • standalone items need no extra fields.

That is the entire contract — the back-end does the rest. The editor's running total stays the member sum in every mode, matching the registration balance.

Why the switch lives in the editor

Set grouping is only known where the request/item tree is rendered (front-end). The back-end intentionally stays the single, tested authority for how a chosen mode maps to printed lines, so the editor only needs to pick the mode and tag the items — it does not re-implement the pricing rules.

Persistence note

Draft/preview PDFs render straight from the posted invc JSON, so the contract works end-to-end for previews immediately. For a finalised invoice to re-render in a chosen mode later, the per-item type/setId flags (and the setmode option) must be persisted with the stored invoice items (fds__createInvoice_Details / fds__invoice_items). setmode already persists via InvoiceOptions; persisting the per-item set tags is a small SSDT + create-proc change to make once the editor wiring is confirmed.