Files
Django/merchant_ops/services/wallet.py

183 lines
6.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""资金池:划额度、派单扣减、退款释放。"""
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