feat(fusion_accounting_ai): wire FollowupAdapter fusion paths to engine
- Switch FUSION_MODEL to fusion.followup.engine so adapter mode selection matches the new module - Add list_overdue() with fusion/enterprise/community variants - Re-route send_followup_via_fusion to engine.send_followup_email - 4 new TransactionCase tests (73 total) Existing aging / overdue_invoices adapter methods continue to fall back to the community implementation. Made-with: Cursor
This commit is contained in:
@@ -28,7 +28,7 @@ def _bucket_for_days(days):
|
||||
|
||||
|
||||
class FollowupAdapter(DataAdapter):
|
||||
FUSION_MODEL = 'fusion.followup.line'
|
||||
FUSION_MODEL = 'fusion.followup.engine'
|
||||
ENTERPRISE_MODULE = 'account_followup'
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@@ -179,15 +179,29 @@ class FollowupAdapter(DataAdapter):
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# send_followup — Enterprise-only action
|
||||
# send_followup — routes to fusion engine when available
|
||||
# ------------------------------------------------------------------
|
||||
def send_followup(self, partner_id, options=None):
|
||||
return self._dispatch('send_followup', partner_id=partner_id, options=options)
|
||||
def send_followup(self, partner_id, level_id=None, force=False, options=None):
|
||||
return self._dispatch(
|
||||
'send_followup',
|
||||
partner_id=partner_id, level_id=level_id,
|
||||
force=force, options=options,
|
||||
)
|
||||
|
||||
def send_followup_via_fusion(self, partner_id, options=None):
|
||||
return self.send_followup_via_community(partner_id=partner_id, options=options)
|
||||
def send_followup_via_fusion(self, partner_id, level_id=None,
|
||||
force=False, options=None):
|
||||
if 'fusion.followup.engine' not in self.env.registry:
|
||||
return {'error': 'fusion_accounting_followup not installed'}
|
||||
partner = self.env['res.partner'].browse(int(partner_id))
|
||||
level = None
|
||||
if level_id:
|
||||
level = self.env['fusion.followup.level'].browse(int(level_id))
|
||||
return self.env['fusion.followup.engine'].send_followup_email(
|
||||
partner, level=level, force=bool(force),
|
||||
)
|
||||
|
||||
def send_followup_via_enterprise(self, partner_id, options=None):
|
||||
def send_followup_via_enterprise(self, partner_id, level_id=None,
|
||||
force=False, options=None):
|
||||
partner = self.env['res.partner'].browse(partner_id)
|
||||
if not partner.exists():
|
||||
return {'error': 'Partner not found'}
|
||||
@@ -198,7 +212,8 @@ class FollowupAdapter(DataAdapter):
|
||||
'result': str(result) if result else 'done',
|
||||
}
|
||||
|
||||
def send_followup_via_community(self, partner_id, options=None):
|
||||
def send_followup_via_community(self, partner_id, level_id=None,
|
||||
force=False, options=None):
|
||||
return {
|
||||
'error': (
|
||||
'Sending follow-ups is only available when account_followup '
|
||||
@@ -206,5 +221,61 @@ class FollowupAdapter(DataAdapter):
|
||||
),
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# list_overdue — partner-centric overdue rollup (fusion engine)
|
||||
# ------------------------------------------------------------------
|
||||
def list_overdue(self, status=None, limit=50, company_id=None):
|
||||
return self._dispatch(
|
||||
'list_overdue',
|
||||
status=status, limit=limit, company_id=company_id,
|
||||
)
|
||||
|
||||
def list_overdue_via_fusion(self, status=None, limit=50, company_id=None):
|
||||
if 'fusion.followup.engine' not in self.env.registry:
|
||||
return {'partners': [], 'count': 0, 'total': 0}
|
||||
company_id = company_id or self.env.company.id
|
||||
Line = self.env['account.move.line'].sudo()
|
||||
partner_ids = Line.search([
|
||||
('parent_state', '=', 'posted'),
|
||||
('account_id.account_type', '=', 'asset_receivable'),
|
||||
('reconciled', '=', False),
|
||||
('amount_residual', '>', 0),
|
||||
('date_maturity', '<', date.today()),
|
||||
('company_id', '=', company_id),
|
||||
]).mapped('partner_id').ids
|
||||
Partner = self.env['res.partner'].sudo()
|
||||
domain = [('id', 'in', partner_ids)]
|
||||
if status:
|
||||
domain.append(('fusion_followup_status', '=', status))
|
||||
partners = Partner.search(domain, limit=int(limit))
|
||||
engine = self.env['fusion.followup.engine']
|
||||
rows = []
|
||||
for p in partners:
|
||||
try:
|
||||
overdue = engine.get_overdue_for_partner(p)
|
||||
rows.append({
|
||||
'partner_id': p.id,
|
||||
'partner_name': p.name,
|
||||
'overdue_amount': overdue['aging']['total_overdue_amount'],
|
||||
'risk_score': overdue['risk']['score'],
|
||||
'risk_band': overdue['risk']['band'],
|
||||
'status': p.fusion_followup_status,
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
return {'count': len(rows), 'total': len(partner_ids), 'partners': rows}
|
||||
|
||||
def list_overdue_via_enterprise(self, status=None, limit=50, company_id=None):
|
||||
return {
|
||||
'partners': [], 'count': 0, 'total': 0,
|
||||
'error': 'Enterprise account_followup must be used from its UI',
|
||||
}
|
||||
|
||||
def list_overdue_via_community(self, status=None, limit=50, company_id=None):
|
||||
return {
|
||||
'partners': [], 'count': 0, 'total': 0,
|
||||
'error': 'No follow-up engine in pure Community',
|
||||
}
|
||||
|
||||
|
||||
register_adapter('followup', FollowupAdapter)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting Follow-up',
|
||||
'version': '19.0.1.0.15',
|
||||
'version': '19.0.1.0.16',
|
||||
'category': 'Accounting/Accounting',
|
||||
'summary': 'AI-augmented customer follow-ups (dunning) for unpaid invoices.',
|
||||
'description': """
|
||||
|
||||
@@ -11,3 +11,4 @@ from . import test_account_move_line_inherit
|
||||
from . import test_fusion_followup_engine
|
||||
from . import test_engine_integration
|
||||
from . import test_followup_controller
|
||||
from . import test_followup_adapter
|
||||
|
||||
42
fusion_accounting_followup/tests/test_followup_adapter.py
Normal file
42
fusion_accounting_followup/tests/test_followup_adapter.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""FollowupAdapter wiring tests — engine paths."""
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
from odoo.addons.fusion_accounting_ai.services.data_adapters.followup import (
|
||||
FollowupAdapter,
|
||||
)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestFollowupAdapter(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.adapter = FollowupAdapter(self.env)
|
||||
|
||||
def test_list_overdue_via_fusion_returns_dict(self):
|
||||
result = self.adapter.list_overdue_via_fusion(
|
||||
company_id=self.env.company.id,
|
||||
)
|
||||
self.assertIn('partners', result)
|
||||
self.assertIn('total', result)
|
||||
self.assertIn('count', result)
|
||||
|
||||
def test_list_overdue_via_community_returns_error(self):
|
||||
result = self.adapter.list_overdue_via_community()
|
||||
self.assertIn('error', result)
|
||||
|
||||
def test_send_followup_via_fusion_no_overdue(self):
|
||||
partner = self.env['res.partner'].create({'name': 'AdapterTest'})
|
||||
result = self.adapter.send_followup_via_fusion(
|
||||
partner_id=partner.id, force=True,
|
||||
)
|
||||
self.assertIn(
|
||||
result.get('status', ''),
|
||||
('no_action', 'no_overdue', 'sent', 'manual_review'),
|
||||
)
|
||||
|
||||
def test_send_followup_via_community_returns_error(self):
|
||||
result = self.adapter.send_followup_via_community(partner_id=1)
|
||||
self.assertIn('error', result)
|
||||
Reference in New Issue
Block a user