diff --git a/backend/utils.py b/backend/utils.py index 31de699..c79b6e1 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -77,6 +77,13 @@ class _AllPermissions(list): return True +def has_merchant_order_permission(permissions): + """商家/链接订单处理权限:002ac 商家订单、002ab 平台订单、003aa 跨平台。""" + if isinstance(permissions, _AllPermissions): + return True + return any(p in permissions for p in ('002ac', '002ab', '003aa')) + + def _legacy_backend_role_permissions(phone): """ 读取旧权限表(backend.models): diff --git a/backend/view.py b/backend/view.py index 1329b40..2f8b6fa 100644 --- a/backend/view.py +++ b/backend/view.py @@ -53,6 +53,7 @@ from .utils import ( PERMISSION_TO_SHENFEN, SHENFEN_PROFILE_MAP, has_fadan_view_permission, + has_merchant_order_permission, check_fadan_permission, pick_order_id, pick_penalty_create_params, @@ -646,13 +647,18 @@ class ModifyAdminUserView(APIView): if not role: missing.append(rc) continue - UserRole.objects.get_or_create( - UserRoleUUID=uuid.uuid4().bytes, + _, created = UserRole.objects.get_or_create( UserUUID=target_user.UserUUID, RoleUUID=role.RoleUUID, - CompanyUUID=uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes, + defaults={ + 'UserRoleUUID': uuid.uuid4().bytes, + 'CompanyUUID': uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes, + }, ) - added.append(role.RoleName) + if created: + added.append(role.RoleName) + elif role.RoleName not in added: + added.append(role.RoleName) if not added: return Response({ 'code': 400, @@ -710,33 +716,46 @@ class AddAdminUserView(APIView): if len(password) < 6 or len(erjimima) < 6: return Response({'code': 400, 'msg': '密码和二级密码至少6位'}) - # 校验手机号是否已被客服占用 - if User.query.filter(Phone=phone, KefuProfile__isnull=False).exists(): + from jituan.services.club_context import resolve_club_id_from_request + from jituan.services.club_rbac import resolve_role_by_name + + 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': '该账号已存在'}) - # 生成唯一的 yonghuid(7位数字) - while True: - yonghuid = str(random.randint(1000000, 9999999)) - if not User.query.filter(UserUID=yonghuid).exists(): - break + with transaction.atomic(): + # 事务内二次校验,防止并发重复创建 + if User.query.filter(Phone=phone, KefuProfile__isnull=False).exists(): + return Response({'code': 400, 'msg': '该账号已存在'}) - # 生成唯一的 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(): + # 生成唯一的 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}" - with transaction.atomic(): 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']) + 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') @@ -746,27 +765,28 @@ class AddAdminUserView(APIView): erjimima=erjimima_hashed, zhuangtai=1 ) - # 分配客服角色 - kefu_role = Role.objects.filter(RoleName='客服').first() - if kefu_role: - UserRole.objects.create( - UserRoleUUID=uuid.uuid4().bytes, + # 分配角色(默认客服 + 所选角色,按俱乐部解析,去重) + role_names_to_bind = [] + if '客服' not in role_codes: + role_names_to_bind.append('客服') + role_names_to_bind.extend(role_codes) + bound_role_uuids = set() + for rc in role_names_to_bind: + role = resolve_role_by_name(rc, target_club_id) + if not role: + continue + role_uuid = bytes(role.RoleUUID) if not isinstance(role.RoleUUID, bytes) else role.RoleUUID + if role_uuid in bound_role_uuids: + continue + bound_role_uuids.add(role_uuid) + UserRole.objects.get_or_create( UserUUID=user_main.UserUUID, - RoleUUID=kefu_role.RoleUUID, - CompanyUUID=uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes, + RoleUUID=role.RoleUUID, + defaults={ + 'UserRoleUUID': uuid.uuid4().bytes, + 'CompanyUUID': uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes, + }, ) - # 分配额外角色 - for rc in role_codes: - try: - role = Role.objects.get(RoleName=rc) - UserRole.objects.create( - UserRoleUUID=uuid.uuid4().bytes, - UserUUID=user_main.UserUUID, - RoleUUID=role.RoleUUID, - CompanyUUID=uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes, - ) - except Role.DoesNotExist: - pass return Response({'code': 0, 'msg': '添加成功'}) @@ -8536,7 +8556,7 @@ class ZxkfghdsView(APIView): 4. 生成新的派单链接,并标记为已使用 5. 新链接记录上一个链接ID和上一个订单ID 6. 【关键】不操作商家余额、退款次数、每日统计,因新订单是替换而非新增扣款 - 权限:需要'003aa'(跨平台管理)权限,与退款接口一致 + 权限:002ac(商家订单管理)/ 002ab(平台订单管理)/ 003aa(跨平台管理) """ permission_classes = [IsAuthenticated] @@ -8544,17 +8564,17 @@ class ZxkfghdsView(APIView): def post(self, request): # ========== 1. 参数提取 ========== - username = request.data.get('username', '').strip() + username = request.data.get('username', '').strip() or request.data.get('phone', '').strip() OrderID = pick_order_id(request.data) if not username or not OrderID: logger.warning("更换打手参数不完整") return Response({'code': 400, 'msg': '参数不完整'}) - # ========== 2. 权限校验(与跨平台退款相同) ========== + # ========== 2. 权限校验(商家链接页客服需 002ac,非仅跨平台 003aa) ========== kefu, permissions = verify_kefu_permission(request, username) if kefu is None: - return permissions # 返回错误响应 - if '003aa' not in permissions: + return permissions # 返回 error response + if not has_merchant_order_permission(permissions): return Response({'code': 403, 'msg': '您无权处理订单'}) current_user = request.user