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.Runtime/Documents/DocumentService.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;
  • post / unpost / repost;
  • mark / unmark for deletion;
  • derivation actions and derivation execution;
  • relationship graph;
  • effects surface.

DocumentService is therefore the document boundary service, 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;
  • UI effect flags such as can-edit / can-post / can-unpost / can-repost / can-apply.

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.

DocumentService exposes:

  • GetDerivationActionsAsync(...)
  • DeriveAsync(...)

and delegates actual draft creation to IDocumentDerivationService.

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 is the public document application boundary.
  • DocumentService implements CRUD, post/unpost/repost, mark/unmark, derivation actions, derive, relationship graph, and 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.