商家罚款增加平台审核:新增状态5、证据图、审核API;审核通过前不影响抢单,提现仍拦截未结案罚单

This commit is contained in:
XingQue
2026-07-02 01:53:25 +08:00
parent 9fe03727c5
commit c21168ad2d
8 changed files with 194 additions and 26 deletions

View File

@@ -444,7 +444,7 @@ class OrderPlayerHistory(QModel):
class Penalty(QModel):
"""罚单表——平台各类处罚信息
状态码1=待缴纳, 2=已缴纳, 3=申诉中, 4=已驳回
状态码1=待缴纳, 2=已缴纳, 3=申诉中, 4=已驳回, 5=平台审核中
被处罚者身份shenfen1=打手, 2=管事, 3=商家, 4=组长
"""
@@ -475,7 +475,7 @@ class Penalty(QModel):
db_column='guanliandingdan_id', verbose_name='关联订单ID',
)
Status = models.IntegerField(
choices=[(1, '待缴纳'), (2, '已缴纳'), (3, '申诉中'), (4, '已驳回')],
choices=[(1, '待缴纳'), (2, '已缴纳'), (3, '申诉中'), (4, '已驳回'), (5, '平台审核中')],
default=1, db_index=True,
db_column='zhuangtai', verbose_name='罚单状态',
)

View File

@@ -2294,6 +2294,20 @@ class ShangjiaDingdanXiangqingView(APIView):
if fadan_obj:
ct = fmt_datetime(fadan_obj.CreateTime)
ut = fmt_datetime(fadan_obj.UpdateTime)
zhengju_urls = list(
PenaltyAppealImage.query.filter(Penalty=fadan_obj, Purpose=1).values_list('ImageURL', flat=True)
)
shensu_urls = list(
PenaltyAppealImage.query.filter(Penalty=fadan_obj, Purpose=2).values_list('ImageURL', flat=True)
)
zhengju_imgs = [
{'id': t.id, 'url': t.ImageURL}
for t in PenaltyAppealImage.query.filter(Penalty=fadan_obj, Purpose=1)
]
shensu_imgs = [
{'id': t.id, 'url': t.ImageURL}
for t in PenaltyAppealImage.query.filter(Penalty=fadan_obj, Purpose=2)
]
fadan_dict = {
'id': fadan_obj.id,
'chufaliyou': fadan_obj.Reason or '',
@@ -2306,6 +2320,8 @@ class ShangjiaDingdanXiangqingView(APIView):
'yingde_fenhong': float(fadan_obj.ApplicantBonusAmount or 0),
'fenhong_lilv': float(fadan_obj.ApplicantBonusRate or 0),
'fenhong_yiruzhang': bool(fadan_obj.BonusCredited),
'zhengju_tupian': zhengju_imgs,
'shensu_tupian': shensu_imgs,
'CreateTime': ct,
'create_time': ct,
'UpdateTime': ut,
@@ -4147,6 +4163,20 @@ class DashouCOSZhengshuView(APIView):
).first()
if not fadan:
return Response({'code': 403, 'msg': '罚单不存在或无权操作'}, status=403)
elif yongtu == 'fakuan_evidence_draft':
# 商家申请罚款前上传证据图(仅需订单归属校验)
if not dingdan_id:
return Response({'code': 400, 'msg': '缺少订单ID'}, status=400)
try:
shangjia_profile = request.user.ShopProfile
if shangjia_profile.zhuangtai != 1:
return Response({'code': 403, 'msg': '商家状态异常'}, status=403)
except Exception:
return Response({'code': 403, 'msg': '非商家无权上传'}, status=403)
try:
MerchantOrderExt.query.get(Order__OrderID=dingdan_id, MerchantID=request.user.UserUID)
except MerchantOrderExt.DoesNotExist:
return Response({'code': 403, 'msg': '订单不存在或无权操作'}, status=403)
else:
# 默认为订单相关,需要验证订单且打手必须接单
try:
@@ -4190,6 +4220,10 @@ class DashouCOSZhengshuView(APIView):
resources.append(
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/fakuan/shangjiafakuan/zhengju/*"
)
elif yongtu == 'fakuan_evidence_draft':
resources.append(
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/fakuan/shangjiafakuan/zhengju/*"
)
else:
resources.append(
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/order/{dingdan_id}/*"
@@ -6801,10 +6835,19 @@ class ShangjiaFakuanApplyView(APIView):
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:
@@ -6838,6 +6881,8 @@ class ShangjiaFakuanApplyView(APIView):
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,
@@ -6846,7 +6891,7 @@ class ShangjiaFakuanApplyView(APIView):
Reason=chufa_liyou,
FineAmount=fakuanjine,
RelatedOrderID=dingdan_id,
Status=1, # 待缴纳
Status=PENALTY_PLATFORM_AUDIT, # 平台审核中,通过后变为待缴纳
AffectsGrabbing=yingxiang_qiangdan,
ApplicantIdentity=5, # 申请人是商家
ClubID=resolve_penalty_club_id(
@@ -6858,9 +6903,16 @@ class ShangjiaFakuanApplyView(APIView):
),
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': '罚款申请已提交'})
logger.info(f"商家{shangjia_id}对订单{dingdan_id}打手{dashou_id}提交罚款申请(平台审核),金额{fakuanjine}")
return Response({'code': 0, 'msg': '罚款申请已提交,等待平台审核', 'data': {'fadan_id': fadan.id}})
class ShangjiaFakuaiXiugaiView(APIView):
@@ -6944,8 +6996,9 @@ class ShangjiaFakuaiXiugaiView(APIView):
return Response({'code': 0, 'msg': '退款理由修改成功'})
def _modify_penalty(self, fadan, liyou, jine_str, processor_id=None):
"""修改罚款(仅状态1待缴纳 或 3申诉中)"""
if fadan.Status not in (1, 3):
"""修改罚款(待缴纳 / 申诉中 / 平台审核中)"""
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': '罚款理由不能为空'})
@@ -6966,13 +7019,14 @@ class ShangjiaFakuaiXiugaiView(APIView):
return Response({'code': 0, 'msg': '罚款修改成功'})
def _cancel_penalty(self, fadan, liyou, processor_id=None):
"""撤销罚款(状态1或3 → 状态4 已驳回)"""
if fadan.Status not in (1, 3):
"""撤销罚款(待缴纳 / 申诉中 / 平台审核中 → 已驳回)"""
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 = 4 # 已驳回
fadan.Status = PENALTY_REJECTED # 已驳回
fadan.RejectReason = liyou[:500]
if processor_id:
fadan.ProcessorID = processor_id
@@ -6981,8 +7035,9 @@ class ShangjiaFakuaiXiugaiView(APIView):
return Response({'code': 0, 'msg': '罚款已撤销(驳回)'})
def _resubmit_penalty(self, fadan, liyou, jine_str, processor_id=None):
"""再次申请罚款(状态4 → 状态1修改理由和金额"""
if fadan.Status != 4:
"""再次申请罚款(已驳回 → 平台审核中"""
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': '罚款理由不能为空'})
@@ -6993,7 +7048,7 @@ class ShangjiaFakuaiXiugaiView(APIView):
except:
return Response({'code': 400, 'msg': '罚款金额必须为正数'})
with transaction.atomic():
fadan.Status = 1 # 待缴纳
fadan.Status = PENALTY_PLATFORM_AUDIT
fadan.Reason = liyou[:500]
fadan.FineAmount = jine
fadan.RejectReason = '' # 清空驳回理由
@@ -7007,7 +7062,8 @@ class ShangjiaFakuaiXiugaiView(APIView):
def _manage_penalty_evidence(self, fadan, data):
"""管理罚单证据图片Purpose=1与申诉图片严格隔离"""
if fadan.Status not in (1, 3):
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 []
@@ -7018,6 +7074,9 @@ class ShangjiaFakuaiXiugaiView(APIView):
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: