diff --git a/fusion_accounting_followup/__manifest__.py b/fusion_accounting_followup/__manifest__.py index dfb505bf..4c37bdaf 100644 --- a/fusion_accounting_followup/__manifest__.py +++ b/fusion_accounting_followup/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Fusion Accounting Follow-up', - 'version': '19.0.1.0.29', + 'version': '19.0.1.0.30', 'category': 'Accounting/Accounting', 'summary': 'AI-augmented customer follow-ups (dunning) for unpaid invoices.', 'description': """ diff --git a/fusion_accounting_followup/tests/__init__.py b/fusion_accounting_followup/tests/__init__.py index cc9e8340..208a9ae0 100644 --- a/fusion_accounting_followup/tests/__init__.py +++ b/fusion_accounting_followup/tests/__init__.py @@ -21,3 +21,4 @@ from . import test_batch_followup_wizard from . import test_migration_round_trip from . import test_coexistence from . import test_followup_tours +from . import test_local_llm_compat diff --git a/fusion_accounting_followup/tests/test_local_llm_compat.py b/fusion_accounting_followup/tests/test_local_llm_compat.py new file mode 100644 index 00000000..1a46b01f --- /dev/null +++ b/fusion_accounting_followup/tests/test_local_llm_compat.py @@ -0,0 +1,69 @@ +"""Local LLM compat test for followup_text_generator. + +Auto-detects LM Studio (:1234) or Ollama (:11434), skips when absent.""" + +import socket + +from odoo.tests.common import TransactionCase +from odoo.tests import tagged + + +def _server_reachable(host, port, timeout=1.0): + try: + with socket.create_connection((host, port), timeout=timeout): + return True + except (OSError, socket.timeout): + return False + + +def _detect_local_llm(): + for host, port, default_model in [ + ('host.docker.internal', 1234, 'local-model'), + ('host.docker.internal', 11434, 'llama3.1:8b'), + ('localhost', 1234, 'local-model'), + ('localhost', 11434, 'llama3.1:8b'), + ]: + if _server_reachable(host, port, timeout=0.5): + return (f'http://{host}:{port}/v1', default_model) + return (None, None) + + +@tagged('post_install', '-at_install', 'local_llm') +class TestLocalLLMFollowupText(TransactionCase): + + def setUp(self): + super().setUp() + self.base_url, self.model = _detect_local_llm() + if not self.base_url: + self.skipTest("No local LLM server detected") + + def test_followup_text_with_local_llm(self): + params = self.env['ir.config_parameter'].sudo() + prior = {k: params.get_param(k) for k in [ + 'fusion_accounting.openai_base_url', + 'fusion_accounting.openai_model', + 'fusion_accounting.provider.followup_text', + ]} + params.set_param('fusion_accounting.openai_base_url', self.base_url) + params.set_param('fusion_accounting.openai_model', self.model) + params.set_param('fusion_accounting.openai_api_key', 'lm-studio') + params.set_param('fusion_accounting.provider.followup_text', 'openai') + + try: + from odoo.addons.fusion_accounting_followup.services.followup_text_generator import ( + generate_followup_text, + ) + result = generate_followup_text( + self.env, partner_name='Acme Corp', + total_overdue=15000, currency_code='USD', + longest_overdue_days=45, tone='firm', + invoice_count=3, + risk_drivers=['8/12 invoices paid late', 'Avg 30 days late'], + ) + self.assertIn('subject', result) + self.assertIn('body', result) + self.assertIn('tone_used', result) + finally: + for k, v in prior.items(): + if v is not None: + params.set_param(k, v)