Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/Apps/W1/PowerBIReports/App/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Power BI Report embeddings for Dynamics 365 Business Central

Provides the BC-side connector for Microsoft's Power BI apps -- out-of-the-box embedded reports covering Finance, Sales, Purchasing, Inventory, Manufacturing, and Projects. This AL app is one half of a two-part system: it exposes BC data via API pages/queries and hosts Power BI reports inline via embedded pages. The other half is a Power BI template app (installed separately from Marketplace) containing the semantic model and report definitions.

## Quick reference

- **ID range**: 36950--37049, 37055--37119
- **Namespace**: `Microsoft.Finance.PowerBIReports`, `Microsoft.Sales.PowerBIReports`, etc. (per domain)
- **Dependencies**: None (uses base application tables directly)

## How it works

The app is organized into 7 domain modules (Core, Finance, Sales, Purchasing, Inventory, Manufacturing, Projects) that all follow the same structure. Each module has an `APIs/` folder with Query and Page objects that expose BC data as OData endpoints, and an `Embedded/` folder with pages that host Power BI reports via the `PowerBIManagement` control add-in.

Data flows in a loop: BC exposes data through API queries/pages -> Power BI pulls data via its Business Central connector -> Power BI builds semantic models and reports -> those reports are embedded back inside BC via iframe. The API queries denormalize BC's normalized data into wide tables optimized for Power BI's columnar model. For example, the `PowerBI Dimension Sets` query pivots up to 8 dimension entries per set into a single flat row.

The app ships a setup table (`PowerBI Reports Setup`) that stores calendar configuration (fiscal year start, first day of week, UTC offset), date ranges for filtering, and per-domain report ID GUIDs. An assisted setup wizard guides users through initial configuration and maps embed pages to Power BI workspace/report pairs. Each domain also adds its own fields to the setup table via table extensions.

Date filtering is a key design concern -- Power BI's data volumes must be controlled. Each domain has a "filter helper" codeunit that generates date filter expressions, applied in the Query's `OnBeforeOpen` trigger. This ensures consistent date range filtering before data reaches Power BI.

## Structure

- `Core/` -- Setup tables, initialization codeunits, dimension caching job, permission sets, 14 role center extensions, and shared API endpoints (dimensions, customers, vendors, items, etc.)
- `Finance/` -- GL-specific APIs (account categories, budgets, income statement, balance sheet), 16 embedded report pages, account category mapping table
- `Sales/` -- Sales order/invoice/customer analytics queries, 22 embedded report pages
- `Purchasing/` -- Purchase order/vendor analytics queries, 19 embedded report pages
- `Inventory/` -- Item ledger, stock level, valuation queries, 18 embedded report pages (split into Inventory/ and Inventory Valuation/ subfolders)
- `Manufacturing/` -- Production order, capacity, routing queries, 17 embedded report pages
- `Projects/` -- Job planning, resource utilization queries, 9 embedded report pages
- `_Obsolete/` -- Deprecated SubscriptionBilling and Sustainability reports (do not use as patterns for new code)

## Documentation

- [docs/data-model.md](docs/data-model.md) -- Setup table, dimension caching, account categories, domain extensions
- [docs/business-logic.md](docs/business-logic.md) -- Installation, dimension caching, embedded page pattern, date filtering

## Things to know

- **Two-part system** -- this AL app alone does nothing visible. Users must also install the Power BI template app from Marketplace and run the "Connect to Power BI" assisted setup to map reports.
- **Every domain follows the same pattern** -- APIs/ folder for data exposure, Embedded/ folder for report containers, a filter helper codeunit, and a table extension on `PowerBI Reports Setup`. Understanding one domain means understanding all of them.
- **Embedded pages are thin containers** -- they just host a Power BI iframe via `PowerBIManagement` control add-in. The actual report logic, visuals, and measures live in the Power BI template app, not here.
- **Queries denormalize aggressively** -- API queries flatten BC's normalized data model (joins, left outer joins, calculated fields) into wide tables. This is intentional -- Power BI's columnar engine prefers wide denormalized tables over normalized joins.
- **Dimension set caching runs hourly** -- `UpdateDimSetEntries` codeunit flattens the M:M dimension set entries into an 8-column wide table (`PowerBI Flat Dim. Set Entry`). Uses `SystemModifiedAt` delta tracking to avoid full table scans.
- **Date filtering at query level** -- each domain's filter helper generates filter expressions applied in `OnBeforeOpen`. This prevents Power BI from pulling unbounded date ranges of transactional data.
- **Per-company setup** -- the Power BI template app must be installed per company. Multi-company environments need separate Power BI workspaces per company.
- **14 role centers extended** -- role center page extensions add Power BI embedded parts to Administrator, Finance Manager, Sales Manager, Purchasing Agent, Warehouse Manager, Production Planner, Project Manager, and more.
- **_Obsolete/ is not dead code** -- it contains deprecated objects with `ObsoleteState` attributes. They exist for backwards compatibility during upgrade and should not be used as patterns for new modules.
- **Account Category mapping (Finance only)** -- `FinanceInstallationHandler` populates 25 GL account hierarchy mappings (L1/L2/L3 Assets, Liabilities, etc.) on install, enabling chart-of-accounts rollup in Power BI.
101 changes: 101 additions & 0 deletions src/Apps/W1/PowerBIReports/App/docs/business-logic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Business logic

## Overview

The app's business logic falls into four areas: installation/initialization (setting up defaults and jobs), dimension set caching (an hourly background job), the embedded report page pattern (how reports are loaded), and date filtering (how data volume is controlled). There is no transactional business logic -- this app only reads data and manages configuration.

## Installation and initialization

The `InstallationHandler` codeunit runs on `OnInstallAppPerCompany` and calls `Initialization.SetupDefaultsForPowerBIReportsIfNotInitialized`. This procedure orchestrates all first-time setup:

1. **Guided experience** -- registers the "Connect to Power BI" assisted setup entry
2. **Setup singleton** -- creates the `PowerBI Reports Setup` record if it doesn't exist
3. **Working days** -- inserts 7 rows (Sun-Sat) with Mon-Fri marked as working
4. **Date ranges** -- pulls start/end dates from the `Accounting Period` table
5. **Dimension cache job** -- schedules the `UpdateDimSetEntries` codeunit as an hourly job queue entry
6. **Finance setup** -- calls `FinanceInstallationHandler` to populate account category mappings and close income source codes

The same initialization runs on `OnCompanyInitialize` (new company creation). The `OnClearCompanyConfig` and `OnAfterCreatedNewCompanyByCopyCompany` subscribers clear report IDs when copying companies -- you don't want a copied company pointing to the original's Power BI reports.

## Dimension set caching

```mermaid
flowchart TD
A[Job Queue fires hourly] --> B[UpdateDimSetEntries codeunit]
B --> C[Query: PowerBI Dimension Sets]
C --> D{New/changed since last run?}
D -->|Yes| E[Read dimension set entry rows]
E --> F[Pivot up to 8 dimensions into flat columns]
F --> G[Upsert PowerBI Flat Dim. Set Entry]
G --> H[Update last modified timestamp]
D -->|No| I[Skip]
```

The `UpdateDimSetEntries` codeunit reads dimension set entries via the `PowerBI Dimension Sets` query, which cross-joins against the 8 shortcut dimension codes from General Ledger Setup. For each dimension set, it creates or updates a single row in `PowerBI Flat Dim. Set Entry` with up to 8 dimension code/name pairs.

The codeunit tracks the last `SystemModifiedAt` value to process only delta changes. This is critical for performance -- BC environments can have millions of dimension set entries, and a full rescan would be prohibitively expensive.

## Embedded report page pattern

All 70+ embedded pages follow the same lifecycle:

```mermaid
flowchart TD
A[User opens embedded page] --> B[OnOpenPage]
B --> C[EnsureUserAcceptedPowerBITerms]
C --> D{PBI authenticated?}
D -->|No| E[Redirect to PBI setup]
D -->|Yes| F[GetReportIdAndEnsureSetup]
F --> G{Report ID configured?}
G -->|No| H[Launch assisted setup wizard]
G -->|Yes| I[ControlAddInReady fires]
I --> J[InitializeEmbeddedAddin]
J --> K[Power BI report loads in iframe]
K --> L[ReportLoaded: log telemetry]
K --> M[ErrorOccurred: show notification]
```

Each embedded page is a `UserControlHost` hosting the `PowerBIManagement` control add-in. The page stores a `ReportPageLbl` label containing the Power BI report page section identifier. The `PowerBIReportSetup` codeunit handles the shared logic:

- **EnsureUserAcceptedPowerBITerms** -- validates the user has authenticated with Power BI service
- **GetReportIdAndEnsureSetup** -- reads the report GUID from the setup table (each domain has its own field). If empty, launches the assisted setup wizard.
- **InitializeEmbeddedAddin** -- configures the control with auth token, locale, filter context, and report URL

The pages are intentionally thin -- they contain no business logic or data manipulation. All report intelligence lives in the Power BI template app.

## Date filtering

Each domain has a filter helper codeunit (e.g., `FinanceFilterHelper`, `SalesFilterHelper`) that generates BC filter expressions for date fields. These are applied in the Query's `OnBeforeOpen` trigger:

```
Query OnBeforeOpen:
→ FilterHelper.GenerateXxxReportDateFilter()
→ Returns filter string like "2024-01-01..2024-12-31"
→ SetFilter(DateField, FilterString)
```

The Sales module supports two date modes:
- **Absolute** -- explicit start/end dates from setup
- **Relative** -- uses `DateFormula` (e.g., `-30D`) to calculate a rolling window from today

This filtering is critical for performance. Without it, Power BI would attempt to pull the entire transaction history, which can be millions of rows in a production environment.

## Assisted setup wizard

The `PowerBIAssistedSetup` page (36951) guides users through:

1. **Calendar type** -- Standard (Gregorian), Fiscal, or Weekly (445/454/544 patterns)
2. **UTC offset** -- timezone for accurate date calculations
3. **Date table range** -- auto-populated from accounting periods, determines the date dimension in Power BI
4. **Working days** -- which days of the week are business days
5. **Report mapping** -- for each domain, the user selects a Power BI workspace, then selects a report within that workspace. The report GUID is stored in the setup table.

The `PowerBISelectionLookup` page provides the workspace/report picker, using the Power BI REST API (via the base application's Power BI integration framework) to list available workspaces and reports.

## Upgrade logic

The `PowerBIUpgrade` codeunit handles version migrations using upgrade tags to prevent re-execution:

- **Dimension data transfer** -- uses `DataTransfer` (bulk copy) to migrate pre-flattened dimension entries from old table structures
- **Setup initialization** -- ensures new setup fields have proper defaults after upgrade
- **Close income source code** -- migrates finance-specific source code configuration
54 changes: 54 additions & 0 deletions src/Apps/W1/PowerBIReports/App/docs/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Data model

## Overview

The app has a small number of actual tables (5) plus 7 domain-specific table extensions. The data model is focused on configuration and caching -- the actual business data comes from BC's existing tables, exposed through API queries. The key design insight is the dimension set caching table, which denormalizes BC's M:M dimension structure into a flat format optimized for Power BI.

## Setup and configuration

```mermaid
erDiagram
"PowerBI Reports Setup" ||--|| "Working Day" : "calendar config"
"PowerBI Reports Setup" ||..o{ "PowerBI Flat Dim. Set Entry" : "caching job populates"
"PowerBI Reports Setup" ||--o| "Account Category" : "finance config"
```

### PowerBI Reports Setup (36951)

Singleton configuration table (primary key is a fixed `Code[10]`). Stores three categories of settings:

- **Calendar configuration** -- fiscal year start date, first day of week, UTC offset, and date table range. These align with the Power BI semantic model's date table generation.
- **Dimension configuration** -- tracks when dimension set entries were last updated, and the date table start/end range based on accounting periods.
- **Report ID mappings** -- each domain module adds its own report ID field via table extension (Finance Report Id, Sales Report Id, etc.). These GUIDs link embed pages to specific Power BI reports.

The setup is extended by 7 table extensions (one per domain). Each extension adds domain-specific date range fields and a report ID GUID. For example, `SetupFinance.TableExt.al` adds `Finance Report Start Date`, `Finance Report End Date`, `Finance Report Id`, and `Sales Report Id`. The Sales extension adds `Item Sales Date Type` (absolute vs relative), date formulas for relative filtering, and `Sales Report Id`.

### Working Day (36952)

Simple 7-row table mapping day numbers (0=Sunday through 6=Saturday) to names and a `Working` boolean. Initialized by `InstallationHandler` with Monday-Friday as working days. Used by the Power BI semantic model to build a date table that excludes non-working days from calculations.

### PowerBI Flat Dim. Set Entry (36954)

Denormalized cache of BC's dimension set entries. Each row stores a Dimension Set ID plus up to 8 dimension code/name pairs (Dimension 1 Code, Dimension 1 Name, through Dimension 8). This flattens the M:M relationship between dimension sets and dimension values into a single wide row.

The table is populated by the `UpdateDimSetEntries` codeunit running as an hourly job queue entry. It uses `SystemModifiedAt` tracking to process only new/changed entries, avoiding full table scans. A secondary key on `SystemModifiedAt` supports this delta pattern.

This design exists because Power BI's tabular model performs much better with wide denormalized tables than with the normalized dimension set entry structure BC uses internally.

## Finance-specific tables

```mermaid
erDiagram
"Account Category" }o--|| "G/L Account Category" : "maps to (by Entry No.)"
"PBI C. Income St. Source Code" }o--|| "Source Code" : "references"
```

### Account Category (36953)

Maps Power BI's GL account hierarchy (25 values in the `Account Category Type` enum -- L1 Assets, L1 Liabilities, L2 Current Assets, L3 Inventory, etc.) to BC's `G/L Account Category` records. This enables chart-of-accounts rollup in Power BI reports that matches the BC account structure.

The `FinanceInstallationHandler` populates these mappings on install by walking the G/L Account Categories table and matching indentation levels to L1/L2/L3 hierarchy tiers.

### PBI C. Income St. Source Code (36955)

Stores source codes used in close income statement batch jobs. Used to filter out closing entries from income statement reports in Power BI (you typically don't want year-end closing entries appearing in monthly P&L views).
Loading