fix: 俱乐部超管可审本店提现与订单,禁跨店改单
放宽身份校验与权限推断,列表带回俱乐部信息,写操作按本店范围拦截。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -85,9 +85,39 @@ def has_perm_code(permissions, *codes):
|
|||||||
return any(c in perms for c in 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):
|
def has_merchant_order_permission(permissions):
|
||||||
"""商家/链接订单处理权限:002ac 商家订单、002ab 平台订单、003aa 跨平台。"""
|
"""商家/链接订单处理权限:002ac 商家订单、002ab 平台订单、003aa 跨平台;本店超管 000001 亦可。"""
|
||||||
return has_perm_code(permissions, '002ac', '002ab', '003aa')
|
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):
|
def _legacy_backend_role_permissions(phone):
|
||||||
|
|||||||
@@ -41,3 +41,20 @@ def forbid_if_order_out_of_scope(request, order):
|
|||||||
if order_belongs_to_request(order, request):
|
if order_belongs_to_request(order, request):
|
||||||
return None
|
return None
|
||||||
return Response({'code': 403, 'msg': '该订单不属于当前俱乐部数据范围'})
|
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)
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
- 当前俱乐部超级管理 000001:仅限 SINGLE_CLUB 本店,且目标用户必须属于当前俱乐部
|
- 当前俱乐部超级管理 000001:仅限 SINGLE_CLUB 本店,且目标用户必须属于当前俱乐部
|
||||||
不再强制「仅集团高管才能加余额」。
|
不再强制「仅集团高管才能加余额」。
|
||||||
|
|
||||||
提现审核:权限码 005bb + 按俱乐部隔离——
|
提现审核:权限码 005bb 或本店超级管理 000001 + 按俱乐部隔离——
|
||||||
- 子公司视图:持有 005bb 且任职含当前俱乐部 → 可审本俱乐部
|
- 子公司视图:持有 005bb/000001 且任职含当前俱乐部 → 可审本俱乐部
|
||||||
- 集团汇总视图 / 跨俱乐部:须集团资金处置权
|
- 集团汇总视图 / 跨俱乐部:须集团资金处置权
|
||||||
|
不再强制「必须集团权限才能审本店提现」。
|
||||||
"""
|
"""
|
||||||
from django.db.models import Q
|
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):
|
def require_withdraw_audit_access(request, phone):
|
||||||
"""
|
"""
|
||||||
提现审核访问:
|
提现审核访问:
|
||||||
1) 须权限码 005bb
|
1) 权限码 005bb,或本店超级管理 000001
|
||||||
2) 集团高管:可在 ALL_CLUBS 或任意子公司视图操作
|
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 rest_framework.response import Response
|
||||||
from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
|
from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
|
||||||
|
|
||||||
kefu, permissions = verify_kefu_permission(request, phone)
|
kefu, permissions = verify_kefu_permission(request, phone)
|
||||||
if kefu is None:
|
if kefu is None:
|
||||||
return None, permissions
|
return None, permissions
|
||||||
if '005bb' not in permissions:
|
if '005bb' not in permissions and not has_club_super_perm(permissions):
|
||||||
return None, Response({'code': 403, 'msg': '您无权操作提现审核'})
|
return None, Response({'code': 403, 'msg': '您无权操作提现审核(需要 005bb 或本店超级管理 000001)'})
|
||||||
|
|
||||||
if has_group_finance_exec(request.user, request):
|
if has_group_finance_exec(request.user, request):
|
||||||
return kefu, None
|
return kefu, None
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from types import SimpleNamespace
|
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.models import KefuMenuPage
|
||||||
from jituan.services.admin_context import (
|
from jituan.services.admin_context import (
|
||||||
build_admin_club_context,
|
build_admin_club_context,
|
||||||
@@ -43,14 +43,19 @@ def _page_visible(page, permissions, user):
|
|||||||
if page.require_super:
|
if page.require_super:
|
||||||
return _has_super_perm(permissions) or is_system_super_admin(user)
|
return _has_super_perm(permissions) or is_system_super_admin(user)
|
||||||
|
|
||||||
# 仅系统级超管可见全部菜单;俱乐部 000001 不展开业务菜单
|
# 仅系统级超管可见全部菜单;俱乐部 000001 不展开全部业务菜单
|
||||||
if is_system_super_admin(user):
|
if is_system_super_admin(user):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
codes = page.perm_codes or []
|
codes = page.perm_codes or []
|
||||||
if not codes:
|
if not codes:
|
||||||
return False
|
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):
|
def _permission_codes_list(permissions):
|
||||||
|
|||||||
@@ -57,7 +57,9 @@ from backend.utils import (
|
|||||||
update_dashou_daily_by_action, update_guanshi_daily_by_action,
|
update_dashou_daily_by_action, update_guanshi_daily_by_action,
|
||||||
update_shangjia_daily, update_zuzhang_daily_by_action,
|
update_shangjia_daily, update_zuzhang_daily_by_action,
|
||||||
verify_kefu_permission, has_perm_code, has_merchant_order_permission,
|
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 rank.utils import get_tag_fee, create_shenhe_jilu, validate_shenheguan
|
||||||
|
|
||||||
from ..models import (
|
from ..models import (
|
||||||
@@ -101,6 +103,29 @@ import logging
|
|||||||
logger = logging.getLogger(__name__)
|
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):
|
class KefuGetOrderListView(APIView):
|
||||||
"""
|
"""
|
||||||
客服获取平台订单列表接口(发单平台=1,非跨平台)
|
客服获取平台订单列表接口(发单平台=1,非跨平台)
|
||||||
@@ -135,8 +160,8 @@ class KefuGetOrderListView(APIView):
|
|||||||
if kefu is None:
|
if kefu is None:
|
||||||
return permissions
|
return permissions
|
||||||
|
|
||||||
if not has_perm_code(permissions, '002ab'):
|
if not has_platform_order_permission(permissions):
|
||||||
return Response({'code': 403, 'msg': '您无权查看平台订单列表'})
|
return Response({'code': 403, 'msg': '您无权查看平台订单列表(需要 002ab 或本店超级管理 000001)'})
|
||||||
|
|
||||||
# 4. 获取分页和筛选参数
|
# 4. 获取分页和筛选参数
|
||||||
page = int(request.data.get('page', 1))
|
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 ''
|
nickname_map[user.UserUID] = (user.BossProfile.nickname or '') if getattr(user, 'BossProfile', None) else ''
|
||||||
|
|
||||||
from jituan.services.order_deal import order_auto_settle_payload
|
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 = []
|
result_list = []
|
||||||
for order in orders_page:
|
for order in orders_page:
|
||||||
ext = ext_map.get(order.OrderID)
|
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 '',
|
'fadanshijian': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else '',
|
||||||
'youxi_nicheng': youxi_nicheng
|
'youxi_nicheng': youxi_nicheng
|
||||||
}
|
}
|
||||||
|
_attach_order_club_fields(item, order, club_names)
|
||||||
item.update(order_auto_settle_payload(order))
|
item.update(order_auto_settle_payload(order))
|
||||||
result_list.append(item)
|
result_list.append(item)
|
||||||
|
|
||||||
@@ -424,6 +451,7 @@ class KefuGetShangjiaOrderListView(APIView):
|
|||||||
nickname_map[user.UserUID] = user.ShopProfile.nicheng or '' if hasattr(user, 'ShopProfile') else ''
|
nickname_map[user.UserUID] = user.ShopProfile.nicheng or '' if hasattr(user, 'ShopProfile') else ''
|
||||||
|
|
||||||
from jituan.services.order_deal import order_auto_settle_payload
|
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 = []
|
result_list = []
|
||||||
for order in orders_page:
|
for order in orders_page:
|
||||||
ext = ext_map.get(order.OrderID)
|
ext = ext_map.get(order.OrderID)
|
||||||
@@ -438,8 +466,11 @@ class KefuGetShangjiaOrderListView(APIView):
|
|||||||
'jiage': float(order.Amount) if order.Amount else 0.00,
|
'jiage': float(order.Amount) if order.Amount else 0.00,
|
||||||
'tupian_url': order.ImageURL or '',
|
'tupian_url': order.ImageURL or '',
|
||||||
'fadanshijian': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else '',
|
'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))
|
item.update(order_auto_settle_payload(order))
|
||||||
result_list.append(item)
|
result_list.append(item)
|
||||||
|
|
||||||
@@ -494,11 +525,11 @@ class KefuChangeDashouView(APIView):
|
|||||||
logger.warning(f"手机号不匹配: {phone}")
|
logger.warning(f"手机号不匹配: {phone}")
|
||||||
return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
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}")
|
logger.warning(f"用户类型非客服: {current_user.UserUID}")
|
||||||
return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
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
|
kefu = None
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
try:
|
try:
|
||||||
@@ -514,6 +545,11 @@ class KefuChangeDashouView(APIView):
|
|||||||
# 3. 查询订单
|
# 3. 查询订单
|
||||||
try:
|
try:
|
||||||
order = Order.query.get(OrderID=dingdan_id)
|
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:
|
except Order.DoesNotExist:
|
||||||
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
@@ -638,10 +674,10 @@ class KefuRecoverOrderView(APIView):
|
|||||||
if getattr(current_user, 'Phone', '') != phone:
|
if getattr(current_user, 'Phone', '') != phone:
|
||||||
return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
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)
|
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
|
kefu = None
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
try:
|
try:
|
||||||
@@ -655,6 +691,11 @@ class KefuRecoverOrderView(APIView):
|
|||||||
# 3. 查询订单
|
# 3. 查询订单
|
||||||
try:
|
try:
|
||||||
order = Order.query.get(OrderID=dingdan_id)
|
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:
|
except Order.DoesNotExist:
|
||||||
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
@@ -706,11 +747,11 @@ class KefuPlatformRefundView(APIView):
|
|||||||
logger.warning(f"手机号不匹配: {phone}")
|
logger.warning(f"手机号不匹配: {phone}")
|
||||||
return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
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}")
|
logger.warning(f"用户类型非客服: {current_user.UserUID}")
|
||||||
return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
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
|
kefu = None
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
try:
|
try:
|
||||||
@@ -728,6 +769,11 @@ class KefuPlatformRefundView(APIView):
|
|||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
order = Order.query.select_related('pingtai_kuozhan').select_for_update().get(OrderID=dingdan_id)
|
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. 验证平台订单
|
# 4. 验证平台订单
|
||||||
if order.Platform != 1:
|
if order.Platform != 1:
|
||||||
return Response({'code': 400, 'msg': '该订单不是平台订单'}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'code': 400, 'msg': '该订单不是平台订单'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
@@ -996,10 +1042,10 @@ class KefuRejectRefundView(APIView):
|
|||||||
if getattr(current_user, 'Phone', '') != phone:
|
if getattr(current_user, 'Phone', '') != phone:
|
||||||
return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
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)
|
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
|
kefu = None
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
try:
|
try:
|
||||||
@@ -1015,6 +1061,11 @@ class KefuRejectRefundView(APIView):
|
|||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
order = Order.objects.select_for_update().get(OrderID=dingdan_id)
|
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)
|
# 4. 验证订单状态为退款审核中(4)
|
||||||
if order.Status != 4:
|
if order.Status != 4:
|
||||||
return Response({'code': 400, 'msg': '订单状态不是退款审核中'}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'code': 400, 'msg': '订单状态不是退款审核中'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
@@ -1158,10 +1209,10 @@ class KefuMerchantRefundView(APIView):
|
|||||||
if getattr(current_user, 'Phone', '') != phone:
|
if getattr(current_user, 'Phone', '') != phone:
|
||||||
return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
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)
|
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
|
kefu = None
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
try:
|
try:
|
||||||
@@ -1176,6 +1227,11 @@ class KefuMerchantRefundView(APIView):
|
|||||||
# 查询订单,预加载商家扩展表
|
# 查询订单,预加载商家扩展表
|
||||||
try:
|
try:
|
||||||
order = Order.query.select_related('shangjia_kuozhan').get(OrderID=dingdan_id)
|
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:
|
except Order.DoesNotExist:
|
||||||
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
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:
|
if not is_admin:
|
||||||
kefu = current_user.KefuProfile
|
kefu = current_user.KefuProfile
|
||||||
kefu.jinrichuli += 1
|
kefu.jinrichuli += 1
|
||||||
@@ -1305,11 +1361,11 @@ class KefuRejectSettlementView(APIView):
|
|||||||
logger.warning(f"手机号不匹配: {phone}")
|
logger.warning(f"手机号不匹配: {phone}")
|
||||||
return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
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}")
|
logger.warning(f"用户类型非客服: {current_user.UserUID}")
|
||||||
return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
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
|
kefu = None
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
try:
|
try:
|
||||||
@@ -1325,6 +1381,11 @@ class KefuRejectSettlementView(APIView):
|
|||||||
# 3. 查询订单,预加载平台扩展表
|
# 3. 查询订单,预加载平台扩展表
|
||||||
try:
|
try:
|
||||||
order = Order.query.select_related('pingtai_kuozhan').get(OrderID=dingdan_id)
|
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:
|
except Order.DoesNotExist:
|
||||||
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
@@ -1419,9 +1480,9 @@ class KefuTransferHallView(APIView):
|
|||||||
current_user = request.user
|
current_user = request.user
|
||||||
if getattr(current_user, 'Phone', '') != phone:
|
if getattr(current_user, 'Phone', '') != phone:
|
||||||
return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
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)
|
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
|
kefu = None
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
try:
|
try:
|
||||||
@@ -1434,6 +1495,11 @@ class KefuTransferHallView(APIView):
|
|||||||
# 3. 查询订单
|
# 3. 查询订单
|
||||||
try:
|
try:
|
||||||
order = Order.query.get(OrderID=dingdan_id)
|
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:
|
except Order.DoesNotExist:
|
||||||
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
@@ -1490,9 +1556,9 @@ class KefuCancelDesignationView(APIView):
|
|||||||
current_user = request.user
|
current_user = request.user
|
||||||
if getattr(current_user, 'Phone', '') != phone:
|
if getattr(current_user, 'Phone', '') != phone:
|
||||||
return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
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)
|
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
|
kefu = None
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
try:
|
try:
|
||||||
@@ -1505,6 +1571,11 @@ class KefuCancelDesignationView(APIView):
|
|||||||
# 3. 查询订单
|
# 3. 查询订单
|
||||||
try:
|
try:
|
||||||
order = Order.query.get(OrderID=dingdan_id)
|
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:
|
except Order.DoesNotExist:
|
||||||
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
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)
|
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)
|
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
|
kefu = None
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
try:
|
try:
|
||||||
@@ -1570,15 +1641,25 @@ class KefuGetOrderDetailView(APIView):
|
|||||||
logger.exception(f"[kefuhqddxq] 订单查询异常 dingdan_id={dingdan_id}")
|
logger.exception(f"[kefuhqddxq] 订单查询异常 dingdan_id={dingdan_id}")
|
||||||
return Response({'code': 500, 'msg': '服务器错误'}, status=500)
|
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)
|
deny = forbid_if_order_out_of_scope(request, dingdan_obj)
|
||||||
if deny:
|
if deny:
|
||||||
return deny
|
return deny
|
||||||
|
_detail_can_mutate = forbid_if_order_mutate_out_of_scope(request, dingdan_obj) is None
|
||||||
|
|
||||||
# 4. 构建基础数据
|
# 4. 构建基础数据
|
||||||
try:
|
try:
|
||||||
|
_cid = (getattr(dingdan_obj, 'ClubID', None) or '').strip()
|
||||||
|
_cname = _club_label_map([_cid]).get(_cid, _cid)
|
||||||
response_data = {
|
response_data = {
|
||||||
'dingdan_id': dingdan_obj.OrderID or '',
|
'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,
|
'zhuangtai': dingdan_obj.Status or 0,
|
||||||
'fadanpingtai': dingdan_obj.Platform or 0,
|
'fadanpingtai': dingdan_obj.Platform or 0,
|
||||||
'jiage': float(dingdan_obj.Amount) if dingdan_obj.Amount else 0.00,
|
'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:
|
if getattr(current_user, 'Phone', '') != phone:
|
||||||
return Response({'code': 401, 'msg': '认证失败'})
|
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': '认证失败'})
|
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
|
kefu = None
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
try:
|
try:
|
||||||
@@ -1760,6 +1841,11 @@ class KefuForceCompleteView(APIView):
|
|||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
order = Order.objects.select_for_update().get(OrderID=dingdan_id)
|
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. 校验订单状态
|
# 3. 校验订单状态
|
||||||
if order.Status != 8:
|
if order.Status != 8:
|
||||||
return Response({'code': 400, 'msg': f'订单状态不是结算中,当前状态: {order.Status}'})
|
return Response({'code': 400, 'msg': f'订单状态不是结算中,当前状态: {order.Status}'})
|
||||||
|
|||||||
@@ -419,24 +419,25 @@ class KefuWithdrawDetailView(APIView):
|
|||||||
current_user = request.user
|
current_user = request.user
|
||||||
if getattr(current_user, 'Phone', '') != phone:
|
if getattr(current_user, 'Phone', '') != phone:
|
||||||
logger.warning(f"手机号不匹配: {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'):
|
from jituan.services.admin_context import is_kefu_backend_account, is_system_super_admin
|
||||||
logger.warning(f"用户类型非客服: {current_user.UserUID}")
|
if not is_kefu_backend_account(current_user):
|
||||||
return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
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
|
kefu = None
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
try:
|
try:
|
||||||
kefu = current_user.KefuProfile
|
kefu = current_user.KefuProfile
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
logger.warning(f"客服扩展表不存在: {current_user.UserUID}")
|
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:
|
if kefu.zhuangtai != 1:
|
||||||
logger.warning(f"客服账号已禁用: {current_user.UserUID}")
|
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
|
from jituan.services.group_finance_gate import require_withdraw_audit_access
|
||||||
_, err_resp = require_withdraw_audit_access(request, phone)
|
_, err_resp = require_withdraw_audit_access(request, phone)
|
||||||
@@ -539,22 +540,23 @@ class KefuWithdrawActionView(APIView):
|
|||||||
parser_classes = [JSONParser]
|
parser_classes = [JSONParser]
|
||||||
|
|
||||||
def _verify_kefu(self, request, phone):
|
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:
|
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
|
current_user = request.user
|
||||||
if getattr(current_user, 'Phone', '') != phone:
|
if getattr(current_user, 'Phone', '') != phone:
|
||||||
return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
return None, Response({'code': 403, 'msg': '身份不匹配'}, status=status.HTTP_403_FORBIDDEN)
|
||||||
if current_user.UserType not in ('kefu', 'admin'):
|
if not is_kefu_backend_account(current_user):
|
||||||
return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
return None, Response({'code': 403, 'msg': '非后台账号'}, status=status.HTTP_403_FORBIDDEN)
|
||||||
# 管理员跳过客服扩展表检查
|
is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin'
|
||||||
is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser
|
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
try:
|
try:
|
||||||
kefu = current_user.KefuProfile
|
kefu = current_user.KefuProfile
|
||||||
except ObjectDoesNotExist:
|
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:
|
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
|
return current_user, None
|
||||||
|
|
||||||
def _parse_items(self, request):
|
def _parse_items(self, request):
|
||||||
|
|||||||
Reference in New Issue
Block a user