6.1 KiB
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_productionprorate— first/last-period prorating: full_month, days_365, days_periodsalvage_value— % of cost, fixed amount, zeroanomaly_detection— variance vs expected schedule, low utilizationuseful_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 → disposedfusion.asset.depreciation.line— board linesfusion.asset.category— templatesfusion.asset.disposal— disposal recordsfusion.asset.anomaly— flagged variancesfusion.asset.book.values.mv— pre-aggregated materialized viewfusion.asset.engine— AbstractModel (the API)fusion.assets.cron— cron handlers (post depreciations, MV refresh, anomaly scan)account.move.line(inherits) — addsfusion_asset_idlinkagefusion.migration.wizard(inherits inmodels/) — adds asset backfill step
Wizards (TransientModel) in wizards/:
fusion.create.asset.wizard— assisted creation with AI useful-life suggestionfusion.disposal.wizard— full disposal flowfusion.partial.sale.wizard— partial-quantity disposalfusion.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 wrappersviews/asset_dashboard/*— top-level dashboard viewcomponents/asset_card,asset_detail_panel,depreciation_board,disposal_dialog,ai_useful_life_panel,anomaly_strip— 6 componentsscss/_variables.scss+assets.scss+dark_mode.scsstours/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(usemodels.Constraint),@api.depends('id')(raisesNotImplementedError),@route(type='json')(usetype='jsonrpc'),numbercallfield onir.cron(removed),groups_idonres.users(useall_group_idsfor searching),usersfield onres.groups(useuser_ids),groups_idonir.ui.menu(usegroup_ids). - Materialized view refresh:
fusion.asset.book.values.mvis 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 tofusion_accounting.provider.default. When neither is set the templated keyword fallback inuseful_life_predictorkeeps 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 (taggedlocal_llm, skips when no LLM), 5 OWL tour tests (taggedtour, 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_assetwrites thefusion.asset.disposalrecord 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 thanwizards/(small inconsistency with the rest of the wizard layout — intentional to keep ORM ordering simple) useful_life_predictoralways returns a usable dict (templated fallback when LLM absent), so callers can't distinguish "AI said so" from "fallback fired"; theconfidencekey is the only signal