Files
Odoo-Modules/fusion_accounting_migration/models/ir_module_module.py
gsinghpal de71a61a8b fix(fusion_accounting_migration): add menu + tighten safety-guard test coverage
Addresses code review feedback on Task 17:
- Add menuitem so 'Fusion Accounting -> Migrate from Enterprise' is reachable
  (the UserError guidance now actually works). Placed at top level since
  parenting under fusion_accounting_ai.menu_fusion_accounting_root would
  require adding that module as a hard dep, which is wrong semantically
  (migration should not require AI). Both menuitems carry the admin group
  so the menu stays hidden from users who can't open the wizard anyway.
- Update the UserError wording to "Fusion Accounting -> Migrate from
  Enterprise" (no longer "Settings -> ...") to match the actual menu
  location; 'migration' is preserved per the test's assertIn check.
- Add skipTest guard to test_uninstall_not_blocked_when_migration_completed
  so it doesn't pass vacuously on Community-only CI (the guard's
  `if not installed: continue` would otherwise return True regardless of
  the flag value, giving a false green).
- Move GUARDED_MODULES import to top of wizards/migration_wizard.py
  (no circular-import risk -- models/ir_module_module.py doesn't import
  from wizards/).
- Expand docstrings on button_immediate_uninstall and module_uninstall
  overrides to note they may both fire in a single UI uninstall call
  and that the guard is idempotent (pure read + raise).

Made-with: Cursor
2026-04-19 00:51:32 -04:00

85 lines
3.2 KiB
Python

"""Safety guard: blocks Odoo Enterprise accounting uninstall until migration runs.
For each Enterprise accounting module the user attempts to uninstall, the
guard checks an ir.config_parameter flag named:
fusion_accounting.migration.<module_name>.completed
If the flag is False/unset and the module is currently installed, the guard
raises UserError pointing the user to the top-level
Fusion Accounting -> Migrate from Enterprise menu.
The migration wizard sets the flag to True after a successful migration run
for that module.
"""
from odoo import _, api, models
from odoo.exceptions import UserError
GUARDED_MODULES = (
'account_accountant',
'account_reports',
'accountant',
'account_followup',
'account_asset',
'account_budget',
'account_loans',
)
class IrModuleModule(models.Model):
_inherit = "ir.module.module"
@api.model
def _fusion_check_uninstall_guard(self, module_names):
"""Verify it's safe to uninstall the given modules.
Returns True if all checks pass; raises UserError otherwise.
"""
Param = self.env['ir.config_parameter'].sudo()
for name in module_names:
if name not in GUARDED_MODULES:
continue
installed = self.sudo().search_count([
('name', '=', name), ('state', '=', 'installed'),
])
if not installed:
continue
flag_key = f'fusion_accounting.migration.{name}.completed'
if Param.get_param(flag_key, default='False').lower() != 'true':
raise UserError(_(
"Cannot uninstall %s: the Fusion Accounting migration "
"for this module has not run yet. Please open\n"
" Fusion Accounting -> Migrate from Enterprise\n"
"and run the migration before uninstalling. Once the "
"migration has completed, the safety guard will allow "
"uninstall.\n\n"
"If you genuinely want to uninstall WITHOUT migrating "
"(data will be lost), set the parameter %s to True manually.",
name, flag_key,
))
return True
def button_immediate_uninstall(self):
"""Override to invoke the safety guard before allowing uninstall.
Both this and ``module_uninstall`` below can fire in a single UI
uninstall call (button_immediate_uninstall -> module_uninstall). The
guard is a pure read + raise, so re-running it is idempotent: on the
happy path it just re-confirms; on the blocked path the first call
already raised and the second is never reached.
"""
self._fusion_check_uninstall_guard(self.mapped('name'))
return super().button_immediate_uninstall()
def module_uninstall(self):
"""Override the lower-level uninstall path too (CLI / API uninstall).
See ``button_immediate_uninstall`` above -- both overrides may run in
the same UI uninstall; the guard is idempotent so double-invocation
is safe.
"""
self._fusion_check_uninstall_guard(self.mapped('name'))
return super().module_uninstall()