diff --git a/yonghu/migrations/0022_backfill_tixianjilu_shenqing_jine.py b/yonghu/migrations/0022_backfill_tixianjilu_shenqing_jine.py new file mode 100644 index 0000000..150e0e4 --- /dev/null +++ b/yonghu/migrations/0022_backfill_tixianjilu_shenqing_jine.py @@ -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), + ] diff --git a/yonghu/tixian_shenhe_services.py b/yonghu/tixian_shenhe_services.py index ed8f41b..a3f57c3 100644 --- a/yonghu/tixian_shenhe_services.py +++ b/yonghu/tixian_shenhe_services.py @@ -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): diff --git a/yonghu/views.py b/yonghu/views.py index 18b158a..7e0b0b5 100644 --- a/yonghu/views.py +++ b/yonghu/views.py @@ -5725,32 +5725,29 @@ class AdtixianqkView(APIView): 'data': None }, status=status.HTTP_400_BAD_REQUEST) - # 获取提现记录 - try: - withdrawal = Tixianjilu.objects.get(id=tixian_id) - except Tixianjilu.DoesNotExist: - return Response({ - 'code': 404, - 'message': '提现记录不存在', - 'data': None - }, status=status.HTTP_404_NOT_FOUND) - - # 检查状态,只有待审核的可以处理 - if withdrawal.zhuangtai != 1: - return Response({ - 'code': 400, - 'message': '该提现申请已被处理', - 'data': None - }, status=status.HTTP_400_BAD_REQUEST) - # 使用事务确保数据一致性 with transaction.atomic(): - # 更新提现记录 + try: + withdrawal = Tixianjilu.objects.select_for_update().get(id=tixian_id) + except Tixianjilu.DoesNotExist: + return Response({ + 'code': 404, + 'message': '提现记录不存在', + 'data': None + }, status=status.HTTP_404_NOT_FOUND) + + if withdrawal.zhuangtai != 1: + return Response({ + 'code': 400, + 'message': '该提现申请已被处理', + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + 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}不存在')