fix: 手动打款驳回按申请扣款额全额退回各身份余额

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-07 09:52:57 +08:00
parent fedb32f78a
commit 146a64b710
3 changed files with 141 additions and 61 deletions

View File

@@ -0,0 +1,32 @@
from decimal import Decimal
from django.db import migrations
def backfill_shenqing_jine(apps, schema_editor):
Tixianjilu = apps.get_model('yonghu', 'Tixianjilu')
TixianShenheJilu = apps.get_model('yonghu', 'TixianShenheJilu')
for row in Tixianjilu.objects.filter(shenqing_jine__isnull=True).iterator():
amount = None
if row.shenhe_jilu_id:
audit = TixianShenheJilu.objects.filter(pk=row.shenhe_jilu_id).only('shenqing_jine').first()
if audit and audit.shenqing_jine is not None:
amount = Decimal(str(audit.shenqing_jine))
if amount is None:
jine = Decimal(str(row.jine or 0))
fee = Decimal(str(row.shouxufei or 0))
amount = jine + fee
if amount > 0:
Tixianjilu.objects.filter(pk=row.pk).update(shenqing_jine=amount)
class Migration(migrations.Migration):
dependencies = [
('yonghu', '0021_tixianjilu_shenqing_jine_shouxufei'),
]
operations = [
migrations.RunPython(backfill_shenqing_jine, migrations.RunPython.noop),
]

View File

@@ -822,31 +822,69 @@ def reject_audit_and_refund(user_main, audit, reason):
bhliyou=reason,
fail_reason=reason,
)
refund_balance(user_main, audit.leixing, audit.shenqing_jine)
if audit.tixianjilu_id:
tixian = Tixianjilu.objects.filter(pk=audit.tixianjilu_id).first()
refund_amount = get_tixianjilu_refund_amount(tixian) if tixian else _quantize_money(audit.shenqing_jine)
else:
refund_amount = _quantize_money(audit.shenqing_jine)
refund_balance(user_main, audit.leixing, refund_amount)
logger.info(
'提现驳回退款 shenhe_danhao=%s yonghuid=%s leixing=%s refund=%s reason=%s',
audit.shenhe_danhao, audit.yonghuid, audit.leixing, audit.shenqing_jine, reason,
audit.shenhe_danhao, audit.yonghuid, audit.leixing, refund_amount, reason,
)
return True, reason
def _quantize_money(value):
return decimal.Decimal(str(value or 0)).quantize(
decimal.Decimal('0.01'), rounding=decimal.ROUND_HALF_UP,
)
def get_tixianjilu_refund_amount(tixian):
"""手动提现驳回应退还的申请扣款额(非到账额)。"""
if getattr(tixian, 'shenqing_jine', None) not in (None, ''):
return decimal.Decimal(str(tixian.shenqing_jine))
fee = getattr(tixian, 'shouxufei', None) or decimal.Decimal('0')
base = decimal.Decimal(str(tixian.jine or 0))
if fee:
return base + decimal.Decimal(str(fee))
return base
"""
驳回应退还的申请扣款额(申请时从对应身份余额扣减的全额,含手续费部分;非实际到账额)。
优先级tixian.shenqing_jine → 关联审核单.shenqing_jine → jine + shouxufei
"""
sj = getattr(tixian, 'shenqing_jine', None)
if sj is not None and str(sj).strip() != '':
amount = _quantize_money(sj)
if amount > 0:
return amount
shenhe_jilu_id = getattr(tixian, 'shenhe_jilu_id', None)
if shenhe_jilu_id:
audit = (
TixianShenheJilu.objects.filter(pk=shenhe_jilu_id)
.only('shenqing_jine')
.first()
)
if audit and audit.shenqing_jine is not None:
amount = _quantize_money(audit.shenqing_jine)
if amount > 0:
return amount
fee = _quantize_money(getattr(tixian, 'shouxufei', None) or 0)
base = _quantize_money(getattr(tixian, 'jine', None) or 0)
amount = base + fee
if amount <= 0:
raise ValueError(f'提现记录{tixian.id}无法确定驳回退款金额')
return amount
def refund_manual_tixianjilu(user_main, tixian):
"""手动提现记录驳回退款(须在 transaction.atomic 内)。"""
leixing = int(tixian.leixing)
if leixing not in (1, 2, 3, 4, 5, 6):
raise ValueError(f'提现记录{tixian.id}提现类型无效: {leixing}')
amount = get_tixianjilu_refund_amount(tixian)
refund_balance(user_main, tixian.leixing, amount)
refund_balance(user_main, leixing, amount)
logger.info(
'手动提现驳回退款 tixian_id=%s yonghuid=%s leixing=%s amount=%s',
tixian.id, tixian.yonghuid, leixing, amount,
)
def record_manual_withdraw_success_stats(user_main, tixian):
@@ -896,33 +934,45 @@ def record_manual_withdraw_success_stats(user_main, tixian):
def refund_balance(user_main, leixing, jine):
"""审核驳回时退还申请扣款额 shenqing_jine(须在 transaction.atomic 内)"""
if leixing == 1:
dashou = UserDashou.objects.select_for_update().get(user=user_main)
dashou.yue += jine
dashou.save()
elif leixing == 2:
guanshi = UserGuanshi.objects.select_for_update().get(user=user_main)
guanshi.yue += jine
guanshi.save()
elif leixing == 3:
zuzhang = UserZuzhang.objects.select_for_update().get(user=user_main)
zuzhang.ketixian_jine += jine
zuzhang.save()
elif leixing == 4:
kaoheguan = UserShenheguan.objects.select_for_update().get(user=user_main)
kaoheguan.yue += jine
kaoheguan.save()
elif leixing == 5:
dashou = UserDashou.objects.select_for_update().get(user=user_main)
dashou.yajin += jine
dashou.save()
elif leixing == 6:
shangjia = UserShangjia.objects.select_for_update().get(user=user_main)
shangjia.yue += jine
shangjia.save()
else:
raise ValueError('无效的提现类型')
"""审核/手动驳回:将申请扣款额全额退回对应身份余额(须在 transaction.atomic 内)"""
amount = _quantize_money(jine)
if amount <= 0:
raise ValueError(f'退款金额必须大于0实际: {amount}')
try:
leixing = int(leixing)
except (TypeError, ValueError):
raise ValueError(f'无效的提现类型: {leixing}')
refund_targets = {
1: (UserDashou, 'yue'),
2: (UserGuanshi, 'yue'),
3: (UserZuzhang, 'ketixian_jine'),
4: (UserShenheguan, 'yue'),
5: (UserDashou, 'yajin'),
6: (UserShangjia, 'yue'),
}
target = refund_targets.get(leixing)
if not target:
raise ValueError(f'无效的提现类型: {leixing}')
Model, field_name = target
try:
profile = Model.objects.select_for_update().get(user=user_main)
except Model.DoesNotExist:
raise ValueError(f'用户{user_main.yonghuid}缺少身份账户(leixing={leixing})')
before = _quantize_money(getattr(profile, field_name))
after = before + amount
setattr(profile, field_name, after)
update_fields = [field_name]
if hasattr(profile, 'update_time'):
update_fields.append('update_time')
profile.save(update_fields=update_fields)
logger.info(
'提现驳回退款 yonghuid=%s leixing=%s amount=%s field=%s before=%s after=%s',
user_main.yonghuid, leixing, amount, field_name, before, after,
)
def deduct_balance(user_main, leixing, jine):

View File

@@ -5725,9 +5725,10 @@ class AdtixianqkView(APIView):
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 获取提现记录
# 使用事务确保数据一致性
with transaction.atomic():
try:
withdrawal = Tixianjilu.objects.get(id=tixian_id)
withdrawal = Tixianjilu.objects.select_for_update().get(id=tixian_id)
except Tixianjilu.DoesNotExist:
return Response({
'code': 404,
@@ -5735,7 +5736,6 @@ class AdtixianqkView(APIView):
'data': None
}, status=status.HTTP_404_NOT_FOUND)
# 检查状态,只有待审核的可以处理
if withdrawal.zhuangtai != 1:
return Response({
'code': 400,
@@ -5743,14 +5743,11 @@ class AdtixianqkView(APIView):
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 使用事务确保数据一致性
with transaction.atomic():
# 更新提现记录
withdrawal.zhuangtai = result
withdrawal.shenheid = request.user.yonghuid
if result == 3: # 拒绝
user_main = UserMain.objects.get(yonghuid=withdrawal.yonghuid)
user_main = UserMain.objects.select_for_update().get(yonghuid=withdrawal.yonghuid)
from yonghu.tixian_shenhe_services import refund_manual_tixianjilu
refund_manual_tixianjilu(user_main, withdrawal)
withdrawal.bhliyou = reason
@@ -12099,8 +12096,9 @@ class KefuWithdrawActionView(APIView):
bhliyou=reason,
bo_hui_ren_id=kefu_user.yonghuid,
)
# 退还申请时扣除的 shenqing_jine全额申请扣款手续费不单独返还
refund_balance(user, audit.leixing, audit.shenqing_jine)
from .tixian_shenhe_services import get_tixianjilu_refund_amount
refund_amount = get_tixianjilu_refund_amount(tixian)
refund_balance(user, audit.leixing, refund_amount)
tixian.shenheid = kefu_user.yonghuid
tixian.bhliyou = reason
tixian.save(update_fields=['shenheid', 'bhliyou', 'update_time'])
@@ -12126,7 +12124,7 @@ class KefuWithdrawActionView(APIView):
raise ValueError(f'提现记录{tixian_id}:自动打款请走自动流程')
try:
user = UserMain.objects.get(yonghuid=tixian.yonghuid)
user = UserMain.objects.select_for_update().get(yonghuid=tixian.yonghuid)
except UserMain.DoesNotExist:
raise ValueError(f'用户{tixian.yonghuid}不存在')