from odoo import api, fields, models, _ class MasterControl(models.Model): _name = 'master.control' _description = 'Master Control' _order = 'sequence, name, id' sequence = fields.Integer() name = fields.Char(string='Master Name', required=True) code = fields.Char(string='Code', required=True) user_ids = fields.Many2many( 'res.users', 'master_control_res_users_rel', 'master_control_id', 'user_id', string='Users', ) menu_line_ids = fields.One2many( 'master.control.menu.line', 'master_control_id', string='Menus', domain=[('menu_id.parent_id', '=', False)], ) submenu_line_ids = fields.One2many( 'master.control.menu.line', 'master_control_id', string='Sub Menus', domain=[('menu_id.parent_id', '!=', False)], ) allowed_menu_ids = fields.Many2many( 'ir.ui.menu', compute='_compute_allowed_menu_ids', string='Allowed Menus', ) _sql_constraints = [ ('master_control_code_unique', 'unique(code)', 'Master code must be unique.'), ] @api.depends('name', 'code') def _compute_display_name(self): for record in self: if record.name: record.display_name = record.name + (f' ({record.code})' if record.code else '') else: record.display_name = False @api.depends('menu_line_ids.show_menu', 'menu_line_ids.menu_id', 'submenu_line_ids.show_menu', 'submenu_line_ids.menu_id') def _compute_allowed_menu_ids(self): for record in self: all_lines = record.menu_line_ids | record.submenu_line_ids visible_menu_ids = record._expand_menu_ids(all_lines.filtered('show_menu').mapped('menu_id').ids) hidden_menu_ids = record._expand_menu_ids(all_lines.filtered(lambda line: not line.show_menu).mapped('menu_id').ids) menus = self.env['ir.ui.menu'].browse(list(visible_menu_ids - hidden_menu_ids)) ancestors = self.env['ir.ui.menu'] for menu in menus: parent = menu.parent_id while parent: ancestors |= parent parent = parent.parent_id record.allowed_menu_ids = menus | ancestors def _expand_menu_ids(self, menu_ids): if not menu_ids: return set() return set( self.env['ir.ui.menu'] .sudo() .with_context({'ir.ui.menu.full_list': True}) .search([('id', 'child_of', list(menu_ids))]).ids ) def _get_all_menus_sql(self): self.env.cr.execute(""" WITH RECURSIVE menu_tree AS ( SELECT id, parent_id FROM ir_ui_menu WHERE parent_id IS NULL AND active = true UNION ALL SELECT menu.id, menu.parent_id FROM ir_ui_menu menu JOIN menu_tree tree ON tree.id = menu.parent_id WHERE menu.active = true ) SELECT id, parent_id FROM menu_tree ORDER BY parent_id NULLS FIRST, id """) return self.env.cr.dictfetchall() def _sync_menu_lines(self, create_missing_only=False): line_model = self.env['master.control.menu.line'] notification_count = 0 for record in self: menus = record._get_all_menus_sql() children_map = {} for menu in menus: children_map.setdefault(menu['parent_id'], []).append(menu) existing_lines = { line.menu_id.id: line for line in record.menu_line_ids | record.submenu_line_ids } if not create_missing_only: (record.menu_line_ids | record.submenu_line_ids).unlink() existing_lines = {} for main_menu in children_map.get(None, []): parent_line = existing_lines.get(main_menu['id']) if not parent_line: parent_line = line_model.create({ 'master_control_id': record.id, 'menu_id': main_menu['id'], 'show_menu': True, }) existing_lines[main_menu['id']] = parent_line notification_count += 1 stack = list(children_map.get(main_menu['id'], [])) while stack: submenu = stack.pop() if submenu['id'] not in existing_lines: line_model.create({ 'master_control_id': record.id, 'menu_id': submenu['id'], 'show_menu': True, 'parent_line_id': parent_line.id, }) existing_lines[submenu['id']] = True notification_count += 1 stack.extend(children_map.get(submenu['id'], [])) return notification_count def action_generate_menus(self): self._sync_menu_lines(create_missing_only=False) return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _('Success'), 'message': _('Menus generated successfully.'), 'type': 'success', 'sticky': False, }, } def action_update_menus(self): created_count = self._sync_menu_lines(create_missing_only=True) return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _('Success') if created_count else _('Info'), 'message': _('Added %s new menu(s).') % created_count if created_count else _('No new menus found to add.'), 'type': 'success' if created_count else 'info', 'sticky': False, }, } @api.model_create_multi def create(self, vals_list): records = super().create(vals_list) self.env['ir.ui.menu'].sudo().clear_caches() return records def write(self, vals): result = super().write(vals) self.env['ir.ui.menu'].sudo().clear_caches() return result class MasterControlMenuLine(models.Model): _name = 'master.control.menu.line' _description = 'Master Control Menu Line' _rec_name = 'menu_id' _order = 'menu_id' master_control_id = fields.Many2one('master.control', required=True, ondelete='cascade') menu_id = fields.Many2one('ir.ui.menu', string='Menu', required=True) show_menu = fields.Boolean(string='Show Menu', default=True) parent_menu_id = fields.Many2one('ir.ui.menu', related='menu_id.parent_id', string='Parent Menu', store=True) parent_line_id = fields.Many2one('master.control.menu.line', string='Parent Line') _sql_constraints = [ ('master_control_menu_unique', 'unique(master_control_id, menu_id)', 'Menu already exists for this master control.'), ] def open_submenus_popup_view(self): self.ensure_one() return { 'name': _('Sub Menus'), 'type': 'ir.actions.act_window', 'res_model': 'master.control.menu.line', 'view_mode': 'list', 'views': [ (self.env.ref('menu_control_center.view_master_submenu_line_list').id, 'list'), ], 'target': 'new', 'domain': [('parent_line_id', '=', self.id)], } @api.model_create_multi def create(self, vals_list): records = super().create(vals_list) self.env['ir.ui.menu'].sudo().clear_caches() return records def write(self, vals): result = super().write(vals) self.env['ir.ui.menu'].sudo().clear_caches() return result def unlink(self): result = super().unlink() self.env['ir.ui.menu'].sudo().clear_caches() return result class ResUsers(models.Model): _inherit = 'res.users' master_control_ids = fields.Many2many( 'master.control', 'master_control_res_users_rel', 'user_id', 'master_control_id', string='Master Controls', help='Masters this user is allowed to access. Updating this field also updates the Users field on the master.', ) @api.model_create_multi def create(self, vals_list): records = super().create(vals_list) if any('master_control_ids' in vals for vals in vals_list): self.env['ir.ui.menu'].sudo().clear_caches() return records def write(self, vals): result = super().write(vals) if 'master_control_ids' in vals: self.env['ir.ui.menu'].sudo().clear_caches() return result