feat(fusion_accounting_ai): wire AssetsAdapter fusion paths to engine
Made-with: Cursor
This commit is contained in:
@@ -1,42 +1,98 @@
|
|||||||
"""Assets data adapter."""
|
"""Assets data adapter — routes asset queries through fusion engine if installed."""
|
||||||
|
|
||||||
from .base import DataAdapter
|
from .base import DataAdapter
|
||||||
from ._registry import register_adapter
|
from ._registry import register_adapter
|
||||||
|
|
||||||
|
|
||||||
class AssetsAdapter(DataAdapter):
|
class AssetsAdapter(DataAdapter):
|
||||||
FUSION_MODEL = 'fusion.asset'
|
FUSION_MODEL = 'fusion.asset.engine'
|
||||||
ENTERPRISE_MODULE = 'account_asset'
|
ENTERPRISE_MODULE = 'account_asset'
|
||||||
|
|
||||||
def list_assets(self, state=None):
|
# ============================================================
|
||||||
return self._dispatch('list_assets', state=state)
|
# list_assets
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
def list_assets_via_fusion(self, state=None):
|
def list_assets(self, state=None, limit=50, company_id=None):
|
||||||
return self._read_fusion('fusion.asset', state=state)
|
return self._dispatch(
|
||||||
|
'list_assets', state=state, limit=limit, company_id=company_id,
|
||||||
|
)
|
||||||
|
|
||||||
def list_assets_via_enterprise(self, state=None):
|
def list_assets_via_fusion(self, **kwargs):
|
||||||
return self._read_fusion('account.asset', state=state)
|
if 'fusion.asset.engine' not in self.env.registry:
|
||||||
|
return {'assets': [], 'count': 0, 'total': 0}
|
||||||
|
Asset = self.env['fusion.asset'].sudo()
|
||||||
|
domain = [('company_id', '=', kwargs.get('company_id') or self.env.company.id)]
|
||||||
|
if kwargs.get('state'):
|
||||||
|
domain.append(('state', '=', kwargs['state']))
|
||||||
|
total = Asset.search_count(domain)
|
||||||
|
assets = Asset.search(
|
||||||
|
domain, limit=int(kwargs.get('limit', 50)),
|
||||||
|
order='acquisition_date desc',
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
'count': len(assets), 'total': total,
|
||||||
|
'assets': [{
|
||||||
|
'id': a.id, 'name': a.name, 'state': a.state,
|
||||||
|
'cost': a.cost, 'book_value': a.book_value,
|
||||||
|
'method': a.method,
|
||||||
|
'category_name': a.category_id.name if a.category_id else None,
|
||||||
|
} for a in assets],
|
||||||
|
}
|
||||||
|
|
||||||
def list_assets_via_community(self, state=None):
|
def list_assets_via_enterprise(self, **kwargs):
|
||||||
# No assets feature in pure Community — return empty list with a hint.
|
return {
|
||||||
return []
|
'assets': [], 'count': 0, 'total': 0,
|
||||||
|
'error': 'Enterprise account_asset must be queried from Enterprise UI',
|
||||||
|
}
|
||||||
|
|
||||||
def _read_fusion(self, model_name, state=None):
|
def list_assets_via_community(self, **kwargs):
|
||||||
"""Shared shape between fusion and enterprise (both use account.asset-like API)."""
|
return {
|
||||||
Model = self.env[model_name].sudo()
|
'assets': [], 'count': 0, 'total': 0,
|
||||||
domain = []
|
'error': 'No assets engine in pure Community',
|
||||||
if state:
|
}
|
||||||
domain.append(('state', '=', state))
|
|
||||||
records = Model.search(domain, limit=200)
|
# ============================================================
|
||||||
out = []
|
# suggest_useful_life
|
||||||
for r in records:
|
# ============================================================
|
||||||
out.append({
|
|
||||||
'id': r.id,
|
def suggest_useful_life(self, description, amount=None, partner_name=None):
|
||||||
'name': getattr(r, 'name', None),
|
return self._dispatch(
|
||||||
'state': getattr(r, 'state', None),
|
'suggest_useful_life',
|
||||||
'value': getattr(r, 'original_value', None) or getattr(r, 'acquisition_cost', None),
|
description=description, amount=amount, partner_name=partner_name,
|
||||||
})
|
)
|
||||||
return out
|
|
||||||
|
def suggest_useful_life_via_fusion(self, **kwargs):
|
||||||
|
if 'fusion.asset.engine' not in self.env.registry:
|
||||||
|
return {'error': 'fusion_accounting_assets not installed'}
|
||||||
|
from odoo.addons.fusion_accounting_assets.services.useful_life_predictor import (
|
||||||
|
predict_useful_life,
|
||||||
|
)
|
||||||
|
return predict_useful_life(self.env, **kwargs)
|
||||||
|
|
||||||
|
def suggest_useful_life_via_enterprise(self, **kwargs):
|
||||||
|
return {'error': 'AI useful-life suggestion is fusion-only'}
|
||||||
|
|
||||||
|
def suggest_useful_life_via_community(self, **kwargs):
|
||||||
|
return {'error': 'AI useful-life suggestion is fusion-only'}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# dispose_asset
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def dispose_asset(self, asset_id, **kwargs):
|
||||||
|
return self._dispatch('dispose_asset', asset_id=asset_id, **kwargs)
|
||||||
|
|
||||||
|
def dispose_asset_via_fusion(self, asset_id, **kwargs):
|
||||||
|
if 'fusion.asset.engine' not in self.env.registry:
|
||||||
|
return {'error': 'fusion_accounting_assets not installed'}
|
||||||
|
asset = self.env['fusion.asset'].sudo().browse(int(asset_id))
|
||||||
|
return self.env['fusion.asset.engine'].sudo().dispose_asset(asset, **kwargs)
|
||||||
|
|
||||||
|
def dispose_asset_via_enterprise(self, asset_id, **kwargs):
|
||||||
|
return {'error': 'Enterprise asset disposal must use Enterprise UI'}
|
||||||
|
|
||||||
|
def dispose_asset_via_community(self, asset_id, **kwargs):
|
||||||
|
return {'error': 'Community has no asset disposal flow'}
|
||||||
|
|
||||||
|
|
||||||
register_adapter('assets', AssetsAdapter)
|
register_adapter('assets', AssetsAdapter)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
'name': 'Fusion Accounting Assets',
|
'name': 'Fusion Accounting Assets',
|
||||||
'version': '19.0.1.0.14',
|
'version': '19.0.1.0.15',
|
||||||
'category': 'Accounting/Accounting',
|
'category': 'Accounting/Accounting',
|
||||||
'summary': 'AI-augmented asset management with depreciation schedules.',
|
'summary': 'AI-augmented asset management with depreciation schedules.',
|
||||||
'description': """
|
'description': """
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ from . import test_account_move_inherit
|
|||||||
from . import test_fusion_asset_engine
|
from . import test_fusion_asset_engine
|
||||||
from . import test_engine_integration
|
from . import test_engine_integration
|
||||||
from . import test_assets_controller
|
from . import test_assets_controller
|
||||||
|
from . import test_assets_adapter
|
||||||
|
|||||||
40
fusion_accounting_assets/tests/test_assets_adapter.py
Normal file
40
fusion_accounting_assets/tests/test_assets_adapter.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"""AssetsAdapter wiring tests — fusion-mode dispatch."""
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from odoo.tests import tagged
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
from odoo.addons.fusion_accounting_ai.services.data_adapters.assets import (
|
||||||
|
AssetsAdapter,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tagged('post_install', '-at_install')
|
||||||
|
class TestAssetsAdapter(TransactionCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.adapter = AssetsAdapter(self.env)
|
||||||
|
|
||||||
|
def test_list_assets_via_fusion(self):
|
||||||
|
self.env['fusion.asset'].create({
|
||||||
|
'name': 'Adapter Test', 'cost': 1000,
|
||||||
|
'acquisition_date': date(2026, 1, 1),
|
||||||
|
'method': 'straight_line', 'useful_life_years': 4,
|
||||||
|
})
|
||||||
|
result = self.adapter.list_assets_via_fusion(company_id=self.env.company.id)
|
||||||
|
self.assertGreaterEqual(result['count'], 1)
|
||||||
|
|
||||||
|
def test_suggest_useful_life_via_fusion_uses_templated_fallback(self):
|
||||||
|
self.env['ir.config_parameter'].sudo().search([
|
||||||
|
('key', 'in', ['fusion_accounting.provider.asset_useful_life',
|
||||||
|
'fusion_accounting.provider.default'])
|
||||||
|
]).unlink()
|
||||||
|
result = self.adapter.suggest_useful_life_via_fusion(description='laptop')
|
||||||
|
self.assertEqual(result['useful_life_years'], 4)
|
||||||
|
self.assertEqual(result['depreciation_method'], 'straight_line')
|
||||||
|
|
||||||
|
def test_dispose_asset_via_community_returns_error(self):
|
||||||
|
result = self.adapter.dispose_asset_via_community(asset_id=1, sale_amount=100)
|
||||||
|
self.assertIn('error', result)
|
||||||
Reference in New Issue
Block a user