"""拒绝退款后的可配置比例结算(full 明细 × ratio,Status=6)。""" from __future__ import annotations import logging from decimal import Decimal, InvalidOperation, ROUND_HALF_UP from typing import Any, Optional from django.db import transaction from django.db.models import F from django.utils import timezone logger = logging.getLogger(__name__) MONEY = Decimal('0.01') RATIO_Q = Decimal('0.0001') PARTIAL_STATUS_NONE = 0 PARTIAL_STATUS_ARMED = 1 PARTIAL_STATUS_DONE = 2 STATUS_TEXT = { PARTIAL_STATUS_NONE: '', PARTIAL_STATUS_ARMED: '已锁定比例,待结算', PARTIAL_STATUS_DONE: '已按比例结算', } class PartialSettleError(Exception): """比例结算业务错误。""" def __init__(self, msg: str, code: int = 400): super().__init__(msg) self.msg = msg self.code = code def _d(v) -> Decimal: try: return Decimal(str(v if v is not None else 0)).quantize(MONEY, rounding=ROUND_HALF_UP) except Exception: return Decimal('0.00') def parse_settle_ratio(value, *, allow_percent=True) -> Decimal: """解析 0.01~1.00;allow_percent 时 1~100 视为百分比。""" if value is None or value == '': raise PartialSettleError('请填写结算比例') try: val = Decimal(str(value).strip()) except (InvalidOperation, ValueError, TypeError) as e: raise PartialSettleError('结算比例无效') from e if allow_percent and val > 1: val = val / Decimal('100') val = val.quantize(RATIO_Q) if val < Decimal('0.01') or val > Decimal('1.00'): raise PartialSettleError('结算比例须在 1%~100%') return val def club_default_partial_ratio(club_id: str) -> Decimal: from jituan.services.order_deal import get_or_create_deal_config row = get_or_create_deal_config(club_id or '') if not row: return Decimal('0.5000') try: return parse_settle_ratio(getattr(row, 'default_partial_settle_ratio', None) or '0.5') except PartialSettleError: return Decimal('0.5000') def preview_partial_amounts(order, ratio: Decimal) -> dict: """ 按 full×ratio 预览(不入账)。 - 打手/管事:分成 × ratio - 退商家:订单金额 Amount × (1-ratio) 回可提现余额 """ ratio = parse_settle_ratio(ratio) order_amount = _d(getattr(order, 'Amount', None)) full_player = _d(getattr(order, 'PlayerCommission', None)) full_manager = _d(getattr(order, 'ManagerCommission', None)) pay_player = _d(full_player * ratio) pay_manager = _d(full_manager * ratio) merchant_refund = _d(order_amount * (Decimal('1') - ratio)) team_shares = [] try: from orders.services.team_recruit import ( calc_lock_shares, get_active_recruit, team_should_split, _d as team_d, ) from orders.models import TeamRecruit, TeamMember oid = getattr(order, 'OrderID', '') or '' rec = None if team_should_split(order): rec = TeamRecruit.query.filter( order_id=oid, status=TeamRecruit.STATUS_LOCKED, ).first() if not rec: rec = get_active_recruit(oid) if rec: members = list( TeamMember.query.filter(recruit_id=rec.id, join_status=TeamMember.JOIN_ACTIVE) ) if members: c = full_player if any(team_d(m.share_amount) <= 0 and m.role == TeamMember.ROLE_MEMBER for m in members) or \ sum((team_d(m.share_amount) for m in members), Decimal('0')) <= 0: mate_uids = [m.user_uid for m in members if m.role == TeamMember.ROLE_MEMBER] shares = calc_lock_shares( c, rec.share_mode, rec.per_person_amount, rec.leader_uid, mate_uids, ) else: shares = {m.user_uid: team_d(m.share_amount) for m in members} total = sum(shares.values(), Decimal('0.00')) if total != c: diff = team_d(c - total) if rec.leader_uid in shares: shares[rec.leader_uid] = team_d(shares[rec.leader_uid] + diff) for m in members: full_share = team_d(shares.get(m.user_uid, 0)) team_shares.append({ 'user_uid': m.user_uid, 'role': m.role, 'full_share': float(full_share), 'partial_share': float(_d(full_share * ratio)), }) except Exception as e: logger.warning('比例结算预览组队份额失败 %s: %s', getattr(order, 'OrderID', ''), e) return { 'settle_ratio': str(ratio), 'settle_percent': int((ratio * 100).quantize(Decimal('1'))), 'refund_percent': int(((Decimal('1') - ratio) * 100).quantize(Decimal('1'))), 'full_player_commission': float(full_player), 'full_manager_commission': float(full_manager), 'partial_player_amount': float(pay_player), 'partial_manager_amount': float(pay_manager), 'merchant_refund_amount': float(merchant_refund), 'team_shares': team_shares, 'order_amount': float(order_amount), } def build_refund_settle_payload(order, *, viewer_uid: str = '') -> dict: """详情 API 附加:申请/拒绝/同意原因 + 比例结算状态与金额。""" from orders.models import RefundRecord from backend.utils import fmt_datetime oid = getattr(order, 'OrderID', '') or '' apply_reason = '' reject_reason = '' approve_reason = '' apply_status = None try: rec = RefundRecord.query.filter(OrderID=oid).order_by('-CreateTime').first() if rec: apply_reason = (rec.Reason or '').strip() reject_reason = (rec.RejectReason or '').strip() apply_status = rec.ApplyStatus if int(rec.ApplyStatus or 0) == 1: approve_reason = (getattr(order, 'RefundReason', None) or '').strip() except Exception: pass if not reject_reason and int(getattr(order, 'Status', 0) or 0) in (6, 8): # 历史单:拒绝理由可能只写在订单 RefundReason if int(getattr(order, 'PartialSettleStatus', 0) or 0) or int(getattr(order, 'Status', 0) or 0) == 6: reject_reason = reject_reason or (getattr(order, 'RefundReason', None) or '').strip() st = int(getattr(order, 'PartialSettleStatus', 0) or 0) ratio = getattr(order, 'PartialSettleRatio', None) ratio_d = None if ratio is not None: try: ratio_d = parse_settle_ratio(ratio) except PartialSettleError: ratio_d = None full_player = _d(getattr(order, 'PlayerCommission', None)) full_manager = _d(getattr(order, 'ManagerCommission', None)) order_amount = _d(getattr(order, 'Amount', None)) if st == PARTIAL_STATUS_DONE: pay_player = _d(getattr(order, 'PartialSettlePlayerAmount', None)) pay_manager = _d(getattr(order, 'PartialSettleManagerAmount', None)) merchant_refund = _d(getattr(order, 'PartialSettleMerchantRefund', None)) elif ratio_d is not None: pay_player = _d(full_player * ratio_d) pay_manager = _d(full_manager * ratio_d) merchant_refund = _d(order_amount * (Decimal('1') - ratio_d)) else: pay_player = Decimal('0.00') pay_manager = Decimal('0.00') merchant_refund = Decimal('0.00') can_partial = ( st == PARTIAL_STATUS_ARMED and int(getattr(order, 'Status', 0) or 0) == 8 and ratio_d is not None ) my_partial_share = None if viewer_uid and ratio_d is not None: try: from orders.services.team_recruit import get_team_role_for_user ctx = get_team_role_for_user(oid, viewer_uid) if ctx.get('my_share') is not None: my_partial_share = float(_d(Decimal(str(ctx['my_share'])) * ratio_d)) elif (getattr(order, 'PlayerID', None) or '') == viewer_uid and not ctx.get('has_team'): my_partial_share = float(pay_player) except Exception: pass settled_at = getattr(order, 'PartialSettledAt', None) # tuikuan_liyou:优先展示商家申请理由;无记录时回落订单字段(兼容历史) show_apply = apply_reason or ( (getattr(order, 'RefundReason', None) or '').strip() if apply_status in (0, None) and not reject_reason else '' ) team_shares = [] if ratio_d is not None: try: team_shares = preview_partial_amounts(order, ratio_d).get('team_shares') or [] except Exception as e: logger.warning('比例结算详情组队份额失败 %s: %s', oid, e) return { 'tuikuan_liyou': show_apply, 'apply_reason': apply_reason, 'reject_reason': reject_reason, 'approve_reason': approve_reason, 'refund_apply_status': apply_status, 'settle_ratio': str(ratio_d) if ratio_d is not None else '', 'settle_percent': int((ratio_d * 100).quantize(Decimal('1'))) if ratio_d is not None else None, 'partial_settle_status': st, 'partial_settle_status_text': STATUS_TEXT.get(st, ''), 'full_player_commission': float(full_player), 'full_manager_commission': float(full_manager), 'partial_player_amount': float(pay_player) if (st or ratio_d) else None, 'partial_manager_amount': float(pay_manager) if (st or ratio_d) else None, 'merchant_refund_amount': float(merchant_refund) if (st or ratio_d) else None, 'refund_percent': int(((Decimal('1') - ratio_d) * 100).quantize(Decimal('1'))) if ratio_d is not None else None, 'partial_settled_at': fmt_datetime(settled_at) if settled_at else '', 'partial_settled_by': getattr(order, 'PartialSettledBy', '') or '', 'can_partial_settle': can_partial, 'my_partial_share': my_partial_share, 'team_shares': team_shares, 'default_partial_settle_ratio': str(club_default_partial_ratio(getattr(order, 'ClubID', '') or '')), } def _upsert_refund_reject(order, *, processor_id: str, jujue_liyou: str): from orders.models import RefundRecord from orders.services.merchant_refund_approve import normalize_processor_id dingdan_id = order.OrderID phone = normalize_processor_id(processor_id) try: refund_record = RefundRecord.query.get(OrderID=dingdan_id) refund_record.ApplyStatus = 2 refund_record.ProcessorID = phone refund_record.RejectReason = jujue_liyou or '' refund_record.save() except RefundRecord.DoesNotExist: RefundRecord.query.create( OrderID=dingdan_id, PlayerID=order.PlayerID or '', ApplicantID='', ProcessorID=phone, Reason=jujue_liyou or '客服拒绝退款', ApplyStatus=2, Amount=order.Amount, PlayerCommission=order.PlayerCommission or 0, Description=order.Description or '', Remark='客服拒绝退款', Nickname=order.Nickname or '', RejectReason=jujue_liyou or '', ) def arm_partial_settle( order, *, ratio: Decimal, jujue_liyou: str, operator: str, ) -> dict: """锁定比例,回 Status=8,清自动结算,待商家/客服强制比例结。""" ratio = parse_settle_ratio(ratio) if int(order.Status) != 4: raise PartialSettleError('订单状态不是退款审核中') if int(getattr(order, 'PartialSettleStatus', 0) or 0) == PARTIAL_STATUS_DONE: raise PartialSettleError('订单已比例结算,无法重复操作') preview = preview_partial_amounts(order, ratio) order.PartialSettleRatio = ratio order.PartialSettleStatus = PARTIAL_STATUS_ARMED order.PartialSettlePlayerAmount = _d(preview['partial_player_amount']) order.PartialSettleManagerAmount = _d(preview['partial_manager_amount']) order.PartialSettleMerchantRefund = _d(preview['merchant_refund_amount']) order.Status = 8 order.AutoSettleEligible = False order.AutoExpireAt = None order.PendingDispatch = False order.AutoTaskID = '' order.SettlementTime = timezone.now() if jujue_liyou: order.RefundReason = jujue_liyou if operator: order.AssignedCS = (operator or '')[:20] order.save(update_fields=[ 'PartialSettleRatio', 'PartialSettleStatus', 'PartialSettlePlayerAmount', 'PartialSettleManagerAmount', 'PartialSettleMerchantRefund', 'Status', 'AutoSettleEligible', 'AutoExpireAt', 'PendingDispatch', 'AutoTaskID', 'SettlementTime', 'RefundReason', 'AssignedCS', 'UpdateTime', ]) _upsert_refund_reject(order, processor_id=operator, jujue_liyou=jujue_liyou) return preview def _credit_team_or_solo_partial(order, *, ratio: Decimal, pay_player: Decimal) -> bool: """组队:full 份额 × ratio 入账;否则单人折后入账。返回是否组队。""" from orders.services.team_recruit import ( calc_lock_shares, get_active_recruit, lock_recruit, team_should_split, _d as team_d, _log, ) from orders.models import TeamRecruit, TeamMember, TeamSettleLedger from users.business_models import User from jituan.services.fund_freeze import credit_dashou_order_settle oid = order.OrderID rec = get_active_recruit(oid) if rec and rec.status == TeamRecruit.STATUS_RECRUITING: lock_recruit(rec.id, actor_uid='system', reason='partial_settle_force_lock') if not team_should_split(order): # 仍有有效组队则禁止全额打队长 if get_active_recruit(oid): raise PartialSettleError('存在有效组队但未锁定,无法比例结算') return False rec = TeamRecruit.objects.select_for_update().filter( order_id=oid, status=TeamRecruit.STATUS_LOCKED, ).first() if not rec: return False members = list( TeamMember.query.filter(recruit_id=rec.id, join_status=TeamMember.JOIN_ACTIVE) ) if not members: return False c = team_d(order.PlayerCommission) if any(team_d(m.share_amount) <= 0 and m.role == TeamMember.ROLE_MEMBER for m in members) or \ sum((team_d(m.share_amount) for m in members), Decimal('0')) <= 0: mate_uids = [m.user_uid for m in members if m.role == TeamMember.ROLE_MEMBER] shares = calc_lock_shares(c, rec.share_mode, rec.per_person_amount, rec.leader_uid, mate_uids) for m in members: m.share_amount = shares.get(m.user_uid, Decimal('0.00')) m.save(update_fields=['share_amount', 'UpdateTime']) total = sum((team_d(m.share_amount) for m in members), Decimal('0.00')) if total != c: diff = team_d(c - total) for m in members: if m.role == TeamMember.ROLE_LEADER: m.share_amount = team_d(m.share_amount) + diff m.save(update_fields=['share_amount', 'UpdateTime']) break # 折后份额合计对齐 pay_player(尾差给队长) partial_map = {} for m in members: partial_map[m.user_uid] = _d(team_d(m.share_amount) * ratio) sum_partial = sum(partial_map.values(), Decimal('0.00')) if sum_partial != pay_player: for m in members: if m.role == TeamMember.ROLE_LEADER: partial_map[m.user_uid] = _d(partial_map[m.user_uid] + (pay_player - sum_partial)) break for m in members: amt = partial_map.get(m.user_uid, Decimal('0.00')) biz_id = f'{oid}:team:{m.user_uid}:partial_settle' if TeamSettleLedger.query.filter(biz_id=biz_id).exists(): continue # 禁止与全额结单幂等键混用:若已有全额入账则报错 full_biz = f'{oid}:team:{m.user_uid}' if TeamSettleLedger.query.filter(biz_id=full_biz).exists(): raise PartialSettleError('订单已全额组队结算,无法比例结算') try: user = User.query.get(UserUID=m.user_uid) profile = user.DashouProfile except Exception: logger.error('比例结算组队打手不存在 %s %s', oid, m.user_uid) continue credit_dashou_order_settle( order=order, dashou_profile=profile, user_id=m.user_uid, amount=amt, set_idle_status=True, increment_chengjiao=True, settle_biz_id=biz_id, ) TeamSettleLedger( club_id=rec.club_id, order_id=oid, user_uid=m.user_uid, amount=amt, biz_id=biz_id, ).save() now = timezone.now() rec.status = TeamRecruit.STATUS_FINISHED rec.closed_at = now rec.save(update_fields=['status', 'closed_at', 'UpdateTime']) _log(rec.club_id, oid, rec.id, 'system', 'partial_settle', f'ratio={ratio} total={pay_player}') return True def _credit_merchant_partial_refund( order, *, amount: Decimal, ratio: Decimal, reason: str, source: str, operator: str, ) -> dict: """ 退商家 Amount×(1-ratio) 到可提现余额;店员额度单则释放额度。 幂等:PartialMerchantRefundLedger.order_id / biz_id 唯一。 """ from orders.models import PartialMerchantRefundLedger from users.business_models import User from users.models import UserShangjia from merchant_ops.services.wallet import is_staff_quota_order, release_dispatch_quota from backend.utils import update_shangjia_daily oid = str(getattr(order, 'OrderID', '') or '') amount = _d(amount) if amount <= 0: return {'credited': False, 'amount': '0.00', 'idempotent': True} biz_id = f'{oid}:merchant:partial_refund' if PartialMerchantRefundLedger.query.filter(biz_id=biz_id).exists(): logger.warning('比例结算退商家幂等跳过 order=%s', oid) return {'credited': False, 'amount': str(amount), 'idempotent': True} try: mid = order.shangjia_kuozhan.MerchantID except Exception as e: raise PartialSettleError('订单缺少商家信息,无法退还余额') from e if not mid: raise PartialSettleError('订单缺少商家ID,无法退还余额') staff_quota = bool(is_staff_quota_order(oid)) reason_text = (reason or f'拒绝退款比例结算退还 ratio={ratio}').strip()[:200] # 先落账本再入账,唯一约束防并发双退 try: ledger = PartialMerchantRefundLedger( order_id=oid, merchant_uid=str(mid)[:32], amount=amount, order_amount=_d(order.Amount), settle_ratio=ratio, reason=reason_text, source=(source or '')[:32], operator=(operator or '')[:32], biz_id=biz_id, staff_quota=staff_quota, ) ledger.save() except Exception as e: if PartialMerchantRefundLedger.query.filter(biz_id=biz_id).exists(): return {'credited': False, 'amount': str(amount), 'idempotent': True} raise PartialSettleError(f'退商家账本写入失败: {e}') from e if staff_quota: try: release_dispatch_quota( oid, amount, operator or mid, remark=f'比例结算退还额度 {reason_text}'[:80], ) except Exception as e: logger.error('比例结算释放店员额度失败 order=%s: %s', oid, e, exc_info=True) raise PartialSettleError(f'退还店员额度失败: {e}') from e else: try: sj_user = User.objects.select_for_update().get(UserUID=mid) profile = sj_user.ShopProfile UserShangjia.objects.filter(id=profile.id).update(yue=F('yue') + amount) except (User.DoesNotExist, AttributeError) as e: raise PartialSettleError('商家不存在,无法退还余额') from e try: update_shangjia_daily( yonghuid=mid, amount=amount, action=3, # 退款 order_id=oid, ) except Exception as e: logger.error('比例结算退商家日统计失败: %s', e) logger.info( '比例结算退商家成功 order=%s merchant=%s amount=%s staff_quota=%s', oid, mid, amount, staff_quota, ) return { 'credited': True, 'amount': str(amount), 'idempotent': False, 'staff_quota': staff_quota, 'merchant_uid': mid, } @transaction.atomic def settle_merchant_order_partial( order, *, ratio: Optional[Decimal] = None, operator: str = '', source: str = '', ) -> dict: """ 统一比例结算:full×ratio 入账打手/组队/管事,Status=6,写快照。 调用方须保证在合适的业务状态(4 立即结 / 8 且已锁定)。 """ from orders.models import Order from users.business_models import User from users.models import UserShangjia from jituan.services.fund_freeze import credit_dashou_order_settle from orders.utils import settle_shangjia_order_guanshi_fenhong from backend.utils import update_dashou_daily_by_action, update_shangjia_daily locked = Order.objects.select_for_update().select_related('shangjia_kuozhan').get(pk=order.pk) st = int(getattr(locked, 'PartialSettleStatus', 0) or 0) if st == PARTIAL_STATUS_DONE: raise PartialSettleError('订单已比例结算', code=400) if int(locked.Status or 0) == 3: raise PartialSettleError('订单已全额结单,无法比例结算') if int(locked.Status or 0) == 5: raise PartialSettleError('订单已退款,无法比例结算') if int(locked.Status or 0) == 6: raise PartialSettleError('订单已关闭(退款失败),无法再结算') if ratio is not None: ratio_d = parse_settle_ratio(ratio) elif locked.PartialSettleRatio is not None: ratio_d = parse_settle_ratio(locked.PartialSettleRatio) else: raise PartialSettleError('缺少结算比例') # 允许:退款审核中立即结;或已锁定待结且 Status=8 status = int(locked.Status or 0) if status == 4: pass elif status == 8 and st in (PARTIAL_STATUS_NONE, PARTIAL_STATUS_ARMED): # NONE 兼容强制比例结传入 ratio pass else: raise PartialSettleError(f'当前状态不允许比例结算(status={status}, partial={st})') if not locked.PlayerID: raise PartialSettleError('订单没有接单打手') if locked.PlayerCommission is None: raise PartialSettleError('订单缺少打手分成金额') preview = preview_partial_amounts(locked, ratio_d) pay_player = _d(preview['partial_player_amount']) pay_manager = _d(preview['partial_manager_amount']) merchant_refund = _d(preview['merchant_refund_amount']) dashou_id = locked.PlayerID oid = locked.OrderID reject_reason = (getattr(locked, 'RefundReason', None) or '').strip() team_done = _credit_team_or_solo_partial(locked, ratio=ratio_d, pay_player=pay_player) if not team_done: try: dashou_user = User.query.get(UserUID=dashou_id) dashou_profile = dashou_user.DashouProfile except (User.DoesNotExist, AttributeError) as e: raise PartialSettleError('接单打手不存在或信息不完整') from e credit_dashou_order_settle( order=locked, dashou_profile=dashou_profile, user_id=dashou_id, amount=pay_player, set_idle_status=True, increment_chengjiao=True, settle_biz_id=f'{oid}:partial_settle', ) merchant_refund_result = {'credited': False, 'amount': '0.00'} if int(locked.Platform or 0) == 2: try: mid = locked.shangjia_kuozhan.MerchantID if mid: try: sj_user = User.query.get(UserUID=mid) UserShangjia.query.filter(id=sj_user.ShopProfile.id).update( chengjiao=F('chengjiao') + 1, ) except (User.DoesNotExist, AttributeError): logger.warning('比例结算:商家 %s 不存在,跳过成交+1', mid) # 日统计:成交口径记 Amount×ratio;退还另记 action=3 try: kept = _d(_d(locked.Amount) * ratio_d) if kept > 0: update_shangjia_daily( yonghuid=mid, amount=kept, action=2, order_id=oid, ) except Exception as e: logger.error('比例结算商家日统计失败: %s', e) except Exception as e: logger.warning('比例结算更新商家成交失败: %s', e) settle_shangjia_order_guanshi_fenhong( locked, dashou_id, amount=pay_manager, biz_id=f'{oid}:guanshi:merchant:partial_settle', shuoming='商家订单比例结算分红', ) # 剩余 (1-ratio)×Amount 退回商家(幂等) if merchant_refund > 0: merchant_refund_result = _credit_merchant_partial_refund( locked, amount=merchant_refund, ratio=ratio_d, reason=reject_reason or f'比例结算退还{preview.get("refund_percent")}%', source=source or '', operator=operator or '', ) try: update_dashou_daily_by_action( yonghuid=dashou_id, amount=pay_player, action=2, ) except Exception as e: logger.error('比例结算打手日统计失败: %s', e) now = timezone.now() locked.PartialSettleRatio = ratio_d locked.PartialSettleStatus = PARTIAL_STATUS_DONE locked.PartialSettlePlayerAmount = pay_player locked.PartialSettleManagerAmount = pay_manager locked.PartialSettleMerchantRefund = merchant_refund locked.PartialSettledAt = now locked.PartialSettledBy = (operator or source or '')[:32] locked.Status = 6 locked.AutoSettleEligible = False locked.AutoExpireAt = None locked.PendingDispatch = False locked.AutoTaskID = '' if operator: locked.AssignedCS = (operator or '')[:20] locked.save(update_fields=[ 'PartialSettleRatio', 'PartialSettleStatus', 'PartialSettlePlayerAmount', 'PartialSettleManagerAmount', 'PartialSettleMerchantRefund', 'PartialSettledAt', 'PartialSettledBy', 'Status', 'AutoSettleEligible', 'AutoExpireAt', 'PendingDispatch', 'AutoTaskID', 'AssignedCS', 'UpdateTime', ]) logger.info( '比例结算完成 order=%s ratio=%s player=%s manager=%s refund=%s source=%s team=%s', oid, ratio_d, pay_player, pay_manager, merchant_refund, source, team_done, ) return { **preview, 'partial_settle_status': PARTIAL_STATUS_DONE, 'team_settled': team_done, 'source': source, 'merchant_refund': merchant_refund_result, } def reject_refund_partial( order, *, jujue_liyou: str, settle_ratio, settle_timing: str, operator: str, ) -> dict: """ 客服/后台拒绝退款入口。 settle_timing: immediate | merchant """ liyou = (jujue_liyou or '').strip() if not liyou: raise PartialSettleError('请填写拒绝理由') # 默认留给商家结算(客服强制立即结需显式传 immediate) timing = (settle_timing or 'merchant').strip().lower() if timing not in ('immediate', 'merchant'): raise PartialSettleError('settle_timing 须为 immediate 或 merchant') ratio = parse_settle_ratio(settle_ratio) if timing == 'merchant': preview = arm_partial_settle( order, ratio=ratio, jujue_liyou=liyou, operator=operator, ) return {'timing': 'merchant', 'status': 8, **preview} # 立即比例结:先写拒绝理由到订单,再结算 if liyou: order.RefundReason = liyou order.save(update_fields=['RefundReason', 'UpdateTime']) _upsert_refund_reject(order, processor_id=operator, jujue_liyou=liyou) result = settle_merchant_order_partial( order, ratio=ratio, operator=operator, source='reject_immediate', ) return {'timing': 'immediate', 'status': 6, **result}