User feedback: 'i like the odoo enterprise style reports, I hate our style.'
Replaces our custom 'o_fusion_reports' visual with a faithful adaptation
of Enterprise account_reports. Same .account_report root class, same
table semantics (line_name + line_cell + line_level_N), same border
treatment (1px gray-300 borders, 0.25rem radius, sticky thead), same
button hover behavior (gray-300 -> enterprise-action-color), same dense
0.8rem font-size + padded cells.
SCSS layout:
- reports.scss in web.assets_backend bundle (eager light)
- reports.dark.scss in web.assets_web_dark bundle (lazy dark mode)
- _variables.scss reduced to spacing/typography only -- colors use
Odoo's \$o-* SCSS vars so dark mode flips automatically via the
separate dark bundle
- old dark_mode.scss removed (was using non-Odoo [data-color-scheme]
selector that never matched anything)
QWeb templates rewritten to mirror Enterprise's structure:
- report_viewer.xml roots at .account_report with scroll container
- report_table.xml uses Enterprise's td.line_name + td.line_cell with
.wrapper > .content nesting; partner-grouped reports now actually
render their aging buckets (previously showed nothing)
- period_filter.xml is now a clean Bootstrap-styled inline filter bar
Kept Fusion-only components but restyled to fit:
- anomaly_strip uses Bootstrap alert-{danger,warning,info} colors
- ai_commentary_panel is a plain bordered panel, no gradients/emojis
- drill_down_dialog unchanged (already a Bootstrap modal)
Made-with: Cursor
User reported that after Enterprise uninstall, clicking 'Reports' opened
PDF statements instead of the dynamic Fusion report viewer. Root cause:
the OWL ReportViewer (registered as view_type='fusion_reports') was only
reachable via the period-picker WIZARD; no menu items used the OWL view
directly. Plus the JS service ignored report_code, so even within the
viewer, all PnL-typed reports rendered the canonical P&L line_specs.
Changes:
JS layer
- reports_service.js: runReport now accepts and forwards reportCode;
state tracks currentReportCode so re-runs after period/comparison
changes preserve the variant.
- report_viewer.js: reads default_report_code (and default_comparison)
from the action context.
- period_filter.js: passes the cached reportCode on date changes;
clears it when the user picks a different report_type.
Backend
- New fusion_accounting_reports/views/report_actions.xml with 11
dedicated ir.actions.act_window records, one per built-in report
(P&L, Balance Sheet, Trial Balance, GL, Cash Flow, Executive Summary,
Annual Statements, Aged Receivable, Aged Payable, Partner Ledger,
Tax Summary). Each opens view_mode='fusion_reports' with the
appropriate default_report_type + default_report_code context.
- views/menu_views.xml: each report now gets its own menu item
directly under Accounting > Reporting (sequence 10-40), matching
Enterprise's flat structure. Custom Period wizard, XLSX export and
Anomaly browser collected under a 'Tools' sub-group at the bottom.
- fusion_accounting_l10n_ca: adds menu items for 'Profit and Loss
(Canada)' and 'Balance Sheet (Canada)' as siblings, plus a 'Tax
Returns (CA)' configuration menu.
Verified live on westin-v19:
- pnl rendering 3 rows, cash_flow 9, executive_summary 7,
annual_statements 5, ca_profit_loss 9 \u2014 each report now renders
its own line_specs correctly.
- Reporting menu shows 14 Fusion report entries + Tools group.
- 136/136 reports + l10n_ca tests pass.
Version bumps: reports 19.0.1.1.1, l10n_ca 19.0.1.1.0.
Made-with: Cursor
User reported two UX problems after the Enterprise uninstall:
1. Each Fusion sub-module showed up as its own standalone app in the
launcher (Bank Reconciliation, Financial Reports, Asset Management,
Customer Follow-ups, Fusion AI). Should look like ONE Accounting app.
2. Clicking the 'Fusion Accounting' app still opened the migration
wizard even though Enterprise had been uninstalled and there was
nothing to migrate.
Fix:
- Move all Fusion sub-module roots under the Community account.menu_finance
hierarchy:
* Bank Reconciliation \u2192 Accounting > Bank Reconciliation
* Asset Management \u2192 Accounting > Asset Management
* Financial Reports \u2192 Reporting > Financial Reports
* Follow-ups \u2192 Customers > Follow-ups
* Fusion AI \u2192 Configuration > Fusion AI
* Migrate from Ent. \u2192 Configuration > Migrate from Enterprise
- Rename Community's 'Invoicing' top-level menu to 'Accounting' (what
Enterprise's accountant module did). Set the Fusion icon on it. This
rename lives in the meta-module so it only fires when the full suite
is installed.
- Add second computed group 'group_fusion_show_when_enterprise_present'
(inverse of the existing 'absent' group). Migration menus are gated
by this group, so they auto-hide once Enterprise is uninstalled.
- _fusion_recompute_coexistence_group now maintains both groups in lockstep.
- Meta-module now also depends on l10n_ca, hr_payroll, ocr, documents
(the Phase 6/7 sub-modules) for one-click full-suite install.
- Fusion AI menu's old parent ('accountant.menu_accounting') was deleted
with the Enterprise uninstall \u2014 reparented under Configuration.
Result: single 'Accounting' top-level menu containing the standard
V19 Community structure (Dashboard / Customers / Vendors / Accounting /
Reporting / Configuration), with all Fusion features slotted into the
appropriate sub-section. Verified live on westin-v19: 6 separate
Fusion top-level menus collapsed to 1; coexistence groups recomputed
(absent=10 users, present=0 users); 604/604 tests pass.
Version bump: all touched modules \u2192 19.0.1.1.0.
Made-with: Cursor
When multiple fusion.report rows share a report_type (e.g. 4 PnL-typed
reports: pnl, cash_flow, executive_summary, annual_statements), the
engine's _get_report previously returned whichever matched the type
filter first \u2014 so all four reports rendered the canonical P&L
line_specs regardless of which report the user selected.
Adds report_code kwarg to compute_pnl, compute_balance_sheet,
compute_trial_balance, compute_gl. Controller /fusion/reports/run now
accepts and forwards report_code. _get_report has a 3-tier resolution:
1. Exact code match (validates type)
2. Canonical (code == report_type)
3. First by sequence
Two new tests assert distinct line_specs render for distinct codes and
that wrong-type code raises ValidationError.
Verified live on westin-v19: pnl/cash_flow/executive_summary/
annual_statements now return 3/9/7/5 rows respectively (was all
3 before).
Made-with: Cursor
Adds Aged Receivable, Aged Payable, and Partner Ledger as fusion.report
records using the new compute_partner_grouped engine method.
REPORT_TYPES is extended with aged_receivable / aged_payable /
partner_ledger so each report has a unique report_type. The HTTP
controller dispatches these to engine.compute_partner_grouped with
the appropriate account_type via PARTNER_GROUPED_ACCOUNT_TYPE.
Output includes per-partner aging buckets: current, 1-30, 31-60,
61-90, 90+ days.
Westin total: 4 + 4 + 3 = 11 of Enterprise's 22 standard reports.
Made-with: Cursor
Adds engine.compute_partner_grouped(period, account_type=...) that
returns per-partner aggregations with aging buckets (current/1-30/
31-60/61-90/90+). SQL-direct for performance — single GROUP BY query
with conditional sum per bucket.
Foundation for the 3 partner-grouped reports landing in commit 3:
Aged Receivable, Aged Payable, Partner Ledger.
Made-with: Cursor
Adds Cash Flow Statement, Executive Summary, Tax Summary, and Annual
Statements as fusion.report records with line_specs. All work with the
existing engine's bucket-sum pattern — no engine changes needed.
Westin total: 4 + 4 = 8 of Enterprise's 22 standard reports now in
fusion_accounting_reports. Partner-grouped reports (Aged AR/AP,
Partner Ledger) need an engine extension — in commit 2.
Made-with: Cursor
V19 removed the 'rpc' service from the registry. All 4 fusion services
(bank_reconciliation, reports, assets, followup) declared dependencies:
['rpc', ...] and accessed services.rpc in their constructor. At runtime
this caused:
Error: Some services could not be started: fusion_bank_reconciliation,
fusion_reports, fusion_assets, fusion_followup. Missing dependencies: rpc
\u2014 which prevented the entire OWL backend from booting (blank screen).
Fix per V19 docs:
- Add 'import { rpc } from "@web/core/network/rpc";'
- Set 'this.rpc = rpc;' in constructor (instead of services.rpc)
- Remove 'rpc' from dependencies list
This is the workspace CLAUDE.md guidance Phase 4's subagent flagged
but didn't act on for backward consistency. V19 actually removed the
service entirely, so the consistency choice was wrong \u2014 fixing now.
All call sites still use this.rpc(...) so no per-method changes needed.
Bundle rebuilt clean; backend boots correctly.
Made-with: Cursor
Phases 1-3's SCSS files used '@import "variables";' to pull in tokens
from _variables.scss. V19's odoo.addons.base.models.assetsbundle
forbids cross-file SCSS imports for security ('Local import forbidden')
and the asset bundle warning was firing on every web request.
Phase 4 caught + fixed this for fusion_accounting_followup; Phases 1-3
were never updated. Today's deployment surfaced the CSS error reported
by the user.
Resolution:
- Removed @import lines from 7 SCSS files across bank_rec, reports, assets
- Variables come from _variables.scss via manifest concatenation order
(bundle order is _variables.scss first, then dependent files)
- Replaced documentation comments to NOT contain the literal string
'@import "variables"' \u2014 Odoo's check is regex-based and was
matching even SCSS comments
Verified clean: bundle rebuilds with zero 'Local import forbidden'
warnings; all 534 fusion-module tests still pass.
Made-with: Cursor
Mirrors Phase 1's coexistence test pattern. Verifies:
- The coexistence group (group_fusion_show_when_enterprise_absent)
exists and is referenceable
- The reports engine model (fusion.report.engine) is always
registered, regardless of Enterprise install state
- The Financial Reports root menu requires the coexistence group
- The Open Report... sub-menu (period picker wizard) is gated too
Uses V19 group_ids attribute with a graceful fallback to groups_id for
older runtime variants.
Tests: 3 new (test_coexistence.py). Net 115 -> 118.
Made-with: Cursor
Adds views/menu_views.xml with a Financial Reports root menu (sequence
50) and three sub-items: Open Report... (period picker wizard), Export
to XLSX... (xlsx wizard), and Anomalies (list view of fusion.report.anomaly).
Every menu and the root are gated by group_fusion_show_when_enterprise_absent
so the entire Fusion Reports tree disappears when Enterprise's
account_reports module is installed - the engine, AI tools, and exports
remain available; only the UI hides to avoid duplicate menus.
Includes a window action for fusion.report.anomaly (list,form).
Made-with: Cursor
Inherits fusion.migration.wizard from fusion_accounting_migration and
appends a _reports_bootstrap_step that confirms the 4 CORE report
definitions (pnl, balance_sheet, trial_balance, general_ledger) exist
after migration. Returns a structured result with expected, present, and
missing report types.
Hooked into action_run_migration via super(); failures are logged
(warning) but never raised, so the migration chain remains tolerant of
ordering between sub-modules.
Adds fusion_accounting_migration to manifest depends.
Tests: 1 new (test_migration_round_trip.py). Net 114 -> 115.
Made-with: Cursor
Adds fusion.period.picker.wizard - a guided entry point that lets users
pick a report type and a common period preset (this/last month, quarter,
YTD, last year, or custom range). The wizard uses the existing date_periods
service helpers (month_bounds, quarter_bounds, fiscal_year_bounds) to
pre-fill date_from / date_to via @api.onchange.
action_open_report returns a client action that launches the OWL reports
viewer with default_report_type / default_date_from / default_date_to /
default_comparison in the context.
Tests: 3 new (test_period_picker.py). Net 111 -> 114.
Made-with: Cursor
Adds a TransientModel wizard fusion.xlsx.export.wizard that lets users
pick a report type, date range, and comparison mode, then runs the
engine and produces an XLSX via xlsxwriter (in-memory).
The wizard exposes a download field that becomes available after export
finishes. Works on P&L, Balance Sheet, Trial Balance, and General Ledger.
Comparison columns are written when the engine returns a comparison_period
in the result.
Also wires the controller's /fusion/reports/export_xlsx endpoint to drive
the wizard and return base64-encoded XLSX bytes (replaces the not_implemented
placeholder).
Tests: 2 new (test_xlsx_export.py) + 1 controller test updated. Manifest
declares xlsxwriter as an external_dependency.
Made-with: Cursor
Adds an AbstractModel report (report_pdf.py) and a single multi-purpose
QWeb template (report_pdf_template.xml) that renders P&L, Balance Sheet,
Trial Balance, and General Ledger results from the engine.
Wires the controller's /fusion/reports/export_pdf endpoint to actually
return base64-encoded PDF bytes via _render_qweb_pdf. The template walks
the result['rows'] list and applies indentation/bold based on level and
is_subtotal flags, with optional comparison columns when present.
Tests: 2 new (test_pdf_export.py) + 1 controller test updated to assert
the real PDF response. Net 109 -> 111.
Made-with: Cursor
Adds financial_reports.py tools module with 5 fusion-engine-routed
tools registered in TOOL_DISPATCH:
- fusion_run_report
- fusion_get_anomalies
- fusion_generate_commentary
- fusion_drill_down_report_line
- fusion_compare_periods
Each tool guards on 'fusion.report.engine' being in the registry and
otherwise returns a structured error so the chat agent can surface a
clear "module not installed" message.
6 new TransactionCase tests (including a TOOL_DISPATCH registration
sanity check).
Made-with: Cursor
Adds three new method families on ReportsAdapter that route through
fusion.report.engine when fusion_accounting_reports is installed:
- run_fusion_report (pnl/balance_sheet/trial_balance/general_ledger)
- get_anomalies (variance detection on engine output)
- get_commentary (LLM narrative; falls back to templated)
These coexist with the legacy ref_id-shaped run_report / export_report
API so existing reporting tools (profit_loss, balance_sheet, etc.) keep
working unchanged. FUSION_MODEL is updated to fusion.report.engine so
mode detection picks FUSION when the new engine is installed.
4 new TransactionCase tests cover the fusion + community paths.
Made-with: Cursor
Adds FusionReportsController exposing:
- list_available, run, drill_down
- get_anomalies (with optional persistence to fusion.report.anomaly)
- get_commentary (LLM cache via fusion.report.commentary, force_regenerate flag)
- compare_periods (delegates to run with comparison flag)
- export_pdf / export_xlsx (Phase 2 placeholders for Tasks 34/35)
All endpoints use V19's type='jsonrpc' and route through
fusion.report.engine - no direct ORM aggregation in the controller.
8 new HttpCase tests cover each endpoint. Total: 78 logical tests.
Made-with: Cursor
Adds data/report_general_ledger.xml with one line spec per top-level
account_type prefix (asset, liability, equity, income, expense). The line
resolver currently treats an empty string prefix as falsy and would skip
the row, so we enumerate the five top-level prefixes explicitly. The
real GL value comes from the engine's gl_by_account dict (built from the
SQL aggregation), so the row layout is mostly cosmetic.
Adds tests/test_seeded_reports.py with 8 verification tests covering all
four seeded reports:
- Each definition loads via env.ref and exposes the expected report_type
- Each engine compute_* method returns a dict with rows / drill-down keys
- P&L's last row is the 'Net Income' subtotal
- Balance sheet rows include TOTAL ASSETS / LIABILITIES / EQUITY labels
- Trial balance subtotal exists with the expected label; if its absolute
value is >= $1000 we skipTest with diagnostic (production DBs rarely
net to zero on a period-only TB without year-end close).
Bumps manifest to 19.0.1.0.8. Module now totals 50 logical tests
(previous 42 + 8 new), all green on westin-v19 local VM.
Made-with: Cursor
Adds data/report_trial_balance.xml grouping balances by top-level
account_type prefix (asset, liability, equity, income, expense). Each
group is sign-adjusted so that posted, balanced books sum to ~0 in the
'Total (should be 0)' subtotal -- a quick visual sanity check.
Bumps manifest to 19.0.1.0.7.
Made-with: Cursor
Adds data/report_balance_sheet.xml with sections for assets, liabilities,
and equity, using the V19 account_type prefixes (asset_current,
asset_receivable, asset_cash, asset_prepayments, asset_non_current,
asset_fixed; liability_payable, liability_credit_card, liability_current,
liability_non_current; equity). Header rows ('ASSETS', 'LIABILITIES',
'EQUITY') are present for visual structure -- the line resolver currently
skips spec entries without compute or account_type_prefix, which means
they don't render but also don't disturb subtotal counts.
Bumps manifest to 19.0.1.0.6.
Made-with: Cursor
Adds data/report_pnl.xml seeding a company-agnostic fusion.report record
for the Income Statement (report_type='pnl'). Line specs are loaded via
eval= so Odoo passes a real Python list to the JSON field instead of a
string-encoded blob.
Structure: Revenue (sign -1) - Operating Expenses (sign -1) = Net Income
(subtotal above 2). Comparison defaults to previous_year.
Bumps manifest to 19.0.1.0.5.
Made-with: Cursor
The engine orchestrator. compute_pnl, compute_balance_sheet,
compute_trial_balance, compute_gl, drill_down. All controllers,
wizards, AI tools must route through these methods; no direct
SQL aggregation from anywhere else.
Internal pipeline: validate -> fetch hierarchy -> SQL aggregate
-> resolve line_specs -> optional comparison + anomaly. Uses raw
SQL for the per-account aggregate (the perf-critical step), ORM
for everything else.
Per-company report lookup with global fallback (company_id desc
nulls last). Balance sheet uses 1970 epoch as date_from for
cumulative-since-inception semantics.
7 new tests, 42 total passing.
Made-with: Cursor
Pure-Python helper that, given an account_id and a date range, fetches
posted account.move.line records and returns a flat list of dicts ready
for the drill-down OWL dialog. Used by the engine's drill_down() method.
3 new tests, 35 total passing.
Made-with: Cursor
Pure-Python helper that resolves a fusion.report's line_specs against
account_totals -> ordered list of report row dicts. Supports three spec
types: account_type_prefix (sum accounts by type), account_id (single
account, drill-downable), and compute='subtotal' (sum last N rows).
Comparison-period support: variance_pct computed automatically when
comparison_totals are supplied.
5 new tests, 32 total passing.
Made-with: Cursor
Persistent definition of a Fusion financial report. Each report (P&L,
balance sheet, trial balance, GL) has one row in fusion.report holding
its metadata + line specs (stored as JSON for layout flexibility).
V19 conventions: models.Constraint inline, no _sql_constraints. Per-
company uniqueness on (company_id, code).
3 new tests, 27 total passing.
Made-with: Cursor
Pure-Python helper for FX conversion at report end-date. Handles direct
rates, inverse rates, and fallback to most-recent-rate-on-or-before.
fetch_rates() pulls from res.currency.rate using the same
1/rate inversion convention Odoo uses internally.
Made-with: Cursor