feat(fusion_accounting_followup): migration wizard backfill from account_followup
Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
'name': 'Fusion Accounting Follow-up',
|
'name': 'Fusion Accounting Follow-up',
|
||||||
'version': '19.0.1.0.26',
|
'version': '19.0.1.0.27',
|
||||||
'category': 'Accounting/Accounting',
|
'category': 'Accounting/Accounting',
|
||||||
'summary': 'AI-augmented customer follow-ups (dunning) for unpaid invoices.',
|
'summary': 'AI-augmented customer follow-ups (dunning) for unpaid invoices.',
|
||||||
'description': """
|
'description': """
|
||||||
@@ -28,6 +28,7 @@ menu hides; the engine + AI tools remain available for the chat.
|
|||||||
'depends': [
|
'depends': [
|
||||||
'fusion_accounting_core',
|
'fusion_accounting_core',
|
||||||
'fusion_accounting_ai',
|
'fusion_accounting_ai',
|
||||||
|
'fusion_accounting_migration',
|
||||||
'account',
|
'account',
|
||||||
'mail',
|
'mail',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ from . import res_partner
|
|||||||
from . import account_move_line
|
from . import account_move_line
|
||||||
from . import fusion_followup_engine
|
from . import fusion_followup_engine
|
||||||
from . import fusion_followup_cron
|
from . import fusion_followup_cron
|
||||||
|
from . import fusion_migration_wizard
|
||||||
|
|||||||
80
fusion_accounting_followup/models/fusion_migration_wizard.py
Normal file
80
fusion_accounting_followup/models/fusion_migration_wizard.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
"""Followup-specific migration step.
|
||||||
|
|
||||||
|
Backfills fusion.followup.level from Enterprise's account_followup.followup.line
|
||||||
|
records (if Enterprise account_followup is installed)."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FusionMigrationWizard(models.TransientModel):
|
||||||
|
_inherit = "fusion.migration.wizard"
|
||||||
|
|
||||||
|
def _followup_bootstrap_step(self):
|
||||||
|
"""Backfill fusion.followup.level from account_followup.followup.line."""
|
||||||
|
result = {
|
||||||
|
'step': 'followup_bootstrap',
|
||||||
|
'enterprise_module_present': False,
|
||||||
|
'created': 0, 'skipped': 0, 'errors': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enterprise's followup model — name varies by version
|
||||||
|
EnterpriseLine = self.env.get('account_followup.followup.line')
|
||||||
|
if EnterpriseLine is None:
|
||||||
|
EnterpriseLine = self.env.get('account.followup.line')
|
||||||
|
if EnterpriseLine is None:
|
||||||
|
result['enterprise_module_present'] = False
|
||||||
|
return result
|
||||||
|
result['enterprise_module_present'] = True
|
||||||
|
|
||||||
|
FusionLevel = self.env['fusion.followup.level'].sudo()
|
||||||
|
try:
|
||||||
|
ee_records = EnterpriseLine.sudo().search([])
|
||||||
|
except Exception as e:
|
||||||
|
result['errors'].append(f"Enterprise search failed: {e}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Map Enterprise tone-ish fields to ours
|
||||||
|
for ee in ee_records:
|
||||||
|
try:
|
||||||
|
# Idempotency: skip if a level with same sequence + name already exists
|
||||||
|
seq = getattr(ee, 'sequence', None) or 50
|
||||||
|
name = getattr(ee, 'name', None) or f"Migrated Level {seq}"
|
||||||
|
existing = FusionLevel.search([
|
||||||
|
('sequence', '=', seq),
|
||||||
|
('name', '=', name),
|
||||||
|
], limit=1)
|
||||||
|
if existing:
|
||||||
|
result['skipped'] += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
delay = getattr(ee, 'delay', None) or getattr(ee, 'delay_days', 7)
|
||||||
|
# Enterprise tone heuristic: scale by sequence
|
||||||
|
tone = 'gentle' if seq <= 1 else 'firm' if seq <= 2 else 'legal'
|
||||||
|
|
||||||
|
FusionLevel.create({
|
||||||
|
'name': name,
|
||||||
|
'sequence': seq + 100, # offset so we don't clash with seeded defaults
|
||||||
|
'delay_days': delay,
|
||||||
|
'tone': tone,
|
||||||
|
'active': True,
|
||||||
|
})
|
||||||
|
result['created'] += 1
|
||||||
|
except Exception as e:
|
||||||
|
result['errors'].append(f"Line {ee.id}: {e}")
|
||||||
|
|
||||||
|
_logger.info(
|
||||||
|
"fusion_accounting_followup migration: %d created, %d skipped, %d errors",
|
||||||
|
result['created'], result['skipped'], len(result['errors']))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def action_run_migration(self):
|
||||||
|
result = super().action_run_migration() if hasattr(super(), 'action_run_migration') else None
|
||||||
|
try:
|
||||||
|
self._followup_bootstrap_step()
|
||||||
|
except Exception as e:
|
||||||
|
_logger.warning("followup_bootstrap_step failed: %s", e)
|
||||||
|
return result
|
||||||
@@ -18,3 +18,4 @@ from . import test_engine_property
|
|||||||
from . import test_followup_full_flow
|
from . import test_followup_full_flow
|
||||||
from . import test_performance_benchmarks
|
from . import test_performance_benchmarks
|
||||||
from . import test_batch_followup_wizard
|
from . import test_batch_followup_wizard
|
||||||
|
from . import test_migration_round_trip
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
from odoo.tests import tagged
|
||||||
|
|
||||||
|
|
||||||
|
@tagged('post_install', '-at_install')
|
||||||
|
class TestFollowupMigrationRoundTrip(TransactionCase):
|
||||||
|
|
||||||
|
def test_bootstrap_step_runs(self):
|
||||||
|
wizard = self.env['fusion.migration.wizard'].create({})
|
||||||
|
result = wizard._followup_bootstrap_step()
|
||||||
|
self.assertEqual(result['step'], 'followup_bootstrap')
|
||||||
|
# Either Enterprise present or not — both OK
|
||||||
|
self.assertIn(result['enterprise_module_present'], [True, False])
|
||||||
|
|
||||||
|
def test_bootstrap_idempotent(self):
|
||||||
|
wizard = self.env['fusion.migration.wizard'].create({})
|
||||||
|
first = wizard._followup_bootstrap_step()
|
||||||
|
second = wizard._followup_bootstrap_step()
|
||||||
|
# Second run skips what first created (or both no-op)
|
||||||
|
if first['enterprise_module_present']:
|
||||||
|
self.assertGreaterEqual(second['skipped'], first['created'])
|
||||||
Reference in New Issue
Block a user