docs(fusion_accounting_reports): CLAUDE.md, UPGRADE_NOTES.md, README.md
Made-with: Cursor
This commit is contained in:
147
fusion_accounting_reports/CLAUDE.md
Normal file
147
fusion_accounting_reports/CLAUDE.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# fusion_accounting_reports — Cursor / Claude Context
|
||||
|
||||
## Purpose
|
||||
|
||||
AI-augmented financial reports — a Fusion-native replacement for Odoo
|
||||
Enterprise's `account_reports` module. Phase 2 of the fusion_accounting
|
||||
roadmap.
|
||||
|
||||
CORE scope:
|
||||
- Income Statement (P&L)
|
||||
- Balance Sheet
|
||||
- Trial Balance
|
||||
- General Ledger (with drill-down)
|
||||
|
||||
AI augmentation:
|
||||
- Anomaly detection (variance vs prior period)
|
||||
- AI commentary (LLM-generated narrative)
|
||||
|
||||
## Architecture
|
||||
|
||||
Hybrid: the engine (`fusion.report.engine`, AbstractModel) is the SINGLE
|
||||
read surface for reports. Per-report definitions are stored as `fusion.report`
|
||||
records with JSON `line_specs` so non-developers can tweak the layouts.
|
||||
|
||||
Public engine API (5 methods):
|
||||
- `compute_pnl(period, *, comparison='none', company_id=None)`
|
||||
- `compute_balance_sheet(date_to, *, comparison='none', company_id=None)`
|
||||
- `compute_trial_balance(period, *, company_id=None)`
|
||||
- `compute_gl(period, *, account_ids=None, company_id=None)`
|
||||
- `drill_down(*, account_id, period, company_id=None)`
|
||||
|
||||
Pure-Python services in `services/` (no Odoo imports — independently
|
||||
unit-testable):
|
||||
- `date_periods` — `Period` dataclass + comparison-period math
|
||||
- `account_hierarchy` — chart-of-accounts tree walk
|
||||
- `totaling` — debit/credit/balance roll-ups
|
||||
- `currency_conversion` — multi-currency conversion via `res.currency.rate`
|
||||
- `line_resolver` — JSON `line_specs` → rendered rows
|
||||
- `drill_down_resolver` — line → underlying journal items
|
||||
- `anomaly_detection` — variance vs prior period (z-score + abs/pct gates)
|
||||
- `commentary_generator` — LLM narrative with templated fallback
|
||||
- `commentary_prompt` — provider-agnostic system + user prompt
|
||||
|
||||
Persisted models in `models/`:
|
||||
- `fusion.report` — definition with JSON `line_specs`
|
||||
- `fusion.report.commentary` — LLM-output cache (one per period+mode)
|
||||
- `fusion.report.anomaly` — flagged variances
|
||||
- `fusion.account.balance.mv` — pre-aggregated materialized view
|
||||
- `fusion.report.engine` — AbstractModel (the API)
|
||||
- `fusion.reports.cron` — cron handlers (commentary refresh, MV refresh)
|
||||
- `fusion.xlsx.export.wizard` — TransientModel (XLSX export)
|
||||
- `fusion.period.picker.wizard` — TransientModel (UX entry-point)
|
||||
- `fusion.migration.wizard` (inherits) — adds `_reports_bootstrap_step`
|
||||
|
||||
Controller: `controllers/reports_controller.py` exposes 8 JSON-RPC endpoints
|
||||
under `/fusion/reports/*`. All read paths route through the engine.
|
||||
|
||||
OWL frontend: `static/src/`
|
||||
- `scss/` — variables, base styles, dark-mode overrides
|
||||
- `services/reports_service.js` — central reactive state + RPC wrappers
|
||||
- `views/report_viewer/` — top-level OWL view + view-registry adapter
|
||||
- `components/report_table/` — generic financial-table renderer
|
||||
- `components/drill_down_dialog/` — modal for journal-item listing
|
||||
- `components/period_filter/` — date-range + comparison picker
|
||||
- `components/ai_commentary_panel/` — LLM commentary surface
|
||||
- `components/anomaly_strip/` — variance summary banner
|
||||
- `tours/reports_tours.js` — 5 OWL tour smoke tests
|
||||
|
||||
## Coexistence
|
||||
|
||||
When `account_reports` is installed, the Reports menu hides via
|
||||
`fusion_accounting_core.group_fusion_show_when_enterprise_absent`
|
||||
(a computed group). The engine + AI tools (commentary, anomaly detection)
|
||||
remain available for the chat regardless.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **V19 deprecations to avoid:** `_sql_constraints` (use `models.Constraint`),
|
||||
`@api.depends('id')` (raises `NotImplementedError`), `@route(type='json')`
|
||||
(use `type='jsonrpc'`), `numbercall` field on `ir.cron` (removed),
|
||||
`groups_id` on `res.users` (use `all_group_ids` for searching),
|
||||
`users` field on `res.groups` (use `user_ids`), `groups_id` on
|
||||
`ir.ui.menu` (use `group_ids`).
|
||||
|
||||
- **Engine signature:** Public methods are keyword-only after the leading
|
||||
positional `period` / `date_to`. Always pass `company_id=...` explicitly.
|
||||
|
||||
- **`fusion.report` lookup:** `_get_report` falls back from per-company
|
||||
override to global (`company_id=False`) — order is `company_id desc nulls
|
||||
last`.
|
||||
|
||||
- **Materialized view refresh:** `fusion.account.balance.mv` rebuilds via a
|
||||
dedicated autocommit cursor (REFRESH CONCURRENTLY can't run inside Odoo's
|
||||
regular transaction). Triggered by cron + on demand from the engine when
|
||||
data is older than the configured TTL.
|
||||
|
||||
- **JSON `line_specs`:** Strings prefixed `account:`, `prefix:`, `formula:`
|
||||
or `header` — `line_resolver.py` resolves each spec to a row. Header rows
|
||||
have no compute payload and are silently skipped by downstream totals.
|
||||
|
||||
- **Commentary cache:** Keyed on `(report_id, company_id, period_from,
|
||||
period_to, comparison_mode)` with a unique constraint. Re-runs use the
|
||||
cache unless `force_refresh=True`.
|
||||
|
||||
## Test counts (Phase 2 ship)
|
||||
|
||||
- 130 logical tests, 0 failed, 0 errors
|
||||
- Includes:
|
||||
- 6 benchmarks (tagged `benchmark`)
|
||||
- 1 LLM compat smoke (tagged `local_llm`, skips when no LLM)
|
||||
- 5 OWL tours (tagged `tour`, skips without `websocket-client`)
|
||||
- Property-based, integration, controller, materialized-view, coexistence,
|
||||
migration round-trip, PDF/XLSX export
|
||||
|
||||
## Performance baseline
|
||||
|
||||
| Operation | Median | P95 | Budget |
|
||||
|---|---|---|---|
|
||||
| `engine.compute_pnl` | 3ms | 8ms | <2000ms |
|
||||
| `engine.compute_balance_sheet` | 15ms | 20ms | <2000ms |
|
||||
| `engine.compute_trial_balance` | 3ms | 8ms | <1000ms |
|
||||
| `engine.compute_gl` | 25ms | 81ms | <3000ms |
|
||||
| `engine.drill_down` | 2ms | 10ms | <500ms |
|
||||
| `controller.run` (HTTP round-trip) | 9ms | 46ms | <2500ms |
|
||||
|
||||
All metrics within 1x of budget at Phase 2 ship. Numbers from
|
||||
`tests/test_performance_benchmarks.py` against the dev VM
|
||||
(`westin-v19`, ~1 fiscal year of data).
|
||||
|
||||
## Known concerns / Phase 2.5 backlog
|
||||
|
||||
- Trial balance period-only sum doesn't auto-close to retained earnings
|
||||
(drift visible in `test_trial_balance_total_near_zero`, currently skipped)
|
||||
- Balance sheet `TOTAL LIABILITIES + EQUITY` math limited (no
|
||||
subtotal-of-subtotals expansion in `formula:` specs)
|
||||
- GL `line_specs` need `prefix:` empty-string handling for
|
||||
"all accounts" semantics
|
||||
- Header rows (no compute payload) silently skipped by `line_resolver` —
|
||||
fine for layout, but a `header_only=True` flag would be clearer
|
||||
- `expense` prefix overlaps with subtypes (`expense_direct_cost`,
|
||||
`expense_depreciation`) — current line_specs need explicit ordering or a
|
||||
longer-prefix-wins rule
|
||||
- `wkhtmltopdf` may need configuration for PDF export on first install
|
||||
- `ReportsAdapter.run_report` vs `run_fusion_report` naming (legacy clash
|
||||
with Enterprise wrapper)
|
||||
- Tour tests skip when `websocket-client` is absent — install it in CI to
|
||||
exercise the OWL surface end-to-end
|
||||
103
fusion_accounting_reports/README.md
Normal file
103
fusion_accounting_reports/README.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# fusion_accounting_reports
|
||||
|
||||
AI-augmented financial reports for Odoo 19 Community — a Fusion-native
|
||||
replacement for Enterprise's `account_reports` module.
|
||||
|
||||
## What it does
|
||||
|
||||
- **CORE reports**: Income Statement (P&L), Balance Sheet, Trial Balance,
|
||||
General Ledger (with drill-down to journal items)
|
||||
- **AI augmentation**: variance-based anomaly detection + LLM-generated
|
||||
commentary (Claude / GPT / local LM Studio / Ollama)
|
||||
- **Wizards**: period picker (common presets — MTD, QTD, YTD, last month,
|
||||
custom range) + XLSX export
|
||||
- **Coexists** with Enterprise's `account_reports` (Enterprise wins by
|
||||
default; the Fusion menu appears only when Enterprise is uninstalled —
|
||||
the engine and AI tools are always available via the AI chat)
|
||||
- **Multi-currency** aware via `services/currency_conversion.py`
|
||||
- **Multi-company** aware (per-company `fusion.report` overrides fall back
|
||||
to global definitions)
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# Install
|
||||
odoo --addons-path=... -i fusion_accounting_reports
|
||||
|
||||
# Open the reports menu (when Enterprise's account_reports is NOT installed)
|
||||
# Apps → Reports → Open Financial Report
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### LLM commentary (optional)
|
||||
|
||||
For LM Studio / Ollama (local):
|
||||
|
||||
- `fusion_accounting.openai_base_url` = `http://host.docker.internal:1234/v1`
|
||||
- `fusion_accounting.openai_model` = your local model name
|
||||
- `fusion_accounting.openai_api_key` = `lm-studio` (or anything non-empty)
|
||||
- `fusion_accounting.provider.reports_commentary` = `openai`
|
||||
|
||||
For OpenAI / Anthropic, set the corresponding API keys via the
|
||||
`fusion_accounting_ai` config screen — `reports_commentary` will route
|
||||
through whatever provider you choose.
|
||||
|
||||
If no provider is configured, commentary falls back to a deterministic
|
||||
templated summary (no LLM call).
|
||||
|
||||
### Cron jobs
|
||||
|
||||
Two cron handlers live in `models/fusion_reports_cron.py`:
|
||||
|
||||
- `fusion_reports_commentary_refresh` — daily, regenerates commentary for
|
||||
the most recently completed period
|
||||
- `fusion_reports_mv_refresh` — every 15 min, refreshes
|
||||
`fusion.account.balance.mv`
|
||||
|
||||
## Public engine API
|
||||
|
||||
```python
|
||||
engine = env['fusion.report.engine']
|
||||
|
||||
# Income statement
|
||||
result = engine.compute_pnl(period, comparison='previous_year')
|
||||
|
||||
# Balance sheet (point-in-time)
|
||||
result = engine.compute_balance_sheet(date(2026, 12, 31))
|
||||
|
||||
# Trial balance
|
||||
result = engine.compute_trial_balance(period)
|
||||
|
||||
# General ledger (journal items per account)
|
||||
result = engine.compute_gl(period, account_ids=[1, 2, 3])
|
||||
|
||||
# Drill-down (one account, period)
|
||||
items = engine.drill_down(account_id=1, period=period)
|
||||
```
|
||||
|
||||
## JSON-RPC endpoints
|
||||
|
||||
All under `/fusion/reports/`:
|
||||
|
||||
- `POST /fusion/reports/run` — single entry-point (dispatches by `report_type`)
|
||||
- `POST /fusion/reports/drill_down` — journal items for an account+period
|
||||
- `POST /fusion/reports/commentary` — fetch/refresh LLM commentary
|
||||
- `POST /fusion/reports/anomalies` — flagged variances for a period
|
||||
- `POST /fusion/reports/export_xlsx` — XLSX bytes
|
||||
- `POST /fusion/reports/export_pdf` — PDF bytes (via wkhtmltopdf)
|
||||
- `POST /fusion/reports/list_definitions` — available `fusion.report` records
|
||||
- `POST /fusion/reports/period_presets` — date-range presets for the picker
|
||||
|
||||
## Test counts
|
||||
|
||||
- 130 logical tests, 0 failures, 0 errors
|
||||
- 6 performance benchmarks (tagged `benchmark`)
|
||||
- 1 local-LLM compat smoke (tagged `local_llm`, skips without LLM)
|
||||
- 5 OWL tour tests (tagged `tour`, skips without `websocket-client`)
|
||||
|
||||
## See also
|
||||
|
||||
- `CLAUDE.md` — agent context (architecture, conventions, perf baseline,
|
||||
Phase 2.5 backlog)
|
||||
- `UPGRADE_NOTES.md` — V19 anchor + migration strategy
|
||||
60
fusion_accounting_reports/UPGRADE_NOTES.md
Normal file
60
fusion_accounting_reports/UPGRADE_NOTES.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# fusion_accounting_reports — Upgrade Notes
|
||||
|
||||
## Odoo Version Anchor
|
||||
|
||||
This module targets **Odoo 19.0** (community-base).
|
||||
|
||||
Reference snapshot of Enterprise code mirrored from:
|
||||
- `account_reports` (Odoo 19.0.x)
|
||||
- Source: `/Users/gurpreet/Github/RePackaged-Odoo/accounting/account_reports/`
|
||||
|
||||
## Cross-Version Diff Strategy
|
||||
|
||||
When a new Odoo version ships:
|
||||
|
||||
1. Run `check_odoo_diff.sh` (in repo root) against the new Enterprise version
|
||||
2. Note any breaking changes in `account.move.line` / `account.account` API
|
||||
surfaces relied on by `services/totaling.py` and
|
||||
`services/drill_down_resolver.py`
|
||||
3. For mirrored OWL components, diff Enterprise's new versions against ours
|
||||
and port material changes (signature renames, new behaviour we want to
|
||||
inherit)
|
||||
4. Re-run the full test suite + tour tests + benchmarks against the new Odoo
|
||||
version
|
||||
5. Update this file with the new version anchor + any deviations
|
||||
|
||||
## V19 Migration Notes (already applied — Phase 1 lessons)
|
||||
|
||||
These were the bite-points from Phase 1 (`fusion_accounting_bank_rec`); we
|
||||
preempted them in Phase 2 from day one:
|
||||
|
||||
- `_sql_constraints` → `models.Constraint` (used in `fusion.report`,
|
||||
`fusion.report.commentary`, `fusion.report.anomaly`)
|
||||
- `@api.depends('id')` → removed everywhere; computed fields depend on real
|
||||
field names instead
|
||||
- `@route(type='json')` → `type='jsonrpc'` (all 8 endpoints)
|
||||
- `numbercall` field on `ir.cron` → omitted (removed in V19)
|
||||
- `res.groups.users` → `user_ids`
|
||||
- `ir.ui.menu.groups_id` → `group_ids` (used in `views/menu_views.xml` and
|
||||
the two wizard view files for the coexistence-group filter)
|
||||
|
||||
## Engine API Stability
|
||||
|
||||
The 5 public engine methods (`compute_pnl`, `compute_balance_sheet`,
|
||||
`compute_trial_balance`, `compute_gl`, `drill_down`) are the public contract.
|
||||
Their signatures are keyword-only after the first positional argument and
|
||||
will be treated as semver-stable across patch releases. Breaking changes
|
||||
will bump the minor version (e.g. 19.0.2.x.y).
|
||||
|
||||
## Phase 2 → Phase 2.5 Migration
|
||||
|
||||
If we ship Phase 2.5 (line_spec polish, deferred features, header_only
|
||||
flag, prefix overlap fix), changes will go in incremental commits. No DB
|
||||
migration needed — Phase 2 schema is forward-compatible:
|
||||
|
||||
- `fusion.report.line_specs` is a JSON column; the migration path is to
|
||||
rewrite specs in place
|
||||
- `fusion.account.balance.mv` can be dropped/re-created freely
|
||||
- `fusion.report.commentary` is a cache; safe to truncate on upgrade
|
||||
- `fusion.report.anomaly` records carry Period as date_from/date_to fields;
|
||||
no schema-level changes anticipated
|
||||
Reference in New Issue
Block a user