This article is currently available in English only. Translation coming soon.
ہماری Data Analytics & BI سیریز کا حصہ
مکمل گائیڈ پڑھیںPower BI for Odoo: 12 Production-Ready DAX Patterns
Connecting Power BI to Odoo is the easy part. The hard part is modeling Odoo's normalized, multi-company, ID-keyed schema in a way that finance and operations leaders can actually slice. After deploying Power BI dashboards on top of Odoo for 60+ ECOSIRE clients running v15 through v19, the same dozen DAX patterns keep paying for themselves. This article is the field guide.
We assume you have already pulled account_move, account_move_line, sale_order, sale_order_line, stock_quant, stock_move, res_partner, res_company, product_product, product_template, and account_account into a Power BI dataset. Keys are joined, currencies converted, and the date table is marked as a date table. From there, the patterns below are the difference between a data dump and a dashboard a CFO will trust.
Key Takeaways
- DAX time intelligence works on Odoo only when you build a proper Date table and mark it correctly — Odoo's
create_dateis a timestamp, not a date.- Multi-company consolidation needs a
Companydimension and explicit currency conversion measures, not a column.- Account hierarchy in Odoo (parent_id self-reference) requires
PATH()andPATHITEM()to flatten for Power BI.- Cohort retention measures rely on
EARLIER()orVAR ... CALCULATEpatterns to capture the customer's first purchase.- Inventory aging buckets are a
SWITCH(TRUE())overDATEDIFFfromstock_quant.in_date.- DAX measures should never reference Odoo IDs directly — always go through dimension tables for filter context.
- Calculation Groups (Power BI Desktop 2026-01) replace dozens of duplicate measures (PY, YoY, MTD, QTD).
- For 10M+ row Odoo deployments, switch to Import mode with Incremental Refresh, not DirectQuery.
Pattern 1: A Bullet-Proof Date Table
Odoo's account_move.date is a date, but create_date and write_date are timestamps with timezone. Mixing them in a date table causes silent filter mismatches. Build a clean date table:
Date =
ADDCOLUMNS (
CALENDAR ( DATE ( 2018, 1, 1 ), DATE ( 2030, 12, 31 ) ),
"Year", YEAR ( [Date] ),
"Quarter", "Q" & QUARTER ( [Date] ),
"Month", FORMAT ( [Date], "MMM" ),
"Month Number", MONTH ( [Date] ),
"YearMonth", FORMAT ( [Date], "YYYY-MM" ),
"FY", IF ( MONTH ( [Date] ) >= 7,
"FY" & ( YEAR ( [Date] ) + 1 ),
"FY" & YEAR ( [Date] ) ),
"Is Weekday", WEEKDAY ( [Date], 2 ) <= 5,
"Is Current Month", FORMAT ( [Date], "YYYY-MM" ) = FORMAT ( TODAY (), "YYYY-MM" )
)
Then in the model: right-click Date → "Mark as date table." Without this step, SAMEPERIODLASTYEAR and DATEADD will fail silently on misaligned filters.
Pattern 2: Year-to-Date Sales (Multi-Year Comparable)
This is the bread-and-butter measure. The Odoo subtlety: state must be 'sale' or 'done' — quotations and cancelled orders should not count.
Sales YTD :=
CALCULATE (
SUMX (
FILTER (
'sale_order',
'sale_order'[state] IN { "sale", "done" }
),
'sale_order'[amount_total]
),
DATESYTD ( 'Date'[Date] )
)
Sales YTD PY :=
CALCULATE ( [Sales YTD], SAMEPERIODLASTYEAR ( 'Date'[Date] ) )
Sales YTD vs PY % :=
DIVIDE ( [Sales YTD] - [Sales YTD PY], [Sales YTD PY] )
If your fiscal year is non-calendar, replace DATESYTD with DATESYTD('Date'[Date], "06-30") for a July-June fiscal year.
Pattern 3: Multi-Company Consolidation
Odoo lets each res_company have its own currency. Power BI must consolidate to a single reporting currency. Build a currency_rate table (one row per currency-date pair, a join column to Date), then:
Sales (Reporting Currency) :=
SUMX (
'sale_order',
'sale_order'[amount_total]
* RELATED ( 'currency_rate'[rate_to_usd] )
)
Add a Company slicer driven by res_company.name. Now leadership can flip between consolidated USD and local currency per entity.
Pattern 4: Account Hierarchy Flattening
Odoo's account_account.parent_id is self-referential — chart of accounts is a tree. Power BI does not slice well on parent_id directly. Flatten with PATH:
Account Path = PATH ( 'account_account'[id], 'account_account'[parent_id] )
Level 1 = LOOKUPVALUE ( 'account_account'[name], 'account_account'[id], PATHITEM ( [Account Path], 1, INTEGER ) )
Level 2 = LOOKUPVALUE ( 'account_account'[name], 'account_account'[id], PATHITEM ( [Account Path], 2, INTEGER ) )
Level 3 = LOOKUPVALUE ( 'account_account'[name], 'account_account'[id], PATHITEM ( [Account Path], 3, INTEGER ) )
Account Depth = PATHLENGTH ( [Account Path] )
Now use Level 1, Level 2, Level 3 as a hierarchical row in a matrix visual — collapsible P&L done.
Pattern 5: Customer Cohort Retention
Cohort analysis is the highest-value pattern most Odoo teams skip because the SQL is painful. DAX makes it readable.
First Purchase Date :=
CALCULATE (
MIN ( 'sale_order'[date_order] ),
ALLEXCEPT ( 'sale_order', 'res_partner'[id] )
)
Cohort Month :=
FORMAT ( [First Purchase Date], "YYYY-MM" )
Months Since Cohort :=
DATEDIFF ( [First Purchase Date], MAX ( 'sale_order'[date_order] ), MONTH )
Active Customers :=
DISTINCTCOUNT ( 'sale_order'[partner_id] )
Cohort Retention % :=
DIVIDE (
[Active Customers],
CALCULATE (
DISTINCTCOUNT ( 'res_partner'[id] ),
ALLEXCEPT ( 'res_partner', 'sale_order'[Cohort Month] )
)
)
Pivot rows = Cohort Month, columns = Months Since Cohort, values = Cohort Retention %. Conditional format the matrix and you have a triangle that finance and growth teams will both want.
Pattern 6: Inventory Aging Buckets
Odoo's stock_quant.in_date is when inventory was received. Aging buckets help spot dead stock.
Aging Bucket :=
SWITCH (
TRUE (),
DATEDIFF ( 'stock_quant'[in_date], TODAY (), DAY ) <= 30, "0-30 days",
DATEDIFF ( 'stock_quant'[in_date], TODAY (), DAY ) <= 60, "31-60 days",
DATEDIFF ( 'stock_quant'[in_date], TODAY (), DAY ) <= 90, "61-90 days",
DATEDIFF ( 'stock_quant'[in_date], TODAY (), DAY ) <= 180, "91-180 days",
"180+ days"
)
Inventory Value :=
SUMX (
'stock_quant',
'stock_quant'[quantity] * RELATED ( 'product_template'[standard_price] )
)
Stack the inventory value by aging bucket. Anything in the 180+ bucket needs a write-down conversation.
Pattern 7: Day Sales Outstanding (DSO)
DSO is the single most asked-for finance measure. The Odoo logic: only account.move of type out_invoice and out_refund count, posted state, and net of credit notes.
AR Outstanding :=
CALCULATE (
SUM ( 'account_move_line'[amount_residual] ),
'account_move_line'[account_internal_type] = "receivable",
'account_move'[state] = "posted"
)
Trailing 90d Sales :=
CALCULATE (
[Sales],
DATESINPERIOD ( 'Date'[Date], TODAY (), -90, DAY )
)
DSO :=
DIVIDE ( [AR Outstanding], [Trailing 90d Sales] / 90 )
A DSO above your payment terms (e.g., 60 on net-30) is a collections issue. Trend it weekly.
Pattern 8: Inventory Turnover
COGS :=
CALCULATE (
SUM ( 'account_move_line'[debit] ),
'account_account'[account_type] = "expense_direct_cost"
)
Average Inventory :=
AVERAGEX (
VALUES ( 'Date'[YearMonth] ),
[Inventory Value]
)
Inventory Turnover :=
DIVIDE ( [COGS], [Average Inventory] )
Days Inventory Outstanding :=
DIVIDE ( 365, [Inventory Turnover] )
A turnover below 4 in retail or below 8 in fast-moving consumer goods means inventory is parked. Drill by category to find the culprit.
Pattern 9: Composite Key Joins (Multi-Company Lookups)
Odoo schemas use (company_id, code) as a logical key for accounts, products, and warehouses. Power BI does not support multi-column relationships well — workaround:
'account_account'[Composite Key] = 'account_account'[company_id] & "|" & 'account_account'[code]
Add the same calculated column to the fact table you are joining and use single-column relationships on the composite key. This is the cleanest workaround until Power BI ships native composite keys.
Pattern 10: Sales Funnel Conversion
Odoo CRM tracks stages. To compute funnel conversion:
Leads in Stage :=
CALCULATE (
DISTINCTCOUNT ( 'crm_lead'[id] ),
USERELATIONSHIP ( 'Date'[Date], 'crm_lead'[date_last_stage_update] )
)
Win Rate :=
DIVIDE (
CALCULATE ( [Leads in Stage], 'crm_stage'[is_won] = TRUE ),
[Leads in Stage]
)
Average Deal Size :=
AVERAGEX (
FILTER ( 'crm_lead', 'crm_stage'[is_won] = TRUE ),
'crm_lead'[expected_revenue]
)
USERELATIONSHIP is the trick — crm_lead has multiple date columns (created, last update, won) and you want the right one for the visual.
Pattern 11: Manufacturing Yield
For Odoo MRP users:
Planned Quantity :=
SUM ( 'mrp_production'[product_qty] )
Produced Quantity :=
SUM ( 'mrp_production'[qty_produced] )
Yield % :=
DIVIDE ( [Produced Quantity], [Planned Quantity] )
Scrap Rate :=
DIVIDE (
SUM ( 'stock_move'[product_qty] ),
[Produced Quantity]
) * IF ( CONTAINSSTRING ( SELECTEDVALUE ( 'stock_picking_type'[code] ), "scrap" ), 1, 0 )
Yield by product, by work center, by week — operations teams will live in this dashboard.
Pattern 12: Calculation Groups for Time Intelligence
This is the modeling shortcut that replaces 30 measures with 5. As of Power BI Desktop 2026-01, calculation groups are stable in the desktop UI.
Create a calculation group called "Time Intelligence" with these calculation items:
-- Current
Current = SELECTEDMEASURE ()
-- YTD
YTD = CALCULATE ( SELECTEDMEASURE (), DATESYTD ( 'Date'[Date] ) )
-- PY
PY = CALCULATE ( SELECTEDMEASURE (), SAMEPERIODLASTYEAR ( 'Date'[Date] ) )
-- YoY
YoY = SELECTEDMEASURE () - CALCULATE ( SELECTEDMEASURE (), SAMEPERIODLASTYEAR ( 'Date'[Date] ) )
-- YoY %
[YoY %] =
DIVIDE (
SELECTEDMEASURE () - CALCULATE ( SELECTEDMEASURE (), SAMEPERIODLASTYEAR ( 'Date'[Date] ) ),
CALCULATE ( SELECTEDMEASURE (), SAMEPERIODLASTYEAR ( 'Date'[Date] ) )
)
Now any base measure (Sales, COGS, Margin, Inventory Value) automatically gets YTD, PY, YoY, YoY% variants. Drop the calculation group on a slicer or column header and let users flip between them.
Putting the Patterns Together
A complete Odoo + Power BI dataset uses all 12 patterns:
| Domain | Patterns |
|---|---|
| Foundation | 1 (Date), 3 (Multi-Company), 9 (Composite Keys) |
| Sales | 2 (YTD), 5 (Cohort), 10 (Funnel) |
| Finance | 4 (Account Hierarchy), 7 (DSO), 12 (Calc Groups) |
| Operations | 6 (Aging), 8 (Turnover), 11 (Yield) |
Build the foundation patterns first. Add domain patterns as stakeholders demand them. Refuse the request to add raw Odoo IDs to visuals — they will lead to confusing filter context bugs.
Performance Notes for Large Odoo Datasets
For Odoo deployments above 10M rows in account_move_line:
- Switch to Import mode + Incremental Refresh. DirectQuery against Odoo's PostgreSQL backend is too slow once you cross 10M rows. See our incremental refresh deep dive.
- Pre-aggregate in PostgreSQL. Build materialized views for monthly aggregates. Power BI consumes the views.
- Star schema, not snowflake. Odoo's normalized schema must be denormalized for VertiPaq to compress efficiently.
- Disable auto-date hierarchies. Power BI auto-generates a date table per date column — kill this in options or your model bloats.
Frequently Asked Questions
Should I use the Odoo XML-RPC connector or read PostgreSQL directly?
Read PostgreSQL directly via the native Power BI PostgreSQL connector. The XML-RPC connector is fine for small lookups but gets slow over 100K rows. For production dashboards, give Power BI a read-only PostgreSQL user and query the warehouse views.
Do these patterns work for Odoo Online (SaaS)?
Odoo Online does not give you direct PostgreSQL access. You can either use the XML-RPC connector (slow), export to a warehouse via Odoo's automated actions, or use a third-party connector. Most ECOSIRE clients on Odoo Online sync nightly to BigQuery or PostgreSQL via Airbyte and point Power BI at the warehouse.
How do I handle Odoo multi-currency at the line level?
Use currency_id on account_move_line and a currency_rate table. Odoo stores both amount_currency (transaction currency) and balance (company currency). For consolidation across companies, multiply balance by the company-to-reporting currency rate (Pattern 3). Do not use amount_currency for consolidation — you will mix currencies.
Are there ready-made templates?
Yes. ECOSIRE's Power BI dashboard service ships pre-built Odoo dashboards covering sales, inventory, manufacturing, and finance. We license the .pbit templates so your team can extend them. Browse our Power BI products catalog for the current library.
What about Microsoft Fabric? Does it change this?
Fabric absorbs Power BI Premium and adds OneLake, Data Factory, and Synapse. The DAX patterns above are unchanged — they live in the Power BI semantic model regardless of capacity SKU. Fabric mainly changes how data lands and is governed before it reaches the model.
These twelve patterns cover the 80% of Odoo + Power BI dashboard requests that come across our desk. If you need help implementing them on your specific Odoo version, or you want a managed Power BI deployment with refresh schedules, RLS, and embedded portals, our Power BI consultancy team builds these into production every month.
تحریر
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Odoo ERP کے ساتھ اپنے کاروبار کو تبدیل کریں
آپ کے کاموں کو ہموار کرنے کے لیے ماہر Odoo کا نفاذ، حسب ضرورت، اور معاونت۔
متعلقہ مضامین
How to Add a Custom Button to an Odoo Form View (2026)
Add custom action buttons to Odoo 19 form views: Python action method, view inheritance, conditional visibility, confirmation dialogs. Production-tested.
How to Add a Custom Field in Odoo Without Studio (2026)
Add custom fields via custom module in Odoo 19: model inheritance, view extension, computed fields, store/non-store decisions. Code-first, version-controlled.
How to Add a Custom Report in Odoo Using External Layout
Build a branded PDF report in Odoo 19 using web.external_layout: QWeb template, paperformat, action binding. With print logo + footer overrides.
Data Analytics & BI سے مزید
Power BI Row-Level Security: Dynamic vs Static Patterns
Power BI RLS deep dive: static vs dynamic roles, USERPRINCIPALNAME patterns, security tables, manager hierarchies, RLS testing, and embedded RLS for SaaS.
Power BI vs Looker Studio 2026: Cost & Capability Compared
Power BI vs Looker Studio: licensing, performance, governance, embedded analytics, and which fits your data team. 30-point comparison.
Power BI vs Tableau 2026: Complete Business Intelligence Comparison
Power BI vs Tableau 2026: head-to-head on features, pricing, ecosystem, governance, and TCO. Clear guidance on when to pick each and how to migrate.
اکاؤنٹنگ KPIs: 30 مالیاتی میٹرکس ہر کاروبار کو ٹریک کرنا چاہیے
30 ضروری اکاؤنٹنگ KPIs کو ٹریک کریں جس میں منافع، لیکویڈیٹی، کارکردگی، اور گروتھ میٹرکس جیسے مجموعی مارجن، EBITDA، DSO، DPO، اور انوینٹری موڑ شامل ہیں۔
کاروباری ذہانت کے لیے ڈیٹا گودام: فن تعمیر اور نفاذ
کاروباری ذہانت کے لیے ایک جدید ڈیٹا گودام بنائیں۔ Snowflake، BigQuery، Redshift کا موازنہ کریں، ETL/ELT، ڈائمینشنل ماڈلنگ، اور Power BI انٹیگریشن سیکھیں۔
پاور BI کسٹمر تجزیات: RFM سیگمنٹیشن اور لائف ٹائم ویلیو
DAX فارمولوں کے ساتھ Power BI میں RFM سیگمنٹیشن، کوہورٹ تجزیہ، چرن پریڈیکشن ویژولائزیشن، CLV کیلکولیشن، اور کسٹمر ٹریول میپنگ کو لاگو کریں۔