From 955b76cbeac52e01ed56bd639d9388720ce2f191 Mon Sep 17 00:00:00 2001 From: XingQue Date: Mon, 27 Jul 2026 18:39:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=B1=E4=B9=90=E9=83=A8=E8=B6=85?= =?UTF-8?q?=E7=AE=A1=E5=8F=AF=E5=AE=A1=E6=9C=AC=E5=BA=97=E6=8F=90=E7=8E=B0?= =?UTF-8?q?=E4=B8=8E=E8=AE=A2=E5=8D=95=EF=BC=8C=E7=A6=81=E8=B7=A8=E5=BA=97?= =?UTF-8?q?=E6=94=B9=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 放宽身份校验与权限推断,列表带回俱乐部信息,写操作按本店范围拦截。 Co-authored-by: Cursor --- backend/utils.py | 34 ++++++- jituan/services/club_user_access.py | 17 ++++ jituan/services/group_finance_gate.py | 15 +-- jituan/services/kefu_menu.py | 11 ++- users/views/kefu_orders.py | 136 +++++++++++++++++++++----- users/views/kefu_withdraw.py | 32 +++--- 6 files changed, 193 insertions(+), 52 deletions(-) diff --git a/backend/utils.py b/backend/utils.py index 750a77c..3c16a34 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -85,9 +85,39 @@ def has_perm_code(permissions, *codes): return any(c in perms for c in codes) +def has_club_super_perm(permissions): + """本店超级管理 000001(含系统超管 _AllPermissions)。""" + if isinstance(permissions, _AllPermissions): + return True + return '000001' in (permissions or []) + + +# 本店超管可直接使用的核心业务码(不必再绑集团权限) +CLUB_SUPER_IMPLIED_PERMS = frozenset({ + '002ab', # 平台订单 + '002ac', # 商家订单 + '003aa', # 跨平台订单(只读/本店范围仍由 club 闸门控制) + '005bb', # 提现审核 +}) + + +def has_perm_code_or_club_super(permissions, *codes): + """指定业务码,或本店超管 000001(对核心业务码生效)。""" + if has_perm_code(permissions, *codes): + return True + if has_club_super_perm(permissions) and any(c in CLUB_SUPER_IMPLIED_PERMS for c in codes): + return True + return False + + def has_merchant_order_permission(permissions): - """商家/链接订单处理权限:002ac 商家订单、002ab 平台订单、003aa 跨平台。""" - return has_perm_code(permissions, '002ac', '002ab', '003aa') + """商家/链接订单处理权限:002ac 商家订单、002ab 平台订单、003aa 跨平台;本店超管 000001 亦可。""" + return has_perm_code_or_club_super(permissions, '002ac', '002ab', '003aa') + + +def has_platform_order_permission(permissions): + """平台订单:002ab 或本店超管 000001。""" + return has_perm_code_or_club_super(permissions, '002ab') def _legacy_backend_role_permissions(phone): diff --git a/jituan/services/club_user_access.py b/jituan/services/club_user_access.py index 45920bd..f4d61d4 100644 --- a/jituan/services/club_user_access.py +++ b/jituan/services/club_user_access.py @@ -41,3 +41,20 @@ def forbid_if_order_out_of_scope(request, order): if order_belongs_to_request(order, request): return None return Response({'code': 403, 'msg': '该订单不属于当前俱乐部数据范围'}) + + +def forbid_if_order_mutate_out_of_scope(request, order): + """ + 订单写操作闸门: + - SINGLE_CLUB:只能处理当前俱乐部订单 + - ALL_CLUBS:仅集团高管可处理(本店超管须切到具体俱乐部) + """ + if resolve_club_scope(request) == DATA_SCOPE_ALL: + from jituan.services.group_finance_gate import has_group_finance_exec + if not has_group_finance_exec(request.user, request): + return Response({ + 'code': 403, + 'msg': '跨俱乐部订单处理仅集团高管可执行,请切换到具体俱乐部后再操作', + }) + return None + return forbid_if_order_out_of_scope(request, order) diff --git a/jituan/services/group_finance_gate.py b/jituan/services/group_finance_gate.py index 150d7a5..d20304d 100644 --- a/jituan/services/group_finance_gate.py +++ b/jituan/services/group_finance_gate.py @@ -5,9 +5,10 @@ - 当前俱乐部超级管理 000001:仅限 SINGLE_CLUB 本店,且目标用户必须属于当前俱乐部 不再强制「仅集团高管才能加余额」。 -提现审核:权限码 005bb + 按俱乐部隔离—— -- 子公司视图:持有 005bb 且任职含当前俱乐部 → 可审本俱乐部 +提现审核:权限码 005bb 或本店超级管理 000001 + 按俱乐部隔离—— +- 子公司视图:持有 005bb/000001 且任职含当前俱乐部 → 可审本俱乐部 - 集团汇总视图 / 跨俱乐部:须集团资金处置权 +不再强制「必须集团权限才能审本店提现」。 """ from django.db.models import Q @@ -107,19 +108,19 @@ def deny_balance_add_exec(request, *, permissions=None, target_user=None): def require_withdraw_audit_access(request, phone): """ 提现审核访问: - 1) 须权限码 005bb + 1) 权限码 005bb,或本店超级管理 000001 2) 集团高管:可在 ALL_CLUBS 或任意子公司视图操作 - 3) 俱乐部管理员:仅 SINGLE_CLUB 且当前 club 在其任职范围内 + 3) 俱乐部侧:仅 SINGLE_CLUB 且当前 club 在其任职范围内(只能审本店) """ - from backend.utils import verify_kefu_permission + from backend.utils import has_club_super_perm, verify_kefu_permission from rest_framework.response import Response from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope kefu, permissions = verify_kefu_permission(request, phone) if kefu is None: return None, permissions - if '005bb' not in permissions: - return None, Response({'code': 403, 'msg': '您无权操作提现审核'}) + if '005bb' not in permissions and not has_club_super_perm(permissions): + return None, Response({'code': 403, 'msg': '您无权操作提现审核(需要 005bb 或本店超级管理 000001)'}) if has_group_finance_exec(request.user, request): return kefu, None diff --git a/jituan/services/kefu_menu.py b/jituan/services/kefu_menu.py index 481454b..2791700 100644 --- a/jituan/services/kefu_menu.py +++ b/jituan/services/kefu_menu.py @@ -2,7 +2,7 @@ import logging from types import SimpleNamespace -from backend.utils import _AllPermissions, verify_kefu_permission +from backend.utils import _AllPermissions, verify_kefu_permission, CLUB_SUPER_IMPLIED_PERMS from jituan.models import KefuMenuPage from jituan.services.admin_context import ( build_admin_club_context, @@ -43,14 +43,19 @@ def _page_visible(page, permissions, user): if page.require_super: return _has_super_perm(permissions) or is_system_super_admin(user) - # 仅系统级超管可见全部菜单;俱乐部 000001 不展开业务菜单 + # 仅系统级超管可见全部菜单;俱乐部 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) + if any(code in permissions for code in codes): + return True + # 本店超管:解锁订单/提现审核等核心页(与加余额闸门一致,不要求集团权限) + if _has_super_perm(permissions) and any(code in CLUB_SUPER_IMPLIED_PERMS for code in codes): + return True + return False def _permission_codes_list(permissions): diff --git a/users/views/kefu_orders.py b/users/views/kefu_orders.py index e203470..fc4e022 100644 --- a/users/views/kefu_orders.py +++ b/users/views/kefu_orders.py @@ -57,7 +57,9 @@ from backend.utils import ( update_dashou_daily_by_action, update_guanshi_daily_by_action, update_shangjia_daily, update_zuzhang_daily_by_action, verify_kefu_permission, has_perm_code, has_merchant_order_permission, + has_platform_order_permission, ) +from jituan.services.admin_context import is_kefu_backend_account, is_system_super_admin from rank.utils import get_tag_fee, create_shenhe_jilu, validate_shenheguan from ..models import ( @@ -101,6 +103,29 @@ import logging logger = logging.getLogger(__name__) +def _club_label_map(club_ids): + """club_id -> 展示名""" + ids = [c for c in set(club_ids or []) if c] + if not ids: + return {} + try: + from jituan.models import Club + return { + c.club_id: (c.name or c.club_id) + for c in Club.objects.filter(club_id__in=ids) + } + except Exception: + return {cid: cid for cid in ids} + + +def _attach_order_club_fields(item, order, name_map=None): + cid = (getattr(order, 'ClubID', None) or '').strip() + item['club_id'] = cid + item['club_name'] = (name_map or {}).get(cid) or cid or '' + return item + + + class KefuGetOrderListView(APIView): """ 客服获取平台订单列表接口(发单平台=1,非跨平台) @@ -135,8 +160,8 @@ class KefuGetOrderListView(APIView): if kefu is None: return permissions - if not has_perm_code(permissions, '002ab'): - return Response({'code': 403, 'msg': '您无权查看平台订单列表'}) + if not has_platform_order_permission(permissions): + return Response({'code': 403, 'msg': '您无权查看平台订单列表(需要 002ab 或本店超级管理 000001)'}) # 4. 获取分页和筛选参数 page = int(request.data.get('page', 1)) @@ -235,6 +260,7 @@ class KefuGetOrderListView(APIView): nickname_map[user.UserUID] = (user.BossProfile.nickname or '') if getattr(user, 'BossProfile', None) else '' from jituan.services.order_deal import order_auto_settle_payload + club_names = _club_label_map([(getattr(o, 'ClubID', None) or '') for o in orders_page]) result_list = [] for order in orders_page: ext = ext_map.get(order.OrderID) @@ -251,6 +277,7 @@ class KefuGetOrderListView(APIView): 'fadanshijian': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else '', 'youxi_nicheng': youxi_nicheng } + _attach_order_club_fields(item, order, club_names) item.update(order_auto_settle_payload(order)) result_list.append(item) @@ -424,6 +451,7 @@ class KefuGetShangjiaOrderListView(APIView): nickname_map[user.UserUID] = user.ShopProfile.nicheng or '' if hasattr(user, 'ShopProfile') else '' from jituan.services.order_deal import order_auto_settle_payload + club_names = _club_label_map([(getattr(o, 'ClubID', None) or '') for o in orders_page]) result_list = [] for order in orders_page: ext = ext_map.get(order.OrderID) @@ -438,8 +466,11 @@ class KefuGetShangjiaOrderListView(APIView): 'jiage': float(order.Amount) if order.Amount else 0.00, 'tupian_url': order.ImageURL or '', 'fadanshijian': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else '', - 'youxi_nicheng': youxi_nicheng + 'youxi_nicheng': youxi_nicheng, + 'shangjia_id': shangjia_id, + 'shangjia_nicheng': nickname_map.get(shangjia_id, ''), } + _attach_order_club_fields(item, order, club_names) item.update(order_auto_settle_payload(order)) result_list.append(item) @@ -494,11 +525,11 @@ class KefuChangeDashouView(APIView): logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.UserType not in ('kefu', 'admin'): + if not is_kefu_backend_account(current_user): logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' kefu = None if not is_admin: try: @@ -514,6 +545,11 @@ class KefuChangeDashouView(APIView): # 3. 查询订单 try: order = Order.query.get(OrderID=dingdan_id) + from jituan.services.club_user_access import forbid_if_order_mutate_out_of_scope + _ms = forbid_if_order_mutate_out_of_scope(request, order) + if _ms: + return _ms + except Order.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) @@ -638,10 +674,10 @@ class KefuRecoverOrderView(APIView): if getattr(current_user, 'Phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.UserType not in ('kefu', 'admin'): + if not is_kefu_backend_account(current_user): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' kefu = None if not is_admin: try: @@ -655,6 +691,11 @@ class KefuRecoverOrderView(APIView): # 3. 查询订单 try: order = Order.query.get(OrderID=dingdan_id) + from jituan.services.club_user_access import forbid_if_order_mutate_out_of_scope + _ms = forbid_if_order_mutate_out_of_scope(request, order) + if _ms: + return _ms + except Order.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) @@ -706,11 +747,11 @@ class KefuPlatformRefundView(APIView): logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.UserType not in ('kefu', 'admin'): + if not is_kefu_backend_account(current_user): logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' kefu = None if not is_admin: try: @@ -728,6 +769,11 @@ class KefuPlatformRefundView(APIView): with transaction.atomic(): order = Order.query.select_related('pingtai_kuozhan').select_for_update().get(OrderID=dingdan_id) + from jituan.services.club_user_access import forbid_if_order_mutate_out_of_scope + _ms = forbid_if_order_mutate_out_of_scope(request, order) + if _ms: + return _ms + # 4. 验证平台订单 if order.Platform != 1: return Response({'code': 400, 'msg': '该订单不是平台订单'}, status=status.HTTP_400_BAD_REQUEST) @@ -996,10 +1042,10 @@ class KefuRejectRefundView(APIView): if getattr(current_user, 'Phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.UserType not in ('kefu', 'admin'): + if not is_kefu_backend_account(current_user): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' kefu = None if not is_admin: try: @@ -1015,6 +1061,11 @@ class KefuRejectRefundView(APIView): with transaction.atomic(): order = Order.objects.select_for_update().get(OrderID=dingdan_id) + from jituan.services.club_user_access import forbid_if_order_mutate_out_of_scope + _ms = forbid_if_order_mutate_out_of_scope(request, order) + if _ms: + return _ms + # 4. 验证订单状态为退款审核中(4) if order.Status != 4: return Response({'code': 400, 'msg': '订单状态不是退款审核中'}, status=status.HTTP_400_BAD_REQUEST) @@ -1158,10 +1209,10 @@ class KefuMerchantRefundView(APIView): if getattr(current_user, 'Phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.UserType not in ('kefu', 'admin'): + if not is_kefu_backend_account(current_user): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' kefu = None if not is_admin: try: @@ -1176,6 +1227,11 @@ class KefuMerchantRefundView(APIView): # 查询订单,预加载商家扩展表 try: order = Order.query.select_related('shangjia_kuozhan').get(OrderID=dingdan_id) + from jituan.services.club_user_access import forbid_if_order_mutate_out_of_scope + _ms = forbid_if_order_mutate_out_of_scope(request, order) + if _ms: + return _ms + except Order.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) @@ -1264,7 +1320,7 @@ class KefuMerchantRefundView(APIView): # 管理员无客服扩展表,跳过计数器更新 - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' if not is_admin: kefu = current_user.KefuProfile kefu.jinrichuli += 1 @@ -1305,11 +1361,11 @@ class KefuRejectSettlementView(APIView): logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.UserType not in ('kefu', 'admin'): + if not is_kefu_backend_account(current_user): logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' kefu = None if not is_admin: try: @@ -1325,6 +1381,11 @@ class KefuRejectSettlementView(APIView): # 3. 查询订单,预加载平台扩展表 try: order = Order.query.select_related('pingtai_kuozhan').get(OrderID=dingdan_id) + from jituan.services.club_user_access import forbid_if_order_mutate_out_of_scope + _ms = forbid_if_order_mutate_out_of_scope(request, order) + if _ms: + return _ms + except Order.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) @@ -1419,9 +1480,9 @@ class KefuTransferHallView(APIView): current_user = request.user if getattr(current_user, 'Phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.UserType not in ('kefu', 'admin'): + if not is_kefu_backend_account(current_user): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' kefu = None if not is_admin: try: @@ -1434,6 +1495,11 @@ class KefuTransferHallView(APIView): # 3. 查询订单 try: order = Order.query.get(OrderID=dingdan_id) + from jituan.services.club_user_access import forbid_if_order_mutate_out_of_scope + _ms = forbid_if_order_mutate_out_of_scope(request, order) + if _ms: + return _ms + except Order.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) @@ -1490,9 +1556,9 @@ class KefuCancelDesignationView(APIView): current_user = request.user if getattr(current_user, 'Phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.UserType not in ('kefu', 'admin'): + if not is_kefu_backend_account(current_user): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' kefu = None if not is_admin: try: @@ -1505,6 +1571,11 @@ class KefuCancelDesignationView(APIView): # 3. 查询订单 try: order = Order.query.get(OrderID=dingdan_id) + from jituan.services.club_user_access import forbid_if_order_mutate_out_of_scope + _ms = forbid_if_order_mutate_out_of_scope(request, order) + if _ms: + return _ms + except Order.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) @@ -1544,11 +1615,11 @@ class KefuGetOrderDetailView(APIView): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 验证用户类型 - if current_user.UserType not in ('kefu', 'admin'): + if not is_kefu_backend_account(current_user): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 客服扩展表 - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' kefu = None if not is_admin: try: @@ -1570,15 +1641,25 @@ class KefuGetOrderDetailView(APIView): logger.exception(f"[kefuhqddxq] 订单查询异常 dingdan_id={dingdan_id}") return Response({'code': 500, 'msg': '服务器错误'}, status=500) - from jituan.services.club_user_access import forbid_if_order_out_of_scope + from jituan.services.club_user_access import ( + forbid_if_order_out_of_scope, + forbid_if_order_mutate_out_of_scope, + order_belongs_to_request, + ) deny = forbid_if_order_out_of_scope(request, dingdan_obj) if deny: return deny + _detail_can_mutate = forbid_if_order_mutate_out_of_scope(request, dingdan_obj) is None # 4. 构建基础数据 try: + _cid = (getattr(dingdan_obj, 'ClubID', None) or '').strip() + _cname = _club_label_map([_cid]).get(_cid, _cid) response_data = { 'dingdan_id': dingdan_obj.OrderID or '', + 'club_id': _cid, + 'club_name': _cname, + 'can_mutate': bool(_detail_can_mutate and order_belongs_to_request(dingdan_obj, request)), 'zhuangtai': dingdan_obj.Status or 0, 'fadanpingtai': dingdan_obj.Platform or 0, 'jiage': float(dingdan_obj.Amount) if dingdan_obj.Amount else 0.00, @@ -1741,10 +1822,10 @@ class KefuForceCompleteView(APIView): if getattr(current_user, 'Phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}) - if current_user.UserType not in ('kefu', 'admin'): + if not is_kefu_backend_account(current_user): return Response({'code': 401, 'msg': '认证失败'}) - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' kefu = None if not is_admin: try: @@ -1760,6 +1841,11 @@ class KefuForceCompleteView(APIView): with transaction.atomic(): order = Order.objects.select_for_update().get(OrderID=dingdan_id) + from jituan.services.club_user_access import forbid_if_order_mutate_out_of_scope + _ms = forbid_if_order_mutate_out_of_scope(request, order) + if _ms: + return _ms + # 3. 校验订单状态 if order.Status != 8: return Response({'code': 400, 'msg': f'订单状态不是结算中,当前状态: {order.Status}'}) diff --git a/users/views/kefu_withdraw.py b/users/views/kefu_withdraw.py index bb0ae28..63ddbb6 100644 --- a/users/views/kefu_withdraw.py +++ b/users/views/kefu_withdraw.py @@ -419,24 +419,25 @@ class KefuWithdrawDetailView(APIView): current_user = request.user if getattr(current_user, 'Phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") - return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) + return Response({'code': 403, 'msg': '身份不匹配'}, status=status.HTTP_403_FORBIDDEN) - if current_user.UserType not in ('kefu', 'admin'): - logger.warning(f"用户类型非客服: {current_user.UserUID}") - return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) + from jituan.services.admin_context import is_kefu_backend_account, is_system_super_admin + if not is_kefu_backend_account(current_user): + logger.warning(f"非后台账号: {current_user.UserUID}") + return Response({'code': 403, 'msg': '非后台账号'}, status=status.HTTP_403_FORBIDDEN) - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' kefu = None if not is_admin: try: kefu = current_user.KefuProfile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.UserUID}") - return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) + return Response({'code': 403, 'msg': '客服账号不存在'}, status=status.HTTP_403_FORBIDDEN) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.UserUID}") - return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) + return Response({'code': 403, 'msg': '客服账号已被禁用'}, status=status.HTTP_403_FORBIDDEN) from jituan.services.group_finance_gate import require_withdraw_audit_access _, err_resp = require_withdraw_audit_access(request, phone) @@ -539,22 +540,23 @@ class KefuWithdrawActionView(APIView): parser_classes = [JSONParser] def _verify_kefu(self, request, phone): + """与 menu-access / verify_kefu_permission 对齐:按后台账号放行,不因 UserType 推断失败而 401。""" + from jituan.services.admin_context import is_kefu_backend_account, is_system_super_admin if not phone: - return None, Response({'code': 401, 'msg': '手机号不能为空'}, status=status.HTTP_400_BAD_REQUEST) + return None, Response({'code': 400, 'msg': '手机号不能为空'}, status=status.HTTP_400_BAD_REQUEST) current_user = request.user if getattr(current_user, 'Phone', '') != phone: - return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.UserType not in ('kefu', 'admin'): - return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - # 管理员跳过客服扩展表检查 - is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser + return None, Response({'code': 403, 'msg': '身份不匹配'}, status=status.HTTP_403_FORBIDDEN) + if not is_kefu_backend_account(current_user): + return None, Response({'code': 403, 'msg': '非后台账号'}, status=status.HTTP_403_FORBIDDEN) + is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin' if not is_admin: try: kefu = current_user.KefuProfile except ObjectDoesNotExist: - return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) + return None, Response({'code': 403, 'msg': '客服账号不存在'}, status=status.HTTP_403_FORBIDDEN) if kefu.zhuangtai != 1: - return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) + return None, Response({'code': 403, 'msg': '客服账号已被禁用'}, status=status.HTTP_403_FORBIDDEN) return current_user, None def _parse_items(self, request):