The hole
A `.print` template has no way to control how a bound value is formatted. `DataBinder.stringify` applies the money pattern (`### ### ##0.00`, space-grouped, ROOT locale) only to values typed `Double`/`Float`/`BigDecimal`; everything else renders via `toString`.
Both print paths (`PrintEndpoint` for the browser POST, `PrintFacade` for the server-side snapshot mint) parse the `{document, items}` JSON with Gson `ToNumberPolicy.LONG_OR_DOUBLE`, so the presence of a decimal point in the JSON decides the type:
- `2863.24` → `Double` → `2 863.24` ✔
- `5390` → `Long` → `5390` ✘
A money value that lands on a whole figure prints bare. The browser path makes this systematic: `JSON.stringify(5390.00)` emits `5390`, so any amount that round-trips through the UI loses its scale marker. On a real invoice this shows as a mixed totals block - amounts with cents formatted, round amounts bare - and there is nothing the template author can write to fix it. Dates have the sibling problem: an ISO `2026-08-29` cannot be reformatted to the local convention (`29.08.2026`).
Blanket-formatting `Long` in the binder would be wrong (an id or a year printed through a template would render `7.00`), so the knob belongs to the template author.
Proposal: an optional `:pattern` suffix on placeholder operands
Mirroring the `{date:yyyyMMdd}` token the declarative `fileName` patterns already use (#6904):
<column width="*" align="right" label="PRICE">{{Price:#,##0.00}}</column>
<text align="right">Date: {{document.Date:dd.MM.yyyy}}</text>
<text>{{document.Customer.NameLocal|document.Customer.Name}}</text> <!-- unchanged -->
Semantics:
- the suffix starts at the first `:` of an operand; the pattern applies to that operand's resolved value
- a `Number` (any subtype, `Long` included) formats via `DecimalFormat` with the existing ROOT + space-grouping symbols - `{{Price:#,##0.00}}` renders `5390` as `5 390.00`
- a `Temporal`, or a `String` parseable as an ISO date/date-time (the shape the feeders emit), formats via `DateTimeFormatter.ofPattern`
- anything else - and any pattern that does not fit the value - falls back to today's rendering (lenient by the parser's own contract: a printout never shows raw braces or an exception)
- `|`-fallback semantics unchanged: blankness is judged on the rendered result, the last operand always renders
- backward compatible: no existing `.print` template (shipped or generated) carries a `:` inside a placeholder body
Found on a generated sales-invoice print where the items table showed `5390` next to a formatted totals block.
The hole
A `.print` template has no way to control how a bound value is formatted. `DataBinder.stringify` applies the money pattern (`### ### ##0.00`, space-grouped, ROOT locale) only to values typed `Double`/`Float`/`BigDecimal`; everything else renders via `toString`.
Both print paths (`PrintEndpoint` for the browser POST, `PrintFacade` for the server-side snapshot mint) parse the `{document, items}` JSON with Gson `ToNumberPolicy.LONG_OR_DOUBLE`, so the presence of a decimal point in the JSON decides the type:
A money value that lands on a whole figure prints bare. The browser path makes this systematic: `JSON.stringify(5390.00)` emits `5390`, so any amount that round-trips through the UI loses its scale marker. On a real invoice this shows as a mixed totals block - amounts with cents formatted, round amounts bare - and there is nothing the template author can write to fix it. Dates have the sibling problem: an ISO `2026-08-29` cannot be reformatted to the local convention (`29.08.2026`).
Blanket-formatting `Long` in the binder would be wrong (an id or a year printed through a template would render `7.00`), so the knob belongs to the template author.
Proposal: an optional `:pattern` suffix on placeholder operands
Mirroring the `{date:yyyyMMdd}` token the declarative `fileName` patterns already use (#6904):
Semantics:
Found on a generated sales-invoice print where the items table showed `5390` next to a formatted totals block.