fix: 组队结单后罚款仍按队员均摊

商家常在结单后申请罚款,招募已是结束态;现含已结束组队,并从结算流水兜底还原队员。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-27 08:18:06 +08:00
parent 7d366e8075
commit 871144b53b

View File

@@ -741,35 +741,75 @@ def settle_team_or_legacy(order, *, set_idle_leader=False) -> bool:
return True return True
def maybe_split_penalties(*, order_id: str, total_amount, reason: str, applicant_id, applicant_identity, def _team_members_for_penalty_split(order_id: str):
affects_grabbing=1, club_id='', extra_fields=None) -> Optional[list]:
""" """
若有有效组队(锁定或招募中已有队友),为每人创建罚单并返回列表;否则返回 None 走原逻辑 取本单应均摊罚款的组队成员
含招募中/已锁定/已结束(商家多在结单后申请罚款;结单会把招募置为已结束)。
取消/作废的组队不均摊。
""" """
from orders.models import TeamRecruit, TeamMember, Penalty from orders.models import TeamRecruit, TeamMember, TeamSettleLedger
from decimal import Decimal as D
rec = TeamRecruit.query.filter( oid = (order_id or '').strip()
order_id=order_id, if not oid:
status__in=[TeamRecruit.STATUS_RECRUITING, TeamRecruit.STATUS_LOCKED], return None, []
).first() rec = (
TeamRecruit.query.filter(
order_id=oid,
status__in=[
TeamRecruit.STATUS_RECRUITING,
TeamRecruit.STATUS_LOCKED,
TeamRecruit.STATUS_FINISHED,
],
)
.order_by('-id')
.first()
)
if not rec: if not rec:
return None return None, []
members = list( members = list(
TeamMember.query.filter(recruit_id=rec.id, join_status=TeamMember.JOIN_ACTIVE) TeamMember.query.filter(recruit_id=rec.id, join_status=TeamMember.JOIN_ACTIVE)
) )
if len(members) < 2: # 结单后偶发清退:用同单结算流水还原参与人
if len(members) < 2 and int(rec.status or 0) == TeamRecruit.STATUS_FINISHED:
ledger_uids = list(
TeamSettleLedger.query.filter(order_id=oid)
.values_list('user_uid', flat=True)
.distinct()
)
if len(ledger_uids) >= 2:
members = list(TeamMember.query.filter(recruit_id=rec.id, user_uid__in=ledger_uids))
if len(members) < 2:
# 无成员行时,用流水 UID 构造轻量对象供均摊
class _M:
def __init__(self, uid, role):
self.user_uid = uid
self.role = role
leader = rec.leader_uid or ''
members = [
_M(u, TeamMember.ROLE_LEADER if u == leader else TeamMember.ROLE_MEMBER)
for u in ledger_uids
]
return rec, members
def maybe_split_penalties(*, order_id: str, total_amount, reason: str, applicant_id, applicant_identity,
affects_grabbing=1, club_id='', extra_fields=None) -> Optional[list]:
"""
若有组队且至少 2 人在队(含结单后已结束),为每人创建罚单并返回列表;否则返回 None 走原逻辑。
"""
from orders.models import Penalty
from decimal import Decimal as D
rec, members = _team_members_for_penalty_split(order_id)
if not rec or len(members) < 2:
return None return None
total = _d(total_amount) total = _d(total_amount)
k = len(members) k = len(members)
# 队长优先拿尾差
members.sort(key=lambda m: 0 if getattr(m, 'role', '') == 'leader' else 1)
base = (total / D(k)).quantize(MONEY, rounding=ROUND_DOWN) base = (total / D(k)).quantize(MONEY, rounding=ROUND_DOWN)
amounts = [base] * k amounts = [base] * k
amounts[0] = _d(total - base * (k - 1)) # 尾差给列表第一人(队长优先排前面)
# 队长优先
members.sort(key=lambda m: 0 if m.role == TeamMember.ROLE_LEADER else 1)
amounts = [base] * k
amounts[0] = _d(total - base * (k - 1)) amounts[0] = _d(total - base * (k - 1))
reason_text = f'【组队均摊】{reason or ""}' reason_text = f'【组队均摊】{reason or ""}'