Invoice set pricing: wire editor + align editor/backend contract
Front-end (fis.inv_shared.js / fis.inv_txt_de.js, rebuilt bundles): - 3-way set display switch (setprice/itemprices/setonly) via admin.setmode, emitted into InvoiceOptions by FdsInvoiceData.BuildInvoiceOptions. - Each request block now posts items[] in the backend contract shape (title/desc/qty/price_net/total_net + set type/setId tags) via itemToContract. - invcPayload normalises the editor model to the field names BuildInvoiceParams reads (sms totals -> new.total_net/total_gross, invoicetitle->title, loc->provisionlocation, admin.paymentterms->new.paymentterm, CustomerId->customerid). Back-end: - BuildInvoiceOptions adds the setmode token alongside §13b. - VAT rate+amount now taken from sms.vat (HighestVat) instead of the broken items 'is List<object>' detection that pinned the rate to 19. - InvoiceSetPricing blanks price/total cells for text/title heading lines. Tests: set-pricing text-line blanking, HighestVat selection/parsing, updated the VAT param test to the sms.vat contract. 180 passing. Note: the second VAT slot (InvoiceVAT_2) stays unused by design; mixed-rate invoices store only the highest rate (pre-existing, accepted). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -29,23 +29,59 @@ Three display modes (switchable in the invoice editor):
|
||||
set header line is rendered emphasised. Invoices without sets pass through
|
||||
unchanged.
|
||||
|
||||
## Front-end contract (to wire in the invoice editor)
|
||||
## Front-end contract (implemented 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:
|
||||
Wired in `Fuchs/js/intranet/modules/fis.inv_shared.js` (bundled to
|
||||
`wwwroot/web/fis.inv.de.js` via gulp `min:js`):
|
||||
|
||||
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.
|
||||
1. **Mode** — a 3-way switch (`$inv.ssetmode`, menu entry `setm`, label
|
||||
`$ict.setm`) writes the choice onto `admin.setmode`
|
||||
(`setprice` | `itemprices` | `setonly`). The back-end
|
||||
`FdsInvoiceData.BuildInvoiceOptions` turns that into the
|
||||
`setmode:<mode>` token inside `@InvoiceOptions` (default `setprice` omitted),
|
||||
persisted by `fds__createInvoice_Details` and read back by
|
||||
`InvoiceSetPricing.ModeFromInvoiceOptions`. This rides the **same `admin`
|
||||
channel as `§13b`** (the posted payload is `{admin, req, sms, new}` — `inv`
|
||||
is not sent).
|
||||
|
||||
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.
|
||||
2. **Item shape** — `$inv.invSumUpdate` now posts each request block's
|
||||
`items[]` in the back-end contract shape via `$inv.itemToContract`:
|
||||
`{ id, type, title, desc, qty, price_net, total_net, vat }`. (Previously the
|
||||
editor only posted the legacy on-screen `itm`/`co` objects, which
|
||||
`FdsInvoiceData.InvoiceItems` does not read — so line items never reached the
|
||||
C# PDF. This change closes that gap for **all** invoices, not just sets.)
|
||||
|
||||
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.
|
||||
3. **Set flags** — `invSumUpdate` tags items as it builds `items[]`: an item with
|
||||
`type === 'set'` is a header (`id` = its set id); the **following items in the
|
||||
same block become its members** (`setId` = the header's id) until the next set
|
||||
header. `mfr__items` has a `Type='set'` header but **no explicit member link**,
|
||||
so this "header claims the following items in its block" rule is the convention
|
||||
— adjust in `invSumUpdate` if mfr later exposes a real grouping.
|
||||
|
||||
### Editor → backend field normalization (`$inv.invcPayload`)
|
||||
The editor's internal model keeps the long-standing key names, but the migrated C#
|
||||
`BuildInvoiceParams` reads different ones. At post time `$inv.invcPayload(d)` maps the
|
||||
working model onto the exact field names the back-end reads (non-destructively):
|
||||
`sms.ttn → new.total_net`, `sms.ttb → new.total_gross`, each `sms.vat` rate →
|
||||
`new.vat_<rate>_net`, `new.invoicetitle → new.title`, `new.loc → new.provisionlocation`,
|
||||
`admin.paymentterms → new.paymentterm`, `admin.CustomerId → admin.customerid`. Both
|
||||
`req/save` and `req/sprep|sedit` post through it, so titles/balances/VAT now reach the
|
||||
backend correctly.
|
||||
|
||||
VAT (rate **and** amount) is taken by the backend directly from the posted `sms.vat`
|
||||
map via `FdsInvoiceData.HighestVat` (highest rate wins) — `invcPayload` therefore emits
|
||||
no per-rate `vat_*` keys.
|
||||
|
||||
**Back-end fixes applied alongside the wiring:**
|
||||
- Heading/free-text lines (`type` `text`/`title`) now render a **blank** price/total
|
||||
(`InvoiceSetPricing.IsNoPriceLine`), instead of `0,00 €`.
|
||||
- VAT rate detection no longer reads line items (the old `items is List<object>` test
|
||||
failed on Newtonsoft `JArray` and pinned `@InvoiceVAT_1` to `19`); it now comes from
|
||||
`sms.vat`, so non-19 % rates are stored correctly. Single-rate procs still store only
|
||||
the highest rate.
|
||||
|
||||
The editor's running **total stays the member sum in every mode**, matching the
|
||||
registration balance — switching modes is purely presentational.
|
||||
|
||||
### Why the switch lives in the editor
|
||||
Set grouping is only known where the request/item tree is rendered (front-end).
|
||||
@@ -55,9 +91,9 @@ 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.
|
||||
works end-to-end for previews and creation. `setmode` persists via
|
||||
`InvoiceOptions`; the finalised document is rendered once and stored as a file, so
|
||||
re-rendering from line items is not needed for correctness. Persisting the
|
||||
per-item `type`/`setId` flags (an SSDT + `fds__createInvoice_Details` change) is
|
||||
only required if a finalised invoice must be **re-generated** from stored items in
|
||||
a different mode later — not done here.
|
||||
|
||||
Reference in New Issue
Block a user