745 lines
30 KiB
Python
745 lines
30 KiB
Python
import hmac
|
||
import threading
|
||
import traceback
|
||
import uuid
|
||
import hashlib
|
||
import xmltodict
|
||
import time
|
||
import logging
|
||
import requests
|
||
import os
|
||
import secrets
|
||
import random
|
||
import string
|
||
from calendar import monthrange
|
||
from decimal import Decimal
|
||
from datetime import date, datetime, timedelta
|
||
from django.utils import timezone
|
||
from django.conf import settings
|
||
from django.core.exceptions import ObjectDoesNotExist
|
||
from django.core.paginator import Paginator
|
||
from django.db import models, transaction, IntegrityError
|
||
from django.db.models import Q, Count, Sum, F, Exists, OuterRef
|
||
from gvsdsdk.fluent import db, func, FQ
|
||
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
|
||
from django.contrib.auth.hashers import make_password
|
||
from django.views.decorators.csrf import csrf_exempt
|
||
from django.utils.decorators import method_decorator
|
||
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import status
|
||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
|
||
|
||
# 工具类导入
|
||
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
|
||
from backend.utils import (
|
||
update_dashou_daily_by_action,
|
||
update_shangjia_daily
|
||
)
|
||
from jituan.services.club_context import filter_club_char_field, filter_queryset_by_club, filter_user_related_by_club, filter_user_qs_by_club, orders_for_request
|
||
from jituan.services.catalog_scope import block_non_group_catalog_scope
|
||
from jituan.services.club_penalty import (
|
||
filter_penalty_qs, resolve_penalty_club_id, filter_gsfenhong_qs,
|
||
forbid_penalty_out_of_scope, resolve_penalty_record_club_id,
|
||
)
|
||
from jituan.services.club_user_access import forbid_if_user_out_of_scope, list_response_meta
|
||
from shop.utils import update_dianpu_daily_stat
|
||
from products.utils import update_shangpin_daily_stat
|
||
from orders.notice_tasks import dingdan_guangbo
|
||
from ..utils import (
|
||
verify_kefu_permission,
|
||
PERMISSION_TO_SHENFEN,
|
||
SHENFEN_PROFILE_MAP,
|
||
has_fadan_view_permission,
|
||
has_merchant_order_permission,
|
||
check_fadan_permission,
|
||
pick_order_id,
|
||
pick_penalty_create_params,
|
||
write_xiugai_log,
|
||
XIUGAI_LEIXING_DASHOU,
|
||
XIUGAI_LEIXING_GUANSHI,
|
||
XIUGAI_LEIXING_SHANGJIA,
|
||
XIUGAI_LEIXING_ZUZHANG,
|
||
XIUGAI_LEIXING_LABEL,
|
||
)
|
||
|
||
# models 集中导入
|
||
## gvsdsdk RBAC 模型(使用 PascalCase 字段名,匹配实际数据库表结构)
|
||
from gvsdsdk.models import Role, Permission, RolePermission, UserRole
|
||
## backend
|
||
from backend.models import WithdrawalDailyStats
|
||
|
||
## users
|
||
from users.models import (
|
||
UserGuanshi, UserBoss, UserZuzhang,
|
||
UserKefu, UserDashou, Xiugaijilu, UserShangjia, UserShenheguan
|
||
)
|
||
from users.business_models import User
|
||
|
||
## orders
|
||
from orders.models import (
|
||
CommissionRate, PlayerDeliveryImage, PenaltyRecord,
|
||
OrderPlayerHistory, Order, RefundRecord,
|
||
MerchantOrderExt, Penalty, PenaltyAppealImage, PenaltyBonus
|
||
)
|
||
|
||
## shop
|
||
from shop.models import Dianpu, DianpuMorenPeizhi, YonghuPingzheng, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi
|
||
|
||
## products
|
||
from products.models import (
|
||
Shangpin, ShangpinLeixing, ShangpinZhuanqu, Huiyuan,
|
||
DuociFenhong, Czjilu, Huiyuangoumai, Gsfenhong
|
||
)
|
||
|
||
## config
|
||
from config.models import (
|
||
WithdrawConfig, ShangjiaLianjie, AccountPermission,
|
||
TixianQuotaDefault,
|
||
DailyIncomeStat, DailyPayoutStat, PopupPage, PopupConfig, PopupImage
|
||
)
|
||
|
||
## rank
|
||
from rank.models import (
|
||
KaoheguanBankuai, Bankuai, Chenghao, KaoheCishuFeiyong,
|
||
YonghuChenghao, ShenheJilu, KaoheJiluFeiyong, KaoheJujueJilu
|
||
)
|
||
|
||
# 序列化器
|
||
from ..serializers import PopupPageSerializer
|
||
|
||
# 全局常量、日志对象
|
||
logger = logging.getLogger('houtai')
|
||
SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd']
|
||
|
||
# 审核记录状态映射(原位于 zxkf.py,按功能组归类到考核模块)
|
||
SHENHE_ZHUANGTAI_MAP = {
|
||
0: '考核中',
|
||
1: '已通过',
|
||
2: '未通过',
|
||
3: '待审核',
|
||
}
|
||
|
||
class KhgglView(APIView):
|
||
"""
|
||
考核官管理数据接口(列表、筛选、分页)
|
||
POST /houtai/khggl
|
||
Body: {
|
||
"phone": "管理员手机号",
|
||
"page": 1, // 可选,默认1
|
||
"page_size": 15, // 可选,默认15
|
||
"yonghuid": "1234567", // 按用户ID筛选,可选
|
||
"zhuangtai": 1, // 账号状态 0=禁用,1=正常,可选
|
||
"shenhe_zhuangtai": 0, // 审核官状态 0=空闲,1=审核中,可选
|
||
"bankuai_name": "王者" // 板块名称模糊筛选,可选
|
||
}
|
||
"""
|
||
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', 15))
|
||
if page < 1: page = 1
|
||
if page_size < 1: page_size = 15
|
||
|
||
# 筛选条件
|
||
filter_yonghuid = request.data.get('yonghuid', '').strip()
|
||
filter_zhuangtai = request.data.get('zhuangtai')
|
||
filter_shenhe_zhuangtai = request.data.get('shenhe_zhuangtai')
|
||
filter_bankuai_name = request.data.get('bankuai_name', '').strip()
|
||
|
||
# 基础查询:所有审核官,预加载用户及打手扩展表
|
||
queryset = filter_user_related_by_club(
|
||
UserShenheguan.query.select_related('user__DashouProfile'),
|
||
request,
|
||
)
|
||
|
||
if filter_yonghuid:
|
||
queryset = queryset.filter(user__UserUID=filter_yonghuid)
|
||
if filter_zhuangtai is not None:
|
||
queryset = queryset.filter(zhuangtai=filter_zhuangtai)
|
||
if filter_shenhe_zhuangtai is not None:
|
||
queryset = queryset.filter(shenhe_zhuangtai=filter_shenhe_zhuangtai)
|
||
|
||
# 如果填写了板块名称,筛选出关联该板块的审核官
|
||
if filter_bankuai_name:
|
||
bankuai_ids = Bankuai.query.filter(
|
||
mingcheng__icontains=filter_bankuai_name
|
||
).values_list('bankuai_id', flat=True)
|
||
shenheguan_ids = KaoheguanBankuai.query.filter(
|
||
bankuai_id__in=bankuai_ids
|
||
).values_list('kaoheguan_id', flat=True)
|
||
queryset = queryset.filter(user__UserUID__in=shenheguan_ids)
|
||
|
||
total = queryset.count()
|
||
start = (page - 1) * page_size
|
||
end = start + page_size
|
||
shenheguan_list = queryset.order_by('-CreateTime')[start:end]
|
||
|
||
from collections import defaultdict
|
||
from jituan.services.shenhe_club import yonghu_chenghao_qs_for_request
|
||
|
||
# ---------- 优化:板块打手数量(1 次 annotate 替代 2N 次循环查询)----------
|
||
# 原逻辑:每板块查 dashou 标签 ID,再统计关联的不同用户数(要求有 DashouProfile)
|
||
bankuai_dashou_qs = yonghu_chenghao_qs_for_request(
|
||
YonghuChenghao.query.filter(
|
||
chenghao__leixing='dashou',
|
||
yonghu__DashouProfile__isnull=False,
|
||
),
|
||
request,
|
||
)
|
||
bankuai_dashou_count_map = {
|
||
item['chenghao__bankuai_id']: item['user_count']
|
||
for item in bankuai_dashou_qs.values('chenghao__bankuai_id').annotate(
|
||
user_count=Count('yonghu', distinct=True)
|
||
)
|
||
}
|
||
|
||
# ---------- 优化:板块标签统计(1 次 annotate 替代 N*M 次循环查询)----------
|
||
# 原逻辑:每板块每标签分别统计关联的不同用户数(不要求 DashouProfile)
|
||
tag_counts_qs = yonghu_chenghao_qs_for_request(
|
||
YonghuChenghao.query.filter(chenghao__leixing='dashou'),
|
||
request,
|
||
).values(
|
||
'chenghao_id', 'chenghao__bankuai_id', 'chenghao__mingcheng'
|
||
).annotate(user_count=Count('yonghu', distinct=True))
|
||
|
||
tags_by_bankuai = defaultdict(list)
|
||
for item in tag_counts_qs:
|
||
tags_by_bankuai[item['chenghao__bankuai_id']].append({
|
||
'id': item['chenghao_id'],
|
||
'name': item['chenghao__mingcheng'],
|
||
'user_count': item['user_count'],
|
||
})
|
||
|
||
all_banquai = Bankuai.query.all()
|
||
bankuai_tags_data = [
|
||
{
|
||
'bankuai_id': bk.bankuai_id,
|
||
'mingcheng': bk.mingcheng,
|
||
'tags': tags_by_bankuai.get(bk.bankuai_id, []),
|
||
}
|
||
for bk in all_banquai
|
||
]
|
||
|
||
# ---------- 优化:审核官板块关联(1 次批量查询替代 P 次循环查询)----------
|
||
shenheguan_user_uids = [sg.user.UserUID for sg in shenheguan_list]
|
||
all_kgs = KaoheguanBankuai.query.filter(
|
||
kaoheguan_id__in=shenheguan_user_uids
|
||
).select_related('bankuai')
|
||
kg_map = defaultdict(list)
|
||
for kg in all_kgs:
|
||
kg_map[kg.kaoheguan_id].append(kg)
|
||
|
||
# 构造返回数据(审核官列表)
|
||
data_list = []
|
||
for sg in shenheguan_list:
|
||
user = sg.user
|
||
bankuai_info = [
|
||
{
|
||
'bankuai_id': kg.bankuai.bankuai_id,
|
||
'mingcheng': kg.bankuai.mingcheng,
|
||
'dashou_count': bankuai_dashou_count_map.get(kg.bankuai.bankuai_id, 0),
|
||
}
|
||
for kg in kg_map.get(user.UserUID, [])
|
||
]
|
||
|
||
dashou_nick = user.DashouProfile.nicheng if hasattr(user, 'DashouProfile') and user.DashouProfile else ''
|
||
|
||
data_list.append({
|
||
'yonghuid': user.UserUID,
|
||
'avatar': user.Avatar or '',
|
||
'phone': user.Phone or '',
|
||
'zhuangtai': sg.zhuangtai,
|
||
'shenhe_zhuangtai': sg.shenhe_zhuangtai,
|
||
'shenhe_zongshu': sg.shenhe_zongshu,
|
||
'tongguo_zongshu': sg.tongguo_zongshu,
|
||
'yue': float(sg.yue),
|
||
'zonge': float(sg.zonge),
|
||
'CreateTime': sg.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if sg.CreateTime else '',
|
||
'UpdateTime': sg.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if sg.UpdateTime else '',
|
||
'bankuai': bankuai_info,
|
||
'nicheng': dashou_nick,
|
||
'is_renzheng': sg.is_renzheng,
|
||
})
|
||
|
||
all_bankuai = list(Bankuai.query.values('bankuai_id', 'mingcheng'))
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': 'ok',
|
||
'data': {
|
||
'total': total,
|
||
'page': page,
|
||
'page_size': page_size,
|
||
'list': data_list,
|
||
'all_bankuai': all_bankuai,
|
||
'bankuai_tags': bankuai_tags_data, # 新增板块标签统计
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.exception("KhgglView 接口错误")
|
||
return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
||
|
||
class ShgxgsjView(APIView):
|
||
"""
|
||
审核官信息修改(余额、状态、板块关联)
|
||
POST /houtai/shgxgsj
|
||
"""
|
||
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)
|
||
|
||
yonghuid = request.data.get('yonghuid')
|
||
if not yonghuid:
|
||
return Response({'code': 1, 'msg': '缺少用户ID'}, status=400)
|
||
|
||
try:
|
||
shenheguan = UserShenheguan.query.select_related('user').get(user__UserUID=yonghuid)
|
||
except UserShenheguan.DoesNotExist:
|
||
return Response({'code': 1, 'msg': '审核官不存在'}, status=404)
|
||
|
||
from jituan.services.shenhe_club import user_belongs_to_request_club
|
||
if not user_belongs_to_request_club(shenheguan.user, request):
|
||
return Response({'code': 403, 'msg': '该考核官不属于当前俱乐部'}, status=403)
|
||
|
||
action = request.data.get('action')
|
||
|
||
if action == 'update_info':
|
||
if 'yue' in request.data:
|
||
shenheguan.yue = float(request.data.get('yue', 0))
|
||
if 'zhuangtai' in request.data:
|
||
zt = int(request.data.get('zhuangtai'))
|
||
if zt not in [0, 1]:
|
||
return Response({'code': 1, 'msg': '无效的账号状态'}, status=400)
|
||
shenheguan.zhuangtai = zt
|
||
if 'shenhe_zhuangtai' in request.data:
|
||
szt = int(request.data.get('shenhe_zhuangtai'))
|
||
if szt not in [0, 1]:
|
||
return Response({'code': 1, 'msg': '无效的审核官状态'}, status=400)
|
||
shenheguan.shenhe_zhuangtai = szt
|
||
# 新增认证状态编辑
|
||
if 'is_renzheng' in request.data:
|
||
shenheguan.is_renzheng = bool(request.data.get('is_renzheng'))
|
||
shenheguan.save()
|
||
return Response({'code': 0, 'msg': '修改成功'})
|
||
|
||
elif action == 'add_bankuai':
|
||
bankuai_id = request.data.get('bankuai_id')
|
||
if not bankuai_id:
|
||
return Response({'code': 1, 'msg': '缺少板块ID'}, status=400)
|
||
if not Bankuai.query.filter(bankuai_id=bankuai_id).exists():
|
||
return Response({'code': 1, 'msg': '板块不存在'}, status=400)
|
||
if KaoheguanBankuai.query.filter(kaoheguan_id=yonghuid, bankuai_id=bankuai_id).exists():
|
||
return Response({'code': 1, 'msg': '已关联该板块'}, status=400)
|
||
KaoheguanBankuai.query.create(kaoheguan_id=yonghuid, bankuai_id=bankuai_id)
|
||
return Response({'code': 0, 'msg': '板块关联成功'})
|
||
|
||
elif action == 'remove_bankuai':
|
||
bankuai_id = request.data.get('bankuai_id')
|
||
if not bankuai_id:
|
||
return Response({'code': 1, 'msg': '缺少板块ID'}, status=400)
|
||
deleted, _ = KaoheguanBankuai.query.filter(
|
||
kaoheguan_id=yonghuid, bankuai_id=bankuai_id
|
||
).delete()
|
||
if not deleted:
|
||
return Response({'code': 1, 'msg': '未找到该板块关联'}, status=400)
|
||
return Response({'code': 0, 'msg': '板块移除成功'})
|
||
|
||
else:
|
||
return Response({'code': 1, 'msg': '未知操作'}, status=400)
|
||
|
||
except Exception as e:
|
||
logger.exception("ShgxgsjView 接口错误")
|
||
return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
||
|
||
|
||
|
||
|
||
|
||
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
|
||
|
||
from jituan.services.shenhe_club import filter_shenhe_jilu_by_request
|
||
|
||
filter_data = request.data
|
||
qs = filter_shenhe_jilu_by_request(
|
||
_build_shenhe_jilu_filters(filter_data),
|
||
request,
|
||
)
|
||
|
||
# FluentQuery.filter() 会原地修改同一对象;统计与列表必须各自独立查询
|
||
stats_base = filter_shenhe_jilu_by_request(
|
||
_build_shenhe_jilu_filters(filter_data),
|
||
request,
|
||
)
|
||
agg = stats_base.aggregate(total_fee=Sum('jiaofei_jine'))
|
||
tag_rows = filter_shenhe_jilu_by_request(
|
||
_build_shenhe_jilu_filters(filter_data),
|
||
request,
|
||
).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_base.count(),
|
||
'passed': stats_base.filter(zhuangtai=1).count(),
|
||
'failed': stats_base.filter(zhuangtai=2).count(),
|
||
'in_progress': stats_base.filter(zhuangtai=0).count(),
|
||
'pending': stats_base.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].to_list()
|
||
|
||
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 = {
|
||
r['jilu_id']: r['cnt']
|
||
for r in KaoheJujueJilu.query.filter(jilu_id__in=jilu_ids)
|
||
.values('jilu_id')
|
||
.annotate(cnt=Count('id'))
|
||
.all()
|
||
} 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)
|
||
|