fix: 组队仅队长提交修改,组员可见交付;罚单聚合改撤联动
详情返回 can_submit/can_modify;交付图按订单聚合;组队均摊罚单展示总额并联动修改/撤销;罚单 ClubID 归属订单店。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -81,6 +81,78 @@ def get_active_recruit(order_id: str):
|
||||
).first()
|
||||
|
||||
|
||||
def get_team_role_for_user(order_id: str, user_uid: str) -> dict:
|
||||
"""
|
||||
当前用户在本单组队中的角色。
|
||||
返回: has_team, is_leader, is_member, leader_uid, my_share, recruit
|
||||
"""
|
||||
from orders.models import TeamMember, TeamRecruit
|
||||
out = {
|
||||
'has_team': False,
|
||||
'is_leader': False,
|
||||
'is_member': False,
|
||||
'leader_uid': '',
|
||||
'my_share': None,
|
||||
'recruit': None,
|
||||
}
|
||||
oid = (order_id or '').strip()
|
||||
uid = (user_uid or '').strip()
|
||||
if not oid or not uid:
|
||||
return out
|
||||
rec = get_active_recruit(oid)
|
||||
if not rec:
|
||||
rec = TeamRecruit.query.filter(order_id=oid).order_by('-id').first()
|
||||
if not rec:
|
||||
return out
|
||||
out['has_team'] = True
|
||||
out['recruit'] = rec
|
||||
out['leader_uid'] = rec.leader_uid or ''
|
||||
out['is_leader'] = (rec.leader_uid or '') == uid
|
||||
mem = TeamMember.query.filter(
|
||||
recruit_id=rec.id, user_uid=uid, join_status=TeamMember.JOIN_ACTIVE,
|
||||
).first()
|
||||
if mem:
|
||||
out['is_member'] = True
|
||||
out['my_share'] = float(_d(mem.share_amount))
|
||||
elif out['is_leader']:
|
||||
out['is_member'] = True
|
||||
return out
|
||||
|
||||
|
||||
def ensure_team_leader_can_submit(order, user_uid: str) -> Optional[str]:
|
||||
"""
|
||||
有有效组队时:仅队长可提交/修改交付。
|
||||
无组队时:须为订单 PlayerID。
|
||||
返回错误文案;通过返回 None。
|
||||
"""
|
||||
oid = getattr(order, 'OrderID', None) or ''
|
||||
uid = (user_uid or '').strip()
|
||||
player = (getattr(order, 'PlayerID', None) or '').strip()
|
||||
from orders.models import TeamRecruit
|
||||
ctx = get_team_role_for_user(oid, uid)
|
||||
if ctx['has_team'] and ctx['recruit'] and int(ctx['recruit'].status or 0) in (
|
||||
TeamRecruit.STATUS_RECRUITING, TeamRecruit.STATUS_LOCKED,
|
||||
):
|
||||
if not ctx['is_leader']:
|
||||
return '仅队长可提交或修改交付内容'
|
||||
return None
|
||||
if player and player != uid:
|
||||
return '订单不存在或没有提交权限'
|
||||
return None
|
||||
|
||||
|
||||
def list_team_split_penalties(order_id: str, *, include_rejected=False):
|
||||
"""同订单【组队均摊】罚单列表(按创建顺序)。"""
|
||||
from orders.models import Penalty
|
||||
qs = Penalty.query.filter(
|
||||
RelatedOrderID=order_id,
|
||||
Reason__startswith='【组队均摊】',
|
||||
)
|
||||
if not include_rejected:
|
||||
qs = qs.exclude(Status=4)
|
||||
return list(qs.order_by('id'))
|
||||
|
||||
|
||||
def team_should_split(order) -> bool:
|
||||
"""结单是否走组队分账:已锁定(或招募中但已有人——进8会强制锁)且有在队成员。"""
|
||||
from orders.models import TeamRecruit, TeamMember
|
||||
@@ -702,7 +774,15 @@ def maybe_split_penalties(*, order_id: str, total_amount, reason: str, applicant
|
||||
|
||||
reason_text = f'【组队均摊】{reason or ""}'
|
||||
created = []
|
||||
cid = (club_id or rec.club_id or 'xq')[:16]
|
||||
# 罚单归属订单俱乐部,避免跨店接单时记到错误店;利率仍按申请人俱乐部在 lock_penalty_bonus 锁定
|
||||
order_club = ''
|
||||
try:
|
||||
from orders.models import Order
|
||||
o = Order.query.filter(OrderID=order_id).only('ClubID').first()
|
||||
order_club = (getattr(o, 'ClubID', None) or '').strip() if o else ''
|
||||
except Exception:
|
||||
order_club = ''
|
||||
cid = (order_club or club_id or rec.club_id or 'xq')[:16]
|
||||
for i, m in enumerate(members):
|
||||
p = Penalty(
|
||||
PenalizedUserID=m.user_uid,
|
||||
|
||||
@@ -862,18 +862,28 @@ class DashouCOSZhengshuView(APIView):
|
||||
if not allowed:
|
||||
return Response({'code': 403, 'msg': '无权上传退款图片'}, status=403)
|
||||
else:
|
||||
# 默认为订单相关,需要验证订单且打手必须接单
|
||||
# 默认为订单交付图:组队仅队长;非组队须接单人
|
||||
try:
|
||||
dingdan = Order.query.get(
|
||||
OrderID=dingdan_id,
|
||||
PlayerID=request.user.UserUID,
|
||||
Status__in=[2, 8]
|
||||
)
|
||||
except Order.DoesNotExist:
|
||||
return Response(
|
||||
{'code': 403, 'msg': '此功能暂未开放', 'error': f'Order {dingdan_id} not found or not assigned to user {request.user.UserUID}'},
|
||||
{'code': 403, 'msg': '此功能暂未开放', 'error': f'Order {dingdan_id} not found'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
try:
|
||||
from orders.services.team_recruit import ensure_team_leader_can_submit
|
||||
deny = ensure_team_leader_can_submit(dingdan, request.user.UserUID)
|
||||
if deny:
|
||||
return Response({'code': 403, 'msg': deny}, status=status.HTTP_403_FORBIDDEN)
|
||||
except Exception:
|
||||
if dingdan.PlayerID != request.user.UserUID:
|
||||
return Response(
|
||||
{'code': 403, 'msg': '此功能暂未开放'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
# 4. 获取腾讯云STS临时密钥
|
||||
try:
|
||||
@@ -1220,26 +1230,34 @@ class DashouTijiaoView(APIView):
|
||||
'data': None
|
||||
}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# 4. 验证订单状态和权限
|
||||
# 4. 验证订单状态和权限(组队单仅队长可提交)
|
||||
try:
|
||||
dingdan = Order.query.get(
|
||||
OrderID=dingdan_id,
|
||||
PlayerID=yonghuid,
|
||||
Status=2 # 必须是进行中的订单
|
||||
)
|
||||
dingdan = Order.query.get(OrderID=dingdan_id, Status=2)
|
||||
except Order.DoesNotExist:
|
||||
return Response({
|
||||
'code': 7,
|
||||
'msg': '订单不存在或没有提交权限',
|
||||
'data': None
|
||||
}, status=status.HTTP_403_FORBIDDEN)
|
||||
try:
|
||||
from orders.services.team_recruit import ensure_team_leader_can_submit
|
||||
deny = ensure_team_leader_can_submit(dingdan, yonghuid)
|
||||
if deny:
|
||||
return Response({'code': 7, 'msg': deny, 'data': None}, status=status.HTTP_403_FORBIDDEN)
|
||||
except Exception:
|
||||
if dingdan.PlayerID != yonghuid:
|
||||
return Response({
|
||||
'code': 7,
|
||||
'msg': '订单不存在或没有提交权限',
|
||||
'data': None
|
||||
}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# 5. 存储图片到Dashoutupian表
|
||||
# 5. 存储图片到Dashoutupian表(统一记在接单人/队长名下,组员可读)
|
||||
owner_uid = dingdan.PlayerID or yonghuid
|
||||
for tupian_url in jiaofu_tupian_urls:
|
||||
# 创建打手提交图片记录
|
||||
PlayerDeliveryImage.query.create(
|
||||
OrderID=dingdan_id,
|
||||
PlayerID=yonghuid,
|
||||
PlayerID=owner_uid,
|
||||
ImageURL=tupian_url,
|
||||
CreateTime=timezone.now()
|
||||
)
|
||||
@@ -1342,11 +1360,32 @@ class DashouDingdanXiangqingView(APIView):
|
||||
'data': None
|
||||
}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# 5. 构建基础订单数据
|
||||
# 5. 构建基础订单数据(组队:组员看本人份额;交付图全员可见)
|
||||
from orders.services.team_recruit import get_team_role_for_user, ensure_team_leader_can_submit
|
||||
team_ctx = get_team_role_for_user(dingdan.OrderID, yonghuid)
|
||||
full_commission = float(dingdan.PlayerCommission) if dingdan.PlayerCommission else 0.0
|
||||
my_share = team_ctx.get('my_share')
|
||||
show_jine = full_commission
|
||||
if team_ctx.get('has_team') and not team_ctx.get('is_leader') and my_share is not None:
|
||||
show_jine = float(my_share)
|
||||
submit_deny = ensure_team_leader_can_submit(dingdan, yonghuid)
|
||||
can_submit = int(dingdan.Status or 0) == 2 and submit_deny is None
|
||||
can_modify = int(dingdan.Status or 0) in (4, 8) and submit_deny is None
|
||||
is_team_leader = (
|
||||
bool(team_ctx.get('is_leader'))
|
||||
if team_ctx.get('has_team')
|
||||
else (dingdan.PlayerID == yonghuid)
|
||||
)
|
||||
|
||||
dingdan_data = {
|
||||
'dingdan_id': dingdan.OrderID,
|
||||
'zhuangtai': dingdan.Status,
|
||||
'jine': float(dingdan.PlayerCommission) if dingdan.PlayerCommission else 0.0,
|
||||
'jine': show_jine,
|
||||
'order_commission': full_commission,
|
||||
'my_share_amount': my_share if my_share is not None else full_commission,
|
||||
'is_team_leader': is_team_leader,
|
||||
'can_submit': bool(can_submit),
|
||||
'can_modify': bool(can_modify),
|
||||
'jieshao': dingdan.Description or '',
|
||||
'tupian': dingdan.ImageURL or '',
|
||||
'nicheng': dingdan.Nickname or '',
|
||||
@@ -1391,10 +1430,9 @@ class DashouDingdanXiangqingView(APIView):
|
||||
except Exception:
|
||||
dingdan_data['team'] = None
|
||||
|
||||
# 6. 查询打手提交的图片
|
||||
# 6. 订单交付图(组队全员可见,不按当前用户过滤)
|
||||
dashou_tupian_list = PlayerDeliveryImage.query.filter(
|
||||
OrderID=dingdan_id,
|
||||
PlayerID=yonghuid
|
||||
).order_by('CreateTime')
|
||||
|
||||
jiaofu_tupian_urls = []
|
||||
@@ -1606,30 +1644,34 @@ class DashouXiugaiView(APIView):
|
||||
return Response({'code': 5, 'msg': '用户不是打手身份', 'data': None},
|
||||
status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# ---------- 订单查询及权限验证 ----------
|
||||
# ---------- 订单查询及权限验证(组队仅队长) ----------
|
||||
try:
|
||||
# 订单必须属于该打手,且状态为4(退款中)或8(结算中)
|
||||
dingdan = Order.query.get(
|
||||
OrderID=dingdan_id,
|
||||
PlayerID=yonghuid,
|
||||
Status__in=[4, 8] # 只允许修改退款中或结算中的订单
|
||||
)
|
||||
dingdan = Order.query.get(OrderID=dingdan_id, Status__in=[4, 8])
|
||||
except Order.DoesNotExist:
|
||||
return Response({'code': 6, 'msg': '订单不存在或没有修改权限', 'data': None},
|
||||
status=status.HTTP_403_FORBIDDEN)
|
||||
try:
|
||||
from orders.services.team_recruit import ensure_team_leader_can_submit
|
||||
deny = ensure_team_leader_can_submit(dingdan, yonghuid)
|
||||
if deny:
|
||||
return Response({'code': 6, 'msg': deny, 'data': None},
|
||||
status=status.HTTP_403_FORBIDDEN)
|
||||
except Exception:
|
||||
if dingdan.PlayerID != yonghuid:
|
||||
return Response({'code': 6, 'msg': '订单不存在或没有修改权限', 'data': None},
|
||||
status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
owner_uid = dingdan.PlayerID or yonghuid
|
||||
|
||||
# ---------- 执行修改(使用事务保证原子性) ----------
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# 1. 处理删除的图片:从 Dashoutupian 表中删除记录
|
||||
if deleted_images:
|
||||
# 根据订单ID、打手ID和图片URL删除记录
|
||||
delete_count, _ = PlayerDeliveryImage.query.filter(
|
||||
OrderID=dingdan_id,
|
||||
PlayerID=yonghuid,
|
||||
ImageURL__in=deleted_images
|
||||
).delete()
|
||||
# 可选:记录删除数量
|
||||
logger.info(f"订单 {dingdan_id} 删除图片 {delete_count} 张")
|
||||
|
||||
# 2. 处理新增的图片:插入新记录
|
||||
@@ -1639,7 +1681,7 @@ class DashouXiugaiView(APIView):
|
||||
for url in new_images:
|
||||
new_records.append(PlayerDeliveryImage(
|
||||
OrderID=dingdan_id,
|
||||
PlayerID=yonghuid,
|
||||
PlayerID=owner_uid,
|
||||
ImageURL=url,
|
||||
CreateTime=timezone.now()
|
||||
))
|
||||
|
||||
@@ -642,21 +642,37 @@ class ShangjiaDingdanXiangqingView(APIView):
|
||||
if hasattr(dashou_user, 'DashouProfile') and dashou_user.DashouProfile:
|
||||
dashou_nicheng = dashou_user.DashouProfile.nicheng or ''
|
||||
|
||||
# 6. 罚款数据(如果存在)
|
||||
fadan_obj = Penalty.query.filter(
|
||||
RelatedOrderID=dingdan_id,
|
||||
PenalizedUserID=dashou_yonghuid
|
||||
).first()
|
||||
# 6. 罚款数据(组队均摊:聚合全部罚单;展示总额+明细)
|
||||
from orders.services.team_recruit import list_team_split_penalties
|
||||
split_list = list_team_split_penalties(dingdan_id, include_rejected=True)
|
||||
fadan_qs = Penalty.query.filter(RelatedOrderID=dingdan_id).order_by('id')
|
||||
if split_list:
|
||||
fadan_list = split_list
|
||||
else:
|
||||
fadan_list = list(
|
||||
fadan_qs.filter(PenalizedUserID=dashou_yonghuid)[:1]
|
||||
) or list(fadan_qs[:1])
|
||||
fadan_obj = fadan_list[0] if fadan_list else None
|
||||
fadan_dict = None
|
||||
if fadan_obj:
|
||||
fadan_items = []
|
||||
if fadan_list:
|
||||
total_fine = 0.0
|
||||
total_bonus = 0.0
|
||||
rate = 0.0
|
||||
for p in fadan_list:
|
||||
total_fine += float(p.FineAmount or 0)
|
||||
total_bonus += float(p.ApplicantBonusAmount or 0)
|
||||
if p.ApplicantBonusRate:
|
||||
rate = float(p.ApplicantBonusRate)
|
||||
fadan_items.append({
|
||||
'id': p.id,
|
||||
'beichufa_id': p.PenalizedUserID or '',
|
||||
'fakuanjine': float(p.FineAmount or 0),
|
||||
'zhuangtai': p.Status,
|
||||
'yingde_fenhong': float(p.ApplicantBonusAmount or 0),
|
||||
})
|
||||
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)
|
||||
@@ -665,20 +681,23 @@ class ShangjiaDingdanXiangqingView(APIView):
|
||||
{'id': t.id, 'url': t.ImageURL}
|
||||
for t in PenaltyAppealImage.query.filter(Penalty=fadan_obj, Purpose=2)
|
||||
]
|
||||
reason = (fadan_obj.Reason or '').replace('【组队均摊】', '', 1)
|
||||
fadan_dict = {
|
||||
'id': fadan_obj.id,
|
||||
'chufaliyou': fadan_obj.Reason or '',
|
||||
'fakuanjine': float(fadan_obj.FineAmount) if fadan_obj.FineAmount else 0.0,
|
||||
'chufaliyou': reason,
|
||||
'fakuanjine': total_fine,
|
||||
'yingxiang_qiangdan': fadan_obj.AffectsGrabbing,
|
||||
'zhuangtai': fadan_obj.Status,
|
||||
'shensuliyou': fadan_obj.AppealReason or '',
|
||||
'tongyi_chufaliyou': fadan_obj.ApproveReason or '',
|
||||
'bohuiliyou': fadan_obj.RejectReason or '',
|
||||
'yingde_fenhong': float(fadan_obj.ApplicantBonusAmount or 0),
|
||||
'fenhong_lilv': float(fadan_obj.ApplicantBonusRate or 0),
|
||||
'fenhong_yiruzhang': bool(fadan_obj.BonusCredited),
|
||||
'yingde_fenhong': total_bonus,
|
||||
'fenhong_lilv': rate,
|
||||
'fenhong_yiruzhang': all(bool(p.BonusCredited) for p in fadan_list),
|
||||
'zhengju_tupian': zhengju_imgs,
|
||||
'shensu_tupian': shensu_imgs,
|
||||
'is_team_split': len(fadan_list) > 1 or (fadan_obj.Reason or '').startswith('【组队均摊】'),
|
||||
'items': fadan_items,
|
||||
'CreateTime': ct,
|
||||
'create_time': ct,
|
||||
'UpdateTime': ut,
|
||||
|
||||
@@ -385,9 +385,23 @@ class ShangjiaFakuaiXiugaiView(APIView):
|
||||
dingdan.save()
|
||||
return Response({'code': 0, 'msg': '退款理由修改成功'})
|
||||
|
||||
def _team_split_siblings(self, fadan, *, include_rejected=False):
|
||||
"""同订单组队均摊罚单;非均摊则只返回自身。"""
|
||||
from orders.services.team_recruit import list_team_split_penalties
|
||||
reason = fadan.Reason or ''
|
||||
if reason.startswith('【组队均摊】') and fadan.RelatedOrderID:
|
||||
sibs = list_team_split_penalties(
|
||||
fadan.RelatedOrderID, include_rejected=include_rejected,
|
||||
)
|
||||
if sibs:
|
||||
return sibs
|
||||
return [fadan]
|
||||
|
||||
def _modify_penalty(self, fadan, liyou, jine_str, processor_id=None):
|
||||
"""修改罚款(待缴纳 / 申诉中 / 平台审核中)"""
|
||||
"""修改罚款(待缴纳 / 申诉中 / 平台审核中);组队均摊联动改总额并重分。"""
|
||||
from utils.penalty_status import PENALTY_PENDING_PAY, PENALTY_APPEALING, PENALTY_PLATFORM_AUDIT
|
||||
from orders.services.team_recruit import _d, MONEY
|
||||
from decimal import ROUND_DOWN
|
||||
if fadan.Status not in (PENALTY_PENDING_PAY, PENALTY_APPEALING, PENALTY_PLATFORM_AUDIT):
|
||||
return Response({'code': 400, 'msg': '当前罚单状态不可修改'})
|
||||
if not liyou.strip():
|
||||
@@ -396,37 +410,70 @@ class ShangjiaFakuaiXiugaiView(APIView):
|
||||
jine = Decimal(jine_str)
|
||||
if jine <= 0:
|
||||
raise ValueError
|
||||
except:
|
||||
except Exception:
|
||||
return Response({'code': 400, 'msg': '罚款金额必须为正数'})
|
||||
siblings = self._team_split_siblings(fadan, include_rejected=False)
|
||||
for s in siblings:
|
||||
if s.Status not in (PENALTY_PENDING_PAY, PENALTY_APPEALING, PENALTY_PLATFORM_AUDIT):
|
||||
return Response({'code': 400, 'msg': '组队均摊罚单状态不一致,无法修改'})
|
||||
reason_text = liyou[:500]
|
||||
if (fadan.Reason or '').startswith('【组队均摊】') and not reason_text.startswith('【组队均摊】'):
|
||||
reason_text = f'【组队均摊】{reason_text}'
|
||||
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)
|
||||
k = len(siblings)
|
||||
if k > 1:
|
||||
total = _d(jine)
|
||||
base = (total / Decimal(k)).quantize(MONEY, rounding=ROUND_DOWN)
|
||||
amounts = [base] * k
|
||||
amounts[0] = _d(total - base * (k - 1))
|
||||
# 队长优先(与创建时一致)
|
||||
siblings = sorted(
|
||||
siblings,
|
||||
key=lambda p: 0 if p.id == fadan.id else 1,
|
||||
)
|
||||
for i, s in enumerate(siblings):
|
||||
s.Reason = reason_text
|
||||
s.FineAmount = amounts[i]
|
||||
if processor_id:
|
||||
s.ProcessorID = processor_id
|
||||
s.ProcessorIdentity = 5
|
||||
s.save()
|
||||
lock_penalty_bonus(s)
|
||||
else:
|
||||
fadan.Reason = reason_text
|
||||
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': '驳回理由不能为空'})
|
||||
siblings = self._team_split_siblings(fadan, include_rejected=False)
|
||||
with transaction.atomic():
|
||||
fadan.Status = PENALTY_REJECTED # 已驳回
|
||||
fadan.RejectReason = liyou[:500]
|
||||
if processor_id:
|
||||
fadan.ProcessorID = processor_id
|
||||
fadan.ProcessorIdentity = 5 # 小程序商家撤销=商家作为处理人
|
||||
fadan.save()
|
||||
for s in siblings:
|
||||
if s.Status not in (PENALTY_PENDING_PAY, PENALTY_APPEALING, PENALTY_PLATFORM_AUDIT):
|
||||
continue
|
||||
s.Status = PENALTY_REJECTED
|
||||
s.RejectReason = liyou[:500]
|
||||
if processor_id:
|
||||
s.ProcessorID = processor_id
|
||||
s.ProcessorIdentity = 5
|
||||
s.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
|
||||
from orders.services.team_recruit import _d, MONEY
|
||||
from decimal import ROUND_DOWN
|
||||
if fadan.Status != PENALTY_REJECTED:
|
||||
return Response({'code': 400, 'msg': '只有已驳回的罚单可再次申请'})
|
||||
if not liyou.strip():
|
||||
@@ -435,19 +482,33 @@ class ShangjiaFakuaiXiugaiView(APIView):
|
||||
jine = Decimal(jine_str)
|
||||
if jine <= 0:
|
||||
raise ValueError
|
||||
except:
|
||||
except Exception:
|
||||
return Response({'code': 400, 'msg': '罚款金额必须为正数'})
|
||||
siblings = self._team_split_siblings(fadan, include_rejected=True)
|
||||
siblings = [s for s in siblings if s.Status == PENALTY_REJECTED] or [fadan]
|
||||
reason_text = liyou[:500]
|
||||
if (fadan.Reason or '').startswith('【组队均摊】') and not reason_text.startswith('【组队均摊】'):
|
||||
reason_text = f'【组队均摊】{reason_text}'
|
||||
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}")
|
||||
k = len(siblings)
|
||||
total = _d(jine)
|
||||
if k > 1:
|
||||
base = (total / Decimal(k)).quantize(MONEY, rounding=ROUND_DOWN)
|
||||
amounts = [base] * k
|
||||
amounts[0] = _d(total - base * (k - 1))
|
||||
else:
|
||||
amounts = [total]
|
||||
for i, s in enumerate(siblings):
|
||||
s.Status = PENALTY_PLATFORM_AUDIT
|
||||
s.Reason = reason_text
|
||||
s.FineAmount = amounts[i] if i < len(amounts) else amounts[-1]
|
||||
s.RejectReason = ''
|
||||
if processor_id:
|
||||
s.ProcessorID = processor_id
|
||||
s.ProcessorIdentity = 5
|
||||
s.save()
|
||||
lock_penalty_bonus(s)
|
||||
logger.info(f"商家重新申请组队罚单 order={fadan.RelatedOrderID} 总额={jine} n={k}")
|
||||
return Response({'code': 0, 'msg': '已重新申请罚款'})
|
||||
|
||||
def _manage_penalty_evidence(self, fadan, data):
|
||||
|
||||
Reference in New Issue
Block a user