917 lines
40 KiB
Python
917 lines
40 KiB
Python
import hmac
|
||
import threading
|
||
import traceback
|
||
import uuid
|
||
import hashlib
|
||
import xmltodict
|
||
import time
|
||
import logging
|
||
import requests
|
||
import os
|
||
import secrets
|
||
import random
|
||
import string
|
||
from calendar import monthrange
|
||
from decimal import Decimal
|
||
from datetime import date, datetime, timedelta
|
||
from django.utils import timezone
|
||
from django.conf import settings
|
||
from django.core.exceptions import ObjectDoesNotExist
|
||
from django.core.paginator import Paginator
|
||
from django.db import models, transaction, IntegrityError
|
||
from django.db.models import Q, Count, Sum, F, Exists, OuterRef
|
||
from gvsdsdk.fluent import db, func, FQ
|
||
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
|
||
from django.contrib.auth.hashers import make_password
|
||
from django.views.decorators.csrf import csrf_exempt
|
||
from django.utils.decorators import method_decorator
|
||
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import status
|
||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
|
||
|
||
# 工具类导入
|
||
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
|
||
from backend.utils import (
|
||
update_dashou_daily_by_action,
|
||
update_shangjia_daily
|
||
)
|
||
from jituan.services.club_context import filter_club_char_field, filter_queryset_by_club, filter_user_related_by_club, filter_user_qs_by_club, orders_for_request
|
||
from jituan.services.catalog_scope import block_non_group_catalog_scope
|
||
from jituan.services.club_penalty import (
|
||
filter_penalty_qs, resolve_penalty_club_id, filter_gsfenhong_qs,
|
||
forbid_penalty_out_of_scope, resolve_penalty_record_club_id,
|
||
)
|
||
from jituan.services.club_user_access import forbid_if_user_out_of_scope, list_response_meta
|
||
from shop.utils import update_dianpu_daily_stat
|
||
from products.utils import update_shangpin_daily_stat
|
||
from orders.notice_tasks import dingdan_guangbo
|
||
from ..utils import (
|
||
verify_kefu_permission,
|
||
PERMISSION_TO_SHENFEN,
|
||
SHENFEN_PROFILE_MAP,
|
||
has_fadan_view_permission,
|
||
has_merchant_order_permission,
|
||
check_fadan_permission,
|
||
pick_order_id,
|
||
pick_penalty_create_params,
|
||
write_xiugai_log,
|
||
XIUGAI_LEIXING_DASHOU,
|
||
XIUGAI_LEIXING_GUANSHI,
|
||
XIUGAI_LEIXING_SHANGJIA,
|
||
XIUGAI_LEIXING_ZUZHANG,
|
||
XIUGAI_LEIXING_LABEL,
|
||
)
|
||
|
||
# models 集中导入
|
||
## gvsdsdk RBAC 模型(使用 PascalCase 字段名,匹配实际数据库表结构)
|
||
from gvsdsdk.models import Role, Permission, RolePermission, UserRole
|
||
## backend
|
||
from backend.models import WithdrawalDailyStats
|
||
|
||
## users
|
||
from users.models import (
|
||
UserGuanshi, UserBoss, UserZuzhang,
|
||
UserKefu, UserDashou, Xiugaijilu, UserShangjia, UserShenheguan
|
||
)
|
||
from users.business_models import User
|
||
|
||
## orders
|
||
from orders.models import (
|
||
CommissionRate, PlayerDeliveryImage, PenaltyRecord,
|
||
OrderPlayerHistory, Order, RefundRecord,
|
||
MerchantOrderExt, Penalty, PenaltyAppealImage, PenaltyBonus
|
||
)
|
||
|
||
## shop
|
||
from shop.models import Dianpu, DianpuMorenPeizhi, YonghuPingzheng, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi
|
||
|
||
## products
|
||
from products.models import (
|
||
Shangpin, ShangpinLeixing, ShangpinZhuanqu, Huiyuan,
|
||
DuociFenhong, Czjilu, Huiyuangoumai, Gsfenhong
|
||
)
|
||
|
||
## config
|
||
from config.models import (
|
||
WithdrawConfig, ShangjiaLianjie, AccountPermission,
|
||
TixianQuotaDefault,
|
||
DailyIncomeStat, DailyPayoutStat, PopupPage, PopupConfig, PopupImage
|
||
)
|
||
|
||
## rank
|
||
from rank.models import (
|
||
KaoheguanBankuai, Bankuai, Chenghao, KaoheCishuFeiyong,
|
||
YonghuChenghao, ShenheJilu, KaoheJiluFeiyong, KaoheJujueJilu
|
||
)
|
||
|
||
# 序列化器
|
||
from ..serializers import PopupPageSerializer
|
||
|
||
# 全局常量、日志对象
|
||
logger = logging.getLogger('houtai')
|
||
SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd']
|
||
|
||
def _serialize_permission_row(perm_row):
|
||
"""gvsdsdk Permission → 后台 Vue 约定的小写字段"""
|
||
if isinstance(perm_row, dict):
|
||
code = perm_row.get('PermCode') or perm_row.get('perm_code') or ''
|
||
name = perm_row.get('PermName') or perm_row.get('perm_name') or code
|
||
desc = perm_row.get('PermDesc') or perm_row.get('perm_desc') or ''
|
||
else:
|
||
code = perm_row.PermCode or ''
|
||
name = perm_row.PermName or code
|
||
desc = perm_row.PermDesc or ''
|
||
return {'perm_code': code, 'perm_name': name, 'perm_desc': desc}
|
||
|
||
|
||
class GetRolePermissionView(APIView):
|
||
"""
|
||
获取所有角色、权限以及角色关联的用户数
|
||
权限要求:用户必须拥有 000001 权限
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username', '').strip()
|
||
if not username:
|
||
return Response({'code': 400, 'msg': '缺少username'})
|
||
|
||
# 1. 权限校验(验证客服身份,并获取权限列表)
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return permissions # 直接返回错误响应
|
||
|
||
# 2. 检查是否有 000001 权限
|
||
if '000001' not in permissions:
|
||
return Response({'code': 403, 'msg': '您无权访问此页面'})
|
||
|
||
# 3. 获取所有权限
|
||
all_perms = Permission.objects.filter(PermStatus=1).values('PermCode', 'PermName', 'PermDesc')
|
||
permissions_list = [_serialize_permission_row(p) for p in all_perms]
|
||
|
||
from jituan.services.club_rbac import count_role_users_in_scope, filter_roles_for_request
|
||
|
||
kefu_user_uuids = list(
|
||
User.objects.filter(KefuProfile__isnull=False).values_list('UserUUID', flat=True)
|
||
)
|
||
roles = list(filter_roles_for_request(request))
|
||
# 批量预取 RolePermission + Permission(避免循环内 N+1)
|
||
_role_uuids = [r.RoleUUID for r in roles]
|
||
_rp_pairs = list(
|
||
RolePermission.objects.filter(RoleUUID__in=_role_uuids)
|
||
.values_list('RoleUUID', 'PermUUID')
|
||
) if _role_uuids else []
|
||
_perm_uuids = list({p for _, p in _rp_pairs})
|
||
_perm_rows_by_uuid = {
|
||
p['PermUUID']: p
|
||
for p in Permission.objects.filter(PermUUID__in=_perm_uuids)
|
||
.values('PermUUID', 'PermCode', 'PermName', 'PermDesc')
|
||
} if _perm_uuids else {}
|
||
_perms_by_role = {}
|
||
for role_uuid, perm_uuid in _rp_pairs:
|
||
perm_row = _perm_rows_by_uuid.get(perm_uuid)
|
||
if perm_row is not None:
|
||
_perms_by_role.setdefault(role_uuid, []).append(_serialize_permission_row(perm_row))
|
||
|
||
roles_data = []
|
||
for role in roles:
|
||
user_count = count_role_users_in_scope(role, request, kefu_user_uuids)
|
||
perms_of_role = _perms_by_role.get(role.RoleUUID, [])
|
||
roles_data.append({
|
||
'role_code': role.RoleName,
|
||
'role_name': role.RoleName,
|
||
'description': role.RoleDesc or '',
|
||
'club_id': getattr(role, 'ClubID', '') or '',
|
||
'user_count': user_count,
|
||
'permissions': perms_of_role
|
||
})
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'data': {
|
||
'roles': roles_data,
|
||
'permissions': permissions_list
|
||
}
|
||
})
|
||
|
||
|
||
class ModifyRolePermissionView(APIView):
|
||
"""
|
||
通用接口:支持删除角色、为角色添加权限、移除角色权限
|
||
操作类型通过 action 字段区分
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username', '').strip()
|
||
action = request.data.get('action') # delete_role, add_permission, remove_permission
|
||
if not username or not action:
|
||
return Response({'code': 400, 'msg': '缺少必要参数'})
|
||
|
||
# 权限校验
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return permissions
|
||
if '000001' not in permissions:
|
||
return Response({'code': 403, 'msg': '您无权进行此操作'})
|
||
|
||
# 获取角色(需要角色名称)
|
||
role_code = request.data.get('role_code')
|
||
if not role_code:
|
||
return Response({'code': 400, 'msg': '缺少角色编码'})
|
||
|
||
try:
|
||
role = Role.objects.get(RoleName=role_code)
|
||
except Role.DoesNotExist:
|
||
return Response({'code': 404, 'msg': '角色不存在'})
|
||
|
||
# 超级管理员角色的特殊限制
|
||
if role.RoleName == '管理员':
|
||
if action in ['delete_role']:
|
||
return Response({'code': 403, 'msg': '超级管理员角色不能删除'})
|
||
|
||
# 根据 action 处理
|
||
if action == 'delete_role':
|
||
# 删除角色:同时删除角色权限关联、用户角色关联
|
||
with transaction.atomic():
|
||
RolePermission.objects.filter(RoleUUID=role.RoleUUID).delete()
|
||
UserRole.objects.filter(RoleUUID=role.RoleUUID).delete()
|
||
role.delete()
|
||
return Response({'code': 0, 'msg': '角色删除成功'})
|
||
|
||
elif action == 'add_permission':
|
||
perm_code = request.data.get('perm_code')
|
||
if not perm_code:
|
||
return Response({'code': 400, 'msg': '缺少权限编码'})
|
||
if role.RoleName == '管理员' and perm_code == '000001':
|
||
return Response({'code': 403, 'msg': '此权限已存在,不能重复添加'})
|
||
try:
|
||
perm = Permission.objects.get(PermCode=perm_code)
|
||
except Permission.DoesNotExist:
|
||
return Response({'code': 404, 'msg': '权限不存在'})
|
||
# 检查是否已存在
|
||
if RolePermission.objects.filter(RoleUUID=role.RoleUUID, PermUUID=perm.PermUUID).exists():
|
||
return Response({'code': 400, 'msg': '该角色已拥有此权限'})
|
||
RolePermission.objects.create(
|
||
RolePermUUID=uuid.uuid4().bytes,
|
||
RoleUUID=role.RoleUUID,
|
||
PermUUID=perm.PermUUID,
|
||
CreateTime=timezone.now(),
|
||
)
|
||
return Response({'code': 0, 'msg': '权限添加成功'})
|
||
|
||
elif action == 'remove_permission':
|
||
perm_code = request.data.get('perm_code')
|
||
if not perm_code:
|
||
return Response({'code': 400, 'msg': '缺少权限编码'})
|
||
if role.RoleName == '管理员' and perm_code == '000001':
|
||
return Response({'code': 403, 'msg': '不能移除超级管理员的此权限'})
|
||
try:
|
||
perm = Permission.objects.get(PermCode=perm_code)
|
||
except Permission.DoesNotExist:
|
||
return Response({'code': 404, 'msg': '权限不存在'})
|
||
deleted, _ = RolePermission.objects.filter(RoleUUID=role.RoleUUID, PermUUID=perm.PermUUID).delete()
|
||
if deleted == 0:
|
||
return Response({'code': 400, 'msg': '该角色未拥有此权限'})
|
||
return Response({'code': 0, 'msg': '权限移除成功'})
|
||
|
||
elif action == 'update_role':
|
||
role_name = (request.data.get('role_name') or '').strip()
|
||
description = (request.data.get('description') or '').strip()
|
||
perm_codes = request.data.get('perm_codes') or []
|
||
if role.RoleName == '管理员':
|
||
role_name = role.RoleName
|
||
elif role_name and role_name != role.RoleName:
|
||
if Role.objects.filter(RoleName=role_name).exclude(RoleUUID=role.RoleUUID).exists():
|
||
return Response({'code': 400, 'msg': '角色名称已存在'})
|
||
with transaction.atomic():
|
||
if role.RoleName != '管理员':
|
||
if role_name:
|
||
role.RoleName = role_name
|
||
role.RoleDesc = description
|
||
role.save(update_fields=['RoleName', 'RoleDesc'])
|
||
RolePermission.objects.filter(RoleUUID=role.RoleUUID).delete()
|
||
for pc in perm_codes:
|
||
if pc == '000001' and role.RoleName != '管理员':
|
||
continue
|
||
try:
|
||
perm = Permission.objects.get(PermCode=pc, PermStatus=1)
|
||
except Permission.DoesNotExist:
|
||
continue
|
||
RolePermission.objects.create(
|
||
RolePermUUID=uuid.uuid4().bytes,
|
||
RoleUUID=role.RoleUUID,
|
||
PermUUID=perm.PermUUID,
|
||
CreateTime=timezone.now(),
|
||
)
|
||
if role.RoleName == '管理员':
|
||
try:
|
||
super_perm = Permission.objects.get(PermCode='000001', PermStatus=1)
|
||
if not RolePermission.objects.filter(
|
||
RoleUUID=role.RoleUUID, PermUUID=super_perm.PermUUID,
|
||
).exists():
|
||
RolePermission.objects.create(
|
||
RolePermUUID=uuid.uuid4().bytes,
|
||
RoleUUID=role.RoleUUID,
|
||
PermUUID=super_perm.PermUUID,
|
||
CreateTime=timezone.now(),
|
||
)
|
||
except Permission.DoesNotExist:
|
||
pass
|
||
return Response({'code': 0, 'msg': '角色保存成功'})
|
||
|
||
else:
|
||
return Response({'code': 400, 'msg': '无效的 action'})
|
||
|
||
|
||
class AddRoleView(APIView):
|
||
"""
|
||
添加角色:自动生成角色编码,前端提供角色名称、描述、选择的权限列表
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username', '').strip()
|
||
role_name = request.data.get('role_name', '').strip()
|
||
description = request.data.get('description', '').strip()
|
||
perm_codes = request.data.get('perm_codes', []) # 列表
|
||
|
||
if not username or not role_name:
|
||
return Response({'code': 400, 'msg': '缺少必要参数'})
|
||
|
||
# 权限校验
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return permissions
|
||
if '000001' not in permissions:
|
||
return Response({'code': 403, 'msg': '您无权进行此操作'})
|
||
|
||
# 检查角色名称是否已存在(同俱乐部内唯一)
|
||
from jituan.services.club_rbac import resolve_role_club_id, role_name_exists
|
||
target_club_id = resolve_role_club_id(
|
||
request, (request.data.get('target_club_id') or '').strip() or None,
|
||
)
|
||
if role_name_exists(role_name, target_club_id):
|
||
return Response({'code': 400, 'msg': '该俱乐部下角色名称已存在'})
|
||
|
||
# 生成唯一角色编码(格式:ROLE_ + 8位随机字母数字)
|
||
role_code = f"ROLE_{''.join(random.choices(string.ascii_uppercase + string.digits, k=8))}"
|
||
|
||
# 创建角色
|
||
with transaction.atomic():
|
||
role = Role.objects.create(
|
||
RoleUUID=uuid.uuid4().bytes,
|
||
TenantUUID=b'\x00' * 16,
|
||
RoleName=role_name,
|
||
RoleType=2,
|
||
RoleStatus=1,
|
||
AssignScope=0,
|
||
RoleDesc=description,
|
||
ClubID=target_club_id,
|
||
CreateTime=timezone.now(),
|
||
)
|
||
# 绑定权限(过滤掉不存在的权限编码)
|
||
for pc in perm_codes:
|
||
# 不允许添加 000001 权限(此权限仅供超级管理员)
|
||
if pc == '000001':
|
||
continue
|
||
try:
|
||
perm = Permission.objects.get(PermCode=pc)
|
||
RolePermission.objects.create(
|
||
RolePermUUID=uuid.uuid4().bytes,
|
||
RoleUUID=role.RoleUUID,
|
||
PermUUID=perm.PermUUID,
|
||
CreateTime=timezone.now(),
|
||
)
|
||
except Permission.DoesNotExist:
|
||
logger.warning(f"添加角色时忽略不存在的权限编码: {pc}")
|
||
|
||
return Response({'code': 0, 'msg': '角色添加成功', 'data': {'role_code': role_code}})
|
||
|
||
|
||
|
||
class GetAdminUserListView(APIView):
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
username = request.data.get('username', '').strip()
|
||
if not username:
|
||
return Response({'code': 400, 'msg': '缺少username'})
|
||
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return permissions
|
||
if '000001' not in permissions:
|
||
return Response({'code': 403, 'msg': '您无权访问此页面'})
|
||
|
||
phone = request.data.get('phone', '').strip()
|
||
nicheng = request.data.get('nicheng', '').strip()
|
||
role_code = request.data.get('roleCode', '').strip()
|
||
page = int(request.data.get('page', 1))
|
||
page_size = int(request.data.get('pageSize', 20))
|
||
|
||
# UserType 是 @property 非数据库字段,不能用 filter;
|
||
# KefuProfile__isnull=False 已确保只查到客服用户
|
||
users = User.query.filter(
|
||
KefuProfile__isnull=False
|
||
).select_related('KefuProfile')
|
||
|
||
from jituan.models import AdminAssignment, Club
|
||
from jituan.constants import ADMIN_ROLE_LABELS
|
||
from jituan.services.club_rbac import filter_kefu_users_by_admin_scope
|
||
users = filter_kefu_users_by_admin_scope(users, request)
|
||
|
||
if phone:
|
||
users = users.filter(Phone__icontains=phone)
|
||
if nicheng:
|
||
users = users.filter(KefuProfile__nicheng__icontains=nicheng)
|
||
|
||
# 关键修正:先求值为列表,再传入 phone__in
|
||
if role_code:
|
||
target_role = Role.objects.filter(RoleName=role_code).first()
|
||
if target_role:
|
||
user_uuids = list(
|
||
UserRole.objects.filter(RoleUUID=target_role.RoleUUID)
|
||
.values_list('UserUUID', flat=True)
|
||
)
|
||
users = users.filter(UserUUID__in=user_uuids)
|
||
else:
|
||
users = users.none()
|
||
|
||
users = users.order_by('-UserCreateTime')
|
||
paginator = Paginator(users, page_size)
|
||
page_obj = paginator.get_page(page)
|
||
|
||
# 批量预取本页所有用户的 UserRole + Role(避免循环内 N+1)
|
||
_page_users = list(page_obj)
|
||
_page_user_uuids = [u.UserUUID for u in _page_users]
|
||
_ur_pairs = list(
|
||
UserRole.objects.filter(UserUUID__in=_page_user_uuids)
|
||
.values_list('UserUUID', 'RoleUUID')
|
||
) if _page_user_uuids else []
|
||
_role_uuids_in_page = list({r for _, r in _ur_pairs})
|
||
_role_info_by_uuid = {
|
||
r.RoleUUID: {'role_name': r.RoleName, 'club_id': getattr(r, 'ClubID', '') or ''}
|
||
for r in Role.objects.filter(RoleUUID__in=_role_uuids_in_page)
|
||
} if _role_uuids_in_page else {}
|
||
_roles_by_user = {}
|
||
for user_uuid, role_uuid in _ur_pairs:
|
||
info = _role_info_by_uuid.get(role_uuid)
|
||
if info is not None:
|
||
_roles_by_user.setdefault(user_uuid, []).append({
|
||
'role_code': info['role_name'],
|
||
'role_name': info['role_name'],
|
||
'club_id': info['club_id'],
|
||
})
|
||
|
||
_page_yonghuids = [u.UserUID for u in _page_users]
|
||
_club_name_map = {c.club_id: c.name for c in Club.query.filter(status=1)}
|
||
_assignments_by_user = {}
|
||
if _page_yonghuids:
|
||
for a in AdminAssignment.query.filter(yonghuid__in=_page_yonghuids, status=1):
|
||
if a.club_id:
|
||
club_label = _club_name_map.get(a.club_id, a.club_id)
|
||
else:
|
||
club_label = '集团(全部子公司)'
|
||
_assignments_by_user.setdefault(a.yonghuid, []).append({
|
||
'id': a.id,
|
||
'club_id': a.club_id,
|
||
'club_label': club_label,
|
||
'role_code': a.role_code,
|
||
'role_name': ADMIN_ROLE_LABELS.get(a.role_code, a.role_code),
|
||
'is_primary': a.is_primary,
|
||
})
|
||
|
||
data_list = []
|
||
for user in _page_users:
|
||
roles_data = _roles_by_user.get(user.UserUUID, [])
|
||
data_list.append({
|
||
'phone': user.Phone or '',
|
||
'yonghuid': user.UserUID or '',
|
||
'nicheng': user.KefuProfile.nicheng,
|
||
'club_id': getattr(user, 'ClubID', '') or '',
|
||
'roles': roles_data,
|
||
'assignments': _assignments_by_user.get(user.UserUID, []),
|
||
'status': user.KefuProfile.zhuangtai,
|
||
'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '',
|
||
'update_time': user.KefuProfile.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user.KefuProfile, 'UpdateTime', None) else '',
|
||
'CreateTime': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '',
|
||
'UpdateTime': user.KefuProfile.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user.KefuProfile, 'UpdateTime', None) else '',
|
||
})
|
||
|
||
from jituan.services.admin_context import can_manage_admin_assignments
|
||
return Response({
|
||
'code': 0,
|
||
'data': {
|
||
'list': data_list,
|
||
'total': paginator.count,
|
||
'page': page,
|
||
'pageSize': page_size,
|
||
'can_manage_club_assignments': can_manage_admin_assignments(request.user, permissions),
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"GetAdminUserListView 异常: {e}\n{traceback.format_exc()}")
|
||
return Response({'code': 500, 'msg': f'服务器内部错误: {str(e)}'})
|
||
|
||
|
||
|
||
class GetAdminRolesView(APIView):
|
||
"""获取所有角色(用于筛选和分配)"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username', '').strip()
|
||
if not username:
|
||
return Response({'code': 400, 'msg': '缺少username'})
|
||
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return permissions
|
||
if '000001' not in permissions:
|
||
return Response({'code': 403, 'msg': '您无权访问此页面'})
|
||
|
||
from jituan.services.club_rbac import filter_roles_for_request
|
||
all_clubs = request.data.get('all_clubs') in (True, 1, '1', 'true', 'yes')
|
||
if all_clubs:
|
||
roles = Role.objects.filter(RoleStatus=1).order_by('ClubID', 'RoleName').values('RoleName', 'ClubID')
|
||
else:
|
||
roles = filter_roles_for_request(request).values('RoleName', 'ClubID')
|
||
return Response({
|
||
'code': 0,
|
||
'data': {
|
||
'roles': [
|
||
{
|
||
'role_code': r['RoleName'],
|
||
'role_name': r['RoleName'],
|
||
'club_id': r.get('ClubID') or '',
|
||
}
|
||
for r in roles
|
||
]
|
||
}
|
||
})
|
||
|
||
|
||
|
||
|
||
|
||
|
||
'''class GetAdminUserListView(APIView):
|
||
"""获取后台用户列表(支持分页、筛选)"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username', '').strip()
|
||
if not username:
|
||
return Response({'code': 400, 'msg': '缺少username'})
|
||
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return permissions
|
||
if '000001' not in permissions:
|
||
return Response({'code': 403, 'msg': '您无权访问此页面'})
|
||
|
||
# 筛选条件
|
||
phone = request.data.get('phone', '').strip()
|
||
nicheng = request.data.get('nicheng', '').strip()
|
||
role_code = request.data.get('roleCode', '').strip()
|
||
page = int(request.data.get('page', 1))
|
||
page_size = int(request.data.get('pageSize', 20))
|
||
|
||
# 基础查询:存在客服扩展表
|
||
users = User.query.filter(KefuProfile__isnull=False).select_related('KefuProfile')
|
||
if phone:
|
||
users = users.filter(Phone__icontains=phone)
|
||
if nicheng:
|
||
users = users.filter(KefuProfile__nicheng__icontains=nicheng)
|
||
if role_code:
|
||
# 筛选拥有该角色的用户
|
||
user_ids_with_role = UserRole.query.filter(role__role_code=role_code).values_list('account_id', flat=True)
|
||
users = users.filter(phone__in=user_ids_with_role)
|
||
|
||
# 排序
|
||
users = users.order_by('-CreateTime')
|
||
|
||
# 分页
|
||
paginator = Paginator(users, page_size)
|
||
page_obj = paginator.get_page(page)
|
||
|
||
# 构建返回数据
|
||
data_list = []
|
||
for user in page_obj:
|
||
# 获取用户的所有角色
|
||
user_roles = UserRole.query.filter(account_id=user.Phone).select_related('role')
|
||
roles_data = [{'role_code': ur.role.role_code, 'role_name': ur.role.role_name} for ur in user_roles]
|
||
data_list.append({
|
||
'phone': user.Phone,
|
||
'nicheng': user.KefuProfile.nicheng,
|
||
'roles': roles_data,
|
||
'status': user.KefuProfile.zhuangtai,
|
||
'CreateTime': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S'),
|
||
'UpdateTime': user.UpdateTime.strftime('%Y-%m-%d %H:%M:%S'),
|
||
})
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'data': {
|
||
'list': data_list,
|
||
'total': paginator.count,
|
||
'page': page,
|
||
'pageSize': page_size
|
||
}
|
||
})'''
|
||
|
||
|
||
|
||
|
||
|
||
class ModifyAdminUserView(APIView):
|
||
"""修改后台用户信息(昵称、密码、二级密码、状态、角色)"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username', '').strip()
|
||
action = request.data.get('action')
|
||
if not username or not action:
|
||
return Response({'code': 400, 'msg': '缺少参数'})
|
||
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return permissions
|
||
|
||
from jituan.services.admin_context import can_manage_admin_assignments
|
||
|
||
club_assign_actions = {'add_club_assignment', 'remove_club_assignment'}
|
||
if action in club_assign_actions:
|
||
if not can_manage_admin_assignments(request.user, permissions):
|
||
return Response({'code': 403, 'msg': '仅集团高层(集团总负责人/集团超管)可分配俱乐部任职'})
|
||
elif '000001' not in permissions:
|
||
return Response({'code': 403, 'msg': '您无权进行此操作'})
|
||
|
||
target_yonghuid = request.data.get('yonghuid', '').strip()
|
||
target_phone = request.data.get('phone', '').strip()
|
||
if not target_yonghuid and not target_phone:
|
||
return Response({'code': 400, 'msg': '缺少目标用户(yonghuid 或 phone)'})
|
||
|
||
from jituan.services.club_rbac import resolve_kefu_admin_user
|
||
target_user = resolve_kefu_admin_user(yonghuid=target_yonghuid, phone=target_phone)
|
||
if not target_user:
|
||
return Response({'code': 404, 'msg': '用户不存在或非后台账号'})
|
||
try:
|
||
target_kefu = target_user.KefuProfile
|
||
except UserKefu.DoesNotExist:
|
||
return Response({'code': 404, 'msg': '用户扩展信息缺失'})
|
||
|
||
with transaction.atomic():
|
||
if action == 'update_user_info':
|
||
# 修改昵称、状态、密码、二级密码
|
||
nicheng = request.data.get('nicheng')
|
||
status = request.data.get('status')
|
||
password = request.data.get('password')
|
||
erjimima = request.data.get('erjimima')
|
||
if nicheng is not None:
|
||
target_kefu.nicheng = nicheng
|
||
if status is not None:
|
||
target_kefu.zhuangtai = int(status)
|
||
if password:
|
||
if len(password) < 6:
|
||
return Response({'code': 400, 'msg': '密码至少6位'})
|
||
target_user.SetPassword(password)
|
||
if erjimima:
|
||
if len(erjimima) < 6:
|
||
return Response({'code': 400, 'msg': '二级密码至少6位'})
|
||
import bcrypt as _bcrypt
|
||
target_kefu.erjimima = _bcrypt.hashpw(erjimima.encode('utf-8'), _bcrypt.gensalt(rounds=12)).decode('utf-8')
|
||
target_user.save()
|
||
target_kefu.save()
|
||
return Response({'code': 0, 'msg': '修改成功'})
|
||
|
||
elif action == 'add_user_roles':
|
||
role_codes = request.data.get('role_codes', [])
|
||
if not role_codes:
|
||
return Response({'code': 400, 'msg': '请选择角色'})
|
||
from jituan.services.club_rbac import resolve_role_by_name
|
||
from jituan.services.club_context import resolve_club_id_from_request
|
||
bind_club_id = (request.data.get('target_club_id') or '').strip()
|
||
if not bind_club_id:
|
||
bind_club_id = resolve_club_id_from_request(request)
|
||
added, missing = [], []
|
||
for rc in role_codes:
|
||
role = resolve_role_by_name(rc, bind_club_id)
|
||
if not role:
|
||
missing.append(rc)
|
||
continue
|
||
_, created = UserRole.objects.get_or_create(
|
||
UserUUID=target_user.UserUUID,
|
||
RoleUUID=role.RoleUUID,
|
||
defaults={
|
||
'UserRoleUUID': uuid.uuid4().bytes,
|
||
'CompanyUUID': uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes,
|
||
},
|
||
)
|
||
if created:
|
||
added.append(role.RoleName)
|
||
elif role.RoleName not in added:
|
||
added.append(role.RoleName)
|
||
if not added:
|
||
return Response({
|
||
'code': 400,
|
||
'msg': f'未找到可绑定的角色:{", ".join(missing or role_codes)}。请确认顶栏俱乐部与角色所属俱乐部一致',
|
||
})
|
||
if missing:
|
||
return Response({
|
||
'code': 0,
|
||
'msg': f'已绑定:{", ".join(added)};未找到:{", ".join(missing)}',
|
||
})
|
||
return Response({'code': 0, 'msg': '角色添加成功'})
|
||
|
||
elif action == 'remove_user_role':
|
||
role_code = request.data.get('role_code')
|
||
if not role_code:
|
||
return Response({'code': 400, 'msg': '缺少角色编码'})
|
||
from jituan.services.club_rbac import resolve_role_by_name
|
||
from jituan.services.club_context import resolve_club_id_from_request
|
||
bind_club_id = (request.data.get('target_club_id') or '').strip()
|
||
if not bind_club_id:
|
||
bind_club_id = resolve_club_id_from_request(request)
|
||
role = resolve_role_by_name(role_code, bind_club_id)
|
||
if not role:
|
||
return Response({'code': 404, 'msg': '角色不存在或与所选俱乐部不匹配'})
|
||
UserRole.objects.filter(UserUUID=target_user.UserUUID, RoleUUID=role.RoleUUID).delete()
|
||
return Response({'code': 0, 'msg': '角色移除成功'})
|
||
|
||
elif action == 'add_club_assignment':
|
||
club_id = (request.data.get('club_id') or '').strip()
|
||
if not club_id:
|
||
return Response({'code': 400, 'msg': '请选择俱乐部'})
|
||
from jituan.services.admin_assignments import create_or_reactivate_assignment
|
||
from jituan.constants import DATA_SCOPE_SINGLE
|
||
role_code = (request.data.get('role_code') or 'CLUB_ADMIN').strip()
|
||
is_primary = bool(request.data.get('is_primary', False))
|
||
_, err = create_or_reactivate_assignment(
|
||
yonghuid=target_user.UserUID,
|
||
club_id=club_id,
|
||
role_code=role_code,
|
||
data_scope=DATA_SCOPE_SINGLE,
|
||
is_primary=is_primary,
|
||
granted_by=request.user.UserUID,
|
||
)
|
||
if err:
|
||
return Response({'code': 400, 'msg': err})
|
||
return Response({'code': 0, 'msg': '俱乐部任职已添加'})
|
||
|
||
elif action == 'remove_club_assignment':
|
||
assignment_id = request.data.get('assignment_id')
|
||
if not assignment_id:
|
||
return Response({'code': 400, 'msg': '缺少任职 id'})
|
||
from jituan.services.admin_assignments import deactivate_assignment
|
||
from jituan.models import AdminAssignment
|
||
row = AdminAssignment.query.filter(
|
||
id=assignment_id, yonghuid=target_user.UserUID, status=1,
|
||
).first()
|
||
if not row:
|
||
return Response({'code': 404, 'msg': '任职记录不存在'})
|
||
ok, err = deactivate_assignment(assignment_id)
|
||
if not ok:
|
||
return Response({'code': 404, 'msg': err or '停用失败'})
|
||
return Response({'code': 0, 'msg': '俱乐部任职已移除'})
|
||
|
||
else:
|
||
return Response({'code': 400, 'msg': '无效的action'})
|
||
|
||
|
||
|
||
|
||
|
||
class AddAdminUserView(APIView):
|
||
"""添加后台用户(客服账号)"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username', '').strip()
|
||
if not username:
|
||
return Response({'code': 400, 'msg': '缺少username'})
|
||
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return permissions
|
||
if '000001' not in permissions:
|
||
return Response({'code': 403, 'msg': '您无权进行此操作'})
|
||
|
||
phone = request.data.get('phone', '').strip()
|
||
password = request.data.get('password', '').strip()
|
||
erjimima = request.data.get('erjimima', '').strip()
|
||
nicheng = request.data.get('nicheng', '').strip()
|
||
role_codes = request.data.get('role_codes', [])
|
||
|
||
if not all([phone, password, erjimima, nicheng]):
|
||
return Response({'code': 400, 'msg': '请填写完整信息'})
|
||
if len(password) < 6 or len(erjimima) < 6:
|
||
return Response({'code': 400, 'msg': '密码和二级密码至少6位'})
|
||
|
||
from jituan.services.club_context import resolve_club_id_from_request
|
||
from jituan.services.club_rbac import resolve_roles_for_binding
|
||
|
||
target_club_id = resolve_club_id_from_request(request)
|
||
|
||
# 校验手机号是否已被后台客服占用(同俱乐部优先)
|
||
existing_kefu = User.query.filter(
|
||
Phone=phone, KefuProfile__isnull=False
|
||
).order_by('-UserCreateTime').first()
|
||
if existing_kefu:
|
||
return Response({'code': 400, 'msg': '该账号已存在'})
|
||
|
||
if not isinstance(role_codes, list):
|
||
return Response({'code': 400, 'msg': 'role_codes 格式错误'})
|
||
|
||
# 分配角色(默认客服 + 所选角色,去重;任一角色找不到则整单拒绝)
|
||
role_names_to_bind = []
|
||
seen_names = set()
|
||
for name in (['客服'] if '客服' not in role_codes else []) + list(role_codes):
|
||
n = (name or '').strip()
|
||
if n and n not in seen_names:
|
||
seen_names.add(n)
|
||
role_names_to_bind.append(n)
|
||
|
||
roles_to_bind, missing_roles = resolve_roles_for_binding(role_names_to_bind, target_club_id)
|
||
if missing_roles:
|
||
return Response({
|
||
'code': 400,
|
||
'msg': f'角色不存在或与当前俱乐部不匹配:{", ".join(missing_roles)}',
|
||
})
|
||
if not roles_to_bind:
|
||
return Response({'code': 400, 'msg': '请至少选择一个有效角色'})
|
||
|
||
with transaction.atomic():
|
||
# 事务内二次校验,防止并发重复创建
|
||
if User.query.filter(Phone=phone, KefuProfile__isnull=False).exists():
|
||
return Response({'code': 400, 'msg': '该账号已存在'})
|
||
|
||
# 生成唯一的 yonghuid(7位数字)
|
||
while True:
|
||
yonghuid = str(random.randint(1000000, 9999999))
|
||
if not User.query.filter(UserUID=yonghuid).exists():
|
||
break
|
||
|
||
# 生成唯一的 openid(格式:admin_{时间戳}_{随机6位})
|
||
timestamp = str(int(time.time() * 1000))
|
||
random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
|
||
openid = f"admin_{timestamp}_{random_suffix}"
|
||
while User.query.filter(OpenID=openid).exists():
|
||
random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
|
||
openid = f"admin_{timestamp}_{random_suffix}"
|
||
|
||
user_main = User.query.create(
|
||
UserUID=yonghuid,
|
||
UserName=openid,
|
||
OpenID=openid,
|
||
Phone=phone,
|
||
ClubID=target_club_id,
|
||
)
|
||
user_main.SetPassword(password)
|
||
user_main.save(update_fields=['UserPassword', 'ClubID'])
|
||
# 创建客服扩展表(后台用户扩展)
|
||
import bcrypt as _bcrypt
|
||
erjimima_hashed = _bcrypt.hashpw(erjimima.encode('utf-8'), _bcrypt.gensalt(rounds=12)).decode('utf-8')
|
||
UserKefu.query.create(
|
||
user=user_main,
|
||
nicheng=nicheng,
|
||
erjimima=erjimima_hashed,
|
||
zhuangtai=1
|
||
)
|
||
for role in roles_to_bind:
|
||
UserRole.objects.get_or_create(
|
||
UserUUID=user_main.UserUUID,
|
||
RoleUUID=role.RoleUUID,
|
||
defaults={
|
||
'UserRoleUUID': uuid.uuid4().bytes,
|
||
'CompanyUUID': uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes,
|
||
},
|
||
)
|
||
|
||
from jituan.services.admin_assignments import create_or_reactivate_assignment
|
||
from jituan.constants import DATA_SCOPE_SINGLE
|
||
create_or_reactivate_assignment(
|
||
yonghuid=yonghuid,
|
||
club_id=target_club_id,
|
||
role_code='CLUB_ADMIN',
|
||
data_scope=DATA_SCOPE_SINGLE,
|
||
is_primary=True,
|
||
granted_by=request.user.UserUID,
|
||
)
|
||
|
||
return Response({'code': 0, 'msg': '添加成功'})
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|