Skip to Content

Accounts Performance Dashboard

Odoo accounts dashboard in Power BI | KoderXpert

Financial reporting build · Power BI on live Odoo accounting data

They did not need a new accounting system. They needed to see the one they had.

the data was complete and accurate, and effectively unreadable

A manufacturing business running finance on Odoo, with multiple legal entities, a distributed sales team and a large base of trade partners buying on credit. Every invoice, receipt, refund and journal entry already existed. KoderXpert put a two page analytics layer above the ledger without touching the ERP.

Line art of an Odoo ledger becoming a decision tool: invoices stacked into an ageing ladder, a collection clock counting days sales outstanding, and a partner exposure list, for a finance audience 0-30 31-60 61-90 38.8 days
record, read, collect ✨
one version!11governed measures, each defined once with finance
38.8days DSO, measured for the first time
3ageing bands turning one overdue total into a call list
0changes made inside Odoo
Client
Manufacturer, name withheld
Domain
Financial & receivables analytics
Source
Odoo accounting, invoicing, payments
Platform
Microsoft Power BI
Headline result
Month end rebuild replaced by a refresh

01 · Overview

The numbers existed. Getting to them cost a person and a day.

  • A manufacturing business running finance on Odoo, across multiple legal entities, selling on credit into a large trade partner base.
  • In manufacturing, cash is committed long before it is collected. Material is bought, production is run, goods are shipped, and only then does an invoice begin its wait. Working capital lives in that gap, and the gap is made of receivables.
  • KoderXpert modelled the Odoo accounting tables into a star schema and delivered two pages: profit and loss performance, and receivables and collections. The connection is read only, so nothing inside Odoo changed.
they did not need a new system, they needed to see this one
five record types in, one set of definitions out ✨

02 · The challenge

Reporting was a manual export and rebuild cycle.

Everything below follows from one thing: an operational ledger is built to record a transaction correctly, not to answer an executive question quickly.

The same report, rebuilt every month

Figures were pulled out of Odoo and reassembled in spreadsheets each period, by hand, by someone whose job was supposed to be analysis.

The cost → a person and a day, every month

Receivables had no live view

Outstanding balances existed in the ledger but there was no ageing view and no per partner exposure report anyone could simply open.

The cost → collections worked from a ledger

Collections performance was invisible

Nobody could state the collection rate or days sales outstanding without building the calculation from scratch, so nobody stated them.

The cost → a cash cycle nobody was measuring

Management asked, finance produced

Every question from leadership became a request, a queue and a wait, so fewer questions got asked and decisions were made on older numbers.

The cost → curiosity rationed by capacity

Move types are not intuitive

Invoices, refunds and receipts sit in one journal structure. Reading revenue out of it requires knowing exactly what to include, and two people rarely included the same things.

The cost → no number was trusted

Multi entity, without double counting

Figures had to be readable per company and consolidated at once, which a spreadsheet built per entity cannot do without someone adding it up again.

The cost → consolidation by hand, monthly

the hard part was never the chart, it was agreeing what a number means

03 · Before vs after

Same ledger. A refresh instead of a rebuild.

Left is what financial reporting cost before. Right is what two modelled pages replaced it with.

BeforeAfter
rebuilding the report
  • Monthly export into spreadsheets, rebuilt by hand each period
  • No ageing view and no per partner exposure report
  • Collection rate and DSO calculated from scratch, or not at all
  • Every leadership question queued behind finance
  • Margin, DSO and collection rate meaning different things to different people
  • Consolidation across entities done manually
a ledger, not a view
who to call, in order
opening it
  • A scheduled refresh instead of a monthly rebuild
  • AR ageing in three bands with per partner exposure by quarter
  • Collection rate and a measured 38.8 day DSO as standing measures
  • Leadership self serving from the dashboard
  • Eleven governed measures, agreed with finance and coded once
  • Per company and consolidated, from the same model

drag the orange handle, or use the arrow keys ✨

04 · Goals

What the build had to achieve

Six goals agreed with finance before any DAX, because a dashboard is only as trustworthy as its definitions.

One version of every number. Definitions agreed once, encoded in DAX and reused everywhere, so the figure on the screen is the figure in the meeting.

Self serve, not request driven. Leadership opens the dashboard instead of queuing a request, so more questions get asked and earlier.

Working capital under control. Ageing, partner exposure and DSO visible without a rebuild, because cash decisions cannot wait for month end.

Sliceable by every dimension. Month, year, company, salesperson and move type, from one model rather than one report per cut.

Zero disruption to Odoo. Read from the ledger and change nothing inside it: no customisation, no upgrade risk, no performance cost.

Fast enough to be used. A dashboard that opens in seconds gets opened; one that takes a minute gets replaced by a spreadsheet.

05 · What we delivered

Two pages, built around the questions people actually ask

Profitability and cash are different questions with different readers, which is why they are separate pages rather than one crowded screen.

Block 01

Executive KPI strip

Total revenue, total expenses, profit margin, collection rate, year on year growth and GST collected. The six numbers that frame every other question, on one line.

the frame, before the detail
Block 02

Revenue against expenses by month

A twelve month clustered view that exposes seasonality and the months where cost outran income, visible instantly rather than derived at quarter end.

the months that need explaining
Block 03

Quarterly roll up and P and L split

The same comparison at quarter grain for board cycles, with revenue, expense and profit as proportions so three absolute figures become a shape people remember.

a shape, not three numbers
Block 04

AR, AP and DSO

What is owed to the business, what the business owes, and how long cash actually takes to arrive, as standing measures rather than one off calculations.

38.8 days, finally measured
Block 05

AR ageing buckets

Zero to thirty, thirty one to sixty and sixty one to ninety day bands, so overdue debt is grouped by risk rather than read as one undifferentiated number.

a prioritised call list
Block 06

Partner exposure matrix

Every trade partner ranked by exposure, quarter by quarter, with a consolidated total. The collections team's working list rather than a ledger to read.

who to call first
Interactive 01

The ageing ladder and the cash it implies

one overdue total, three very different risks

Receivables split into what is current and what is overdue. Move the slider to change the collected share and watch the outstanding balance and the implied DSO move with it. The build put a measured 38.8 days in front of a business that had never had one, and you cannot shorten a cash cycle you are not measuring.

Interactive meter linking collection rate to outstanding receivables and days sales outstanding Invoiced 100% of the book Collected -- Outstanding -- Implied DSO -- cash visibility, not just profit
--Outstanding share
--Collection rate
Reading

At the measured collection rate, DSO stands at 38.8 days.

Ageing bands turn one overdue total into a prioritised call list
68%

06 · Functional deep dive

How each number gets its meaning

Four decisions that turned an operational ledger into figures finance will defend in a board meeting.

Reading revenue out of a journal structure

Invoices, refunds, receipts and entries share one structure in Odoo. Revenue is not a column; it is a decision about which move types count.

  • Move types classified once, with finance, and encoded in the model
  • Refunds netted against revenue rather than reported separately
  • Amount by move type kept on the page so the composition stays auditable

DSO as a standing measure

Days sales outstanding is trivial to define and easy to define differently. The version that matters is the one finance will defend.

  • Definition agreed and documented before it was coded
  • Computed from receivables and credit sales over a rolling window
  • The same measure feeds the KPI strip and the receivables page

Ageing bands that group by risk

One overdue total tells a collections team nothing. Grouping by age turns the same balance into an ordered list of calls.

  • Zero to thirty, thirty one to sixty, sixty one to ninety day bands
  • Bands computed from invoice due date, not posting date
  • Partner exposure cross tabulated by quarter for the working list

Multi entity without double counting

Figures had to read per company and consolidated at once, from one model, without the consolidation being a second calculation.

  • Company carried as a dimension rather than as separate models
  • Intercompany moves identified so consolidation does not inflate
  • Every measure tested at both entity and consolidated grain
Interactive 02

A collections list, not a ledger

the team does not need the ledger, it needs who to call first

Trade partners ranked by total exposure, which is where most collections conversations start. Switch to the amount past ninety days and the order changes, because a large balance inside terms is not the same problem as a smaller one that has gone stale. Sort by days overdue to get the call list.

Interactive partner exposure table sortable by exposure, overdue amount and days overdue Partner exposure sorted by total exposure
--Top of the current ranking
7Partners in this sample
What the sort changes

Ranked by total exposure, which is the list finance usually starts from.

Ageing grouped by risk rather than read as one number
Rank on

07 · How we built it

Six stages from ledger to dashboard

The workshop came first. Everything downstream depended on the definitions agreed in it.

01

Requirement and metric workshop week one

Agreed with finance exactly what collection rate, DSO and profit margin mean, in writing, before a line of DAX was written.

02

Data extraction and modelling week two

Pulled the Odoo accounting tables and shaped them into a clean analytical star schema with date and partner dimensions.

03

DAX measure development about a week

Built and unit checked every measure against the ledger, including the ageing bands and the collection rate over a rolling window.

04

Dashboard design four days

Two pages, one KPI strip each, laid out around the questions people actually ask rather than around the shape of the data.

05

Validation against Odoo four days

Reconciled every headline figure back to the source system, each within tolerance or explained, with the reconciliation kept in the report.

06

Deployment, training and tuning final week

Scheduled refresh and publication, then walked finance and leadership through slicers, drill paths and definitions, and tuned against real usage.

08 · Engineering notes

How the ERP stays untouched

A reporting layer that requires changes to a live accounting system does not get approved, and should not.

A read only path out of Odoo

Power BI connects through a dedicated role with SELECT rights, scoped to the accounting schema. No module is installed, no field added and nothing writes back.

  • Reporting logic held in versioned views, not in the report file
  • Column renames handled in one place rather than across visuals
  • Gateway used where the instance is self hosted

A measure library finance signed off

Eleven governed measures, each defined once and reused, which is what makes the number on the screen the number in the meeting.

  • Base measures for arithmetic, display measures for formatting
  • DIVIDE with a blank fallback so an empty partner reads blank
  • Definitions document handed over with the model

Access control

Financial detail is sensitive and the partner table names customers, so the report was built to be shared without exposing everything to everyone.

  • Row level security by company for entity managers
  • Salary and margin detail restricted to the finance role
  • Aggregate views for anyone outside finance

Refresh and reconciliation

Scheduled refresh with a reconciliation view that compares headline figures back to Odoo every time it runs, so drift surfaces as a visible variance.

  • Revenue, AR and AP checked on each refresh
  • Tolerances agreed with finance rather than assumed
  • Failures visible on the page, not silent
Interactive 03

Revenue against expenses, month by month

two totals tell you little, twelve pairs tell you when

Twelve months of revenue, then the same axis redrawn as expenses. The months where cost outran income are visible instantly rather than derived at quarter end, which is the whole reason the P and L page leads with this chart.

Interactive monthly chart comparing revenue and expenses Revenue by month twelve months, financial year seasonality, made visible
--'Select a month'
2Months where cost outran income
Reading

Revenue by month, in one clustered view rather than a quarterly summary.

Definitions agreed once, encoded in DAX, reused everywhere
Series

09 · The impact

What changed for finance

All of it from a ledger that was already complete and already accurate.

11governed measures, identical everywhere they appear
38.8days DSO, standing in front of the business for the first time
3ageing bands turning one total into a prioritised call list
2pages replacing a monthly export and rebuild cycle
  • Finance stopped rebuilding the same report; the month end rebuild became a refresh
  • Receivables became actionable, with a ranked list to work rather than a ledger to read
  • Leadership self serves, so questions get asked earlier and answered on current data
  • Margin, DSO and collection rate now mean the same thing in every meeting

the ledger was always right, now it is legible

10 · The stack

What this platform runs on

Six layers, none of them inside Odoo.

Source
OdooPostgreSQLInvoicesReceiptsRefundsJournal entriesPartner master
Access
Read only database roleVersioned viewsOn premise gateway
Model
Star schemaDate and partner dimensionsCompany as a dimension
Measures
DAXMarginYoY growthCollection rateDSOAR ageing
Report
Microsoft Power BITwo linked pagesKPI strip per pageSlicers by company and month
Operations
Scheduled refreshReconciliation to OdooRow level security
what the build came down to
Odoo is excellent at recording a transaction and poor at cross period, cross entity analysis. Rather than bend the ERP into a reporting tool, we left it to do its job and put a purpose built analytics layer above it: no customisation, no upgrade risk, no performance cost.
The engagement outcome, manufacturing client
Client name withheld at the client's request

11 · FAQ

Questions finance teams ask before commissioning a build like this

No. The connection is read only through a dedicated database role with SELECT rights, and any reporting logic sits in versioned views alongside your schema. No module is installed, no field is added and nothing writes back. That is deliberate: it keeps the ERP clean and the upgrade path clear.

Odoo records transactions correctly and reports well on one model at a time. This needs cross period, cross entity analysis over invoices, receipts, refunds and the partner master at once, with measures that exist nowhere as stored fields. That is modelling work, and a BI layer is the right place for it.

From receivables against credit sales over a rolling window, with the exact definition agreed with finance and documented before it was coded. DSO is easy to define and easy to define differently, so the version that matters is the one your finance team will defend in a board meeting.

Yes, from one model. Company is carried as a dimension rather than as separate models, intercompany moves are identified so consolidation does not inflate, and every measure was tested at both entity and consolidated grain before sign off.

Daily suits most finance reporting, scheduled around when your postings settle. Receivables do not change minute to minute, and a refresh that completes before the morning is worth more than a live connection loading your production database.

Row level security restricts entity managers to their own company, and margin and salary detail sit in a separate role so the operational pages can be shared widely. Distribution runs through your own workspace, inside your tenant, under your identity provider.

It surfaces immediately, which is useful. Misclassified move types, partners duplicated under slightly different names and entries posted to the wrong company all show up as visible anomalies in the first draft rather than being averaged away. We ship a data quality view alongside the two pages.

Odoo Online does not expose direct database access, so we connect through the external API or a scheduled replica instead of a live SQL connection. The model, the measures and the pages are identical. Odoo.sh and self hosted instances use the read only connection directly, which is simpler and faster.

Far less than a customisation would, because nothing inside Odoo was changed. The report reads standard accounting tables and the parts most likely to move between versions are isolated in versioned views, so a version change is usually a view level fix. We test against the new version on staging before you upgrade production.

Two to four weeks for a standard Odoo accounting setup: workshop and modelling first, then measures, the two pages and reconciliation. Multiple entities, a long history or extra domains such as inventory valuation extend it, and we scope that against your instance.

Yes, and receivables ageing is the natural foundation for it, because expected collection dates come from the same data. We build the ageing and DSO first, since a forecast built on definitions nobody has agreed is a forecast nobody will use.

Because we work on both sides of the join: we implement and support Odoo and we build the BI layers above it, so we know what the accounting tables actually contain rather than guessing from a schema diagram. See our data analytics services or talk to a consultant.

the ledger is already complete, now make it legible

You know what you invoiced. Do you know when it arrives?

Tell us what your ERP already records and what leadership keeps asking finance for. We will agree the definitions, model the ledger and hand back a dashboard nobody has to rebuild.

Read next: a hospital that could record everything and report almost nothing

the story continues...

More client stories

All case studies

Your business could be the next story here.

Same process, already proven across 20+ industries.

Your business is not a template. Your ERP should not be either.

Every project on this page started the same way: a conversation about how the business actually runs today, and where the manual hand-offs are. That is a good place to start yours too.