feat: 商家订单准确统计、客服备注与按客服筛选

This commit is contained in:
XingQue
2026-06-27 05:17:36 +08:00
parent 2445b39f3b
commit fdecf2fe3c
10 changed files with 304 additions and 15 deletions

View File

@@ -78,4 +78,21 @@ def apply_merchant_order_list_filters(qs, data):
| Q(Order__PlayerID__exact=keyword)
| Q(Order__ExternalOrderID__exact=keyword)
)
staff_member_id = data.get('staff_member_id')
if staff_member_id:
from merchant_ops.models import MerchantOrderDispatch
try:
smid = int(staff_member_id)
except (TypeError, ValueError):
return qs.none()
merchant_id = data.get('_merchant_id_filter')
disp_q = MerchantOrderDispatch.objects.filter(staff_member_id=smid)
if merchant_id:
disp_q = disp_q.filter(merchant_id=merchant_id)
order_ids = list(disp_q.values_list('order_id', flat=True))
if not order_ids:
return qs.none()
qs = qs.filter(Order__OrderID__in=order_ids)
return qs

View File

@@ -0,0 +1,71 @@
"""商家订单维度统计(从订单表聚合,保证金额准确)。"""
from decimal import Decimal
from django.db.models import Count, Sum
from merchant_ops.models import MerchantOrderDispatch
from orders.models import MerchantOrderExt
STATUS_PENDING = 8
STATUS_COMPLETED = 3
STATUS_REFUNDED = 5
def _money_str(val):
if val is None:
return '0.00'
return str(val)
def _agg_amount(qs, status=None):
if status is not None:
qs = qs.filter(Order__Status=status)
row = qs.aggregate(cnt=Count('id'), amt=Sum('Order__Amount'))
return int(row['cnt'] or 0), _money_str(row['amt'])
def build_merchant_order_stats(base_qs):
"""
base_qs: MerchantOrderExt queryset已含商家/客服范围与时间等筛选)
返回订单统计字典。
"""
pending_cnt, pending_amt = _agg_amount(base_qs, STATUS_PENDING)
completed_cnt, completed_amt = _agg_amount(base_qs, STATUS_COMPLETED)
refund_cnt, refund_amt = _agg_amount(base_qs, STATUS_REFUNDED)
dispatch_cnt, dispatch_amt = _agg_amount(base_qs, None)
return {
'pending_count': pending_cnt,
'pending_amount': pending_amt,
'completed_count': completed_cnt,
'completed_amount': completed_amt,
'refund_count': refund_cnt,
'refund_amount': refund_amt,
'dispatch_count': dispatch_cnt,
'dispatch_amount': dispatch_amt,
}
def merchant_ext_qs(merchant_id, zhuangtai_list=None):
qs = MerchantOrderExt.query.filter(MerchantID=merchant_id).select_related('Order')
if zhuangtai_list is not None:
qs = qs.filter(Order__Status__in=zhuangtai_list)
return qs
def filter_by_staff_member(qs, merchant_id, staff_member_id):
if not staff_member_id:
return qs
try:
mid = int(staff_member_id)
except (TypeError, ValueError):
return qs.none()
order_ids = list(
MerchantOrderDispatch.query.filter(
merchant_id=merchant_id,
staff_member_id=mid,
).values_list('order_id', flat=True)
)
if not order_ids:
return qs.none()
return qs.filter(Order__OrderID__in=order_ids)

View File

@@ -9,7 +9,7 @@ from .views import CreateOrderView, WechatPayNotifyView, \
AdTongYiTuiKuanPingTai, AdTongYiTuiKuanShangJia, AdQiangZhiJieDan, AdJuJueTuiKuan,\
AdGengHuanDaShou,ShangjiaCOSZhengshuView, DashouHuoquLeixingView,AdJuJueJieSuan, AdZhuanYiDaTing, AdQuXiaoZhiDing,DashouXiugaiView,DaiLiQunLiaoXiaoXiView,\
ShangjiaLianjieLiuYanZhuanFaView, DingdanXiangqingView2, JiedanView2, ZxsjghdsView, \
ShangjiaFakuanApplyView, ShangjiaFakuaiXiugaiView
ShangjiaFakuanApplyView, ShangjiaFakuaiXiugaiView, ShangjiaShujuTongjiView
urlpatterns = [
@@ -24,6 +24,7 @@ urlpatterns = [
path('sjspleixing', ShangpinLeixingView.as_view(), name='派单获取商品类型'),
path('sjpaifa', ShangjiaPaifaView.as_view(), name='商家发单'),
path('sjdingdanhq', ShangjiaDingdanHuoquView.as_view(), name='商家订单获取'),
path('sjshujutj', ShangjiaShujuTongjiView.as_view(), name='商家经营统计'),
path('sjddxq', ShangjiaDingdanXiangqingView.as_view(), name='商家订单详情获取'),
path('sjjiesuan', ShangjiaJiesuanView.as_view(), name='商家结单'),
path('sjchexiao', ShangjiaChexiaoView.as_view(), name='商家撤销订单'),

View File

@@ -2082,17 +2082,22 @@ class ShangjiaDingdanHuoquView(APIView):
MerchantID=yonghuid
).select_related('Order')
filter_payload = dict(data)
filter_payload['_merchant_id_filter'] = yonghuid
# 5. 状态过滤
base_qs = base_qs.filter(Order__Status__in=zhuangtai_list)
# 67. 类型 / 关键字 / 时间段
base_qs = apply_merchant_order_list_filters(base_qs, data)
# 67. 类型 / 关键字 / 时间段 / 客服筛选
base_qs = apply_merchant_order_list_filters(base_qs, filter_payload)
# 8. 计算待结算订单数量状态为8
pending_count = MerchantOrderExt.query.filter(
MerchantID=yonghuid,
Order__Status=8
).count()
# 8. 待结算统计(全量聚合,非分页求和
from orders.services.merchant_order_stats import (
build_merchant_order_stats, merchant_ext_qs, filter_by_staff_member,
)
stats_qs = merchant_ext_qs(yonghuid)
stats_qs = apply_merchant_order_list_filters(stats_qs, filter_payload)
order_stats = build_merchant_order_stats(stats_qs)
# 9. 排序、分页
total_count = base_qs.count()
@@ -2117,7 +2122,8 @@ class ShangjiaDingdanHuoquView(APIView):
'list': order_list,
'has_more': has_more,
'total': total_count,
'pending_count': pending_count
'pending_count': order_stats['pending_count'],
'pending_amount': order_stats['pending_amount'],
}
}, status=status.HTTP_200_OK)
@@ -2130,6 +2136,71 @@ class ShangjiaDingdanHuoquView(APIView):
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class ShangjiaShujuTongjiView(APIView):
"""
商家经营数据统计(订单表聚合)
POST /dingdan/sjshujutj
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
current_user = request.user
yonghuid = current_user.UserUID
except AttributeError:
return Response({'code': 1, 'msg': '用户认证失败', 'data': None}, status=401)
try:
shangjia = current_user.ShopProfile
if shangjia.zhuangtai != 1:
return Response({'code': 403, 'msg': '商家状态异常', 'data': None}, status=403)
except Exception:
return Response({'code': 403, 'msg': '非商家用户', 'data': None}, status=403)
data = request.data or {}
from orders.services.merchant_order_list_filters import apply_merchant_order_list_filters
from orders.services.merchant_order_stats import merchant_ext_qs, build_merchant_order_stats
filter_payload = dict(data)
filter_payload['_merchant_id_filter'] = yonghuid
qs = merchant_ext_qs(yonghuid)
qs = apply_merchant_order_list_filters(qs, filter_payload)
order_stats = build_merchant_order_stats(qs)
penalty_stats = None
try:
from orders.models import Penalty, PenaltyRecord
from django.db.models import Q, Sum
fakuan_pending = Penalty.query.filter(ApplicantID=yonghuid, Status=1).count()
fakuan_pending_amt = Penalty.query.filter(ApplicantID=yonghuid, Status=1).aggregate(
amt=Sum('FineAmount')
)['amt']
jifen_pending = PenaltyRecord.query.filter(
ApplicantID=yonghuid,
).filter(Q(ApplyStatus=0) | Q(ApplyStatus=3)).count()
penalty_stats = {
'fakuan_pending': fakuan_pending,
'fakuan_pending_amount': str(fakuan_pending_amt or '0.00'),
'jifen_pending': jifen_pending,
}
except Exception:
penalty_stats = {
'fakuan_pending': 0,
'fakuan_pending_amount': '0.00',
'jifen_pending': 0,
}
return Response({
'code': 0,
'msg': '成功',
'data': {
'order': order_stats,
'penalty': penalty_stats,
'can_view_finance': True,
},
})
class ShangjiaDingdanXiangqingView(APIView):