From 8dae04c32cccc66252a85b833e2341fa22ea2072 Mon Sep 17 00:00:00 2001 From: XingQue Date: Tue, 23 Jun 2026 22:34:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8E=E5=8F=B0=E8=80=83=E6=A0=B8?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E5=88=97=E8=A1=A8=E7=BB=9F=E8=AE=A1=E4=B8=8E?= =?UTF-8?q?=E9=80=80=E5=9B=9E=E5=BE=85=E5=AE=A1=E6=A0=B8=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/urls.py | 4 +- backend/view.py | 368 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 370 insertions(+), 2 deletions(-) diff --git a/backend/urls.py b/backend/urls.py index d4566eb..4fa641e 100644 --- a/backend/urls.py +++ b/backend/urls.py @@ -14,7 +14,7 @@ from .view import (GetRolePermissionView, FineApplyView, PunishDashouView, FaKuanTongJiView, FaKuanLieBiaoView, FaKuanChuLiView, FaKuanChuangJianView, HqbkxxView,BkxgView,CaiwuView,CwhybkhqView, HybkjtsjView, SzxxView, CwddhqlxView, CwhqjtddsjView, - CwqtczhqView, KhpzhqView, ChzsgcView,KhgglView, ShgxgsjView, ZxkfghdsView) + CwqtczhqView, KhpzhqView, ChzsgcView,KhgglView, ShgxgsjView, KhjlglView, KhjlczView, ZxkfghdsView) urlpatterns = [ path('gljshq', GetRolePermissionView.as_view(), name='获取就角色信息'), @@ -103,6 +103,8 @@ urlpatterns = [ path('khggl', KhgglView.as_view(), name='获取考核管信息'), path('shgxgsj', ShgxgsjView.as_view(), name='修改考核管数据'), + path('khjlgl', KhjlglView.as_view(), name='考核记录列表与统计'), + path('khjlcz', KhjlczView.as_view(), name='考核记录操作'), path('zxkfghds', ZxkfghdsView.as_view(), name='链接换打手或重新下单'), diff --git a/backend/view.py b/backend/view.py index f439479..c6f5a95 100644 --- a/backend/view.py +++ b/backend/view.py @@ -97,7 +97,7 @@ from config.models import ( ## rank from rank.models import ( KaoheguanBankuai, Bankuai, Chenghao, KaoheCishuFeiyong, - YonghuChenghao + YonghuChenghao, ShenheJilu, KaoheJiluFeiyong, KaoheJujueJilu ) # 序列化器 @@ -8836,6 +8836,372 @@ class ZxkfghdsView(APIView): return '游戏订单' +# ==================== 考核记录管理 ==================== + +SHENHE_ZHUANGTAI_MAP = { + 0: '考核中', + 1: '已通过', + 2: '未通过', + 3: '待审核', +} + + +def _batch_user_profile_map(user_uids): + """批量获取用户头像、昵称、手机号""" + uids = list({u for u in user_uids if u}) + if not uids: + return {} + users = User.query.filter(UserUID__in=uids) + dashou_nicks = { + d.user.UserUID: d.nicheng + for d in UserDashou.query.filter(user__UserUID__in=uids).select_related('user') + } + result = {} + for u in users: + result[u.UserUID] = { + 'yonghuid': u.UserUID, + 'avatar': u.Avatar or '', + 'phone': u.Phone or '', + 'nicheng': dashou_nicks.get(u.UserUID) or u.UserName or u.Phone or u.UserUID, + } + return result + + +def _build_shenhe_jilu_filters(request_data): + """构造 ShenheJilu 查询条件(列表与统计共用)""" + qs = ShenheJilu.query.select_related('bankuai', 'chenghao') + + jilu_id = (request_data.get('jilu_id') or '').strip() + if jilu_id: + qs = qs.filter(jilu_id=jilu_id) + + zhuangtai = request_data.get('zhuangtai') + if zhuangtai is not None and zhuangtai != '': + qs = qs.filter(zhuangtai=int(zhuangtai)) + + assessed = request_data.get('assessed') + if assessed == 'done': + qs = qs.filter(zhuangtai__in=[1, 2]) + elif assessed == 'undone': + qs = qs.filter(zhuangtai__in=[0, 3]) + + bankuai_id = request_data.get('bankuai_id') + if bankuai_id not in (None, ''): + qs = qs.filter(bankuai_id=int(bankuai_id)) + + chenghao_id = request_data.get('chenghao_id') + if chenghao_id not in (None, ''): + qs = qs.filter(chenghao_id=int(chenghao_id)) + + cishu = request_data.get('cishu') + if cishu not in (None, ''): + qs = qs.filter(cishu=int(cishu)) + + dashou_kw = (request_data.get('dashou_keyword') or '').strip() + if dashou_kw: + dashou_uids = list( + User.query.filter( + Q(UserUID=dashou_kw) | + Q(UserName__icontains=dashou_kw) | + Q(Phone__icontains=dashou_kw) | + Q(DashouProfile__nicheng__icontains=dashou_kw) + ).values_list('UserUID', flat=True) + ) + if dashou_uids: + qs = qs.filter(shenqingren_id__in=dashou_uids) + else: + qs = qs.none() + + kg_kw = (request_data.get('kaoheguan_keyword') or '').strip() + if kg_kw: + kg_uids = list( + User.query.filter( + Q(UserUID=kg_kw) | + Q(UserName__icontains=kg_kw) | + Q(Phone__icontains=kg_kw) | + Q(DashouProfile__nicheng__icontains=kg_kw) + ).values_list('UserUID', flat=True) + ) + if kg_uids: + qs = qs.filter(shenheguan_id__in=kg_uids) + else: + qs = qs.none() + + start_time = (request_data.get('start_time') or '').strip() + end_time = (request_data.get('end_time') or '').strip() + if start_time: + qs = qs.filter(CreateTime__gte=start_time) + if end_time: + qs = qs.filter(CreateTime__lte=end_time) + + has_kaoheguan = request_data.get('has_kaoheguan') + if has_kaoheguan in (True, 'true', '1', 1): + qs = qs.exclude(shenheguan_id__isnull=True).exclude(shenheguan_id='') + elif has_kaoheguan in (False, 'false', '0', 0): + qs = qs.filter(Q(shenheguan_id__isnull=True) | Q(shenheguan_id='')) + + return qs + + +def _serialize_shenhe_jilu_row(jl, profile_map, fee_map=None, reject_count_map=None): + dashou = profile_map.get(jl.shenqingren_id, { + 'yonghuid': jl.shenqingren_id, + 'avatar': '', + 'phone': '', + 'nicheng': jl.shenqingren_id, + }) + kaoheguan = None + if jl.shenheguan_id: + kaoheguan = profile_map.get(jl.shenheguan_id, { + 'yonghuid': jl.shenheguan_id, + 'avatar': '', + 'phone': '', + 'nicheng': jl.shenheguan_id, + }) + kaoheguan['game_id'] = jl.shenheguan_youxi_id or '' + + current_fee = 0.0 + if fee_map is not None: + current_fee = fee_map.get((jl.jilu_id, jl.cishu), 0.0) + else: + fd = KaoheJiluFeiyong.query.filter(jilu_id=jl.jilu_id, cishu=jl.cishu).first() + if fd: + current_fee = float(fd.jine) + + reject_count = 0 + if reject_count_map is not None: + reject_count = reject_count_map.get(jl.jilu_id, 0) + + return { + 'jilu_id': jl.jilu_id, + 'zhuangtai': jl.zhuangtai, + 'zhuangtai_text': SHENHE_ZHUANGTAI_MAP.get(jl.zhuangtai, str(jl.zhuangtai)), + 'cishu': jl.cishu, + 'jiaofei_jine': float(jl.jiaofei_jine or 0), + 'current_fee': current_fee, + 'bankuai_id': jl.bankuai.bankuai_id if jl.bankuai else None, + 'bankuai_name': jl.bankuai.mingcheng if jl.bankuai else '', + 'chenghao_id': jl.chenghao.id if jl.chenghao else None, + 'chenghao_name': jl.chenghao.mingcheng if jl.chenghao else '', + 'chenghao_icon': jl.chenghao.tubiao_url if jl.chenghao else '', + 'guize_neirong': jl.guize_neirong or '', + 'dashou': { + **dashou, + 'game_id': jl.dashou_youxi_id or '', + 'beizhu': jl.dashou_beizhu or '', + }, + 'kaoheguan': kaoheguan, + 'reject_count': reject_count, + 'CreateTime': jl.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if jl.CreateTime else '', + 'UpdateTime': jl.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if jl.UpdateTime else '', + } + + +class KhjlglView(APIView): + """ + 考核记录列表 + 统计 + POST /houtai/khjlgl + """ + permission_classes = [IsAuthenticated] + + def post(self, request): + try: + username_from_frontend = request.data.get('phone', None) + kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend) + if kefu_obj is None: + return permissions + if 'kaohepeizhi' not in permissions: + return Response({'code': 403, 'msg': '无考核管理权限'}, status=403) + + page = int(request.data.get('page', 1)) + page_size = int(request.data.get('page_size', 20)) + if page < 1: + page = 1 + if page_size < 1: + page_size = 20 + if page_size > 100: + page_size = 100 + + qs = _build_shenhe_jilu_filters(request.data) + + stats_qs = qs + agg = stats_qs.aggregate( + total_fee=Sum('jiaofei_jine'), + ) + tag_rows = stats_qs.values( + 'chenghao_id', 'chenghao__mingcheng' + ).annotate( + total=Count('jilu_id'), + passed=Count('jilu_id', filter=Q(zhuangtai=1)), + failed=Count('jilu_id', filter=Q(zhuangtai=2)), + in_progress=Count('jilu_id', filter=Q(zhuangtai=0)), + pending=Count('jilu_id', filter=Q(zhuangtai=3)), + ).order_by('-total') + + stats = { + 'total': stats_qs.count(), + 'passed': stats_qs.filter(zhuangtai=1).count(), + 'failed': stats_qs.filter(zhuangtai=2).count(), + 'in_progress': stats_qs.filter(zhuangtai=0).count(), + 'pending': stats_qs.filter(zhuangtai=3).count(), + 'total_fee': float(agg['total_fee'] or 0), + 'tag_stats': [ + { + 'chenghao_id': r['chenghao_id'], + 'chenghao_name': r['chenghao__mingcheng'] or '未知标签', + 'total': r['total'], + 'passed': r['passed'], + 'failed': r['failed'], + 'in_progress': r['in_progress'], + 'pending': r['pending'], + } + for r in tag_rows if r['chenghao_id'] + ], + } + + total = qs.count() + start = (page - 1) * page_size + rows = qs.order_by('-CreateTime')[start:start + page_size] + + uids = [] + jilu_ids = [] + for jl in rows: + uids.append(jl.shenqingren_id) + if jl.shenheguan_id: + uids.append(jl.shenheguan_id) + jilu_ids.append(jl.jilu_id) + profile_map = _batch_user_profile_map(uids) + + fee_pairs = [(jl.jilu_id, jl.cishu) for jl in rows] + fee_map = {} + if fee_pairs: + jids = [p[0] for p in fee_pairs] + for fd in KaoheJiluFeiyong.query.filter(jilu_id__in=jids): + fee_map[(fd.jilu_id, fd.cishu)] = float(fd.jine) + + reject_counts = dict( + KaoheJujueJilu.query.filter(jilu_id__in=jilu_ids) + .values('jilu_id') + .annotate(cnt=Count('id')) + .values_list('jilu_id', 'cnt') + ) if jilu_ids else {} + + data_list = [ + _serialize_shenhe_jilu_row(jl, profile_map, fee_map, reject_counts) + for jl in rows + ] + + all_bankuai = list(Bankuai.query.values('bankuai_id', 'mingcheng')) + all_tags = list( + Chenghao.query.filter(leixing='dashou') + .values('id', 'mingcheng', 'bankuai_id') + .order_by('mingcheng') + ) + + return Response({ + 'code': 0, + 'msg': 'ok', + 'data': { + 'total': total, + 'page': page, + 'page_size': page_size, + 'list': data_list, + 'stats': stats, + 'all_bankuai': all_bankuai, + 'all_tags': all_tags, + }, + }) + except Exception as e: + logger.exception('KhjlglView 接口错误') + return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500) + + +class KhjlczView(APIView): + """ + 考核记录操作 / 详情 + POST /houtai/khjlcz + action: + - get_detail: 获取单条详情(含缴费明细、拒绝历史) + - to_pending: 考核中 → 待审核(取消指定考核官,与小程序 transfer 一致) + """ + permission_classes = [IsAuthenticated] + + def post(self, request): + try: + username_from_frontend = request.data.get('phone', None) + kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend) + if kefu_obj is None: + return permissions + if 'kaohepeizhi' not in permissions: + return Response({'code': 403, 'msg': '无考核管理权限'}, status=403) + + action = request.data.get('action') + jilu_id = (request.data.get('jilu_id') or '').strip() + if not jilu_id: + return Response({'code': 1, 'msg': '缺少记录ID'}, status=400) + + if action == 'get_detail': + try: + jl = ShenheJilu.query.select_related('bankuai', 'chenghao').get(jilu_id=jilu_id) + except ShenheJilu.DoesNotExist: + return Response({'code': 1, 'msg': '记录不存在'}, status=404) + + uids = [jl.shenqingren_id] + if jl.shenheguan_id: + uids.append(jl.shenheguan_id) + profile_map = _batch_user_profile_map(uids) + + fee_list = [ + { + 'cishu': f.cishu, + 'jine': float(f.jine), + } + for f in KaoheJiluFeiyong.query.filter(jilu_id=jilu_id).order_by('cishu') + ] + reject_list = [] + for r in KaoheJujueJilu.query.filter(jilu_id=jilu_id).order_by('-CreateTime'): + kg_prof = _batch_user_profile_map([r.shenheguan_id]).get(r.shenheguan_id, {}) + reject_list.append({ + 'cishu': r.cishu, + 'shenheguan_id': r.shenheguan_id, + 'shenheguan_nicheng': kg_prof.get('nicheng', r.shenheguan_id), + 'yuanyin': r.yuanyin, + 'CreateTime': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '', + }) + + record = _serialize_shenhe_jilu_row(jl, profile_map) + record['fee_list'] = fee_list + record['reject_list'] = reject_list + return Response({'code': 0, 'msg': 'ok', 'data': record}) + + if action == 'to_pending': + with transaction.atomic(): + jl = ShenheJilu.objects.select_for_update().filter(jilu_id=jilu_id).first() + if not jl: + return Response({'code': 1, 'msg': '记录不存在'}, status=404) + if jl.zhuangtai != 0: + return Response({'code': 1, 'msg': '仅「考核中」的记录可退回待审核'}, status=400) + + old_kg_id = jl.shenheguan_id + jl.shenheguan_id = None + jl.shenheguan_youxi_id = None + jl.zhuangtai = 3 + jl.save(update_fields=['shenheguan_id', 'shenheguan_youxi_id', 'zhuangtai', 'UpdateTime']) + + if old_kg_id: + UserShenheguan.query.filter(user__UserUID=old_kg_id).update(shenhe_zhuangtai=0) + + return Response({'code': 0, 'msg': '已退回待审核,考核官指定已取消'}) + + return Response({'code': 1, 'msg': '无效的操作类型'}, status=400) + + except Exception as e: + logger.exception('KhjlczView 接口错误') + return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500) + + + +