198 lines
7.8 KiB
Python
198 lines
7.8 KiB
Python
"""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
|
|
|
|
# Pick a starting offset that doesn't clash with anything already in
|
|
# fusion_followup_level (seeded defaults at 1..3 plus any prior
|
|
# migration runs). We allocate a unique sequence per Enterprise line
|
|
# by max(existing) + 1, ensuring idempotency + within-batch uniqueness.
|
|
existing_max = max(FusionLevel.search([]).mapped('sequence') or [100])
|
|
next_seq = max(existing_max + 1, 101)
|
|
|
|
# Map Enterprise tone-ish fields to ours
|
|
for ee in ee_records:
|
|
try:
|
|
raw_seq = getattr(ee, 'sequence', None) or 50
|
|
name = getattr(ee, 'name', None) or f"Migrated Level {raw_seq}"
|
|
# Idempotency: skip if a level with same name was already
|
|
# backfilled in a prior migration run.
|
|
existing = FusionLevel.search([('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 raw_seq <= 1 else 'firm' if raw_seq <= 2 else 'legal'
|
|
|
|
with self.env.cr.savepoint():
|
|
FusionLevel.create({
|
|
'name': name,
|
|
'sequence': next_seq,
|
|
'delay_days': delay,
|
|
'tone': tone,
|
|
'active': True,
|
|
})
|
|
next_seq += 1
|
|
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 _followup_partner_state_bootstrap_step(self):
|
|
"""Migration step: copy Enterprise account_followup per-partner state
|
|
onto Fusion's fields on res.partner.
|
|
|
|
Idempotent: only updates partners whose Fusion field is at default
|
|
(no_action) and whose Enterprise field has a non-default value.
|
|
"""
|
|
self.ensure_one()
|
|
_logger.info("fusion_accounting_followup partner-state migration starting")
|
|
|
|
Partner = self.env['res.partner'].sudo()
|
|
has_status = 'followup_status' in Partner._fields
|
|
has_next_date = 'payment_next_action_date' in Partner._fields
|
|
has_line = 'followup_line_id' in Partner._fields
|
|
if not (has_status or has_next_date or has_line):
|
|
_logger.info(
|
|
"Enterprise account_followup partner fields not present \u2014 skipping")
|
|
return {
|
|
'step': 'followup_partner_state',
|
|
'enterprise_module_present': False,
|
|
'updated': 0, 'skipped': 0, 'errors': [],
|
|
}
|
|
|
|
result = {
|
|
'step': 'followup_partner_state',
|
|
'enterprise_module_present': True,
|
|
'updated': 0, 'skipped': 0, 'errors': [],
|
|
}
|
|
|
|
domain_terms = []
|
|
if has_status:
|
|
domain_terms.append(('followup_status', '!=', 'no_action_needed'))
|
|
if has_next_date:
|
|
domain_terms.append(('payment_next_action_date', '!=', False))
|
|
if not domain_terms:
|
|
_logger.info("No usable Enterprise follow-up fields \u2014 skipping")
|
|
return result
|
|
if len(domain_terms) > 1:
|
|
domain = ['|'] * (len(domain_terms) - 1) + domain_terms
|
|
else:
|
|
domain = domain_terms
|
|
candidates = Partner.search(domain)
|
|
_logger.info(
|
|
"Found %d partners with non-default Enterprise follow-up state",
|
|
len(candidates))
|
|
|
|
Level = self.env['fusion.followup.level'].sudo()
|
|
today = fields.Date.today()
|
|
|
|
status_map = {
|
|
'in_need_of_action': 'action_due',
|
|
'with_overdue_invoices': 'action_due',
|
|
'no_action_needed': 'no_action',
|
|
}
|
|
|
|
for partner in candidates:
|
|
try:
|
|
if partner.fusion_followup_status not in (False, 'no_action'):
|
|
result['skipped'] += 1
|
|
continue
|
|
|
|
vals = {}
|
|
|
|
ent_status = (
|
|
getattr(partner, 'followup_status', None)
|
|
if has_status else None)
|
|
if ent_status and ent_status in status_map:
|
|
vals['fusion_followup_status'] = status_map[ent_status]
|
|
|
|
next_date = (
|
|
getattr(partner, 'payment_next_action_date', False)
|
|
if has_next_date else False)
|
|
if next_date and next_date > today:
|
|
vals['fusion_followup_paused_until'] = next_date
|
|
vals['fusion_followup_status'] = 'paused'
|
|
|
|
ent_line = (
|
|
getattr(partner, 'followup_line_id', None)
|
|
if has_line else None)
|
|
if ent_line:
|
|
fusion_level = Level.search([
|
|
('name', '=', ent_line.name),
|
|
], limit=1)
|
|
if fusion_level:
|
|
vals['fusion_followup_last_level_id'] = fusion_level.id
|
|
|
|
if vals:
|
|
partner.write(vals)
|
|
result['updated'] += 1
|
|
_logger.debug(
|
|
"Migrated partner %s: %s", partner.name, vals)
|
|
else:
|
|
result['skipped'] += 1
|
|
|
|
except Exception as e:
|
|
result['errors'].append(
|
|
f"Partner {partner.id} ({partner.name}): {e}")
|
|
_logger.warning(
|
|
"Migration failed for partner %s: %s", partner.id, e)
|
|
|
|
_logger.info(
|
|
"fusion_accounting_followup partner-state migration: "
|
|
"updated=%d skipped=%d errors=%d",
|
|
result['updated'], 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)
|
|
try:
|
|
self._followup_partner_state_bootstrap_step()
|
|
except Exception as e:
|
|
_logger.warning("followup_partner_state_bootstrap_step failed: %s", e)
|
|
return result
|