feat: 提现审核支持状态筛选、待收款改状态与驳回返还选项
This commit is contained in:
@@ -74,6 +74,8 @@ from ..tixian_shenhe_services import (
|
||||
close_audit_wechat_failed, query_wechat_transfer_bill,
|
||||
check_shijidaozhang_within_limit,
|
||||
check_dashou_trial_blocks_commission_withdraw,
|
||||
admin_mark_pending_collect_success,
|
||||
admin_reject_pending_or_auditing,
|
||||
WX_STATE_FAIL, WX_STATE_SUCCESS, WX_STATE_WAIT, WX_STATE_CANCELING,
|
||||
)
|
||||
from ..tixian_shenhe_views import process_audit_collect
|
||||
@@ -208,6 +210,7 @@ class KefuWithdrawListView(APIView):
|
||||
page_size = min(max(page_size, 1), 100)
|
||||
|
||||
status_filter = request.data.get('status')
|
||||
zhuangtai_exact = request.data.get('zhuangtai')
|
||||
type_filter = request.data.get('type')
|
||||
payment_filter = request.data.get('payment')
|
||||
search_uid = (request.data.get('search_uid') or '').strip()
|
||||
@@ -230,6 +233,15 @@ class KefuWithdrawListView(APIView):
|
||||
except (TypeError, ValueError):
|
||||
status_val = None
|
||||
|
||||
try:
|
||||
zhuangtai_val = (
|
||||
int(zhuangtai_exact)
|
||||
if zhuangtai_exact is not None and str(zhuangtai_exact).strip() != ''
|
||||
else None
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
zhuangtai_val = None
|
||||
|
||||
try:
|
||||
dakuan_mode_val = int(dakuan_mode_filter) if dakuan_mode_filter is not None and str(dakuan_mode_filter).strip() != '' else None
|
||||
except (TypeError, ValueError):
|
||||
@@ -238,13 +250,17 @@ class KefuWithdrawListView(APIView):
|
||||
# 未传打款方式参数 → 默认手动打款(1)
|
||||
effective_dakuan = dakuan_mode_val if dakuan_mode_val in (1, 2) else 1
|
||||
|
||||
if status_val == 1:
|
||||
# 精确状态优先(1审核中 2成功 3失败 4审核成功 5审核失败 6待收款)
|
||||
if zhuangtai_val in (1, 2, 3, 4, 5, 6):
|
||||
q &= Q(zhuangtai=zhuangtai_val)
|
||||
elif status_val == 1:
|
||||
q &= Q(zhuangtai=1)
|
||||
elif status_val == 2:
|
||||
if effective_dakuan == 2:
|
||||
q &= Q(zhuangtai__in=[2, 3, 4, 5, 6])
|
||||
else:
|
||||
q &= Q(zhuangtai__in=[2, 3])
|
||||
# status_val 为空或 0:不按桶过滤(配合精确状态或看全部)
|
||||
|
||||
if type_filter is not None and str(type_filter).strip() != '':
|
||||
try:
|
||||
@@ -265,8 +281,9 @@ class KefuWithdrawListView(APIView):
|
||||
if search_uid:
|
||||
q &= Q(yonghuid=search_uid)
|
||||
|
||||
# 按申请日筛选(CreateTime),不是处理日
|
||||
if date_filter:
|
||||
q &= Q(UpdateTime__date=date_filter)
|
||||
q &= Q(CreateTime__date=date_filter)
|
||||
|
||||
if min_amount is not None and str(min_amount).strip() != '':
|
||||
try:
|
||||
@@ -341,6 +358,8 @@ class KefuWithdrawListView(APIView):
|
||||
meta = audit_meta_map.get(shenhe_jilu_id, {}) if shenhe_jilu_id else {}
|
||||
if shenhe_jilu_id and not shenhe_danhao:
|
||||
shenhe_danhao = meta.get('shenhe_danhao', '')
|
||||
create_time = item.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if item.CreateTime else ''
|
||||
update_time = item.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if item.UpdateTime else ''
|
||||
list_data.append({
|
||||
'id': item.id,
|
||||
'shenhe_jilu_id': shenhe_jilu_id,
|
||||
@@ -360,8 +379,11 @@ class KefuWithdrawListView(APIView):
|
||||
'dakuan_mode': self._resolve_row_dakuan_mode(item, audit_mode_map, shenhe_field_ok),
|
||||
'shenheid': item.shenheid or '',
|
||||
'bhliyou': item.bhliyou or '',
|
||||
'CreateTime': item.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if item.CreateTime else '',
|
||||
'UpdateTime': item.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if item.UpdateTime else '',
|
||||
# 同时返回两种命名,兼容前后端
|
||||
'CreateTime': create_time,
|
||||
'UpdateTime': update_time,
|
||||
'create_time': create_time,
|
||||
'update_time': update_time,
|
||||
})
|
||||
|
||||
from jituan.services.club_user_access import list_response_meta
|
||||
@@ -465,7 +487,9 @@ class KefuWithdrawDetailView(APIView):
|
||||
except User.DoesNotExist:
|
||||
user = None
|
||||
|
||||
# 构建基础返回数据(字段必须与前端一致)
|
||||
# 构建基础返回数据(字段必须与前端一致;时间同时给 snake_case)
|
||||
create_time = record.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if record.CreateTime else ''
|
||||
update_time = record.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if record.UpdateTime else ''
|
||||
data = {
|
||||
'id': record.id,
|
||||
'yonghuid': record.yonghuid,
|
||||
@@ -480,10 +504,29 @@ class KefuWithdrawDetailView(APIView):
|
||||
'fangshi': record.fangshi,
|
||||
'shenheid': record.shenheid or '',
|
||||
'bhliyou': record.bhliyou or '',
|
||||
'CreateTime': record.CreateTime,
|
||||
'UpdateTime': record.UpdateTime,
|
||||
'CreateTime': create_time,
|
||||
'UpdateTime': update_time,
|
||||
'create_time': create_time,
|
||||
'update_time': update_time,
|
||||
'shenhe_jilu_id': getattr(record, 'shenhe_jilu_id', None),
|
||||
'shenhe_danhao': getattr(record, 'shenhe_danhao', None) or '',
|
||||
'dakuan_mode': 2 if getattr(record, 'shenhe_jilu_id', None) else 1,
|
||||
'shouxufei': '0.00',
|
||||
'shijidaozhang': str(record.jine) if record.jine else '0.00',
|
||||
}
|
||||
|
||||
sid = getattr(record, 'shenhe_jilu_id', None)
|
||||
if sid:
|
||||
try:
|
||||
meta_map = load_audit_meta_map([sid])
|
||||
meta = meta_map.get(sid) or {}
|
||||
data['shenhe_danhao'] = data['shenhe_danhao'] or meta.get('shenhe_danhao', '')
|
||||
data['shouxufei'] = meta.get('shouxufei', '0.00')
|
||||
data['shijidaozhang'] = meta.get('shijidaozhang') or data['shijidaozhang']
|
||||
data['dakuan_mode'] = meta.get('dakuan_mode', 2) or 2
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 根据提现类型添加扩展信息
|
||||
if user:
|
||||
if record.leixing == 1: # 打手
|
||||
@@ -536,13 +579,19 @@ class KefuWithdrawActionView(APIView):
|
||||
单条:{ phone, action, tixian_id, yonghuid, leixing, reason? }
|
||||
批量:{ phone, action, batch_list: [{tixian_id, yonghuid, leixing}, ...], reason? }
|
||||
|
||||
驳回(action=2)可选(默认 false,与现网一致):
|
||||
refund_fee: 是否退还手续费
|
||||
apply_entry_freeze: 退回余额是否走入账冻结(按俱乐部配置;押金/商家无效)
|
||||
action:
|
||||
1 = 同意(审核中→待收款)
|
||||
2 = 驳回(审核中/待收款→审核失败)
|
||||
3 = 标记已收款(仅待收款→提现成功)
|
||||
|
||||
自动打款(dakuan_mode=2):同意→6待收款;拒绝→5+驳回原因+退款,不更新平台收支/每日统计
|
||||
手动打款(dakuan_mode=1):原逻辑不变
|
||||
状态要求:Tixianjilu.zhuangtai=1 且 TixianShenheJilu.zhuangtai=1(审核中),其他状态一律拒绝
|
||||
驳回(action=2)可选:
|
||||
refund_principal: 是否返还到账本金(默认 true)
|
||||
refund_fee: 是否退还手续费(默认 false)
|
||||
apply_entry_freeze: 退回余额是否走入账冻结(默认 false)
|
||||
三者均可关:只改状态不退钱
|
||||
|
||||
自动打款(dakuan_mode=2):同意→6待收款;拒绝→5;标记已收款→2
|
||||
手动打款(dakuan_mode=1):原逻辑不变(仅审核中)
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
parser_classes = [JSONParser]
|
||||
@@ -622,7 +671,8 @@ class KefuWithdrawActionView(APIView):
|
||||
return [item], None
|
||||
|
||||
def _process_auto_item(self, request, item, action, reason, kefu_user,
|
||||
refund_fee=False, apply_entry_freeze=False):
|
||||
refund_fee=False, apply_entry_freeze=False,
|
||||
refund_principal=True, with_accounting=True):
|
||||
"""自动打款单条处理(须在 transaction.atomic 内调用)"""
|
||||
tixian_id = item['tixian_id']
|
||||
req_yonghuid = item['yonghuid']
|
||||
@@ -645,8 +695,6 @@ class KefuWithdrawActionView(APIView):
|
||||
raise ValueError(f'提现记录{tixian_id}:用户ID不匹配')
|
||||
if tixian.leixing != req_leixing:
|
||||
raise ValueError(f'提现记录{tixian_id}:提现类型不匹配')
|
||||
if tixian.zhuangtai != 1:
|
||||
raise ValueError(f'提现记录{tixian_id}:非审核中状态,无法处理')
|
||||
if not tixian.shenhe_jilu_id:
|
||||
raise ValueError(f'提现记录{tixian_id}:非自动打款审核记录')
|
||||
|
||||
@@ -659,53 +707,59 @@ class KefuWithdrawActionView(APIView):
|
||||
raise ValueError(f'提现记录{tixian_id}:审核记录用户ID不匹配')
|
||||
if audit.leixing != req_leixing:
|
||||
raise ValueError(f'提现记录{tixian_id}:审核记录提现类型不匹配')
|
||||
if audit.zhuangtai != 1:
|
||||
raise ValueError(f'提现记录{tixian_id}:审核记录非审核中状态')
|
||||
|
||||
try:
|
||||
user = User.objects.select_for_update().get(UserUID=req_yonghuid)
|
||||
except User.DoesNotExist:
|
||||
raise ValueError(f'用户{req_yonghuid}不存在')
|
||||
|
||||
if action == 1:
|
||||
limit_ok, limit_msg = check_shijidaozhang_within_limit(audit.shijidaozhang)
|
||||
if not limit_ok:
|
||||
sync_audit_and_jilu_status(
|
||||
audit, 5,
|
||||
bhliyou=limit_msg,
|
||||
bo_hui_ren_id=kefu_user.UserUID,
|
||||
)
|
||||
# 超限自动驳回:保持现网(不退手续费、不走冻结)
|
||||
refund_balance(user, audit.leixing, audit.shijidaozhang)
|
||||
tixian.shenheid = kefu_user.UserUID
|
||||
tixian.bhliyou = limit_msg
|
||||
tixian.save(update_fields=['shenheid', 'bhliyou', 'UpdateTime'])
|
||||
raise ValueError(f'提现记录{tixian_id}:{limit_msg},已自动驳回并退款')
|
||||
sync_audit_and_jilu_status(audit, 6, shenhe_ren_id=kefu_user.UserUID)
|
||||
tixian.shenheid = kefu_user.UserUID
|
||||
tixian.save(update_fields=['shenheid', 'UpdateTime'])
|
||||
else:
|
||||
sync_audit_and_jilu_status(
|
||||
audit, 5,
|
||||
bhliyou=reason,
|
||||
bo_hui_ren_id=kefu_user.UserUID,
|
||||
cur = int(tixian.zhuangtai)
|
||||
kefu_uid = getattr(kefu_user, 'UserUID', '') or ''
|
||||
|
||||
# action=3:待收款 → 已收款
|
||||
if action == 3:
|
||||
if cur != 6 or int(audit.zhuangtai) != 6:
|
||||
raise ValueError(f'提现记录{tixian_id}:仅待收款可标记已收款')
|
||||
admin_mark_pending_collect_success(
|
||||
audit, tixian, kefu_uid, with_accounting=with_accounting,
|
||||
)
|
||||
refund_withdraw_reject(
|
||||
user,
|
||||
audit.leixing,
|
||||
shijidaozhang=audit.shijidaozhang,
|
||||
shouxufei=audit.shouxufei,
|
||||
return
|
||||
|
||||
# action=2:审核中/待收款 → 驳回
|
||||
if action == 2:
|
||||
if cur not in (1, 6) or int(audit.zhuangtai) not in (1, 6):
|
||||
raise ValueError(f'提现记录{tixian_id}:仅审核中/待收款可驳回')
|
||||
admin_reject_pending_or_auditing(
|
||||
audit, tixian, user, kefu_uid, reason,
|
||||
refund_principal=refund_principal,
|
||||
refund_fee=refund_fee,
|
||||
apply_entry_freeze=apply_entry_freeze,
|
||||
biz_id=f'withdraw_reject:auto:{tixian_id}',
|
||||
freeze_reason=f'提现驳回退款 #{tixian_id}',
|
||||
)
|
||||
tixian.shenheid = kefu_user.UserUID
|
||||
tixian.bhliyou = reason
|
||||
tixian.save(update_fields=['shenheid', 'bhliyou', 'UpdateTime'])
|
||||
return
|
||||
|
||||
# action=1:仅审核中 → 待收款
|
||||
if action == 1:
|
||||
if cur != 1 or int(audit.zhuangtai) != 1:
|
||||
raise ValueError(f'提现记录{tixian_id}:非审核中状态,无法同意')
|
||||
limit_ok, limit_msg = check_shijidaozhang_within_limit(audit.shijidaozhang)
|
||||
if not limit_ok:
|
||||
admin_reject_pending_or_auditing(
|
||||
audit, tixian, user, kefu_uid, limit_msg,
|
||||
refund_principal=True,
|
||||
refund_fee=False,
|
||||
apply_entry_freeze=False,
|
||||
)
|
||||
raise ValueError(f'提现记录{tixian_id}:{limit_msg},已自动驳回并退款')
|
||||
sync_audit_and_jilu_status(audit, 6, shenhe_ren_id=kefu_uid)
|
||||
tixian.shenheid = kefu_uid
|
||||
tixian.save(update_fields=['shenheid', 'UpdateTime'])
|
||||
return
|
||||
|
||||
raise ValueError(f'不支持的 action={action}')
|
||||
|
||||
def _process_manual_item(self, request, item, action, reason, phone,
|
||||
refund_fee=False, apply_entry_freeze=False):
|
||||
refund_fee=False, apply_entry_freeze=False,
|
||||
refund_principal=True):
|
||||
"""手动打款单条处理(原逻辑,须在 transaction.atomic 内调用)"""
|
||||
tixian_id = item['tixian_id']
|
||||
try:
|
||||
@@ -747,22 +801,25 @@ class KefuWithdrawActionView(APIView):
|
||||
except Exception as e:
|
||||
logger.error(f'更新收支记录失败: {e}')
|
||||
else:
|
||||
fee = estimate_manual_shouxufei(user, tixian.leixing, tixian.jine) if refund_fee else Decimal('0')
|
||||
try:
|
||||
refund_withdraw_reject(
|
||||
user,
|
||||
tixian.leixing,
|
||||
shijidaozhang=tixian.jine,
|
||||
shouxufei=fee,
|
||||
refund_fee=refund_fee,
|
||||
apply_entry_freeze=apply_entry_freeze,
|
||||
biz_id=f'withdraw_reject:manual:{tixian_id}',
|
||||
freeze_reason=f'提现驳回退款 #{tixian_id}',
|
||||
)
|
||||
except ValueError as e:
|
||||
raise ValueError(str(e))
|
||||
# 先改状态,再按选项退款
|
||||
tixian.zhuangtai = 3
|
||||
tixian.bhliyou = reason
|
||||
if refund_principal or refund_fee:
|
||||
fee = estimate_manual_shouxufei(user, tixian.leixing, tixian.jine) if refund_fee else Decimal('0')
|
||||
principal = tixian.jine if refund_principal else Decimal('0')
|
||||
try:
|
||||
refund_withdraw_reject(
|
||||
user,
|
||||
tixian.leixing,
|
||||
shijidaozhang=principal,
|
||||
shouxufei=fee,
|
||||
refund_fee=refund_fee,
|
||||
apply_entry_freeze=apply_entry_freeze,
|
||||
biz_id=f'withdraw_reject:manual:{tixian_id}',
|
||||
freeze_reason=f'提现驳回退款 #{tixian_id}',
|
||||
)
|
||||
except ValueError as e:
|
||||
raise ValueError(str(e))
|
||||
|
||||
tixian.shenheid = phone
|
||||
tixian.UpdateTime = timezone.now()
|
||||
@@ -776,13 +833,19 @@ class KefuWithdrawActionView(APIView):
|
||||
reason = request.data.get('reason', '').strip()
|
||||
refund_fee = self._parse_bool(request.data.get('refund_fee'), False)
|
||||
apply_entry_freeze = self._parse_bool(request.data.get('apply_entry_freeze'), False)
|
||||
# 驳回默认返还本金;可显式关掉只改状态
|
||||
refund_principal = self._parse_bool(request.data.get('refund_principal'), True)
|
||||
with_accounting = self._parse_bool(request.data.get('with_accounting'), True)
|
||||
|
||||
try:
|
||||
action = int(action_raw)
|
||||
except (TypeError, ValueError):
|
||||
return Response({'code': 400, 'msg': 'action 参数必须为数字'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if action not in [1, 2]:
|
||||
return Response({'code': 400, 'msg': 'action 必须为1(同意)或2(拒绝)'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if action not in [1, 2, 3]:
|
||||
return Response(
|
||||
{'code': 400, 'msg': 'action 必须为1(同意)/2(驳回)/3(标记已收款)'},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
if action == 2 and not reason:
|
||||
return Response({'code': 400, 'msg': '拒绝提现时理由不能为空'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@@ -823,14 +886,19 @@ class KefuWithdrawActionView(APIView):
|
||||
request, item, action, reason, kefu_user,
|
||||
refund_fee=refund_fee if action == 2 else False,
|
||||
apply_entry_freeze=apply_entry_freeze if action == 2 else False,
|
||||
refund_principal=refund_principal if action == 2 else True,
|
||||
with_accounting=with_accounting if action == 3 else True,
|
||||
)
|
||||
else:
|
||||
if action == 3:
|
||||
raise ValueError(f'提现记录{tixian_id}:手动打款无「待收款」,不能标记已收款')
|
||||
if is_batch:
|
||||
raise ValueError(f'提现记录{tixian_id}:批量操作仅支持自动打款记录')
|
||||
self._process_manual_item(
|
||||
request, item, action, reason, phone,
|
||||
refund_fee=refund_fee if action == 2 else False,
|
||||
apply_entry_freeze=apply_entry_freeze if action == 2 else False,
|
||||
refund_principal=refund_principal if action == 2 else True,
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
|
||||
Reference in New Issue
Block a user