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.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,
    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']


def _serialize_permission_row(perm_row):
    """gvsdsdk Permission → 后台 Vue 约定的小写字段"""
    if isinstance(perm_row, dict):
        code = perm_row.get('PermCode') or perm_row.get('perm_code') or ''
        name = perm_row.get('PermName') or perm_row.get('perm_name') or code
        desc = perm_row.get('PermDesc') or perm_row.get('perm_desc') or ''
    else:
        code = perm_row.PermCode or ''
        name = perm_row.PermName or code
        desc = perm_row.PermDesc or ''
    return {'perm_code': code, 'perm_name': name, 'perm_desc': desc}


class GetRolePermissionView(APIView):
    """
    获取所有角色、权限以及角色关联的用户数
    权限要求：用户必须拥有 000001 权限
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 400, 'msg': '缺少username'})

        # 1. 权限校验（验证客服身份，并获取权限列表）
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions  # 直接返回错误响应

        # 2. 检查是否有 000001 权限
        if '000001' not in permissions:
            return Response({'code': 403, 'msg': '您无权访问此页面'})

        # 3. 获取所有权限
        all_perms = Permission.objects.filter(PermStatus=1).values('PermCode', 'PermName', 'PermDesc')
        permissions_list = [_serialize_permission_row(p) for p in all_perms]

        from jituan.services.club_rbac import count_role_users_in_scope, filter_roles_for_request

        kefu_user_uuids = list(
            User.objects.filter(KefuProfile__isnull=False).values_list('UserUUID', flat=True)
        )
        roles = filter_roles_for_request(request)
        roles_data = []
        for role in roles:
            user_count = count_role_users_in_scope(role, request, kefu_user_uuids)
            role_perm_uuids = RolePermission.objects.filter(
                RoleUUID=role.RoleUUID
            ).values_list('PermUUID', flat=True)
            perms_of_role = [
                _serialize_permission_row(p)
                for p in Permission.objects.filter(
                    PermUUID__in=list(role_perm_uuids)
                ).values('PermCode', 'PermName', 'PermDesc')
            ]
            roles_data.append({
                'role_code': role.RoleName,
                'role_name': role.RoleName,
                'description': role.RoleDesc or '',
                'club_id': getattr(role, 'ClubID', '') or '',
                'user_count': user_count,
                'permissions': perms_of_role
            })

        return Response({
            'code': 0,
            'data': {
                'roles': roles_data,
                'permissions': permissions_list
            }
        })


class ModifyRolePermissionView(APIView):
    """
    通用接口：支持删除角色、为角色添加权限、移除角色权限
    操作类型通过 action 字段区分
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('username', '').strip()
        action = request.data.get('action')  # delete_role, add_permission, remove_permission
        if not username or not action:
            return Response({'code': 400, 'msg': '缺少必要参数'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '000001' not in permissions:
            return Response({'code': 403, 'msg': '您无权进行此操作'})

        # 获取角色（需要角色名称）
        role_code = request.data.get('role_code')
        if not role_code:
            return Response({'code': 400, 'msg': '缺少角色编码'})

        try:
            role = Role.objects.get(RoleName=role_code)
        except Role.DoesNotExist:
            return Response({'code': 404, 'msg': '角色不存在'})

        # 超级管理员角色的特殊限制
        if role.RoleName == '管理员':
            if action in ['delete_role']:
                return Response({'code': 403, 'msg': '超级管理员角色不能删除'})

        # 根据 action 处理
        if action == 'delete_role':
            # 删除角色：同时删除角色权限关联、用户角色关联
            with transaction.atomic():
                RolePermission.objects.filter(RoleUUID=role.RoleUUID).delete()
                UserRole.objects.filter(RoleUUID=role.RoleUUID).delete()
                role.delete()
            return Response({'code': 0, 'msg': '角色删除成功'})

        elif action == 'add_permission':
            perm_code = request.data.get('perm_code')
            if not perm_code:
                return Response({'code': 400, 'msg': '缺少权限编码'})
            if role.RoleName == '管理员' and perm_code == '000001':
                return Response({'code': 403, 'msg': '此权限已存在，不能重复添加'})
            try:
                perm = Permission.objects.get(PermCode=perm_code)
            except Permission.DoesNotExist:
                return Response({'code': 404, 'msg': '权限不存在'})
            # 检查是否已存在
            if RolePermission.objects.filter(RoleUUID=role.RoleUUID, PermUUID=perm.PermUUID).exists():
                return Response({'code': 400, 'msg': '该角色已拥有此权限'})
            RolePermission.objects.create(
                RolePermUUID=uuid.uuid4().bytes,
                RoleUUID=role.RoleUUID,
                PermUUID=perm.PermUUID,
                CreateTime=timezone.now(),
            )
            return Response({'code': 0, 'msg': '权限添加成功'})

        elif action == 'remove_permission':
            perm_code = request.data.get('perm_code')
            if not perm_code:
                return Response({'code': 400, 'msg': '缺少权限编码'})
            if role.RoleName == '管理员' and perm_code == '000001':
                return Response({'code': 403, 'msg': '不能移除超级管理员的此权限'})
            try:
                perm = Permission.objects.get(PermCode=perm_code)
            except Permission.DoesNotExist:
                return Response({'code': 404, 'msg': '权限不存在'})
            deleted, _ = RolePermission.objects.filter(RoleUUID=role.RoleUUID, PermUUID=perm.PermUUID).delete()
            if deleted == 0:
                return Response({'code': 400, 'msg': '该角色未拥有此权限'})
            return Response({'code': 0, 'msg': '权限移除成功'})

        elif action == 'update_role':
            role_name = (request.data.get('role_name') or '').strip()
            description = (request.data.get('description') or '').strip()
            perm_codes = request.data.get('perm_codes') or []
            if role.RoleName == '管理员':
                role_name = role.RoleName
            elif role_name and role_name != role.RoleName:
                if Role.objects.filter(RoleName=role_name).exclude(RoleUUID=role.RoleUUID).exists():
                    return Response({'code': 400, 'msg': '角色名称已存在'})
            with transaction.atomic():
                if role.RoleName != '管理员':
                    if role_name:
                        role.RoleName = role_name
                    role.RoleDesc = description
                    role.save(update_fields=['RoleName', 'RoleDesc'])
                RolePermission.objects.filter(RoleUUID=role.RoleUUID).delete()
                for pc in perm_codes:
                    if pc == '000001' and role.RoleName != '管理员':
                        continue
                    try:
                        perm = Permission.objects.get(PermCode=pc, PermStatus=1)
                    except Permission.DoesNotExist:
                        continue
                    RolePermission.objects.create(
                        RolePermUUID=uuid.uuid4().bytes,
                        RoleUUID=role.RoleUUID,
                        PermUUID=perm.PermUUID,
                        CreateTime=timezone.now(),
                    )
                if role.RoleName == '管理员':
                    try:
                        super_perm = Permission.objects.get(PermCode='000001', PermStatus=1)
                        if not RolePermission.objects.filter(
                            RoleUUID=role.RoleUUID, PermUUID=super_perm.PermUUID,
                        ).exists():
                            RolePermission.objects.create(
                                RolePermUUID=uuid.uuid4().bytes,
                                RoleUUID=role.RoleUUID,
                                PermUUID=super_perm.PermUUID,
                                CreateTime=timezone.now(),
                            )
                    except Permission.DoesNotExist:
                        pass
            return Response({'code': 0, 'msg': '角色保存成功'})

        else:
            return Response({'code': 400, 'msg': '无效的 action'})


class AddRoleView(APIView):
    """
    添加角色：自动生成角色编码，前端提供角色名称、描述、选择的权限列表
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('username', '').strip()
        role_name = request.data.get('role_name', '').strip()
        description = request.data.get('description', '').strip()
        perm_codes = request.data.get('perm_codes', [])  # 列表

        if not username or not role_name:
            return Response({'code': 400, 'msg': '缺少必要参数'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '000001' not in permissions:
            return Response({'code': 403, 'msg': '您无权进行此操作'})

        # 检查角色名称是否已存在（同俱乐部内唯一）
        from jituan.services.club_rbac import resolve_role_club_id, role_name_exists
        target_club_id = resolve_role_club_id(
            request, (request.data.get('target_club_id') or '').strip() or None,
        )
        if role_name_exists(role_name, target_club_id):
            return Response({'code': 400, 'msg': '该俱乐部下角色名称已存在'})

        # 生成唯一角色编码（格式：ROLE_ + 8位随机字母数字）
        role_code = f"ROLE_{''.join(random.choices(string.ascii_uppercase + string.digits, k=8))}"

        # 创建角色
        with transaction.atomic():
            role = Role.objects.create(
                RoleUUID=uuid.uuid4().bytes,
                TenantUUID=b'\x00' * 16,
                RoleName=role_name,
                RoleType=2,
                RoleStatus=1,
                AssignScope=0,
                RoleDesc=description,
                ClubID=target_club_id,
                CreateTime=timezone.now(),
            )
            # 绑定权限（过滤掉不存在的权限编码）
            for pc in perm_codes:
                # 不允许添加 000001 权限（此权限仅供超级管理员）
                if pc == '000001':
                    continue
                try:
                    perm = Permission.objects.get(PermCode=pc)
                    RolePermission.objects.create(
                        RolePermUUID=uuid.uuid4().bytes,
                        RoleUUID=role.RoleUUID,
                        PermUUID=perm.PermUUID,
                        CreateTime=timezone.now(),
                    )
                except Permission.DoesNotExist:
                    logger.warning(f"添加角色时忽略不存在的权限编码: {pc}")

        return Response({'code': 0, 'msg': '角色添加成功', 'data': {'role_code': role_code}})



class GetAdminUserListView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        try:
            username = request.data.get('username', '').strip()
            if not username:
                return Response({'code': 400, 'msg': '缺少username'})

            kefu, permissions = verify_kefu_permission(request, username)
            if kefu is None:
                return permissions
            if '000001' not in permissions:
                return Response({'code': 403, 'msg': '您无权访问此页面'})

            phone = request.data.get('phone', '').strip()
            nicheng = request.data.get('nicheng', '').strip()
            role_code = request.data.get('roleCode', '').strip()
            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('pageSize', 20))

            # UserType 是 @property 非数据库字段，不能用 filter；
            # KefuProfile__isnull=False 已确保只查到客服用户
            users = User.query.filter(
                KefuProfile__isnull=False
            ).select_related('KefuProfile')

            from jituan.services.club_rbac import filter_kefu_users_qs
            users = filter_kefu_users_qs(users, request)

            if phone:
                users = users.filter(Phone__icontains=phone)
            if nicheng:
                users = users.filter(KefuProfile__nicheng__icontains=nicheng)

            # 关键修正：先求值为列表，再传入 phone__in
            if role_code:
                target_role = Role.objects.filter(RoleName=role_code).first()
                if target_role:
                    user_uuids = list(
                        UserRole.objects.filter(RoleUUID=target_role.RoleUUID)
                        .values_list('UserUUID', flat=True)
                    )
                    users = users.filter(UserUUID__in=user_uuids)
                else:
                    users = users.none()

            users = users.order_by('-UserCreateTime')
            paginator = Paginator(users, page_size)
            page_obj = paginator.get_page(page)

            data_list = []
            for user in page_obj:
                # 通过 gvsdsdk UserRole 获取用户角色
                user_role_uuids = list(
                    UserRole.objects.filter(UserUUID=user.UserUUID)
                    .values_list('RoleUUID', flat=True)
                )
                user_roles = Role.objects.filter(RoleUUID__in=user_role_uuids)
                roles_data = [
                    {'role_code': r.RoleName, 'role_name': r.RoleName}
                    for r in user_roles
                ]
                data_list.append({
                    'phone': user.Phone or '',
                    'yonghuid': user.UserUID or '',
                    'nicheng': user.KefuProfile.nicheng,
                    'club_id': getattr(user, 'ClubID', '') or '',
                    'roles': roles_data,
                    'status': user.KefuProfile.zhuangtai,
                    'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '',
                    'update_time': user.KefuProfile.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user.KefuProfile, 'UpdateTime', None) else '',
                    'CreateTime': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '',
                    'UpdateTime': user.KefuProfile.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user.KefuProfile, 'UpdateTime', None) else '',
                })

            return Response({
                'code': 0,
                'data': {
                    'list': data_list,
                    'total': paginator.count,
                    'page': page,
                    'pageSize': page_size
                }
            })

        except Exception as e:
            logger.error(f"GetAdminUserListView 异常: {e}\n{traceback.format_exc()}")
            return Response({'code': 500, 'msg': f'服务器内部错误: {str(e)}'})



class GetAdminRolesView(APIView):
    """获取所有角色（用于筛选和分配）"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 400, 'msg': '缺少username'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '000001' not in permissions:
            return Response({'code': 403, 'msg': '您无权访问此页面'})

        from jituan.services.club_rbac import filter_roles_for_request
        roles = filter_roles_for_request(request).values('RoleName', 'ClubID')
        return Response({
            'code': 0,
            'data': {
                'roles': [
                    {
                        'role_code': r['RoleName'],
                        'role_name': r['RoleName'],
                        'club_id': r.get('ClubID') or '',
                    }
                    for r in roles
                ]
            }
        })






'''class GetAdminUserListView(APIView):
    """获取后台用户列表（支持分页、筛选）"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 400, 'msg': '缺少username'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '000001' not in permissions:
            return Response({'code': 403, 'msg': '您无权访问此页面'})

        # 筛选条件
        phone = request.data.get('phone', '').strip()
        nicheng = request.data.get('nicheng', '').strip()
        role_code = request.data.get('roleCode', '').strip()
        page = int(request.data.get('page', 1))
        page_size = int(request.data.get('pageSize', 20))

        # 基础查询：存在客服扩展表
        users = User.query.filter(KefuProfile__isnull=False).select_related('KefuProfile')
        if phone:
            users = users.filter(Phone__icontains=phone)
        if nicheng:
            users = users.filter(KefuProfile__nicheng__icontains=nicheng)
        if role_code:
            # 筛选拥有该角色的用户
            user_ids_with_role = UserRole.query.filter(role__role_code=role_code).values_list('account_id', flat=True)
            users = users.filter(phone__in=user_ids_with_role)

        # 排序
        users = users.order_by('-CreateTime')

        # 分页
        paginator = Paginator(users, page_size)
        page_obj = paginator.get_page(page)

        # 构建返回数据
        data_list = []
        for user in page_obj:
            # 获取用户的所有角色
            user_roles = UserRole.query.filter(account_id=user.Phone).select_related('role')
            roles_data = [{'role_code': ur.role.role_code, 'role_name': ur.role.role_name} for ur in user_roles]
            data_list.append({
                'phone': user.Phone,
                'nicheng': user.KefuProfile.nicheng,
                'roles': roles_data,
                'status': user.KefuProfile.zhuangtai,
                'CreateTime': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S'),
                'UpdateTime': user.UpdateTime.strftime('%Y-%m-%d %H:%M:%S'),
            })

        return Response({
            'code': 0,
            'data': {
                'list': data_list,
                'total': paginator.count,
                'page': page,
                'pageSize': page_size
            }
        })'''





class ModifyAdminUserView(APIView):
    """修改后台用户信息（昵称、密码、二级密码、状态、角色）"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('username', '').strip()
        action = request.data.get('action')
        if not username or not action:
            return Response({'code': 400, 'msg': '缺少参数'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '000001' not in permissions:
            return Response({'code': 403, 'msg': '您无权进行此操作'})

        target_phone = request.data.get('phone', '').strip()
        if not target_phone:
            return Response({'code': 400, 'msg': '缺少目标用户账号'})

        # Phone 非唯一，同一手机号可能对应多个微信用户；后台操作只定位客服账号
        target_user = User.query.filter(
            Phone=target_phone,
            KefuProfile__isnull=False,
        ).first()
        if not target_user:
            return Response({'code': 404, 'msg': '用户不存在或非后台账号'})
        try:
            target_kefu = target_user.KefuProfile
        except UserKefu.DoesNotExist:
            return Response({'code': 404, 'msg': '用户扩展信息缺失'})

        with transaction.atomic():
            if action == 'update_user_info':
                # 修改昵称、状态、密码、二级密码
                nicheng = request.data.get('nicheng')
                status = request.data.get('status')
                password = request.data.get('password')
                erjimima = request.data.get('erjimima')
                if nicheng is not None:
                    target_kefu.nicheng = nicheng
                if status is not None:
                    target_kefu.zhuangtai = int(status)
                if password:
                    if len(password) < 6:
                        return Response({'code': 400, 'msg': '密码至少6位'})
                    target_user.SetPassword(password)
                if erjimima:
                    if len(erjimima) < 6:
                        return Response({'code': 400, 'msg': '二级密码至少6位'})
                    import bcrypt as _bcrypt
                    target_kefu.erjimima = _bcrypt.hashpw(erjimima.encode('utf-8'), _bcrypt.gensalt(rounds=12)).decode('utf-8')
                target_user.save()
                target_kefu.save()
                return Response({'code': 0, 'msg': '修改成功'})

            elif action == 'add_user_roles':
                role_codes = request.data.get('role_codes', [])
                if not role_codes:
                    return Response({'code': 400, 'msg': '请选择角色'})
                for rc in role_codes:
                    try:
                        role = Role.objects.get(RoleName=rc)
                    except Role.DoesNotExist:
                        continue
                    UserRole.objects.get_or_create(
                        UserRoleUUID=uuid.uuid4().bytes,
                        UserUUID=target_user.UserUUID,
                        RoleUUID=role.RoleUUID,
                        CompanyUUID=uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes,
                    )
                return Response({'code': 0, 'msg': '角色添加成功'})

            elif action == 'remove_user_role':
                role_code = request.data.get('role_code')
                if not role_code:
                    return Response({'code': 400, 'msg': '缺少角色编码'})
                try:
                    role = Role.objects.get(RoleName=role_code)
                except Role.DoesNotExist:
                    return Response({'code': 404, 'msg': '角色不存在'})
                UserRole.objects.filter(UserUUID=target_user.UserUUID, RoleUUID=role.RoleUUID).delete()
                return Response({'code': 0, 'msg': '角色移除成功'})

            else:
                return Response({'code': 400, 'msg': '无效的action'})





class AddAdminUserView(APIView):
    """添加后台用户（客服账号）"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 400, 'msg': '缺少username'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '000001' not in permissions:
            return Response({'code': 403, 'msg': '您无权进行此操作'})

        phone = request.data.get('phone', '').strip()
        password = request.data.get('password', '').strip()
        erjimima = request.data.get('erjimima', '').strip()
        nicheng = request.data.get('nicheng', '').strip()
        role_codes = request.data.get('role_codes', [])

        if not all([phone, password, erjimima, nicheng]):
            return Response({'code': 400, 'msg': '请填写完整信息'})
        if len(password) < 6 or len(erjimima) < 6:
            return Response({'code': 400, 'msg': '密码和二级密码至少6位'})

        # 校验手机号是否已被客服占用
        if User.query.filter(Phone=phone, KefuProfile__isnull=False).exists():
            return Response({'code': 400, 'msg': '该账号已存在'})

        # 生成唯一的 yonghuid（7位数字）
        while True:
            yonghuid = str(random.randint(1000000, 9999999))
            if not User.query.filter(UserUID=yonghuid).exists():
                break

        # 生成唯一的 openid（格式：admin_{时间戳}_{随机6位}）
        timestamp = str(int(time.time() * 1000))
        random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
        openid = f"admin_{timestamp}_{random_suffix}"
        while User.query.filter(OpenID=openid).exists():
            random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
            openid = f"admin_{timestamp}_{random_suffix}"

        with transaction.atomic():
            user_main = User.query.create(
                UserUID=yonghuid,
                UserName=openid,
                OpenID=openid,
                Phone=phone,
            )
            user_main.SetPassword(password)
            user_main.save(update_fields=['UserPassword'])
            # 创建客服扩展表（后台用户扩展）
            import bcrypt as _bcrypt
            erjimima_hashed = _bcrypt.hashpw(erjimima.encode('utf-8'), _bcrypt.gensalt(rounds=12)).decode('utf-8')
            UserKefu.query.create(
                user=user_main,
                nicheng=nicheng,
                erjimima=erjimima_hashed,
                zhuangtai=1
            )
            # 分配客服角色
            kefu_role = Role.objects.filter(RoleName='客服').first()
            if kefu_role:
                UserRole.objects.create(
                    UserRoleUUID=uuid.uuid4().bytes,
                    UserUUID=user_main.UserUUID,
                    RoleUUID=kefu_role.RoleUUID,
                    CompanyUUID=uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes,
                )
            # 分配额外角色
            for rc in role_codes:
                try:
                    role = Role.objects.get(RoleName=rc)
                    UserRole.objects.create(
                        UserRoleUUID=uuid.uuid4().bytes,
                        UserUUID=user_main.UserUUID,
                        RoleUUID=role.RoleUUID,
                        CompanyUUID=uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes,
                    )
                except Role.DoesNotExist:
                    pass

        return Response({'code': 0, 'msg': '添加成功'})








class KefuGetDashouListView(APIView):
    """
    客服获取打手列表接口（仅打手类型）
    需要拥有以下任一权限：001aa,001bb,001cc,001dd,001ee,001ff,001gg
    请求：POST /yonghu/kefuhqdslb
    参数：
        username: 客服手机号（用于权限校验）
        keyword: 搜索关键词（可选，搜索用户ID或昵称）
        page: 页码（从1开始）
        page_size: 每页数量
    返回：
        code: 0
        data: {
            list: [打手对象],
            has_more: bool,
            total: int,               // 总打手数
            valid_dashou_count: int   // 有效打手数（有有效会员）
        }
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        # 1. 获取参数（前端字段为 username）
        username = request.data.get('username', '').strip()
        keyword = request.data.get('keyword', '').strip()
        try:
            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('page_size', 20))
        except ValueError:
            return Response({'code': 400, 'msg': '分页参数错误'})

        if not username:
            return Response({'code': 401, 'msg': '认证失败'})

        # 2. 公共权限校验（验证客服身份，获取权限列表）
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions  # 直接返回错误响应

        # 3. 检查是否拥有打手管理相关权限（至少一个）
        required_perms = {'001aa', '001bb', '001cc', '001dd', '001ee', '001ff', '001gg'}
        if not any(p in permissions for p in required_perms):
            return Response({'code': 403, 'msg': '您没有权限查看打手列表'})

        # 4. 构建打手查询集（仅打手类型）
        dashou_qs = filter_user_related_by_club(
            UserDashou.query.select_related('user').all(), request,
        )

        # 关键词筛选（用户ID或昵称）
        if keyword:
            dashou_qs = dashou_qs.filter(
                Q(user__UserUID__icontains=keyword) |
                Q(nicheng__icontains=keyword)
            )

        # 5. 统计打手总数
        total_dashou = dashou_qs.count()

        # 6. 统计有效打手数（有会员且未过期）
        dashou_user_ids = dashou_qs.values_list('user__UserUID', flat=True)
        now = timezone.now()
        hy_qs = filter_queryset_by_club(Huiyuangoumai.query.all(), request)
        valid_dashou_count = hy_qs.filter(
            yonghu_id__in=dashou_user_ids,
            huiyuan_zhuangtai=1,
            daoqi_time__gt=now,
        ).values('yonghu_id').distinct().count()

        # 7. 分页
        offset = (page - 1) * page_size
        dashou_list = dashou_qs.order_by('-CreateTime')[offset:offset + page_size]

        # 8. 构建返回数据
        result = []
        for dashou in dashou_list:
            user = dashou.user
            result.append({
                'yonghuid': user.UserUID,
                'avatar': user.Avatar or '',
                'zaixianzhuangtai': dashou.zaixianzhuangtai,
                'zhanghaozhuangtai': dashou.zhanghaozhuangtai,
                'nicheng': dashou.nicheng or '',
                'zhuangtai': dashou.zhuangtai,
            })

        has_more = (offset + len(dashou_list)) < total_dashou

        meta = list_response_meta(request)
        return Response({
            'code': 0,
            'club_id': meta['club_id'],
            'scope': meta['scope'],
            'data': {
                'list': result,
                'has_more': has_more,
                'total': total_dashou,
                'valid_dashou_count': valid_dashou_count,
            }
        })



class KefuGetDashouDetailView(APIView):
    """
    客服获取打手详情接口
    请求：POST /yonghu/kefuhqdsxq
    参数：{
        "username": "客服手机号",
        "uid": "打手ID (yonghuid)"
    }
    认证：JWT
    返回：code=0 + 打手信息 + 会员列表 + 所有会员类型 + 客服权限列表
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        # 1. 获取参数
        username = request.data.get('phone', '').strip()
        uid = request.data.get('uid', '').strip()

        if not username or not uid:
            return Response({'code': 400, 'msg': '缺少参数'})

        # 2. 公共权限校验（验证客服身份，获取权限列表）
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        # 3. 检查是否拥有打手管理相关权限（至少一个）
        required_perms = {'001aa', '001bb', '001cc', '001dd', '001ee', '001ff', '001gg'}
        if not any(p in permissions for p in required_perms):
            return Response({'code': 403, 'msg': '您没有权限查看打手详情'})

        # 4. 查询打手主表和扩展表
        try:
            user_main = User.query.select_related('DashouProfile').get(
                UserUID=uid
            )
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '打手不存在'})

        denied = forbid_if_user_out_of_scope(request, user_main)
        if denied:
            return denied

        try:
            dashou = user_main.DashouProfile
        except ObjectDoesNotExist:
            # 用户存在但未注册打手身份，返回基本信息
            user_info = {
                'yonghuid': user_main.UserUID,
                'avatar': user_main.Avatar or '',
                'zaixianzhuangtai': 0,
                'zhanghaozhuangtai': 0,
                'zhuangtai': 0,
                'nicheng': '',
                'chenghao': '',
                'dianhua': '',
                'wechat': '',
                'phone': user_main.Phone or '',
                'CreateTime': user_main.UserCreateTime,
                'create_time': user_main.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user_main, 'UserCreateTime', None) else '',
                'ip': user_main.IP or '',
                'jiedanzongliang': 0,
                'chengjiaozongliang': 0,
                'tuikuanliang': 0,
                'jinrijiedan': 0,
                'yue': 0.00,
                'zonge': 0.00,
                'jifen': 0,
                'yajin': 0.00,
                'jinrishouyi': 0.00,
                'jinyueshouyi': 0.00,
                'yaoqingren': '',
                'jieshao': '',
            }
            member_list = []
            # 仍然需要返回 all_members 和 permissions
            all_members = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao'))
            return Response({
                'code': 0,
                'data': {
                    'user_info': user_info,
                    'member_list': member_list,
                    'all_members': all_members,
                    'permissions': permissions,
                }
            })

        # 5. 构建打手信息
        user_info = {
            'yonghuid': user_main.UserUID,
            'avatar': user_main.Avatar or '',
            'zaixianzhuangtai': dashou.zaixianzhuangtai,
            'zhanghaozhuangtai': dashou.zhanghaozhuangtai,
            'zhuangtai': dashou.zhuangtai,
            'nicheng': dashou.nicheng or '',
            'chenghao': dashou.chenghao or '',
            'dianhua': dashou.dianhua or '',
            'wechat': dashou.wechat or '',
            'phone': user_main.Phone or '',
            'CreateTime': user_main.UserCreateTime,
            'create_time': user_main.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user_main, 'UserCreateTime', None) else '',
            'ip': user_main.IP or '',
            'jiedanzongliang': dashou.jiedanzongliang,
            'chengjiaozongliang': dashou.chengjiaozongliang,
            'tuikuanliang': dashou.tuikuanliang,
            'jinrijiedan': dashou.jinrijiedan,
            'yue': float(dashou.yue) if dashou.yue else 0.00,
            'zonge': float(dashou.zonge) if dashou.zonge else 0.00,
            'jifen': dashou.jifen,
            'yajin': float(dashou.yajin) if dashou.yajin else 0.00,
            'jinrishouyi': float(dashou.jinrishouyi) if dashou.jinrishouyi else 0.00,
            'jinyueshouyi': float(dashou.jinyueshouyi) if dashou.jinyueshouyi else 0.00,
            'yaoqingren': dashou.yaoqingren or '',
            'jieshao': dashou.jieshao or '',
        }

        # 6. 查询打手已拥有的会员（未过期）
        member_records = Huiyuangoumai.query.filter(
            yonghu_id=uid
        ).order_by('-CreateTime')
        member_list = []
        for record in member_records:
            # 检查是否过期
            if hasattr(record, 'jiance_shifou_daoqi'):
                is_expired = record.jiance_shifou_daoqi()
            else:
                is_expired = record.daoqi_time < timezone.now() if record.daoqi_time else True

            if not is_expired:
                try:
                    huiyuan = Huiyuan.query.get(huiyuan_id=record.huiyuan_id)
                    huiyuan_name = huiyuan.jieshao
                except Huiyuan.DoesNotExist:
                    huiyuan_name = '未知会员'
                member_list.append({
                    'id': record.id,
                    'huiyuan_name': huiyuan_name,
                    'daoqi_time': record.daoqi_time,
                    'is_active': record.huiyuan_zhuangtai == 1,
                    'huiyuan_id': record.huiyuan_id,  # 用于删除操作
                })

        # 7. 查询所有会员类型（用于前端添加会员）
        all_members = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao'))

        return Response({
            'code': 0,
            'data': {
                'user_info': user_info,
                'member_list': member_list,
                'all_members': all_members,
                'permissions': permissions,  # 返回当前客服的权限列表
            }
        })



class KefuUpdateDashouView(APIView):
    """
    客服修改打手信息接口
    支持操作：
    - jian_yue / jia_yue           : 减/加打手余额 (权限001aa/001bb)
    - jian_yajin / jia_yajin       : 减/加打手押金 (权限001aa/001bb)
    - jian_jifen / jia_jifen       : 减/加打手积分 (权限001cc/001dd)
    - gai_zhanghaozhuangtai        : 修改打手账号状态（封禁/解封） (权限001ee)
    - gai_zaixianzhuangtai         : 修改打手在线状态 (权限001ee)
    - gai_zhuangtai                : 修改打手接单状态（空闲/忙碌） (权限001ee)
    - jia_huiyuan / jian_huiyuan   : 加/减会员 (权限001ff/001gg)
    - gai_chenghao                 : 修改打手称号（升级/降级金牌） (权限001hh)
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 获取前端参数
        username = request.data.get('username', '').strip()
        dashou_id = request.data.get('dashou_id', '').strip()
        caozuo = request.data.get('caozuo', '').strip()
        value = request.data.get('value')          # 金额、积分、天数、称号等
        huiyuan_id = request.data.get('huiyuan_id', '').strip()

        if not username or not dashou_id or not caozuo:
            return Response({'code': 400, 'msg': '参数不完整'})

        # 2. 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        # 3. 查询打手
        try:
            dashou_user = User.query.get(UserUID=dashou_id)
            dashou_profile = dashou_user.DashouProfile
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '打手不存在'})
        except UserDashou.DoesNotExist:
            return Response({'code': 404, 'msg': '打手扩展信息缺失'})

        with transaction.atomic():
            try:
                before = {
                    'yue': dashou_profile.yue,
                    'yajin': dashou_profile.yajin,
                    'jifen': dashou_profile.jifen,
                    'zhanghaozhuangtai': dashou_profile.zhanghaozhuangtai,
                    'zaixianzhuangtai': dashou_profile.zaixianzhuangtai,
                    'zhuangtai': dashou_profile.zhuangtai,
                    'chenghao': dashou_profile.chenghao,
                }

                # ---------- 余额操作 ----------
                if caozuo == 'jian_yue':
                    if '001aa' not in permissions:
                        return Response({'code': 403, 'msg': '无权限减打手余额'})
                    amount = Decimal(str(value))
                    if dashou_profile.yue < amount:
                        return Response({'code': 400, 'msg': '余额不足'})
                    dashou_profile.yue -= amount
                    dashou_profile.save()
                    after = dashou_profile.yue
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        xiugaitijiao=after,
                        xiugaitijiaoq=before['yue'],
                        qitashuoming=(
                            f'【后台】减少打手可提现余额：{before["yue"]}元 → {after}元，'
                            f'减少{amount}元，打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '减余额成功', 'data': {'new_yue': str(after)}})

                elif caozuo == 'jia_yue':
                    if '001bb' not in permissions:
                        return Response({'code': 403, 'msg': '无权限加打手余额'})
                    amount = Decimal(str(value))
                    dashou_profile.yue += amount
                    dashou_profile.save()
                    after = dashou_profile.yue
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        xiugaitijiao=after,
                        xiugaitijiaoq=before['yue'],
                        qitashuoming=(
                            f'【后台】增加打手可提现余额：{before["yue"]}元 → {after}元，'
                            f'增加{amount}元，打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '加余额成功', 'data': {'new_yue': str(after)}})

                # ---------- 押金操作 ----------
                elif caozuo == 'jian_yajin':
                    if '001aa' not in permissions:
                        return Response({'code': 403, 'msg': '无权限减打手押金'})
                    amount = Decimal(str(value))
                    if dashou_profile.yajin < amount:
                        return Response({'code': 400, 'msg': '押金不足'})
                    dashou_profile.yajin -= amount
                    dashou_profile.save()
                    after = dashou_profile.yajin
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        yajin=after,
                        yyajin=before['yajin'],
                        qitashuoming=(
                            f'【后台】减少打手押金：{before["yajin"]}元 → {after}元，'
                            f'减少{amount}元，打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '减押金成功', 'data': {'new_yajin': str(after)}})

                elif caozuo == 'jia_yajin':
                    if '001bb' not in permissions:
                        return Response({'code': 403, 'msg': '无权限加打手押金'})
                    amount = Decimal(str(value))
                    dashou_profile.yajin += amount
                    dashou_profile.save()
                    after = dashou_profile.yajin
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        yajin=after,
                        yyajin=before['yajin'],
                        qitashuoming=(
                            f'【后台】增加打手押金：{before["yajin"]}元 → {after}元，'
                            f'增加{amount}元，打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '加押金成功', 'data': {'new_yajin': str(after)}})

                # ---------- 积分操作 ----------
                elif caozuo == 'jian_jifen':
                    if '001cc' not in permissions:
                        return Response({'code': 403, 'msg': '无权限减打手积分'})
                    jifen = int(value)
                    if dashou_profile.jifen < jifen:
                        return Response({'code': 400, 'msg': '积分不足'})
                    dashou_profile.jifen -= jifen
                    dashou_profile.save()
                    after = dashou_profile.jifen
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        jifen=after,
                        yjifen=before['jifen'],
                        qitashuoming=(
                            f'【后台】减少打手积分：{before["jifen"]} → {after}，'
                            f'减少{jifen}分，打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '减积分成功', 'data': {'new_jifen': after}})

                elif caozuo == 'jia_jifen':
                    if '001dd' not in permissions:
                        return Response({'code': 403, 'msg': '无权限加打手积分'})
                    jifen = int(value)
                    dashou_profile.jifen += jifen
                    dashou_profile.save()
                    after = dashou_profile.jifen
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        jifen=after,
                        yjifen=before['jifen'],
                        qitashuoming=(
                            f'【后台】增加打手积分：{before["jifen"]} → {after}，'
                            f'增加{jifen}分，打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '加积分成功', 'data': {'new_jifen': after}})

                # ---------- 状态修改 ----------
                elif caozuo == 'gai_zhanghaozhuangtai':
                    if '001ee' not in permissions:
                        return Response({'code': 403, 'msg': '无权限修改打手账号状态'})
                    new_status = int(value)
                    if new_status not in [0, 1]:
                        return Response({'code': 400, 'msg': '状态值无效（0=封禁,1=正常）'})
                    dashou_profile.zhanghaozhuangtai = new_status
                    dashou_profile.save()
                    status_label = '正常' if new_status == 1 else '封禁'
                    old_label = '正常' if before['zhanghaozhuangtai'] == 1 else '封禁'
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        qitashuoming=(
                            f'【后台】修改打手账号状态：{old_label} → {status_label}，'
                            f'打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '修改账号状态成功', 'data': {'new_zhanghaozhuangtai': new_status}})

                elif caozuo == 'gai_zaixianzhuangtai':
                    if '001ee' not in permissions:
                        return Response({'code': 403, 'msg': '无权限修改打手在线状态'})
                    new_status = int(value)
                    if new_status not in [0, 1]:
                        return Response({'code': 400, 'msg': '状态值无效（0=离线,1=在线）'})
                    dashou_profile.zaixianzhuangtai = new_status
                    dashou_profile.save()
                    status_label = '在线' if new_status == 1 else '离线'
                    old_label = '在线' if before['zaixianzhuangtai'] == 1 else '离线'
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        qitashuoming=(
                            f'【后台】修改打手在线状态：{old_label} → {status_label}，'
                            f'打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '修改在线状态成功', 'data': {'new_zaixianzhuangtai': new_status}})

                elif caozuo == 'gai_zhuangtai':
                    if '001ee' not in permissions:
                        return Response({'code': 403, 'msg': '无权限修改打手接单状态'})
                    new_status = int(value)
                    if new_status not in [0, 1]:
                        return Response({'code': 400, 'msg': '状态值无效（0=忙碌,1=空闲）'})
                    dashou_profile.zhuangtai = new_status
                    dashou_profile.save()
                    status_label = '空闲' if new_status == 1 else '忙碌'
                    old_label = '空闲' if before['zhuangtai'] == 1 else '忙碌'
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        qitashuoming=(
                            f'【后台】修改打手接单状态：{old_label} → {status_label}，'
                            f'打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '修改接单状态成功', 'data': {'new_zhuangtai': new_status}})

                # ---------- 会员操作 ----------
                elif caozuo == 'jia_huiyuan':
                    if '001ff' not in permissions and '000001' not in permissions:
                        return Response({'code': 403, 'msg': '无权限给打手加会员'})
                    if not huiyuan_id:
                        return Response({'code': 400, 'msg': '缺少会员ID'})
                    try:
                        huiyuan = Huiyuan.query.get(huiyuan_id=huiyuan_id)
                    except Huiyuan.DoesNotExist:
                        return Response({'code': 404, 'msg': '会员类型不存在'})
                    from jituan.services.club_user import get_user_club_id
                    from jituan.services.member_recharge import extend_member_entitlement_admin, get_club_huiyuan_row
                    player_club = get_user_club_id(dashou_user)
                    row = get_club_huiyuan_row(player_club, huiyuan_id, require_enabled=False)
                    days = int(value) if value else int(row.formal_days or 30) if row else 30
                    record, err = extend_member_entitlement_admin(
                        dashou_id, huiyuan_id, days, player_club,
                    )
                    if err:
                        return Response({'code': 400, 'msg': err})
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        huiyuants=days,
                        huiyuan_id=huiyuan_id,
                        qitashuoming=(
                            f'【后台】给打手添加正式会员：会员ID={huiyuan_id}（{huiyuan.jieshao}），'
                            f'增加{days}天，到期时间={record.daoqi_time.strftime("%Y-%m-%d %H:%M:%S")}，'
                            f'打手ID={dashou_id}，操作人={kefu.user.Phone}（无支付单、无分红）'
                        ),
                    )
                    return Response({'code': 0, 'msg': '加会员成功', 'data': {'new_daoqi': record.daoqi_time.strftime('%Y-%m-%d %H:%M:%S')}})

                elif caozuo == 'jian_huiyuan':
                    if '001gg' not in permissions:
                        return Response({'code': 403, 'msg': '无权限给打手减会员'})
                    if not huiyuan_id:
                        return Response({'code': 400, 'msg': '缺少会员ID'})
                    try:
                        record = Huiyuangoumai.query.get(yonghu_id=dashou_id, huiyuan_id=huiyuan_id)
                    except Huiyuangoumai.DoesNotExist:
                        return Response({'code': 404, 'msg': '该打手未购买此会员'})
                    record.delete()
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        huiyuan_id=huiyuan_id,
                        qitashuoming=(
                            f'【后台】移除打手会员：会员ID={huiyuan_id}，'
                            f'打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '减会员成功'})

                # ---------- 称号修改（金牌打手） ----------
                elif caozuo == 'gai_chenghao':
                    if '001hh' not in permissions:
                        return Response({'code': 403, 'msg': '无权限修改打手称号'})
                    new_chenghao = str(value).strip()
                    if not new_chenghao:
                        return Response({'code': 400, 'msg': '称号不能为空'})
                    # 可限制合法值，这里只记录日志
                    dashou_profile.chenghao = new_chenghao
                    dashou_profile.save()
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        qitashuoming=(
                            f'【后台】修改打手称号：{before["chenghao"] or "无"} → {new_chenghao}，'
                            f'打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '修改称号成功', 'data': {'new_chenghao': new_chenghao}})

                else:
                    return Response({'code': 400, 'msg': '未知操作类型'})

            except Exception as e:
                logger.error(f"修改打手信息异常: {str(e)}", exc_info=True)
                return Response({'code': 500, 'msg': '服务器内部错误'})






class KefuGetShangjiaListView(APIView):
    """
    客服/管理员获取商家列表接口
    需要拥有以下任一权限：1100a, 1100b
    请求：POST /houtai/hqsjgllb
    参数：
        username: 客服手机号（用于权限校验，必填）
        keyword: 搜索关键词（可选，搜索商家用户ID或昵称）
        balance_op: 余额比较符，gte(>=) / lte(<=)（可选）
        balance_value: 余额数值（可选，需与balance_op同时存在）
        order_count_op: 派单量比较符，gte(>=) / lte(<=)（可选）
        order_count_value: 派单量数值（可选，需与order_count_op同时存在）
        status: 商家状态，1=正常，2=禁用（可选）
        page: 页码，从1开始，默认1
        page_size: 每页数量，默认20
    返回：
        code: 0 成功，其他失败
        data: {
            list: [商家对象],
            total: int,          // 总商家数
        }
    """
    permission_classes = []   # 不依赖全局认证，手动校验
    parser_classes = [JSONParser]

    def post(self, request):
        # 1. 获取参数
        username = request.data.get('username', '').strip()
        keyword = request.data.get('keyword', '').strip()
        balance_op = request.data.get('balance_op', '').strip().lower()
        balance_value_str = request.data.get('balance_value')
        order_count_op = request.data.get('order_count_op', '').strip().lower()
        order_count_value_str = request.data.get('order_count_value')
        status_str = request.data.get('status')

        try:
            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('page_size', 20))
            if page < 1 or page_size < 1:
                raise ValueError
        except ValueError:
            return Response({'code': 400, 'msg': '分页参数错误'})

        if not username:
            return Response({'code': 401, 'msg': '认证失败，缺少username'})

        # 2. 公共权限校验（复用现有函数 verify_kefu_permission）
        # 假设 verify_kefu_permission 定义在某个公共模块，这里引入

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions   # 直接返回错误响应

        # 3. 检查商家管理权限（1100a 或 1100b）
        required_perms = {'1100a', '1100b'}
        if not any(p in permissions for p in required_perms):
            return Response({'code': 403, 'msg': '您没有权限查看商家列表'})

        # 4. 构建商家查询集（商家扩展表 + 关联用户主表）
        shangjia_qs = filter_user_related_by_club(
            UserShangjia.query.select_related('user').all(), request,
        )

        # 关键词筛选（用户ID或昵称）
        if keyword:
            shangjia_qs = shangjia_qs.filter(
                Q(user__UserUID__icontains=keyword) |
                Q(nicheng__icontains=keyword)
            )

        # 余额筛选
        if balance_op and balance_value_str is not None and balance_value_str != '':
            try:
                balance_value = Decimal(str(balance_value_str))
                if balance_op == 'gte':
                    shangjia_qs = shangjia_qs.filter(yue__gte=balance_value)
                elif balance_op == 'lte':
                    shangjia_qs = shangjia_qs.filter(yue__lte=balance_value)
                else:
                    return Response({'code': 400, 'msg': '余额比较符只支持gte或lte'})
            except Exception:
                return Response({'code': 400, 'msg': '余额数值格式错误'})

        # 派单总量筛选（fabu字段）
        if order_count_op and order_count_value_str is not None and order_count_value_str != '':
            try:
                order_count_value = int(order_count_value_str)
                if order_count_op == 'gte':
                    shangjia_qs = shangjia_qs.filter(fabu__gte=order_count_value)
                elif order_count_op == 'lte':
                    shangjia_qs = shangjia_qs.filter(fabu__lte=order_count_value)
                else:
                    return Response({'code': 400, 'msg': '派单量比较符只支持gte或lte'})
            except Exception:
                return Response({'code': 400, 'msg': '派单量数值必须为整数'})

        # 商家状态筛选
        if status_str is not None and status_str != '':
            try:
                status_val = int(status_str)
                if status_val not in [1, 2]:
                    return Response({'code': 400, 'msg': '商家状态值无效，应为1或2'})
                shangjia_qs = shangjia_qs.filter(zhuangtai=status_val)
            except ValueError:
                return Response({'code': 400, 'msg': '商家状态格式错误'})

        # 5. 统计总商家数（应用筛选后）
        total = shangjia_qs.count()
        valid_merchant_count = shangjia_qs.filter(zhuangtai=1).count()

        # 6. 分页
        offset = (page - 1) * page_size
        shangjia_list = shangjia_qs.order_by('-CreateTime')[offset:offset + page_size]

        # 7. 构建返回数据
        result = []
        for shangjia in shangjia_list:
            user = shangjia.user
            result.append({
                'yonghuid': user.UserUID,
                'avatar': user.Avatar or '',
                'nicheng': shangjia.nicheng or '',
                'yue': str(shangjia.yue),          # 转换为字符串，避免前端精度问题
                'fabu': shangjia.fabu,
                'chengjiao': shangjia.chengjiao,
                'zhuangtai': shangjia.zhuangtai,    # 1=正常,2=禁用
            })

        meta = list_response_meta(request)
        return Response({
            'code': 0,
            'club_id': meta['club_id'],
            'scope': meta['scope'],
            'data': {
                'list': result,
                'total': total,
                'valid_merchant_count': valid_merchant_count,
            }
        })






# yonghu/views.py 中添加

class KefuGetShangjiaDetailView(APIView):
    """
    客服/管理员获取商家详情接口
    需要权限：1100a 或 1100b
    请求：POST /houtai/hthqsjxq
    参数：
        username: 客服手机号（用于权限校验，必填）
        yonghuid: 商家用户ID（必填）
    返回：
        code: 0 成功
        data: 商家所有字段（含主表头像等）
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        yonghuid = request.data.get('yonghuid', '').strip()

        if not username:
            return Response({'code': 401, 'msg': '认证失败，缺少username'})
        if not yonghuid:
            return Response({'code': 400, 'msg': '缺少商家ID'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        required_perms = {'1100a', '1100b'}
        if not any(p in permissions for p in required_perms):
            return Response({'code': 403, 'msg': '您没有权限查看商家详情'})

        # 查询商家
        try:
            shangjia = UserShangjia.query.select_related('user').get(user__UserUID=yonghuid)
        except UserShangjia.DoesNotExist:
            return Response({'code': 404, 'msg': '商家不存在'})

        denied = forbid_if_user_out_of_scope(request, shangjia.user)
        if denied:
            return denied

        user = shangjia.user

        data = {
            'yonghuid': user.UserUID,
            'avatar': user.Avatar or '',
            'nicheng': shangjia.nicheng,
            'zhuangtai': shangjia.zhuangtai,        # 1正常，2禁用
            'dianhua': shangjia.dianhua,
            'wechat': shangjia.wechat,
            'yue': str(shangjia.yue),
            'fabu': shangjia.fabu,
            'chengjiao': shangjia.chengjiao,
            'tuikuan': shangjia.tuikuan,
            'jinridingdan': shangjia.jinridingdan,
            'jinriliushui': str(shangjia.jinriliushui),
            'jinyuedingdan': shangjia.jinyuedingdan,
            'jinyueliushui': str(shangjia.jinyueliushui),
            'is_youzhi_shangjia': bool(shangjia.is_youzhi_shangjia),
            'CreateTime': shangjia.CreateTime.isoformat() if shangjia.CreateTime else None,
            'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '',
        }

        return Response({'code': 0, 'data': data})







class KefuUpdateShangjiaView(APIView):
    """
    客服/管理员修改商家信息（余额、状态）
    POST /houtai/xgsjxx
    参数：
        username, yonghuid, action
        action可选：add_balance, sub_balance, ban, unban
        amount（add_balance/sub_balance时必填）
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        yonghuid = request.data.get('yonghuid', '').strip()
        action = request.data.get('action', '').strip().lower()

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not yonghuid:
            return Response({'code': 400, 'msg': '缺少商家ID'})
        if action not in ('add_balance', 'sub_balance', 'ban', 'unban', 'set_youzhi'):
            return Response({'code': 400, 'msg': 'action参数无效'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        # 操作权限校验
        if action in ('add_balance', 'ban', 'unban', 'set_youzhi') and '1100a' not in permissions:
            return Response({'code': 403, 'msg': '无权限执行此操作（需要1100a）'})
        if action == 'sub_balance' and '1100b' not in permissions:
            return Response({'code': 403, 'msg': '无权限减少商家余额（需要1100b）'})

        # 使用事务，select_for_update 必须在事务内
        with transaction.atomic():
            try:
                shangjia = UserShangjia.objects.select_for_update().get(user__UserUID=yonghuid)
            except UserShangjia.DoesNotExist:
                return Response({'code': 404, 'msg': '商家不存在'})

            # 增加余额
            if action == 'add_balance':
                amount_str = request.data.get('amount')
                try:
                    amount = Decimal(str(amount_str))
                    if amount <= 0 or amount > 1000000:
                        return Response({'code': 400, 'msg': '金额需在0.01~1000000之间'})
                except:
                    return Response({'code': 400, 'msg': '金额格式错误'})
                old_yue = shangjia.yue
                new_yue = shangjia.yue + amount
                if new_yue > 999999999.99:
                    return Response({'code': 400, 'msg': '增加后余额超出上限'})
                shangjia.yue = new_yue
                shangjia.save(update_fields=['yue'])
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=kefu.user.Phone,
                    leixing=XIUGAI_LEIXING_SHANGJIA,
                    shangjiayue=new_yue,
                    shangjiayueq=old_yue,
                    qitashuoming=(
                        f'【后台】增加商家余额：{old_yue}元 → {new_yue}元，增加{amount}元，'
                        f'商家ID={yonghuid}，昵称={shangjia.nicheng}，操作人={kefu.user.Phone}'
                    ),
                )
                return Response({'code': 0, 'msg': '余额增加成功', 'data': {'new_balance': str(shangjia.yue)}})

            # 减少余额
            if action == 'sub_balance':
                amount_str = request.data.get('amount')
                try:
                    amount = Decimal(str(amount_str))
                    if amount <= 0 or amount > 1000000:
                        return Response({'code': 400, 'msg': '金额需在0.01~1000000之间'})
                except:
                    return Response({'code': 400, 'msg': '金额格式错误'})
                if shangjia.yue < amount:
                    return Response({'code': 400, 'msg': '余额不足'})
                old_yue = shangjia.yue
                shangjia.yue = shangjia.yue - amount
                shangjia.save(update_fields=['yue'])
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=kefu.user.Phone,
                    leixing=XIUGAI_LEIXING_SHANGJIA,
                    shangjiayue=shangjia.yue,
                    shangjiayueq=old_yue,
                    qitashuoming=(
                        f'【后台】减少商家余额：{old_yue}元 → {shangjia.yue}元，减少{amount}元，'
                        f'商家ID={yonghuid}，昵称={shangjia.nicheng}，操作人={kefu.user.Phone}'
                    ),
                )
                return Response({'code': 0, 'msg': '余额减少成功', 'data': {'new_balance': str(shangjia.yue)}})

            # 封禁
            if action == 'ban':
                if shangjia.zhuangtai == 2:
                    return Response({'code': 400, 'msg': '商家已是封禁状态'})
                shangjia.zhuangtai = 2
                shangjia.save(update_fields=['zhuangtai'])
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=kefu.user.Phone,
                    leixing=XIUGAI_LEIXING_SHANGJIA,
                    qitashuoming=(
                        f'【后台】封禁商家账号：正常 → 禁用，'
                        f'商家ID={yonghuid}，昵称={shangjia.nicheng}，操作人={kefu.user.Phone}'
                    ),
                )
                return Response({'code': 0, 'msg': '商家已封禁', 'data': {'zhuangtai': 2}})

            # 解封
            if action == 'unban':
                if shangjia.zhuangtai == 1:
                    return Response({'code': 400, 'msg': '商家已是正常状态'})
                shangjia.zhuangtai = 1
                shangjia.save(update_fields=['zhuangtai'])
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=kefu.user.Phone,
                    leixing=XIUGAI_LEIXING_SHANGJIA,
                    qitashuoming=(
                        f'【后台】解封商家账号：禁用 → 正常，'
                        f'商家ID={yonghuid}，昵称={shangjia.nicheng}，操作人={kefu.user.Phone}'
                    ),
                )
                return Response({'code': 0, 'msg': '商家已解封', 'data': {'zhuangtai': 1}})

            if action == 'set_youzhi':
                if '1100a' not in permissions:
                    return Response({'code': 403, 'msg': '无权限设置优质商家（需要1100a）'})
                is_youzhi = bool(request.data.get('is_youzhi_shangjia', False))
                shangjia.is_youzhi_shangjia = is_youzhi
                shangjia.save(update_fields=['is_youzhi_shangjia'])
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=kefu.user.Phone,
                    leixing=XIUGAI_LEIXING_SHANGJIA,
                    qitashuoming=(
                        f'【后台】设置优质商家：{is_youzhi}，'
                        f'商家ID={yonghuid}，昵称={shangjia.nicheng}，操作人={kefu.user.Phone}'
                    ),
                )
                return Response({
                    'code': 0,
                    'msg': '优质商家设置成功',
                    'data': {'is_youzhi_shangjia': is_youzhi},
                })

        return Response({'code': 400, 'msg': '未知操作'})







class GetProductBaseDataView(APIView):
    """
    获取商品基础数据（类型、专区、会员）
    权限：2200a
    POST /houtai/htsphqjcsx
    参数：username
    返回：
        code:0, data: {
            type_list: [ {id, jieshao, tupian_url, paixu, shenhezhuangtai, product_count}, ...],
            zone_list: [ {id, mingzi, leixing_id, paixu, shenhezhuangtai, product_count}, ...],
            member_list: [ {huiyuan_id, jieshao, jiage}, ...]
        }
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '2200a' not in permissions:
            return Response({'code': 403, 'msg': '无权限管理商品'})

        # 获取所有商品类型（按 paixu 升序）
        type_qs = ShangpinLeixing.query.all().order_by('paixu')
        type_list = []
        for t in type_qs:
            # 🔥 修改：统计该类型下的公共商品数量（店铺为空且店铺商品类型为空）
            product_count = Shangpin.query.filter(
                leixing_id=t.id,
                dianpu__isnull=True,           # 所属店铺为空
                dianpu_leixing__isnull=True    # 店铺商品类型为空
            ).count()
            type_list.append({
                'id': t.id,
                'jieshao': t.jieshao or '',
                'tupian_url': t.tupian_url or '',
                'paixu': t.paixu,
                'shenhezhuangtai': t.shenhezhuangtai,
                'product_count': product_count,
            })

        # 获取所有专区（按 paixu 升序）
        zone_qs = ShangpinZhuanqu.query.all().order_by('paixu')
        zone_list = []
        for z in zone_qs:
            # 🔥 修改：统计该专区下的公共商品数量（店铺为空且店铺商品类型为空）
            product_count = Shangpin.query.filter(
                zhuanqu_id=z.id,
                dianpu__isnull=True,           # 所属店铺为空
                dianpu_leixing__isnull=True    # 店铺商品类型为空
            ).count()
            zone_list.append({
                'id': z.id,
                'mingzi': z.mingzi or '',
                'leixing_id': z.leixing_id,
                'paixu': z.paixu,
                'shenhezhuangtai': z.shenhezhuangtai,
                'product_count': product_count,
            })

        # 获取所有会员（只需 ID、介绍、价格）
        member_list = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao', 'jiage'))

        return Response({
            'code': 0,
            'data': {
                'type_list': type_list,
                'zone_list': zone_list,
                'member_list': member_list,
            }
        })



class GetProductListView(APIView):
    """
    获取商品列表（支持筛选、排序）
    权限：2200a
    POST /houtai/hthqsplb
    参数：
        username: 必填
        leixing_id: 类型ID（可选）
        zhuanqu_id: 专区ID（可选）
        keyword: 搜索关键词（商品标题/介绍）
        sales_op: 销量比较符 gte/lte
        sales_value: 销量数值
        stock_op: 库存比较符 gte/lte
        stock_value: 库存数值
        sort_field: 排序字段 zhenshi_xiaoliang / jiage
        sort_order: asc/desc
        page: 页码（默认1）
        page_size: 每页数量（默认1000，前端说不需要分页，但为性能仍保留）
    返回：
        code:0, data: { list: [...], total }
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '2200a' not in permissions:
            return Response({'code': 403, 'msg': '无权限管理商品'})

        # 获取参数
        leixing_id = request.data.get('leixing_id')
        zhuanqu_id = request.data.get('zhuanqu_id')
        keyword = request.data.get('keyword', '').strip()
        sales_op = request.data.get('sales_op', '').strip().lower()
        sales_value = request.data.get('sales_value')
        stock_op = request.data.get('stock_op', '').strip().lower()
        stock_value = request.data.get('stock_value')
        sort_field = request.data.get('sort_field', '')  # zhenshi_xiaoliang / jiage
        sort_order = request.data.get('sort_order', 'desc').lower()
        try:
            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('page_size', 1000))
            if page < 1 or page_size < 1:
                raise ValueError
        except:
            return Response({'code': 400, 'msg': '分页参数错误'})

        # 🔥 修改：构建查询集，增加公共商品筛选（店铺为空且店铺商品类型为空）
        qs = Shangpin.query.filter(
            dianpu__isnull=True,           # 所属店铺为空
            dianpu_leixing__isnull=True    # 店铺商品类型为空
        )

        if leixing_id:
            try:
                leixing_id = int(leixing_id)
                qs = qs.filter(leixing_id=leixing_id)
            except:
                pass
        if zhuanqu_id:
            try:
                zhuanqu_id = int(zhuanqu_id)
                qs = qs.filter(zhuanqu_id=zhuanqu_id)
            except:
                pass
        if keyword:
            qs = qs.filter(Q(biaoqian__icontains=keyword) | Q(jieshao__icontains=keyword))

        # 销量筛选
        if sales_op and sales_value is not None:
            try:
                sales_val = int(sales_value)
                if sales_op == 'gte':
                    qs = qs.filter(zhenshi_xiaoliang__gte=sales_val)
                elif sales_op == 'lte':
                    qs = qs.filter(zhenshi_xiaoliang__lte=sales_val)
            except:
                pass

        # 库存筛选
        if stock_op and stock_value is not None:
            try:
                stock_val = int(stock_value)
                if stock_op == 'gte':
                    qs = qs.filter(kucun__gte=stock_val)
                elif stock_op == 'lte':
                    qs = qs.filter(kucun__lte=stock_val)
            except:
                pass

        # 排序
        if sort_field in ('zhenshi_xiaoliang', 'jiage'):
            if sort_order == 'asc':
                qs = qs.order_by(sort_field)
            else:
                qs = qs.order_by(f'-{sort_field}')
        else:
            qs = qs.order_by('-CreateTime')  # 默认按创建时间倒序

        # 分页
        total = qs.count()
        offset = (page - 1) * page_size
        products = qs[offset:offset + page_size]

        # 构建返回数据
        product_list = []
        for p in products:
            product_list.append({
                'id': p.id,
                'biaoqian': p.biaoqian or '',
                'jiage': str(p.jiage),
                'kucun': p.kucun,
                'zhenshi_xiaoliang': p.zhenshi_xiaoliang,
                'tupian_url': p.tupian_url or '',
                'jieshao': p.jieshao or '',
            })

        return Response({
            'code': 0,
            'data': {
                'list': product_list,
                'total': total,
            }
        })




'''class GetProductBaseDataView(APIView):
    """
    获取商品基础数据（类型、专区、会员）
    权限：2200a
    POST /houtai/htsphqjcsx
    参数：username
    返回：
        code:0, data: {
            type_list: [ {id, jieshao, tupian_url, paixu, shenhezhuangtai, product_count}, ...],
            zone_list: [ {id, mingzi, leixing_id, paixu, shenhezhuangtai, product_count}, ...],
            member_list: [ {huiyuan_id, jieshao, jiage}, ...]
        }
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '2200a' not in permissions:
            return Response({'code': 403, 'msg': '无权限管理商品'})

        # 获取所有商品类型（按 paixu 升序）
        type_qs = ShangpinLeixing.query.all().order_by('paixu')
        type_list = []
        for t in type_qs:
            # 统计该类型下的商品数量（忽略审核状态？通常统计所有商品，可根据需求调整）
            product_count = Shangpin.query.filter(leixing_id=t.id).count()
            type_list.append({
                'id': t.id,
                'jieshao': t.jieshao or '',
                'tupian_url': t.tupian_url or '',
                'paixu': t.paixu,
                'shenhezhuangtai': t.shenhezhuangtai,
                'product_count': product_count,
            })

        # 获取所有专区（按 paixu 升序）
        zone_qs = ShangpinZhuanqu.query.all().order_by('paixu')
        zone_list = []
        for z in zone_qs:
            product_count = Shangpin.query.filter(zhuanqu_id=z.id).count()
            zone_list.append({
                'id': z.id,
                'mingzi': z.mingzi or '',
                'leixing_id': z.leixing_id,
                'paixu': z.paixu,
                'shenhezhuangtai': z.shenhezhuangtai,
                'product_count': product_count,
            })

        # 获取所有会员（只需 ID、介绍、价格）
        member_list = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao', 'jiage'))

        return Response({
            'code': 0,
            'data': {
                'type_list': type_list,
                'zone_list': zone_list,
                'member_list': member_list,
            }
        })


class GetProductListView(APIView):
    """
    获取商品列表（支持筛选、排序）
    权限：2200a
    POST /houtai/hthqsplb
    参数：
        username: 必填
        leixing_id: 类型ID（可选）
        zhuanqu_id: 专区ID（可选）
        keyword: 搜索关键词（商品标题/介绍）
        sales_op: 销量比较符 gte/lte
        sales_value: 销量数值
        stock_op: 库存比较符 gte/lte
        stock_value: 库存数值
        sort_field: 排序字段 zhenshi_xiaoliang / jiage
        sort_order: asc/desc
        page: 页码（默认1）
        page_size: 每页数量（默认1000，前端说不需要分页，但为性能仍保留）
    返回：
        code:0, data: { list: [...], total }
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '2200a' not in permissions:
            return Response({'code': 403, 'msg': '无权限管理商品'})

        # 获取参数
        leixing_id = request.data.get('leixing_id')
        zhuanqu_id = request.data.get('zhuanqu_id')
        keyword = request.data.get('keyword', '').strip()
        sales_op = request.data.get('sales_op', '').strip().lower()
        sales_value = request.data.get('sales_value')
        stock_op = request.data.get('stock_op', '').strip().lower()
        stock_value = request.data.get('stock_value')
        sort_field = request.data.get('sort_field', '')  # zhenshi_xiaoliang / jiage
        sort_order = request.data.get('sort_order', 'desc').lower()
        try:
            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('page_size', 1000))
            if page < 1 or page_size < 1:
                raise ValueError
        except:
            return Response({'code': 400, 'msg': '分页参数错误'})

        # 构建查询集
        qs = Shangpin.query.all()

        if leixing_id:
            try:
                leixing_id = int(leixing_id)
                qs = qs.filter(leixing_id=leixing_id)
            except:
                pass
        if zhuanqu_id:
            try:
                zhuanqu_id = int(zhuanqu_id)
                qs = qs.filter(zhuanqu_id=zhuanqu_id)
            except:
                pass
        if keyword:
            qs = qs.filter(Q(biaoqian__icontains=keyword) | Q(jieshao__icontains=keyword))

        # 销量筛选
        if sales_op and sales_value is not None:
            try:
                sales_val = int(sales_value)
                if sales_op == 'gte':
                    qs = qs.filter(zhenshi_xiaoliang__gte=sales_val)
                elif sales_op == 'lte':
                    qs = qs.filter(zhenshi_xiaoliang__lte=sales_val)
            except:
                pass

        # 库存筛选
        if stock_op and stock_value is not None:
            try:
                stock_val = int(stock_value)
                if stock_op == 'gte':
                    qs = qs.filter(kucun__gte=stock_val)
                elif stock_op == 'lte':
                    qs = qs.filter(kucun__lte=stock_val)
            except:
                pass

        # 排序
        if sort_field in ('zhenshi_xiaoliang', 'jiage'):
            if sort_order == 'asc':
                qs = qs.order_by(sort_field)
            else:
                qs = qs.order_by(f'-{sort_field}')
        else:
            qs = qs.order_by('-CreateTime')  # 默认按创建时间倒序

        # 分页
        total = qs.count()
        offset = (page - 1) * page_size
        products = qs[offset:offset + page_size]

        # 构建返回数据
        product_list = []
        for p in products:
            product_list.append({
                'id': p.id,
                'biaoqian': p.biaoqian or '',
                'jiage': str(p.jiage),
                'kucun': p.kucun,
                'zhenshi_xiaoliang': p.zhenshi_xiaoliang,
                'tupian_url': p.tupian_url or '',
                'jieshao': p.jieshao or '',
            })

        return Response({
            'code': 0,
            'data': {
                'list': product_list,
                'total': total,
            }
        })'''


class SaveProductOrderView(APIView):
    """
    保存商品类型或专区的排序
    权限：2200a
    POST /houtai/htsppaixu
    参数：
        username: 必填
        type: 'leixing' 或 'zhuanqu'
        leixing_id: 当 type='zhuanqu' 时必填（用于限定该类型下的专区）
        items: [ {id, paixu}, ... ]   # 每个项目的新排序值（从1开始）
    返回：
        code:0 成功
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '2200a' not in permissions:
            return Response({'code': 403, 'msg': '无权限管理商品'})

        sort_type = request.data.get('type', '').strip().lower()
        if sort_type not in ('leixing', 'zhuanqu'):
            return Response({'code': 400, 'msg': 'type参数必须为leixing或zhuanqu'})

        items = request.data.get('items')
        if not isinstance(items, list) or len(items) == 0:
            return Response({'code': 400, 'msg': 'items参数无效'})

        # 校验每个 item 的格式
        for item in items:
            if not isinstance(item, dict) or 'id' not in item or 'paixu' not in item:
                return Response({'code': 400, 'msg': 'items内每项需包含id和paixu'})
            try:
                item['id'] = int(item['id'])
                item['paixu'] = int(item['paixu'])
                if item['paixu'] < 1:
                    return Response({'code': 400, 'msg': 'paixu必须为正整数'})
            except:
                return Response({'code': 400, 'msg': 'id或paixu格式错误'})

        with transaction.atomic():
            if sort_type == 'leixing':
                # 更新商品类型的排序
                for item in items:
                    ShangpinLeixing.query.filter(id=item['id']).update(paixu=item['paixu'])
            else:  # zhuanqu
                leixing_id = request.data.get('leixing_id')
                if not leixing_id:
                    return Response({'code': 400, 'msg': '更新专区排序时必须提供leixing_id'})
                try:
                    leixing_id = int(leixing_id)
                except:
                    return Response({'code': 400, 'msg': 'leixing_id格式错误'})
                # 可选：检查该专区是否属于该类型，但不强制，由前端保证
                for item in items:
                    ShangpinZhuanqu.query.filter(id=item['id']).update(paixu=item['paixu'])

        return Response({'code': 0, 'msg': '排序保存成功'})


class AddProductView(APIView):
    """
    添加商品（含图片上传）
    权限：2200a
    POST /houtai/htfbsp
    参数：multipart/form-data，包含所有商品字段及图片文件
    字段说明见代码
    """
    permission_classes = []
    parser_classes = [MultiPartParser, FormParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '2200a' not in permissions:
            return Response({'code': 403, 'msg': '无权限管理商品'})

        # 获取表单字段
        biaoqian = request.data.get('biaoqian', '').strip()
        jiage_str = request.data.get('jiage')
        kucun_str = request.data.get('kucun')
        duiwai_xiaoliang_str = request.data.get('duiwai_xiaoliang', '0')
        leixing_id_str = request.data.get('leixing_id')
        zhuanqu_id_str = request.data.get('zhuanqu_id')
        yaoqiuleixing_str = request.data.get('yaoqiuleixing', '1')
        huiyuan_id = request.data.get('huiyuan_id', '').strip()
        yongjin_str = request.data.get('yongjin', '0')
        fencheng_type = request.data.get('fencheng_type', 'default')
        ewai_PlayerCommission_str = request.data.get('ewai_PlayerCommission', '0')
        jieshao = request.data.get('jieshao', '').strip()
        xiadan_xuzhi = request.data.get('xiadan_xuzhi', '').strip()
        # 图片文件
        image_file = request.FILES.get('file')  # 前端上传时字段名为 'file'

        # 必填字段校验
        if not biaoqian:
            return Response({'code': 400, 'msg': '商品标题不能为空'})
        try:
            jiage = Decimal(jiage_str)
            if jiage <= 0:
                return Response({'code': 400, 'msg': '价格必须大于0'})
        except:
            return Response({'code': 400, 'msg': '价格格式错误'})
        try:
            kucun = int(kucun_str)
            if kucun < 0:
                return Response({'code': 400, 'msg': '库存不能为负数'})
        except:
            return Response({'code': 400, 'msg': '库存格式错误'})
        try:
            duiwai_xiaoliang = int(duiwai_xiaoliang_str)
        except:
            duiwai_xiaoliang = 0
        try:
            leixing_id = int(leixing_id_str)
        except:
            return Response({'code': 400, 'msg': '商品类型ID无效'})
        try:
            zhuanqu_id = int(zhuanqu_id_str)
        except:
            return Response({'code': 400, 'msg': '商品专区ID无效'})
        try:
            yaoqiuleixing = int(yaoqiuleixing_str)
            if yaoqiuleixing not in (1, 2):
                raise ValueError
        except:
            return Response({'code': 400, 'msg': '抢单要求类型必须为1或2'})

        # 校验类型和专区的存在性及关联性
        try:
            leixing_obj = ShangpinLeixing.query.get(id=leixing_id)
        except ShangpinLeixing.DoesNotExist:
            return Response({'code': 400, 'msg': '商品类型不存在'})
        try:
            zhuanqu_obj = ShangpinZhuanqu.query.get(id=zhuanqu_id)
            if zhuanqu_obj.leixing_id != leixing_id:
                return Response({'code': 400, 'msg': '专区不属于该商品类型'})
        except ShangpinZhuanqu.DoesNotExist:
            return Response({'code': 400, 'msg': '商品专区不存在'})

        # 处理抢单要求相关字段
        huiyuan_id_val = None
        yongjin_val = None
        if yaoqiuleixing == 1:
            # 会员要求
            if not huiyuan_id:
                # 未传递时，尝试从商品类型中获取默认会员ID
                huiyuan_id_val = leixing_obj.huiyuan_id
                if not huiyuan_id_val:
                    return Response({'code': 400, 'msg': '未指定会员且商品类型无默认会员'})
            else:
                huiyuan_id_val = huiyuan_id
                # 验证会员是否存在
                if not Huiyuan.query.filter(huiyuan_id=huiyuan_id_val).exists():
                    return Response({'code': 400, 'msg': '指定的会员不存在'})
        else:
            # 佣金要求
            try:
                yongjin_val = Decimal(yongjin_str)
                if yongjin_val < 0:
                    return Response({'code': 400, 'msg': '佣金不能为负数'})
            except:
                # 若未传递或无效，尝试从商品类型中获取默认佣金
                if leixing_obj.yongjin is not None:
                    yongjin_val = leixing_obj.yongjin
                else:
                    return Response({'code': 400, 'msg': '佣金格式错误且类型无默认佣金'})

        # 处理打手分成方式
        kaioi_ewai = False
        ewai_fencheng = Decimal('0')
        if fencheng_type == 'custom':
            try:
                ewai_fencheng = Decimal(ewai_PlayerCommission_str)
                if ewai_fencheng < 0:
                    return Response({'code': 400, 'msg': '自定义分成金额不能为负数'})
                kaioi_ewai = True
            except:
                return Response({'code': 400, 'msg': '自定义分成金额格式错误'})

        # 图片上传处理
        tupian_url = ''
        if image_file:
            # 验证图片
            valid, msg = validate_image(image_file)
            if not valid:
                return Response({'code': 400, 'msg': f'图片无效：{msg}'})
            # 生成唯一文件名
            ext = image_file.name.split('.')[-1].lower()
            if ext not in ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']:
                return Response({'code': 400, 'msg': '不支持的图片格式'})
            timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
            random_str = uuid.uuid4().hex[:8]
            filename = f"shangpin/shangpintupian/{timestamp}_{random_str}.{ext}"
            # 上传到 OSS
            uploaded_url = upload_to_oss(image_file, filename)
            if not uploaded_url:
                return Response({'code': 500, 'msg': '图片上传失败，请稍后重试'})
            tupian_url = filename  # 存储相对路径，前端拼接域名
        else:
            return Response({'code': 400, 'msg': '商品图片不能为空'})

        # 创建商品
        try:
            with transaction.atomic():
                product = Shangpin.query.create(
                    biaoqian=biaoqian,
                    jiage=jiage,
                    kucun=kucun,
                    leixing_id=leixing_id,
                    zhuanqu_id=zhuanqu_id,
                    zhenshi_xiaoliang=0,          # 初始真实销量为0
                    duiwai_xiaoliang=duiwai_xiaoliang,
                    jieshao=jieshao,
                    xiadan_xuzhi=xiadan_xuzhi,
                    tupian_url=tupian_url,
                    yaoqiuleixing=yaoqiuleixing,
                    huiyuan_id=huiyuan_id_val,
                    yongjin=yongjin_val,
                    kaioi_ewai_dashou_fencheng=kaioi_ewai,
                    ewai_dashou_fencheng=ewai_fencheng,
                    paixu=0,                      # 新商品默认排序最后
                    shenhezhuangtai=1,            # 正常状态
                )
        except Exception as e:
            logger.error(f"创建商品失败: {str(e)}", exc_info=True)
            return Response({'code': 500, 'msg': '创建商品失败，请稍后重试'})

        return Response({
            'code': 0,
            'msg': '商品发布成功',
            'data': {
                'id': product.id,
                'tupian_url': tupian_url,
            }
        })


# houtai/views_product.py 中添加

class DeleteProductView(APIView):
    """
    删除商品（物理删除，同时删除OSS图片）
    权限：2200a
    POST /houtai/htscsp
    参数：
        username: 客服/管理员账号
        id: 商品ID
    返回：
        code:0 成功
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        product_id = request.data.get('id')

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not product_id:
            return Response({'code': 400, 'msg': '缺少商品ID'})
        try:
            product_id = int(product_id)
        except:
            return Response({'code': 400, 'msg': '商品ID格式错误'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '2200a' not in permissions:
            return Response({'code': 403, 'msg': '无权限管理商品'})

        # 查询商品是否存在
        try:
            product = Shangpin.query.get(id=product_id)
        except Shangpin.DoesNotExist:
            return Response({'code': 404, 'msg': '商品不存在'})

        # 先删除OSS上的图片
        tupian_url = product.tupian_url
        if tupian_url:
            # 调用已有的 delete_from_oss 函数
            delete_success = delete_from_oss(tupian_url)
            if not delete_success:
                logger.warning(f"删除商品图片失败: {tupian_url}, 商品ID: {product_id}")
                # 注意：即使图片删除失败，也应继续删除数据库记录，避免数据不一致
                # 但可以记录日志供后续人工处理

        # 删除数据库记录
        product.delete()

        logger.info(f"商品删除成功: id={product_id}, 图片路径={tupian_url}, 操作人={username}")
        return Response({'code': 0, 'msg': '商品删除成功'})







class GetProductDetailView(APIView):
    """
    获取商品详情（包含商品类型、专区、会员列表）
    权限：2200a
    POST /houtai/htsphqxq
    参数：
        username: 客服/管理员账号
        id: 商品ID
    返回：
        code:0, data: { product, type_list, zone_list, member_list }
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        product_id = request.data.get('id')

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not product_id:
            return Response({'code': 400, 'msg': '缺少商品ID'})
        try:
            product_id = int(product_id)
        except ValueError:
            return Response({'code': 400, 'msg': '商品ID格式错误'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '2200a' not in permissions:
            return Response({'code': 403, 'msg': '无权限管理商品'})

        # 查询商品
        try:
            product = Shangpin.query.get(id=product_id)
        except Shangpin.DoesNotExist:
            return Response({'code': 404, 'msg': '商品不存在'})

        # 查询所有商品类型、专区、会员（用于前端下拉选择）
        type_list = list(ShangpinLeixing.query.all().values(
            'id', 'jieshao', 'tupian_url', 'shenhezhuangtai'
        ))
        zone_list = list(ShangpinZhuanqu.query.all().values(
            'id', 'mingzi', 'leixing_id', 'shenhezhuangtai'
        ))
        member_list = list(Huiyuan.query.all().values(
            'huiyuan_id', 'jieshao', 'jiage'
        ))

        # 构建商品返回数据
        product_data = {
            'id': product.id,
            'biaoqian': product.biaoqian,
            'jiage': str(product.jiage),
            'kucun': product.kucun,
            'leixing_id': product.leixing_id,
            'zhuanqu_id': product.zhuanqu_id,
            'zhenshi_xiaoliang': product.zhenshi_xiaoliang,
            'duiwai_xiaoliang': product.duiwai_xiaoliang,
            'jieshao': product.jieshao,
            'xiadan_xuzhi': product.xiadan_xuzhi,
            'guize_tupian': product.guize_tupian,
            'yaoqiuleixing': product.yaoqiuleixing,
            'huiyuan_id': product.huiyuan_id,
            'yongjin': str(product.yongjin) if product.yongjin is not None else None,
            'tupian_url': product.tupian_url,
            'kaioi_ewai_PlayerCommission': product.kaioi_ewai_dashou_fencheng,
            'ewai_PlayerCommission': str(product.ewai_dashou_fencheng) if product.ewai_dashou_fencheng is not None else '0',
            'CreateTime': product.CreateTime.isoformat() if product.CreateTime else None,
            'shenhezhuangtai': product.shenhezhuangtai,
        }

        return Response({
            'code': 0,
            'data': {
                'product': product_data,
                'type_list': type_list,
                'zone_list': zone_list,
                'member_list': member_list,
            }
        })



class UpdateProductView(APIView):
    """
    修改商品（支持商品图片和规则图片的更新）
    权限：2200a
    POST /houtai/bcspxg
    """
    permission_classes = []
    parser_classes = [MultiPartParser, FormParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        product_id = request.data.get('id')

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not product_id:
            return Response({'code': 400, 'msg': '缺少商品ID'})
        try:
            product_id = int(product_id)
        except ValueError:
            return Response({'code': 400, 'msg': '商品ID格式错误'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '2200a' not in permissions:
            return Response({'code': 403, 'msg': '无权限管理商品'})

        # 使用事务，将 select_for_update 放在事务内
        with transaction.atomic():
            try:
                product = Shangpin.objects.select_for_update().get(id=product_id)
            except Shangpin.DoesNotExist:
                return Response({'code': 404, 'msg': '商品不存在'})

            # ---------- 1. 基础字段 ----------
            biaoqian = request.data.get('biaoqian', '').strip()
            if not biaoqian:
                return Response({'code': 400, 'msg': '商品标题不能为空'})

            try:
                jiage = Decimal(str(request.data.get('jiage', 0)))
                if jiage <= 0:
                    return Response({'code': 400, 'msg': '价格必须大于0'})
            except:
                return Response({'code': 400, 'msg': '价格格式错误'})

            try:
                kucun = int(request.data.get('kucun', 0))
                if kucun < 0:
                    return Response({'code': 400, 'msg': '库存不能为负数'})
            except:
                return Response({'code': 400, 'msg': '库存格式错误'})

            try:
                duiwai_xiaoliang = int(request.data.get('duiwai_xiaoliang', 0))
            except:
                duiwai_xiaoliang = 0

            try:
                leixing_id = int(request.data.get('leixing_id'))
            except:
                return Response({'code': 400, 'msg': '商品类型ID无效'})

            try:
                zhuanqu_id = int(request.data.get('zhuanqu_id'))
            except:
                return Response({'code': 400, 'msg': '商品专区ID无效'})

            try:
                yaoqiuleixing = int(request.data.get('yaoqiuleixing', 1))
                if yaoqiuleixing not in (1, 2):
                    raise ValueError
            except:
                return Response({'code': 400, 'msg': '抢单要求类型必须为1或2'})

            try:
                shenhezhuangtai = int(request.data.get('shenhezhuangtai', 1))
            except:
                shenhezhuangtai = 1

            # 校验类型和专区存在性
            if not ShangpinLeixing.query.filter(id=leixing_id).exists():
                return Response({'code': 400, 'msg': '商品类型不存在'})
            if not ShangpinZhuanqu.query.filter(id=zhuanqu_id, leixing_id=leixing_id).exists():
                return Response({'code': 400, 'msg': '专区不存在或不属于该类型'})

            # ---------- 2. 会员/佣金 ----------
            huiyuan_id = None
            yongjin = None
            if yaoqiuleixing == 1:
                huiyuan_id = request.data.get('huiyuan_id', '').strip() or None
                if huiyuan_id and not Huiyuan.query.filter(huiyuan_id=huiyuan_id).exists():
                    return Response({'code': 400, 'msg': '指定的会员不存在'})
            else:
                try:
                    yongjin = Decimal(str(request.data.get('yongjin', 0)))
                    if yongjin < 0:
                        return Response({'code': 400, 'msg': '佣金不能为负数'})
                except:
                    yongjin = Decimal('0')

            # ---------- 3. 打手分成 ----------
            kaioi_ewai = request.data.get('kaioi_ewai_PlayerCommission') == 'true'
            ewai_fencheng = Decimal('0')
            if kaioi_ewai:
                try:
                    ewai_fencheng = Decimal(str(request.data.get('ewai_PlayerCommission', 0)))
                    if ewai_fencheng < 0:
                        return Response({'code': 400, 'msg': '分成金额不能为负数'})
                except:
                    return Response({'code': 400, 'msg': '分成金额格式错误'})

            jieshao = request.data.get('jieshao', '').strip()
            xiadan_xuzhi = request.data.get('xiadan_xuzhi', '').strip()

            # ---------- 4. 商品图片更新 ----------
            tupian_url = product.tupian_url
            tupian_file = request.FILES.get('tupian_file')
            if tupian_file:
                valid, msg = validate_image(tupian_file)
                if not valid:
                    return Response({'code': 400, 'msg': f'商品图片无效：{msg}'})
                ext = tupian_file.name.split('.')[-1].lower()
                if ext not in ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']:
                    return Response({'code': 400, 'msg': '不支持的图片格式'})
                timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
                random_str = uuid.uuid4().hex[:8]
                new_filename = f"shangpin/shangpintupian/{timestamp}_{random_str}.{ext}"
                uploaded_url = upload_to_oss(tupian_file, new_filename)
                if not uploaded_url:
                    return Response({'code': 500, 'msg': '商品图片上传失败'})
                if product.tupian_url:
                    delete_from_oss(product.tupian_url)
                tupian_url = new_filename

            # ---------- 5. 规则图片更新 ----------
            guize_tupian = product.guize_tupian
            guize_file = request.FILES.get('guize_file')
            if guize_file:
                valid, msg = validate_image(guize_file)
                if not valid:
                    return Response({'code': 400, 'msg': f'规则图片无效：{msg}'})
                ext = guize_file.name.split('.')[-1].lower()
                if ext not in ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']:
                    return Response({'code': 400, 'msg': '不支持的图片格式'})
                timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
                random_str = uuid.uuid4().hex[:8]
                new_filename = f"shangpin/guizetupian/{timestamp}_{random_str}.{ext}"
                uploaded_url = upload_to_oss(guize_file, new_filename)
                if not uploaded_url:
                    return Response({'code': 500, 'msg': '规则图片上传失败'})
                if product.guize_tupian:
                    delete_from_oss(product.guize_tupian)
                guize_tupian = new_filename

            # ---------- 6. 更新数据库 ----------
            product.biaoqian = biaoqian
            product.jiage = jiage
            product.kucun = kucun
            product.duiwai_xiaoliang = duiwai_xiaoliang
            product.leixing_id = leixing_id
            product.zhuanqu_id = zhuanqu_id
            product.yaoqiuleixing = yaoqiuleixing
            product.huiyuan_id = huiyuan_id
            product.yongjin = yongjin
            product.kaioi_ewai_dashou_fencheng = kaioi_ewai
            product.ewai_dashou_fencheng = ewai_fencheng
            product.jieshao = jieshao
            product.xiadan_xuzhi = xiadan_xuzhi
            product.tupian_url = tupian_url
            product.guize_tupian = guize_tupian
            product.shenhezhuangtai = shenhezhuangtai
            product.save()

        return Response({'code': 0, 'msg': '商品修改成功', 'data': {'id': product.id}})





class GetMemberListView(APIView):
    """
    获取会员列表
    权限：3300a
    POST /houtai/hthqhylb
    参数：username
    返回：code:0, data: { list: [...] }
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '3300a' not in permissions:
            return Response({'code': 403, 'msg': '无权限查看会员列表'})

        from jituan.services.club_member_admin import build_member_list_payload

        payload = build_member_list_payload(request)
        return Response({
            'code': 0,
            'data': {'list': payload['list']},
            'club_id': payload.get('club_id'),
            'scope': payload.get('scope'),
            'price_note': payload.get('price_note'),
        })


class UpdateMemberView(APIView):
    """
    修改会员信息
    权限：3300a
    POST /houtai/htxghyxx
    参数：
        username, huiyuan_id, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao
    返回：code:0 成功
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        huiyuan_id = request.data.get('huiyuan_id', '').strip()
        jieshao = request.data.get('jieshao', '').strip()
        jiage = request.data.get('jiage')
        guanshifc = request.data.get('guanshifc')
        zuzhangfc = request.data.get('zuzhangfc')
        jtjieshao = request.data.get('jtjieshao', '').strip()

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not huiyuan_id:
            return Response({'code': 400, 'msg': '缺少会员ID'})
        if not jieshao:
            return Response({'code': 400, 'msg': '会员名称不能为空'})

        try:
            jiage = Decimal(str(jiage))
            guanshifc = Decimal(str(guanshifc))
            zuzhangfc = Decimal(str(zuzhangfc))
            if jiage < 0 or guanshifc < 0 or zuzhangfc < 0:
                raise ValueError
        except:
            return Response({'code': 400, 'msg': '金额格式错误'})

        if guanshifc + zuzhangfc > jiage:
            return Response({'code': 400, 'msg': '管事分成+组长分成不能大于会员价格'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '3300a' not in permissions:
            return Response({'code': 403, 'msg': '无权限修改会员'})

        from jituan.services.club_member_admin import update_member_for_club

        extra = {
            'formal_days': request.data.get('formal_days'),
            'trial_enabled': request.data.get('trial_enabled'),
            'trial_price': request.data.get('trial_price'),
            'trial_days': request.data.get('trial_days'),
            'trial_guanshifc': request.data.get('trial_guanshifc'),
            'trial_zuzhangfc': request.data.get('trial_zuzhangfc'),
        }
        data, err = update_member_for_club(
            request, huiyuan_id, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao, extra=extra,
        )
        if err:
            return Response({'code': 400, 'msg': err})
        return Response({'code': 0, 'msg': '修改成功'})



class AddMemberView(APIView):
    """
    添加会员
    权限：3300a
    POST /houtai/httjhy
    参数：
        username, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao
    返回：code:0, data: { huiyuan_id }
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        jieshao = request.data.get('jieshao', '').strip()
        jiage = request.data.get('jiage')
        guanshifc = request.data.get('guanshifc')
        zuzhangfc = request.data.get('zuzhangfc')
        jtjieshao = request.data.get('jtjieshao', '').strip()

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not jieshao:
            return Response({'code': 400, 'msg': '会员名称不能为空'})

        try:
            jiage = Decimal(str(jiage))
            guanshifc = Decimal(str(guanshifc))
            zuzhangfc = Decimal(str(zuzhangfc))
            if jiage <= 0 or guanshifc < 0 or zuzhangfc < 0:
                raise ValueError
        except:
            return Response({'code': 400, 'msg': '金额格式错误'})

        if guanshifc + zuzhangfc > jiage:
            return Response({'code': 400, 'msg': '管事分成+组长分成不能大于会员价格'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '3300a' not in permissions:
            return Response({'code': 403, 'msg': '无权限添加会员'})

        from jituan.services.club_member_admin import add_global_member
        from jituan.services.club_context import resolve_club_scope
        from jituan.constants import DATA_SCOPE_ALL

        if resolve_club_scope(request) != DATA_SCOPE_ALL and '000001' not in permissions:
            return Response({
                'code': 403,
                'msg': '仅集团汇总视图可新建全局会员类型；俱乐部请从会员 catalog 上架',
            })

        data, err = add_global_member(jieshao, jiage, guanshifc, zuzhangfc, jtjieshao)
        if err:
            return Response({'code': 400, 'msg': err})
        return Response({'code': 0, 'msg': '添加成功', 'data': data})




class ClubMemberCatalogView(APIView):
    """俱乐部可上架的集团会员 catalog。POST /houtai/hthycatalog"""
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '3300a' not in permissions:
            return Response({'code': 403, 'msg': '无权限'})
        from jituan.services.club_member_admin import build_member_catalog_payload
        return Response({'code': 0, 'data': build_member_catalog_payload(request)})


class ClubMemberEnableView(APIView):
    """俱乐部上架会员。POST /houtai/hthysj"""
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        huiyuan_id = request.data.get('huiyuan_id', '').strip()
        if not username or not huiyuan_id:
            return Response({'code': 400, 'msg': '参数不完整'})
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '3300a' not in permissions:
            return Response({'code': 403, 'msg': '无权限'})
        try:
            jiage = Decimal(str(request.data.get('jiage')))
            guanshifc = Decimal(str(request.data.get('guanshifc')))
            zuzhangfc = Decimal(str(request.data.get('zuzhangfc')))
        except Exception:
            return Response({'code': 400, 'msg': '金额格式错误'})
        from jituan.services.club_member_admin import enable_member_for_club
        extra = request.data
        data, err = enable_member_for_club(
            request, huiyuan_id, jiage, guanshifc, zuzhangfc, extra=extra,
        )
        if err:
            return Response({'code': 400, 'msg': err})
        return Response({'code': 0, 'msg': '上架成功', 'data': data})


class ClubMemberDisableView(APIView):
    """俱乐部下架会员。POST /houtai/hthyxj"""
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        huiyuan_id = request.data.get('huiyuan_id', '').strip()
        if not username or not huiyuan_id:
            return Response({'code': 400, 'msg': '参数不完整'})
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '3300a' not in permissions:
            return Response({'code': 403, 'msg': '无权限'})
        from jituan.services.club_member_admin import disable_member_for_club
        data, err = disable_member_for_club(request, huiyuan_id)
        if err:
            return Response({'code': 400, 'msg': err})
        return Response({'code': 0, 'msg': '已下架', 'data': data})




class GetGuanliListView(APIView):
    """
    获取管事列表
    权限：4400a,4400b,4400c,4400d,4400e,4400f 任一
    POST /houtai/hthqgslb
    参数：
        username, keyword(可选), balance_op/balance_value, commission_op/commission_value,
        invite_count_op/invite_count_value, status, page, page_size
    返回：
        code:0, data: { list: [...], total }
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})

        # 权限校验（需要任一权限）
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        required_perms = {'4400a', '4400b', '4400c', '4400d', '4400e', '4400f'}
        if not any(p in permissions for p in required_perms):
            return Response({'code': 403, 'msg': '您没有权限查看管事列表'})

        # 分页参数
        try:
            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('page_size', 20))
            if page < 1 or page_size < 1:
                raise ValueError
        except:
            return Response({'code': 400, 'msg': '分页参数错误'})

        # 筛选参数
        keyword = request.data.get('keyword', '').strip()
        balance_op = request.data.get('balance_op', '').strip().lower()
        balance_value = request.data.get('balance_value')
        commission_op = request.data.get('commission_op', '').strip().lower()
        commission_value = request.data.get('commission_value')
        invite_count_op = request.data.get('invite_count_op', '').strip().lower()
        invite_count_value = request.data.get('invite_count_value')
        status = request.data.get('status')

        # 构建查询：从 User 开始，关联 UserGuanshi 和 UserBoss（左连接获取昵称）
        qs = filter_user_qs_by_club(
            User.query.filter(
                GuanshiProfile__isnull=False  # 确保是管事
            ).select_related('GuanshiProfile').select_related('BossProfile'),
            request,
        )

        # 关键词搜索（用户ID 或 昵称来自 boss_profile.nickname）
        if keyword:
            qs = qs.filter(
                Q(UserUID__icontains=keyword) |
                Q(BossProfile__nickname__icontains=keyword)
            )

        # 余额筛选（使用 guanshi_profile.yue）
        if balance_op and balance_value is not None and balance_value != '':
            try:
                balance_val = Decimal(str(balance_value))
                if balance_op == 'gte':
                    qs = qs.filter(GuanshiProfile__yue__gte=balance_val)
                elif balance_op == 'lte':
                    qs = qs.filter(GuanshiProfile__yue__lte=balance_val)
                else:
                    return Response({'code': 400, 'msg': '余额比较符无效'})
            except:
                return Response({'code': 400, 'msg': '余额数值格式错误'})

        # 分佣总额筛选（guanshi_profile.chongzhifenrun）
        if commission_op and commission_value is not None and commission_value != '':
            try:
                commission_val = Decimal(str(commission_value))
                if commission_op == 'gte':
                    qs = qs.filter(GuanshiProfile__chongzhifenrun__gte=commission_val)
                elif commission_op == 'lte':
                    qs = qs.filter(GuanshiProfile__chongzhifenrun__lte=commission_val)
                else:
                    return Response({'code': 400, 'msg': '分佣比较符无效'})
            except:
                return Response({'code': 400, 'msg': '分佣数值格式错误'})

        # 邀请打手总数筛选（guanshi_profile.yaogingshuliang）
        if invite_count_op and invite_count_value is not None and invite_count_value != '':
            try:
                invite_val = int(invite_count_value)
                if invite_count_op == 'gte':
                    qs = qs.filter(GuanshiProfile__yaogingshuliang__gte=invite_val)
                elif invite_count_op == 'lte':
                    qs = qs.filter(GuanshiProfile__yaogingshuliang__lte=invite_val)
                else:
                    return Response({'code': 400, 'msg': '邀请数量比较符无效'})
            except:
                return Response({'code': 400, 'msg': '邀请数量格式错误'})

        # 状态筛选
        if status is not None and status != '':
            try:
                status_val = int(status)
                if status_val not in (0, 1):
                    raise ValueError
                qs = qs.filter(GuanshiProfile__zhuangtai=status_val)
            except:
                return Response({'code': 400, 'msg': '状态值无效'})

        # 计算总数
        total = qs.count()

        # 分页
        offset = (page - 1) * page_size
        users = qs.order_by('-UserCreateTime')[offset:offset + page_size]

        # 构建返回数据
        result = []
        for user in users:
            guanshi = user.GuanshiProfile
            boss = user.BossProfile if hasattr(user, 'BossProfile') else None
            result.append({
                'yonghuid': user.UserUID,
                'avatar': user.Avatar or '',
                'nickname': boss.nickname if boss else '',
                'zhuangtai': guanshi.zhuangtai,
                'yaogingshuliang': guanshi.yaogingshuliang,
                'yue': str(guanshi.yue),
                'chongzhifenrun': str(guanshi.chongzhifenrun),
            })

        return Response({
            'code': 0,
            **list_response_meta(request),
            'data': {'list': result, 'total': total},
        })








class GetGuanliDetailView(APIView):
    """
    获取管事详情
    权限：4400a,4400b,4400c,4400d,4400e,4400f 任一
    POST /houtai/hthqgsxq
    参数：username, yonghuid
    返回：
        code:0, data: {
            base: {...},
            balance: {...},
            permanent_enabled, permanent_amount,
            custom_first_dividend: {},
            extra_dividend_map: {},
            all_members: []
        }
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        yonghuid = request.data.get('yonghuid', '').strip()

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not yonghuid:
            return Response({'code': 400, 'msg': '缺少用户ID'})

        # 权限校验（需要任一权限）
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        required_perms = {'4400a', '4400b', '4400c', '4400d', '4400e', '4400f'}
        if not any(p in permissions for p in required_perms):
            return Response({'code': 403, 'msg': '您没有权限查看管事详情'})

        # 查询用户主表和管事扩展表
        try:
            user = User.query.select_related('GuanshiProfile', 'BossProfile').get(UserUID=yonghuid)
            guanshi = user.GuanshiProfile
            boss = user.BossProfile if hasattr(user, 'BossProfile') else None
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '管事不存在'})
        except UserGuanshi.DoesNotExist:
            return Response({'code': 404, 'msg': '用户不是管事'})

        denied = forbid_if_user_out_of_scope(request, user)
        if denied:
            return denied

        # 基础信息
        is_zuzhang = UserZuzhang.query.filter(user=user).exists()
        base_data = {
            'yonghuid': user.UserUID,
            'nickname': boss.nickname if boss else '',
            'avatar': user.Avatar or '',
            'dianhua': guanshi.dianhua or '',
            'wechat': guanshi.wechat or '',
            'zhuangtai': guanshi.zhuangtai,
            'yaoqingma': guanshi.yaoqingma,
            'yaoqingren': guanshi.yaoqingren,
            'invite_qrcode_url': guanshi.invite_qrcode_url or '',
            'yaogingshuliang': guanshi.yaogingshuliang,
            'jinrichongzhi': guanshi.jinrichongzhi,
            'jinyuechongzhi': guanshi.jinyuechongzhi,
            'chongzhifenrun': str(guanshi.chongzhifenrun),
            'create_time': guanshi.CreateTime.isoformat() if guanshi.CreateTime else None,
            'CreateTime': guanshi.CreateTime.isoformat() if guanshi.CreateTime else None,
        }

        # 余额提现相关
        balance_data = {
            'yue': str(guanshi.yue),
            'jinritixian_jine': str(guanshi.jinritixian_jine),
            'last_tixian_date': guanshi.last_tixian_date.isoformat() if guanshi.last_tixian_date else None,
            'kaioi_ewai_xiane': guanshi.kaioi_ewai_xiane,
            'ewai_xiane': str(guanshi.ewai_xiane),
        }

        # 永久分红（二次分红）
        permanent_enabled = guanshi.fenghong_erci_enabled
        permanent_amount = str(guanshi.fenghong_erci) if guanshi.fenghong_erci else '0'

        # 获取所有会员（用于前端选择）
        all_members = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao', 'jiage', 'guanshifc'))
        for m in all_members:
            m['jiage'] = str(m['jiage'])
            m['guanshifc'] = str(m['guanshifc'])

        # 获取多次分红配置（按次数和会员组织，按俱乐部隔离）
        from jituan.services.club_penalty import resolve_gsfenhong_club_id
        guanshi_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
        duoci_records = DuociFenhong.query.filter(
            yonghuid=yonghuid, club_id=guanshi_club,
        ).order_by('cishu')
        custom_first = {}      # 首次分红定制（cishu=1）
        extra_map = {}         # 其他次数 { cishu: { huiyuan_id: { guanshi_fenhong, jieshao, jiage } } }

        for record in duoci_records:
            # 获取会员信息
            member = Huiyuan.query.filter(huiyuan_id=record.huiyuan).first()
            if not member:
                continue
            member_info = {
                'jieshao': member.jieshao,
                'jiage': str(member.jiage),
            }
            if record.cishu == 1:
                custom_first[record.huiyuan] = float(record.guanshi_fenhong)
            else:
                times = record.cishu
                if times not in extra_map:
                    extra_map[times] = {}
                extra_map[times][record.huiyuan] = {
                    'guanshi_fenhong': float(record.guanshi_fenhong),
                    **member_info
                }

        # 注意：首次分红定制只存储有定制记录的会员，没有定制的会员使用默认值（前端通过会员表展示）
        # 额外次数分红按次数返回

        return Response({
            'code': 0,
            'data': {
                'base': base_data,
                'balance': balance_data,
                'permanent_enabled': permanent_enabled,
                'permanent_amount': permanent_amount,
                'custom_first_dividend': custom_first,
                'extra_dividend_map': extra_map,
                'all_members': all_members,
                'is_zuzhang': is_zuzhang,
            }
        })




def _generate_unique_zuzhang_yaoqingma(yonghuid):
    """生成唯一组长邀请码（与小程序组长注册逻辑一致）"""
    timestamp = str(int(time.time()))[-6:]
    uid_part = yonghuid[-6:] if len(yonghuid) >= 6 else yonghuid.rjust(6, '0')
    rand_str = ''.join(random.choices(string.ascii_uppercase + string.digits, k=2))
    base = f"Z{timestamp}{uid_part}{rand_str}"
    for _ in range(5):
        if not UserZuzhang.query.filter(yaoqingma=base).exists():
            return base
        base = base + random.choice(string.digits)
    return base + str(int(time.time() * 1000))[-4:]


def _create_zuzhang_profile(user):
    """为已有用户创建组长扩展记录（默认字段与小程序注册一致）"""
    invite_guanshi_count = UserGuanshi.query.filter(yaoqingren=user.UserUID).count()
    yaoqingma = _generate_unique_zuzhang_yaoqingma(user.UserUID)
    return UserZuzhang.query.create(
        user=user,
        yaoqingma=yaoqingma,
        fenyong_zonge=Decimal('0.00'),
        ketixian_jine=Decimal('0.00'),
        yaoqing_zongshu=invite_guanshi_count,
        jinri_fenyong=Decimal('0.00'),
        jinyue_fenyong=Decimal('0.00'),
        zhuangtai=1,
        jinri_tixian=Decimal('0.00'),
        ewai_tixian_xiane=Decimal('0.00'),
        kaioi_ewai_tixian=False,
        kaioi_ewai_fenhong=False,
        ewai_fenhong_jine=Decimal('0.00'),
    )


class PromoteGuanshiToZuzhangView(APIView):
    """
    后台将管事用户升级为组长（写入 user_zuzhang 扩展表）
    POST /houtai/htgssjzz
    权限：sjzz666
    参数：username, yonghuid
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        yonghuid = request.data.get('yonghuid', '').strip()

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not yonghuid:
            return Response({'code': 400, 'msg': '缺少用户ID'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if 'sjzz666' not in permissions:
            return Response({'code': 403, 'msg': '无权限升级组长（需要 sjzz666）'})

        try:
            user = User.query.select_related('GuanshiProfile').get(UserUID=yonghuid)
            guanshi = user.GuanshiProfile
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '用户不存在'})
        except UserGuanshi.DoesNotExist:
            return Response({'code': 404, 'msg': '用户不是管事，无法升级组长'})

        if UserZuzhang.query.filter(user=user).exists():
            zuzhang = user.ZuzhangProfile
            return Response({
                'code': 400,
                'msg': '该用户已是组长',
                'data': {
                    'yonghuid': user.UserUID,
                    'yaoqingma': zuzhang.yaoqingma,
                },
            })

        if guanshi.zhuangtai != 1:
            return Response({'code': 400, 'msg': '管事账号已禁用，无法升级组长'})

        try:
            with transaction.atomic():
                zuzhang = _create_zuzhang_profile(user)
        except IntegrityError:
            return Response({'code': 400, 'msg': '升级失败，请稍后重试'})

        write_xiugai_log(
            yonghuid=yonghuid,
            xiugaiid=kefu.user.Phone,
            leixing=XIUGAI_LEIXING_GUANSHI,
            qitashuoming=(
                f'【后台】管事升级组长：用户ID={yonghuid}，'
                f'生成组长邀请码={zuzhang.yaoqingma}，操作人={kefu.user.Phone}'
            ),
        )

        logger.info(f"管事升级组长成功: yonghuid={yonghuid}, yaoqingma={zuzhang.yaoqingma}")
        return Response({
            'code': 0,
            'msg': '已升级为组长',
            'data': {
                'yonghuid': user.UserUID,
                'nickname': getattr(user.BossProfile, 'nickname', '') if hasattr(user, 'BossProfile') else '',
                'yaoqingma': zuzhang.yaoqingma,
                'yaoqing_zongshu': zuzhang.yaoqing_zongshu,
                'ketixian_jine': str(zuzhang.ketixian_jine),
            },
        })




class UpdateGuanliView(APIView):
    """
    修改管事信息（基础信息、余额、提现限额、分红策略）
    权限细分：
        - 修改状态（封禁/解封）及昵称/电话/微信：需要4400a
        - 增加余额：需要4400c
        - 减少余额：需要4400b
        - 修改提现限额（kaioi_ewai_xiane/ewai_xiane）：需要4400e
        - 修改分红相关（永久分红、定制首次、额外次数）：需要4400f
    POST /houtai/htxggsxx
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        yonghuid = request.data.get('yonghuid', '').strip()

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not yonghuid:
            return Response({'code': 400, 'msg': '缺少用户ID'})

        # 权限校验（先获取权限列表）
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        # 查询管事对象（不在事务外加锁，事务内加锁）
        try:
            user = User.query.get(UserUID=yonghuid)
            guanshi = user.GuanshiProfile
            boss = user.BossProfile if hasattr(user, 'BossProfile') else None
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '用户不存在'})
        except UserGuanshi.DoesNotExist:
            return Response({'code': 404, 'msg': '用户不是管事'})

        # 开始事务，内部使用 select_for_update
        with transaction.atomic():
            # 重新获取加锁对象
            user = User.objects.select_for_update().get(UserUID=yonghuid)
            guanshi = user.GuanshiProfile
            boss = user.BossProfile if hasattr(user, 'BossProfile') else None
            operator = kefu.user.Phone

            # ---------- 1. 基础信息修改（需要4400a）----------
            nickname = request.data.get('nickname')
            dianhua = request.data.get('dianhua')
            wechat = request.data.get('wechat')
            zhuangtai = request.data.get('zhuangtai')
            avatar = request.data.get('avatar')

            if any(v is not None for v in [nickname, dianhua, wechat, zhuangtai, avatar]):
                if '4400a' not in permissions:
                    return Response({'code': 403, 'msg': '无权限修改管事基础信息（需要4400a）'})
                base_changes = []
                old_nickname = boss.nickname if boss else ''
                old_dianhua = guanshi.dianhua
                old_wechat = guanshi.wechat
                old_zhuangtai = guanshi.zhuangtai
                # 处理昵称（在老板扩展表中）
                if nickname is not None:
                    if not boss:
                        boss = UserBoss.query.create(user=user, nickname=nickname)
                    else:
                        boss.nickname = nickname
                        boss.save()
                    if str(nickname) != str(old_nickname):
                        base_changes.append(f'昵称：{old_nickname or "无"}→{nickname}')
                # 处理其他字段
                if dianhua is not None:
                    guanshi.dianhua = dianhua
                    if str(dianhua) != str(old_dianhua):
                        base_changes.append(f'电话：{old_dianhua or "无"}→{dianhua}')
                if wechat is not None:
                    guanshi.wechat = wechat
                    if str(wechat) != str(old_wechat):
                        base_changes.append(f'微信：{old_wechat or "无"}→{wechat}')
                if zhuangtai is not None:
                    try:
                        zt = int(zhuangtai)
                        if zt in (0, 1):
                            guanshi.zhuangtai = zt
                            if zt != old_zhuangtai:
                                zt_map = {0: '禁用', 1: '正常'}
                                base_changes.append(f'状态：{zt_map.get(old_zhuangtai, old_zhuangtai)}→{zt_map.get(zt, zt)}')
                    except:
                        pass
                if avatar is not None:
                    user.Avatar = avatar
                    user.save()
                    base_changes.append('更新了头像')
                guanshi.save()
                if base_changes:
                    write_xiugai_log(
                        yonghuid=yonghuid,
                        xiugaiid=operator,
                        leixing=XIUGAI_LEIXING_GUANSHI,
                        qitashuoming=(
                            f'【后台】修改管事基础信息：{"；".join(base_changes)}，'
                            f'管事ID={yonghuid}，操作人={operator}'
                        ),
                    )

            # ---------- 2. 余额修改（加：4400c；减：4400b）----------
            new_yue = request.data.get('yue')
            if new_yue is not None:
                try:
                    new_yue = Decimal(str(new_yue))
                    if new_yue < 0:
                        return Response({'code': 400, 'msg': '余额不能为负数'})
                except:
                    return Response({'code': 400, 'msg': '余额格式错误'})

                old_yue = guanshi.yue
                if new_yue > old_yue:
                    if '4400c' not in permissions:
                        return Response({'code': 403, 'msg': '无权限增加余额（需要4400c）'})
                    action_desc = f'增加{new_yue - old_yue}元'
                elif new_yue < old_yue:
                    if '4400b' not in permissions:
                        return Response({'code': 403, 'msg': '无权限减少余额（需要4400b）'})
                    action_desc = f'减少{old_yue - new_yue}元'
                else:
                    action_desc = '无变动'
                if new_yue != old_yue:
                    guanshi.yue = new_yue
                    guanshi.save()
                    write_xiugai_log(
                        yonghuid=yonghuid,
                        xiugaiid=operator,
                        leixing=XIUGAI_LEIXING_GUANSHI,
                        guanshiyue=new_yue,
                        guanshiyueq=old_yue,
                        qitashuoming=(
                            f'【后台】修改管事可提现余额：{old_yue}元 → {new_yue}元（{action_desc}），'
                            f'管事ID={yonghuid}，操作人={operator}'
                        ),
                    )

            # ---------- 3. 提现限额修改（需要4400e）----------
            kaioi_ewai = request.data.get('kaioi_ewai_xiane')
            ewai_val = request.data.get('ewai_xiane')
            if kaioi_ewai is not None or ewai_val is not None:
                if '4400e' not in permissions:
                    return Response({'code': 403, 'msg': '无权限修改提现限额（需要4400e）'})
                limit_changes = []
                old_kaioi = guanshi.kaioi_ewai_xiane
                old_ewai = guanshi.ewai_xiane
                if kaioi_ewai is not None:
                    guanshi.kaioi_ewai_xiane = bool(kaioi_ewai)
                    if bool(kaioi_ewai) != old_kaioi:
                        limit_changes.append(f'额外限额开关：{"开" if old_kaioi else "关"}→{"开" if kaioi_ewai else "关"}')
                if ewai_val is not None:
                    try:
                        guanshi.ewai_xiane = Decimal(str(ewai_val))
                        if guanshi.ewai_xiane != old_ewai:
                            limit_changes.append(f'额外限额金额：{old_ewai}→{guanshi.ewai_xiane}')
                    except:
                        return Response({'code': 400, 'msg': '额外限额金额格式错误'})
                guanshi.save()
                if limit_changes:
                    write_xiugai_log(
                        yonghuid=yonghuid,
                        xiugaiid=operator,
                        leixing=XIUGAI_LEIXING_GUANSHI,
                        qitashuoming=(
                            f'【后台】修改管事提现限额：{"；".join(limit_changes)}，'
                            f'管事ID={yonghuid}，操作人={operator}'
                        ),
                    )

            # ---------- 4. 分红相关修改（需要4400f）----------
            perm_enabled = request.data.get('permanent_enabled')
            perm_amount = request.data.get('permanent_amount')
            custom_first = request.data.get('custom_first_dividend')
            extra_map = request.data.get('extra_dividend_map')

            if any(v is not None for v in [perm_enabled, perm_amount, custom_first, extra_map]):
                if '4400f' not in permissions:
                    return Response({'code': 403, 'msg': '无权限修改分红策略（需要4400f）'})

                # 4.1 永久分红
                if perm_enabled is not None:
                    guanshi.fenghong_erci_enabled = bool(perm_enabled)
                if perm_amount is not None:
                    try:
                        guanshi.fenghong_erci = Decimal(str(perm_amount))
                    except:
                        return Response({'code': 400, 'msg': '永久分红金额格式错误'})
                guanshi.save()

                # 4.2 首次定制分红（cishu=1）
                if custom_first is not None and isinstance(custom_first, dict):
                    from jituan.services.club_penalty import resolve_gsfenhong_club_id
                    guanshi_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
                    # 获取当前所有首次定制记录
                    existing_first = DuociFenhong.query.filter(
                        club_id=guanshi_club, yonghuid=yonghuid, cishu=1,
                    )
                    # 前端传来的定制字典 {会员ID: 金额}
                    for huiyuan_id, amount in custom_first.items():
                        try:
                            amount = Decimal(str(amount))
                        except:
                            continue
                        if amount <= 0:
                            DuociFenhong.query.filter(
                                club_id=guanshi_club, yonghuid=yonghuid, huiyuan=huiyuan_id, cishu=1,
                            ).delete()
                        else:
                            DuociFenhong.query.update_or_create(
                                club_id=guanshi_club,
                                yonghuid=yonghuid,
                                huiyuan=huiyuan_id,
                                cishu=1,
                                defaults={
                                    'guanshi_fenhong': amount,
                                    'zuzhang_fenhong': Decimal('0'),
                                },
                            )
                    # 删除那些在前端字典中不存在的首次定制记录（即已取消定制）
                    for record in existing_first:
                        if record.huiyuan not in custom_first:
                            record.delete()

                # 4.3 额外次数分红（cishu >= 2）
                if extra_map is not None and isinstance(extra_map, dict):
                    from jituan.services.club_penalty import resolve_gsfenhong_club_id
                    guanshi_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
                    # 收集需要保留的记录
                    keep_set = set()
                    for times_str, members in extra_map.items():
                        try:
                            cishu = int(times_str)
                            if cishu < 2:
                                continue
                        except:
                            continue
                        if not isinstance(members, dict):
                            continue
                        for huiyuan_id, data in members.items():
                            if not isinstance(data, dict):
                                continue
                            amount = data.get('guanshi_fenhong')
                            if amount is None:
                                continue
                            try:
                                amount = Decimal(str(amount))
                            except:
                                continue
                            keep_set.add((cishu, huiyuan_id))
                            # 更新或创建
                            DuociFenhong.query.update_or_create(
                                club_id=guanshi_club,
                                yonghuid=yonghuid,
                                huiyuan=huiyuan_id,
                                cishu=cishu,
                                defaults={
                                    'guanshi_fenhong': amount,
                                    'zuzhang_fenhong': Decimal('0')
                                }
                            )
                    # 删除不在 keep_set 中的额外次数记录
                    existing_extra = DuociFenhong.query.filter(
                        club_id=guanshi_club, yonghuid=yonghuid, cishu__gte=2,
                    )
                    for record in existing_extra:
                        if (record.cishu, record.huiyuan) not in keep_set:
                            record.delete()

                dividend_parts = []
                if perm_enabled is not None:
                    dividend_parts.append(f'永久分红开关→{"开" if perm_enabled else "关"}')
                if perm_amount is not None:
                    dividend_parts.append(f'永久分红金额→{perm_amount}')
                if custom_first is not None:
                    dividend_parts.append(f'更新首次定制分红({len(custom_first)}项)')
                if extra_map is not None:
                    dividend_parts.append(f'更新额外次数分红({len(extra_map)}组)')
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=operator,
                    leixing=XIUGAI_LEIXING_GUANSHI,
                    qitashuoming=(
                        f'【后台】修改管事分红策略：{"；".join(dividend_parts) or "配置已更新"}，'
                        f'管事ID={yonghuid}，操作人={operator}'
                    ),
                )

        return Response({'code': 0, 'msg': '修改成功'})









class GetWithdrawSettingsView(APIView):
    """
    获取提现设置（手续费利率、每日限额、当日已提现总额）
    权限：需要 5500a 或 5500b 或 5500c 任一
    POST /houtai/hthqtxpz
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        # 需要5500a/b/c任一
        if not any(p in permissions for p in ('5500a', '5500b', '5500c')):
            return Response({'code': 403, 'msg': '无权限查看提现设置'})

        from jituan.services.withdraw_config import build_withdraw_settings_payload
        return Response({'code': 0, 'data': build_withdraw_settings_payload(request)})


class UpdateWithdrawSettingsView(APIView):
    """
    修改提现设置（权限细分）：
        - 修改打手相关（利率、个人限额、总限额）需要5500a
        - 修改管事相关需要5500b
        - 修改组长相关需要5500c
    注意：修改每日总限额时，会联动影响“当日已提现总额”的显示（由前端实时计算），
    后端不直接修改 WithdrawalDailyStats，该表由提现流程自动累加。
    POST /houtai/htxgtxsz
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        # 权限映射：要修改的角色需要对应的权限
        # 前端传来 rates, quotas, total_limits 三个对象，每个包含 1,2,3
        from jituan.services.withdraw_config import apply_withdraw_settings_update
        return apply_withdraw_settings_update(request, permissions)



class GetZuzhangListView(APIView):
    """
    组长列表接口（分页 + 多条件筛选）
    路由：POST /houtai/hthqzzlb
    权限：需要登录，且拥有 6600a~6600e 任一权限
    """
    permission_classes = [IsAuthenticated]

    @transaction.atomic  # 保证查询的一致性（可选，对只读查询影响不大但无坏处）
    def post(self, request):
        # 1. 获取前端传递的 username（用于防越权）
        frontend_username = request.data.get('username', '')

        # 2. 调用公共校验方法，验证身份并获取权限列表
        result = verify_kefu_permission(request, username_from_frontend=frontend_username)

        # 重要：verify_kefu_permission 返回格式为 (user_obj, permissions_list) 或 (None, Response)
        # 如果第二个元素是 Response 对象，说明校验失败，直接返回该 Response
        if isinstance(result, tuple) and len(result) == 2:
            user_obj, second = result
            if isinstance(second, Response):
                return second  # 校验失败，直接返回错误响应
            else:
                permissions = second  # 正常情况：permissions 是列表
        else:
            # 防止意外返回格式，视为失败
            return Response({'code': 500, 'msg': '权限校验模块异常'})

        # 3. 检查是否拥有组长管理所需权限（6600a ~ 6600e 任一）
        required_perms = ['6600a', '6600b', '6600c', '6600d', '6600e']
        if not any(perm in permissions for perm in required_perms):
            logger.warning(f"用户 {request.user.Phone} 尝试访问组长列表但缺少必要权限，已有权限: {permissions}")
            return Response({'code': 403, 'msg': '您无权访问组长管理页面'})

        # 4. 提取请求参数（分页 + 筛选）
        try:
            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('page_size', 20))
        except ValueError:
            page, page_size = 1, 20

        keyword = request.data.get('keyword', '').strip()
        status = request.data.get('status')  # 1=正常, 0=禁用
        invite_count_op = request.data.get('invite_count_op')  # gte / lte
        invite_count_value = request.data.get('invite_count_value')
        amount_op = request.data.get('amount_op')
        amount_value = request.data.get('amount_value')
        commission_op = request.data.get('commission_op')
        commission_value = request.data.get('commission_value')

        # 5. 构建基础 QuerySet（预加载关联表）
        queryset = filter_user_related_by_club(
            UserZuzhang.query.select_related('user', 'user__BossProfile').all(),
            request,
        )

        # 6. 应用筛选条件（使用 ORM 安全过滤）
        if keyword:
            queryset = queryset.filter(
                Q(user__UserUID__icontains=keyword) |
                Q(user__BossProfile__nickname__icontains=keyword)
            )

        if status is not None and status != '':
            try:
                queryset = queryset.filter(zhuangtai=int(status))
            except ValueError:
                pass

        # 邀请总数筛选
        if invite_count_value not in (None, ''):
            try:
                val = float(invite_count_value)
                op = invite_count_op if invite_count_op in ('gte', 'lte') else 'gte'
                queryset = queryset.filter(**{f'yaoqing_zongshu__{op}': val})
            except (ValueError, TypeError):
                pass

        # 可提现金额筛选
        if amount_value not in (None, ''):
            try:
                val = float(amount_value)
                op = amount_op if amount_op in ('gte', 'lte') else 'gte'
                queryset = queryset.filter(**{f'ketixian_jine__{op}': val})
            except (ValueError, TypeError):
                pass

        # 分佣总额筛选
        if commission_value not in (None, ''):
            try:
                val = float(commission_value)
                op = commission_op if commission_op in ('gte', 'lte') else 'gte'
                queryset = queryset.filter(**{f'fenyong_zonge__{op}': val})
            except (ValueError, TypeError):
                pass

        # 7. 总数统计
        total = queryset.count()

        # 8. 分页
        offset = (page - 1) * page_size
        paged_queryset = queryset[offset:offset + page_size]

        # 9. 构造返回数据
        data_list = []
        for zu in paged_queryset:
            user = zu.user
            nickname = ''
            if hasattr(user, 'BossProfile') and user.BossProfile:
                nickname = user.BossProfile.nickname or ''

            data_list.append({
                'yonghuid': user.UserUID,
                'avatar': user.Avatar or '',
                'nickname': nickname,
                'fenyong_zonge': float(zu.fenyong_zonge),
                'ketixian_jine': float(zu.ketixian_jine),
                'yaoqing_zongshu': zu.yaoqing_zongshu,
                'zhuangtai': zu.zhuangtai,
            })

        return Response({
            'code': 0,
            'msg': 'success',
            **list_response_meta(request),
            'data': {
                'list': data_list,
                'total': total,
            }
        })










class GetZuzhangDetailView(APIView):
    """
    获取组长详情
    权限：6600a,6600b,6600c,6600d,6600e 任一
    POST /houtai/hthqzzxq
    参数：username, yonghuid
    返回：
        code:0, data: {
            base: {...},
            balance: {...},
            permanent_enabled, permanent_amount,
            custom_first_dividend: {},
            extra_dividend_map: {},
            all_members: []
        }
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        yonghuid = request.data.get('yonghuid', '').strip()

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not yonghuid:
            return Response({'code': 400, 'msg': '缺少用户ID'})

        # 权限校验（需要任一权限）
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        required_perms = {'6600a', '6600b', '6600c', '6600d', '6600e'}
        if not any(p in permissions for p in required_perms):
            return Response({'code': 403, 'msg': '您没有权限查看组长详情'})

        # 查询组长主表、扩展表、老板扩展表（昵称）
        try:
            user = User.query.select_related('ZuzhangProfile', 'BossProfile').get(UserUID=yonghuid)
            zuzhang = user.ZuzhangProfile
            boss = user.BossProfile if hasattr(user, 'BossProfile') else None
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '组长不存在'})
        except UserZuzhang.DoesNotExist:
            return Response({'code': 404, 'msg': '用户不是组长'})

        denied = forbid_if_user_out_of_scope(request, user)
        if denied:
            return denied

        # 基础信息
        base_data = {
            'yonghuid': user.UserUID,
            'nickname': boss.nickname if boss else '',
            'avatar': user.Avatar or '',
            'dianhua': '',          # 组长表无电话字段？组长扩展表没有电话/微信，但前端可能需要，可从User关联？实际上组长表无电话字段，这里留空或从其他表获取
            'wechat': '',
            'zhuangtai': zuzhang.zhuangtai,
            'yaoqingma': zuzhang.yaoqingma,
            'yaoqingren': '',       # 组长表没有邀请人字段
            'haibao_url': zuzhang.haibao_url or '',
            'yaoqing_zongshu': zuzhang.yaoqing_zongshu,
            'jinri_fenyong': str(zuzhang.jinri_fenyong),
            'jinyue_fenyong': str(zuzhang.jinyue_fenyong),
            'fenyong_zonge': str(zuzhang.fenyong_zonge),
            'CreateTime': zuzhang.CreateTime.isoformat() if zuzhang.CreateTime else None,
            'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '',
        }

        # 余额提现相关
        balance_data = {
            'ketixian_jine': str(zuzhang.ketixian_jine),
            'jinri_tixian': str(zuzhang.jinri_tixian),
            'last_tixian_time': zuzhang.last_tixian_time.isoformat() if zuzhang.last_tixian_time else None,
            'kaioi_ewai_tixian': zuzhang.kaioi_ewai_tixian,
            'ewai_tixian_xiane': str(zuzhang.ewai_tixian_xiane),
        }

        # 永久分红（额外分红开关和金额）
        permanent_enabled = zuzhang.kaioi_ewai_fenhong
        permanent_amount = str(zuzhang.ewai_fenhong_jine) if zuzhang.ewai_fenhong_jine else '0'

        # 获取所有会员（用于前端选择）
        all_members = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao', 'jiage', 'zuzhangfc'))
        for m in all_members:
            m['jiage'] = str(m['jiage'])
            m['zuzhangfc'] = str(m['zuzhangfc'])

        # 获取多次分红配置（按次数和会员组织，按俱乐部隔离）
        from jituan.services.club_penalty import resolve_gsfenhong_club_id
        zuzhang_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
        duoci_records = DuociFenhong.query.filter(
            yonghuid=yonghuid, club_id=zuzhang_club,
        ).order_by('cishu')
        custom_first = {}      # 首次分红定制（cishu=1）
        extra_map = {}         # 其他次数 { cishu: { huiyuan_id: { zuzhang_fenhong, jieshao, jiage } } }

        for record in duoci_records:
            # 获取会员信息
            member = Huiyuan.query.filter(huiyuan_id=record.huiyuan).first()
            if not member:
                continue
            member_info = {
                'jieshao': member.jieshao,
                'jiage': str(member.jiage),
            }
            if record.cishu == 1:
                custom_first[record.huiyuan] = float(record.zuzhang_fenhong)
            else:
                times = record.cishu
                if times not in extra_map:
                    extra_map[times] = {}
                extra_map[times][record.huiyuan] = {
                    'zuzhang_fenhong': float(record.zuzhang_fenhong),
                    **member_info
                }

        return Response({
            'code': 0,
            'data': {
                'base': base_data,
                'balance': balance_data,
                'permanent_enabled': permanent_enabled,
                'permanent_amount': permanent_amount,
                'custom_first_dividend': custom_first,
                'extra_dividend_map': extra_map,
                'all_members': all_members,
            }
        })




class UpdateZuzhangView(APIView):
    """
    修改组长信息（基础信息、余额、提现限额、分红策略）
    权限细分：
        - 增加可提现金额：6600a
        - 减少可提现金额：6600b
        - 修改状态（封禁/解封）：6600c
        - 修改提现限额（kaioi_ewai_tixian/ewai_tixian_xiane）：6600d
        - 修改分红相关（永久分红、定制首次、额外次数）：6600e
    POST /houtai/htxgzzxx
    参数：
        username, yonghuid,
        nickname, avatar, dianhua, wechat, zhuangtai,   # 基础
        ketixian_jine, kaioi_ewai_tixian, ewai_tixian_xiane,  # 余额提现
        permanent_enabled, permanent_amount,            # 永久分红
        custom_first_dividend: {},                      # 首次定制分红 {会员ID: 金额}
        extra_dividend_map: {}                          # 额外次数分红 {次数: {会员ID: {zuzhang_fenhong}}}
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        yonghuid = request.data.get('yonghuid', '').strip()

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not yonghuid:
            return Response({'code': 400, 'msg': '缺少用户ID'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        # 查询组长对象
        try:
            user = User.query.get(UserUID=yonghuid)
            zuzhang = user.ZuzhangProfile
            boss = user.BossProfile if hasattr(user, 'BossProfile') else None
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '用户不存在'})
        except UserZuzhang.DoesNotExist:
            return Response({'code': 404, 'msg': '用户不是组长'})

        # 获取原始数据（用于对比金额变化）
        old_ketixian_jine = zuzhang.ketixian_jine

        # 开始事务
        with transaction.atomic():
            operator = kefu.user.Phone
            old_nickname = boss.nickname if boss else ''
            old_zhuangtai = zuzhang.zhuangtai
            # ---------- 1. 基础信息修改（需要6600c？按需求，基础信息修改通常用6600c，但为了安全，暂不校验，如有需要可加）----------
            nickname = request.data.get('nickname', '').strip()
            avatar = request.data.get('avatar', '').strip()
            dianhua = request.data.get('dianhua', '').strip()
            wechat = request.data.get('wechat', '').strip()
            zhuangtai = request.data.get('zhuangtai')
            base_changes = []
            # 状态修改需要6600c
            if zhuangtai is not None:
                if '6600c' not in permissions:
                    return Response({'code': 403, 'msg': '无权限修改组长状态（需要6600c）'})
                try:
                    zt = int(zhuangtai)
                    if zt in (0, 1):
                        zuzhang.zhuangtai = zt
                        if zt != old_zhuangtai:
                            zt_map = {0: '禁用', 1: '正常'}
                            base_changes.append(f'状态：{zt_map.get(old_zhuangtai, old_zhuangtai)}→{zt_map.get(zt, zt)}')
                except:
                    pass
            if nickname:
                if not boss:
                    boss = UserBoss.query.create(user=user, nickname=nickname)
                else:
                    boss.nickname = nickname
                    boss.save()
                if nickname != old_nickname:
                    base_changes.append(f'昵称：{old_nickname or "无"}→{nickname}')
            if avatar:
                user.Avatar = avatar
                user.save()
                base_changes.append('更新了头像')
            if base_changes:
                zuzhang.save()
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=operator,
                    leixing=XIUGAI_LEIXING_ZUZHANG,
                    qitashuoming=(
                        f'【后台】修改组长基础信息：{"；".join(base_changes)}，'
                        f'组长ID={yonghuid}，操作人={operator}'
                    ),
                )
            # 组长表没有电话/微信字段，如需存储可暂存到User或忽略
            # 这里不处理

            # ---------- 2. 可提现金额修改（加：6600a；减：6600b）----------
            new_ketixian_jine = request.data.get('ketixian_jine')
            if new_ketixian_jine is not None:
                try:
                    new_val = Decimal(str(new_ketixian_jine))
                    if new_val < 0:
                        return Response({'code': 400, 'msg': '可提现金额不能为负数'})
                except:
                    return Response({'code': 400, 'msg': '金额格式错误'})
                if new_val > old_ketixian_jine:
                    if '6600a' not in permissions:
                        return Response({'code': 403, 'msg': '无权限增加可提现金额（需要6600a）'})
                    action_desc = f'增加{new_val - old_ketixian_jine}元'
                elif new_val < old_ketixian_jine:
                    if '6600b' not in permissions:
                        return Response({'code': 403, 'msg': '无权限减少可提现金额（需要6600b）'})
                    action_desc = f'减少{old_ketixian_jine - new_val}元'
                else:
                    action_desc = '无变动'
                if new_val != old_ketixian_jine:
                    zuzhang.ketixian_jine = new_val
                    zuzhang.save(update_fields=['ketixian_jine'])
                    write_xiugai_log(
                        yonghuid=yonghuid,
                        xiugaiid=operator,
                        leixing=XIUGAI_LEIXING_ZUZHANG,
                        xiugaitijiao=new_val,
                        xiugaitijiaoq=old_ketixian_jine,
                        qitashuoming=(
                            f'【后台】修改组长可提现金额：{old_ketixian_jine}元 → {new_val}元（{action_desc}），'
                            f'组长ID={yonghuid}，操作人={operator}'
                        ),
                    )

            # ---------- 3. 提现限额修改（需要6600d）----------
            kaioi_ewai = request.data.get('kaioi_ewai_tixian')
            ewai_val = request.data.get('ewai_tixian_xiane')
            if kaioi_ewai is not None or ewai_val is not None:
                if '6600d' not in permissions:
                    return Response({'code': 403, 'msg': '无权限修改提现限额（需要6600d）'})
                limit_changes = []
                old_kaioi = zuzhang.kaioi_ewai_tixian
                old_ewai = zuzhang.ewai_tixian_xiane
                if kaioi_ewai is not None:
                    zuzhang.kaioi_ewai_tixian = bool(kaioi_ewai)
                    if bool(kaioi_ewai) != old_kaioi:
                        limit_changes.append(f'额外限额开关：{"开" if old_kaioi else "关"}→{"开" if kaioi_ewai else "关"}')
                if ewai_val is not None:
                    try:
                        zuzhang.ewai_tixian_xiane = Decimal(str(ewai_val))
                        if zuzhang.ewai_tixian_xiane != old_ewai:
                            limit_changes.append(f'额外限额金额：{old_ewai}→{zuzhang.ewai_tixian_xiane}')
                    except:
                        return Response({'code': 400, 'msg': '额外限额金额格式错误'})
                zuzhang.save()
                if limit_changes:
                    write_xiugai_log(
                        yonghuid=yonghuid,
                        xiugaiid=operator,
                        leixing=XIUGAI_LEIXING_ZUZHANG,
                        qitashuoming=(
                            f'【后台】修改组长提现限额：{"；".join(limit_changes)}，'
                            f'组长ID={yonghuid}，操作人={operator}'
                        ),
                    )

            # ---------- 4. 分红相关修改（需要6600e）----------
            perm_enabled = request.data.get('permanent_enabled')
            perm_amount = request.data.get('permanent_amount')
            custom_first = request.data.get('custom_first_dividend')
            extra_map = request.data.get('extra_dividend_map')

            if any(v is not None for v in [perm_enabled, perm_amount, custom_first, extra_map]):
                if '6600e' not in permissions:
                    return Response({'code': 403, 'msg': '无权限修改分红策略（需要6600e）'})

                # 4.1 永久分红（额外分红开关和金额）
                if perm_enabled is not None:
                    zuzhang.kaioi_ewai_fenhong = bool(perm_enabled)
                if perm_amount is not None:
                    try:
                        zuzhang.ewai_fenhong_jine = Decimal(str(perm_amount))
                    except:
                        return Response({'code': 400, 'msg': '永久分红金额格式错误'})
                zuzhang.save()

                # 4.2 首次定制分红（cishu=1）
                if custom_first is not None and isinstance(custom_first, dict):
                    from jituan.services.club_penalty import resolve_gsfenhong_club_id
                    zuzhang_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
                    existing_first = DuociFenhong.query.filter(
                        club_id=zuzhang_club, yonghuid=yonghuid, cishu=1,
                    )
                    for huiyuan_id, amount in custom_first.items():
                        try:
                            amount = Decimal(str(amount))
                        except:
                            continue
                        if amount <= 0:
                            # 删除定制（恢复默认）
                            DuociFenhong.query.filter(
                                club_id=zuzhang_club, yonghuid=yonghuid, huiyuan=huiyuan_id, cishu=1,
                            ).delete()
                        else:
                            DuociFenhong.query.update_or_create(
                                club_id=zuzhang_club,
                                yonghuid=yonghuid,
                                huiyuan=huiyuan_id,
                                cishu=1,
                                defaults={
                                    'zuzhang_fenhong': amount,
                                    'guanshi_fenhong': Decimal('0')  # 组长表只关心组长分红
                                }
                            )
                    # 删除那些在前端字典中不存在的首次定制记录（即已取消定制）
                    for record in existing_first:
                        if record.huiyuan not in custom_first:
                            record.delete()

                # 4.3 额外次数分红（cishu >= 2）
                if extra_map is not None and isinstance(extra_map, dict):
                    from jituan.services.club_penalty import resolve_gsfenhong_club_id
                    zuzhang_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
                    keep_set = set()
                    for times_str, members in extra_map.items():
                        try:
                            cishu = int(times_str)
                            if cishu < 2:
                                continue
                        except:
                            continue
                        if not isinstance(members, dict):
                            continue
                        for huiyuan_id, data in members.items():
                            if not isinstance(data, dict):
                                continue
                            amount = data.get('zuzhang_fenhong')
                            if amount is None:
                                continue
                            try:
                                amount = Decimal(str(amount))
                            except:
                                continue
                            keep_set.add((cishu, huiyuan_id))
                            DuociFenhong.query.update_or_create(
                                club_id=zuzhang_club,
                                yonghuid=yonghuid,
                                huiyuan=huiyuan_id,
                                cishu=cishu,
                                defaults={
                                    'zuzhang_fenhong': amount,
                                    'guanshi_fenhong': Decimal('0')
                                }
                            )
                    # 删除不在 keep_set 中的额外次数记录
                    existing_extra = DuociFenhong.query.filter(
                        club_id=zuzhang_club, yonghuid=yonghuid, cishu__gte=2,
                    )
                    for record in existing_extra:
                        if (record.cishu, record.huiyuan) not in keep_set:
                            record.delete()

                dividend_parts = []
                if perm_enabled is not None:
                    dividend_parts.append(f'永久分红开关→{"开" if perm_enabled else "关"}')
                if perm_amount is not None:
                    dividend_parts.append(f'永久分红金额→{perm_amount}')
                if custom_first is not None:
                    dividend_parts.append(f'更新首次定制分红({len(custom_first)}项)')
                if extra_map is not None:
                    dividend_parts.append(f'更新额外次数分红({len(extra_map)}组)')
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=operator,
                    leixing=XIUGAI_LEIXING_ZUZHANG,
                    qitashuoming=(
                        f'【后台】修改组长分红策略：{"；".join(dividend_parts) or "配置已更新"}，'
                        f'组长ID={yonghuid}，操作人={operator}'
                    ),
                )

            # 保存组长其他可能修改的字段（例如电话、微信没有，忽略）

        return Response({'code': 0, 'msg': '修改成功'})


class ChangeInviterView(APIView):
    """
    后台更换邀请人/上级
    POST /houtai/htghyqr
    参数：
        username: 操作人账号
        yonghuid: 被修改用户ID
        target_type: dashou（打手）| guanshi（管事）
        new_yaoqingren: 新邀请人用户ID（须在 User 主表存在）
    权限：
        dashou → 001ee
        guanshi → 4400a
    校验：
        打手新上级须为管事；管事新上级须为组长；不可设为自己
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        yonghuid = request.data.get('yonghuid', '').strip()
        target_type = request.data.get('target_type', '').strip().lower()
        new_yaoqingren = request.data.get('new_yaoqingren', '').strip()

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not yonghuid:
            return Response({'code': 400, 'msg': '缺少用户ID'})
        if target_type not in ('dashou', 'guanshi'):
            return Response({'code': 400, 'msg': 'target_type 须为 dashou 或 guanshi'})
        if not new_yaoqingren:
            return Response({'code': 400, 'msg': '缺少新邀请人ID'})
        if new_yaoqingren == yonghuid:
            return Response({'code': 400, 'msg': '不能将自己设为邀请人'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        if target_type == 'dashou':
            if '001ee' not in permissions:
                return Response({'code': 403, 'msg': '无权限更换打手邀请人（需要001ee）'})
        else:
            if '4400a' not in permissions:
                return Response({'code': 403, 'msg': '无权限更换管事邀请人（需要4400a）'})

        try:
            target_user = User.query.get(UserUID=yonghuid)
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '被修改用户不存在'})

        try:
            inviter_user = User.query.get(UserUID=new_yaoqingren)
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '新邀请人用户ID不存在'})

        operator = kefu.user.Phone

        with transaction.atomic():
            if target_type == 'dashou':
                try:
                    dashou = target_user.DashouProfile
                except UserDashou.DoesNotExist:
                    return Response({'code': 404, 'msg': '用户不是打手'})
                try:
                    guanshi_profile = inviter_user.GuanshiProfile
                except UserGuanshi.DoesNotExist:
                    return Response({'code': 400, 'msg': '新邀请人不是管事，无法作为打手上级'})
                if guanshi_profile.zhuangtai != 1:
                    return Response({'code': 400, 'msg': '新邀请人管事账号已禁用'})
                old_inviter = dashou.yaoqingren or '无'
                dashou.yaoqingren = new_yaoqingren
                dashou.save(update_fields=['yaoqingren'])
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=operator,
                    leixing=XIUGAI_LEIXING_DASHOU,
                    qitashuoming=(
                        f'【后台】更换打手邀请人：{old_inviter} → {new_yaoqingren}，'
                        f'打手ID={yonghuid}，新上级须为管事（已校验），操作人={operator}'
                    ),
                )
            else:
                try:
                    guanshi = target_user.GuanshiProfile
                except UserGuanshi.DoesNotExist:
                    return Response({'code': 404, 'msg': '用户不是管事'})
                try:
                    zuzhang_profile = inviter_user.ZuzhangProfile
                except UserZuzhang.DoesNotExist:
                    return Response({'code': 400, 'msg': '新邀请人不是组长，无法作为管事上级'})
                if zuzhang_profile.zhuangtai != 1:
                    return Response({'code': 400, 'msg': '新邀请人组长账号已禁用'})
                old_inviter = guanshi.yaoqingren or '无'
                guanshi.yaoqingren = new_yaoqingren
                guanshi.save(update_fields=['yaoqingren'])
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=operator,
                    leixing=XIUGAI_LEIXING_GUANSHI,
                    qitashuoming=(
                        f'【后台】更换管事邀请人：{old_inviter} → {new_yaoqingren}，'
                        f'管事ID={yonghuid}，新上级须为组长（已校验），操作人={operator}'
                    ),
                )

        return Response({
            'code': 0,
            'msg': '邀请人更换成功',
            'data': {'yonghuid': yonghuid, 'new_yaoqingren': new_yaoqingren},
        })


class GetOperationLogListView(APIView):
    """
    后台操作日志列表（xiugaijilu）
    POST /houtai/hqczrz
    权限：000001 超级权限 或 czrz666
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '000001' not in permissions and 'czrz666' not in permissions:
            return Response({'code': 403, 'msg': '无权限查看操作日志（需要000001或czrz666）'})

        yonghuid = request.data.get('yonghuid', '').strip()
        xiugaiid = request.data.get('xiugaiid', '').strip()
        leixing = request.data.get('leixing')
        keyword = request.data.get('keyword', '').strip()
        try:
            page = max(1, int(request.data.get('page', 1)))
        except (ValueError, TypeError):
            page = 1
        try:
            page_size = min(100, max(1, int(request.data.get('page_size', 20))))
        except (ValueError, TypeError):
            page_size = 20

        qs = Xiugaijilu.query.all().order_by('-CreateTime')
        from jituan.services.admin_audit import filter_xiugaijilu_by_request
        qs = filter_xiugaijilu_by_request(qs, request)
        if yonghuid:
            qs = qs.filter(yonghuid=yonghuid)
        if xiugaiid:
            qs = qs.filter(xiugaiid__icontains=xiugaiid)
        if leixing not in (None, ''):
            try:
                qs = qs.filter(leixing=int(leixing))
            except (ValueError, TypeError):
                pass
        if keyword:
            qs = qs.filter(qitashuoming__icontains=keyword)

        total = qs.count()
        start = (page - 1) * page_size
        records = qs[start:start + page_size]

        def _fmt_decimal(val):
            if val is None:
                return None
            return str(val)

        data_list = []
        for r in records:
            data_list.append({
                'id': r.id,
                'yonghuid': r.yonghuid,
                'xiugaiid': r.xiugaiid,
                'leixing': r.leixing,
                'leixing_label': XIUGAI_LEIXING_LABEL.get(r.leixing, f'类型{r.leixing}'),
                'xiugaitijiao': _fmt_decimal(r.xiugaitijiao),
                'xiugaitijiaoq': _fmt_decimal(r.xiugaitijiaoq),
                'jifen': r.jifen,
                'yjifen': r.yjifen,
                'yajin': _fmt_decimal(r.yajin),
                'yyajin': _fmt_decimal(r.yyajin),
                'shangjiayue': _fmt_decimal(r.shangjiayue),
                'shangjiayueq': _fmt_decimal(r.shangjiayueq),
                'guanshiyue': _fmt_decimal(r.guanshiyue),
                'guanshiyueq': _fmt_decimal(r.guanshiyueq),
                'huiyuants': r.huiyuants,
                'huiyuan_id': r.huiyuan_id,
                'qitashuoming': r.qitashuoming or '',
                'create_time': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '',
            })

        from jituan.services.club_user_access import list_response_meta
        return Response({
            'code': 0,
            'data': {
                'list': data_list,
                'total': total,
                'page': page,
                'page_size': page_size,
                **list_response_meta(request),
            },
        })


class GetProductTypeZoneView(APIView):
    """
    获取商品类型、专区及会员列表
    权限：需要拥有 7007a
    请求：POST /houtai/hthqsplxzq
    参数：{"username": "客服手机号"}
    返回：
        code: 0
        data: {
            type_list: [{id, jieshao, tupian_url, yaoqiuleixing, huiyuan_id, yongjin, shenhezhuangtai, paixu, product_count}, ...],
            zone_list: [{id, mingzi, leixing_id, shenhezhuangtai, paixu}, ...],
            member_list: [{huiyuan_id, jieshao, jiage}, ...]
        }
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 400, 'msg': '缺少username'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '7007a' not in permissions:
            return Response({'code': 403, 'msg': '您无权访问此页面'})

        # 查询所有商品类型（含专区数量）
        types = ShangpinLeixing.query.all().order_by('-paixu')  # paixu 越大越靠前
        type_list = []
        for t in types:
            # 统计该类型下的专区数量
            zone_count = ShangpinZhuanqu.query.filter(leixing_id=t.id).count()
            type_list.append({
                'id': t.id,
                'jieshao': t.jieshao or '',
                'tupian_url': t.tupian_url or '',
                'yaoqiuleixing': t.yaoqiuleixing,
                'huiyuan_id': t.huiyuan_id or '',
                'yongjin': float(t.yongjin) if t.yongjin is not None else None,
                'shenhezhuangtai': t.shenhezhuangtai,
                'paixu': t.paixu,
                'product_count': zone_count,
            })

        # 查询所有专区
        zones = ShangpinZhuanqu.query.all().order_by('-paixu')
        zone_list = [
            {
                'id': z.id,
                'mingzi': z.mingzi or '',
                'leixing_id': z.leixing_id,
                'shenhezhuangtai': z.shenhezhuangtai,
                'paixu': z.paixu,
            }
            for z in zones
        ]

        # 查询所有会员（只返回 id、介绍、价格）
        members = Huiyuan.query.all().values('huiyuan_id', 'jieshao', 'jiage')
        member_list = [
            {
                'huiyuan_id': m['huiyuan_id'],
                'jieshao': m['jieshao'],
                'jiage': float(m['jiage']),
            }
            for m in members
        ]

        return Response({
            'code': 0,
            'data': {
                'type_list': type_list,
                'zone_list': zone_list,
                'member_list': member_list,
            }
        })




class ModifyProductTypeZoneView(APIView):
    """
    统一修改商品类型/专区（增、删、改）
    权限：需要拥有 7007a
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser, MultiPartParser, FormParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        obj_type = request.data.get('type')      # 'leixing' 或 'zhuanqu'
        action = request.data.get('action')      # 'add', 'update', 'delete'

        if not username or not obj_type or not action:
            return Response({'code': 400, 'msg': '缺少必要参数'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '7007a' not in permissions:
            return Response({'code': 403, 'msg': '您无权进行此操作'})

        if obj_type == 'leixing':
            return self.handle_leixing(request, action)
        elif obj_type == 'zhuanqu':
            return self.handle_zhuanqu(request, action)
        else:
            return Response({'code': 400, 'msg': '无效的type参数'})

    # ---------- 商品类型处理 ----------
    def handle_leixing(self, request, action):
        if action == 'add':
            return self.add_leixing(request)
        elif action == 'update':
            return self.update_leixing(request)
        elif action == 'delete':
            return self.delete_leixing(request)
        else:
            return Response({'code': 400, 'msg': '无效的action'})

    def add_leixing(self, request):
        # 获取并校验字段
        jieshao = request.data.get('jieshao', '').strip()
        if not jieshao:
            return Response({'code': 400, 'msg': '类型名称不能为空'})

        # 强制转为整数
        try:
            yaoqiuleixing = int(request.data.get('yaoqiuleixing', 0))
            if yaoqiuleixing not in [1, 2]:
                raise ValueError
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': '抢单要求类型无效（必须为1或2）'})

        try:
            shenhezhuangtai = int(request.data.get('shenhezhuangtai', 1))
            if shenhezhuangtai not in [1, 2]:
                raise ValueError
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': '审核状态无效（必须为1或2）'})

        huiyuan_id = request.data.get('huiyuan_id', '').strip() or None
        yongjin = request.data.get('yongjin')
        if yongjin is not None and yongjin != '':
            try:
                yongjin = Decimal(str(yongjin))
            except:
                return Response({'code': 400, 'msg': '佣金金额格式错误'})
        else:
            yongjin = None

        # 图片校验
        image_file = request.FILES.get('file')
        if not image_file:
            return Response({'code': 400, 'msg': '请上传类型图片'})

        # 验证图片
        try:
            valid, msg = validate_image(image_file)
            if not valid:
                return Response({'code': 400, 'msg': msg})
        except ImportError:
            pass

        # 上传图片
        ext = image_file.name.split('.')[-1].lower()
        new_filename = f"{uuid.uuid4().hex}.{ext}"
        oss_path = f"a_long/shangpin/shangpinleixing/{new_filename}"
        url = upload_to_oss(image_file, oss_path)
        if not url:
            return Response({'code': 500, 'msg': '图片上传失败'})

        # 创建类型
        try:
            with transaction.atomic():
                leixing = ShangpinLeixing.query.create(
                    jieshao=jieshao,
                    tupian_url=oss_path,
                    yaoqiuleixing=yaoqiuleixing,
                    huiyuan_id=huiyuan_id,
                    yongjin=yongjin,
                    shenhezhuangtai=shenhezhuangtai,
                    paixu=0
                )
            return Response({'code': 0, 'msg': '添加成功', 'data': {'id': leixing.id}})
        except Exception as e:
            logger.error(f"添加类型失败: {e}", exc_info=True)
            delete_from_oss(oss_path)
            return Response({'code': 500, 'msg': '数据库错误'})

    def update_leixing(self, request):
        type_id = request.data.get('id')
        if not type_id:
            return Response({'code': 400, 'msg': '缺少类型ID'})
        try:
            leixing = ShangpinLeixing.query.get(id=type_id)
        except ShangpinLeixing.DoesNotExist:
            return Response({'code': 404, 'msg': '类型不存在'})

        changes = {}
        # 名称
        jieshao = request.data.get('jieshao')
        if jieshao is not None and jieshao.strip() != leixing.jieshao:
            changes['jieshao'] = jieshao.strip()

        # 抢单要求类型（强制转整数）
        yaoqiuleixing = request.data.get('yaoqiuleixing')
        if yaoqiuleixing is not None:
            try:
                new_val = int(yaoqiuleixing)
                if new_val not in [1, 2]:
                    raise ValueError
                if new_val != leixing.yaoqiuleixing:
                    changes['yaoqiuleixing'] = new_val
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '抢单要求类型无效（必须为1或2）'})

        # 会员ID
        huiyuan_id = request.data.get('huiyuan_id', '').strip() or None
        if huiyuan_id != leixing.huiyuan_id:
            changes['huiyuan_id'] = huiyuan_id

        # 佣金金额
        yongjin = request.data.get('yongjin')
        if yongjin is not None:
            if yongjin == '':
                new_val = None
            else:
                try:
                    new_val = Decimal(str(yongjin))
                except:
                    return Response({'code': 400, 'msg': '佣金金额格式错误'})
            if new_val != leixing.yongjin:
                changes['yongjin'] = new_val

        # 审核状态
        shenhezhuangtai = request.data.get('shenhezhuangtai')
        if shenhezhuangtai is not None:
            try:
                new_val = int(shenhezhuangtai)
                if new_val not in [1, 2]:
                    raise ValueError
                if new_val != leixing.shenhezhuangtai:
                    changes['shenhezhuangtai'] = new_val
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '审核状态无效（必须为1或2）'})

        # 图片处理
        image_file = request.FILES.get('file')
        old_image_path = leixing.tupian_url
        if image_file:
            try:
                valid, msg = validate_image(image_file)
                if not valid:
                    return Response({'code': 400, 'msg': msg})
            except ImportError:
                pass
            ext = image_file.name.split('.')[-1].lower()
            new_filename = f"{uuid.uuid4().hex}.{ext}"
            oss_path = f"a_long/shangpin/shangpinleixing/{new_filename}"
            url = upload_to_oss(image_file, oss_path)
            if not url:
                return Response({'code': 500, 'msg': '图片上传失败'})
            changes['tupian_url'] = oss_path

        if not changes and not image_file:
            return Response({'code': 400, 'msg': '未做任何修改'})

        sync_to_products = request.data.get('sync_to_products') in [True, 'true', '1', 'on']
        try:
            with transaction.atomic():
                for field, value in changes.items():
                    setattr(leixing, field, value)
                leixing.save()

                # 同步商品
                if sync_to_products and ('yaoqiuleixing' in changes or 'huiyuan_id' in changes or 'yongjin' in changes):
                    product_updates = {}
                    if 'yaoqiuleixing' in changes:
                        product_updates['yaoqiuleixing'] = changes['yaoqiuleixing']
                    if 'huiyuan_id' in changes:
                        product_updates['huiyuan_id'] = changes['huiyuan_id']
                    if 'yongjin' in changes:
                        product_updates['yongjin'] = changes['yongjin']
                    if product_updates:
                        Shangpin.query.filter(leixing_id=type_id).update(**product_updates)

            # 删除旧图片
            if image_file and old_image_path:
                delete_from_oss(old_image_path)

            return Response({'code': 0, 'msg': '修改成功'})
        except Exception as e:
            logger.error(f"更新类型失败: {e}", exc_info=True)
            if image_file and 'oss_path' in locals():
                delete_from_oss(oss_path)
            return Response({'code': 500, 'msg': '修改失败'})

    def delete_leixing(self, request):
        type_id = request.data.get('id')
        if not type_id:
            return Response({'code': 400, 'msg': '缺少类型ID'})
        delete_products = request.data.get('delete_products') in [True, 'true', '1', 'on']

        try:
            leixing = ShangpinLeixing.query.get(id=type_id)
        except ShangpinLeixing.DoesNotExist:
            return Response({'code': 404, 'msg': '类型不存在'})

        with transaction.atomic():
            # 删除该类型下的所有专区
            zones = ShangpinZhuanqu.query.filter(leixing_id=type_id)
            if delete_products:
                # 删除专区下的所有商品
                for zone in zones:
                    Shangpin.query.filter(zhuanqu_id=zone.id).delete()
                zones.delete()
                # 删除直接关联该类型的商品
                Shangpin.query.filter(leixing_id=type_id).delete()
            else:
                zones.delete()
                # 仅清除商品的类型关联
                Shangpin.query.filter(leixing_id=type_id).update(leixing_id=None)

            leixing.delete()

        if leixing.tupian_url:
            delete_from_oss(leixing.tupian_url)

        return Response({'code': 0, 'msg': '删除成功'})

    # ---------- 商品专区处理 ----------
    def handle_zhuanqu(self, request, action):
        if action == 'add':
            return self.add_zhuanqu(request)
        elif action == 'update':
            return self.update_zhuanqu(request)
        elif action == 'delete':
            return self.delete_zhuanqu(request)
        else:
            return Response({'code': 400, 'msg': '无效的action'})

    def add_zhuanqu(self, request):
        mingzi = request.data.get('mingzi', '').strip()
        leixing_id = request.data.get('leixing_id')
        if not mingzi:
            return Response({'code': 400, 'msg': '专区名称不能为空'})
        if not leixing_id:
            return Response({'code': 400, 'msg': '请选择所属类型'})

        try:
            leixing_id = int(leixing_id)
            if not ShangpinLeixing.query.filter(id=leixing_id).exists():
                return Response({'code': 404, 'msg': '所属类型不存在'})
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': '所属类型ID无效'})

        try:
            shenhezhuangtai = int(request.data.get('shenhezhuangtai', 1))
            if shenhezhuangtai not in [1, 2]:
                raise ValueError
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': '审核状态无效（必须为1或2）'})

        try:
            with transaction.atomic():
                zhuanqu = ShangpinZhuanqu.query.create(
                    mingzi=mingzi,
                    leixing_id=leixing_id,
                    shenhezhuangtai=shenhezhuangtai,
                    paixu=0
                )
            return Response({'code': 0, 'msg': '添加成功', 'data': {'id': zhuanqu.id}})
        except Exception as e:
            logger.error(f"添加专区失败: {e}", exc_info=True)
            return Response({'code': 500, 'msg': '数据库错误'})

    def update_zhuanqu(self, request):
        zone_id = request.data.get('id')
        if not zone_id:
            return Response({'code': 400, 'msg': '缺少专区ID'})
        try:
            zhuanqu = ShangpinZhuanqu.query.get(id=zone_id)
        except ShangpinZhuanqu.DoesNotExist:
            return Response({'code': 404, 'msg': '专区不存在'})

        changes = {}
        mingzi = request.data.get('mingzi')
        if mingzi is not None and mingzi.strip() != zhuanqu.mingzi:
            changes['mingzi'] = mingzi.strip()

        leixing_id = request.data.get('leixing_id')
        if leixing_id is not None:
            try:
                new_id = int(leixing_id)
                if not ShangpinLeixing.query.filter(id=new_id).exists():
                    return Response({'code': 404, 'msg': '新所属类型不存在'})
                if new_id != zhuanqu.leixing_id:
                    changes['leixing_id'] = new_id
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '所属类型ID无效'})

        shenhezhuangtai = request.data.get('shenhezhuangtai')
        if shenhezhuangtai is not None:
            try:
                new_val = int(shenhezhuangtai)
                if new_val not in [1, 2]:
                    raise ValueError
                if new_val != zhuanqu.shenhezhuangtai:
                    changes['shenhezhuangtai'] = new_val
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '审核状态无效（必须为1或2）'})

        if not changes:
            return Response({'code': 400, 'msg': '未做任何修改'})

        try:
            with transaction.atomic():
                for field, value in changes.items():
                    setattr(zhuanqu, field, value)
                zhuanqu.save()
            return Response({'code': 0, 'msg': '修改成功'})
        except Exception as e:
            logger.error(f"更新专区失败: {e}", exc_info=True)
            return Response({'code': 500, 'msg': '修改失败'})

    def delete_zhuanqu(self, request):
        zone_id = request.data.get('id')
        if not zone_id:
            return Response({'code': 400, 'msg': '缺少专区ID'})
        delete_products = request.data.get('delete_products') in [True, 'true', '1', 'on']

        try:
            zhuanqu = ShangpinZhuanqu.query.get(id=zone_id)
        except ShangpinZhuanqu.DoesNotExist:
            return Response({'code': 404, 'msg': '专区不存在'})

        with transaction.atomic():
            if delete_products:
                Shangpin.query.filter(zhuanqu_id=zone_id).delete()
            else:
                Shangpin.query.filter(zhuanqu_id=zone_id).update(zhuanqu_id=None)
            zhuanqu.delete()

        return Response({'code': 0, 'msg': '删除成功'})


# 继续在 houtai/views_product.py 中添加
class GetRateView(APIView):
    """
    获取打手分成费率（平台订单和商家订单）
    权限：需要拥有 7007b
    请求：POST /houtai/hthqfhpz
    参数：{"username": "客服手机号"}
    返回：{"code":0, "data": {"platform_rate": xx, "merchant_rate": xx}}
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 400, 'msg': '缺少username'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '7007b' not in permissions:
            return Response({'code': 403, 'msg': '您无权查看费率'})

        try:
            platform_rate_obj = CommissionRate.query.get(Platform='1')
            merchant_rate_obj = CommissionRate.query.get(Platform='3')
        except CommissionRate.DoesNotExist as e:
            logger.error(f"费率记录缺失: {e}")
            return Response({'code': 500, 'msg': '费率配置缺失'})

        return Response({
            'code': 0,
            'data': {
                'platform_rate': float(platform_rate_obj.Rate) if platform_rate_obj.Rate else 0,
                'merchant_rate': float(merchant_rate_obj.Rate) if merchant_rate_obj.Rate else 0,
            }
        })


class ModifyRateView(APIView):
    """
    修改打手分成费率
    权限：需要拥有 7007b
    请求：POST /houtai/htxgfl
    参数：{"username": "客服手机号", "type": "platform" / "merchant", "value": 费率(百分比数字)}
    返回：{"code":0, "msg":"修改成功"}
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        rate_type = request.data.get('type')
        value = request.data.get('value')

        if not username or not rate_type or value is None:
            return Response({'code': 400, 'msg': '缺少参数'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '7007b' not in permissions:
            return Response({'code': 403, 'msg': '您无权修改费率'})

        try:
            rate_value = Decimal(str(value))
        except:
            return Response({'code': 400, 'msg': '费率值格式错误'})


        if rate_type == 'platform':
            Platform = '1'
        elif rate_type == 'merchant':
            Platform = '3'
        else:
            return Response({'code': 400, 'msg': '无效的type参数'})

        try:
            with transaction.atomic():
                rate_obj = CommissionRate.objects.select_for_update().get(Platform=Platform)
                rate_obj.Rate = rate_value
                rate_obj.save()
        except CommissionRate.DoesNotExist:
            return Response({'code': 404, 'msg': '费率记录不存在'})
        except Exception as e:
            logger.error(f"修改费率失败: {e}", exc_info=True)
            return Response({'code': 500, 'msg': '修改失败'})

        return Response({'code': 0, 'msg': '修改成功'})






# 权限码（根据你要求，使用 '8080a'）
REQUIRED_PERMISSION = '8080a'
class PopupNoticeListAPIView(APIView):
    """
    获取所有弹窗配置（页面 + 弹窗 + 图片）
    URL: POST /houtai/hthqtcxx
    请求参数: { "username": "客服账号" }
    响应格式: { "code": 0, "data": { "pages": [...] } }
    注意：所有响应均使用默认 HTTP 200 状态码，业务成功/失败通过 code 字段区分
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 获取前端传递的 username（用于防越权）
        username_frontend = request.data.get('username')
        # 2. 调用公共权限验证方法，返回 (客服对象, 权限列表)
        kefu, permissions = verify_kefu_permission(request, username_frontend)
        if kefu is None:
            # 身份验证失败，返回业务码 403，但 HTTP 状态码仍是 200
            return Response({'code': 403, 'msg': '身份验证失败，请检查登录状态'})

        # 3. 检查是否拥有所需权限码 '8080a'
        if REQUIRED_PERMISSION not in permissions:
            return Response({'code': 403, 'msg': '您没有权限访问弹窗管理功能'})

        # 4. 查询所有页面，并预加载关联的弹窗和图片（避免N+1查询）
        from jituan.services.display_config import filter_popup_page_by_request, list_response_meta

        pages = filter_popup_page_by_request(
            PopupPage.query.all().prefetch_related('popups__images'),
            request,
        ).order_by('id')

        # 5. 序列化输出
        serializer = PopupPageSerializer(pages, many=True)
        return Response({
            'code': 0,
            'data': {'pages': serializer.data, **list_response_meta(request)},
        })


class PopupNoticeModifyAPIView(APIView):
    """
    统一修改接口：支持页面、弹窗、图片的增删改
    URL: POST /houtai/htxgtcxx
    请求中必须包含 action 字段，用于区分操作类型。
    所有响应均使用默认 HTTP 200 状态码，业务成功/失败通过 code 字段体现。
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # ---------- 1. 权限验证 ----------
        username_frontend = request.data.get('username')
        kefu, permissions = verify_kefu_permission(request, username_frontend)
        if kefu is None:
            return Response({'code': 403, 'msg': '身份验证失败，请检查登录状态'})
        if REQUIRED_PERMISSION not in permissions:
            return Response({'code': 403, 'msg': '您没有权限操作弹窗管理'})

        from jituan.services.display_config import forbid_display_write_in_all_scope
        deny = forbid_display_write_in_all_scope(request)
        if deny:
            return deny

        # ---------- 2. 获取 action 并分发 ----------
        action = request.data.get('action')
        if not action:
            return Response({'code': 400, 'msg': '缺少action参数'})

        try:
            if action == 'add_page':
                return self.add_page(request)
            elif action == 'update_page':
                return self.update_page(request)
            elif action == 'delete_page':
                return self.delete_page(request)
            elif action == 'add_popup':
                return self.add_popup(request)
            elif action == 'update_popup':
                return self.update_popup(request)
            elif action == 'delete_popup':
                return self.delete_popup(request)
            elif action == 'add_image':
                return self.add_image(request)
            elif action == 'update_image':
                return self.update_image(request)
            elif action == 'delete_image':
                return self.delete_image(request)
            else:
                return Response({'code': 400, 'msg': f'未知的action: {action}'})
        except Exception as e:
            # 捕获未预期的异常，返回服务器错误
            traceback.print_exc()
            return Response({'code': 500, 'msg': f'服务器错误: {str(e)}'})

    # ==================== 页面相关操作 ====================

    def add_page(self, request):
        """添加新页面"""
        from jituan.services.club_context import resolve_club_id_from_request
        from jituan.services.display_config import forbid_display_write_in_all_scope

        deny = forbid_display_write_in_all_scope(request)
        if deny:
            return deny

        data = request.data
        # 必填字段校验
        if not data.get('page_key') or not data.get('name'):
            return Response({'code': 400, 'msg': '缺少 page_key 或 name'})
        club_id = resolve_club_id_from_request(request)
        # 唯一性校验（同俱乐部内）
        if PopupPage.query.filter(club_id=club_id, page_key=data['page_key']).exists():
            return Response({'code': 400, 'msg': '页面标识已存在'})

        page = PopupPage.query.create(
            club_id=club_id,
            page_key=data['page_key'],
            name=data['name'],
            description=data.get('description', ''),
            is_active=data.get('is_active', True),
            ignore_user_mute=data.get('ignore_user_mute', False)
        )
        return Response({'code': 0, 'msg': '添加成功', 'data': {'id': page.id}})

    def update_page(self, request):
        """更新页面信息"""
        page_id = request.data.get('id')
        if not page_id:
            return Response({'code': 400, 'msg': '缺少页面id'})
        try:
            page = PopupPage.query.get(id=page_id)
        except PopupPage.DoesNotExist:
            return Response({'code': 404, 'msg': '页面不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, page)
        if deny:
            return deny

        # 更新允许修改的字段
        page.page_key = request.data.get('page_key', page.page_key)
        page.name = request.data.get('name', page.name)
        page.description = request.data.get('description', page.description)
        page.is_active = request.data.get('is_active', page.is_active)
        page.ignore_user_mute = request.data.get('ignore_user_mute', page.ignore_user_mute)
        page.save()
        return Response({'code': 0, 'msg': '更新成功'})

    def delete_page(self, request):
        """删除页面，可级联删除其下所有弹窗和图片"""
        page_id = request.data.get('id')
        cascade = request.data.get('cascade_delete_popups', True)  # 默认级联
        if not page_id:
            return Response({'code': 400, 'msg': '缺少页面id'})
        try:
            page = PopupPage.query.get(id=page_id)
        except PopupPage.DoesNotExist:
            return Response({'code': 404, 'msg': '页面不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope, forbid_display_write_in_all_scope
        deny = forbid_display_write_in_all_scope(request) or forbid_popup_page_out_of_scope(request, page)
        if deny:
            return deny

        if cascade:
            # 级联删除：使用事务保证原子性
            with transaction.atomic():
                popups = page.popups.all()
                for popup in popups:
                    # 删除弹窗下的所有图片文件
                    for img in popup.images.all():
                        if img.image_url:
                            delete_from_oss(img.image_url)
                popups.delete()
                page.delete()
        else:
            # 非级联删除：如果页面下还有弹窗，则拒绝删除
            if page.popups.exists():
                return Response({'code': 400, 'msg': '该页面下存在弹窗，请先删除弹窗或使用级联删除'})
            page.delete()

        return Response({'code': 0, 'msg': '删除成功'})

    # ==================== 弹窗相关操作 ====================

    def add_popup(self, request):
        """为指定页面添加新弹窗"""
        data = request.data
        page_id = data.get('page_id')
        popup_id = data.get('popup_id')
        if not page_id or not popup_id:
            return Response({'code': 400, 'msg': '缺少 page_id 或 popup_id'})
        try:
            page = PopupPage.query.get(id=page_id)
        except PopupPage.DoesNotExist:
            return Response({'code': 404, 'msg': '页面不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, page)
        if deny:
            return deny

        # 同一页面下 popup_id 必须唯一
        if PopupConfig.query.filter(page=page, popup_id=popup_id).exists():
            return Response({'code': 400, 'msg': '该页面下已存在相同的弹窗ID'})

        popup = PopupConfig.query.create(
            page=page,
            popup_id=popup_id,
            title=data.get('title', ''),
            description=data.get('description', ''),
            strategy_type=data.get('strategy_type', 'daily'),
            max_count=data.get('max_count', 1),
            reset_interval=data.get('reset_interval', 'day'),
            duration=data.get('duration', 0),
            force_even_muted=data.get('force_even_muted', False),
            sort_order=data.get('sort_order', 0),
            is_active=data.get('is_active', True),
            start_time=data.get('start_time') or None,
            end_time=data.get('end_time') or None
        )
        return Response({'code': 0, 'msg': '添加成功', 'data': {'id': popup.id}})

    def update_popup(self, request):
        """更新弹窗信息"""
        popup_id = request.data.get('id')
        if not popup_id:
            return Response({'code': 400, 'msg': '缺少弹窗id'})
        try:
            popup = PopupConfig.query.get(id=popup_id)
        except PopupConfig.DoesNotExist:
            return Response({'code': 404, 'msg': '弹窗不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, popup.page)
        if deny:
            return deny

        # 更新弹窗字段
        popup.popup_id = request.data.get('popup_id', popup.popup_id)
        popup.title = request.data.get('title', popup.title)
        popup.description = request.data.get('description', popup.description)
        popup.strategy_type = request.data.get('strategy_type', popup.strategy_type)
        popup.max_count = request.data.get('max_count', popup.max_count)
        popup.reset_interval = request.data.get('reset_interval', popup.reset_interval)
        popup.duration = request.data.get('duration', popup.duration)
        popup.force_even_muted = request.data.get('force_even_muted', popup.force_even_muted)
        popup.sort_order = request.data.get('sort_order', popup.sort_order)
        popup.is_active = request.data.get('is_active', popup.is_active)
        popup.start_time = request.data.get('start_time') or None
        popup.end_time = request.data.get('end_time') or None
        popup.save()
        return Response({'code': 0, 'msg': '更新成功'})

    def delete_popup(self, request):
        """删除弹窗，同时删除其下所有图片（包括OSS文件）"""
        popup_id = request.data.get('id')
        if not popup_id:
            return Response({'code': 400, 'msg': '缺少弹窗id'})
        try:
            popup = PopupConfig.query.get(id=popup_id)
        except PopupConfig.DoesNotExist:
            return Response({'code': 404, 'msg': '弹窗不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, popup.page)
        if deny:
            return deny

        with transaction.atomic():
            for img in popup.images.all():
                if img.image_url:
                    delete_from_oss(img.image_url)
            popup.images.all().delete()
            popup.delete()
        return Response({'code': 0, 'msg': '删除成功'})

    # ==================== 图片相关操作 ====================

    def _upload_image_file(self, file_obj, page_key):
        """
        内部方法：将图片上传到OSS指定目录
        目录: a_long/tanchuang/tanchuang/
        文件名: {page_key}_{时间戳}_{uuid8}.{ext}
        返回: 相对路径（例如 a_long/tanchuang/tanchuang/dashouzhongxin_20250419120000_abc12345.jpg）
        """
        # 验证图片格式和大小
        valid, msg = validate_image(file_obj)
        if not valid:
            raise ValueError(msg)

        # 生成唯一文件名
        ext = os.path.splitext(file_obj.name)[1].lower()
        timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
        unique_id = str(uuid.uuid4())[:8]
        filename = f"{page_key}_{timestamp}_{unique_id}{ext}"
        relative_path = f"a_long/tanchuang/tanchuang/{filename}"

        # 上传到OSS
        url = upload_to_oss(file_obj, relative_path)
        if not url:
            raise ValueError("图片上传失败")
        return relative_path

    def add_image(self, request):
        """为指定弹窗添加图片（支持上传文件）"""
        popup_id = request.data.get('popup_id')
        if not popup_id:
            return Response({'code': 400, 'msg': '缺少 popup_id'})
        try:
            popup = PopupConfig.query.get(id=popup_id)
        except PopupConfig.DoesNotExist:
            return Response({'code': 404, 'msg': '弹窗不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, popup.page)
        if deny:
            return deny

        # 获取上传的文件
        file_obj = request.FILES.get('file')
        if not file_obj:
            return Response({'code': 400, 'msg': '未提供图片文件'})

        try:
            page_key = popup.page.page_key
            relative_path = self._upload_image_file(file_obj, page_key)
        except ValueError as e:
            return Response({'code': 400, 'msg': str(e)})

        # 保存图片记录
        image = PopupImage.query.create(
            popup_config=popup,
            image_url=relative_path,
            text=request.data.get('text', ''),
            sort_order=request.data.get('sort_order', 0)
        )
        return Response({'code': 0, 'msg': '添加成功', 'data': {'id': image.id}})

    def update_image(self, request):
        """更新图片信息（可替换图片文件、修改文字、排序）"""
        image_id = request.data.get('id')
        if not image_id:
            return Response({'code': 400, 'msg': '缺少图片id'})
        try:
            image = PopupImage.query.get(id=image_id)
        except PopupImage.DoesNotExist:
            return Response({'code': 404, 'msg': '图片不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, image.popup_config.page)
        if deny:
            return deny

        # 更新文字和排序
        image.text = request.data.get('text', image.text)
        image.sort_order = request.data.get('sort_order', image.sort_order)

        # 如果提供了新图片文件，则替换
        file_obj = request.FILES.get('file')
        if file_obj:
            try:
                page_key = image.popup_config.page.page_key
                new_path = self._upload_image_file(file_obj, page_key)
            except ValueError as e:
                return Response({'code': 400, 'msg': str(e)})
            # 删除旧图片
            if image.image_url:
                delete_from_oss(image.image_url)
            image.image_url = new_path

        image.save()
        return Response({'code': 0, 'msg': '更新成功'})

    def delete_image(self, request):
        """删除图片（同时删除OSS文件）"""
        image_id = request.data.get('id')
        if not image_id:
            return Response({'code': 400, 'msg': '缺少图片id'})
        try:
            image = PopupImage.query.get(id=image_id)
        except PopupImage.DoesNotExist:
            return Response({'code': 404, 'msg': '图片不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, image.popup_config.page)
        if deny:
            return deny

        if image.image_url:
            delete_from_oss(image.image_url)
        image.delete()
        return Response({'code': 0, 'msg': '删除成功'})



class ShopListView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 调用公共权限验证（与您给的示例完全一致）
        username_frontend = request.data.get('username')
        kefu, permissions = verify_kefu_permission(request, username_frontend)
        if kefu is None:
            return Response({'code': 403, 'msg': '身份验证失败，请检查登录状态'})

        # 2. 检查是否拥有所需权限（999a~999e 任意一个）
        required_perms = {'999a', '999b', '999c', '999d', '999e'}
        if not required_perms.intersection(set(permissions)):
            return Response({'code': 403, 'msg': '您没有权限访问店铺管理功能'})

        # 3. 解析请求参数
        page = int(request.data.get('page', 1))
        page_size = int(request.data.get('page_size', 10))
        dianpu_mingcheng = request.data.get('dianpu_mingcheng', '').strip()
        zhuangtai = request.data.get('zhuangtai')
        lianxi_dianhua = request.data.get('lianxi_dianhua', '').strip()
        user_id = request.data.get('user_id', '').strip()
        ketixian_yue_min = request.data.get('ketixian_yue_min')
        ketixian_yue_max = request.data.get('ketixian_yue_max')
        zhengqu_zonge_min = request.data.get('zhengqu_zonge_min')
        zhengqu_zonge_max = request.data.get('zhengqu_zonge_max')

        # 4. 构建查询集（先查店铺，预加载关联凭证）
        queryset = Dianpu.query.select_related('pingzheng').all()

        if dianpu_mingcheng:
            queryset = queryset.filter(dianpu_mingcheng__icontains=dianpu_mingcheng)
        if zhuangtai is not None:
            queryset = queryset.filter(zhuangtai=zhuangtai)
        if lianxi_dianhua:
            queryset = queryset.filter(lianxi_dianhua__icontains=lianxi_dianhua)
        if user_id:
            queryset = queryset.filter(pingzheng__yonghu=user_id)

        # 金额范围过滤
        if ketixian_yue_min is not None:
            queryset = queryset.filter(ketixian_yue__gte=ketixian_yue_min)
        if ketixian_yue_max is not None:
            queryset = queryset.filter(ketixian_yue__lte=ketixian_yue_max)
        if zhengqu_zonge_min is not None:
            queryset = queryset.filter(zhengqu_zonge__gte=zhengqu_zonge_min)
        if zhengqu_zonge_max is not None:
            queryset = queryset.filter(zhengqu_zonge__lte=zhengqu_zonge_max)

        # 5. 分页
        paginator = Paginator(queryset, page_size)
        page_obj = paginator.get_page(page)

        # 6. 构造返回数据
        shop_list = []
        for dianpu in page_obj:
            pingzheng = dianpu.pingzheng
            shop_list.append({
                'id': dianpu.id,
                'dianpu_mingcheng': dianpu.dianpu_mingcheng,
                'dianpu_touxiang': dianpu.dianpu_touxiang or '',
                'lianxi_dianhua': dianpu.lianxi_dianhua or '',
                'weixinhao': dianpu.weixinhao or '',
                'zhuangtai': dianpu.zhuangtai,
                'bangding_yonghushu': dianpu.bangding_yonghushu,
                'erweima_url': dianpu.erweima_url or '',
                'kaiqi_shouyi_choucheng': dianpu.kaiqi_shouyi_choucheng,
                'shouyi_choucheng_feilv': dianpu.shouyi_choucheng_feilv or 0,
                'ketixian_yue': dianpu.ketixian_yue,
                'zhengqu_zonge': dianpu.zhengqu_zonge,
                'meiri_tixian_xiane': dianpu.meiri_tixian_xiane or 0,
                'kaiqi_meiri_xiane': dianpu.kaiqi_meiri_xiane,
                'pingzheng__yonghu': pingzheng.yonghu,       # 用户ID
                'pingzheng__zhanghao': pingzheng.zhanghao,   # 登录账号
            })

        # 获取默认配置（ID=1，不存在则创建）
        default_config, _ = DianpuMorenPeizhi.query.get_or_create(id=1, defaults={
            'moren_choucheng_feilv': 0.1,
            'moren_tixian_xiane': 1000.00
        })

        return Response({
            'code': 0,
            'msg': 'success',
            'data': {
                'list': shop_list,
                'total': paginator.count,
                'default_config': {
                    'moren_choucheng_feilv': default_config.moren_choucheng_feilv,
                    'moren_tixian_xiane': default_config.moren_tixian_xiane,
                }
            }
        })


class ShopModifyView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 身份验证
        username_frontend = request.data.get('username')
        kefu, permissions = verify_kefu_permission(request, username_frontend)
        if kefu is None:
            return Response({'code': 403, 'msg': '身份验证失败，请检查登录状态'})

        # 转换权限为集合方便判断
        user_perms = set(permissions)
        action = request.data.get('action', '')

        # 2. 根据 action 分派任务
        if action == 'update_dianpu':
            return self._update_dianpu(request, user_perms)
        elif action == 'add_dianpu':
            return self._add_dianpu(request, user_perms)
        elif action == 'update_default':
            return self._update_default(request, user_perms)
        else:
            return Response({'code': 400, 'msg': '未知操作类型'})

    def _update_dianpu(self, request, user_perms):
        """修改店铺信息，精确权限控制"""
        dianpu_id = request.data.get('dianpu_id')
        if not dianpu_id:
            return Response({'code': 400, 'msg': '缺少店铺ID'})

        try:
            with transaction.atomic():
                dianpu = Dianpu.objects.select_for_update().get(id=dianpu_id)

                # 基础信息修改 (999e)
                if any(k in request.data for k in ['dianpu_mingcheng', 'lianxi_dianhua', 'weixinhao']):
                    if '999e' not in user_perms:
                        return Response({'code': 403, 'msg': '无权限修改店铺基本信息(999e)'})
                    if 'dianpu_mingcheng' in request.data:
                        dianpu.dianpu_mingcheng = request.data['dianpu_mingcheng']
                    if 'lianxi_dianhua' in request.data:
                        dianpu.lianxi_dianhua = request.data['lianxi_dianhua']
                    if 'weixinhao' in request.data:
                        dianpu.weixinhao = request.data['weixinhao']

                # 店铺状态 (999a)
                if 'zhuangtai' in request.data:
                    if '999a' not in user_perms:
                        return Response({'code': 403, 'msg': '无权限修改店铺状态(999a)'})
                    new_status = int(request.data['zhuangtai'])
                    if new_status not in (0, 1):
                        return Response({'code': 400, 'msg': '店铺状态无效'})
                    dianpu.zhuangtai = new_status

                # 可提现余额 (999b)
                if 'ketixian_yue' in request.data:
                    if '999b' not in user_perms:
                        return Response({'code': 403, 'msg': '无权限修改可提现余额(999b)'})
                    dianpu.ketixian_yue = request.data['ketixian_yue']

                # 提现限额相关 (999c)
                if 'kaiqi_meiri_xiane' in request.data or 'meiri_tixian_xiane' in request.data:
                    if '999c' not in user_perms:
                        return Response({'code': 403, 'msg': '无权限修改提现限额配置(999c)'})
                    if 'kaiqi_meiri_xiane' in request.data:
                        dianpu.kaiqi_meiri_xiane = request.data['kaiqi_meiri_xiane']
                    if 'meiri_tixian_xiane' in request.data:
                        dianpu.meiri_tixian_xiane = request.data['meiri_tixian_xiane']

                # 抽成相关 (999d)
                if 'kaiqi_shouyi_choucheng' in request.data or 'shouyi_choucheng_feilv' in request.data:
                    if '999d' not in user_perms:
                        return Response({'code': 403, 'msg': '无权限修改抽成配置(999d)'})
                    if 'kaiqi_shouyi_choucheng' in request.data:
                        dianpu.kaiqi_shouyi_choucheng = request.data['kaiqi_shouyi_choucheng']
                    if 'shouyi_choucheng_feilv' in request.data:
                        dianpu.shouyi_choucheng_feilv = request.data['shouyi_choucheng_feilv']

                dianpu.save()  # Django 会自动判断更新哪些字段
                return Response({'code': 0, 'msg': '店铺信息更新成功'})

        except Dianpu.DoesNotExist:
            return Response({'code': 404, 'msg': '店铺不存在'})
        except Exception as e:
            logger.exception("修改店铺失败")
            return Response({'code': 500, 'msg': '修改失败，请稍后重试'})

    def _add_dianpu(self, request, user_perms):
        """添加店铺，需要权限 999b"""
        if '999b' not in user_perms:
            return Response({'code': 403, 'msg': '无权限添加店铺(999b)'})

        yonghu_id = request.data.get('yonghu_id')
        zhanghao = request.data.get('zhanghao')
        mima = request.data.get('mima')
        dianpu_mingcheng = request.data.get('dianpu_mingcheng')

        if not all([yonghu_id, zhanghao, mima, dianpu_mingcheng]):
            return Response({'code': 400, 'msg': '必填字段缺失（用户ID、账号、密码、店铺名称）'})

        # 检查用户是否存在
        if not User.query.filter(UserUID=yonghu_id).exists():
            return Response({'code': 404, 'msg': '用户不存在'})

        # 检查凭证是否已存在
        if YonghuPingzheng.query.filter(zhanghao=zhanghao).exists():
            return Response({'code': 400, 'msg': '登录账号已存在'})
        if YonghuPingzheng.query.filter(yonghu=yonghu_id).exists():
            return Response({'code': 400, 'msg': '该用户ID已有店铺凭证'})

        try:
            with transaction.atomic():
                pingzheng = YonghuPingzheng.query.create(
                    yonghu=yonghu_id,
                    zhanghao=zhanghao,
                    mima=mima,
                    is_active=True
                )
                dianpu = Dianpu.query.create(
                    pingzheng=pingzheng,
                    dianpu_mingcheng=dianpu_mingcheng,
                    lianxi_dianhua=request.data.get('lianxi_dianhua', ''),
                    weixinhao=request.data.get('weixinhao', ''),
                    kaiqi_shouyi_choucheng=request.data.get('kaiqi_shouyi_choucheng', False),
                    shouyi_choucheng_feilv=request.data.get('shouyi_choucheng_feilv', 0),
                    kaiqi_meiri_xiane=request.data.get('kaiqi_meiri_xiane', False),
                    meiri_tixian_xiane=request.data.get('meiri_tixian_xiane', 0),
                )
                return Response({'code': 0, 'msg': '店铺创建成功', 'dianpu_id': dianpu.id})
        except IntegrityError as e:
            logger.error("创建店铺违反唯一性约束: %s", e)
            return Response({'code': 400, 'msg': '数据冲突，请检查账号或用户ID'})
        except Exception as e:
            logger.exception("创建店铺失败")
            return Response({'code': 500, 'msg': '创建失败，请稍后重试'})

    def _update_default(self, request, user_perms):
        """修改默认配置，按修改内容校验权限"""
        feilv = request.data.get('moren_choucheng_feilv')
        xiane = request.data.get('moren_tixian_xiane')
        modify_commission = feilv is not None
        modify_withdraw = xiane is not None

        if modify_commission and '999d' not in user_perms:
            return Response({'code': 403, 'msg': '无权限修改默认抽成费率(999d)'})
        if modify_withdraw and '999c' not in user_perms:
            return Response({'code': 403, 'msg': '无权限修改默认可提现限额(999c)'})

        try:
            config, _ = DianpuMorenPeizhi.query.get_or_create(id=1)
            if modify_commission:
                config.moren_choucheng_feilv = feilv
            if modify_withdraw:
                config.moren_tixian_xiane = xiane
            config.save(update_fields=(['moren_choucheng_feilv'] if modify_commission else []) +
                                       (['moren_tixian_xiane'] if modify_withdraw else []))
            return Response({'code': 0, 'msg': '默认配置更新成功'})
        except Exception as e:
            logger.exception("更新默认配置失败")
            return Response({'code': 500, 'msg': '更新失败'})   








# ==================== 1. 公共商品类型 + 审核模式 ====================
class ShopPublicTypeAndAuditView(APIView):
    """
    返回：①正常状态的公共商品类型列表  ②全局商品审核模式开关
    前端请求路径：/houtai/htdphqsplx
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # ---------- 身份与权限校验 ----------
        username = request.data.get('username')
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return Response({'code': 403, 'msg': '身份验证失败，请检查登录状态'})

        # 拥有 1199ab/1199abc/1199abd 任一个即可访问
        if not set(permissions).intersection(SHOP_PRODUCT_PERMS):
            return Response({'code': 403, 'msg': '无权访问店铺商品管理功能'})

        # ---------- 查询公共商品类型（仅正常审核状态的） ----------
        try:
            # shenhezhuangtai = 1 表示正常 (根据模型默认值)
            types_qs = ShangpinLeixing.query.filter(shenhezhuangtai=1).values(
                'id', 'jieshao', 'tupian_url', 'yaoqiuleixing', 'huiyuan_id', 'yongjin'
            )
            type_list = []
            for t in types_qs:
                type_list.append({
                    'id': t['id'],
                    'jieshao': t['jieshao'] or '',
                    'tupian_url': t['tupian_url'] or '',
                    'yaoqiuleixing': t['yaoqiuleixing'],
                    'huiyuan_id': t['huiyuan_id'],
                    'yongjin': t['yongjin'],
                })
        except Exception as e:
            logger.exception("查询公共商品类型失败")
            return Response({'code': 500, 'msg': '服务器错误，请稍后重试'})

        # ---------- 获取审核模式配置 ----------
        audit_config = DianpuShangpinShenheShezhi.query.filter(id=1).first()
        kaiqi_shenhe = audit_config.kaiqi_shenhe if audit_config else False

        return Response({
            'code': 0,
            'data': {
                'public_types': type_list,
                'kaiqi_shenhe': kaiqi_shenhe
            }
        })


# ==================== 2. 店铺列表（支持搜索、筛选、分页） ====================
class ShopListForProductView(APIView):
    """
    返回店铺列表，用于商品管理页面选择店铺。
    支持：关键词搜索（店铺ID/名称/用户ID）、店铺状态筛选、分页。
    前端请求路径：/houtai/htdphqdpys
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('username')
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return Response({'code': 403, 'msg': '身份验证失败'})
        if not set(permissions).intersection(SHOP_PRODUCT_PERMS):
            return Response({'code': 403, 'msg': '无权访问店铺列表'})

        # ---------- 安全解析分页参数 ----------
        try:
            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('page_size', 10))
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': '分页参数格式错误'})

        # 搜索关键词（字符串，ORM 自动防注入）
        keyword = str(request.data.get('keyword', '')).strip()
        # 店铺状态筛选（1正常，0封禁，不传则全部）
        zhuangtai = request.data.get('zhuangtai')

        # ---------- 构建查询集（关联凭证表获取用户ID） ----------
        shops = Dianpu.query.select_related('pingzheng').all()

        # 状态筛选：只接受 0 或 1
        if zhuangtai is not None:
            try:
                zhuangtai = int(zhuangtai)
                if zhuangtai in [0, 1]:
                    shops = shops.filter(zhuangtai=zhuangtai)
                else:
                    return Response({'code': 400, 'msg': '状态参数只能为0或1'})
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '状态参数必须为整数'})

        # 关键词搜索（店铺ID/名称/用户ID）
        if keyword:
            shops = shops.filter(
                models.Q(id__icontains=keyword) |
                models.Q(dianpu_mingcheng__icontains=keyword) |
                models.Q(pingzheng__yonghu__icontains=keyword)
            )

        # ---------- 分页 ----------
        paginator = Paginator(shops, page_size)
        page_obj = paginator.get_page(page)

        shop_list = []
        for shop in page_obj:
            shop_list.append({
                'id': shop.id,
                'dianpu_mingcheng': shop.dianpu_mingcheng,
                'zhuangtai': shop.zhuangtai,
                'yonghu_id': shop.pingzheng.yonghu if shop.pingzheng else '',
                'lianxi_dianhua': shop.lianxi_dianhua or '',
                'weixinhao': shop.weixinhao or '',
            })

        return Response({
            'code': 0,
            'data': {
                'list': shop_list,
                'total': paginator.count
            }
        })


# ==================== 3. 店铺商品类型映射列表（含筛选、待审核计数） ====================
class ShopProductTypeMappingView(APIView):
    """
    返回店铺商品类型映射（ShangpinLeixingDianpu）列表。
    支持：店铺ID、公共类型ID、上架/封禁/审核状态筛选，关键字搜索（介绍/ID/店铺名/公共类型名）。
    每次请求同时返回“待审核总数”（shenhe_zhuangtai=False 的记录数）。
    前端请求路径：/houtai/htdphqyssplx
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('username')
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return Response({'code': 403, 'msg': '身份验证失败'})
        if not set(permissions).intersection(SHOP_PRODUCT_PERMS):
            return Response({'code': 403, 'msg': '无权访问商品类型映射'})

        # ---------- 安全解析参数 ----------
        try:
            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('page_size', 10))
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': '分页参数格式错误'})

        keyword = str(request.data.get('keyword', '')).strip()
        shop_id = request.data.get('shop_id')
        public_type_id = request.data.get('public_type_id')

        # 布尔型筛选字段处理：None 表示不筛选，否则转为布尔值
        def parse_bool(val):
            if val is None:
                return None
            return bool(val)

        shangjia = parse_bool(request.data.get('shangjia_zhuangtai'))
        fengjin = parse_bool(request.data.get('fengjin_zhuangtai'))
        shenhe = parse_bool(request.data.get('shenhe_zhuangtai'))

        # ---------- 查询（连表店铺和公共类型） ----------
        mappings = ShangpinLeixingDianpu.query.select_related('dianpu', 'gonggong_leixing').all()

        # 筛选条件：均为参数化，完全防注入
        if shop_id is not None:
            mappings = mappings.filter(dianpu_id=int(shop_id))
        if public_type_id is not None:
            mappings = mappings.filter(gonggong_leixing_id=int(public_type_id))
        if shangjia is not None:
            mappings = mappings.filter(shangjia_zhuangtai=shangjia)
        if fengjin is not None:
            mappings = mappings.filter(fengjin_zhuangtai=fengjin)
        if shenhe is not None:
            mappings = mappings.filter(shenhe_zhuangtai=shenhe)

        # 关键字搜索（介绍、ID、店铺名、公共类型名）
        if keyword:
            mappings = mappings.filter(
                models.Q(jieshao__icontains=keyword) |
                models.Q(id__icontains=keyword) |
                models.Q(dianpu__dianpu_mingcheng__icontains=keyword) |
                models.Q(gonggong_leixing__jieshao__icontains=keyword)
            )

        # ---------- 待审核总数（不受分页影响） ----------
        pending_total = mappings.filter(shenhe_zhuangtai=False).count()

        # ---------- 分页 ----------
        paginator = Paginator(mappings, page_size)
        page_obj = paginator.get_page(page)

        mapping_list = []
        for m in page_obj:
            mapping_list.append({
                'id': m.id,
                'dianpu_id': m.dianpu_id,
                'dianpu_name': m.dianpu.dianpu_mingcheng if m.dianpu else '',
                'gonggong_leixing_id': m.gonggong_leixing_id,
                'gonggong_leixing_name': m.gonggong_leixing.jieshao if m.gonggong_leixing else '',
                'jieshao': m.jieshao,
                'tupian_url': m.tupian_url or '',
                'paixu': m.paixu,
                'shangjia_zhuangtai': m.shangjia_zhuangtai,
                'fengjin_zhuangtai': m.fengjin_zhuangtai,
                'shenhe_zhuangtai': m.shenhe_zhuangtai,
            })

        return Response({
            'code': 0,
            'data': {
                'list': mapping_list,
                'total': paginator.count,
                'pending_total': pending_total
            }
        })


# ==================== 统一修改接口（已修正事务） ====================
class ShopProductModifyView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('username')
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return Response({'code': 403, 'msg': '身份验证失败'})

        user_perms = set(permissions)
        action = request.data.get('action')

        if action == 'update_audit_mode':
            return self._update_audit_mode(request, user_perms)
        elif action == 'update_mapping_status':
            return self._update_mapping_status(request, user_perms)
        else:
            return Response({'code': 400, 'msg': '未知操作'})

    @transaction.atomic                      # ✅ 事务修复点
    def _update_audit_mode(self, request, user_perms):
        if '1199ab' not in user_perms:
            return Response({'code': 403, 'msg': '无权限修改审核模式'})

        kaiqi = request.data.get('kaiqi_shenhe')
        if kaiqi is None:
            return Response({'code': 400, 'msg': '缺少参数 kaiqi_shenhe'})

        try:
            config, _ = DianpuShangpinShenheShezhi.objects.select_for_update().get_or_create(id=1)
            config.kaiqi_shenhe = bool(kaiqi)
            config.save(update_fields=['kaiqi_shenhe'])
            return Response({'code': 0, 'msg': '审核模式已更新'})
        except Exception as e:
            logger.exception("更新审核模式失败")
            return Response({'code': 500, 'msg': '服务器错误'})

    @transaction.atomic
    def _update_mapping_status(self, request, user_perms):
        mapping_id = request.data.get('mapping_id')
        if not mapping_id:
            return Response({'code': 400, 'msg': '缺少 mapping_id'})

        try:
            mapping = ShangpinLeixingDianpu.objects.select_for_update().get(id=int(mapping_id))
        except (ShangpinLeixingDianpu.DoesNotExist, ValueError):
            return Response({'code': 404, 'msg': '映射不存在'})

        fengjin = request.data.get('fengjin_zhuangtai')
        shangjia = request.data.get('shangjia_zhuangtai')
        shenhe = request.data.get('shenhe_zhuangtai')

        # 权限判断
        if fengjin is not None:
            if fengjin:
                if '1199abc' not in user_perms:
                    return Response({'code': 403, 'msg': '无权限封禁'})
            else:
                if '1199abd' not in user_perms:
                    return Response({'code': 403, 'msg': '无权限解封'})
            mapping.fengjin_zhuangtai = bool(fengjin)

        if shangjia is not None:
            if not shangjia:
                if '1199abc' not in user_perms:
                    return Response({'code': 403, 'msg': '无权限下架'})
            else:
                if '1199abd' not in user_perms:
                    return Response({'code': 403, 'msg': '无权限上架'})
            mapping.shangjia_zhuangtai = bool(shangjia)

        if shenhe is not None:
            if shenhe:
                if '1199abd' not in user_perms:
                    return Response({'code': 403, 'msg': '无权限审核通过'})
            mapping.shenhe_zhuangtai = bool(shenhe)

        mapping.save()
        return Response({'code': 0, 'msg': '修改成功'})                 






# 商品管理相关权限组（拥有任意一个即可查看列表）
PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd', '1199abe']


class ProductListView(APIView):
    """
    商品列表接口，支持多条件筛选与分页。
    请求路径: /houtai/hqhtdpsplx
    权限要求: 1199ab / 1199abc / 1199abd / 1199abe 任一即可
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # ----- 身份与权限验证 -----
        username = request.data.get('username')
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return Response({'code': 403, 'msg': '身份验证失败'})

        if not set(permissions).intersection(PRODUCT_PERMS):
            return Response({'code': 403, 'msg': '无商品管理权限'})

        # ----- 安全解析分页与筛选参数 -----
        try:
            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('page_size', 15))
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': '分页参数格式错误'})

        keyword = str(request.data.get('keyword', '')).strip()
        shop_id = request.data.get('shop_id')               # 店铺ID (可选)
        shop_product_type_id = request.data.get('shop_product_type_id')  # 店铺商品类型映射ID (可选)
        public_type_id = request.data.get('public_type_id') # 公共商品类型ID (可选)

        # 布尔型状态筛选
        def parse_bool(val):
            if val is None:
                return None
            return bool(val)

        shenhe = parse_bool(request.data.get('shenhe_zhuangtai'))
        shangjia = parse_bool(request.data.get('shangjia_zhuangtai'))
        fengjin = parse_bool(request.data.get('fengjin_zhuangtai'))

        # ----- 构建查询（预加载店铺和店铺商品类型） -----
        products = Shangpin.query.select_related('dianpu', 'dianpu_leixing').all()

        # 按店铺筛选
        if shop_id is not None:
            try:
                products = products.filter(dianpu_id=int(shop_id))
            except (ValueError, TypeError):
                return Response({'code': 400, 'msg': 'shop_id 格式错误'})

        # 按店铺商品类型映射筛选
        if shop_product_type_id is not None:
            try:
                products = products.filter(dianpu_leixing_id=int(shop_product_type_id))
            except (ValueError, TypeError):
                return Response({'code': 400, 'msg': 'shop_product_type_id 格式错误'})

        # 按公共商品类型筛选（leixing_id 存储的是公共类型的ID）
        if public_type_id is not None:
            try:
                products = products.filter(leixing_id=int(public_type_id))
            except (ValueError, TypeError):
                return Response({'code': 400, 'msg': 'public_type_id 格式错误'})

        # 平台审核状态筛选 (BooleanField)
        if shenhe is not None:
            products = products.filter(shenhe_zhuangtai=shenhe)

        # 上架状态筛选
        if shangjia is not None:
            products = products.filter(shangjia_zhuangtai=shangjia)

        # 封禁状态筛选
        if fengjin is not None:
            products = products.filter(fengjin_zhuangtai=fengjin)

        # 关键词搜索：商品ID或商品标题（搜索具有模糊性，使用icontains）
        if keyword:
            # 尝试纯数字可能是商品ID，使用精确匹配与标题模糊搜索
            products = products.filter(
                models.Q(id__icontains=keyword) |
                models.Q(biaoqian__icontains=keyword)
            )

        # ----- 获取待审核总数（平台审核未通过的数量） -----
        pending_total = products.filter(shenhe_zhuangtai=False).count()

        # ----- 分页 -----
        paginator = Paginator(products, page_size)
        page_obj = paginator.get_page(page)

        # ----- 构造返回数据（批量获取公共类型名称，避免N+1） -----
        # 提取所有公共商品类型ID
        type_ids = {p.leixing_id for p in page_obj if p.leixing_id is not None}
        public_type_map = {}
        if type_ids:
            public_types = ShangpinLeixing.query.filter(id__in=type_ids).values('id', 'jieshao')
            public_type_map = {t['id']: t['jieshao'] for t in public_types}

        product_list = []
        for p in page_obj:
            product_list.append({
                'id': p.id,
                'biaoqian': p.biaoqian or '',
                'jiage': p.jiage,
                'kucun': p.kucun,
                'leixing_id': p.leixing_id,
                'public_type_name': public_type_map.get(p.leixing_id, ''),
                'zhenshi_xiaoliang': p.zhenshi_xiaoliang,
                'duiwai_xiaoliang': p.duiwai_xiaoliang,
                'jieshao': p.jieshao or '',
                'xiadan_xuzhi': p.xiadan_xuzhi or '',
                'guize_tupian': p.guize_tupian or '',
                'yaoqiuleixing': p.yaoqiuleixing,
                'huiyuan_id': p.huiyuan_id or '',
                'yongjin': p.yongjin,
                'kaioi_ewai_PlayerCommission': p.kaioi_ewai_dashou_fencheng,
                'ewai_PlayerCommission': p.ewai_dashou_fencheng,
                'CreateTime': p.CreateTime.isoformat() if p.CreateTime else '',
                'UpdateTime': p.UpdateTime.isoformat() if p.UpdateTime else '',
                'paixu': p.paixu,
                'dianpu_id': p.dianpu_id,
                'dianpu_name': p.dianpu.dianpu_mingcheng if p.dianpu else '',
                'dianpu_leixing_id': p.dianpu_leixing_id,
                'dianpu_leixing_name': p.dianpu_leixing.jieshao if p.dianpu_leixing else '',
                'shangjia_zhuangtai': p.shangjia_zhuangtai,
                'fengjin_zhuangtai': p.fengjin_zhuangtai,
                'shenhe_zhuangtai': p.shenhe_zhuangtai,
            })

        return Response({
            'code': 0,
            'data': {
                'list': product_list,
                'total': paginator.count,
                'pending_total': pending_total,   # 待审核商品总数
            }
        })


class ProductModifyView(APIView):
    """
    商品修改接口，根据修改内容细粒度鉴权。
    请求路径: /houtai/htxgdpspsj
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('username')
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return Response({'code': 403, 'msg': '身份验证失败'})

        user_perms = set(permissions)
        action = request.data.get('action', '')

        if action != 'update_product':
            return Response({'code': 400, 'msg': '未知操作'})

        return self._update_product(request, user_perms)

    @transaction.atomic
    def _update_product(self, request, user_perms):
        """执行商品修改，严格校验权限"""
        product_id = request.data.get('product_id')
        if not product_id:
            return Response({'code': 400, 'msg': '缺少商品ID'})

        try:
            # 行级锁防止并发修改
            product = Shangpin.objects.select_for_update().get(id=int(product_id))
        except (Shangpin.DoesNotExist, ValueError):
            return Response({'code': 404, 'msg': '商品不存在'})

        # 需要修改的字段
        fields_to_update = {}
        # 将要修改的状态标志用于后续权限判断
        status_changes = {
            'shangjia': request.data.get('shangjia_zhuangtai'),
            'fengjin': request.data.get('fengjin_zhuangtai'),
            'shenhe': request.data.get('shenhe_zhuangtai'),
        }

        # ---------- 权限分类检查 ----------
        # 1. 封禁/下架操作 (消极操作) -> 1199abc
        if any(status_changes.get(k) is not None and status_changes[k] == False for k in ['shangjia', 'shenhe']):
            if '1199abc' not in user_perms:
                return Response({'code': 403, 'msg': '无权限执行下架/封禁/驳回审核操作(1199abc)'})
        if status_changes.get('fengjin') is not None and status_changes['fengjin'] == True:
            if '1199abc' not in user_perms:
                return Response({'code': 403, 'msg': '无权限执行封禁操作(1199abc)'})

        # 2. 解封/上架/过审 (积极操作) -> 1199abd
        if any(status_changes.get(k) is not None and status_changes[k] == True for k in ['shangjia', 'shenhe']):
            if '1199abd' not in user_perms:
                return Response({'code': 403, 'msg': '无权限执行上架/解封/审核通过操作(1199abd)'})
        if status_changes.get('fengjin') is not None and status_changes['fengjin'] == False:
            if '1199abd' not in user_perms:
                return Response({'code': 403, 'msg': '无权限执行解封操作(1199abd)'})

        # 3. 修改其他数据（非状态字段）-> 1199abe
        # 除了上架、封禁、审核之外的字段都属于“其他数据”
        other_fields = [k for k in request.data.keys() if k not in ['action', 'product_id', 'username',
                                                                     'shangjia_zhuangtai', 'fengjin_zhuangtai',
                                                                     'shenhe_zhuangtai']]
        if other_fields and '1199abe' not in user_perms:
            return Response({'code': 403, 'msg': '无权限修改商品基本信息(1199abe)'})

        # ---------- 应用修改 ----------
        # 允许修改的字段白名单（防止注入）
        # 前端参数名 → 模型字段名映射
        field_name_map = {
            'kaioi_ewai_PlayerCommission': 'kaioi_ewai_dashou_fencheng',
            'ewai_PlayerCommission': 'ewai_dashou_fencheng',
        }
        allowed_fields = [
            'biaoqian', 'jiage', 'kucun', 'leixing_id', 'duiwai_xiaoliang',
            'jieshao', 'xiadan_xuzhi', 'yaoqiuleixing', 'yongjin',
            'kaioi_ewai_PlayerCommission', 'ewai_PlayerCommission',
            'shangjia_zhuangtai', 'fengjin_zhuangtai', 'shenhe_zhuangtai'
        ]

        update_fields = []
        for field in allowed_fields:
            value = request.data.get(field)
            if value is not None:
                model_field = field_name_map.get(field, field)
                # 简单类型校验（根据字段类型）
                if field in ['jiage', 'kucun', 'duiwai_xiaoliang', 'yongjin', 'ewai_PlayerCommission', 'paixu']:
                    try:
                        value = float(value) if 'jiage' in field or 'yongjin' in field or 'ewai' in field else int(value)
                    except (ValueError, TypeError):
                        return Response({'code': 400, 'msg': f'字段 {field} 值格式错误'})
                setattr(product, model_field, value)
                update_fields.append(model_field)

        if update_fields:
            product.save(update_fields=update_fields)
        return Response({'code': 0, 'msg': '商品修改成功'})        




# 与前端约定的聊天权限码
CHAT_PERM_CODES = ['abca1', 'baac2', 'cabc3', 'cb3a2', 'bcaa4']

class KefuChatPermissionsView(APIView):
    """
    获取客服聊天权限及GoEasy配置
    POST /houtai/kfhqltqx
    Body: { "phone": "客服手机号" }
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 从前端获取传递的账号，用于防越权校验
        username_from_frontend = request.data.get('phone', None)

        # 2. 调用公共验证方法。
        #    返回 (kefu_obj, permissions) 或 (None, Response)
        kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)

        # 3. 如果第一个返回值为 None，说明验证失败，直接返回第二个值（错误Response）
        if kefu_obj is None:
            return permissions

        # ---------- 验证通过，permissions 已经是权限码列表 ----------
        # 4. 检查是否拥有至少一个聊天权限
        has_chat_perm = any(p in CHAT_PERM_CODES for p in permissions)

        # 5. 若有聊天权限，获取 GoEasy AppKey
        goeasy_appkey = ''
        if has_chat_perm:
            goeasy_appkey = getattr(settings, 'GOEASY_APPKEY', '')

        # 6. 返回数据
        return Response({
            'code': 0,
            'msg': 'ok',
            'data': {
                'permissions': permissions,
                'goeasy_appkey': goeasy_appkey
            }
        })





class FaKuanTongJiView(APIView):
    """
    罚款统计接口
    POST /houtai/htglyhqcfsltj
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 400, 'msg': '参数不完整'})

        try:
            kefu, permissions = verify_kefu_permission(request, username)
            if kefu is None:
                return permissions

            if not has_fadan_view_permission(permissions, username):
                return Response({'code': 403, 'msg': '无权限查看罚款统计'})

            stats = filter_penalty_qs(Penalty.query.all(), request).aggregate(
                total=Count('id'),
                daijiaona=Count('id', filter=Q(Status=1)),
                shensuzhong=Count('id', filter=Q(Status=3)),
                yijiaona=Count('id', filter=Q(Status=2)),
                yibohui=Count('id', filter=Q(Status=4)),
            )
            data = {k: v or 0 for k, v in stats.items()}
            from jituan.services.club_user_access import list_response_meta
            return Response({'code': 0, 'msg': '成功', 'data': {**data, **list_response_meta(request)}})
        except Exception as e:
            logger.error(f"罚款统计异常: {traceback.format_exc()}")
            return Response({'code': 500, 'msg': '系统繁忙'})


class FaKuanLieBiaoView(APIView):
    """
    罚款列表接口
    POST /houtai/hthqfklb
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        if not username:
            return Response({'code': 400, 'msg': '参数不完整'})

        try:
            kefu, permissions = verify_kefu_permission(request, username)
            if kefu is None:
                return permissions
            if not has_fadan_view_permission(permissions, username):
                return Response({'code': 403, 'msg': '无权限查看罚款列表'})

            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('page_size', 20))
            if page_size > 100:
                page_size = 100

            qs = filter_penalty_qs(Penalty.query.all(), request)

            # 状态筛选
            zhuangtai = request.data.get('zhuangtai')
            if zhuangtai is not None and int(zhuangtai) in [1, 2, 3, 4]:
                qs = qs.filter(Status=int(zhuangtai))

            # 被处罚人ID
            PenalizedUserID = request.data.get('PenalizedUserID', '').strip()
            if PenalizedUserID:
                qs = qs.filter(PenalizedUserID=PenalizedUserID)

            # 被处罚人身份
            beichufa_shenfen = request.data.get('beichufa_shenfen')
            if beichufa_shenfen is not None and int(beichufa_shenfen) in [1, 2, 3, 4]:
                qs = qs.filter(Identity=int(beichufa_shenfen))

            # 模糊搜索（订单ID、被罚人ID、申请人ID）
            sousuo = request.data.get('sousuo', '').strip()
            if sousuo:
                qs = qs.filter(
                    Q(RelatedOrderID__icontains=sousuo) |
                    Q(PenalizedUserID__icontains=sousuo) |
                    Q(ApplicantID__icontains=sousuo)
                )

            qs = qs.order_by('-CreateTime')
            paginator = Paginator(qs, page_size)
            try:
                page_obj = paginator.page(page)
            except Exception:
                return Response({
                    'code': 0, 'msg': '成功',
                    'data': {'list': [], 'total': 0, 'page': page, 'page_size': page_size}
                })

            records = list(page_obj)
            penalty_ids = [r.id for r in records]

            # 批量获取图片并分组
            zhengju_map = {}
            shensu_map = {}
            if penalty_ids:
                tupians = PenaltyAppealImage.query.filter(
                    Penalty_id__in=penalty_ids
                ).values('Penalty_id', 'ImageURL', 'Purpose')
                for t in tupians:
                    fid = t['Penalty_id']
                    url = t['ImageURL']
                    if t['Purpose'] == 1:
                        zhengju_map.setdefault(fid, []).append(url)
                    else:
                        shensu_map.setdefault(fid, []).append(url)

            # 组装列表数据
            results = []
            for r in records:
                results.append({
                    'id': r.id,
                    'beichufa_id': r.PenalizedUserID,
                    'shenfen': r.Identity,
                    'shenfen_text': dict(Penalty._meta.get_field('Identity').choices).get(r.Identity, ''),
                    'shenqing_chufa': r.ApplicantID or '',
                    'shenqingren_shenfen': r.ApplicantIdentity,
                    'chufaliyou': r.Reason or '',
                    'fakuanjine': str(r.FineAmount),
                    'guanliandingdan_id': r.RelatedOrderID or '',
                    'zhuangtai': r.Status,
                    'yingxiang_qiangdan': r.AffectsGrabbing,
                    'shensuliyou': r.AppealReason or '',
                    'bohuiliyou': r.RejectReason or '',
                    'chulizhe': r.ProcessorID or '',
                    'chulizhe_shenfen': r.ProcessorIdentity,
                    'zhengju_tupian': zhengju_map.get(r.id, []),
                    'shensu_tupian': shensu_map.get(r.id, []),
                    'creat_time': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '',
                    'create_time': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '',
                    'UpdateTime': r.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if r.UpdateTime else '',
                })

            from jituan.services.club_user_access import list_response_meta
            return Response({
                'code': 0, 'msg': '成功',
                'data': {
                    'list': results,
                    'total': paginator.count,
                    'page': page,
                    'page_size': page_size,
                    **list_response_meta(request),
                }
            })
        except Exception as e:
            logger.error(f"罚款列表异常: {traceback.format_exc()}")
            return Response({'code': 500, 'msg': '系统繁忙'})


class FaKuanChuLiView(APIView):
    """
    处理罚款申诉（同意/拒绝）
    POST /houtai/glyclfk
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def post(self, request):
        try:
            username = request.data.get('username', '').strip()
            penalty_id = request.data.get('penalty_id') or request.data.get('fadan_id')
            action = request.data.get('action', '').strip()
            chuli_liyou = request.data.get('chuli_liyou', '').strip()

            if not all([username, penalty_id, action]):
                return Response({'code': 400, 'msg': '参数不完整'})

            kefu, permissions = verify_kefu_permission(request, username)
            if kefu is None:
                return permissions

            with transaction.atomic():
                try:
                    penalty = Penalty.objects.select_for_update().get(id=penalty_id)
                except Penalty.DoesNotExist:
                    return Response({'code': 404, 'msg': '罚单不存在'})

                deny = forbid_penalty_out_of_scope(request, penalty)
                if deny:
                    return deny

                if penalty.Status != 3:
                    return Response({'code': 400, 'msg': '当前状态不可处理'})

                if not check_fadan_permission(permissions, penalty.Identity):
                    return Response({'code': 403, 'msg': '无权限处理该类罚单'})

                # 处理上传的图片
                uploaded_urls = []
                files = request.FILES.getlist('tupian') or []
                if not files:
                    single = request.FILES.get('tupian')
                    if single:
                        files = [single]
                for file in files:
                    valid, msg = validate_image(file)
                    if not valid:
                        return Response({'code': 400, 'msg': msg})
                    ext = file.name.split('.')[-1] if '.' in file.name else 'jpg'
                    file_name = f"chufa_{penalty_id}_{int(time.time() * 1000)}.{ext}"
                    file_path = f"chufatupian/kechufatupian/{file_name}"
                    url = upload_to_oss(file, file_path)
                    if not url:
                        return Response({'code': 500, 'msg': '图片上传失败'})
                    uploaded_urls.append(file_path)

                # 更新状态
                if action == 'agree':          # 同意申诉 → 驳回处罚
                    penalty.Status = 4
                    penalty.RejectReason = chuli_liyou
                elif action == 'reject':       # 拒绝申诉 → 回到待缴纳
                    penalty.Status = 1
                    penalty.RejectReason = chuli_liyou
                else:
                    return Response({'code': 400, 'msg': '无效的操作类型'})

                # 记录处理者（使用 request.user 的 phone）
                penalty.ProcessorID = request.user.Phone
                penalty.ProcessorIdentity = 2   # 这里假设处理者是售后身份，可根据实际业务获取
                penalty.save()

                # 保存图片（证据图片，用途1）
                for relative_url in uploaded_urls:
                    PenaltyAppealImage.query.create(
                        Penalty_id=penalty_id,
                        PenalizedUserID=penalty.PenalizedUserID,
                        ImageURL=relative_url,
                        Purpose=1
                    )

            logger.info(f"罚款处理成功: {penalty_id}, 操作: {action}")
            return Response({'code': 0, 'msg': '处理成功'})

        except Exception as e:
            logger.error(f"罚款处理异常: {traceback.format_exc()}")
            return Response({'code': 500, 'msg': '系统繁忙'})


class FaKuanChuangJianView(APIView):
    """
    管理员创建罚款
    POST /houtai/htfksc
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def post(self, request):
        try:
            username = request.data.get('username', '').strip()
            PenalizedUserID, Reason, FineAmount, AffectsGrabbing = pick_penalty_create_params(request.data)
            shenfen = request.data.get('shenfen')

            if not all([username, PenalizedUserID, shenfen, Reason, FineAmount is not None]):
                return Response({'code': 400, 'msg': '参数不完整'})

            try:
                shenfen = int(shenfen)
                FineAmount = float(FineAmount)
            except (ValueError, TypeError):
                return Response({'code': 400, 'msg': '参数格式错误'})

            if shenfen not in [1, 2, 3, 4]:
                return Response({'code': 400, 'msg': '无效的被罚人身份'})

            kefu, permissions = verify_kefu_permission(request, username)
            if kefu is None:
                return permissions

            if not check_fadan_permission(permissions, shenfen):
                return Response({'code': 403, 'msg': '无权限创建该类罚单'})

            # 验证用户是否存在对应的扩展表
            try:
                user = User.query.get(UserUID=PenalizedUserID)
                profile_attr = SHENFEN_PROFILE_MAP.get(shenfen)
                if not profile_attr or not hasattr(user, profile_attr):
                    return Response({'code': 400, 'msg': '该用户不是所选身份'})
            except User.DoesNotExist:
                return Response({'code': 404, 'msg': '用户不存在'})

            # 处理图片上传
            uploaded_urls = []
            files = request.FILES.getlist('tupian') or []
            if not files:
                single = request.FILES.get('tupian')
                if single:
                    files = [single]
            for file in files:
                valid, msg = validate_image(file)
                if not valid:
                    return Response({'code': 400, 'msg': msg})
                ext = file.name.split('.')[-1] if '.' in file.name else 'jpg'
                file_name = f"chufa_{int(time.time() * 1000)}.{ext}"
                file_path = f"chufatupian/kechufatupian/{file_name}"
                url = upload_to_oss(file, file_path)
                if not url:
                    return Response({'code': 500, 'msg': '图片上传失败'})
                uploaded_urls.append(file_path)

            with transaction.atomic():
                penalty = Penalty.query.create(
                    PenalizedUserID=PenalizedUserID,
                    Identity=shenfen,
                    ApplicantID=request.user.Phone,      # 申请人（客服）手机号
                    ApplicantIdentity=2,                   # 售后身份
                    Reason=Reason,
                    FineAmount=FineAmount,
                    AffectsGrabbing=AffectsGrabbing,
                    Status=1,                            # 待缴纳
                    ClubID=resolve_penalty_club_id(
                        penalized_user_id=PenalizedUserID, request=request,
                    ),
                )
                for relative_url in uploaded_urls:
                    PenaltyAppealImage.query.create(
                        Penalty=penalty,
                        PenalizedUserID=PenalizedUserID,
                        ImageURL=relative_url,
                        Purpose=1
                    )

            logger.info(f"罚款创建成功: {penalty.id}")
            return Response({'code': 0, 'msg': '罚款已创建', 'data': {'id': penalty.id}})

        except Exception as e:
            logger.error(f"创建罚款异常: {traceback.format_exc()}")
            return Response({'code': 500, 'msg': '系统繁忙'})


class FineApplyView(APIView):
    """
    客服罚款申请接口（基于订单对打手发起罚款）
    POST /houtai/kffkdssq
    权限: JWT认证 + 客服权限验证

    请求参数:
        username             (必填) : 客服用户名（手机号）
        OrderID           (必填) : 订单ID
        Reason           (必填) : 罚款原因
        FineAmount           (必填) : 罚款金额（元）
        AffectsGrabbing   (可选) : 是否影响抢单，1=是，0=否，默认1
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def post(self, request):
        try:
            username = request.data.get('username', '').strip()
            OrderID = pick_order_id(request.data)
            _, Reason, FineAmount, AffectsGrabbing = pick_penalty_create_params(request.data)

            if not all([username, OrderID, Reason, FineAmount is not None]):
                return Response({'code': 400, 'msg': '参数不完整'})

            try:
                FineAmount = float(FineAmount)
            except (ValueError, TypeError):
                return Response({'code': 400, 'msg': '罚款金额格式错误'})

            kefu, permissions = verify_kefu_permission(request, username)
            if kefu is None:
                return permissions

            # 验证订单存在
            try:
                order = Order.query.get(OrderID=OrderID)
            except Order.DoesNotExist:
                return Response({'code': 404, 'msg': '订单不存在'})

            # 获取打手ID
            dashou_id = order.PlayerID
            if not dashou_id:
                return Response({'code': 400, 'msg': '该订单无接单打手，无法罚款'})

            # 重复罚款检查
            if Penalty.query.filter(PenalizedUserID=dashou_id, RelatedOrderID=OrderID).exists():
                return Response({'code': 400, 'msg': '该打手已被罚款过'})

            # 检查权限（打手身份 shenfen=1）
            if not check_fadan_permission(permissions, 1):
                return Response({'code': 403, 'msg': '无权限处罚打手'})

            # 处理图片上传
            uploaded_urls = []
            files = request.FILES.getlist('tupian') or []
            if not files:
                single = request.FILES.get('tupian')
                if single:
                    files = [single]
            for file in files:
                valid, msg = validate_image(file)
                if not valid:
                    return Response({'code': 400, 'msg': msg})
                ext = file.name.split('.')[-1] if '.' in file.name else 'jpg'
                file_name = f"chufa_{int(time.time() * 1000)}.{ext}"
                file_path = f"chufatupian/kechufatupian/{file_name}"
                url = upload_to_oss(file, file_path)
                if not url:
                    return Response({'code': 500, 'msg': '图片上传失败'})
                uploaded_urls.append(file_path)

            # 创建罚单
            with transaction.atomic():
                penalty = Penalty.query.create(
                    PenalizedUserID=dashou_id,
                    ApplicantID=request.user.Phone,
                    Identity=1,                                # 被处罚人身份：1 打手
                    Reason=Reason,
                    FineAmount=FineAmount,
                    RelatedOrderID=OrderID,
                    Status=1,                              # 待缴纳
                    AffectsGrabbing=AffectsGrabbing,
                    ApplicantIdentity=1,                    # 申请人身份：1 客服
                    ClubID=resolve_penalty_club_id(order=order, request=request),
                )
                for relative_url in uploaded_urls:
                    PenaltyAppealImage.query.create(
                        Penalty=penalty,
                        PenalizedUserID=dashou_id,
                        ImageURL=relative_url,
                        Purpose=1
                    )

            logger.info(f"客服罚款申请成功: 订单 {OrderID}, 打手 {dashou_id}, 金额 {FineAmount}")
            return Response({'code': 0, 'msg': '罚款已生成', 'data': {'id': penalty.id}})

        except Exception as e:
            logger.error(f"客服罚款申请异常: {traceback.format_exc()}")
            return Response({'code': 500, 'msg': '系统繁忙'})


class PunishDashouView(APIView):
    """
    客服处罚打手接口（扣除积分）
    POST /houtai/kpcf
    权限: JWT认证 + 客服权限验证

    请求参数:
        username       (必填) : 客服用户名（手机号）
        OrderID     (必填) : 订单ID
        reason         (可选) : 处罚原因
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        try:
            username = request.data.get('username', '').strip()
            OrderID = request.data.get('OrderID', '').strip()
            reason = request.data.get('reason', '').strip()

            if not all([username, OrderID]):
                return Response({'code': 400, 'msg': '参数不完整'})

            kefu, permissions = verify_kefu_permission(request, username)
            if kefu is None:
                return permissions

            # 检查权限（打手身份 shenfen=1）
            if not check_fadan_permission(permissions, 1):
                return Response({'code': 403, 'msg': '无权限处罚打手'})

            # 查询订单
            try:
                order = Order.query.get(OrderID=OrderID)
            except Order.DoesNotExist:
                return Response({'code': 404, 'msg': '订单不存在'})

            # 获取打手ID
            dashou_id = order.PlayerID
            if not dashou_id:
                return Response({'code': 400, 'msg': '订单未接单，无法处罚打手'})

            # 查询打手
            try:
                dashou_user = User.query.get(UserUID=dashou_id)
                dashou = dashou_user.DashouProfile
            except User.DoesNotExist:
                return Response({'code': 404, 'msg': '打手用户不存在'})
            except ObjectDoesNotExist:
                return Response({'code': 400, 'msg': '该用户不是打手'})

            # 创建处罚记录（扣除积分5分）
            jifen = 5
            with transaction.atomic():
                penalty_record = PenaltyRecord.query.create(
                    PlayerID=dashou_id,
                    ApplicantID=username,
                    PenaltyReason=reason or '',
                    ApplyStatus=0,
                    DeductedPoints=jifen,
                    OrderID=OrderID,
                    ClubID=resolve_penalty_record_club_id(order_id=OrderID, player_id=dashou_id),
                )
                # 扣除打手积分
                dashou.jifen = F('jifen') - jifen
                dashou.save(update_fields=['jifen'])

            logger.info(f"客服处罚打手成功: 打手 {dashou_id}, 订单 {OrderID}, 扣除积分 {jifen}")
            return Response({'code': 0, 'msg': '处罚成功', 'data': {'penalty_record_id': penalty_record.id}})

        except Exception as e:
            logger.error(f"客服处罚打手异常: {traceback.format_exc()}")
            return Response({'code': 500, 'msg': '系统繁忙'})







class HqbkxxView(APIView):
    """
    获取板块配置所需全部数据
    POST /houtai/hqbkxx
    Body: { "phone": "管理员手机号" }
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 从前端获取传递的账号，用于防越权校验
        username_from_frontend = request.data.get('phone', None)

        # 2. 调用公共验证方法
        kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)

        # 3. 如果第一个返回值为 None，说明验证失败，直接返回第二个值
        if kefu_obj is None:
            return permissions

        # ---------- 验证通过 ----------
        # 4. 检查是否有板块管理权限
        if 'bankuai' not in permissions:
            return Response({'code': 403, 'msg': '无板块管理权限'}, status=403)

        # 5. 查询所有板块
        bankuai_list = []
        for bk in Bankuai.query.all():
            bankuai_list.append({
                'bankuai_id': bk.bankuai_id,
                'mingcheng': bk.mingcheng,
                'CreateTime': bk.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if bk.CreateTime else ''
            })

        # 6. 查询所有商品类型
        shangpin_leixing_list = []
        for sp in ShangpinLeixing.query.all():
            shangpin_leixing_list.append({
                'id': sp.id,
                'jieshao': sp.jieshao,
                'tupian_url': sp.tupian_url or '',
                'bankuai_id': sp.bankuai_id
            })

        # 7. 查询会员（附带当前俱乐部售价）
        from jituan.constants import CLUB_ID_DEFAULT, DATA_SCOPE_ALL
        from jituan.models import ClubHuiyuanPrice
        from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope

        scope = resolve_club_scope(request)
        club_id = resolve_club_id_from_request(request)
        price_map = {}
        if scope != DATA_SCOPE_ALL:
            for row in ClubHuiyuanPrice.query.filter(club_id=club_id):
                price_map[row.huiyuan_id] = row

        huiyuan_list = []
        for hy in Huiyuan.query.all():
            row = price_map.get(hy.huiyuan_id)
            if scope != DATA_SCOPE_ALL and club_id != CLUB_ID_DEFAULT and row is None:
                continue
            if row is not None and not row.is_enabled:
                continue
            item = {
                'huiyuan_id': hy.huiyuan_id,
                'jieshao': hy.jieshao,
                'bankuai_id': hy.bankuai_id,
                'jiage': str(row.jiage) if row else str(hy.jiage),
                'guanshifc': str(row.guanshifc) if row else str(hy.guanshifc),
                'zuzhangfc': str(row.zuzhangfc) if row else str(hy.zuzhangfc),
            }
            huiyuan_list.append(item)

        return Response({
            'code': 0,
            'msg': 'ok',
            'data': {
                'bankuai': bankuai_list,
                'shangpin_leixing': shangpin_leixing_list,
                'huiyuan': huiyuan_list,
                'club_id': club_id,
                'scope': scope,
            }
        })


class BkxgView(APIView):
    """
    板块增删改统一接口
    POST /houtai/bkxg
    Body: {
        "phone": "管理员手机号",
        "action": "create | update | delete | add_items | remove_items",
        "bankuai_id": int,
        "bankuai_name": str,
        "item_type": "shangpin_leixing | huiyuan",
        "item_ids": [1,2,3]
    }
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 从前端获取传递的账号
        username_from_frontend = request.data.get('phone', None)

        # 2. 调用公共验证方法
        kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)

        # 3. 验证失败直接返回
        if kefu_obj is None:
            return permissions

        # 4. 检查板块管理权限
        if 'bankuai' not in permissions:
            return Response({'code': 403, 'msg': '无板块管理权限'}, status=403)

        # 5. 获取操作类型
        action = request.data.get('action')
        if not action:
            return Response({'code': 1, 'msg': '缺少 action 参数'}, status=400)

        # ==================== 创建板块 ====================
        if action == 'create':
            bankuai_name = request.data.get('bankuai_name', '').strip()
            if not bankuai_name:
                return Response({'code': 1, 'msg': '板块名称不能为空'}, status=400)
            if Bankuai.query.filter(mingcheng=bankuai_name).exists():
                return Response({'code': 1, 'msg': '板块名称已存在'}, status=400)

            shangpin_ids = request.data.get('shangpin_ids', [])
            huiyuan_ids = request.data.get('huiyuan_ids', [])

            with transaction.atomic():
                new_bk = Bankuai.query.create(mingcheng=bankuai_name)
                if shangpin_ids:
                    ShangpinLeixing.query.filter(
                        id__in=shangpin_ids,
                        bankuai__isnull=True
                    ).update(bankuai=new_bk)
                if huiyuan_ids:
                    Huiyuan.query.filter(
                        huiyuan_id__in=huiyuan_ids,
                        bankuai__isnull=True
                    ).update(bankuai=new_bk)

            return Response({
                'code': 0,
                'msg': '创建成功',
                'data': {'bankuai_id': new_bk.bankuai_id}
            })

        # ==================== 以下操作需要 bankuai_id ====================
        bankuai_id = request.data.get('bankuai_id')
        if not bankuai_id:
            return Response({'code': 1, 'msg': '缺少 bankuai_id'}, status=400)

        try:
            bk = Bankuai.query.get(bankuai_id=bankuai_id)
        except Bankuai.DoesNotExist:
            return Response({'code': 1, 'msg': '板块不存在'}, status=404)

        # ==================== 修改板块名称 ====================
        if action == 'update':
            new_name = request.data.get('bankuai_name', '').strip()
            if not new_name:
                return Response({'code': 1, 'msg': '板块名称不能为空'}, status=400)
            if Bankuai.query.filter(mingcheng=new_name).exclude(bankuai_id=bankuai_id).exists():
                return Response({'code': 1, 'msg': '板块名称已存在'}, status=400)
            bk.mingcheng = new_name
            bk.save()
            return Response({'code': 0, 'msg': '修改成功'})

        # ==================== 删除板块 ====================
        elif action == 'delete':
            with transaction.atomic():
                ShangpinLeixing.query.filter(bankuai=bk).update(bankuai=None)
                Huiyuan.query.filter(bankuai=bk).update(bankuai=None)
                bk.delete()
            return Response({'code': 0, 'msg': '删除成功'})

        # ==================== 添加绑定 ====================
        elif action == 'add_items':
            item_type = request.data.get('item_type')
            item_ids = request.data.get('item_ids', [])
            if not item_type or not item_ids:
                return Response({'code': 1, 'msg': '缺少 item_type 或 item_ids'}, status=400)

            if item_type == 'shangpin_leixing':
                ShangpinLeixing.query.filter(
                    id__in=item_ids,
                    bankuai__isnull=True
                ).update(bankuai=bk)
            elif item_type == 'huiyuan':
                Huiyuan.query.filter(
                    huiyuan_id__in=item_ids,
                    bankuai__isnull=True
                ).update(bankuai=bk)
            else:
                return Response({'code': 1, 'msg': '无效的 item_type'}, status=400)
            return Response({'code': 0, 'msg': '添加成功'})

        # ==================== 移除绑定 ====================
        elif action == 'remove_items':
            item_type = request.data.get('item_type')
            item_ids = request.data.get('item_ids', [])
            if not item_type or not item_ids:
                return Response({'code': 1, 'msg': '缺少 item_type 或 item_ids'}, status=400)

            if item_type == 'shangpin_leixing':
                ShangpinLeixing.query.filter(
                    id__in=item_ids,
                    bankuai=bk
                ).update(bankuai=None)
            elif item_type == 'huiyuan':
                Huiyuan.query.filter(
                    huiyuan_id__in=item_ids,
                    bankuai=bk
                ).update(bankuai=None)
            else:
                return Response({'code': 1, 'msg': '无效的 item_type'}, status=400)
            return Response({'code': 0, 'msg': '移除成功'})

        else:
            return Response({'code': 1, 'msg': '未知操作'}, status=400)            









class CaiwuView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        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 'caiwu' not in permissions:
            return Response({'code': 403, 'msg': '无财务查看权限'}, status=403)

        from jituan.services.caiwu_stats import build_caiwu_payload

        try:
            data = build_caiwu_payload(request)
            return Response({'code': 0, 'msg': 'ok', 'data': data})
        except Exception:
            logger.exception('CaiwuView 异常')
            return Response({'code': 99, 'msg': '统计失败'}, status=500)


class CwhybkhqView(APIView):
    """
    获取所有板块及对应会员列表
    POST /houtai/cwhybkhq
    Body: { "phone": "管理员手机号" }
    """
    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 'huiyuancaiwu' not in permissions:
                return Response({'code': 403, 'msg': '无会员财务查看权限'}, status=403)

            bankuai_list = []
            for bk in Bankuai.query.all():
                members = Huiyuan.query.filter(bankuai=bk).values('huiyuan_id', 'jieshao', 'bankuai_id')
                bankuai_list.append({
                    'bankuai_id': bk.bankuai_id,
                    'mingcheng': bk.mingcheng,
                    'members': list(members)
                })

            return Response({'code': 0, 'msg': 'ok', 'data': bankuai_list})
        except Exception as e:
            logger.exception("CwhybkhqView 接口错误")
            return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)


class HybkjtsjView(APIView):
    """
    会员详细统计数据
    POST /houtai/hybkjtsj
    Body: {
        "phone": "管理员手机号",
        "huiyuan_id": "会员ID",
        "granularity": "day" | "month" | "year",
        "year": 2026,
        "month": 5,
        "include_guanshi": true,
        "include_zuzhang": true,
        "summary_date": "2026-05-16"
    }
    """
    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 'huiyuancaiwu' not in permissions:
                return Response({'code': 403, 'msg': '无会员财务查看权限'}, status=403)

            huiyuan_id = request.data.get('huiyuan_id')
            granularity = request.data.get('granularity', 'day')
            year = request.data.get('year')
            month = request.data.get('month')
            include_guanshi = request.data.get('include_guanshi', False)
            include_zuzhang = request.data.get('include_zuzhang', False)
            summary_date = request.data.get('summary_date')

            if not huiyuan_id:
                return Response({'code': 1, 'msg': '缺少会员ID'}, status=400)

            # 🔧 直接使用 date.today() 避免模块名冲突
            now = date.today()

            # ---------- 构造时间范围 ----------
            if granularity == 'day':
                if not year or not month:
                    return Response({'code': 1, 'msg': '按日统计需要年份和月份'}, status=400)
                start_date = date(year, month, 1)
                if month == 12:
                    end_date = date(year + 1, 1, 1)
                else:
                    end_date = date(year, month + 1, 1)
                trunc_func = TruncDate('CreateTime')
                if not summary_date:
                    summary_date = now.strftime('%Y-%m-%d')
                summary_q = datetime.strptime(summary_date, '%Y-%m-%d').date()
            elif granularity == 'month':
                if not year:
                    return Response({'code': 1, 'msg': '按月统计需要年份'}, status=400)
                start_date = date(year, 1, 1)
                end_date = date(year + 1, 1, 1)
                trunc_func = TruncMonth('CreateTime')
                if not summary_date:
                    summary_date = now.strftime('%Y-%m')
                summary_q = datetime.strptime(summary_date + '-01', '%Y-%m-%d').date()
            elif granularity == 'year':
                year = year or now.year
                start_date = date(year, 1, 1)
                end_date = date(year + 1, 1, 1)
                trunc_func = TruncYear('CreateTime')
                if not summary_date:
                    summary_date = str(year)
                summary_q = date(year, 1, 1)
            else:
                return Response({'code': 1, 'msg': '无效的颗粒度'}, status=400)

            # 生成 naive datetime（避免 USE_TZ=False 报错）
            start_dt = datetime.combine(start_date, datetime.min.time())
            end_dt = datetime.combine(end_date, datetime.min.time())

            # ---------- 充值过滤 ----------
            chongzhi_filter = {
                'huiyuan_id': huiyuan_id,       # 会员ID筛选
                'leixing': 1,
                'zhuangtai': 3,
                'CreateTime__gte': start_dt,
                'CreateTime__lt': end_dt
            }

            # ---------- 分红过滤 ----------
            fenhong_filter = {
                'huiyuan_id': huiyuan_id,       # 分红表会员ID筛选
                'CreateTime__gte': start_dt,
                'CreateTime__lt': end_dt
            }

            # 充值分组查询
            chongzhi_qs = filter_queryset_by_club(
                Czjilu.query.filter(**chongzhi_filter), request,
            ) \
                .annotate(period=trunc_func) \
                .values('period') \
                .annotate(
                    chongzhi_count=Count('id'),
                    chongzhi_amount=Sum('jine')
                ).order_by('period')

            # 分红分组查询
            fenhong_qs = None
            if include_guanshi or include_zuzhang:
                fenhong_agg = {}
                if include_guanshi:
                    fenhong_agg['guanshi_amount'] = Sum('fenhong')
                if include_zuzhang:
                    fenhong_agg['zuzhang_amount'] = Sum('zuzhang_fenhong')
                fenhong_qs = filter_gsfenhong_qs(
                    Gsfenhong.query.filter(**fenhong_filter), request,
                ) \
                    .annotate(period=trunc_func) \
                    .values('period') \
                    .annotate(**fenhong_agg) \
                    .order_by('period')

            # 合并数据
            data_map = {}
            for item in chongzhi_qs:
                period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
                             item['period'].strftime('%Y-%m') if granularity == 'month' else \
                             item['period'].strftime('%Y')
                data_map[period_str] = {
                    'chongzhi_count': item['chongzhi_count'],
                    'chongzhi_amount': float(item['chongzhi_amount'] or 0)
                }

            if fenhong_qs:
                for item in fenhong_qs:
                    period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
                                 item['period'].strftime('%Y-%m') if granularity == 'month' else \
                                 item['period'].strftime('%Y')
                    if period_str not in data_map:
                        data_map[period_str] = {'chongzhi_count': 0, 'chongzhi_amount': 0.0}
                    if include_guanshi:
                        data_map[period_str]['guanshi_amount'] = float(item.get('guanshi_amount') or 0)
                    if include_zuzhang:
                        data_map[period_str]['zuzhang_amount'] = float(item.get('zuzhang_amount') or 0)

            time_series = []
            for period_str, vals in data_map.items():
                guanshi = vals.get('guanshi_amount', 0) if include_guanshi else 0
                zuzhang = vals.get('zuzhang_amount', 0) if include_zuzhang else 0
                vals['shouyi'] = round(vals['chongzhi_amount'] - guanshi - zuzhang, 2)
                vals['date'] = period_str
                time_series.append(vals)
            time_series.sort(key=lambda x: x['date'])

            # ---------- 汇总数据 ----------
            summary_chongzhi_filter = chongzhi_filter.copy()
            if granularity == 'day':
                summary_chongzhi_filter['CreateTime__date'] = summary_q
            elif granularity == 'month':
                summary_chongzhi_filter['CreateTime__year'] = summary_q.year
                summary_chongzhi_filter['CreateTime__month'] = summary_q.month
            else:
                summary_chongzhi_filter['CreateTime__year'] = summary_q.year

            summary_chongzhi = Czjilu.query.filter(**summary_chongzhi_filter).aggregate(
                total_count=Count('id'),
                total_amount=Sum('jine')
            )
            total_chongzhi_count = summary_chongzhi['total_count'] or 0
            total_chongzhi_amount = float(summary_chongzhi['total_amount'] or 0)

            total_guanshi = 0.0
            total_zuzhang = 0.0
            if include_guanshi or include_zuzhang:
                summary_fenhong_filter = fenhong_filter.copy()
                if granularity == 'day':
                    summary_fenhong_filter['CreateTime__date'] = summary_q
                elif granularity == 'month':
                    summary_fenhong_filter['CreateTime__year'] = summary_q.year
                    summary_fenhong_filter['CreateTime__month'] = summary_q.month
                else:
                    summary_fenhong_filter['CreateTime__year'] = summary_q.year

                fenhong_agg = {}
                if include_guanshi:
                    fenhong_agg['guanshi_sum'] = Sum('fenhong')
                if include_zuzhang:
                    fenhong_agg['zuzhang_sum'] = Sum('zuzhang_fenhong')
                summary_fenhong = Gsfenhong.query.filter(**summary_fenhong_filter).aggregate(**fenhong_agg)
                total_guanshi = float(summary_fenhong.get('guanshi_sum') or 0)
                total_zuzhang = float(summary_fenhong.get('zuzhang_sum') or 0)

            total_shouyi = round(total_chongzhi_amount - total_guanshi - total_zuzhang, 2)

            return Response({
                'code': 0,
                'msg': 'ok',
                'data': {
                    'time_series': time_series,
                    'summary': {
                        'total_chongzhi_count': total_chongzhi_count,
                        'total_chongzhi_amount': total_chongzhi_amount,
                        'total_guanshi_fenhong': total_guanshi,
                        'total_zuzhang_fenhong': total_zuzhang,
                        'total_shouyi': total_shouyi
                    }
                }
            })
        except Exception as e:
            logger.exception("HybkjtsjView 接口错误")
            return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)





class SzxxView(APIView):
    """
    每日收支详细统计数据
    POST /houtai/szxx
    只读 daily_income_stat / daily_payout_stat，按 date 字段汇总。
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        try:
            from jituan.services.szxx_stats import build_szxx_payload

            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 'caiwu' not in permissions:
                return Response({'code': 403, 'msg': '无财务查看权限'}, status=403)

            granularity = request.data.get('granularity', 'day')
            year = request.data.get('year')
            month = request.data.get('month')
            summary_date = request.data.get('summary_date')

            data, err = build_szxx_payload(request, granularity, year, month, summary_date)
            if err:
                return Response({'code': 1, 'msg': err}, status=400)

            return Response({'code': 0, 'msg': 'ok', 'data': data})

        except Exception as e:
            logger.exception("SzxxView 接口错误")
            return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)    






     

class CwddhqlxView(APIView):
    """
    获取订单类型（商品类型）用于筛选
    POST /houtai/cwddhqlx
    Body: { "phone": "管理员手机号" }
    """
    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 'dingdancaiwu' not in permissions:
                return Response({'code': 403, 'msg': '无订单财务查看权限'}, status=403)

            # 获取非审核状态（shenhezhuangtai=1）的商品类型，只返回ID和名称
            types = ShangpinLeixing.query.filter(shenhezhuangtai=1).values('id', 'jieshao')
            return Response({
                'code': 0,
                'msg': 'ok',
                'data': list(types)
            })
        except Exception as e:
            logger.exception("CwddhqlxView 接口错误")
            return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)




class CwhqjtddsjView(APIView):
    """
    订单详细统计数据（修正版）
    POST /houtai/cwhqjtddsj
    Body: {
        "phone": "管理员手机号",
        "granularity": "day" | "month" | "year",
        "year": 2026,
        "month": 5,
        "type_id": 1,            # 商品类型ID，0表示全部
        "order_source": 1,       # 1=平台单，2=商家单，0=全部
        "summary_date": "2026-05-16"
    }
    """
    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 'dingdancaiwu' not in permissions:
                return Response({'code': 403, 'msg': '无订单财务查看权限'}, status=403)

            granularity = request.data.get('granularity', 'day')
            year = request.data.get('year')
            month = request.data.get('month')
            type_id = request.data.get('type_id', 0)
            order_source = request.data.get('order_source', 0)
            summary_date = request.data.get('summary_date')

            now = date.today()

            # ---------- 时间范围 ----------
            if granularity == 'day':
                if not year or not month:
                    return Response({'code': 1, 'msg': '按日统计需要年份和月份'}, status=400)
                start_date = date(year, month, 1)
                end_date = date(year, month + 1, 1) if month < 12 else date(year + 1, 1, 1)
                trunc_func = TruncDate('CreateTime')
                if not summary_date:
                    summary_date = now.strftime('%Y-%m-%d')
                summary_q = datetime.strptime(summary_date, '%Y-%m-%d').date()
            elif granularity == 'month':
                if not year:
                    return Response({'code': 1, 'msg': '按月统计需要年份'}, status=400)
                start_date = date(year, 1, 1)
                end_date = date(year + 1, 1, 1)
                trunc_func = TruncMonth('CreateTime')
                if not summary_date:
                    summary_date = now.strftime('%Y-%m')
                summary_q = datetime.strptime(summary_date + '-01', '%Y-%m-%d').date()
            elif granularity == 'year':
                year = year or now.year
                start_date = date(year, 1, 1)
                end_date = date(year + 1, 1, 1)
                trunc_func = TruncYear('CreateTime')
                if not summary_date:
                    summary_date = str(year)
                summary_q = date(year, 1, 1)
            else:
                return Response({'code': 1, 'msg': '无效的颗粒度'}, status=400)

            start_dt = datetime.combine(start_date, datetime.min.time())
            end_dt = datetime.combine(end_date, datetime.min.time())

            # ---------- 基础过滤 ----------
            filters = {
                'CreateTime__gte': start_dt,
                'CreateTime__lt': end_dt,
                'Status__in': [1, 2, 3, 4, 5, 6, 7, 8]  # 只统计有效订单状态
            }
            if int(type_id) != 0:
                filters['ProductTypeID'] = type_id
            if int(order_source) in [1, 2]:
                filters['Platform'] = order_source

            # ---------- 时间序列分组查询 ----------
            order_base = filter_queryset_by_club(
                Order.query.filter(**filters), request, club_field='ClubID',
            )
            qs = order_base \
                .annotate(period=trunc_func) \
                .values('period') \
                .annotate(
                # 订单总量（所有符合条件的状态）
                order_count=Count('id'),
                # 订单总额
                order_amount=Sum('Amount'),
                # 成交单量 (Status=3)
                success_count=Count('id', filter=Q(Status=3)),
                # 成交订单总额
                success_amount=Sum('Amount', filter=Q(Status=3)),
                # 退款单量 (Status=5)
                refund_count=Count('id', filter=Q(Status=5)),
                # 打手分成总额（仅成交订单）
                PlayerCommission_sum=Sum('PlayerCommission', filter=Q(Status=3)),
                # 平台成交订单的店铺分红
                dianpu_fenhong_sum=Sum('pingtai_kuozhan__ShopIncome',
                                       filter=Q(Status=3, Platform=1)),
                # 成交订单中：平台单金额、商家单金额、各自打手分成
                platform_success_amount=Sum('Amount', filter=Q(Status=3, Platform=1)),
                merchant_success_amount=Sum('Amount', filter=Q(Status=3, Platform=2)),
                platform_dashou=Sum('PlayerCommission', filter=Q(Status=3, Platform=1)),
                merchant_dashou=Sum('PlayerCommission', filter=Q(Status=3, Platform=2)),
            ).order_by('period')

            time_series = []
            for item in qs:
                period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
                    item['period'].strftime('%Y-%m') if granularity == 'month' else \
                        item['period'].strftime('%Y')

                # 平台收益计算
                platform_profit = (item['platform_success_amount'] or 0) \
                                  - (item['platform_dashou'] or 0) \
                                  - (item['dianpu_fenhong_sum'] or 0)
                merchant_profit = (item['merchant_success_amount'] or 0) \
                                  - (item['merchant_dashou'] or 0)
                total_profit = platform_profit + merchant_profit

                time_series.append({
                    'date': period_str,
                    'order_count': item['order_count'],
                    'order_amount': float(item['order_amount'] or 0),
                    'success_count': item['success_count'],
                    'success_amount': float(item['success_amount'] or 0),
                    'refund_count': item['refund_count'],
                    'dashou_fencheng': float(item['PlayerCommission_sum'] or 0),
                    'dianpu_fenhong': float(item['dianpu_fenhong_sum'] or 0),
                    'profit': round(total_profit, 2)
                })

            # ---------- 汇总数据 ----------
            summary_qs = filter_queryset_by_club(
                Order.query.filter(**filters), request, club_field='ClubID',
            )
            total_order_count = summary_qs.count()
            total_order_amount = summary_qs.aggregate(s=Sum('Amount'))['s'] or 0
            total_success_count = summary_qs.filter(Status=3).count()
            total_success_amount = summary_qs.filter(Status=3).aggregate(s=Sum('Amount'))['s'] or 0
            total_refund_count = summary_qs.filter(Status=5).count()
            total_dashou = summary_qs.filter(Status=3).aggregate(s=Sum('PlayerCommission'))['s'] or 0
            total_dianpu_fenhong = summary_qs.filter(Status=3, Platform=1).aggregate(
                s=Sum('pingtai_kuozhan__ShopIncome'))['s'] or 0

            platform_amount = summary_qs.filter(Status=3, Platform=1).aggregate(s=Sum('Amount'))['s'] or 0
            merchant_amount = summary_qs.filter(Status=3, Platform=2).aggregate(s=Sum('Amount'))['s'] or 0
            platform_dashou_sum = \
            summary_qs.filter(Status=3, Platform=1).aggregate(s=Sum('PlayerCommission'))['s'] or 0
            merchant_dashou_sum = \
            summary_qs.filter(Status=3, Platform=2).aggregate(s=Sum('PlayerCommission'))['s'] or 0

            profit_platform = platform_amount - platform_dashou_sum - total_dianpu_fenhong
            profit_merchant = merchant_amount - merchant_dashou_sum
            total_profit = profit_platform + profit_merchant

            summary = {
                'total_order_count': total_order_count,
                'total_order_amount': round(float(total_order_amount), 2),
                'total_success_count': total_success_count,
                'total_success_amount': round(float(total_success_amount), 2),
                'total_refund_count': total_refund_count,
                'total_PlayerCommission': round(float(total_dashou), 2),
                'total_dianpu_fenhong': round(float(total_dianpu_fenhong), 2),
                'total_profit': round(total_profit, 2)
            }

            return Response({
                'code': 0,
                'msg': 'ok',
                'data': {
                    'time_series': time_series,
                    'summary': summary
                }
            })
        except Exception as e:
            logger.exception("CwhqjtddsjView 接口错误")
            return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)










class CwqtczhqView(APIView):
    """
    其他充值/罚款数据统计（非会员充值）
    POST /houtai/cwqtczhq
    Body: {
        "phone": "管理员手机号",
        "granularity": "day" | "month" | "year",
        "year": 2026,
        "month": 5,                         // day时必填
        "types": [2,3,4,5,6],               // 2=押金,3=积分,4=商家,5=考核,6=罚款
        "summary_date": "2026-05-17"        // 汇总日期，格式随颗粒度（day:YYYY-MM-DD, month:YYYY-MM, year:YYYY）
    }
    """
    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 'qtczcaiwu' not in permissions:
                return Response({'code': 403, 'msg': '无其他充值财务查看权限'}, status=403)

            granularity = request.data.get('granularity', 'day')
            year = request.data.get('year')
            month = request.data.get('month')
            types = request.data.get('types', [2, 3, 4, 5, 6])   # 默认全部
            summary_date = request.data.get('summary_date')

            if isinstance(types, list) and len(types) == 0:
                types = [2, 3, 4, 5, 6]

            now = date.today()

            # ---------- 时间范围（用于曲线图） ----------
            if granularity == 'day':
                if not year or not month:
                    return Response({'code': 1, 'msg': '按日统计需要年份和月份'}, status=400)
                start_date = date(year, month, 1)
                end_date = date(year, month + 1, 1) if month < 12 else date(year + 1, 1, 1)
                trunc_func = TruncDate('CreateTime')
                # 默认汇总日期
                if not summary_date:
                    summary_date = now.strftime('%Y-%m-%d')
                summary_q = datetime.strptime(summary_date, '%Y-%m-%d').date()
            elif granularity == 'month':
                if not year:
                    return Response({'code': 1, 'msg': '按月统计需要年份'}, status=400)
                start_date = date(year, 1, 1)
                end_date = date(year + 1, 1, 1)
                trunc_func = TruncMonth('CreateTime')
                if not summary_date:
                    summary_date = now.strftime('%Y-%m')
                summary_q = datetime.strptime(summary_date + '-01', '%Y-%m-%d').date()
            elif granularity == 'year':
                year = year or now.year
                start_date = date(year, 1, 1)
                end_date = date(year + 1, 1, 1)
                trunc_func = TruncYear('CreateTime')
                if not summary_date:
                    summary_date = str(year)
                summary_q = date(year, 1, 1)
            else:
                return Response({'code': 1, 'msg': '无效的颗粒度'}, status=400)

            start_dt = datetime.combine(start_date, datetime.min.time())
            end_dt = datetime.combine(end_date, datetime.min.time())

            # ---------- 构建时间序列 ----------
            time_series_map = {}   # period_str -> { type_code: {count, amount, profit, fenhong_count, fenhong_amount} }

            def ensure_date(ds):
                if ds not in time_series_map:
                    time_series_map[ds] = {}
                return time_series_map[ds]

            # 1. 处理充值记录（Czjilu）—— 完全不变
            cz_types = [t for t in types if t in [2, 3, 4, 5]]
            if cz_types:
                cz_qs = filter_queryset_by_club(
                    Czjilu.query.filter(
                        CreateTime__gte=start_dt,
                        CreateTime__lt=end_dt,
                        leixing__in=cz_types,
                        zhuangtai=3,
                    ),
                    request,
                ).annotate(
                    period=trunc_func
                ).values('period', 'leixing').annotate(
                    total_count=Count('id'),
                    total_amount=Sum('jine')
                ).order_by('period', 'leixing')

                for item in cz_qs:
                    period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
                                 item['period'].strftime('%Y-%m') if granularity == 'month' else \
                                 item['period'].strftime('%Y')
                    lx = item['leixing']
                    amount = float(item['total_amount'] or 0)
                    count = item['total_count']
                    # 收益：积分(3)收益为金额本身，其他为0
                    profit = amount if lx == 3 else 0.0

                    day_data = ensure_date(period_str)
                    day_data[str(lx)] = {
                        'count': count,
                        'amount': amount,
                        'profit': profit,
                        'fenhong_count': 0,      # 充值类型无分红
                        'fenhong_amount': 0.0
                    }

            # 2. 处理罚款（Penalty）—— 修改：直接从 Penalty 表获取分红总额和分红笔数
            if 6 in types:
                # 使用一次聚合查询，同时获取罚款笔数、罚款金额、分红总额、分红笔数
                penalty_qs = filter_penalty_qs(
                    Penalty.query.filter(
                        CreateTime__gte=start_dt,
                        CreateTime__lt=end_dt,
                        Status=2,
                    ),
                    request,
                ).annotate(
                    period=trunc_func
                ).values('period').annotate(
                    penalty_count=Count('id'),
                    penalty_amount=Sum('FineAmount'),
                    # 直接使用罚单表中的申请人分红金额字段
                    fenhong_amount=Sum('ApplicantBonusAmount'),
                    fenhong_count=Count('id', filter=Q(ApplicantBonusAmount__gt=0))
                ).order_by('period')

                for item in penalty_qs:
                    period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
                                 item['period'].strftime('%Y-%m') if granularity == 'month' else \
                                 item['period'].strftime('%Y')
                    day_data = ensure_date(period_str)
                    penalty_amt = float(item['penalty_amount'] or 0)
                    fenhong_amt = float(item['fenhong_amount'] or 0)
                    profit = round(penalty_amt - fenhong_amt, 2)
                    day_data['6'] = {
                        'count': item['penalty_count'],
                        'amount': penalty_amt,
                        'profit': profit,
                        'fenhong_count': item['fenhong_count'],
                        'fenhong_amount': fenhong_amt
                    }

            # 构造有序时间序列列表
            time_series = []
            for period_str in sorted(time_series_map.keys()):
                entry = {'date': period_str, 'types': {}}
                for type_code in sorted(time_series_map[period_str].keys()):
                    entry['types'][type_code] = time_series_map[period_str][type_code]
                time_series.append(entry)

            # ---------- 汇总指定日期（summary）—— 完全保留原逻辑，仅将罚款部分改为从 Penalty 表直接取分红 ----------
            summary_result = {'by_type': {}, 'total_count': 0, 'total_amount': 0.0, 'total_profit': 0.0}

            # 汇总充值类型（完全不变）
            for lx in cz_types:
                filter_kwargs = {
                    'leixing': lx,
                    'zhuangtai': 3,
                }
                if granularity == 'day':
                    filter_kwargs['CreateTime__date'] = summary_q
                elif granularity == 'month':
                    filter_kwargs['CreateTime__year'] = summary_q.year
                    filter_kwargs['CreateTime__month'] = summary_q.month
                else:
                    filter_kwargs['CreateTime__year'] = summary_q.year
                agg = filter_queryset_by_club(
                    Czjilu.query.filter(**filter_kwargs), request,
                ).aggregate(count=Count('id'), amount=Sum('jine'))
                count = agg['count'] or 0
                amount = float(agg['amount'] or 0)
                profit = amount if lx == 3 else 0.0
                summary_result['by_type'][str(lx)] = {
                    'count': count,
                    'amount': round(amount, 2),
                    'profit': round(profit, 2),
                    'fenhong_count': 0,
                    'fenhong_amount': 0.0
                }
                summary_result['total_count'] += count
                summary_result['total_amount'] += amount
                summary_result['total_profit'] += profit

            # 汇总罚款（修改：从 Penalty 表直接取分红，不再查询 PenaltyBonus）
            if 6 in types:
                penalty_filter = {
                    'Status': 2,
                }
                if granularity == 'day':
                    penalty_filter['CreateTime__date'] = summary_q
                elif granularity == 'month':
                    penalty_filter['CreateTime__year'] = summary_q.year
                    penalty_filter['CreateTime__month'] = summary_q.month
                else:
                    penalty_filter['CreateTime__year'] = summary_q.year

                # 一次聚合：罚款笔数、金额、分红总额、分红笔数
                agg = filter_penalty_qs(
                    Penalty.query.filter(**penalty_filter), request,
                ).aggregate(
                    count=Count('id'),
                    amount=Sum('FineAmount'),
                    fenhong_amount=Sum('ApplicantBonusAmount'),
                    fenhong_count=Count('id', filter=Q(ApplicantBonusAmount__gt=0))
                )
                penalty_count = agg['count'] or 0
                penalty_amount = float(agg['amount'] or 0)
                fh_amount = float(agg['fenhong_amount'] or 0)
                fh_count = agg['fenhong_count'] or 0
                profit = round(penalty_amount - fh_amount, 2)

                summary_result['by_type']['6'] = {
                    'count': penalty_count,
                    'amount': round(penalty_amount, 2),
                    'profit': profit,
                    'fenhong_count': fh_count,
                    'fenhong_amount': round(fh_amount, 2)
                }
                summary_result['total_count'] += penalty_count
                summary_result['total_amount'] += penalty_amount
                summary_result['total_profit'] += profit

            summary_result['total_amount'] = round(summary_result['total_amount'], 2)
            summary_result['total_profit'] = round(summary_result['total_profit'], 2)

            return Response({
                'code': 0,
                'msg': 'ok',
                'data': {
                    'time_series': time_series,
                    'summary': summary_result
                }
            })
        except Exception as e:
            logger.exception("CwqtczhqView 接口错误")
            return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)

'''class CwqtczhqView(APIView):
    """
    其他充值/罚款数据统计（非会员充值）
    POST /houtai/cwqtczhq
    Body: {
        "phone": "管理员手机号",
        "granularity": "day" | "month" | "year",
        "year": 2026,
        "month": 5,                         // day时必填
        "types": [2,3,4,5,6],               // 2=押金,3=积分,4=商家,5=考核,6=罚款
        "summary_date": "2026-05-17"        // 汇总日期，格式随颗粒度（day:YYYY-MM-DD, month:YYYY-MM, year:YYYY）
    }
    """
    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 'qtczcaiwu' not in permissions:
                return Response({'code': 403, 'msg': '无其他充值财务查看权限'}, status=403)

            granularity = request.data.get('granularity', 'day')
            year = request.data.get('year')
            month = request.data.get('month')
            types = request.data.get('types', [2, 3, 4, 5, 6])   # 默认全部
            summary_date = request.data.get('summary_date')

            if isinstance(types, list) and len(types) == 0:
                types = [2, 3, 4, 5, 6]

            now = date.today()

            # ---------- 时间范围（用于曲线图） ----------
            if granularity == 'day':
                if not year or not month:
                    return Response({'code': 1, 'msg': '按日统计需要年份和月份'}, status=400)
                start_date = date(year, month, 1)
                end_date = date(year, month + 1, 1) if month < 12 else date(year + 1, 1, 1)
                trunc_func = TruncDate('CreateTime')
                # 默认汇总日期
                if not summary_date:
                    summary_date = now.strftime('%Y-%m-%d')
                summary_q = datetime.strptime(summary_date, '%Y-%m-%d').date()
            elif granularity == 'month':
                if not year:
                    return Response({'code': 1, 'msg': '按月统计需要年份'}, status=400)
                start_date = date(year, 1, 1)
                end_date = date(year + 1, 1, 1)
                trunc_func = TruncMonth('CreateTime')
                if not summary_date:
                    summary_date = now.strftime('%Y-%m')
                summary_q = datetime.strptime(summary_date + '-01', '%Y-%m-%d').date()
            elif granularity == 'year':
                year = year or now.year
                start_date = date(year, 1, 1)
                end_date = date(year + 1, 1, 1)
                trunc_func = TruncYear('CreateTime')
                if not summary_date:
                    summary_date = str(year)
                summary_q = date(year, 1, 1)
            else:
                return Response({'code': 1, 'msg': '无效的颗粒度'}, status=400)

            start_dt = datetime.combine(start_date, datetime.min.time())
            end_dt = datetime.combine(end_date, datetime.min.time())

            # ---------- 构建时间序列 ----------
            time_series_map = {}   # period_str -> { type_code: {count, amount, profit, fenhong_count, fenhong_amount} }

            def ensure_date(ds):
                if ds not in time_series_map:
                    time_series_map[ds] = {}
                return time_series_map[ds]

            # 1. 处理充值记录（Czjilu）
            cz_types = [t for t in types if t in [2, 3, 4, 5]]
            if cz_types:
                cz_qs = Czjilu.query.filter(
                    CreateTime__gte=start_dt,
                    CreateTime__lt=end_dt,
                    leixing__in=cz_types,
                    zhuangtai=3                     # 只统计支付成功的
                ).annotate(
                    period=trunc_func
                ).values('period', 'leixing').annotate(
                    total_count=Count('id'),
                    total_amount=Sum('jine')
                ).order_by('period', 'leixing')

                for item in cz_qs:
                    period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
                                 item['period'].strftime('%Y-%m') if granularity == 'month' else \
                                 item['period'].strftime('%Y')
                    lx = item['leixing']
                    amount = float(item['total_amount'] or 0)
                    count = item['total_count']
                    # 收益：积分(3)收益为金额本身，其他为0
                    profit = amount if lx == 3 else 0.0

                    day_data = ensure_date(period_str)
                    day_data[str(lx)] = {
                        'count': count,
                        'amount': amount,
                        'profit': profit,
                        'fenhong_count': 0,      # 充值类型无分红
                        'fenhong_amount': 0.0
                    }

            # 2. 处理罚款（Penalty）—— 包含分红
            if 6 in types:
                # 罚款记录
                penalty_qs = Penalty.query.filter(
                    CreateTime__gte=start_dt,
                    CreateTime__lt=end_dt,
                    Status=2                     # 已缴纳
                ).annotate(
                    period=trunc_func
                ).values('period').annotate(
                    penalty_count=Count('id'),
                    penalty_amount=Sum('FineAmount')
                ).order_by('period')

                # 分红记录
                fenhong_qs = PenaltyBonus.query.filter(
                    CreateTime__gte=start_dt,
                    CreateTime__lt=end_dt
                ).annotate(
                    period=trunc_func
                ).values('period').annotate(
                    fh_count=Count('id'),
                    fh_amount=Sum('WithdrawableAmount')
                ).order_by('period')

                # 合并
                for item in penalty_qs:
                    period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
                                 item['period'].strftime('%Y-%m') if granularity == 'month' else \
                                 item['period'].strftime('%Y')
                    day_data = ensure_date(period_str)
                    day_data['6'] = {
                        'count': item['penalty_count'],
                        'amount': float(item['penalty_amount'] or 0),
                        'profit': 0.0,   # 先设为0，后续计算
                        'fenhong_count': 0,
                        'fenhong_amount': 0.0
                    }

                for item in fenhong_qs:
                    period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
                                 item['period'].strftime('%Y-%m') if granularity == 'month' else \
                                 item['period'].strftime('%Y')
                    day_data = ensure_date(period_str)
                    if '6' not in day_data:
                        day_data['6'] = {'count': 0, 'amount': 0.0, 'profit': 0.0, 'fenhong_count': 0, 'fenhong_amount': 0.0}
                    day_data['6']['fenhong_count'] += item['fh_count']
                    day_data['6']['fenhong_amount'] += float(item['fh_amount'] or 0)

                # 计算罚款收益 = 罚款金额 - 分红金额
                for period_str, day_data in time_series_map.items():
                    if '6' in day_data:
                        penalty = day_data['6']
                        penalty['profit'] = round(penalty['amount'] - penalty['fenhong_amount'], 2)

            # 构造有序时间序列列表
            time_series = []
            for period_str in sorted(time_series_map.keys()):
                entry = {'date': period_str, 'types': {}}
                for type_code in sorted(time_series_map[period_str].keys()):
                    entry['types'][type_code] = time_series_map[period_str][type_code]
                time_series.append(entry)

            # ---------- 汇总指定日期 ----------
            summary_result = {'by_type': {}, 'total_count': 0, 'total_amount': 0.0, 'total_profit': 0.0}

            # 汇总充值类型
            for lx in cz_types:
                filter_kwargs = {
                    'leixing': lx,
                    'zhuangtai': 3,
                }
                if granularity == 'day':
                    filter_kwargs['CreateTime__date'] = summary_q
                elif granularity == 'month':
                    filter_kwargs['CreateTime__year'] = summary_q.year
                    filter_kwargs['CreateTime__month'] = summary_q.month
                else:
                    filter_kwargs['CreateTime__year'] = summary_q.year
                agg = filter_queryset_by_club(
                    Czjilu.query.filter(**filter_kwargs), request,
                ).aggregate(count=Count('id'), amount=Sum('jine'))
                count = agg['count'] or 0
                amount = float(agg['amount'] or 0)
                profit = amount if lx == 3 else 0.0
                summary_result['by_type'][str(lx)] = {
                    'count': count,
                    'amount': round(amount, 2),
                    'profit': round(profit, 2),
                    'fenhong_count': 0,
                    'fenhong_amount': 0.0
                }
                summary_result['total_count'] += count
                summary_result['total_amount'] += amount
                summary_result['total_profit'] += profit

            # 汇总罚款
            if 6 in types:
                penalty_filter = {
                    'Status': 2,
                }
                fenhong_filter = {}
                if granularity == 'day':
                    penalty_filter['CreateTime__date'] = summary_q
                    fenhong_filter['CreateTime__date'] = summary_q
                elif granularity == 'month':
                    penalty_filter['CreateTime__year'] = summary_q.year
                    penalty_filter['CreateTime__month'] = summary_q.month
                    fenhong_filter['CreateTime__year'] = summary_q.year
                    fenhong_filter['CreateTime__month'] = summary_q.month
                else:
                    penalty_filter['CreateTime__year'] = summary_q.year
                    fenhong_filter['CreateTime__year'] = summary_q.year

                penalty_agg = Penalty.query.filter(**penalty_filter).aggregate(
                    count=Count('id'), amount=Sum('FineAmount'))
                penalty_count = penalty_agg['count'] or 0
                penalty_amount = float(penalty_agg['amount'] or 0)

                fenhong_agg = PenaltyBonus.query.filter(**fenhong_filter).aggregate(
                    fh_count=Count('id'), fh_amount=Sum('WithdrawableAmount'))
                fh_count = fenhong_agg['fh_count'] or 0
                fh_amount = float(fenhong_agg['fh_amount'] or 0)

                profit = round(penalty_amount - fh_amount, 2)
                summary_result['by_type']['6'] = {
                    'count': penalty_count,
                    'amount': round(penalty_amount, 2),
                    'profit': profit,
                    'fenhong_count': fh_count,
                    'fenhong_amount': round(fh_amount, 2)
                }
                summary_result['total_count'] += penalty_count
                summary_result['total_amount'] += penalty_amount
                summary_result['total_profit'] += profit

            summary_result['total_amount'] = round(summary_result['total_amount'], 2)
            summary_result['total_profit'] = round(summary_result['total_profit'], 2)

            return Response({
                'code': 0,
                'msg': 'ok',
                'data': {
                    'time_series': time_series,
                    'summary': summary_result
                }
            })
        except Exception as e:
            logger.exception("CwqtczhqView 接口错误")
            return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)'''





# ==================== 后端接口 ====================
# 文件：views.py （添加以下两个视图类）



class KhpzhqView(APIView):
    """
    获取所有考核配置数据（称号列表、板块、费用）
    POST /houtai/khpzhq
    """
    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)

            bankuai_list = list(Bankuai.query.values('bankuai_id', 'mingcheng'))

            chenghao_qs = Chenghao.query.prefetch_related('kaohecishufeiyong_set').all()
            chenghao_list = []
            for ch in chenghao_qs:
                # 获取费用：按次数排序，转为 {cishu: feiyong} 字典
                feiyong_dict = {
                    item.cishu: float(item.feiyong)
                    for item in ch.kaohecishufeiyong_set.order_by('cishu')
                }
                max_cishu = max(feiyong_dict.keys()) if feiyong_dict else 0

                chenghao_list.append({
                    'id': ch.id,
                    'mingcheng': ch.mingcheng,
                    'leixing': ch.leixing,
                    'leixing_name': ch.get_leixing_display(),
                    'bankuai_id': ch.bankuai_id,
                    'tubiao_url': ch.tubiao_url or '',
                    'texiao_miaoshu': ch.texiao_miaoshu or '',
                    'CreateTime': ch.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if ch.CreateTime else '',
                    'kaohe_guize': ch.kaohe_guize or '',
                    'kaioi_jinpai': ch.kaioi_jinpai,
                    'feiyong': feiyong_dict,       # {1:100.0, 2:200.0}
                    'max_cishu': max_cishu
                })

            return Response({
                'code': 0,
                'msg': 'ok',
                'data': {
                    'bankuai': bankuai_list,
                    'chenghao': chenghao_list
                }
            })
        except Exception as e:
            logger.exception("KhpzhqView 接口错误")
            return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)




class ChzsgcView(APIView):
    """
    称号（标签）增删改查统一接口
    POST /houtai/chzsgc
    """
    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')
            if not action:
                return Response({'code': 1, 'msg': '缺少 action 参数'}, status=400)

            # ========== 创建称号 ==========
            if action == 'create':
                mingcheng = request.data.get('mingcheng', '').strip()
                leixing = request.data.get('leixing', 'dashou')
                bankuai_id = request.data.get('bankuai_id')
                texiao_miaoshu = request.data.get('texiao_miaoshu', '')
                kaohe_guize = request.data.get('kaohe_guize', '')
                kaioi_jinpai = request.data.get('kaioi_jinpai', False)
                feiyong_list = request.data.get('feiyong_list', [])

                if not mingcheng:
                    return Response({'code': 1, 'msg': '称号名称不能为空'}, status=400)
                if leixing not in ['dashou','boss','shangjia','guanshi','zuzhang']:
                    return Response({'code': 1, 'msg': '无效的称号类型'}, status=400)
                if Chenghao.query.filter(mingcheng=mingcheng).exists():
                    return Response({'code': 1, 'msg': '称号名称已存在'}, status=400)

                with transaction.atomic():
                    ch = Chenghao.query.create(
                        mingcheng=mingcheng,
                        leixing=leixing,
                        bankuai_id=bankuai_id,
                        texiao_miaoshu=texiao_miaoshu,
                        kaohe_guize=kaohe_guize,
                        kaioi_jinpai=kaioi_jinpai
                    )
                    # 处理费用：前端传来 [{cishu:1, feiyong:100}, ...]
                    if feiyong_list:
                        for item in feiyong_list:
                            cishu = int(item.get('cishu', 1))
                            feiyong = float(item.get('feiyong', 0))
                            KaoheCishuFeiyong.query.update_or_create(
                                chenghao=ch, cishu=cishu,
                                defaults={'feiyong': feiyong}
                            )
                    else:
                        # 默认添加第一次免费
                        KaoheCishuFeiyong.query.create(chenghao=ch, cishu=1, feiyong=0)

                return Response({'code': 0, 'msg': '创建成功', 'data': {'chenghao_id': ch.id}})

            # ========== 修改称号 ==========
            elif action == 'update':
                ch_id = request.data.get('chenghao_id')
                if not ch_id:
                    return Response({'code': 1, 'msg': '缺少称号ID'}, status=400)
                try:
                    ch = Chenghao.query.get(id=ch_id)
                except Chenghao.DoesNotExist:
                    return Response({'code': 1, 'msg': '称号不存在'}, status=404)

                mingcheng = request.data.get('mingcheng', ch.mingcheng).strip()
                leixing = request.data.get('leixing', ch.leixing)
                bankuai_id = request.data.get('bankuai_id', ch.bankuai_id)
                texiao_miaoshu = request.data.get('texiao_miaoshu', ch.texiao_miaoshu)
                kaohe_guize = request.data.get('kaohe_guize', ch.kaohe_guize)
                kaioi_jinpai = request.data.get('kaioi_jinpai', ch.kaioi_jinpai)

                if not mingcheng:
                    return Response({'code': 1, 'msg': '称号名称不能为空'}, status=400)
                if leixing not in ['dashou','boss','shangjia','guanshi','zuzhang']:
                    return Response({'code': 1, 'msg': '无效的称号类型'}, status=400)
                if Chenghao.query.filter(mingcheng=mingcheng).exclude(id=ch_id).exists():
                    return Response({'code': 1, 'msg': '称号名称已存在'}, status=400)

                ch.mingcheng = mingcheng
                ch.leixing = leixing
                ch.bankuai_id = bankuai_id
                ch.texiao_miaoshu = texiao_miaoshu
                ch.kaohe_guize = kaohe_guize
                ch.kaioi_jinpai = kaioi_jinpai
                ch.save()

                # 如果前端传递了 feiyong_list，则全量替换费用
                feiyong_list = request.data.get('feiyong_list')
                if feiyong_list is not None:
                    with transaction.atomic():
                        ch.kaohecishufeiyong_set.all().delete()
                        for item in feiyong_list:
                            KaoheCishuFeiyong.query.create(
                                chenghao=ch,
                                cishu=int(item.get('cishu', 1)),
                                feiyong=float(item.get('feiyong', 0))
                            )

                return Response({'code': 0, 'msg': '修改成功'})

            # ========== 删除称号 ==========
            elif action == 'delete':
                ch_id = request.data.get('chenghao_id')
                if not ch_id:
                    return Response({'code': 1, 'msg': '缺少称号ID'}, status=400)
                try:
                    ch = Chenghao.query.get(id=ch_id)
                except Chenghao.DoesNotExist:
                    return Response({'code': 1, 'msg': '称号不存在'}, status=404)
                ch.delete()
                return Response({'code': 0, 'msg': '删除成功'})

            # ========== 添加/修改某次费用 ==========
            elif action == 'add_feiyong':
                ch_id = request.data.get('chenghao_id')
                cishu = request.data.get('cishu')
                feiyong = request.data.get('feiyong', 0)
                if not ch_id or not cishu:
                    return Response({'code': 1, 'msg': '缺少称号ID或次数'}, status=400)
                try:
                    ch = Chenghao.query.get(id=ch_id)
                except Chenghao.DoesNotExist:
                    return Response({'code': 1, 'msg': '称号不存在'}, status=404)
                if int(cishu) < 1:
                    return Response({'code': 1, 'msg': '次数必须>=1'}, status=400)
                KaoheCishuFeiyong.query.update_or_create(
                    chenghao=ch, cishu=cishu,
                    defaults={'feiyong': feiyong}
                )
                return Response({'code': 0, 'msg': '添加/修改费用成功'})

            # ========== 删除某次费用 ==========
            elif action == 'remove_feiyong':
                ch_id = request.data.get('chenghao_id')
                cishu = request.data.get('cishu')
                if not ch_id or not cishu:
                    return Response({'code': 1, 'msg': '缺少称号ID或次数'}, status=400)
                if int(cishu) == 1:
                    return Response({'code': 1, 'msg': '不能删除第1次考核费用'}, status=400)
                try:
                    ch = Chenghao.query.get(id=ch_id)
                except Chenghao.DoesNotExist:
                    return Response({'code': 1, 'msg': '称号不存在'}, status=404)
                KaoheCishuFeiyong.query.filter(chenghao=ch, cishu=cishu).delete()
                return Response({'code': 0, 'msg': '删除成功'})

            # ========== 修改某次费用（等价于add） ==========
            elif action == 'update_feiyong':
                return self.add_feiyong(request)   # 复用逻辑

            else:
                return Response({'code': 1, 'msg': '未知操作'}, status=400)

        except Exception as e:
            logger.exception("ChzsgcView 接口错误")
            return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)




# ==================== 后端 views.py ====================
# 请确保已有以下导入，如果没有请在文件头部添加




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 jituan.services.shenhe_club import yonghu_chenghao_qs_for_request

            # ---------- 预计算每个板块的打手数量 ----------
            bankuai_dashou_count_map = {}
            all_banquai = Bankuai.query.all()
            for bk in all_banquai:
                tag_ids = Chenghao.query.filter(bankuai=bk, leixing='dashou').values_list('id', flat=True)
                count = yonghu_chenghao_qs_for_request(
                    YonghuChenghao.query.filter(
                        chenghao_id__in=tag_ids,
                        yonghu__DashouProfile__isnull=False,
                    ),
                    request,
                ).values('yonghu').distinct().count()
                bankuai_dashou_count_map[bk.bankuai_id] = count

            # ---------- 新增：板块标签统计 ----------
            bankuai_tags_data = []
            for bk in all_banquai:
                tags = Chenghao.query.filter(bankuai=bk, leixing='dashou')
                tag_list = []
                for tag in tags:
                    user_count = yonghu_chenghao_qs_for_request(
                        YonghuChenghao.query.filter(chenghao=tag),
                        request,
                    ).values('yonghu').distinct().count()
                    tag_list.append({
                        'id': tag.id,
                        'name': tag.mingcheng,
                        'user_count': user_count,
                    })
                bankuai_tags_data.append({
                    'bankuai_id': bk.bankuai_id,
                    'mingcheng': bk.mingcheng,
                    'tags': tag_list,
                })

            # 构造返回数据（审核官列表）
            data_list = []
            for sg in shenheguan_list:
                user = sg.user
                # 获取该审核官关联的板块
                bk_qs = KaoheguanBankuai.query.filter(kaoheguan_id=user.UserUID).select_related('bankuai')
                bankuai_info = []
                for bk in bk_qs:
                    bid = bk.bankuai.bankuai_id
                    bankuai_info.append({
                        'bankuai_id': bid,
                        'mingcheng': bk.bankuai.mingcheng,
                        'dashou_count': bankuai_dashou_count_map.get(bid, 0)
                    })

                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)

            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)





class ZxkfghdsView(APIView):
    """
    客服更换打手接口
    路径：POST /houtai/zxkfghds
    功能：
        1. 将当前订单状态改为已退款（5），释放接单打手（若有）
        2. 如需跨平台通知，则调用对方平台退款通知接口
        3. 基于原订单信息生成全新订单（状态直接为 1，已替老板填写）
        4. 生成新的派单链接，并标记为已使用
        5. 新链接记录上一个链接ID和上一个订单ID
        6. 【关键】不操作商家余额、退款次数、每日统计，因新订单是替换而非新增扣款
    权限：需要'003aa'（跨平台管理）权限，与退款接口一致
    """

    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        # ========== 1. 参数提取 ==========
        username = request.data.get('username', '').strip()
        OrderID = pick_order_id(request.data)
        if not username or not OrderID:
            logger.warning("更换打手参数不完整")
            return Response({'code': 400, 'msg': '参数不完整'})

        # ========== 2. 权限校验（与跨平台退款相同） ==========
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions  # 返回错误响应
        if '003aa' not in permissions:
            return Response({'code': 403, 'msg': '您无权处理订单'})

        current_user = request.user

        # ========== 3. 查询原订单及关联信息 ==========
        try:
            old_order = Order.query.select_related('shangjia_kuozhan').get(OrderID=OrderID)
        except Order.DoesNotExist:
            logger.warning(f"订单不存在: {OrderID}")
            return Response({'code': 404, 'msg': '订单不存在'})

        # 仅允许状态为 2（进行中）或 8（结算中）更换打手
        if old_order.Status not in [1,7,2, 8]:
            return Response({'code': 400, 'msg': '当前订单状态不允许更换打手'})

        # 本接口仅处理商家发单（Platform=2）
        if old_order.Platform != 2:
            return Response({'code': 400, 'msg': '仅支持商家发单的更换操作'})

        # 获取原链接（用于模板ID等）
        try:
            old_lianjie = ShangjiaLianjie.query.get(OrderID=OrderID)
        except ShangjiaLianjie.DoesNotExist:
            logger.error(f"原链接记录不存在，订单: {OrderID}")
            return Response({'code': 500, 'msg': '链接数据异常'})

        # 商家信息（仅用于记录关联，不操作余额）
        try:
            shangjia_ext = old_order.shangjia_kuozhan
            shangjia_id = shangjia_ext.MerchantID
            # 获取商家昵称用于新订单扩展表
            shangjia_user = User.query.get(UserUID=shangjia_id)
            shangjia_nicheng = shangjia_user.ShopProfile.nicheng or f"商家{shangjia_id}"
        except (ObjectDoesNotExist, UserShangjia.DoesNotExist):
            logger.error(f"商家信息缺失，订单: {OrderID}")
            return Response({'code': 500, 'msg': '商家数据异常'})

        # ========== 4. 退款处理（仅打手侧，不碰商家） ==========
        with transaction.atomic():
            # 5.1 订单状态改为已退款
            old_order.Status = 5
            old_order.AssignedCS = current_user.Phone
            old_order.save()

            # 5.2 处理接单打手（若有）
            dashou_id = old_order.PlayerID
            if dashou_id:
                try:
                    dashou_user = User.query.get(UserUID=dashou_id)
                    dashou_profile = dashou_user.DashouProfile
                    dashou_profile.tuikuanliang += 1      # 退款次数+1
                    dashou_profile.zhuangtai = 1          # 设为空闲
                    dashou_profile.save()
                except (User.DoesNotExist, ObjectDoesNotExist):
                    logger.warning(f"打手 {dashou_id} 不存在，跳过更新")

            # 5.3 更新退款记录表
            RefundRecord.query.update_or_create(
                OrderID=OrderID,
                defaults={
                    'PlayerID': dashou_id or '',
                    'ApplicantID': '',
                    'ProcessorID': current_user.Phone,
                    'Reason': '客服更换打手退款',
                    'ApplyStatus': 1,
                    'Amount': old_order.Amount or 0,
                    'PlayerCommission': old_order.PlayerCommission or 0,
                    'Description': old_order.Description or '',
                    'Remark': '更换打手自动退款',
                    'Nickname': old_order.Nickname or '',
                }
            )

            # 5.4 打手每日统计（退款）
            if dashou_id:
                try:
                    update_dashou_daily_by_action(
                        yonghuid=dashou_id,
                        amount=old_order.PlayerCommission,
                        action=3  # 3=退款
                    )
                except Exception as e:
                    logger.error(f"打手每日统计更新失败: {e}")

            # 5.5 客服处理统计（管理员无 KefuProfile，跳过）
            if hasattr(kefu, 'jinrichuli'):
                kefu.jinrichuli += 1
                kefu.jinyuechuli += 1
                kefu.zongchuli += 1
                kefu.save()

        # 至此，原订单已退款，事务提交

        # ========== 6. 生成新订单和链接（不扣商家余额） ==========
        try:
            with transaction.atomic():
                # ---- 6.1 生成新的订单ID和Token ----
                def generate_OrderID():
                    timestamp = int(time.time() * 1000)
                    process_id = os.getpid() % 1000
                    random_num = random.randint(1000, 9999)
                    return f"SJ{timestamp}{process_id:03d}{random_num}"

                def generate_secure_token(dd_id, uid):
                    timestamp = int(time.time() * 1_000_000)
                    salt = secrets.token_hex(8)
                    raw = f"{timestamp}:{dd_id}:{uid}:{salt}"
                    token = hashlib.sha256(raw.encode()).hexdigest()[:24]
                    if ShangjiaLianjie.query.filter(LinkToken=token).exists():
                        return generate_secure_token(dd_id, uid)
                    return token

                def generate_link_url(token):
                    base = getattr(settings, 'H5_DOMAIN', 'https://h5.yourdomain.com').rstrip('/')
                    return f"{base}/order/{token}"

                new_OrderID = generate_OrderID()
                secure_token = generate_secure_token(new_OrderID, shangjia_id)

                # ---- 6.2 创建新订单（复制核心数据，状态直接为1） ----
                new_order = Order.query.create(
                    OrderID=new_OrderID,
                    Status=1,                     # 下单中
                    Platform=2,
                    Amount=old_order.Amount,
                    PlayerCommission=old_order.PlayerCommission,
                    ProductTypeID=old_order.ProductTypeID,
                    GrabRequirement=old_order.GrabRequirement,
                    MembershipID=old_order.MembershipID,
                    CommissionReq=old_order.CommissionReq,
                    Description=old_order.Description,
                    Nickname=old_order.Nickname,       # 继承游戏昵称
                    Remark=old_order.Remark,
                    User1ID=old_order.User1ID,     # 商家标识
                    ImageURL=old_order.ImageURL,
                    ExternalOrderID=old_order.ExternalOrderID,
                    ClubID=getattr(old_order, 'ClubID', None) or 'xq',
                    # 注意：不继承 AssignedID、PlayerID
                )

                # ---- 6.3 创建商家扩展表 ----
                MerchantOrderExt.query.create(
                    Order=new_order,
                    MerchantID=shangjia_id,
                    MerchantNickname=shangjia_nicheng,
                )

                # ---- 6.4 生成新链接并关联旧链接信息 ----
                link_url = generate_link_url(secure_token)
                new_lianjie = ShangjiaLianjie.query.create(
                    UserID=shangjia_id,
                    ProductTypeID=old_lianjie.ProductTypeID,
                    TemplateID=old_lianjie.TemplateID,
                    LinkURL=link_url,
                    LinkToken=secure_token,
                    OrderID=new_OrderID,
                    is_used=True,                    # 直接标记已使用
                    ExpireTime=timezone.now() + timezone.timedelta(days=1),
                    PreviousLinkID=old_lianjie.id,      # 记录旧链接ID
                    PreviousOrderID=old_order.OrderID, # 记录旧订单ID
                )

                # ---- 6.5 异步通知（广播新订单、跨平台同步） ----
                try:
                    order_info = {
                        'OrderID': new_OrderID,
                        'game_type': self._get_game_type_name(new_order.ProductTypeID),
                        'amount': str(new_order.Amount),
                        'order_desc': (new_order.Description or '')[:50],
                    }
                    dingdan_guangbo.delay(order_info)
                except Exception as e:
                    logger.error(f"广播通知失败: {e}")

                # 注意：此处不更新商家余额、发布量、每日统计，因为这是替换订单

                logger.info(f"更换打手成功：旧订单{OrderID}→退款，新订单{new_OrderID}已生成")

                return Response({
                    'code': 0,
                    'msg': '更换成功',
                    'data': {
                        'new_OrderID': new_OrderID,
                        'new_dingdan_id': new_OrderID,
                        'new_lianjie': link_url,
                    }
                })

        except Exception as e:
            logger.error(f"生成新订单事务失败: {str(e)}", exc_info=True)
            return Response({'code': 500, 'msg': '生成新订单失败'})

    def _get_game_type_name(self, ProductTypeID):
        """根据类型ID获取游戏类型名称"""
        try:
            gt = ShangpinLeixing.query.filter(id=ProductTypeID).first()
            return gt.jieshao if gt else '游戏订单'
        except Exception:
            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

            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)











