进行了完整的视图拆分,同步了 merchant_ops 的修改
This commit is contained in:
7101
orders/views.py
7101
orders/views.py
File diff suppressed because it is too large
Load Diff
87
orders/views/__init__.py
Normal file
87
orders/views/__init__.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""orders.views package aggregate entry - re-export all submodule symbols.
|
||||
|
||||
Preserves external `from orders.views import X` for orders/urls.py and merchant_ops.
|
||||
"""
|
||||
from .payment import (
|
||||
PaymentFailView,
|
||||
WechatPayNotifyView,
|
||||
PaymentVerifyView,
|
||||
CreateOrderView,
|
||||
)
|
||||
from .boss_orders import (
|
||||
DingdanHuoquView,
|
||||
DingdanXiangqingView,
|
||||
JiedanView,
|
||||
DingdanXiangqingView2,
|
||||
JiedanView2,
|
||||
)
|
||||
from .merchant import (
|
||||
ShangpinLeixingView,
|
||||
ShangjiaPaifaView,
|
||||
ShangjiaDingdanHuoquView,
|
||||
ShangjiaShujuTongjiView,
|
||||
ShangjiaDingdanXiangqingView,
|
||||
ShangjiaJiesuanView,
|
||||
ShangjiaChexiaoView,
|
||||
ShangjiaTuikuanShenqingView,
|
||||
ShangjiaChufaShenqingView,
|
||||
)
|
||||
from .chat import (
|
||||
DaiLiQunLiaoXiaoXiView,
|
||||
ShangjiaLianjieLiuYanZhuanFaView,
|
||||
LianTongDuiHuaZhunBeiView,
|
||||
LianTongPeiDuiDingDanLieBiaoView,
|
||||
LianTongDingDanZhuangTaiView,
|
||||
LianTongDingDanZhuangTaiPiLiangView,
|
||||
)
|
||||
from .dashou import (
|
||||
DashouDingdanHuoquView,
|
||||
ShangpinLeixingHuoquView,
|
||||
QiangdanView,
|
||||
DashouDingdanHuoquView1,
|
||||
DashouCOSZhengshuView,
|
||||
ShangjiaCOSZhengshuView,
|
||||
DashouTijiaoView,
|
||||
DashouDingdanXiangqingView,
|
||||
DashouHuoquLeixingView,
|
||||
DashouXiugaiView,
|
||||
)
|
||||
from .admin import (
|
||||
AdGetDingdanXiangQing,
|
||||
AdGengHuanDaShou,
|
||||
AdQiangZhiJieDan,
|
||||
AdJuJueTuiKuan,
|
||||
AdTongYiTuiKuanShangJia,
|
||||
AdTongYiTuiKuanPingTai,
|
||||
AdJuJueJieSuan,
|
||||
AdZhuanYiDaTing,
|
||||
AdQuXiaoZhiDing,
|
||||
ZhiDingChaxunView,
|
||||
ZhiDingHuiFuView,
|
||||
ZxsjghdsView,
|
||||
)
|
||||
from .merchant_penalty import (
|
||||
ShangjiaFakuanApplyView,
|
||||
ShangjiaFakuaiXiugaiView,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"PaymentFailView", "WechatPayNotifyView", "PaymentVerifyView", "CreateOrderView",
|
||||
"DingdanHuoquView", "DingdanXiangqingView", "JiedanView",
|
||||
"DingdanXiangqingView2", "JiedanView2",
|
||||
"ShangpinLeixingView", "ShangjiaPaifaView", "ShangjiaDingdanHuoquView",
|
||||
"ShangjiaShujuTongjiView", "ShangjiaDingdanXiangqingView", "ShangjiaJiesuanView",
|
||||
"ShangjiaChexiaoView", "ShangjiaTuikuanShenqingView", "ShangjiaChufaShenqingView",
|
||||
"DaiLiQunLiaoXiaoXiView", "ShangjiaLianjieLiuYanZhuanFaView",
|
||||
"LianTongDuiHuaZhunBeiView", "LianTongPeiDuiDingDanLieBiaoView",
|
||||
"LianTongDingDanZhuangTaiView", "LianTongDingDanZhuangTaiPiLiangView",
|
||||
"DashouDingdanHuoquView", "ShangpinLeixingHuoquView", "QiangdanView",
|
||||
"DashouDingdanHuoquView1", "DashouCOSZhengshuView", "ShangjiaCOSZhengshuView",
|
||||
"DashouTijiaoView", "DashouDingdanXiangqingView", "DashouHuoquLeixingView",
|
||||
"DashouXiugaiView",
|
||||
"AdGetDingdanXiangQing", "AdGengHuanDaShou", "AdQiangZhiJieDan",
|
||||
"AdJuJueTuiKuan", "AdTongYiTuiKuanShangJia", "AdTongYiTuiKuanPingTai",
|
||||
"AdJuJueJieSuan", "AdZhuanYiDaTing", "AdQuXiaoZhiDing",
|
||||
"ZhiDingChaxunView", "ZhiDingHuiFuView", "ZxsjghdsView",
|
||||
"ShangjiaFakuanApplyView", "ShangjiaFakuaiXiugaiView",
|
||||
]
|
||||
1958
orders/views/admin.py
Normal file
1958
orders/views/admin.py
Normal file
File diff suppressed because it is too large
Load Diff
821
orders/views/boss_orders.py
Normal file
821
orders/views/boss_orders.py
Normal file
@@ -0,0 +1,821 @@
|
||||
"""orders.views.boss_orders - auto-generated by split script."""
|
||||
# ==================== 标准库 ====================
|
||||
import hmac
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
import string
|
||||
import traceback
|
||||
import threading
|
||||
import requests
|
||||
import hashlib
|
||||
import xml.etree.ElementTree as ET
|
||||
import xmltodict
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models, transaction
|
||||
from gvsdsdk.fluent import db, func, FQ
|
||||
from django.db.models import F, Q, OuterRef, Subquery
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.http import HttpResponse
|
||||
from django.utils import timezone
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import permissions, status
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.parsers import JSONParser
|
||||
from rest_framework.throttling import AnonRateThrottle
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
|
||||
from tencentcloud.common import credential
|
||||
from tencentcloud.common.profile.client_profile import ClientProfile
|
||||
from tencentcloud.common.profile.http_profile import HttpProfile
|
||||
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
||||
from tencentcloud.sts.v20180813 import sts_client, models
|
||||
|
||||
from utils.weixin_broadcast import WeixinBroadcastSender
|
||||
from utils.money import yuan_to_fen
|
||||
from utils.chat_utils import (
|
||||
_send_group_message, _subscribe_users_to_group, establish_order_chat,
|
||||
prepare_order_group_chat, resolve_pair_group_id,
|
||||
parse_pair_from_group_id, resolve_pair_from_order, check_pair_chat_permission,
|
||||
query_pair_orders, order_to_chat_dict,
|
||||
)
|
||||
from utils.fadan_utils import check_fadan_qiangdan_eligible
|
||||
|
||||
from orders.utils import (
|
||||
calc_shangjia_order_fencheng,
|
||||
update_daily_payout,
|
||||
settle_shangjia_order_guanshi_fenhong
|
||||
)
|
||||
from shop.utils import calculate_pingtai_and_dianpu_shouyi, validate_shangpin_and_dianpu, update_dianpu_daily_stat
|
||||
from products.utils import update_shangpin_daily_stat
|
||||
from backend.utils import update_shangjia_daily, update_dashou_daily_by_action, pick_leixing_id, datetime_aliases, fmt_datetime
|
||||
from rank.services import record_dashou_biaoxian
|
||||
from rank.utils import check_dashou_biaoqian_required
|
||||
|
||||
from orders.notice_tasks import dingdan_guangbo
|
||||
|
||||
from ..models import (
|
||||
Order, MerchantOrderExt, PlatformOrderExt, PlayerDeliveryImage,
|
||||
PenaltyRecord, PenaltyEvidenceImage, Penalty, PenaltyAppealImage, PenaltyBonus, PenaltyBonusRate,
|
||||
CommissionRate, PlayerRating, RefundRecord,
|
||||
)
|
||||
from jituan.services.club_config import get_commission_rate, get_commission_rate_object
|
||||
from jituan.services.club_context import resolve_club_id_from_request
|
||||
from jituan.services.club_user import get_payment_openid
|
||||
from jituan.services.club_penalty import resolve_penalty_club_id, resolve_penalty_record_club_id
|
||||
from users.fadan_fenhong_utils import lock_penalty_bonus
|
||||
from users.fadan_fenhong_utils import lock_penalty_bonus
|
||||
from products.models import Shangpin, ShangpinLeixing, Huiyuangoumai
|
||||
from users.models import UserDashou, UserShangjia, UserBoss
|
||||
from users.business_models import User
|
||||
from rank.models import DashouBiaoxian, Chenghao, DingdanBiaoqian, YonghuChenghao
|
||||
from config.models import (
|
||||
ShangjiaLianjie
|
||||
)
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DingdanHuoquView(APIView):
|
||||
"""订单获取接口"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
# 获取当前登录用户
|
||||
try:
|
||||
current_user = request.user
|
||||
yonghuid = current_user.UserUID
|
||||
except Exception as e:
|
||||
return Response({
|
||||
'code': 1,
|
||||
'msg': '用户认证失败',
|
||||
'data': None
|
||||
})
|
||||
|
||||
# 获取前端参数
|
||||
try:
|
||||
data = request.data
|
||||
zhuangtai_list = data.get('zhuangtai_list', [])
|
||||
page = int(data.get('page', 1))
|
||||
page_size = int(data.get('page_size', 10))
|
||||
except Exception as e:
|
||||
return Response({
|
||||
'code': 2,
|
||||
'msg': '参数格式错误',
|
||||
'data': None
|
||||
})
|
||||
|
||||
# 参数验证
|
||||
if not isinstance(zhuangtai_list, list) or len(zhuangtai_list) == 0:
|
||||
return Response({
|
||||
'code': 3,
|
||||
'msg': '状态列表不能为空',
|
||||
'data': None
|
||||
})
|
||||
|
||||
if page < 1:
|
||||
page = 1
|
||||
if page_size <= 0:
|
||||
page_size = 10
|
||||
|
||||
try:
|
||||
# 查询扩展表,获取用户的所有订单ID
|
||||
pingtai_orders = PlatformOrderExt.query.filter(BossID=yonghuid)
|
||||
pingtai_order_ids = list(pingtai_orders.values_list('Order__OrderID', flat=True))
|
||||
|
||||
if not pingtai_order_ids:
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': '成功',
|
||||
'data': {
|
||||
'list': [],
|
||||
'has_more': False,
|
||||
'total': 0
|
||||
}
|
||||
})
|
||||
|
||||
# 查询主表
|
||||
status_query = Q()
|
||||
if zhuangtai_list != [1, 2, 3, 4, 5, 6, 7, 8]:
|
||||
status_query = Q(Status__in=zhuangtai_list)
|
||||
|
||||
query = Q(OrderID__in=pingtai_order_ids) & status_query
|
||||
orders = Order.query.filter(query).order_by('-CreateTime')
|
||||
#total_orders = orders.count()
|
||||
|
||||
# 手动分页逻辑
|
||||
start_index = (page - 1) * page_size
|
||||
end_index = start_index + page_size
|
||||
|
||||
# 获取当前页的数据
|
||||
current_page_orders = orders[start_index:end_index]
|
||||
|
||||
# 构建返回数据
|
||||
order_list = []
|
||||
for order in current_page_orders:
|
||||
order_data = {
|
||||
'dingdanId': order.OrderID,
|
||||
'zhuangtai': order.Status,
|
||||
'jine': float(order.Amount) if order.Amount else 0.0,
|
||||
'tupian': order.ImageURL or '',
|
||||
'jieshao': order.Description or '',
|
||||
'beizhu': order.Remark or '',
|
||||
'nicheng': order.Nickname or '',
|
||||
'chuangjianshijian': fmt_datetime(order.CreateTime),
|
||||
'create_time': fmt_datetime(order.CreateTime),
|
||||
}
|
||||
order_list.append(order_data)
|
||||
|
||||
|
||||
# 判断是否还有更多数据
|
||||
current_page_count = len(current_page_orders)
|
||||
has_more = current_page_count == page_size
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': '成功',
|
||||
'data': {
|
||||
'list': order_list,
|
||||
'has_more': has_more,
|
||||
#'total': total_orders
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return Response({
|
||||
'code': 4,
|
||||
'msg': '服务器内部错误',
|
||||
'data': None
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class DingdanXiangqingView(APIView):
|
||||
"""订单详情接口"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
# 获取当前登录用户
|
||||
try:
|
||||
current_user = request.user
|
||||
yonghuid = current_user.UserUID
|
||||
except Exception as e:
|
||||
return Response({
|
||||
'code': 1,
|
||||
'msg': '用户认证失败',
|
||||
'data': None
|
||||
})
|
||||
|
||||
# 获取前端参数
|
||||
try:
|
||||
data = request.data
|
||||
dingdan_id = data.get('dingdanId', '')
|
||||
except Exception as e:
|
||||
return Response({
|
||||
'code': 2,
|
||||
'msg': '参数格式错误',
|
||||
'data': None
|
||||
})
|
||||
|
||||
# 参数验证
|
||||
if not dingdan_id:
|
||||
return Response({
|
||||
'code': 3,
|
||||
'msg': '订单ID不能为空',
|
||||
'data': None
|
||||
})
|
||||
|
||||
try:
|
||||
# 1. 验证订单是否属于当前用户
|
||||
# 查询平台订单扩展表,检查laoban_id是否等于当前用户
|
||||
pingtai_kuozhan = PlatformOrderExt.query.filter(
|
||||
BossID=yonghuid,
|
||||
Order__OrderID=dingdan_id
|
||||
).first()
|
||||
|
||||
if not pingtai_kuozhan:
|
||||
# 如果不是平台订单,检查商家订单
|
||||
shangjia_kuozhan = MerchantOrderExt.query.filter(
|
||||
MerchantID=yonghuid,
|
||||
Order__OrderID=dingdan_id
|
||||
).first()
|
||||
|
||||
if not shangjia_kuozhan:
|
||||
return Response({
|
||||
'code': 4,
|
||||
'msg': '订单不存在或无权限查看',
|
||||
'data': None
|
||||
})
|
||||
|
||||
# 2. 查询订单主表
|
||||
dingdan = Order.query.filter(OrderID=dingdan_id).first()
|
||||
|
||||
if not dingdan:
|
||||
return Response({
|
||||
'code': 5,
|
||||
'msg': '订单不存在',
|
||||
'data': None
|
||||
})
|
||||
|
||||
# 3. 获取订单信息
|
||||
zhuangtai = dingdan.Status or 0
|
||||
zhiding_id = dingdan.AssignedID or ''
|
||||
jiedan_dashou_id = dingdan.PlayerID or ''
|
||||
dashou_liuyan = dingdan.PlayerRemark or ''
|
||||
|
||||
# 4. 查询打手交付图片
|
||||
dashoutupian_records = PlayerDeliveryImage.query.filter(
|
||||
OrderID=dingdan_id
|
||||
).order_by('CreateTime')[:9] # 最多取9条
|
||||
|
||||
dashoujiaofu_list = []
|
||||
for record in dashoutupian_records:
|
||||
if record.ImageURL:
|
||||
dashoujiaofu_list.append(record.ImageURL)
|
||||
|
||||
# 5. 构建返回数据(字段名与前端对应)
|
||||
response_data = {
|
||||
'zhuangtai': zhuangtai, # 订单状态
|
||||
'zhiding': zhiding_id, # 指定打手ID
|
||||
'jiedan_dashou_id': jiedan_dashou_id, # 服务打手ID
|
||||
'dashouliuyan': dashou_liuyan, # 打手留言
|
||||
'dashoujiaofu': dashoujiaofu_list # 打手交付图片列表
|
||||
}
|
||||
|
||||
# 6. 返回成功响应
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': '成功',
|
||||
'data': response_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"订单详情查询失败: {str(e)}")
|
||||
return Response({
|
||||
'code': 6,
|
||||
'msg': '服务器内部错误',
|
||||
'data': None
|
||||
})
|
||||
|
||||
|
||||
class JiedanView(APIView):
|
||||
"""结单接口"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
# 获取当前登录用户
|
||||
try:
|
||||
current_user = request.user
|
||||
yonghuid = current_user.UserUID
|
||||
except Exception as e:
|
||||
return Response({
|
||||
'code': 1,
|
||||
'msg': '用户认证失败',
|
||||
'data': None
|
||||
})
|
||||
|
||||
# 获取前端参数
|
||||
try:
|
||||
data = request.data
|
||||
dingdan_id = data.get('dingdanId', '')
|
||||
pingfen = int(data.get('pingfen', 0)) # 评分,默认0
|
||||
liuyan = data.get('liuyan', '') # 留言,默认空
|
||||
except Exception as e:
|
||||
return Response({
|
||||
'code': 2,
|
||||
'msg': '参数格式错误',
|
||||
'data': None
|
||||
})
|
||||
|
||||
# 参数验证
|
||||
if not dingdan_id:
|
||||
return Response({
|
||||
'code': 3,
|
||||
'msg': '订单ID不能为空',
|
||||
'data': None
|
||||
})
|
||||
|
||||
# 验证评分范围
|
||||
if pingfen < 0 or pingfen > 5:
|
||||
return Response({
|
||||
'code': 4,
|
||||
'msg': '评分必须在0-5分之间',
|
||||
'data': None
|
||||
})
|
||||
|
||||
# 使用事务确保数据一致性
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# 1. 验证订单是否存在且属于当前用户
|
||||
pingtai_kuozhan = PlatformOrderExt.query.filter(
|
||||
BossID=yonghuid,
|
||||
Order__OrderID=dingdan_id
|
||||
).select_related('Order').first()
|
||||
|
||||
if not pingtai_kuozhan:
|
||||
return Response({
|
||||
'code': 5,
|
||||
'msg': '订单不存在或无权限操作',
|
||||
'data': None
|
||||
})
|
||||
|
||||
dingdan = pingtai_kuozhan.Order
|
||||
|
||||
# 2. 验证订单状态(必须是结算中 - 状态8)
|
||||
if dingdan.Status != 8:
|
||||
return Response({
|
||||
'code': 6,
|
||||
'msg': '订单不在结算状态,无法结单',
|
||||
'data': None
|
||||
})
|
||||
|
||||
# 3. 获取订单相关数据
|
||||
dashou_fencheng = dingdan.PlayerCommission or Decimal('0.00') # 打手分成
|
||||
jiedan_dashou_id = dingdan.PlayerID or '' # 接单打手ID
|
||||
shangpin_id = dingdan.ProductID # 商品ID
|
||||
|
||||
# 4. 更新平台扩展表的老板评价(如果有留言)
|
||||
if liuyan:
|
||||
pingtai_kuozhan.BossReview = liuyan
|
||||
pingtai_kuozhan.save()
|
||||
|
||||
# 5. 如果有评分,更新评分表
|
||||
if pingfen > 0 and jiedan_dashou_id:
|
||||
# 使用filter查询,更健壮
|
||||
pingfen_records = PlayerRating.query.filter(PlayerID=jiedan_dashou_id)
|
||||
|
||||
if pingfen_records.exists():
|
||||
# 已有记录,更新
|
||||
pingfen_record = pingfen_records.first()
|
||||
pingfen_record.TotalScore += Decimal(str(pingfen))
|
||||
pingfen_record.RatingCount += 1
|
||||
pingfen_record.save()
|
||||
else:
|
||||
# 新记录,创建
|
||||
PlayerRating.query.create(
|
||||
PlayerID=jiedan_dashou_id,
|
||||
TotalScore=Decimal(str(pingfen)),
|
||||
RatingCount=1
|
||||
)
|
||||
|
||||
# 6. 更新商品真实销量(如果有商品ID)
|
||||
if shangpin_id:
|
||||
shangpin_records = Shangpin.query.filter(id=shangpin_id)
|
||||
|
||||
if shangpin_records.exists():
|
||||
shangpin = shangpin_records.first()
|
||||
# 检查字段是否存在
|
||||
if hasattr(shangpin, 'zhenshi_xiaoliang'):
|
||||
shangpin.zhenshi_xiaoliang = (shangpin.zhenshi_xiaoliang or 0) + 1
|
||||
shangpin.save()
|
||||
|
||||
# 7. 更新打手扩展表(如果有打手ID和分成)
|
||||
if jiedan_dashou_id and dashou_fencheng > 0:
|
||||
# 查询打手用户
|
||||
dashou_user = User.query.filter(UserUID=jiedan_dashou_id).first()
|
||||
|
||||
if dashou_user:
|
||||
# 查询打手扩展表
|
||||
dashou_kuozhan = UserDashou.query.filter(user=dashou_user).first()
|
||||
|
||||
if dashou_kuozhan:
|
||||
# 更新四个金额字段
|
||||
dashou_kuozhan.zonge = (dashou_kuozhan.zonge or Decimal('0.00')) + dashou_fencheng
|
||||
dashou_kuozhan.yue = (dashou_kuozhan.yue or Decimal('0.00')) + dashou_fencheng
|
||||
dashou_kuozhan.jinrishouyi = (dashou_kuozhan.jinrishouyi or Decimal(
|
||||
'0.00')) + dashou_fencheng
|
||||
dashou_kuozhan.jinyueshouyi = (dashou_kuozhan.jinyueshouyi or Decimal(
|
||||
'0.00')) + dashou_fencheng
|
||||
|
||||
# 成交总量加1
|
||||
dashou_kuozhan.chengjiaozongliang = (dashou_kuozhan.chengjiaozongliang or 0) + 1
|
||||
|
||||
# 状态改为数字1
|
||||
dashou_kuozhan.zhuangtai = 1
|
||||
|
||||
dashou_kuozhan.save()
|
||||
|
||||
# 8. 更新订单状态为已完成(状态3)
|
||||
dingdan.Status = 3
|
||||
dingdan.save()
|
||||
|
||||
# 🔥 新增:调用打手每日统计(成交)
|
||||
try:
|
||||
update_dashou_daily_by_action(
|
||||
yonghuid=jiedan_dashou_id,
|
||||
amount=dashou_fencheng,
|
||||
action=2 # 2 表示成交(结算)
|
||||
)
|
||||
logger.info(f"打手 {jiedan_dashou_id} 每日统计更新成功")
|
||||
except Exception as e:
|
||||
logger.error(f"打手每日统计更新失败: {e}")
|
||||
# 不影响主流程
|
||||
|
||||
|
||||
# ========== 新增:商品和店铺每日统计 ==========
|
||||
try:
|
||||
# 商品每日统计(只要有商品ID就执行)
|
||||
if dingdan.ProductID:
|
||||
# 从扩展表获取已计算好的收益值(下单时已存储)
|
||||
pingtai_shouyi = Decimal('0.00')
|
||||
dianpu_shouyi = Decimal('0.00')
|
||||
if hasattr(dingdan, 'pingtai_kuozhan'):
|
||||
pingtai_shouyi = dingdan.pingtai_kuozhan.PlatformIncome or Decimal('0.00')
|
||||
dianpu_shouyi = dingdan.pingtai_kuozhan.ShopIncome or Decimal('0.00')
|
||||
|
||||
update_shangpin_daily_stat(
|
||||
ProductID=dingdan.ProductID,
|
||||
caozuo_leixing=2, # 2 = 结单
|
||||
dingdan_jiage=dingdan.Amount,
|
||||
PlatformIncome=pingtai_shouyi,
|
||||
dianpu_zongshouyi=dianpu_shouyi
|
||||
)
|
||||
logger.info(f"商品统计更新完成,商品ID: {dingdan.ProductID}")
|
||||
|
||||
# 店铺每日统计(仅当存在店铺ID时执行)
|
||||
|
||||
|
||||
# ========== 修改为 ==========
|
||||
# 店铺每日统计(包含上级分红)
|
||||
dianpu_id = None
|
||||
shangji_id = None
|
||||
shangji_fen = Decimal('0.00')
|
||||
if hasattr(dingdan, 'pingtai_kuozhan'):
|
||||
dianpu_id = dingdan.pingtai_kuozhan.ShopID
|
||||
shangji_id = dingdan.pingtai_kuozhan.ParentShopID
|
||||
shangji_fen = dingdan.pingtai_kuozhan.ParentBonus or Decimal('0.00')
|
||||
if dianpu_id:
|
||||
dianpu_shouyi = dingdan.pingtai_kuozhan.ShopIncome or Decimal('0.00')
|
||||
update_dianpu_daily_stat(
|
||||
ShopID=dianpu_id,
|
||||
caozuo_leixing=2, # 结算
|
||||
dingdan_jiage=dingdan.Amount,
|
||||
ShopIncome=dianpu_shouyi,
|
||||
shangji_ShopID=shangji_id, # 新增:上级店铺ID
|
||||
ParentBonus=shangji_fen # 新增:该单给上级的分红
|
||||
)
|
||||
logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}, 上级ID: {shangji_id}, 上级分红: {shangji_fen}")
|
||||
|
||||
|
||||
'''dianpu_id = None
|
||||
if hasattr(dingdan, 'pingtai_kuozhan'):
|
||||
dianpu_id = dingdan.pingtai_kuozhan.ShopID
|
||||
if dianpu_id:
|
||||
dianpu_shouyi = dingdan.pingtai_kuozhan.ShopIncome or Decimal('0.00')
|
||||
update_dianpu_daily_stat(
|
||||
ShopID=dianpu_id,
|
||||
caozuo_leixing=2, # 2 = 结单
|
||||
dingdan_jiage=dingdan.Amount,
|
||||
ShopIncome=dianpu_shouyi
|
||||
)
|
||||
logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}")'''
|
||||
except Exception as e:
|
||||
logger.error(f"商品/店铺统计更新失败: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
# 统计失败不影响主流程
|
||||
# =============================================
|
||||
|
||||
# 9. 返回成功响应
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': '结单成功',
|
||||
'data': None
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
|
||||
# 事务会自动回滚
|
||||
return Response({
|
||||
'code': 7,
|
||||
'msg': '结单失败,服务器内部错误',
|
||||
'data': None
|
||||
})
|
||||
|
||||
|
||||
class DingdanXiangqingView2(APIView):
|
||||
"""订单详情接口(老板端)- 返回完整评价信息"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
yonghuid = request.user.UserUID
|
||||
except Exception:
|
||||
return Response({'code': 1, 'msg': '用户认证失败', 'data': None})
|
||||
|
||||
dingdan_id = request.data.get('dingdanId', '')
|
||||
if not dingdan_id:
|
||||
return Response({'code': 2, 'msg': '订单ID不能为空', 'data': None})
|
||||
|
||||
try:
|
||||
# 权限验证
|
||||
pingtai_ext = PlatformOrderExt.query.filter(
|
||||
BossID=yonghuid,
|
||||
Order__OrderID=dingdan_id
|
||||
).select_related('Order').first()
|
||||
|
||||
if not pingtai_ext:
|
||||
return Response({'code': 3, 'msg': '订单不存在或无权限查看', 'data': None})
|
||||
|
||||
order = pingtai_ext.Order
|
||||
zhuangtai = order.Status or 0
|
||||
jiedan_dashou_id = order.PlayerID or ''
|
||||
dashou_liuyan = order.PlayerRemark or ''
|
||||
UpdateTime = order.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if order.UpdateTime else ''
|
||||
|
||||
# 交付图片
|
||||
dashoujiaofu_list = list(
|
||||
PlayerDeliveryImage.query.filter(OrderID=dingdan_id)
|
||||
.order_by('CreateTime')[:9]
|
||||
.values_list('ImageURL', flat=True)
|
||||
)
|
||||
|
||||
# 打手信息
|
||||
dashou_nicheng = ''
|
||||
dashou_touxiang = ''
|
||||
jiedan_time = ''
|
||||
tijiao_time = ''
|
||||
if jiedan_dashou_id:
|
||||
dashou_user = User.query.filter(UserUID=jiedan_dashou_id).first()
|
||||
if dashou_user:
|
||||
dashou_touxiang = dashou_user.Avatar or ''
|
||||
try:
|
||||
dashou_nicheng = dashou_user.DashouProfile.nicheng or ''
|
||||
except UserDashou.DoesNotExist:
|
||||
pass
|
||||
biaoxian = DashouBiaoxian.query.filter(
|
||||
OrderID=dingdan_id,
|
||||
dashou_id=jiedan_dashou_id
|
||||
).first()
|
||||
if biaoxian:
|
||||
if biaoxian.jiedan_time:
|
||||
jiedan_time = biaoxian.jiedan_time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
if biaoxian.tijiao_time:
|
||||
tijiao_time = biaoxian.tijiao_time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 老板自己的评价(如果已经结算过,可能已有评价)
|
||||
laoban_pingjia = pingtai_ext.BossReview or ''
|
||||
shangxian_sudu = None
|
||||
fuwu_taidu = None
|
||||
fanche_cishu = None
|
||||
if jiedan_dashou_id and zhuangtai in [3, 4, 5, 6]: # 已完成或退款相关状态才可能有评价
|
||||
biaoxian = DashouBiaoxian.query.filter(
|
||||
OrderID=dingdan_id,
|
||||
dashou_id=jiedan_dashou_id
|
||||
).first()
|
||||
if biaoxian:
|
||||
shangxian_sudu = biaoxian.shangxian_sudu
|
||||
fuwu_taidu = biaoxian.fuwu_taidu
|
||||
fanche_cishu = biaoxian.fanche_cishu
|
||||
|
||||
data = {
|
||||
'zhuangtai': zhuangtai,
|
||||
'dashou': jiedan_dashou_id,
|
||||
'dashou_nicheng': dashou_nicheng,
|
||||
'dashou_touxiang': dashou_touxiang,
|
||||
'dashouliuyan': dashou_liuyan,
|
||||
'dashoujiaofu': dashoujiaofu_list,
|
||||
'UpdateTime': UpdateTime,
|
||||
'jiedan_time': jiedan_time,
|
||||
'tijiao_time': tijiao_time,
|
||||
'laoban_pingjia': laoban_pingjia,
|
||||
'shangxian_sudu': shangxian_sudu, # 1-5 或 null
|
||||
'fuwu_taidu': fuwu_taidu, # 1-5 或 null
|
||||
'fanche_cishu': fanche_cishu # int 或 null
|
||||
}
|
||||
|
||||
return Response({'code': 0, 'msg': '成功', 'data': data})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"订单详情查询异常: {e}", exc_info=True)
|
||||
return Response({'code': 4, 'msg': '服务器内部错误', 'data': None})
|
||||
|
||||
|
||||
class JiedanView2(APIView):
|
||||
"""结单接口(老板端)- 接收全新评价字段并写入表现表"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
# 1. 用户认证
|
||||
try:
|
||||
current_user = request.user
|
||||
yonghuid = current_user.UserUID
|
||||
except Exception:
|
||||
return Response({'code': 1, 'msg': '用户认证失败', 'data': None})
|
||||
|
||||
# 2. 获取参数(字段名与前端完全一致)
|
||||
try:
|
||||
data = request.data
|
||||
dingdan_id = data.get('dingdanId', '')
|
||||
fanche_cishu = data.get('fanche_cishu') # 前端传的字符串或数字,后端转为整数
|
||||
shangxian_sudu = data.get('shangxian_sudu')
|
||||
fuwu_taidu = data.get('fuwu_taidu')
|
||||
liuyan = data.get('liuyan', '')
|
||||
except Exception:
|
||||
return Response({'code': 2, 'msg': '参数格式错误', 'data': None})
|
||||
|
||||
# 参数校验
|
||||
if not dingdan_id:
|
||||
return Response({'code': 3, 'msg': '订单ID不能为空', 'data': None})
|
||||
|
||||
# 转换并校验必填评价字段
|
||||
try:
|
||||
fanche_cishu = int(fanche_cishu) if fanche_cishu is not None else None
|
||||
shangxian_sudu = int(shangxian_sudu) if shangxian_sudu is not None else None
|
||||
fuwu_taidu = int(fuwu_taidu) if fuwu_taidu is not None else None
|
||||
except (ValueError, TypeError):
|
||||
return Response({'code': 4, 'msg': '评价参数必须为整数', 'data': None})
|
||||
|
||||
if fanche_cishu is None:
|
||||
return Response({'code': 5, 'msg': '翻车次数不能为空', 'data': None})
|
||||
if not (1 <= shangxian_sudu <= 5 and 1 <= fuwu_taidu <= 5):
|
||||
return Response({'code': 6, 'msg': '评价分数必须在1-5之间', 'data': None})
|
||||
|
||||
# 使用事务保证数据一致性
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# 3. 权限验证:订单属于当前老板
|
||||
pingtai_ext = PlatformOrderExt.query.filter(
|
||||
BossID=yonghuid,
|
||||
Order__OrderID=dingdan_id
|
||||
).select_related('Order').first()
|
||||
|
||||
if not pingtai_ext:
|
||||
return Response({'code': 7, 'msg': '订单不存在或无权限操作', 'data': None})
|
||||
|
||||
order = pingtai_ext.Order
|
||||
|
||||
# 4. 状态校验:必须是结算中(状态8)
|
||||
if order.Status != 8:
|
||||
return Response({'code': 8, 'msg': '订单不在结算状态', 'data': None})
|
||||
|
||||
# 5. 提取订单关键数据
|
||||
dashou_fencheng = order.PlayerCommission or Decimal('0.00')
|
||||
jiedan_dashou_id = order.PlayerID or ''
|
||||
shangpin_id = order.ProductID
|
||||
|
||||
# 6. 保存老板评价(兼容旧逻辑)
|
||||
if liuyan:
|
||||
pingtai_ext.BossReview = liuyan
|
||||
pingtai_ext.save()
|
||||
|
||||
# 7. 更新评分表(用服务态度分作为综合评分,保留原有统计)
|
||||
if jiedan_dashou_id and fuwu_taidu:
|
||||
pingfen_records = PlayerRating.query.filter(PlayerID=jiedan_dashou_id)
|
||||
if pingfen_records.exists():
|
||||
pf = pingfen_records.first()
|
||||
pf.TotalScore += Decimal(str(fuwu_taidu))
|
||||
pf.RatingCount += 1
|
||||
pf.save()
|
||||
else:
|
||||
PlayerRating.query.create(
|
||||
PlayerID=jiedan_dashou_id,
|
||||
TotalScore=Decimal(str(fuwu_taidu)),
|
||||
RatingCount=1
|
||||
)
|
||||
|
||||
# 8. 更新商品真实销量
|
||||
if shangpin_id:
|
||||
shangpin_qs = Shangpin.query.filter(id=shangpin_id)
|
||||
if shangpin_qs.exists():
|
||||
sp = shangpin_qs.first()
|
||||
if hasattr(sp, 'zhenshi_xiaoliang'):
|
||||
sp.zhenshi_xiaoliang = (sp.zhenshi_xiaoliang or 0) + 1
|
||||
sp.save()
|
||||
|
||||
# 9. 更新打手收益与状态
|
||||
if jiedan_dashou_id and dashou_fencheng > 0:
|
||||
dashou_user = User.query.filter(UserUID=jiedan_dashou_id).first()
|
||||
if dashou_user:
|
||||
try:
|
||||
dashou_ext = dashou_user.DashouProfile
|
||||
dashou_ext.zonge = (dashou_ext.zonge or Decimal('0.00')) + dashou_fencheng
|
||||
dashou_ext.yue = (dashou_ext.yue or Decimal('0.00')) + dashou_fencheng
|
||||
dashou_ext.jinrishouyi = (dashou_ext.jinrishouyi or Decimal('0.00')) + dashou_fencheng
|
||||
dashou_ext.jinyueshouyi = (dashou_ext.jinyueshouyi or Decimal('0.00')) + dashou_fencheng
|
||||
dashou_ext.chengjiaozongliang = (dashou_ext.chengjiaozongliang or 0) + 1
|
||||
dashou_ext.zhuangtai = 1
|
||||
dashou_ext.save()
|
||||
except UserDashou.DoesNotExist:
|
||||
pass
|
||||
|
||||
# 10. 更新订单状态为已完成
|
||||
order.Status = 3
|
||||
order.save()
|
||||
|
||||
# 11. 打手每日统计(成交)
|
||||
try:
|
||||
update_dashou_daily_by_action(
|
||||
UserID=jiedan_dashou_id,
|
||||
amount=dashou_fencheng,
|
||||
action=2 # 2=结算/成交
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"打手每日统计失败: {e}")
|
||||
|
||||
# 12. 商品/店铺每日统计
|
||||
try:
|
||||
if shangpin_id:
|
||||
# 从扩展表取已计算收益
|
||||
pingtai_shouyi = Decimal('0.00')
|
||||
dianpu_shouyi = Decimal('0.00')
|
||||
if hasattr(order, 'pingtai_kuozhan'):
|
||||
pingtai_shouyi = order.pingtai_kuozhan.PlatformIncome or Decimal('0.00')
|
||||
dianpu_shouyi = order.pingtai_kuozhan.ShopIncome or Decimal('0.00')
|
||||
|
||||
update_shangpin_daily_stat(
|
||||
ProductID=shangpin_id,
|
||||
caozuo_leixing=2,
|
||||
dingdan_jiage=order.Amount,
|
||||
PlatformIncome=pingtai_shouyi,
|
||||
dianpu_zongshouyi=dianpu_shouyi
|
||||
)
|
||||
|
||||
# 店铺统计(含上级分红)
|
||||
if hasattr(order, 'pingtai_kuozhan'):
|
||||
dianpu_id = order.pingtai_kuozhan.ShopID
|
||||
shangji_id = order.pingtai_kuozhan.ParentShopID
|
||||
shangji_fen = order.pingtai_kuozhan.ParentBonus or Decimal('0.00')
|
||||
if dianpu_id:
|
||||
update_dianpu_daily_stat(
|
||||
ShopID=dianpu_id,
|
||||
caozuo_leixing=2,
|
||||
dingdan_jiage=order.Amount,
|
||||
ShopIncome=dianpu_shouyi,
|
||||
shangji_ShopID=shangji_id,
|
||||
ParentBonus=shangji_fen
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"商品/店铺统计失败: {e}")
|
||||
|
||||
# 14. 🆕 写入打手表现表(结算评价)
|
||||
if jiedan_dashou_id:
|
||||
record_dashou_biaoxian(
|
||||
dingdan_id=dingdan_id,
|
||||
dashou_id=jiedan_dashou_id,
|
||||
xingwei=3, # 3=结算
|
||||
fanche_RatingCount=fanche_cishu,
|
||||
shangxian_sudu=shangxian_sudu,
|
||||
fuwu_taidu=fuwu_taidu
|
||||
# bankuai 暂不传入,后续可从订单商品关联
|
||||
)
|
||||
|
||||
# 15. 返回成功
|
||||
return Response({'code': 0, 'msg': '结单成功', 'data': None})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"结单事务异常: {e}", exc_info=True)
|
||||
return Response({'code': 9, 'msg': '服务器内部错误,结单失败', 'data': None})
|
||||
|
||||
|
||||
|
||||
|
||||
541
orders/views/chat.py
Normal file
541
orders/views/chat.py
Normal file
@@ -0,0 +1,541 @@
|
||||
"""orders.views.chat - auto-generated by split script."""
|
||||
# ==================== 标准库 ====================
|
||||
import hmac
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
import string
|
||||
import traceback
|
||||
import threading
|
||||
import requests
|
||||
import hashlib
|
||||
import xml.etree.ElementTree as ET
|
||||
import xmltodict
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models, transaction
|
||||
from gvsdsdk.fluent import db, func, FQ
|
||||
from django.db.models import F, Q, OuterRef, Subquery
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.http import HttpResponse
|
||||
from django.utils import timezone
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import permissions, status
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.parsers import JSONParser
|
||||
from rest_framework.throttling import AnonRateThrottle
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
|
||||
from tencentcloud.common import credential
|
||||
from tencentcloud.common.profile.client_profile import ClientProfile
|
||||
from tencentcloud.common.profile.http_profile import HttpProfile
|
||||
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
||||
from tencentcloud.sts.v20180813 import sts_client, models
|
||||
|
||||
from utils.weixin_broadcast import WeixinBroadcastSender
|
||||
from utils.money import yuan_to_fen
|
||||
from utils.chat_utils import (
|
||||
_send_group_message, _subscribe_users_to_group, establish_order_chat,
|
||||
prepare_order_group_chat, resolve_pair_group_id,
|
||||
parse_pair_from_group_id, resolve_pair_from_order, check_pair_chat_permission,
|
||||
query_pair_orders, order_to_chat_dict,
|
||||
)
|
||||
from utils.fadan_utils import check_fadan_qiangdan_eligible
|
||||
|
||||
from orders.utils import (
|
||||
calc_shangjia_order_fencheng,
|
||||
update_daily_payout,
|
||||
settle_shangjia_order_guanshi_fenhong
|
||||
)
|
||||
from shop.utils import calculate_pingtai_and_dianpu_shouyi, validate_shangpin_and_dianpu, update_dianpu_daily_stat
|
||||
from products.utils import update_shangpin_daily_stat
|
||||
from backend.utils import update_shangjia_daily, update_dashou_daily_by_action, pick_leixing_id, datetime_aliases, fmt_datetime
|
||||
from rank.services import record_dashou_biaoxian
|
||||
from rank.utils import check_dashou_biaoqian_required
|
||||
|
||||
from orders.notice_tasks import dingdan_guangbo
|
||||
|
||||
from ..models import (
|
||||
Order, MerchantOrderExt, PlatformOrderExt, PlayerDeliveryImage,
|
||||
PenaltyRecord, PenaltyEvidenceImage, Penalty, PenaltyAppealImage, PenaltyBonus, PenaltyBonusRate,
|
||||
CommissionRate, PlayerRating, RefundRecord,
|
||||
)
|
||||
from jituan.services.club_config import get_commission_rate, get_commission_rate_object
|
||||
from jituan.services.club_context import resolve_club_id_from_request
|
||||
from jituan.services.club_user import get_payment_openid
|
||||
from jituan.services.club_penalty import resolve_penalty_club_id, resolve_penalty_record_club_id
|
||||
from users.fadan_fenhong_utils import lock_penalty_bonus
|
||||
from users.fadan_fenhong_utils import lock_penalty_bonus
|
||||
from products.models import Shangpin, ShangpinLeixing, Huiyuangoumai
|
||||
from users.models import UserDashou, UserShangjia, UserBoss
|
||||
from users.business_models import User
|
||||
from rank.models import DashouBiaoxian, Chenghao, DingdanBiaoqian, YonghuChenghao
|
||||
from config.models import (
|
||||
ShangjiaLianjie
|
||||
)
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def get_self_goeasy_appkey():
|
||||
try:
|
||||
return getattr(settings, 'GOEASY_APPKEY', None)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def get_self_goeasy_secret():
|
||||
try:
|
||||
return getattr(settings, 'GOEASY_SECRET', None)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def get_self_storage_domain():
|
||||
try:
|
||||
return getattr(settings, 'COS_DOMAIN', '')
|
||||
except Exception:
|
||||
return ''
|
||||
|
||||
|
||||
def full_avatar(relative_url, bucket_domain=None):
|
||||
if not relative_url:
|
||||
return ''
|
||||
if relative_url.startswith('http'):
|
||||
return relative_url
|
||||
domain = bucket_domain or get_self_storage_domain()
|
||||
if domain:
|
||||
domain = domain.rstrip('/') + '/'
|
||||
return domain + relative_url.lstrip('/')
|
||||
return relative_url
|
||||
|
||||
|
||||
# ----------------- GoEasy API 封装 -----------------
|
||||
def subscribe_users_to_group(user_ids, group_ids, appkey=None, secret=None):
|
||||
"""订阅用户到群组,确保能收发该群消息"""
|
||||
if not appkey:
|
||||
appkey = get_self_goeasy_appkey()
|
||||
if not secret:
|
||||
secret = get_self_goeasy_secret()
|
||||
if not appkey:
|
||||
logger.error("订阅失败:GoEasy AppKey 未配置")
|
||||
return False
|
||||
url = 'https://rest-hangzhou.goeasy.io/v2/im/subscribe-groups'
|
||||
body = {"appkey": appkey, "userIds": user_ids, "groupIds": group_ids}
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if secret:
|
||||
headers["Authorization"] = f"Bearer {secret}"
|
||||
try:
|
||||
r = requests.post(url, headers=headers, json=body, timeout=10)
|
||||
if r.status_code == 200:
|
||||
logger.info(f"订阅成功:用户 {user_ids} -> 群 {group_ids}")
|
||||
return True
|
||||
logger.error(f"订阅失败:{r.status_code} {r.text}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.exception("订阅异常")
|
||||
return False
|
||||
|
||||
|
||||
def send_group_message(
|
||||
appkey, secret, group_id, sender_id, sender_name, sender_avatar,
|
||||
message_text='', custom_payload=None, group_name='', order_id=''
|
||||
):
|
||||
"""
|
||||
通过 GoEasy REST API 发送群消息。
|
||||
message_text: 普通文本内容
|
||||
custom_payload: 自定义消息体(如订单卡片),此时 type 设为 'order'
|
||||
"""
|
||||
url = 'https://rest-hangzhou.goeasy.io/v2/im/message'
|
||||
to_data = {
|
||||
"name": group_name or group_id,
|
||||
"avatar": sender_avatar or '',
|
||||
"orderId": order_id,
|
||||
}
|
||||
msg_type = 'order' if custom_payload else 'text'
|
||||
payload = custom_payload if custom_payload else message_text
|
||||
|
||||
body = {
|
||||
"appkey": appkey,
|
||||
"senderId": sender_id,
|
||||
"senderData": {"avatar": sender_avatar, "name": sender_name},
|
||||
"to": {"type": "group", "id": group_id, "data": to_data},
|
||||
"type": msg_type,
|
||||
"payload": payload
|
||||
}
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if secret:
|
||||
headers["Authorization"] = f"Bearer {secret}"
|
||||
|
||||
for attempt in range(3): # 最多重试3次
|
||||
try:
|
||||
r = requests.post(url, headers=headers, json=body, timeout=10)
|
||||
if r.status_code == 200:
|
||||
logger.info(f"消息发送成功:sender={sender_id}, group={group_id}, type={msg_type}")
|
||||
return True
|
||||
logger.warning(f"消息发送失败 (尝试 {attempt+1}/3):状态码 {r.status_code}, 响应 {r.text}")
|
||||
except Exception as e:
|
||||
logger.warning(f"消息发送异常 (尝试 {attempt+1}/3):{e}")
|
||||
if attempt < 2:
|
||||
time.sleep(1)
|
||||
logger.error(f"消息最终发送失败:sender={sender_id}, group={group_id}")
|
||||
return False
|
||||
|
||||
|
||||
# ----------------- 权限校验 -----------------
|
||||
def check_user_permission(identity_type, uid, order):
|
||||
"""验证用户身份、账号状态、以及是否属于该订单"""
|
||||
try:
|
||||
user = User.query.get(UserUID=uid)
|
||||
except User.DoesNotExist:
|
||||
return False, '用户不存在'
|
||||
|
||||
if identity_type == 'PlayerID':
|
||||
try:
|
||||
dashou = UserDashou.query.get(user=user)
|
||||
if dashou.zhanghaozhuangtai != 1 :
|
||||
return False, '打手账号已被封禁或状态异常'
|
||||
except UserDashou.DoesNotExist:
|
||||
return False, '该用户不是打手'
|
||||
if order.PlayerID != uid:
|
||||
return False, '你不是该订单的接单打手'
|
||||
|
||||
elif identity_type == 'shangjia':
|
||||
try:
|
||||
shop = UserShangjia.query.get(user=user)
|
||||
if shop.zhuangtai != 1:
|
||||
return False, '商家账号已被禁用'
|
||||
except UserShangjia.DoesNotExist:
|
||||
return False, '该用户不是商家'
|
||||
try:
|
||||
if order.shangjia_kuozhan.MerchantID != uid:
|
||||
return False, '你不是该订单的发布商家'
|
||||
except MerchantOrderExt.DoesNotExist:
|
||||
return False, '订单缺少商家信息'
|
||||
|
||||
elif identity_type == 'boss':
|
||||
try:
|
||||
UserBoss.query.get(user=user) # 老板暂无封禁字段,仅需存在
|
||||
except UserBoss.DoesNotExist:
|
||||
return False, '该用户不是老板'
|
||||
try:
|
||||
if order.pingtai_kuozhan.BossID != uid:
|
||||
return False, '你不是该订单的老板'
|
||||
except PlatformOrderExt.DoesNotExist:
|
||||
return False, '订单缺少平台信息'
|
||||
|
||||
else:
|
||||
return False, '不支持的身份类型'
|
||||
return True, ''
|
||||
|
||||
|
||||
# ----------------- 主视图 -----------------
|
||||
|
||||
|
||||
class DaiLiQunLiaoXiaoXiView(APIView):
|
||||
"""
|
||||
代理发送群聊消息
|
||||
"""
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
# ===== 1. 参数解析 =====
|
||||
order_id = request.data.get('orderId')
|
||||
identity_type = request.data.get('identityType') # dashou / shangjia / boss
|
||||
sender_name = request.data.get('senderName', '')
|
||||
sender_avatar_raw = request.data.get('senderAvatar', '')
|
||||
message_type = request.data.get('messageType', 'text')
|
||||
message_text = request.data.get('text', '')
|
||||
order_payload = request.data.get('orderPayload', None)
|
||||
|
||||
logger.info(f"[群聊代理] 收到请求: orderId={order_id}, identityType={identity_type}, "
|
||||
f"messageType={message_type}, text={message_text[:20]}...")
|
||||
|
||||
if not order_id or not identity_type:
|
||||
return Response({'code': 400, 'msg': '缺少 orderId 或 identityType'})
|
||||
if identity_type not in ('PlayerID', 'shangjia', 'boss'):
|
||||
return Response({'code': 400, 'msg': 'identityType 无效'})
|
||||
|
||||
# ===== 2. 构造 senderId =====
|
||||
uid = request.user.UserUID
|
||||
prefix_map = {'PlayerID': 'Ds', 'shangjia': 'Sj', 'boss': 'Boss'}
|
||||
sender_id = prefix_map[identity_type] + uid
|
||||
logger.info(f"[群聊代理] 登录用户 UserID={uid}, 构造 senderId={sender_id}")
|
||||
|
||||
# ===== 3. 查询订单 =====
|
||||
try:
|
||||
order = Order.query.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(OrderID=order_id)
|
||||
except Order.DoesNotExist:
|
||||
logger.warning(f"[群聊代理] 订单 {order_id} 不存在")
|
||||
return Response({'code': 404, 'msg': '订单不存在'})
|
||||
|
||||
# ===== 4. 权限校验 =====
|
||||
valid, err_msg = check_user_permission(identity_type, uid, order)
|
||||
if not valid:
|
||||
logger.warning(f"[群聊代理] 权限校验失败: {err_msg}, uid={uid}, identity={identity_type}")
|
||||
return Response({'code': 403, 'msg': err_msg})
|
||||
|
||||
# ===== 5. 我方 GoEasy 配置 =====
|
||||
self_appkey = get_self_goeasy_appkey()
|
||||
self_secret = get_self_goeasy_secret()
|
||||
if not self_appkey:
|
||||
logger.error("[群聊代理] 我方 GoEasy AppKey 未配置")
|
||||
return Response({'code': 500, 'msg': '系统配置错误(APPKEY)'})
|
||||
|
||||
# ===== 6. 公共群信息(新单走配对群,与文赫一致) =====
|
||||
real_group_id = resolve_pair_group_id(order) or f"group_{order.OrderID}"
|
||||
group_name = (order.Description[:20] + '…') if order.Description and len(order.Description) > 20 else f"订单{order.OrderID}"
|
||||
sender_avatar = full_avatar(sender_avatar_raw) if sender_avatar_raw else full_avatar('')
|
||||
|
||||
# ===== 7. 订阅发送者到群 =====
|
||||
if not subscribe_users_to_group([sender_id], [real_group_id], self_appkey, self_secret):
|
||||
logger.error("[群聊代理] 订阅群组失败")
|
||||
return Response({'code': 500, 'msg': '订阅群组失败'})
|
||||
|
||||
custom_payload = order_payload if message_type == 'order' else None
|
||||
text_content = message_text
|
||||
|
||||
# ---------- 向我方群发送消息 ----------
|
||||
local_ok = send_group_message(
|
||||
appkey=self_appkey,
|
||||
secret=self_secret,
|
||||
group_id=real_group_id,
|
||||
sender_id=sender_id,
|
||||
sender_name=sender_name,
|
||||
sender_avatar=sender_avatar,
|
||||
message_text=text_content,
|
||||
custom_payload=custom_payload,
|
||||
group_name=group_name,
|
||||
order_id=order.OrderID,
|
||||
)
|
||||
if not local_ok:
|
||||
return Response({'code': 500, 'msg': '消息发送失败'})
|
||||
|
||||
return Response({'code': 200, 'msg': '发送成功'})
|
||||
|
||||
|
||||
|
||||
class ShangjiaLianjieLiuYanZhuanFaView(APIView):
|
||||
"""
|
||||
商家链接留言转发接口
|
||||
路径:POST /dingdan/sjljlbxxzf
|
||||
固定以 B{商家ID} 身份发送群消息。
|
||||
"""
|
||||
permission_classes = []
|
||||
|
||||
def post(self, request):
|
||||
token = request.data.get('token', '').strip()
|
||||
dingdan_id = str(request.data.get('dingdan_id') or request.data.get('dingdanId') or '').strip()
|
||||
text = (request.data.get('text') or '').strip()
|
||||
|
||||
if not text or (not token and not dingdan_id):
|
||||
return Response({'code': 400, 'msg': '缺少参数'})
|
||||
|
||||
if token:
|
||||
try:
|
||||
lianjie = ShangjiaLianjie.query.get(LinkToken=token)
|
||||
except ShangjiaLianjie.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '链接不存在或已失效'})
|
||||
else:
|
||||
try:
|
||||
lianjie = ShangjiaLianjie.query.get(OrderID=dingdan_id)
|
||||
except ShangjiaLianjie.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '订单链接不存在或已失效'})
|
||||
|
||||
if not lianjie.OrderID:
|
||||
return Response({'code': 400, 'msg': '链接缺少订单ID'})
|
||||
|
||||
try:
|
||||
order = Order.query.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(OrderID=lianjie.OrderID)
|
||||
except Order.DoesNotExist:
|
||||
return Response({'code': 500, 'msg': '订单数据异常'})
|
||||
|
||||
shangjia_uid = lianjie.UserID
|
||||
sender_id = f"B{shangjia_uid}"
|
||||
sender_name = "老板"
|
||||
sender_avatar = ''
|
||||
|
||||
appkey = get_self_goeasy_appkey()
|
||||
secret = get_self_goeasy_secret()
|
||||
if not appkey:
|
||||
return Response({'code': 500, 'msg': '系统配置错误(APPKEY)'})
|
||||
|
||||
group_id = resolve_pair_group_id(order) or f"group_{order.OrderID}"
|
||||
group_name = (order.Description[:20] + '…') if order.Description and len(order.Description) > 20 else f"订单{order.OrderID}"
|
||||
|
||||
if not _subscribe_users_to_group([sender_id], [group_id], appkey, secret):
|
||||
return Response({'code': 500, 'msg': '订阅群组失败'})
|
||||
|
||||
local_ok = _send_group_message(
|
||||
appkey=appkey,
|
||||
secret=secret,
|
||||
group_id=group_id,
|
||||
sender_id=sender_id,
|
||||
sender_name=sender_name,
|
||||
sender_avatar=sender_avatar,
|
||||
message_text=text,
|
||||
custom_payload=None,
|
||||
group_name=group_name,
|
||||
order_id=order.OrderID,
|
||||
)
|
||||
if not local_ok:
|
||||
return Response({'code': 500, 'msg': '消息发送失败'})
|
||||
|
||||
return Response({'code': 200, 'msg': '发送成功'})
|
||||
|
||||
|
||||
class LianTongDuiHuaZhunBeiView(APIView):
|
||||
"""进入订单群聊前准备:返回配对 groupId 及双方资料,并订阅 GoEasy 群(不影响抢单校验)"""
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
IDENTITY_MAP = {
|
||||
'dashou': 'PlayerID',
|
||||
'shangjia': 'shangjia',
|
||||
'normal': 'boss',
|
||||
'boss': 'boss',
|
||||
}
|
||||
|
||||
def post(self, request):
|
||||
dingdan_id = request.data.get('dingdan_id') or request.data.get('orderId')
|
||||
identity_type = request.data.get('identityType') or ''
|
||||
if not dingdan_id:
|
||||
return Response({'code': 400, 'msg': '缺少 dingdan_id'})
|
||||
|
||||
backend_identity = self.IDENTITY_MAP.get(identity_type, identity_type)
|
||||
if backend_identity not in ('PlayerID', 'shangjia', 'boss'):
|
||||
return Response({'code': 400, 'msg': 'identityType 无效'})
|
||||
|
||||
uid = request.user.UserUID
|
||||
try:
|
||||
order = Order.query.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(OrderID=dingdan_id)
|
||||
except Order.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '订单不存在'})
|
||||
|
||||
valid, err_msg = check_user_permission(backend_identity, uid, order)
|
||||
if not valid:
|
||||
return Response({'code': 403, 'msg': err_msg})
|
||||
|
||||
if backend_identity == 'PlayerID' and not order.PlayerID:
|
||||
return Response({'code': 400, 'msg': '订单尚未接单,暂无法进入群聊'})
|
||||
|
||||
data, subscribed = prepare_order_group_chat(order, backend_identity, uid, subscribe=True)
|
||||
if not subscribed:
|
||||
logger.warning(f'ltdhzb 订阅群失败 order={dingdan_id} uid={uid}')
|
||||
|
||||
return Response({'code': 0, 'msg': 'ok', 'data': data})
|
||||
|
||||
|
||||
class LianTongPeiDuiDingDanLieBiaoView(APIView):
|
||||
"""聊天页:获取打手与商家/老板之间的历史订单列表(可搜索)"""
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
IDENTITY_MAP = {
|
||||
'dashou': 'PlayerID',
|
||||
'shangjia': 'shangjia',
|
||||
'normal': 'boss',
|
||||
'boss': 'boss',
|
||||
}
|
||||
|
||||
def post(self, request):
|
||||
group_id = request.data.get('groupId') or request.data.get('group_id')
|
||||
dingdan_id = request.data.get('dingdan_id') or request.data.get('orderId')
|
||||
keyword = (request.data.get('keyword') or '').strip()
|
||||
identity_type = request.data.get('identityType') or ''
|
||||
backend_identity = self.IDENTITY_MAP.get(identity_type, identity_type)
|
||||
if backend_identity not in ('PlayerID', 'shangjia', 'boss'):
|
||||
return Response({'code': 400, 'msg': 'identityType 无效'})
|
||||
|
||||
dashou_uid, partner_uid, partner_role = parse_pair_from_group_id(group_id)
|
||||
if not dashou_uid and dingdan_id:
|
||||
try:
|
||||
order = Order.query.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(OrderID=dingdan_id)
|
||||
except Order.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '订单不存在'})
|
||||
dashou_uid, partner_uid, partner_role = resolve_pair_from_order(order)
|
||||
|
||||
if not dashou_uid or not partner_uid:
|
||||
return Response({'code': 400, 'msg': '无法解析配对信息,请确认订单已接单'})
|
||||
|
||||
uid = request.user.UserUID
|
||||
valid, err_msg = check_pair_chat_permission(backend_identity, uid, dashou_uid, partner_uid, partner_role)
|
||||
if not valid:
|
||||
return Response({'code': 403, 'msg': err_msg})
|
||||
|
||||
orders = query_pair_orders(dashou_uid, partner_uid, partner_role, keyword=keyword, limit=40)
|
||||
latest = orders[0] if orders else None
|
||||
return Response({'code': 0, 'msg': 'ok', 'data': {'list': orders, 'latest': latest}})
|
||||
|
||||
|
||||
class LianTongDingDanZhuangTaiView(APIView):
|
||||
"""聊天页:刷新单个订单状态"""
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
IDENTITY_MAP = LianTongPeiDuiDingDanLieBiaoView.IDENTITY_MAP
|
||||
|
||||
def post(self, request):
|
||||
dingdan_id = request.data.get('dingdan_id') or request.data.get('orderId')
|
||||
identity_type = request.data.get('identityType') or ''
|
||||
if not dingdan_id:
|
||||
return Response({'code': 400, 'msg': '缺少 dingdan_id'})
|
||||
|
||||
backend_identity = self.IDENTITY_MAP.get(identity_type, identity_type)
|
||||
if backend_identity not in ('PlayerID', 'shangjia', 'boss'):
|
||||
return Response({'code': 400, 'msg': 'identityType 无效'})
|
||||
|
||||
try:
|
||||
order = Order.query.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(OrderID=dingdan_id)
|
||||
except Order.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '订单不存在'})
|
||||
|
||||
valid, err_msg = check_user_permission(backend_identity, request.user.UserUID, order)
|
||||
if not valid:
|
||||
return Response({'code': 403, 'msg': err_msg})
|
||||
|
||||
data = order_to_chat_dict(order)
|
||||
return Response({'code': 0, 'msg': 'ok', 'data': data})
|
||||
|
||||
|
||||
class LianTongDingDanZhuangTaiPiLiangView(APIView):
|
||||
"""消息列表:批量刷新订单状态"""
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
order_ids = request.data.get('order_ids') or []
|
||||
if not isinstance(order_ids, list):
|
||||
return Response({'code': 400, 'msg': 'order_ids 格式错误'})
|
||||
|
||||
uid = request.user.UserUID
|
||||
result = {}
|
||||
for oid in order_ids[:20]:
|
||||
oid = str(oid).strip()
|
||||
if not oid:
|
||||
continue
|
||||
try:
|
||||
order = Order.query.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(OrderID=oid)
|
||||
except Order.DoesNotExist:
|
||||
continue
|
||||
for identity in ('PlayerID', 'shangjia', 'boss'):
|
||||
valid, _ = check_user_permission(identity, uid, order)
|
||||
if valid:
|
||||
d = order_to_chat_dict(order)
|
||||
result[oid] = {
|
||||
'jieshao': d['jieshao'],
|
||||
'jine': d['jine'],
|
||||
'zhuangtai': d['zhuangtai'],
|
||||
'zhuangtaiText': d['zhuangtaiText'],
|
||||
}
|
||||
break
|
||||
return Response({'code': 0, 'msg': 'ok', 'data': result})
|
||||
|
||||
|
||||
1513
orders/views/dashou.py
Normal file
1513
orders/views/dashou.py
Normal file
File diff suppressed because it is too large
Load Diff
1408
orders/views/merchant.py
Normal file
1408
orders/views/merchant.py
Normal file
File diff suppressed because it is too large
Load Diff
378
orders/views/merchant_penalty.py
Normal file
378
orders/views/merchant_penalty.py
Normal file
@@ -0,0 +1,378 @@
|
||||
"""orders.views.merchant_penalty - auto-generated by split script."""
|
||||
# ==================== 标准库 ====================
|
||||
import hmac
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
import string
|
||||
import traceback
|
||||
import threading
|
||||
import requests
|
||||
import hashlib
|
||||
import xml.etree.ElementTree as ET
|
||||
import xmltodict
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models, transaction
|
||||
from gvsdsdk.fluent import db, func, FQ
|
||||
from django.db.models import F, Q, OuterRef, Subquery
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.http import HttpResponse
|
||||
from django.utils import timezone
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import permissions, status
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.parsers import JSONParser
|
||||
from rest_framework.throttling import AnonRateThrottle
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
|
||||
from tencentcloud.common import credential
|
||||
from tencentcloud.common.profile.client_profile import ClientProfile
|
||||
from tencentcloud.common.profile.http_profile import HttpProfile
|
||||
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
||||
from tencentcloud.sts.v20180813 import sts_client, models
|
||||
|
||||
from utils.weixin_broadcast import WeixinBroadcastSender
|
||||
from utils.money import yuan_to_fen
|
||||
from utils.chat_utils import (
|
||||
_send_group_message, _subscribe_users_to_group, establish_order_chat,
|
||||
prepare_order_group_chat, resolve_pair_group_id,
|
||||
parse_pair_from_group_id, resolve_pair_from_order, check_pair_chat_permission,
|
||||
query_pair_orders, order_to_chat_dict,
|
||||
)
|
||||
from utils.fadan_utils import check_fadan_qiangdan_eligible
|
||||
|
||||
from orders.utils import (
|
||||
calc_shangjia_order_fencheng,
|
||||
update_daily_payout,
|
||||
settle_shangjia_order_guanshi_fenhong
|
||||
)
|
||||
from shop.utils import calculate_pingtai_and_dianpu_shouyi, validate_shangpin_and_dianpu, update_dianpu_daily_stat
|
||||
from products.utils import update_shangpin_daily_stat
|
||||
from backend.utils import update_shangjia_daily, update_dashou_daily_by_action, pick_leixing_id, datetime_aliases, fmt_datetime
|
||||
from rank.services import record_dashou_biaoxian
|
||||
from rank.utils import check_dashou_biaoqian_required
|
||||
|
||||
from orders.notice_tasks import dingdan_guangbo
|
||||
|
||||
from ..models import (
|
||||
Order, MerchantOrderExt, PlatformOrderExt, PlayerDeliveryImage,
|
||||
PenaltyRecord, PenaltyEvidenceImage, Penalty, PenaltyAppealImage, PenaltyBonus, PenaltyBonusRate,
|
||||
CommissionRate, PlayerRating, RefundRecord,
|
||||
)
|
||||
from jituan.services.club_config import get_commission_rate, get_commission_rate_object
|
||||
from jituan.services.club_context import resolve_club_id_from_request
|
||||
from jituan.services.club_user import get_payment_openid
|
||||
from jituan.services.club_penalty import resolve_penalty_club_id, resolve_penalty_record_club_id
|
||||
from users.fadan_fenhong_utils import lock_penalty_bonus
|
||||
from users.fadan_fenhong_utils import lock_penalty_bonus
|
||||
from products.models import Shangpin, ShangpinLeixing, Huiyuangoumai
|
||||
from users.models import UserDashou, UserShangjia, UserBoss
|
||||
from users.business_models import User
|
||||
from rank.models import DashouBiaoxian, Chenghao, DingdanBiaoqian, YonghuChenghao
|
||||
from config.models import (
|
||||
ShangjiaLianjie
|
||||
)
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ===================== 视图:商家罚款申请 =====================
|
||||
# 文件路径:dingdan/views.py 或单独文件,根据项目结构放置
|
||||
|
||||
|
||||
class ShangjiaFakuanApplyView(APIView):
|
||||
"""
|
||||
商家罚款申请接口
|
||||
路径:POST /dingdan/sjfksq
|
||||
权限:JWT认证的商家用户
|
||||
参数:
|
||||
dingdan_id - 订单ID (必填)
|
||||
chufa_liyou - 罚款原因 (必填)
|
||||
fakuanjine - 罚款金额,元 (必填)
|
||||
yingxiang_qiangdan - 是否影响抢单 1是 0否 (必填)
|
||||
说明:
|
||||
1. 自动从订单中获取被打手ID,防止商家篡改目标。
|
||||
2. 同一订单同一打手不允许重复罚款。
|
||||
3. 跨平台订单若为我方派单,则通知对方平台处罚;否则本地生成罚单。
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
parser_classes = [JSONParser]
|
||||
|
||||
def post(self, request):
|
||||
# ========== 1. 身份验证 ==========
|
||||
current_user = request.user
|
||||
try:
|
||||
shangjia_id = current_user.UserUID # 商家的用户ID
|
||||
except AttributeError:
|
||||
return Response({'code': 1, 'msg': '用户认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
# ========== 2. 参数提取与校验 ==========
|
||||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||||
chufa_liyou = request.data.get('chufa_liyou', '').strip()
|
||||
fakuanjine = request.data.get('fakuanjine', 0)
|
||||
yingxiang_qiangdan = int(request.data.get('yingxiang_qiangdan', 1))
|
||||
evidence_urls = request.data.get('evidence_urls') or []
|
||||
|
||||
if not dingdan_id or not chufa_liyou or not fakuanjine:
|
||||
return Response({'code': 2, 'msg': '参数不完整'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if not isinstance(evidence_urls, list):
|
||||
return Response({'code': 2, 'msg': '证据图片格式错误'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
evidence_urls = [str(u).strip() for u in evidence_urls if str(u).strip()]
|
||||
if len(evidence_urls) > 9:
|
||||
return Response({'code': 2, 'msg': '证据图片最多9张'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if len(evidence_urls) < 1:
|
||||
return Response({'code': 2, 'msg': '请至少上传1张证据图片'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
fakuanjine = float(fakuanjine)
|
||||
if fakuanjine <= 0:
|
||||
raise ValueError
|
||||
except (ValueError, TypeError):
|
||||
return Response({'code': 2, 'msg': '罚款金额格式错误'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# ========== 3. 获取订单,校验归属商家 ==========
|
||||
try:
|
||||
# 关联查询商家扩展表,确保订单属于当前商家
|
||||
shangjia_kuozhan = MerchantOrderExt.query.select_related('Order').get(
|
||||
Order__OrderID=dingdan_id,
|
||||
MerchantID=shangjia_id
|
||||
)
|
||||
order = shangjia_kuozhan.Order
|
||||
except MerchantOrderExt.DoesNotExist:
|
||||
return Response({'code': 3, 'msg': '订单不存在或您无权操作'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# ========== 4. 获取被打手ID ==========
|
||||
dashou_id = order.PlayerID
|
||||
if not dashou_id:
|
||||
return Response({'code': 4, 'msg': '该订单暂无接单打手,无法罚款'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# ========== 5. 重复罚款检查 ==========
|
||||
if Penalty.query.filter(PenalizedUserID=dashou_id, RelatedOrderID=dingdan_id).exists():
|
||||
return Response({'code': 5, 'msg': '该打手在此订单已被罚款,不能重复申请'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# ========== 6. 本地创建罚单 ==========
|
||||
staff_kefu_id = None
|
||||
staff_member = getattr(request, '_staff_member', None)
|
||||
if staff_member is not None:
|
||||
staff_kefu_id = getattr(staff_member, 'staff_user_id', None) or None
|
||||
|
||||
from utils.penalty_status import PENALTY_PLATFORM_AUDIT
|
||||
|
||||
with transaction.atomic():
|
||||
fadan = Penalty.query.create(
|
||||
PenalizedUserID=dashou_id,
|
||||
ApplicantID=shangjia_id, # 申请人为当前商家
|
||||
Identity=1, # 被打者身份:1=打手
|
||||
Reason=chufa_liyou,
|
||||
FineAmount=fakuanjine,
|
||||
RelatedOrderID=dingdan_id,
|
||||
Status=PENALTY_PLATFORM_AUDIT, # 平台审核中,通过后变为待缴纳
|
||||
AffectsGrabbing=yingxiang_qiangdan,
|
||||
ApplicantIdentity=5, # 申请人是商家
|
||||
ClubID=resolve_penalty_club_id(
|
||||
order=order,
|
||||
penalized_user_id=dashou_id,
|
||||
request=request,
|
||||
applicant_id=shangjia_id,
|
||||
user=current_user,
|
||||
),
|
||||
ShopStaffUserID=staff_kefu_id,
|
||||
)
|
||||
for url in evidence_urls:
|
||||
PenaltyAppealImage.query.create(
|
||||
Penalty=fadan,
|
||||
PenalizedUserID=dashou_id,
|
||||
ImageURL=url,
|
||||
Purpose=1,
|
||||
)
|
||||
lock_penalty_bonus(fadan)
|
||||
logger.info(f"商家{shangjia_id}对订单{dingdan_id}打手{dashou_id}提交罚款申请(平台审核),金额{fakuanjine}")
|
||||
return Response({'code': 0, 'msg': '罚款申请已提交,等待平台审核', 'data': {'fadan_id': fadan.id}})
|
||||
|
||||
|
||||
class ShangjiaFakuaiXiugaiView(APIView):
|
||||
"""
|
||||
商家端统一修改接口:罚款修改/撤销/再次申请,订单退款撤销/修改理由
|
||||
URL: /dingdan/sjfkxgycx
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
user = request.user
|
||||
# 验证商家身份
|
||||
try:
|
||||
shangjia = user.ShopProfile
|
||||
except UserShangjia.DoesNotExist:
|
||||
return Response({'code': 403, 'msg': '您不是商家'}, status=status.HTTP_403_FORBIDDEN)
|
||||
if shangjia.zhuangtai != 1:
|
||||
return Response({'code': 403, 'msg': '商家状态异常'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
operation = request.data.get('operation')
|
||||
dingdan_id = request.data.get('dingdan_id') or request.data.get('OrderID')
|
||||
if not dingdan_id:
|
||||
return Response({'code': 400, 'msg': '缺少订单ID'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 验证订单属于该商家
|
||||
try:
|
||||
dingdan = Order.query.select_related('shangjia_kuozhan').get(OrderID=dingdan_id)
|
||||
except Order.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||
if not hasattr(dingdan, 'shangjia_kuozhan') or dingdan.shangjia_kuozhan.MerchantID != user.UserUID:
|
||||
return Response({'code': 403, 'msg': '无权操作此订单'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# 处理订单相关操作
|
||||
if operation == 'cancel_refund':
|
||||
return self._cancel_refund(dingdan, request.data.get('liyou', ''))
|
||||
elif operation == 'modify_refund_reason':
|
||||
return self._modify_refund_reason(dingdan, request.data.get('liyou', ''))
|
||||
|
||||
# 处理罚款相关操作
|
||||
fadan_id = request.data.get('fadan_id')
|
||||
if not fadan_id:
|
||||
return Response({'code': 400, 'msg': '缺少罚单ID'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
fadan = Penalty.query.get(id=fadan_id)
|
||||
except Penalty.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '罚单不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||
if fadan.RelatedOrderID != dingdan_id:
|
||||
return Response({'code': 403, 'msg': '罚单与订单不匹配'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
if operation == 'modify_penalty':
|
||||
return self._modify_penalty(fadan, request.data.get('liyou', ''), request.data.get('jine'), user.UserUID)
|
||||
elif operation == 'cancel_penalty':
|
||||
return self._cancel_penalty(fadan, request.data.get('liyou', ''), user.UserUID)
|
||||
elif operation == 'resubmit_penalty':
|
||||
return self._resubmit_penalty(fadan, request.data.get('liyou', ''), request.data.get('jine'), user.UserUID)
|
||||
elif operation == 'manage_penalty_evidence':
|
||||
return self._manage_penalty_evidence(fadan, request.data)
|
||||
else:
|
||||
return Response({'code': 400, 'msg': '无效的操作类型'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def _cancel_refund(self, dingdan, liyou):
|
||||
"""撤销退款申请(状态4 → 状态8,清空退款理由)"""
|
||||
if dingdan.Status != 4:
|
||||
return Response({'code': 400, 'msg': '只有退款中的订单可以撤销退款申请'})
|
||||
with transaction.atomic():
|
||||
dingdan.Status = 8
|
||||
dingdan.RefundReason = '' # 清空退款理由
|
||||
dingdan.save()
|
||||
# 记录撤销理由可存入日志(如需要可另建日志表,此处简单处理)
|
||||
logger.info(f"商家撤销退款订单 {dingdan.OrderID},理由: {liyou}")
|
||||
return Response({'code': 0, 'msg': '已撤销退款申请'})
|
||||
|
||||
def _modify_refund_reason(self, dingdan, liyou):
|
||||
"""修改退款理由(仅状态4时)"""
|
||||
if dingdan.Status != 4:
|
||||
return Response({'code': 400, 'msg': '只有退款中的订单可以修改退款理由'})
|
||||
if not liyou.strip():
|
||||
return Response({'code': 400, 'msg': '退款理由不能为空'})
|
||||
dingdan.RefundReason = liyou[:500]
|
||||
dingdan.save()
|
||||
return Response({'code': 0, 'msg': '退款理由修改成功'})
|
||||
|
||||
def _modify_penalty(self, fadan, liyou, jine_str, processor_id=None):
|
||||
"""修改罚款(待缴纳 / 申诉中 / 平台审核中)"""
|
||||
from utils.penalty_status import PENALTY_PENDING_PAY, PENALTY_APPEALING, PENALTY_PLATFORM_AUDIT
|
||||
if fadan.Status not in (PENALTY_PENDING_PAY, PENALTY_APPEALING, PENALTY_PLATFORM_AUDIT):
|
||||
return Response({'code': 400, 'msg': '当前罚单状态不可修改'})
|
||||
if not liyou.strip():
|
||||
return Response({'code': 400, 'msg': '罚款理由不能为空'})
|
||||
try:
|
||||
jine = Decimal(jine_str)
|
||||
if jine <= 0:
|
||||
raise ValueError
|
||||
except:
|
||||
return Response({'code': 400, 'msg': '罚款金额必须为正数'})
|
||||
with transaction.atomic():
|
||||
fadan.Reason = liyou[:500]
|
||||
fadan.FineAmount = jine
|
||||
if processor_id:
|
||||
fadan.ProcessorID = processor_id
|
||||
fadan.ProcessorIdentity = 5 # 小程序商家自行处理
|
||||
fadan.save()
|
||||
lock_penalty_bonus(fadan)
|
||||
return Response({'code': 0, 'msg': '罚款修改成功'})
|
||||
|
||||
def _cancel_penalty(self, fadan, liyou, processor_id=None):
|
||||
"""撤销罚款(待缴纳 / 申诉中 / 平台审核中 → 已驳回)"""
|
||||
from utils.penalty_status import PENALTY_PENDING_PAY, PENALTY_APPEALING, PENALTY_PLATFORM_AUDIT, PENALTY_REJECTED
|
||||
if fadan.Status not in (PENALTY_PENDING_PAY, PENALTY_APPEALING, PENALTY_PLATFORM_AUDIT):
|
||||
return Response({'code': 400, 'msg': '当前罚单状态不可撤销'})
|
||||
if not liyou.strip():
|
||||
return Response({'code': 400, 'msg': '驳回理由不能为空'})
|
||||
with transaction.atomic():
|
||||
fadan.Status = PENALTY_REJECTED # 已驳回
|
||||
fadan.RejectReason = liyou[:500]
|
||||
if processor_id:
|
||||
fadan.ProcessorID = processor_id
|
||||
fadan.ProcessorIdentity = 5 # 小程序商家撤销=商家作为处理人
|
||||
fadan.save()
|
||||
return Response({'code': 0, 'msg': '罚款已撤销(驳回)'})
|
||||
|
||||
def _resubmit_penalty(self, fadan, liyou, jine_str, processor_id=None):
|
||||
"""再次申请罚款(已驳回 → 平台审核中)"""
|
||||
from utils.penalty_status import PENALTY_REJECTED, PENALTY_PLATFORM_AUDIT
|
||||
if fadan.Status != PENALTY_REJECTED:
|
||||
return Response({'code': 400, 'msg': '只有已驳回的罚单可再次申请'})
|
||||
if not liyou.strip():
|
||||
return Response({'code': 400, 'msg': '罚款理由不能为空'})
|
||||
try:
|
||||
jine = Decimal(jine_str)
|
||||
if jine <= 0:
|
||||
raise ValueError
|
||||
except:
|
||||
return Response({'code': 400, 'msg': '罚款金额必须为正数'})
|
||||
with transaction.atomic():
|
||||
fadan.Status = PENALTY_PLATFORM_AUDIT
|
||||
fadan.Reason = liyou[:500]
|
||||
fadan.FineAmount = jine
|
||||
fadan.RejectReason = '' # 清空驳回理由
|
||||
if processor_id:
|
||||
fadan.ProcessorID = processor_id
|
||||
fadan.ProcessorIdentity = 5
|
||||
fadan.save()
|
||||
lock_penalty_bonus(fadan)
|
||||
logger.info(f"商家重新申请罚单{fadan.id},金额={jine}")
|
||||
return Response({'code': 0, 'msg': '已重新申请罚款'})
|
||||
|
||||
def _manage_penalty_evidence(self, fadan, data):
|
||||
"""管理罚单证据图片(Purpose=1),与申诉图片严格隔离"""
|
||||
from utils.penalty_status import PENALTY_PENDING_PAY, PENALTY_APPEALING, PENALTY_PLATFORM_AUDIT
|
||||
if fadan.Status not in (PENALTY_PENDING_PAY, PENALTY_APPEALING, PENALTY_PLATFORM_AUDIT):
|
||||
return Response({'code': 400, 'msg': '当前罚单状态不可修改证据图片'})
|
||||
add_urls = data.get('add_urls') or []
|
||||
remove_ids = data.get('remove_ids') or []
|
||||
if not add_urls and not remove_ids:
|
||||
return Response({'code': 400, 'msg': '未提供图片变更'})
|
||||
with transaction.atomic():
|
||||
if remove_ids:
|
||||
PenaltyAppealImage.query.filter(
|
||||
Penalty=fadan, Purpose=1, id__in=remove_ids
|
||||
).delete()
|
||||
existing_count = PenaltyAppealImage.query.filter(Penalty=fadan, Purpose=1).count()
|
||||
if existing_count + len(add_urls) > 9:
|
||||
return Response({'code': 400, 'msg': '证据图片最多9张'})
|
||||
for url in add_urls:
|
||||
url = str(url).strip()
|
||||
if not url:
|
||||
continue
|
||||
PenaltyAppealImage.query.create(
|
||||
Penalty=fadan,
|
||||
PenalizedUserID=fadan.PenalizedUserID,
|
||||
ImageURL=url,
|
||||
Purpose=1,
|
||||
)
|
||||
logger.info(
|
||||
f"罚单{fadan.id}证据图变更:新增{len(add_urls)}张,删除{len(remove_ids)}张"
|
||||
)
|
||||
return Response({'code': 0, 'msg': '证据图片已更新'})
|
||||
1009
orders/views/payment.py
Normal file
1009
orders/views/payment.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user