Statutory layouts
Some documents have a layout that is set by regulation: a payment instruction, a cash order, a form with prescribed fields. For those the layout is not a design choice, so the print form has to say so. The engine offers two ways to build one. Use the first. The second exists for the case where the geometry itself is prescribed.
create-print-form (in your repo's .claude/skills/) has the full attribute table and the diagnostics. This page
explains when a statutory form is different and which tool to reach for.
Most statutory forms need only the frame and the roles
A statutory document in the reference configuration is a normal print form with three choices made differently:
Frame = PrintFrame.None. No letterhead and no parameters line. The payer or the company is named by the party block, so a letterhead would repeat it. The footer band is still drawn.- Drafts.
Forbiddenfor a document that may not be printed as a draft at all, so the print is refused.Markedfor one that may be printed as a draft, with the marker. - No table, if the document has none. A form made only of parties, references and totals simply has no
[PrintTable]. Don't add a placeholder collection to fill one.
The rest is the same vocabulary as any other form: parties, references, a Note, a GrandTotal, a Currency,
signatures and a VatMode.
Worked example: the payment instruction
The payment instruction (PaymentInstructionPrintForm, "Платіжна інструкція") is the reference configuration's
statutory document with no table. It is not sent to the bank on paper. It is what a user retypes into the client bank.
The settled documents are not printed on it, and it has no letterhead:
[PrintForm(Frame = PrintFrame.None, Drafts = DraftPrinting.Marked)]
[PrintTitle("PaymentInstructionPrintTitle")]
public sealed class PaymentInstructionPrintForm : DocumentPrintForm
{
[Caption("Payer")]
public required PrintParty Payer { get; set; }
// Null when the payment names no counterparty (a bank fee): the block is left out.
[Caption("Recipient")]
public PrintParty? Recipient { get; set; }
[PrintRole(PrintRole.Note)]
[Caption("PaymentPurpose")]
public string PaymentPurpose { get; set; } = string.Empty;
// A payment document makes no VAT statement: its amount may include VAT, so no "Without VAT" note under the amount in words.
public PrintVatMode VatMode { get; set; } = PrintVatMode.None;
[PrintRole(PrintRole.GrandTotal)]
public decimal Amount { get; set; }
[PrintRole(PrintRole.Currency)]
public string? CurrencyCode { get; set; }
public IReadOnlyList<PrintSignature> Signatures { get; set; } = [];
}
Each choice has a reason:
- No letterhead. The payer block is the company, so the letterhead would print the company twice.
- No table. The instruction is for one payment. The documents that payment settles are not listed on it.
VatMode = None. The payment's amount may or may not contain VAT, and "Without VAT" would be a claim the payment cannot make. ANoneform prints the total and the amount in words, and no VAT statement at all.- A
Recipientthat may be null. A bank fee names no counterparty. A null party leaves its block out, rather than printing an empty one.
The cash orders (the receipt and payment orders, "ПКО" and "ВКО") follow the same pattern with Frame = PrintFrame.None
and Drafts = DraftPrinting.Forbidden, because a cash order cannot be printed as a draft. The company is the first
party. The chief accountant's name comes from a constant and the cashier's from the cash point. A cash order is
refused in every format while it is a draft.
The escape hatch: sections and styles
Some statutory forms prescribe where each field sits on the page, not just what it means. For those, the engine keeps the geometric vocabulary:
| Scope | Attribute | What it does |
|---|---|---|
| Class, repeatable | [PrintSection(key, Columns = n)] | A flow section: an n-column label and value grid. PositionX, PositionY and PositionWidth anchor it at a position on the page. |
| Class, repeatable | [FixedSection(key, HeightMm = …)] | A reserved rectangle at absolute millimetre coordinates, always anchored. Its fields print one label and value line each, in declaration order. |
| Class, repeatable | [PrintStyle(key)] | A named style: Align, FontSize, Bold, Italic, Color, BackgroundColor and Borders. A section or column uses it through StyleKey. |
| Property | [PrintSectionRef(key)] | Puts a scalar property into a section. An explicit section wins over a role. |
Three rules apply to all of them:
- A section has no title of its own. Give each field a distinct caption: "Payer IBAN" and "Recipient IBAN", not two "IBAN"s.
- Roles other than
AuditandStatusare ignored inside a[FixedSection]. - Use
Frame = PrintFrame.Nonewith them. A letterhead and a parameters line are not part of a prescribed layout.
No KandraWms form uses the escape hatch today. The engine renders sections and fixed sections, and its own tests
(SectionsOnlyPrintGeneratorTests and PrintRolesGeneratorTests in Kandra.Tests) are the reference for the syntax.
Treat it as untested in a configuration until a form in your own configuration uses it, and check the result against
the statutory sample.
Which tool for which need
| You need | Use |
|---|---|
| Parties, references, totals, signatures | Roles and types, as in any print form |
| No company block | Frame = PrintFrame.None |
| A free-text paragraph under the table | [PrintRole(PrintRole.Note)] |
| A VAT statement that must not appear | VatMode = PrintVatMode.None (a payment document) |
| A layout whose field positions are set by regulation | [PrintSection], [FixedSection] and [PrintStyle] |
Verify
- Render the form and look at the page. A
Noneframe has no company block above the title, and aForbiddenform prints nothing as a draft. - For a
Forbiddenform, print a draft and check for a 409 withPrintDraftForbiddenException, not a PDF. - For a form with no table, export CSV. If the form has neither a table nor legacy sections, the CSV is empty, so
declare
[SupportedExportFormats(ExportFormat.Pdf)]on the document Dto. - For a form that uses sections, check each field's position against the prescribed layout, not just that the field appears.
See also
- Print forms: the roles, frames and the drafts policy.
- Document print forms: the
OnPrintAsyncside, and the VAT modes. - Letterhead and logo: the company block that a letterhead form adds, and the frame that leaves it out.