Files
gsinghpal 9ebf89bde2 changes
2026-05-16 13:18:52 -04:00

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_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