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>
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)→ orderedInvoiceSetLines, each withShowPriceandIsSetHeader.ModeFromInvoiceOptions(...)reads the mode from the invoice options. Fully covered byFuchs.Tests/InvoiceSetPricingTests.cs.FuchsPdf.ApplyInvoicerenders throughInvoiceSetPricing.Build: lines withShowPrice == falserender blank price/total cells (not0,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:
-
Mode — add a token to
inv.InvoiceOptions(CSV, alongside§13b):setmode:setprice(default — may be omitted),setmode:itemprices, orsetmode:setonly. A 3-way switch in the editor sets this token.
-
Item flags — in each service request's
items[]:- the set header item:
type: "set",id: "<setId>", andtotal_net= the set price (or0to let the back-end sum the members); - each member item:
setId: "<setId>"(matching the header's id). - standalone items need no extra fields.
- the set header item:
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.