Paginated Report Migration

Context

The company used BusinessObjects (BOXI) for static PDFs and Power BI for dashboards. BOXI scheduling worked, but metric logic was inconsistent across reports and hard to audit.
Leadership asked to consolidate on Power BI. I took ownership of the paginated layer, reverse engineered BOXI universes, designed reusable SQL datasets, and matched figures exactly.

100+
Reports rebuilt
over 1 year
5
Critical DW issues fixed
impacting P&L
6x
Faster changes
weeks to days

Goals

  1. Deliver the same PDF and Excel outputs from Power BI Paginated.
  2. Replace fragmented BOXI logic with a governed, reusable SQL layer.
  3. Standardise templates and parameters for quicker change control.
  4. Validate totals against BOXI and secure Finance sign off.

Challenges

Approach

1. Audit and prioritisation

I requested a monthly export of all BOXI schedules and recipients. From that I built two working indexes:

These indexes became the plan of record for migration and communication.

2. Rationalising logic

Each BOXI report was reviewed and categorised:

I created a small canonical SQL layer using views and CTEs for shared business metrics. This replaced BOXI variables and removed one off definitions. I also moved away from hard coded variables such as warehouse_1, warehouse_2 by using dynamic groups in Report Builder, so new values appear automatically.

3. Clean SQL pattern

Aggregate the fact first by keys and date, then join dimensions for descriptions. This keeps queries readable and index friendly.

WITH fact_summary AS (
  SELECT
    s.dim_customer_key,
    s.dim_salesperson_key,
    d.year,
    d.month,
    SUM(s.cases_sold) AS cases_sold
  FROM fact_sales s
  JOIN dim_date d
    ON s.dim_order_date_key = d.dim_date_key
  WHERE d.year = @year
  GROUP BY
    s.dim_customer_key,
    s.dim_salesperson_key,
    d.year,
    d.month
)
SELECT
  c.customer_number,
  c.customer_name,
  u.full_name AS salesperson_name,
  f.year,
  f.month,
  f.cases_sold
FROM fact_summary f
JOIN dim_customer c
  ON f.dim_customer_key = c.dim_customer_key
JOIN dim_user u
  ON f.dim_salesperson_key = u.dim_user_key;

Why this works well:

4. Parameterised reports

Legacy reports were often fixed to a period such as this month versus last month.
Each paginated report was rebuilt with parameters so a user can pick any period. SQL calculates the comparison period and headers render with real month and year, for example Sales 2024 Jan and Sales 2023 Dec.
This reduced duplicate schedules and improved self service.

5. Validation with Python

I wrote a small Pandas utility to compare BOXI and PBI outputs by shape, columns, and totals. Finance signed off each report once the diff was empty.

from mylibrary import check_reports
import pandas as pd

pbi = pd.read_excel("pbi_report.xlsx", index=["customer_number","year","month"])
boxi = pd.read_excel("boxi_report.xlsx", index=["customer_number","year","month"])

result = check_reports(pbi, boxi)
result.summary()  # shows shape, column, and total differences

6. Dynamic scheduling

BOXI schedules were static and hard to maintain. In Power BI I introduced dynamic per recipient schedules.

Results

Lessons learned

Conclusion

The migration replaced a fragile BOXI estate with a governed, reusable reporting layer in Power BI.
By standardising the logic, validating results, and automating delivery, reporting moved from reactive maintenance to a predictable and auditable process that the business can trust.