"""资金池:划额度、派单扣减、退款释放。""" from decimal import Decimal from django.db import transaction from django.db.models import F from merchant_ops.constants import ( WALLET_ALLOCATE, WALLET_DISPATCH_CONFIRM, WALLET_REFUND_RELEASE, WALLET_REVOKE, ) from merchant_ops.models import MerchantStaffWallet, MerchantStaffWalletLedger from users.models import UserShangjia def get_or_create_wallet(member): wallet, _ = MerchantStaffWallet.objects.get_or_create( merchant_id=member.merchant_id, staff_user_id=member.staff_user_id, defaults={'member': member}, ) return wallet def allocate_quota(member, amount, operator_id, remark=''): """ 老板划额度:从商家余额转入客服可用额度(商家余额减少,客服 quota_total 增加)。 """ amount = Decimal(str(amount)) if amount <= 0: raise ValueError('划额度必须大于0') with transaction.atomic(): shangjia = UserShangjia.objects.select_for_update().get(user__UserUID=member.merchant_id) if amount > shangjia.yue: raise ValueError(f'商家余额不足,当前{shangjia.yue}元') wallet = MerchantStaffWallet.objects.select_for_update().get( merchant_id=member.merchant_id, staff_user_id=member.staff_user_id, ) UserShangjia.objects.filter(id=shangjia.id).update(yue=F('yue') - amount) wallet.quota_total = F('quota_total') + amount wallet.save(update_fields=['quota_total']) wallet.refresh_from_db() MerchantStaffWalletLedger.objects.create( wallet=wallet, merchant_id=member.merchant_id, staff_user_id=member.staff_user_id, change_type=WALLET_ALLOCATE, amount=amount, balance_after=wallet.quota_available, operator_id=operator_id, remark=remark or '老板划额度', ) return wallet def check_wallet_available(member, amount): amount = Decimal(str(amount)) wallet = get_or_create_wallet(member) if amount > wallet.quota_available: raise ValueError(f'客服可用额度不足,当前{wallet.quota_available}元') def confirm_dispatch_deduct(member, amount, order_id, operator_id): """派单成功:仅扣客服额度(商家余额已在划额度时扣除)。""" amount = Decimal(str(amount)) with transaction.atomic(): wallet = MerchantStaffWallet.objects.select_for_update().get( merchant_id=member.merchant_id, staff_user_id=member.staff_user_id, ) available = wallet.quota_total - wallet.quota_used - wallet.quota_frozen if amount > available: raise ValueError('客服可用额度不足') wallet.quota_used = F('quota_used') + amount wallet.save(update_fields=['quota_used']) wallet.refresh_from_db() MerchantStaffWalletLedger.objects.create( wallet=wallet, merchant_id=member.merchant_id, staff_user_id=member.staff_user_id, change_type=WALLET_DISPATCH_CONFIRM, amount=-amount, balance_after=wallet.quota_available, related_order_id=order_id, operator_id=operator_id, remark='派单扣额度', ) def release_dispatch_quota(order_id, amount, operator_id, remark='退款释放额度'): """订单撤销/退款:释放子客服已扣额度。""" from merchant_ops.models import MerchantOrderDispatch, MerchantStaffMember amount = Decimal(str(amount)) if amount <= 0 or not order_id: return disp = MerchantOrderDispatch.objects.filter(order_id=order_id).first() if not disp or not disp.staff_member_id: return member = MerchantStaffMember.objects.filter(id=disp.staff_member_id).first() if not member: return with transaction.atomic(): wallet = MerchantStaffWallet.objects.select_for_update().filter( merchant_id=member.merchant_id, staff_user_id=member.staff_user_id, ).first() if not wallet: return release = min(amount, wallet.quota_used) if release <= 0: return wallet.quota_used = F('quota_used') - release wallet.save(update_fields=['quota_used']) wallet.refresh_from_db() MerchantStaffWalletLedger.objects.create( wallet=wallet, merchant_id=member.merchant_id, staff_user_id=member.staff_user_id, change_type=WALLET_REFUND_RELEASE, amount=release, balance_after=wallet.quota_available, related_order_id=order_id, operator_id=operator_id or member.staff_user_id, remark=remark, ) def is_staff_quota_order(order_id): """订单是否由子客服额度出资(划额度池 + 派单扣额度,非老板余额直扣)。""" from merchant_ops.models import MerchantOrderDispatch if not order_id: return False return MerchantOrderDispatch.objects.filter( order_id=order_id, staff_member_id__isnull=False, ).exists() def revoke_quota(member, amount, operator_id, remark=''): """ 收回未使用额度:客服 quota_total 减少,商家余额增加。 已派单占用(quota_used)部分不可收回,防止资金悬空或重复退回。 """ amount = Decimal(str(amount)) if amount <= 0: raise ValueError('收回额度必须大于0') with transaction.atomic(): wallet = MerchantStaffWallet.objects.select_for_update().get( merchant_id=member.merchant_id, staff_user_id=member.staff_user_id, ) available = wallet.quota_total - wallet.quota_used - wallet.quota_frozen if amount > available: raise ValueError( f'最多可收回{available}元(未使用额度);' f'已派单占用{wallet.quota_used}元需先撤销/退款订单后再释放' ) shangjia = UserShangjia.objects.select_for_update().get(user__UserUID=member.merchant_id) wallet.quota_total = F('quota_total') - amount wallet.save(update_fields=['quota_total']) wallet.refresh_from_db() UserShangjia.objects.filter(id=shangjia.id).update(yue=F('yue') + amount) MerchantStaffWalletLedger.objects.create( wallet=wallet, merchant_id=member.merchant_id, staff_user_id=member.staff_user_id, change_type=WALLET_REVOKE, amount=-amount, balance_after=wallet.quota_available, operator_id=operator_id, remark=remark or '收回未使用额度', ) return wallet