From 9092a78be24c164647b33a7dee03e66cb70d2579 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 19 Apr 2026 17:16:22 -0400 Subject: [PATCH] feat(fusion_accounting_ai): 5 new asset management AI tools Made-with: Cursor --- .../services/tools/__init__.py | 2 + .../services/tools/asset_management.py | 77 +++++++++++++++++++ fusion_accounting_assets/__manifest__.py | 2 +- fusion_accounting_assets/tests/__init__.py | 1 + .../tests/test_asset_tools.py | 56 ++++++++++++++ 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 fusion_accounting_ai/services/tools/asset_management.py create mode 100644 fusion_accounting_assets/tests/test_asset_tools.py diff --git a/fusion_accounting_ai/services/tools/__init__.py b/fusion_accounting_ai/services/tools/__init__.py index 17a6e9b2..b2331b03 100644 --- a/fusion_accounting_ai/services/tools/__init__.py +++ b/fusion_accounting_ai/services/tools/__init__.py @@ -10,11 +10,13 @@ from .adp import TOOLS as ADP_TOOLS from .reporting import TOOLS as REPORTING_TOOLS from .audit import TOOLS as AUDIT_TOOLS from .financial_reports import TOOLS as FINANCIAL_REPORTS_TOOLS +from .asset_management import TOOLS as ASSET_MANAGEMENT_TOOLS TOOL_DISPATCH = {} for tools_dict in [ BANK_RECON_TOOLS, HST_TOOLS, AR_TOOLS, AP_TOOLS, JOURNAL_TOOLS, MONTH_END_TOOLS, PAYROLL_TOOLS, INVENTORY_TOOLS, ADP_TOOLS, REPORTING_TOOLS, AUDIT_TOOLS, FINANCIAL_REPORTS_TOOLS, + ASSET_MANAGEMENT_TOOLS, ]: TOOL_DISPATCH.update(tools_dict) diff --git a/fusion_accounting_ai/services/tools/asset_management.py b/fusion_accounting_ai/services/tools/asset_management.py new file mode 100644 index 00000000..8698def4 --- /dev/null +++ b/fusion_accounting_ai/services/tools/asset_management.py @@ -0,0 +1,77 @@ +"""Fusion-engine-routed AI tools for asset management.""" + +import logging + +_logger = logging.getLogger(__name__) + + +def fusion_list_assets(env, params): + if 'fusion.asset.engine' not in env.registry: + return {'error': 'fusion_accounting_assets not installed'} + from ..data_adapters import get_adapter + adapter = get_adapter(env, 'assets') + return adapter.list_assets( + state=params.get('state'), + limit=int(params.get('limit', 50)), + company_id=int(params['company_id']) if params.get('company_id') else env.company.id, + ) + + +def fusion_get_asset_detail(env, params): + if 'fusion.asset.engine' not in env.registry: + return {'error': 'fusion_accounting_assets not installed'} + Asset = env['fusion.asset'] + asset = Asset.browse(int(params['asset_id'])) + if not asset.exists(): + return {'error': 'Asset not found'} + return { + 'asset': { + 'id': asset.id, 'name': asset.name, 'state': asset.state, + 'cost': asset.cost, 'book_value': asset.book_value, + 'total_depreciated': asset.total_depreciated, + 'method': asset.method, 'useful_life_years': asset.useful_life_years, + }, + 'depreciation_count': len(asset.depreciation_line_ids), + } + + +def fusion_compute_asset_schedule(env, params): + if 'fusion.asset.engine' not in env.registry: + return {'error': 'fusion_accounting_assets not installed'} + asset = env['fusion.asset'].browse(int(params['asset_id'])) + return env['fusion.asset.engine'].compute_depreciation_schedule( + asset, recompute=bool(params.get('recompute', False)), + ) + + +def fusion_dispose_asset(env, params): + if 'fusion.asset.engine' not in env.registry: + return {'error': 'fusion_accounting_assets not installed'} + from ..data_adapters import get_adapter + adapter = get_adapter(env, 'assets') + return adapter.dispose_asset( + asset_id=int(params['asset_id']), + sale_amount=float(params.get('sale_amount', 0)), + disposal_type=params.get('disposal_type', 'sale'), + ) + + +def fusion_suggest_asset_useful_life(env, params): + if 'fusion.asset.engine' not in env.registry: + return {'error': 'fusion_accounting_assets not installed'} + from ..data_adapters import get_adapter + adapter = get_adapter(env, 'assets') + return adapter.suggest_useful_life( + description=params.get('description', ''), + amount=float(params['amount']) if params.get('amount') else None, + partner_name=params.get('partner_name'), + ) + + +TOOLS = { + 'fusion_list_assets': fusion_list_assets, + 'fusion_get_asset_detail': fusion_get_asset_detail, + 'fusion_compute_asset_schedule': fusion_compute_asset_schedule, + 'fusion_dispose_asset': fusion_dispose_asset, + 'fusion_suggest_asset_useful_life': fusion_suggest_asset_useful_life, +} diff --git a/fusion_accounting_assets/__manifest__.py b/fusion_accounting_assets/__manifest__.py index 1166d828..47ced6ac 100644 --- a/fusion_accounting_assets/__manifest__.py +++ b/fusion_accounting_assets/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Fusion Accounting Assets', - 'version': '19.0.1.0.15', + 'version': '19.0.1.0.16', 'category': 'Accounting/Accounting', 'summary': 'AI-augmented asset management with depreciation schedules.', 'description': """ diff --git a/fusion_accounting_assets/tests/__init__.py b/fusion_accounting_assets/tests/__init__.py index 03d26c3e..343b0c49 100644 --- a/fusion_accounting_assets/tests/__init__.py +++ b/fusion_accounting_assets/tests/__init__.py @@ -13,3 +13,4 @@ from . import test_fusion_asset_engine from . import test_engine_integration from . import test_assets_controller from . import test_assets_adapter +from . import test_asset_tools diff --git a/fusion_accounting_assets/tests/test_asset_tools.py b/fusion_accounting_assets/tests/test_asset_tools.py new file mode 100644 index 00000000..45f48f16 --- /dev/null +++ b/fusion_accounting_assets/tests/test_asset_tools.py @@ -0,0 +1,56 @@ +"""Tests for the 5 fusion-asset AI tools.""" + +from datetime import date + +from odoo.tests import tagged +from odoo.tests.common import TransactionCase + +from odoo.addons.fusion_accounting_ai.services.tools import asset_management as tools + + +@tagged('post_install', '-at_install') +class TestFusionAssetTools(TransactionCase): + + def test_fusion_list_assets(self): + self.env['fusion.asset'].create({ + 'name': 'Tool Test', 'cost': 1000, + 'acquisition_date': date(2026, 1, 1), + 'method': 'straight_line', 'useful_life_years': 4, + }) + result = tools.fusion_list_assets(self.env, {'company_id': self.env.company.id}) + self.assertGreaterEqual(result.get('count', 0), 1) + + def test_fusion_get_asset_detail(self): + asset = self.env['fusion.asset'].create({ + 'name': 'Detail Test', 'cost': 1500, + 'acquisition_date': date(2026, 1, 1), + 'method': 'straight_line', 'useful_life_years': 4, + }) + result = tools.fusion_get_asset_detail(self.env, {'asset_id': asset.id}) + self.assertEqual(result['asset']['name'], 'Detail Test') + + def test_fusion_compute_schedule(self): + asset = self.env['fusion.asset'].create({ + 'name': 'Schedule Test', 'cost': 2000, + 'acquisition_date': date(2026, 1, 1), + 'method': 'straight_line', 'useful_life_years': 4, + }) + result = tools.fusion_compute_asset_schedule(self.env, {'asset_id': asset.id}) + self.assertEqual(result['lines_created'], 4) + + def test_fusion_suggest_useful_life(self): + self.env['ir.config_parameter'].sudo().search([ + ('key', 'in', ['fusion_accounting.provider.asset_useful_life', + 'fusion_accounting.provider.default']) + ]).unlink() + result = tools.fusion_suggest_asset_useful_life(self.env, { + 'description': 'desk', + }) + self.assertEqual(result['useful_life_years'], 7) + + def test_tools_registered_in_dispatch(self): + from odoo.addons.fusion_accounting_ai.services.tools import TOOL_DISPATCH + for tool_name in ['fusion_list_assets', 'fusion_get_asset_detail', + 'fusion_compute_asset_schedule', 'fusion_dispose_asset', + 'fusion_suggest_asset_useful_life']: + self.assertIn(tool_name, TOOL_DISPATCH)