Appearance
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
IDocumentServiceandDocumentService; - the reporting subsystem centered around
ReportEngine,ReportExecutionPlanner, andReportSheetBuilder.
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:
- Document Subsystem Dense Source Map
- Reporting Subsystem Dense Source Map
- Runtime Execution Core Dense Source Map
Verified anchor set used by this chapter
This chapter is based on these verified files:
NGB.Application.Abstractions/Services/IDocumentService.csNGB.Runtime/Documents/DocumentService.csNGB.Persistence/Documents/IDocumentRepository.csNGB.Persistence/Documents/Universal/IDocumentReader.csNGB.Persistence/Documents/Universal/IDocumentWriter.csNGB.Persistence/Documents/Universal/IDocumentPartsReader.csNGB.Persistence/Documents/Universal/IDocumentPartsWriter.csNGB.Runtime/Documents/Derivations/IDocumentDerivationService.csNGB.Runtime/Reporting/ReportEngine.csNGB.Runtime/Reporting/ReportExecutionPlanner.csNGB.Runtime/Reporting/ReportSheetBuilder.csNGB.Runtime/Reporting/ReportDefinitionRuntimeModel.csNGB.Runtime/Reporting/ReportDatasetDefinition.csNGB.Runtime/Reporting/ReportDatasetFieldDefinition.csNGB.PostgreSql/Reporting/IPostgresReportDatasetSource.csNGB.PostgreSql/Reporting/PostgresReportDatasetCatalog.csNGB.PostgreSql/Reporting/PostgresReportSqlBuilder.csNGB.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_displayis 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}anddoc_{type_code}__{part}.
This registry-level identity is what lifecycle operations lock and transition.
Typed head and parts storage
DocumentService uses universal persistence interfaces:
IDocumentReaderIDocumentWriterIDocumentPartsReaderIDocumentPartsWriter
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:
IDocumentServiceis the public document application boundary.DocumentServiceimplements CRUD, post/unpost/repost, mark/unmark, derivation actions, derive, relationship graph, and effects.DocumentServiceusesIDocumentRepositoryplus universal head/part readers and writers.IDocumentRepositoryrepresents the common document registry and explicitly distinguishes it from typed head/part tables.PostgresReportSqlBuilderadds interactive support fields when document/account display fields are selected.PostgresReportDatasetExecutormaterializes SQL rows into dictionaries.ReportEngineenriches document display fields throughEnrichInteractiveFieldsAsync(...).ReportSheetBuilderbuilds the final rendered sheet and uses action-aware helpers.ReportDefinitionRuntimeModelandReportDatasetDefinitionare 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.