diff --git a/merchant_ops/services/order_query.py b/merchant_ops/services/order_query.py new file mode 100644 index 0000000..edd7e50 --- /dev/null +++ b/merchant_ops/services/order_query.py @@ -0,0 +1,59 @@ +"""子客服订单查询:按权限隔离,不返回商家全量(除非 order_view_all)。""" +from django.db.models import Q + +from backend.utils import pick_leixing_id +from merchant_ops.models import MerchantOrderDispatch +from merchant_ops.services.authz import member_has_perm +from orders.models import MerchantOrderExt + + +def staff_can_view_all_orders(member): + return member_has_perm(member, 'order_view_all') + + +def staff_order_ext_qs(member, zhuangtai_list=None): + """商家名下订单;无 order_view_all 时仅本人派单记录。""" + qs = MerchantOrderExt.query.filter( + MerchantID=member.merchant_id, + ).select_related('Order') + if zhuangtai_list is not None: + qs = qs.filter(Order__Status__in=zhuangtai_list) + if not staff_can_view_all_orders(member): + own_ids = list( + MerchantOrderDispatch.query.filter(staff_member_id=member.id).values_list('order_id', flat=True) + ) + qs = qs.filter(Order__OrderID__in=own_ids) + return qs + + +def staff_apply_order_filters(qs, data): + leixing_id = pick_leixing_id(data) + keyword = str(data.get('keyword', '')).strip() + if leixing_id is not None: + qs = qs.filter(Order__ProductTypeID=leixing_id) + if keyword: + qs = qs.filter( + Q(Order__OrderID__exact=keyword) + | Q(Order__Description__icontains=keyword) + | Q(Order__Nickname__icontains=keyword) + | Q(Order__PlayerID__exact=keyword) + ) + return qs + + +def staff_order_to_dict(ext): + o = ext.Order + ct = o.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if o.CreateTime else '' + return { + 'dingdan_id': o.OrderID, + 'zhuangtai': o.Status, + 'jine': float(o.Amount) if o.Amount else 0.0, + 'jieshao': o.Description or '', + 'beizhu': o.Remark or '', + 'nicheng': o.Nickname or '', + 'leixing_id': o.ProductTypeID, + 'tupian': o.ImageURL or '', + 'jiedan_dashou_id': o.PlayerID or '', + 'CreateTime': ct, + 'create_time': ct, + } diff --git a/merchant_ops/services/template_query.py b/merchant_ops/services/template_query.py new file mode 100644 index 0000000..d56dddb --- /dev/null +++ b/merchant_ops/services/template_query.py @@ -0,0 +1,103 @@ +"""子客服读取商家模板:按 merchant_id 查 ShangjiaMoban(与老板同一批数据)。""" +from decimal import Decimal, InvalidOperation + +from django.core.paginator import Paginator +from django.db.models import Q + +from config.models import Chenghao, ShangjiaMoban + + +def _format_moban(moban): + texiao_json = '' + label_name = '' + if moban.TitleID: + try: + ch = Chenghao.query.get(id=moban.TitleID) + label_name = ch.mingcheng + texiao_json = ch.texiao_miaoshu or '' + except Chenghao.DoesNotExist: + pass + return { + 'mobanId': moban.id, + 'shangpinTypeId': moban.ProductTypeID, + 'jieshao': moban.TemplateDesc, + 'jiage': str(moban.Price), + 'fabushuliang': moban.PublishedCount, + 'commissionEnabled': moban.CommissionGrabEnabled, + 'commissionValue': str(moban.CommissionPrice) if moban.CommissionPrice is not None else None, + 'labelId': moban.TitleID, + 'labelName': label_name, + 'texiaoJson': texiao_json, + 'createTime': moban.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if moban.CreateTime else '', + } + + +def list_merchant_templates(merchant_id, data): + """ + 查询某商家全部模板(UserID=merchant_id)。 + 返回 (payload_dict, http_status) 与 /peizhi/sjddmblb 一致,code=200。 + """ + leixing_id = data.get('shangpinTypeId') + if not leixing_id: + return {'code': 400, 'msg': '商品类型ID不能为空', 'data': {}}, 400 + + get_type = int(data.get('getType', 2)) + page = int(data.get('page', 1)) + page_size = int(data.get('pageSize', 50)) + keyword = data.get('keyword', '') + min_price = data.get('minPrice', '') + label_id = data.get('labelId', None) + if label_id is not None: + try: + label_id = int(label_id) + except (ValueError, TypeError): + label_id = None + + query = Q(UserID=merchant_id, ProductTypeID=leixing_id) + + if get_type == 1: + if keyword: + keyword = str(keyword).strip() + try: + mid = int(keyword) + query &= Q(id=mid) | Q(TemplateDesc__icontains=keyword) + except ValueError: + query &= Q(TemplateDesc__icontains=keyword) + if min_price: + try: + query &= Q(Price__gte=Decimal(str(min_price))) + except (InvalidOperation, ValueError, TypeError): + pass + if label_id: + query &= Q(TitleID=label_id) + mobans = ShangjiaMoban.query.filter(query).order_by('-id') + formatted = [_format_moban(m) for m in mobans] + return { + 'code': 200, + 'msg': '搜索成功', + 'data': {'list': formatted, 'total': len(formatted), 'hasMore': False}, + }, 200 + + queryset = ShangjiaMoban.query.filter(query).order_by('-id') + paginator = Paginator(queryset, page_size) + if page < 1 or page > max(paginator.num_pages, 1): + return {'code': 400, 'msg': '页码超出范围', 'data': {}}, 400 + + current_page = paginator.page(page) + formatted = [_format_moban(m) for m in current_page.object_list] + has_more = current_page.has_next() + if len(formatted) < page_size: + has_more = False + + return { + 'code': 200, + 'msg': '获取成功', + 'data': { + 'list': formatted, + 'total': paginator.count, + 'hasMore': has_more, + 'currentPage': page, + 'pageSize': page_size, + 'totalPages': paginator.num_pages, + }, + }, 200 diff --git a/merchant_ops/views_orders.py b/merchant_ops/views_orders.py index 0488486..55222ac 100644 --- a/merchant_ops/views_orders.py +++ b/merchant_ops/views_orders.py @@ -1,16 +1,20 @@ """子客服专用订单接口(不修改商家原接口)。""" import logging -from django.db.models import Q from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView -from backend.utils import pick_leixing_id, pick_order_id -from merchant_ops.helpers import proxy_merchant_order_view, staff_can_access_order -from merchant_ops.models import MerchantOrderDispatch +from backend.utils import pick_order_id +from merchant_ops.helpers import proxy_merchant_order_view from merchant_ops.services.authz import StaffAuthError, get_active_staff_member, member_has_perm +from merchant_ops.services.order_query import ( + staff_apply_order_filters, + staff_can_view_all_orders, + staff_order_ext_qs, + staff_order_to_dict, +) from merchant_ops.services.audit import audit_staff from merchant_ops.services.dispatch import staff_dispatch_order from merchant_ops.services.stats import bump_cancel_count, bump_penalty_count, update_merchant_and_staff_stats @@ -40,7 +44,11 @@ def _err(e): class StaffOrderListView(APIView): - """POST /yonghu/merchant-staff/order/list — 对齐 sjdingdanhq""" + """ + POST /yonghu/merchant-staff/order/list + - 有 order_view_all:该商家全部订单 + - 否则:仅 merchant_order_dispatch 中 staff_member_id=本人的订单 + """ permission_classes = [IsAuthenticated] def post(self, request): @@ -53,61 +61,33 @@ class StaffOrderListView(APIView): zhuangtai_list = data.get('zhuangtai_list', []) page = max(1, int(data.get('page', 1))) page_size = min(100, max(1, int(data.get('page_size', 10)))) - leixing_id = pick_leixing_id(data) - keyword = str(data.get('keyword', '')).strip() if not isinstance(zhuangtai_list, list) or not zhuangtai_list: return Response({'code': 3, 'msg': '状态列表不能为空', 'data': None}) - base_qs = MerchantOrderExt.query.filter( - MerchantID=member.merchant_id, - ).select_related('Order').filter(Order__Status__in=zhuangtai_list) + base_qs = staff_apply_order_filters( + staff_order_ext_qs(member, zhuangtai_list), + data, + ) - if not member_has_perm(member, 'order_view_all'): - own_ids = MerchantOrderDispatch.query.filter( - staff_member_id=member.id, - ).values_list('order_id', flat=True) - base_qs = base_qs.filter(Order__OrderID__in=list(own_ids)) + pending_qs = staff_order_ext_qs(member, [8]) + pending_count = pending_qs.count() - if leixing_id is not None: - base_qs = base_qs.filter(Order__ProductTypeID=leixing_id) - if keyword: - base_qs = base_qs.filter( - Q(Order__OrderID__exact=keyword) - | Q(Order__Description__icontains=keyword) - | Q(Order__Nickname__icontains=keyword) - | Q(Order__PlayerID__exact=keyword) - ) - - pending_count = MerchantOrderExt.query.filter( - MerchantID=member.merchant_id, Order__Status=8, - ).count() total_count = base_qs.count() start = (page - 1) * page_size page_qs = base_qs.order_by('-Order__CreateTime')[start:start + page_size] - order_list = [] - for ext in page_qs: - o = ext.Order - order_list.append({ - 'dingdan_id': o.OrderID, - 'zhuangtai': o.Status, - 'jine': float(o.Amount) if o.Amount else 0.0, - 'jieshao': o.Description or '', - 'beizhu': o.Remark or '', - 'nicheng': o.Nickname or '', - 'leixing_id': o.ProductTypeID, - 'tupian': o.ImageURL or '', - 'jiedan_dashou_id': o.PlayerID or '', - 'CreateTime': o.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if o.CreateTime else '', - }) + order_list = [staff_order_to_dict(ext) for ext in page_qs] return Response({ - 'code': 0, 'msg': '成功', + 'code': 0, + 'msg': '成功', 'data': { 'list': order_list, 'has_more': len(page_qs) == page_size and start + page_size < total_count, + 'total': total_count, 'pending_count': pending_count, + 'view_scope': 'all' if staff_can_view_all_orders(member) else 'self', }, }) except Exception as e: diff --git a/merchant_ops/views_templates.py b/merchant_ops/views_templates.py index 0291146..46138b3 100644 --- a/merchant_ops/views_templates.py +++ b/merchant_ops/views_templates.py @@ -1,10 +1,9 @@ -"""子客服专用:派单模板 / 链接(代理商家 config 接口,校验权限,不修改原 /peizhi/*)。""" +"""子客服专用:派单模板 / 链接(独立路径;读模板用 merchant_id,写模板需 template_manage)。""" from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from config.views import ( - ShangjiaMobanListView, ShangjiaTianjiaMobanView, ShangjiaGengxinMobanView, ShangjiaShanchuMobanView, @@ -13,6 +12,7 @@ from config.views import ( ) from merchant_ops.helpers import proxy_as_merchant_user from merchant_ops.services.authz import StaffAuthError, get_active_staff_member, member_has_perm +from merchant_ops.services.template_query import list_merchant_templates from merchant_ops.views_manage import _err @@ -23,13 +23,23 @@ def _require_staff(request): return member +def _can_read_templates(member): + """派单发常规单需读模板;改模板需 template_manage。""" + return member_has_perm(member, 'order_dispatch') or member_has_perm(member, 'template_manage') + + class StaffTemplateListView(APIView): + """读商家全部模板(UserID=merchant_id),有 order_dispatch 即可。""" + permission_classes = [IsAuthenticated] def post(self, request): try: member = _require_staff(request) - return proxy_as_merchant_user(request, member, ShangjiaMobanListView, 'template_manage') + if not _can_read_templates(member): + raise StaffAuthError(403, '无权限查看模板') + payload, http_status = list_merchant_templates(member.merchant_id, request.data) + return Response(payload, status=http_status) except Exception as e: return _err(e) @@ -40,6 +50,8 @@ class StaffTemplateCreateView(APIView): def post(self, request): try: member = _require_staff(request) + if not member_has_perm(member, 'template_manage'): + raise StaffAuthError(403, '无权限添加模板') return proxy_as_merchant_user(request, member, ShangjiaTianjiaMobanView, 'template_manage') except Exception as e: return _err(e) @@ -51,6 +63,8 @@ class StaffTemplateUpdateView(APIView): def post(self, request): try: member = _require_staff(request) + if not member_has_perm(member, 'template_manage'): + raise StaffAuthError(403, '无权限修改模板') return proxy_as_merchant_user(request, member, ShangjiaGengxinMobanView, 'template_manage') except Exception as e: return _err(e) @@ -62,6 +76,8 @@ class StaffTemplateDeleteView(APIView): def post(self, request): try: member = _require_staff(request) + if not member_has_perm(member, 'template_manage'): + raise StaffAuthError(403, '无权限删除模板') return proxy_as_merchant_user(request, member, ShangjiaShanchuMobanView, 'template_manage') except Exception as e: return _err(e)