Skip to content

Add a Canonical Report ​

This guide describes the recommended workflow for adding a canonical report.

The example uses an American-market report: Customer Statement.

Canonical reports are the right choice when the report needs specialized business logic, custom paging semantics, or a purpose-built read model that should not be expressed as a generic dataset query.

Verified: runtime reporting anchorsTemplate: implementation workflow

How to read this page. Use the verified anchors to understand the shared reporting boundary first, then apply the workflow as a recommended way to implement a domain-specific canonical report.

Verified anchors behind this guide ​

  • NGB.Runtime/Reporting/ReportEngine.cs
  • NGB.Runtime/Reporting/ReportExecutionPlanner.cs
  • NGB.PropertyManagement.Api/Program.cs

1. Recognize the canonical use case ​

Choose a canonical report when one or more of the following is true:

  • the report needs opening/closing balance semantics that are awkward in a generic dataset;
  • the report needs domain-specific row shaping;
  • the report needs custom cursor or running-balance behavior;
  • the report has a specialized read path that should stay hand-controlled.

The verified planner source shows an important distinction: when no dataset is involved, the planner builds canonical predicates from the report filter metadata. That is the architectural signal that canonical reports are still inside the shared reporting pipeline, but not inside the composable dataset path.

2. Define the report contract first ​

Template guidance

Create the report definition for something like ar.customer_statement.

Define:

  • report code;
  • title and presentation metadata;
  • filters such as:
    • customer_id
    • from_date
    • to_date
    • currency_code
  • paging policy;
  • typed drilldown or interaction expectations.

ReportQueryService and ReportEngine resolve the definition and validate the effective request before dispatch, so the definition is the front door of the report.

3. Implement the canonical execution path ​

Template guidance grounded by verified runtime flow

Register canonical executors in the shared reporting pipeline. ReportEngine dispatches to specialized accounting page executors where supported and otherwise uses IReportPlanExecutor. ReportQueryService wraps the execution in a read session and protects continuation cursors.

Recommended structure:

  • a canonical report service or executor that reads the business source data;
  • a mapper that turns source rows into report rows or a ready sheet model;
  • interaction metadata for documents, accounts, customers, or periods;
  • deterministic paging semantics.

4. Keep paging semantics explicit ​

Canonical reports often fail when paging is bolted on too late.

The interactive HTTP endpoint requires bounded cursor pages; it rejects nonzero offsets and disablePaging=true. Define deterministic ordering, a continuation cursor, hasMore, and any opening/running-balance semantics. Treat a nullable total as unavailable, not as zero.

Complete downloads use IReportDownloadService and streaming executors rather than the bounded ExecuteExportSheetAsync helper. Test the page and download paths separately. See Report Browsing and Direct Downloads.

5. Prefer purpose-built reads over generic overreach ​

A canonical report exists because the business semantics matter more than generic reuse.

For a Customer Statement, common purpose-built needs include:

  • opening balance before the selected period;
  • chronological running balance;
  • grouping by invoice/payment/apply event types;
  • direct drilldowns to source documents.

If these semantics start becoming awkward in a dataset definition, keep the report canonical.

6. Surface interactions through the shared report model ​

Even for canonical reports, the output should still fit the platform reporting surface:

  • sheet rows and columns;
  • typed document or account interactions;
  • totals and subtotals where appropriate;
  • exportability.

The verified runtime engine shows that the final response is still normalized through the shared report response path.

7. Compose it through the host, not through a one-off route ​

Verified anchor: NGB.PropertyManagement.Api/Program.cs

A new canonical report should appear because the vertical module is composed into the host, not because the host gained bespoke business logic.

8. Test matrix for a canonical report ​

At minimum, cover:

  • definition resolution;
  • filter validation;
  • empty-result behavior;
  • paging semantics;
  • opening-balance correctness when applicable;
  • row ordering stability;
  • drilldown targets;
  • export sheet behavior.

9. Production checklist ​

Before shipping a canonical report, confirm:

  • the business semantics are explicit and tested;
  • pagination strategy is deliberate;
  • totals/running balances are deterministic;
  • drilldown behavior is stable;
  • the report still participates in the shared reporting UX.

Released under the Apache License 2.0.