fix(fusion_accounting_followup): seeded levels + migration idempotency
- test_create_minimal/negative_delay used sequence=1, which now collides with the seeded Friendly Reminder level. Use sequences 901/902. - migration backfill: search by name (not raw seq) for idempotency, allocate sequence as max(existing)+1 to avoid both seed clashes and within-batch collisions when Enterprise has duplicate sequence values. Made-with: Cursor
This commit is contained in:
@@ -37,31 +37,38 @@ class FusionMigrationWizard(models.TransientModel):
|
|||||||
result['errors'].append(f"Enterprise search failed: {e}")
|
result['errors'].append(f"Enterprise search failed: {e}")
|
||||||
return result
|
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
|
# Map Enterprise tone-ish fields to ours
|
||||||
for ee in ee_records:
|
for ee in ee_records:
|
||||||
try:
|
try:
|
||||||
# Idempotency: skip if a level with same sequence + name already exists
|
raw_seq = getattr(ee, 'sequence', None) or 50
|
||||||
seq = getattr(ee, 'sequence', None) or 50
|
name = getattr(ee, 'name', None) or f"Migrated Level {raw_seq}"
|
||||||
name = getattr(ee, 'name', None) or f"Migrated Level {seq}"
|
# Idempotency: skip if a level with same name was already
|
||||||
existing = FusionLevel.search([
|
# backfilled in a prior migration run.
|
||||||
('sequence', '=', seq),
|
existing = FusionLevel.search([('name', '=', name)], limit=1)
|
||||||
('name', '=', name),
|
|
||||||
], limit=1)
|
|
||||||
if existing:
|
if existing:
|
||||||
result['skipped'] += 1
|
result['skipped'] += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
delay = getattr(ee, 'delay', None) or getattr(ee, 'delay_days', 7)
|
delay = getattr(ee, 'delay', None) or getattr(ee, 'delay_days', 7)
|
||||||
# Enterprise tone heuristic: scale by sequence
|
# Enterprise tone heuristic: scale by sequence
|
||||||
tone = 'gentle' if seq <= 1 else 'firm' if seq <= 2 else 'legal'
|
tone = 'gentle' if raw_seq <= 1 else 'firm' if raw_seq <= 2 else 'legal'
|
||||||
|
|
||||||
FusionLevel.create({
|
with self.env.cr.savepoint():
|
||||||
'name': name,
|
FusionLevel.create({
|
||||||
'sequence': seq + 100, # offset so we don't clash with seeded defaults
|
'name': name,
|
||||||
'delay_days': delay,
|
'sequence': next_seq,
|
||||||
'tone': tone,
|
'delay_days': delay,
|
||||||
'active': True,
|
'tone': tone,
|
||||||
})
|
'active': True,
|
||||||
|
})
|
||||||
|
next_seq += 1
|
||||||
result['created'] += 1
|
result['created'] += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
result['errors'].append(f"Line {ee.id}: {e}")
|
result['errors'].append(f"Line {ee.id}: {e}")
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ from odoo.tests import tagged
|
|||||||
class TestFusionFollowupLevel(TransactionCase):
|
class TestFusionFollowupLevel(TransactionCase):
|
||||||
|
|
||||||
def test_create_minimal(self):
|
def test_create_minimal(self):
|
||||||
|
# Note: sequences 1-3 are reserved for seeded default levels.
|
||||||
level = self.env['fusion.followup.level'].create({
|
level = self.env['fusion.followup.level'].create({
|
||||||
'name': 'Reminder', 'sequence': 1, 'delay_days': 7, 'tone': 'gentle',
|
'name': 'Reminder', 'sequence': 901, 'delay_days': 7, 'tone': 'gentle',
|
||||||
})
|
})
|
||||||
self.assertEqual(level.name, 'Reminder')
|
self.assertEqual(level.name, 'Reminder')
|
||||||
self.assertTrue(level.active)
|
self.assertTrue(level.active)
|
||||||
@@ -15,7 +16,7 @@ class TestFusionFollowupLevel(TransactionCase):
|
|||||||
def test_negative_delay_rejected(self):
|
def test_negative_delay_rejected(self):
|
||||||
with self.assertRaises(Exception):
|
with self.assertRaises(Exception):
|
||||||
self.env['fusion.followup.level'].create({
|
self.env['fusion.followup.level'].create({
|
||||||
'name': 'Bad', 'sequence': 1, 'delay_days': -5, 'tone': 'gentle',
|
'name': 'Bad', 'sequence': 902, 'delay_days': -5, 'tone': 'gentle',
|
||||||
})
|
})
|
||||||
|
|
||||||
def test_duplicate_sequence_rejected(self):
|
def test_duplicate_sequence_rejected(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user