Files
Django/jituan/services/kefu_menu.py
XingQue 4b5130c824 feat: 会员/履约金页文案按俱乐部可配置
新增 club_recharge_page_copy 表与小程序/后台读写接口,kefu 菜单挂「充值页文案」。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 00:04:23 +08:00

184 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""客服后台菜单可见性(侧栏展示,不影响各页面原有权限校验)。"""
import logging
from types import SimpleNamespace
from backend.utils import _AllPermissions, verify_kefu_permission
from jituan.models import KefuMenuPage
from jituan.services.admin_context import (
build_admin_club_context,
can_access_admin_user_management,
can_manage_admin_assignments,
get_allowed_club_ids,
is_system_super_admin,
)
from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
from jituan.services.club_rbac import user_bound_role_names, describe_roles_in_scope
from jituan.services.kefu_menu_definitions import KEFU_MENU_ROW_DEFS
logger = logging.getLogger(__name__)
def _is_super_permissions(permissions):
return isinstance(permissions, _AllPermissions)
def _has_super_perm(permissions):
"""俱乐部超级管理权限 000001角色/后台用户等),不含集团级配置。"""
if _is_super_permissions(permissions):
return True
return '000001' in permissions
def _page_visible(page, permissions, user):
if page.require_group_manage:
return can_manage_admin_assignments(user)
if getattr(page, 'require_admin_user_manage', False):
return can_access_admin_user_management(user, permissions)
if page.require_super:
return _has_super_perm(permissions) or is_system_super_admin(user)
# 仅系统级超管可见全部菜单;俱乐部 000001 不展开业务菜单
if is_system_super_admin(user):
return True
codes = page.perm_codes or []
if not codes:
return False
return any(code in permissions for code in codes)
def _permission_codes_list(permissions):
if isinstance(permissions, _AllPermissions):
return list(dict.fromkeys(permissions)) if len(permissions) else ['000001']
return list(permissions) if permissions else []
def _builtin_menu_pages():
"""DB 无数据时的内置菜单(与 seed_kefu_menu 定义一致)。"""
pages = []
for row in KEFU_MENU_ROW_DEFS:
pages.append(SimpleNamespace(
page_id=row['page_id'],
name=row['name'],
path=row.get('path', ''),
parent_id=row.get('parent_id', ''),
sort_order=row.get('sort_order', 0),
perm_codes=row.get('perm_codes', []),
require_super=row.get('require_super', False),
require_group_manage=row.get('require_group_manage', False),
require_admin_user_manage=row.get('require_admin_user_manage', False),
))
return pages
def _load_menu_pages():
"""从 DB 加载菜单;失败或空表时尝试 seed仍空则用内置定义。"""
pages = []
menu_source = 'db'
try:
pages = KefuMenuPage.objects.all().order_by('sort_order', 'page_id')
pages = list(pages)
except Exception as e:
logger.warning('KefuMenuPage.objects 读取失败: %s', e)
pages = []
if not pages:
try:
pages = KefuMenuPage.query.order_by('sort_order', 'page_id').to_list()
except Exception as e:
logger.warning('KefuMenuPage.query 读取失败: %s', e)
pages = []
need_seed = not pages or not any(
p.page_id in ('miniapp.assets', 'product.fake-orders', 'miniapp.recharge-copy') for p in pages
)
if need_seed:
try:
from django.core.management import call_command
call_command('seed_kefu_menu')
pages = list(KefuMenuPage.objects.all().order_by('sort_order', 'page_id'))
except Exception as e:
logger.warning('seed_kefu_menu 自动执行失败: %s', e)
if not pages:
pages = _builtin_menu_pages()
menu_source = 'builtin'
logger.warning('kefu_menu_page 表无数据,使用内置菜单定义(请执行 migrate jituan + seed_kefu_menu')
return pages, menu_source
def build_menu_access_payload(request, username=None):
"""
根据用户 gvsdsdk 权限计算可见菜单 page_id 列表。
返回 (payload, error_response)error_response 非空时表示校验失败。
"""
phone = (username or getattr(request.user, 'Phone', '') or '').strip()
kefu, permissions = verify_kefu_permission(request, phone)
if kefu is None:
return None, permissions
pages, menu_source = _load_menu_pages()
visible_ids = []
for page in pages:
if not page.path:
continue
if _page_visible(page, permissions, request.user):
visible_ids.append(page.page_id)
for page in pages:
if page.path:
continue
children = [p for p in pages if p.parent_id == page.page_id and p.path]
if any(child.page_id in visible_ids for child in children):
visible_ids.append(page.page_id)
from jituan.constants import DATA_SCOPE_SINGLE
from jituan.models import AdminAssignment
club_ctx = build_admin_club_context(request.user)
club_id = getattr(request, 'club_id', None) or resolve_club_id_from_request(request)
scope = getattr(request, 'club_scope', None) or resolve_club_scope(request)
allowed = list(get_allowed_club_ids(request.user))
perm_codes = _permission_codes_list(permissions)
role_names = user_bound_role_names(request.user, club_id, scope, allowed)
roles_detail = describe_roles_in_scope(request.user, club_id, scope, allowed)
perm_hint = ''
if scope == DATA_SCOPE_SINGLE and not perm_codes:
has_assignment = AdminAssignment.query.filter(
yonghuid=request.user.UserUID, status=1, club_id=club_id,
).exists()
if has_assignment and not role_names:
perm_hint = (
f'当前俱乐部({club_id})已有任职但未绑定功能角色,'
'请在「后台用户」中为该俱乐部分配角色后重新登录。'
)
elif not has_assignment:
perm_hint = (
f'当前俱乐部({club_id})无功能角色;'
'请确认已在该俱乐部绑定 gvsdsdk 角色(与数据范围任职是两套配置)。'
)
path_by_id = {p.page_id: p.path for p in pages if p.path}
visible_paths = [path_by_id[pid] for pid in visible_ids if pid in path_by_id]
return {
'visible_page_ids': sorted(set(visible_ids)),
'visible_paths': visible_paths,
'permission_codes': perm_codes,
'role_names': role_names,
'yonghuid': getattr(request.user, 'UserUID', '') or '',
'club_id': club_id,
'club_scope': scope,
'effective_club_id': club_id,
'perm_hint': perm_hint,
'roles_in_scope': roles_detail,
'can_switch_club': bool(club_ctx.get('can_switch_club')),
'is_group_admin': bool(club_ctx.get('is_group_admin')),
'menu_ready': True,
'menu_source': menu_source,
}, None