Files
gzth/sun_badge_menu/models/ir_ui_menu.py
T
2026-09-01 18:43:57 +08:00

220 lines
10 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
from odoo import models, fields, api, tools
from odoo.osv import expression
def compute_badge_count_recursive(web_menus, menu_id):
"""
递归计算菜单的徽章数
:param web_menus: 包含所有菜单项的字典
:param menu_id: 当前菜单项的ID
:return: 如果有子菜单,则返回子菜单的 badge_count 的和;如果没有子菜单, 则返回当前菜单的 badge_count
"""
menu = web_menus[menu_id]
# 如果有子菜单,返回所有子菜单的badge_count之和
if menu['children']:
children_badge_count = 0
for child_id in menu['children']:
children_badge_count += compute_badge_count_recursive(web_menus, child_id)
return children_badge_count
# 如果没有子菜单,返回当前菜单的badge_count
return menu['badge_count']
class IrUiMenu(models.Model):
_inherit = "ir.ui.menu"
show_badge_count = fields.Boolean(string="显示菜单角标", default=True)
is_phone = fields.Boolean(string="手机菜单",default=False)
is_pc = fields.Boolean(string="PC菜单",default=True)
badge_function = fields.Char(string="菜单角标计算函数")
@api.model
@tools.ormcache_context('self._uid', 'debug', keys=('lang',))
def load_menus(self, debug):
""" Loads all menu items (all applications and their sub-menus).
:return: the menu root
:rtype: dict('children': menu_nodes)
"""
fields = ['name', 'sequence', 'parent_id', 'action', 'web_icon','show_badge_count','badge_function']
menu_roots = self.get_user_roots()
menu_roots_data = menu_roots.read(fields) if menu_roots else []
menu_root = {
'id': False,
'name': 'root',
'parent_id': [-1, ''],
'children': [menu['id'] for menu in menu_roots_data],
}
all_menus = {'root': menu_root}
if not menu_roots_data:
return all_menus
# menus are loaded fully unlike a regular tree view, cause there are a
# limited number of items (752 when all 6.1 addons are installed)
menus_domain = [('id', 'child_of', menu_roots.ids)]
blacklisted_menu_ids = self._load_menus_blacklist()
if blacklisted_menu_ids:
menus_domain = expression.AND([menus_domain, [('id', 'not in', blacklisted_menu_ids)]])
menus = self.search(menus_domain)
menu_items = menus.read(fields)
xmlids = (menu_roots + menus)._get_menuitems_xmlids()
# add roots at the end of the sequence, so that they will overwrite
# equivalent menu items from full menu read when put into id:item
# mapping, resulting in children being correctly set on the roots.
menu_items.extend(menu_roots_data)
mi_attachments = self.env['ir.attachment'].sudo().search_read(
domain=[('res_model', '=', 'ir.ui.menu'),
('res_id', 'in', [menu_item['id'] for menu_item in menu_items if menu_item['id']]),
('res_field', '=', 'web_icon_data')],
fields=['res_id', 'datas', 'mimetype'])
mi_attachment_by_res_id = {attachment['res_id']: attachment for attachment in mi_attachments}
# set children ids and xmlids
menu_items_map = {menu_item["id"]: menu_item for menu_item in menu_items}
for menu_item in menu_items:
menu_item.setdefault('children', [])
parent = menu_item['parent_id'] and menu_item['parent_id'][0]
menu_item['xmlid'] = xmlids.get(menu_item['id'], "")
if parent in menu_items_map:
menu_items_map[parent].setdefault(
'children', []).append(menu_item['id'])
attachment = mi_attachment_by_res_id.get(menu_item['id'])
if attachment:
menu_item['web_icon_data'] = attachment['datas']
menu_item['web_icon_data_mimetype'] = attachment['mimetype']
else:
menu_item['web_icon_data'] = False
menu_item['web_icon_data_mimetype'] = False
all_menus.update(menu_items_map)
# sort by sequence
for menu_id in all_menus:
all_menus[menu_id]['children'].sort(key=lambda id: all_menus[id]['sequence'])
# recursively set app ids to related children
def _set_app_id(app_id, menu):
menu['app_id'] = app_id
for child_id in menu['children']:
_set_app_id(app_id, all_menus[child_id])
for app in menu_roots_data:
app_id = app['id']
_set_app_id(app_id, all_menus[app_id])
# filter out menus not related to an app (+ keep root menu)
all_menus = {menu['id']: menu for menu in all_menus.values() if menu.get('app_id')}
all_menus['root'] = menu_root
return all_menus
def load_web_menus(self, debug):
""" Loads all menu items (all applications and their sub-menus) and
processes them to be used by the webclient. Mainly, it associates with
each application (top level menu) the action of its first child menu
that is associated with an action (recursively), i.e. with the action
to execute when the opening the app.
:return: the menus (including the images in Base64)
"""
menus = self.load_menus(debug)
web_menus = {}
for menu in menus.values():
if not menu['id']:
web_menus['root'] = {
"id": 'root',
"name": menu['name'],
"children": menu['children'],
"appID": False,
"xmlid": "",
"actionID": False,
"actionModel": False,
"actionPath": False,
"webIcon": None,
"webIconData": None,
"webIconDataMimetype": None,
"backgroundImage": menu.get('backgroundImage')
}
else:
action = menu['action']
if menu['id'] == menu['app_id']:
child = menu
while child and not action:
action = child['action']
child = menus[child['children'][0]] if child['children'] else False
action_model, action_id = action.split(',') if action else (False, False)
action_id = int(action_id) if action_id else False
badge_count = 0
res_model = False
# 增加 show_badge_count 限制是否显示"角标".
if action_model and action_id and menu['show_badge_count']:
action_record = self.env[action_model].browse(action_id).sudo()
if action_model == 'ir.actions.act_window' and action_record.view_mode != 'form':
# 不仅仅是 form 表单,获取 model
res_model = action_record.res_model
if res_model in self.env:
# 先检查 menu['badge_function']
if 'badge_function' in menu and menu['badge_function'] and hasattr(self.env[res_model], menu['badge_function']):
badge_count = getattr(self.env[res_model], menu['badge_function'])(action_record)
elif hasattr(self.env[res_model], 'get_menu_badge_count'):
badge_count = self.env[res_model].get_menu_badge_count(action_record)
elif hasattr(self.env[res_model], 'get_no_read_count'):
badge_count = self.env[res_model].get_no_read_count()
elif res_model == 'soong.workflow.record':
# 待办流程 / 传阅流程 共用同一模型,按菜单 xmlid 区分角标
if menu.get('xmlid') and 'circulation' in menu['xmlid']:
badge_count = self.env[res_model].get_workflow_circulation()
else:
badge_count = self.env[res_model].get_workflow_count()
elif hasattr(self.env[res_model], 'get_workflow_count'):
badge_count = self.env[res_model].get_workflow_count()
elif hasattr(self.env[res_model], 'get_workflow_circulation'):
badge_count = self.env[res_model].get_workflow_circulation()
elif hasattr(res_model, 'read_number'):
badge_count = self.env[res_model].read_number()
elif hasattr(res_model, 'read_coming_count'):
badge_count = self.env[res_model].read_coming_count()
elif hasattr(res_model, 'read_due_count'):
badge_count = self.env[res_model].read_due_count()
elif hasattr(res_model, 'get_is_logs2_count'):
badge_count = self.env[res_model].get_is_logs2_count()
elif hasattr(res_model, 'get_contract_count'):
badge_count = self.env[res_model].get_contract_count()
web_menus[menu['id']] = {
"id": menu['id'],
"name": menu['name'],
"children": menu['children'],
"appID": menu['app_id'],
"xmlid": menu['xmlid'],
"actionID": action_id,
"actionModel": action_model,
"webIcon": menu['web_icon'],
"webIconData": menu['web_icon_data'],
"webIconDataMimetype": menu['web_icon_data_mimetype'],
"badge_count": badge_count,
"res_model": res_model
}
# 计算父级的菜单的徽章数
for k, v in web_menus.items():
if v['children']:
web_menus[k]['badge_count'] = compute_badge_count_recursive(web_menus, k)
# 对 root 节点重新计算
return web_menus