183 lines
6.7 KiB
Python
183 lines
6.7 KiB
Python
"""资金池:划额度、派单扣减、退款释放。"""
|
||
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
|