Printing and documents
A document (header-items) entity - one with an *Item composition child, like SalesInvoice + SalesInvoiceItem - gets a printable document template on Generate. The template is written in the document-template DSL (a small XML-like layout language), seeded into the CMS, and rendered to PDF on demand from the entity's own data.
The generated .print template
PrintIntentGenerator emits one template per document master, at the exact path it must occupy in the CMS, under the project's doc/ folder:
doc/Templates/<Entity>/Print/en/standard.printThe master is detected exactly like the document layout in the entity generator - an entity that is the composition parent of a child named *Item. The generated template is a complete starting point: a humanized title, the document number subtitle, the header fields, an items table, and a totals footer.
It is generated once and never regenerated over. Like the developer-owned .settings file, the template is written create-if-absent: a later Generate leaves an existing file untouched. This is deliberate - a printed business document is a formatted, audited artifact you adapt by hand, and a newly added model field must not silently appear on an already-designed invoice. Treat the generated file as a per-tenant starting layout to adapt, not a finished document.
The document-template DSL
The template is a tree of layout tags. The ones you will use most:
| Tag | Purpose |
|---|---|
<page width height padding> | the page box |
<header> / <footer> | rendered once, top and bottom |
<section> / <stack> | a vertical block of content |
<row> | side-by-side columns, one per child <stack width="..."> |
<field label="..."> | a labelled value |
<text style="title|subtitle|caption" align="..."> | free text |
<table source="items"> + <column width label> | the line-items table |
<total align="right"> | the emphasized document total |
<line/> | a horizontal rule |
<if source="..."> | keep the children only when the value is present |
Values are Mustache placeholders. A two-column header with the seller on the left and invoice meta on the right:
<header>
<row>
<stack width="55%">
<text style="title">{{document.Organization}}</text>
<text>{{document.Organization.Address}}</text>
</stack>
<stack width="45%">
<text align="right" style="title">INVOICE</text>
<field align="right" label="No">{{document.Number}}</field>
<field align="right" label="Date">{{document.Date}}</field>
</stack>
</row>
<line/>
</header>
<table source="items">
<column width="4*" label="Description">{{Name}}</column>
<column width="*" align="right" label="Qty">{{Quantity}}</column>
<column width="1.5*" align="right" label="Net">{{Net}}</column>
</table>Decimal values print in the money pattern (### ### ### ##0.00); an unresolved placeholder renders empty (a printout never shows raw braces). Wrap an optional field in <if source="..."> so its label does not print when the value is blank.
The data contract - the print feeder
The template binds against data supplied by a generated print feeder - a small @Controller (gen/events/<Entity>PrintFeeder.java) that loads the document and its related records through the generated repositories and returns a nested document-and-items payload. This is what lets a template reach related data, not just the document's own columns:
{{document.<Property>}} the document's own field
{{document.<Relation>}} a to-one relation's display label (e.g. the customer's name)
{{document.<Relation>.<Field>}} a field of the related record - e.g. document.Organization.Iban,
document.Customer.Address
{{<Property>}} a line-item field (inside <table source="items">)The feeder walks the document master's whole reachable to-one graph (two hops within the same model, one hop into a referenced module) and is regenerated with the application, so the generated Java is an exact, auditable record of everything a print receives. Because it loads through the repositories and is called by the browser as the signed-in user, it inherits the multilingual translation overlay (the document prints in the caller's language) and the caller's authorization and tenant - no server-side data fetching or credential forwarding. Date and timestamp fields are supplied as ISO strings; numbers are left raw so the template's money formatting applies.
The doc/ folder and CMS seeding
Everything under a project's doc/ folder is seeded into the CMS on publish, mirroring the path it has under doc/:
<project>/doc/Templates/SalesInvoice/Print/en/standard.print
-> CMS /Templates/SalesInvoice/Print/en/standard.printThe CmsSeedSynchronizer does this generically - it is not print-specific. Any file under doc/ (print templates, images, other documents) is seeded as CMS content. Three rules:
- Create-if-absent, never overwrite - the CMS copy is the business user's customization surface; a re-publish never clobbers it.
- Delete never touches the CMS - removing the source file (or the project) removes only the tracking record; uploaded customizations survive.
- Per-tenant - each tenant's publish seeds its own copy under its own CMS root.
doc/ is a raw CMS staging area - do not place model artefacts (.csvim, .bpmn, ...) there expecting their normal engines to run; under doc/ they are treated as opaque content.
Customizing and multi-language
Business users customize a document through the Documents perspective: download the seeded standard.print, edit it, and upload it back - the upload is the version that prints, and it is never overwritten by a re-publish.
Only English (en) is generated. To add a language, add another file under a sibling language folder:
doc/Templates/SalesInvoice/Print/en/standard.print
doc/Templates/SalesInvoice/Print/bg/standard.printWhen several languages exist, the Print button asks which to use; otherwise it prints the only one. The default follows the user's Region & Language setting.
Printing at runtime
In a generated document view, the Print button (available while editing a record) calls the feeder for the current record, then posts the payload to the print service, which resolves the template from the CMS, binds the data, and returns a PDF:
GET /services/print/{entity}/languages- the languages a document has templates for.POST /services/print/{entity}?lang=enwith{ "document": { ... }, "items": [ ... ] }- returnsapplication/pdf.
Rendering goes through the document parser, the data binder, and an XSL-FO / Apache FOP transform to PDF.