277 lines
9.7 KiB
Python
277 lines
9.7 KiB
Python
"""角色/后台客服按俱乐部隔离(权限码全局共用,角色实体按 club 分开)。"""
|
||
from django.db.models import Q
|
||
|
||
from gvsdsdk.models import Role, UserRole
|
||
|
||
from jituan.constants import CLUB_ID_DEFAULT, DATA_SCOPE_ALL
|
||
from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
|
||
|
||
|
||
def _global_role_q():
|
||
"""集团全局角色:club_id 为空或 NULL。"""
|
||
return Q(ClubID='') | Q(ClubID__isnull=True)
|
||
|
||
|
||
def resolve_role_club_id(request, explicit=None):
|
||
"""创建/筛选角色时使用的俱乐部;集团视图可显式指定 target_club_id。"""
|
||
if explicit:
|
||
return (explicit or '').strip()
|
||
scope = resolve_club_scope(request)
|
||
if scope == DATA_SCOPE_ALL:
|
||
return ''
|
||
return resolve_club_id_from_request(request)
|
||
|
||
|
||
def filter_roles_for_request(request):
|
||
"""
|
||
子公司视图:仅本俱乐部角色。
|
||
集团汇总视图:全部角色(含各俱乐部 + 全局 club_id 为空)。
|
||
"""
|
||
qs = Role.objects.filter(RoleStatus=1)
|
||
scope = resolve_club_scope(request)
|
||
if scope == DATA_SCOPE_ALL:
|
||
return qs
|
||
club_id = resolve_club_id_from_request(request)
|
||
return qs.filter(Q(ClubID=club_id) | _global_role_q())
|
||
|
||
|
||
def filter_kefu_users_by_admin_scope(qs, request):
|
||
"""
|
||
后台用户列表:按任职俱乐部过滤,而非仅 User.ClubID。
|
||
同一账号有多俱乐部任职时,在任一已分配俱乐部视图下均可见。
|
||
"""
|
||
from jituan.models import AdminAssignment
|
||
|
||
scope = resolve_club_scope(request)
|
||
if scope == DATA_SCOPE_ALL:
|
||
return qs
|
||
club_id = resolve_club_id_from_request(request)
|
||
assigned_uids = list(
|
||
AdminAssignment.query.filter(status=1, club_id=club_id).values_list('yonghuid', flat=True)
|
||
)
|
||
if club_id == CLUB_ID_DEFAULT:
|
||
return qs.filter(
|
||
Q(UserUID__in=assigned_uids)
|
||
| Q(ClubID=club_id)
|
||
| Q(ClubID__isnull=True)
|
||
| Q(ClubID='')
|
||
)
|
||
return qs.filter(Q(UserUID__in=assigned_uids) | Q(ClubID=club_id))
|
||
|
||
|
||
def filter_kefu_users_qs(qs, request):
|
||
"""后台客服账号列表按 User.ClubID 过滤(角色统计等场景)。"""
|
||
scope = resolve_club_scope(request)
|
||
if scope == DATA_SCOPE_ALL:
|
||
return qs
|
||
club_id = resolve_club_id_from_request(request)
|
||
if club_id == CLUB_ID_DEFAULT:
|
||
return qs.filter(Q(ClubID=club_id) | Q(ClubID__isnull=True) | Q(ClubID=''))
|
||
return qs.filter(ClubID=club_id)
|
||
|
||
|
||
def count_role_users_in_scope(role, request, kefu_user_uuids):
|
||
"""统计角色下客服数(仅计当前数据范围内的客服)。"""
|
||
from users.business_models import User
|
||
|
||
uuids = list(
|
||
UserRole.objects.filter(RoleUUID=role.RoleUUID)
|
||
.values_list('UserUUID', flat=True)
|
||
)
|
||
if not uuids:
|
||
return 0
|
||
qs = User.objects.filter(UserUUID__in=uuids, KefuProfile__isnull=False)
|
||
qs = filter_kefu_users_qs(qs, request)
|
||
if kefu_user_uuids is not None:
|
||
qs = qs.filter(UserUUID__in=kefu_user_uuids)
|
||
return qs.count()
|
||
|
||
|
||
def role_name_exists(role_name, club_id):
|
||
return Role.objects.filter(RoleName=role_name, ClubID=club_id or '').exists()
|
||
|
||
|
||
def resolve_role_by_name(role_name, club_id=None):
|
||
"""按角色名解析 Role;优先匹配指定俱乐部,其次全局角色(club_id 为空)。"""
|
||
name = (role_name or '').strip()
|
||
if not name:
|
||
return None
|
||
qs = Role.objects.filter(RoleName=name, RoleStatus=1)
|
||
cid = (club_id or '').strip()
|
||
if cid:
|
||
hit = qs.filter(ClubID=cid).first()
|
||
if hit:
|
||
return hit
|
||
return qs.filter(_global_role_q()).first()
|
||
|
||
|
||
def role_uuid_bytes(role_uuid):
|
||
"""RoleUUID 统一转 bytes(兼容 UUIDObj / bytes / str)。"""
|
||
from gvsdsdk.model_utils import UUIDObj
|
||
if isinstance(role_uuid, bytes):
|
||
return role_uuid
|
||
return UUIDObj(role_uuid).bytes
|
||
|
||
|
||
def resolve_roles_for_binding(role_names, club_id=None):
|
||
"""
|
||
按角色名解析并去重;任一角色在当前俱乐部下找不到则整体失败。
|
||
返回 (roles, missing_names)。
|
||
"""
|
||
resolved = []
|
||
missing = []
|
||
seen_bytes = set()
|
||
for raw in role_names or []:
|
||
name = (raw or '').strip()
|
||
if not name:
|
||
continue
|
||
role = resolve_role_by_name(name, club_id)
|
||
if not role:
|
||
missing.append(name)
|
||
continue
|
||
key = role_uuid_bytes(role.RoleUUID)
|
||
if key in seen_bytes:
|
||
continue
|
||
seen_bytes.add(key)
|
||
resolved.append(role)
|
||
return resolved, missing
|
||
|
||
|
||
def filter_role_uuids_for_club_scope(role_uuids, club_id, scope, allowed_club_ids=None):
|
||
"""
|
||
按当前俱乐部上下文过滤 UserRole 绑定的 RoleUUID。
|
||
SINGLE_CLUB:本俱乐部角色 + 全局角色(ClubID 空/NULL)。
|
||
ALL_CLUBS:已授权各俱乐部角色 + 全局角色。
|
||
"""
|
||
if not role_uuids:
|
||
return []
|
||
qs = Role.objects.filter(RoleUUID__in=role_uuids, RoleStatus=1)
|
||
if scope == DATA_SCOPE_ALL:
|
||
club_ids = [c for c in (allowed_club_ids or []) if c]
|
||
q = _global_role_q()
|
||
if club_ids:
|
||
q |= Q(ClubID__in=club_ids)
|
||
qs = qs.filter(q)
|
||
else:
|
||
cid = (club_id or CLUB_ID_DEFAULT).strip()
|
||
qs = qs.filter(Q(ClubID=cid) | _global_role_q())
|
||
return list(qs.values_list('RoleUUID', flat=True))
|
||
|
||
|
||
def resolve_permission_codes_for_club(user, club_id, allowed_club_ids=None, scope=None):
|
||
"""当前俱乐部上下文下的 gvsdsdk 权限码(与 verify_kefu_permission / 菜单共用)。"""
|
||
from jituan.constants import DATA_SCOPE_SINGLE
|
||
from gvsdsdk.models import Permission as SdkPermission, RolePermission as SdkRolePermission, UserRole as SdkUserRole
|
||
|
||
eff_scope = scope or DATA_SCOPE_SINGLE
|
||
role_uuids = list(SdkUserRole.objects.filter(
|
||
UserUUID=user.UserUUID,
|
||
).values_list('RoleUUID', flat=True))
|
||
role_uuids = filter_role_uuids_for_club_scope(
|
||
role_uuids, club_id, eff_scope, allowed_club_ids,
|
||
)
|
||
if not role_uuids:
|
||
return []
|
||
perm_uuids = SdkRolePermission.objects.filter(
|
||
RoleUUID__in=role_uuids,
|
||
).values_list('PermUUID', flat=True)
|
||
return list(
|
||
SdkPermission.objects.filter(
|
||
PermUUID__in=perm_uuids, PermStatus=1,
|
||
).values_list('PermCode', flat=True).distinct()
|
||
)
|
||
|
||
|
||
def resolve_effective_permission_codes(user, club_id, scope, allowed_club_ids=None):
|
||
"""
|
||
菜单与各业务接口统一权限码:当前俱乐部 gvsdsdk 角色 + 过渡期 legacy 兜底。
|
||
"""
|
||
from backend.utils import _legacy_backend_role_permissions
|
||
from gvsdsdk.models import UserRole as SdkUserRole
|
||
|
||
codes = resolve_permission_codes_for_club(
|
||
user, club_id, allowed_club_ids, scope=scope,
|
||
)
|
||
if codes:
|
||
return codes
|
||
|
||
legacy_codes, _ = _legacy_backend_role_permissions(getattr(user, 'Phone', ''))
|
||
if not legacy_codes:
|
||
return []
|
||
|
||
if not SdkUserRole.objects.filter(UserUUID=user.UserUUID).exists():
|
||
return list(legacy_codes)
|
||
|
||
# gvsdsdk 有绑定但当前俱乐部视图下无权限码:单俱乐部账号仍走 legacy(未迁移完)
|
||
allowed = {c for c in (allowed_club_ids or []) if c}
|
||
if len(allowed) <= 1:
|
||
return list(legacy_codes)
|
||
return []
|
||
|
||
|
||
def describe_roles_in_scope(user, club_id, scope, allowed_club_ids=None):
|
||
"""诊断:当前俱乐部上下文中生效的角色与权限码。"""
|
||
from gvsdsdk.models import Permission as SdkPermission, RolePermission as SdkRolePermission, UserRole as SdkUserRole
|
||
|
||
all_bound = list(SdkUserRole.objects.filter(
|
||
UserUUID=user.UserUUID,
|
||
).values_list('RoleUUID', flat=True))
|
||
in_scope = filter_role_uuids_for_club_scope(
|
||
all_bound, club_id, scope, allowed_club_ids,
|
||
)
|
||
rows = []
|
||
for role in Role.objects.filter(RoleUUID__in=in_scope, RoleStatus=1):
|
||
rcid = (getattr(role, 'ClubID', None) or '').strip() or '(全局)'
|
||
perm_uuids = SdkRolePermission.objects.filter(
|
||
RoleUUID=role.RoleUUID,
|
||
).values_list('PermUUID', flat=True)
|
||
perm_codes = list(
|
||
SdkPermission.objects.filter(
|
||
PermUUID__in=perm_uuids, PermStatus=1,
|
||
).values_list('PermCode', flat=True)
|
||
)
|
||
rows.append({
|
||
'role_name': role.RoleName,
|
||
'club_id': rcid,
|
||
'perm_codes': perm_codes,
|
||
})
|
||
return {
|
||
'bound_role_count': len(all_bound),
|
||
'in_scope_role_count': len(in_scope),
|
||
'roles': rows,
|
||
}
|
||
|
||
|
||
def user_bound_role_names(user, club_id=None, scope=None, allowed_club_ids=None):
|
||
from backend.utils import _legacy_backend_role_permissions
|
||
|
||
uuids = list(
|
||
UserRole.objects.filter(UserUUID=user.UserUUID).values_list('RoleUUID', flat=True)
|
||
)
|
||
if club_id is not None and scope is not None:
|
||
uuids = filter_role_uuids_for_club_scope(uuids, club_id, scope, allowed_club_ids)
|
||
names = []
|
||
if uuids:
|
||
names = list(
|
||
Role.objects.filter(RoleUUID__in=uuids, RoleStatus=1).values_list('RoleName', flat=True)
|
||
)
|
||
if club_id is None:
|
||
_, legacy_names = _legacy_backend_role_permissions(getattr(user, 'Phone', ''))
|
||
return sorted(set(names + legacy_names))
|
||
return sorted(set(names))
|
||
|
||
|
||
def resolve_kefu_admin_user(yonghuid=None, phone=None):
|
||
"""定位后台客服 User;优先 yonghuid(同手机号多账号时必须用 ID)。"""
|
||
from users.business_models import User
|
||
|
||
uid = (yonghuid or '').strip()
|
||
if uid:
|
||
user = User.objects.filter(UserUID=uid, KefuProfile__isnull=False).first()
|
||
if user:
|
||
return user
|
||
ph = (phone or '').strip()
|
||
if not ph:
|
||
return None
|
||
return User.objects.filter(Phone=ph, KefuProfile__isnull=False).order_by('-UserCreateTime').first()
|