"""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..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()