# fusion_accounting_assets — Cursor / Claude Context ## Purpose AI-augmented fixed asset management with depreciation schedules — a Fusion-native replacement for (and coexisting with) Odoo Enterprise's `account_asset` module. Ships in Phase 3 of the fusion_accounting roadmap. ## Architecture Hybrid: the engine (`fusion.asset.engine`, AbstractModel) is the SINGLE write surface for the asset lifecycle. Everything else (controllers, OWL widget, AI tools, wizards, cron) routes through the engine's 7-method public API: - `compute_depreciation_schedule(asset, recompute=False)` - `post_depreciation_entry(asset, period_date=None)` - `dispose_asset(asset, sale_amount=0, sale_date=None, sale_partner=None, disposal_type='sale')` - `partial_sale(asset, sold_amount, sold_qty=None, sale_date=None, sale_partner=None)` - `pause_asset(asset, pause_date=None)` - `resume_asset(asset, resume_date=None)` - `reverse_disposal(asset)` Pure-Python services live in `services/`: - `depreciation_methods` — straight_line, declining_balance, units_of_production - `prorate` — first/last-period prorating: full_month, days_365, days_period - `salvage_value` — % of cost, fixed amount, zero - `anomaly_detection` — variance vs expected schedule, low utilization - `useful_life_predictor` + `useful_life_prompt` — LLM-suggested useful life with templated fallback Persisted models in `models/`: - `fusion.asset` — main model, state machine: draft → running → paused → disposed - `fusion.asset.depreciation.line` — board lines - `fusion.asset.category` — templates - `fusion.asset.disposal` — disposal records - `fusion.asset.anomaly` — flagged variances - `fusion.asset.book.values.mv` — pre-aggregated materialized view - `fusion.asset.engine` — AbstractModel (the API) - `fusion.assets.cron` — cron handlers (post depreciations, MV refresh, anomaly scan) - `account.move.line` (inherits) — adds `fusion_asset_id` linkage - `fusion.migration.wizard` (inherits in `models/`) — adds asset backfill step Wizards (TransientModel) in `wizards/`: - `fusion.create.asset.wizard` — assisted creation with AI useful-life suggestion - `fusion.disposal.wizard` — full disposal flow - `fusion.partial.sale.wizard` — partial-quantity disposal - `fusion.depreciation.run.wizard` — period close runner Controller: `controllers/assets_controller.py` exposes 8 JSON-RPC endpoints under `/fusion/assets/*` (list, get_detail, compute_schedule, post_depreciation, dispose, get_anomalies, suggest_useful_life, get_partner_history). All calls route through the engine. OWL frontend: `static/src/` - `services/assets_service.js` — central reactive state + RPC wrappers - `views/asset_dashboard/*` — top-level dashboard view - `components/asset_card`, `asset_detail_panel`, `depreciation_board`, `disposal_dialog`, `ai_useful_life_panel`, `anomaly_strip` — 6 components - `scss/_variables.scss` + `assets.scss` + `dark_mode.scss` - `tours/assets_tours.js` — 5 OWL tour smoke tests ## Coexistence When `account_asset` is installed the Asset Management menu hides via `fusion_accounting_core.group_fusion_show_when_enterprise_absent` (a computed group). The engine + AI tools remain available for the chat. The migration wizard backfills `fusion.asset` from existing `account.asset` records (verified live: 2 records, Task 35). ## 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`). - **Materialized view refresh:** `fusion.asset.book.values.mv` is refreshed by cron (REFRESH CONCURRENTLY in an autocommit cursor since it can't run inside a regular Odoo transaction). - **Provider routing:** AI features look up `fusion_accounting.provider.asset_useful_life`, falling back to `fusion_accounting.provider.default`. When neither is set the templated keyword fallback in `useful_life_predictor` keeps the feature usable offline. ## Performance baseline (Tasks 23 + 41) | Operation | P95 | Budget | Headroom | |------------------------------------|-------|----------|----------| | `engine.compute_schedule` (10yr SL)| 1ms | 500ms | 500x | | `engine.post_depreciation_entry` | <1ms | 300ms | huge | | `engine.dispose_asset` | 5ms | 300ms | 60x | | `controller.list` (35 assets) | 42ms | 300ms | 7x | | `controller.get_detail` | 40ms | 500ms | 12x | All Phase 3 perf metrics are within 1x of budget; no optimization was needed at ship (Task 42 skipped per the conditional rule). ## Test counts (Phase 3 ship) - 140 logical tests total in fusion_accounting_assets - 0 failures, 0 errors - Coverage includes: 4 engine benchmarks + 1 controller benchmark (tagged `benchmark`), 1 local LLM smoke (tagged `local_llm`, skips when no LLM), 5 OWL tour tests (tagged `tour`, skip without websocket-client), Hypothesis property tests on the engine, integration tests on the public API, controller round-trip tests, MV shape tests. ## Known concerns / Phase 3.5 backlog - Sub-annual depreciation frequency (currently annual only) - Units-of-production assumes even per-period units - Disposal journal entry not yet created — `dispose_asset` writes the `fusion.asset.disposal` record but not the cash / gain-loss move - Multi-currency, allocation rules, and analytic tags for depreciation moves are out of scope for Phase 3 - Partial-sale child asset is created with no own depreciation schedule pre-disposal - Migration wizard inheritance lives in `models/` rather than `wizards/` (small inconsistency with the rest of the wizard layout — intentional to keep ORM ordering simple) - `useful_life_predictor` always returns a usable dict (templated fallback when LLM absent), so callers can't distinguish "AI said so" from "fallback fired"; the `confidence` key is the only signal