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',
|
'name': 'Fusion Accounting Assets',
|
||||||
'version': '19.0.1.0.30',
|
'version': '19.0.1.0.31',
|
||||||
'category': 'Accounting/Accounting',
|
'category': 'Accounting/Accounting',
|
||||||
'summary': 'AI-augmented asset management with depreciation schedules.',
|
'summary': 'AI-augmented asset management with depreciation schedules.',
|
||||||
'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',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ from . import account_move
|
|||||||
from . import fusion_asset_engine
|
from . import fusion_asset_engine
|
||||||
from . import fusion_assets_cron
|
from . import fusion_assets_cron
|
||||||
from . import fusion_asset_book_values_mv
|
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_disposal_wizard
|
||||||
from . import test_partial_sale_wizard
|
from . import test_partial_sale_wizard
|
||||||
from . import test_depreciation_run_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