feat(fusion_accounting_assets): migration wizard backfill from account.asset
Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting Assets',
|
||||
'version': '19.0.1.0.30',
|
||||
'version': '19.0.1.0.31',
|
||||
'category': 'Accounting/Accounting',
|
||||
'summary': 'AI-augmented asset management with depreciation schedules.',
|
||||
'description': """
|
||||
@@ -28,6 +28,7 @@ menu hides; the engine + AI tools remain available for the chat.
|
||||
'depends': [
|
||||
'fusion_accounting_core',
|
||||
'fusion_accounting_ai',
|
||||
'fusion_accounting_migration',
|
||||
'account',
|
||||
'mail',
|
||||
],
|
||||
|
||||
@@ -7,3 +7,4 @@ from . import account_move
|
||||
from . import fusion_asset_engine
|
||||
from . import fusion_assets_cron
|
||||
from . import fusion_asset_book_values_mv
|
||||
from . import fusion_migration_wizard
|
||||
|
||||
105
fusion_accounting_assets/models/fusion_migration_wizard.py
Normal file
105
fusion_accounting_assets/models/fusion_migration_wizard.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""Assets-specific migration step.
|
||||
|
||||
Backfills fusion.asset from existing account.asset rows (Enterprise) so users
|
||||
get all their existing assets in the Fusion namespace after switchover."""
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Map Enterprise method names to Fusion method names
|
||||
ENTERPRISE_METHOD_MAP = {
|
||||
'linear': 'straight_line',
|
||||
'degressive': 'declining_balance',
|
||||
'degressive_then_linear': 'declining_balance', # simplified
|
||||
'manual': 'straight_line',
|
||||
'unit_of_production': 'units_of_production',
|
||||
'units_of_production': 'units_of_production',
|
||||
}
|
||||
|
||||
|
||||
class FusionMigrationWizard(models.TransientModel):
|
||||
_inherit = "fusion.migration.wizard"
|
||||
|
||||
def _assets_bootstrap_step(self):
|
||||
"""Backfill fusion.asset from account.asset (Enterprise) if it exists."""
|
||||
result = {
|
||||
'step': 'assets_bootstrap',
|
||||
'enterprise_module_present': False,
|
||||
'created': 0, 'skipped': 0, 'errors': [],
|
||||
}
|
||||
# Check if Enterprise account.asset exists
|
||||
AccountAsset = self.env.get('account.asset')
|
||||
if AccountAsset is None:
|
||||
result['enterprise_module_present'] = False
|
||||
return result
|
||||
result['enterprise_module_present'] = True
|
||||
|
||||
FusionAsset = self.env['fusion.asset'].sudo()
|
||||
|
||||
# Iterate Enterprise records
|
||||
company_id = self.company_id.id if 'company_id' in self._fields and self.company_id else None
|
||||
domain = []
|
||||
if company_id:
|
||||
domain.append(('company_id', '=', company_id))
|
||||
|
||||
try:
|
||||
ea_records = AccountAsset.sudo().search(domain, limit=10000)
|
||||
except Exception as e:
|
||||
result['errors'].append(f"Enterprise search failed: {e}")
|
||||
return result
|
||||
|
||||
for ea in ea_records:
|
||||
try:
|
||||
# Idempotent: skip if a fusion asset with same source name exists
|
||||
existing = FusionAsset.search([
|
||||
('name', '=', ea.name),
|
||||
('cost', '=', getattr(ea, 'original_value', 0) or 0),
|
||||
('company_id', '=', ea.company_id.id),
|
||||
], limit=1)
|
||||
if existing:
|
||||
result['skipped'] += 1
|
||||
continue
|
||||
|
||||
# Map state — Enterprise has 'draft', 'open' (running), 'paused', 'close' (disposed)
|
||||
ea_state = getattr(ea, 'state', 'draft')
|
||||
state_map = {'draft': 'draft', 'open': 'running',
|
||||
'paused': 'paused', 'close': 'disposed',
|
||||
'model': 'draft'}
|
||||
state = state_map.get(ea_state, 'draft')
|
||||
|
||||
method = ENTERPRISE_METHOD_MAP.get(
|
||||
getattr(ea, 'method', 'linear'), 'straight_line')
|
||||
|
||||
FusionAsset.create({
|
||||
'name': ea.name,
|
||||
'cost': getattr(ea, 'original_value', 0) or 0,
|
||||
'salvage_value': getattr(ea, 'salvage_value', 0) or 0,
|
||||
'acquisition_date': getattr(ea, 'acquisition_date', False) or fields.Date.today(),
|
||||
'in_service_date': getattr(ea, 'prorata_date', False) or False,
|
||||
'method': method,
|
||||
'useful_life_years': getattr(ea, 'method_number', 5) or 5,
|
||||
'declining_rate_pct': getattr(ea, 'method_progress_factor', 0.2) * 100 if hasattr(ea, 'method_progress_factor') else 20.0,
|
||||
'company_id': ea.company_id.id,
|
||||
'state': state,
|
||||
})
|
||||
result['created'] += 1
|
||||
except Exception as e:
|
||||
result['errors'].append(f"Asset {ea.id}: {e}")
|
||||
|
||||
_logger.info(
|
||||
"fusion_accounting_assets migration: %d created, %d skipped, %d errors",
|
||||
result['created'], result['skipped'], len(result['errors']))
|
||||
return result
|
||||
|
||||
def action_run_migration(self):
|
||||
"""Override to add assets-bootstrap step."""
|
||||
result = super().action_run_migration() if hasattr(super(), 'action_run_migration') else None
|
||||
try:
|
||||
self._assets_bootstrap_step()
|
||||
except Exception as e:
|
||||
_logger.warning("assets_bootstrap_step failed: %s", e)
|
||||
return result
|
||||
@@ -23,3 +23,4 @@ from . import test_create_asset_wizard
|
||||
from . import test_disposal_wizard
|
||||
from . import test_partial_sale_wizard
|
||||
from . import test_depreciation_run_wizard
|
||||
from . import test_migration_round_trip
|
||||
|
||||
24
fusion_accounting_assets/tests/test_migration_round_trip.py
Normal file
24
fusion_accounting_assets/tests/test_migration_round_trip.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAssetsMigrationRoundTrip(TransactionCase):
|
||||
|
||||
def test_bootstrap_step_runs_without_enterprise(self):
|
||||
"""When Enterprise account.asset is NOT installed, step is a no-op."""
|
||||
wizard = self.env['fusion.migration.wizard'].create({})
|
||||
result = wizard._assets_bootstrap_step()
|
||||
self.assertEqual(result['step'], 'assets_bootstrap')
|
||||
# In our local DB, Enterprise account.asset may or may not exist
|
||||
# If absent: enterprise_module_present is False
|
||||
# If present: created>=0
|
||||
self.assertIn(result['enterprise_module_present'], [True, False])
|
||||
|
||||
def test_bootstrap_idempotent_on_re_run(self):
|
||||
wizard = self.env['fusion.migration.wizard'].create({})
|
||||
first = wizard._assets_bootstrap_step()
|
||||
second = wizard._assets_bootstrap_step()
|
||||
# Second run should skip what the first created (or both no-op)
|
||||
if first['enterprise_module_present']:
|
||||
self.assertGreaterEqual(second['skipped'], first['created'])
|
||||
Reference in New Issue
Block a user