Skip to content

Document + Reporting Cross-Cutting Integration ​

How to read this page ​

This chapter focuses on the cross-cutting runtime boundary between two already-documented areas:

  • the document subsystem centered around IDocumentService and DocumentService;
  • the reporting subsystem centered around ReportEngine, ReportExecutionPlanner, and ReportSheetBuilder.

This page is intentionally narrow. It documents where those subsystems meet:

  • interactive links from reports to documents;
  • document display enrichment inside reporting;
  • document graph / effects surfaces that complement reports rather than being implemented by reports;
  • the way document identity and display flow through runtime and PostgreSQL reporting execution.

For the full dense chapters, see:

Verified anchor set used by this chapter ​

This chapter is based on these verified files:

  • NGB.Application.Abstractions/Services/IDocumentService.cs
  • NGB.Application.Abstractions/Services/DocumentActions.cs
  • NGB.Runtime/Documents/DocumentService.cs
  • NGB.Runtime/Documents/Actions/DocumentActionQueryService.cs
  • NGB.Runtime/Documents/Actions/DocumentActionDispatcher.cs
  • NGB.Persistence/Documents/IDocumentRepository.cs
  • NGB.Persistence/Documents/Universal/IDocumentReader.cs
  • NGB.Persistence/Documents/Universal/IDocumentWriter.cs
  • NGB.Persistence/Documents/Universal/IDocumentPartsReader.cs
  • NGB.Persistence/Documents/Universal/IDocumentPartsWriter.cs
  • NGB.Runtime/Documents/Derivations/IDocumentDerivationService.cs
  • NGB.Runtime/Reporting/ReportEngine.cs
  • NGB.Runtime/Reporting/ReportExecutionPlanner.cs
  • NGB.Runtime/Reporting/ReportSheetBuilder.cs
  • NGB.Runtime/Reporting/ReportDefinitionRuntimeModel.cs
  • NGB.Runtime/Reporting/ReportDatasetDefinition.cs
  • NGB.Runtime/Reporting/ReportDatasetFieldDefinition.cs
  • NGB.PostgreSql/Reporting/IPostgresReportDatasetSource.cs
  • NGB.PostgreSql/Reporting/PostgresReportDatasetCatalog.cs
  • NGB.PostgreSql/Reporting/PostgresReportSqlBuilder.cs
  • NGB.PostgreSql/Reporting/PostgresReportDatasetExecutor.cs

Cross-cutting responsibility split ​

The verified code shows a clean separation.

Documents own document lifecycle and document-specific surfaces ​

IDocumentService exposes the document-facing surface:

  • draft CRUD;
  • trusted/internal derivation execution;
  • relationship graph;
  • effects surface.

Interactive lifecycle, derivation, and vertical actions use the separate IDocumentActionQueryService and IDocumentActionDispatcher boundaries. DocumentService remains the generic CRUD/graph/effects and trusted-derivation boundary, not just a CRUD helper.

Reporting owns analytical composition and rendered sheet output ​

ReportEngine accepts a report code plus execution request, resolves the report definition, validates layout, builds a plan, executes it, enriches interactive document/account fields when needed, and produces a rendered sheet or export sheet.

ReportExecutionPlanner converts a definition + request into a runtime query plan.

ReportSheetBuilder converts a materialized ReportDataPage into a UI-facing sheet with rows, columns, header rows, row hierarchy, subtotal rows, pivot headers, and action-ready cells.

The meeting point is identity, display, and interaction ​

The document subsystem and reporting subsystem do not merge into one giant service.

Instead, they meet at a smaller contract surface:

  • document ids flowing through report rows;
  • document display values being resolved for report cells;
  • action-ready report cells pointing back to document or account experiences;
  • separate document graph/effects endpoints complementing analytical reports.

That separation is important. Reports remain analytical. Document services remain transactional/document-centric.

Cross-cutting flow 1: report rows that point to documents ​

The verified reporting pipeline shows the following pattern.

Step 1. Dataset selection includes document_display ​

PostgresReportSqlBuilder contains explicit logic for interactive support fields.

When a request selects document_display as a row group, column group, or detail field, the SQL builder also appends a support field for the underlying document id.

The builder does this through AppendInteractiveSupportFields(...), which checks selected fields and, when needed, projects a support column tied to ReportInteractiveSupport.SupportDocumentId.

Step 2. PostgreSQL execution returns raw support ids ​

PostgresReportDatasetExecutor executes the SQL generated by the builder and materializes rows into dictionaries.

At this stage, the result set is still data-oriented. It contains projected aliases and support columns. It is not yet shaped into final UI cells.

Step 3. ReportEngine enriches document display values ​

ReportEngine contains a dedicated EnrichInteractiveFieldsAsync(...) method.

That method:

  • detects whether document_display is present in selected outputs;
  • scans returned rows for document ids;
  • resolves references through the optional IDocumentDisplayReader;
  • overwrites display fields with resolved document display values;
  • writes support type information back into row values.

This is the clearest verified cross-cutting point between reporting and document-oriented read models.

Step 4. ReportSheetBuilder turns enriched rows into UI-facing cells ​

ReportSheetBuilder builds final sheet rows and columns. It constructs action-aware rendering through ReportComposableCellActionResolver, ReportCellFormatter, subtotal builders, pivot header builders, and row hierarchy helpers.

The key architectural point is:

  • SQL builder ensures the support identity is present;
  • engine enrichment resolves display/identity;
  • sheet builder turns enriched row data into a rendered sheet with interaction-ready structure.

Cross-cutting flow 2: reports do not replace document graph/effects ​

The verified IDocumentService contract and DocumentService implementation show that NGB deliberately keeps several document-centric surfaces outside reporting.

Relationship graph ​

IDocumentService exposes GetRelationshipGraphAsync(...).

Inside DocumentService, this method:

  • loads the root document head;
  • requests a graph from a relationship graph service;
  • bulk loads head rows across multiple document types through IDocumentReader.GetHeadRowsByIdsAcrossTypesAsync(...);
  • builds graph nodes and edges.

This is explicitly a single-request document flow surface, not a report execution.

Effects surface ​

IDocumentService exposes GetEffectsAsync(...).

In DocumentService, effects are treated as a document-centric surface that can include:

  • accounting entries;
  • operational register movements;
  • reference register writes.

Executable operations and disabled reasons are returned separately by editor state. They are not part of DocumentEffectsDto in platform 2.0.

This is again not implemented as a report. It is a document explanation / effects surface.

Why this matters ​

The verified source strongly suggests a deliberate product boundary:

  • use reports for analytical projections, grouping, pivoting, subtotals, and interactive navigation;
  • use document graph/effects surfaces for explainability of one specific document.

That boundary prevents analytical pages from becoming overloaded with document explanation logic.

Cross-cutting flow 3: derivation and reporting stay adjacent but separate ​

IDocumentService and IDocumentDerivationService show that derivation is a document-creation feature.

IDocumentActionQueryService.GetEditorStateAsync(...) exposes the evaluated public action list. IDocumentActionDispatcher executes selected derivation actions, and the trusted/internal DocumentService.DeriveAsync(...) port delegates actual draft creation to IDocumentDerivationService. These are separate services with separate responsibilities.

This is close to reporting from a user-experience perspective because reports often lead users to source documents or follow-up actions. But the verified code keeps derivation in the document subsystem:

  • reports may navigate to documents;
  • documents may offer derivation actions;
  • the report runtime itself does not create derived documents.

That separation is clean and production-friendly.

Cross-cutting document identity model ​

The verified sources consistently show a two-level model:

Registry-level document identity ​

IDocumentRepository owns the shared documents registry abstraction.

Its own XML/doc comments describe the important rule:

  • the repository stores only the common document header;
  • per-type data belongs in typed tables such as doc_{type_code} and doc_{type_code}__{part}.

This registry-level identity is what lifecycle operations lock and transition.

Typed head and parts storage ​

DocumentService uses universal persistence interfaces:

  • IDocumentReader
  • IDocumentWriter
  • IDocumentPartsReader
  • IDocumentPartsWriter

Those interfaces operate on typed head descriptors and part tables.

This means reports and graphs can rely on stable document identity while document-specific read/write shape remains metadata-driven.

Reporting side implication ​

Because the report pipeline can receive stable document ids plus a resolved display string, reports can remain generic and still link back to typed document experiences.

Cross-cutting display model ​

The verified code shows a notable split between identity and display.

In document runtime ​

DocumentService builds document DTOs from typed head rows and part rows, then optionally enriches document items through a reference payload enricher.

This is where a document page gets its full document payload, status, display, and related UI-facing data.

In reporting runtime ​

ReportEngine performs a lighter document-oriented enrichment:

  • not full document DTO hydration;
  • just enough reference resolution to replace display-oriented fields and support interaction.

That is an important efficiency boundary:

  • document pages use the document subsystem;
  • reports use targeted enrichment only.

What ReportDefinitionRuntimeModel contributes to the boundary ​

ReportDefinitionRuntimeModel wraps ReportDefinitionDto and exposes:

  • normalized report code;
  • capabilities;
  • default layout;
  • optional ReportDatasetDefinition.

This is important for the cross-cutting story because report interactivity does not start at SQL generation. It starts at the runtime definition model:

  • the layout decides which fields are selected;
  • selected fields determine whether interactive support ids must be added;
  • the engine then decides whether those rows need display enrichment.

In other words, the document/reporting boundary is driven from the runtime report definition layer, not bolted on after the fact.

What ReportDatasetDefinition contributes to the boundary ​

ReportDatasetDefinition validates and normalizes dataset fields and measures into runtime dictionaries.

It tracks which fields are:

  • filterable;
  • groupable;
  • sortable;
  • selectable;
  • time-grain-capable.

That matters because document-facing interactivity in reports is only possible when the dataset definition allows the relevant fields to participate in selection/grouping/detail layouts.

The SQL builder then consumes the requested selections and adds support fields only when those document-facing fields are actually selected.

Architectural synthesis ​

Based on the verified source set, the platform uses the following cross-cutting pattern.

1. Document runtime owns truth about one document ​

DocumentService handles:

  • lifecycle;
  • derivation;
  • graph;
  • effects;
  • document payload assembly.

2. Reporting runtime owns truth about one analytical sheet ​

ReportEngine, ReportExecutionPlanner, ReportSheetBuilder, and PostgreSQL reporting classes handle:

  • definition normalization;
  • layout/planning;
  • dataset execution;
  • sheet rendering.

3. The bridge is small and explicit ​

The bridge is not “reports calling document services everywhere”.

It is a narrow, production-friendly bridge:

  • support ids in report SQL;
  • optional display resolution in ReportEngine;
  • action-aware cells in ReportSheetBuilder;
  • document graph/effects exposed separately through document endpoints.

That is a strong architecture for long-lived ERP-style systems because it keeps analytical and transactional concerns adjacent without collapsing them.

What is verified vs inferred here ​

Verified directly from source ​

Verified directly from the listed anchor files:

  • IDocumentService owns the generic metadata/CRUD/graph/effects and trusted-derivation application surface; trusted lifecycle operations use IDocumentSystemLifecycleService.
  • IDocumentActionQueryService and IDocumentActionDispatcher own interactive action discovery and execution in platform 2.0.
  • DocumentService implements generic CRUD, trusted lifecycle/derivation ports, relationship graph, and accounting/register effects.
  • DocumentService uses IDocumentRepository plus universal head/part readers and writers.
  • IDocumentRepository represents the common document registry and explicitly distinguishes it from typed head/part tables.
  • PostgresReportSqlBuilder adds interactive support fields when document/account display fields are selected.
  • PostgresReportDatasetExecutor materializes SQL rows into dictionaries.
  • ReportEngine enriches document display fields through EnrichInteractiveFieldsAsync(...).
  • ReportSheetBuilder builds the final rendered sheet and uses action-aware helpers.
  • ReportDefinitionRuntimeModel and ReportDatasetDefinition are the runtime layer that normalize report definitions and datasets.

Architecture synthesis ​

This page also contains synthesis based on how those verified anchors fit together:

  • reports are analytical and document surfaces are explanatory;
  • graph/effects are intentionally kept outside report execution;
  • the bridge between the two subsystems is identity/display/action metadata rather than full document hydration inside reporting.

These are strong inferences from the verified code shape, but they are still architectural interpretation rather than direct quotes from a single source file.

Released under the Apache License 2.0.