
from django.db import models

from utils.oss_utils import upload_to_oss, delete_from_oss
from yonghu.models import UserMain, UserDashou, UserShangjia, UserGuanshi, UserZuzhang
from shangdian.models import Dianpu, YonghuDianpuBangding, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi
from shangpin.models import ShangpinLeixing, ShangpinZhuanqu, Shangpin


from rest_framework.permissions import AllowAny

from django.utils import timezone
from rest_framework_simplejwt.tokens import RefreshToken


from yonghu.models import UserMain
from .models import YonghuPingzheng, Dianpu


import logging

# shangdian/views.py 继续添加
import io
import time
import requests
from django.conf import settings
from django.core.cache import cache
from django.db import transaction
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from .utils import verify_shop_permission
from .models import Dianpu
from django.db.models import F
logger = logging.getLogger(__name__)


class BindDianpuView(APIView):
    """
    店铺绑定接口
    接收前端传入的店铺ID，更新用户与店铺的绑定关系
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        dianpu_id = request.data.get('dianpu_id')
        if not dianpu_id:
            return Response({'code': 400, 'msg': '店铺ID不能为空'})

        user = request.user
        if not hasattr(user, 'yonghuid'):
            return Response({'code': 500, 'msg': '用户身份信息异常'})

        # 先获取 UserMain 对象（不加锁，仅用于校验存在性）
        try:
            user_main = UserMain.objects.get(yonghuid=user.yonghuid)
        except UserMain.DoesNotExist:
            return Response({'code': 404, 'msg': '用户不存在'})

        # 校验店铺是否存在且状态正常（不加锁）
        try:
            dianpu = Dianpu.objects.get(id=dianpu_id)
        except Dianpu.DoesNotExist:
            return Response({'code': 404, 'msg': '店铺不存在'})

        if dianpu.zhuangtai != 1:
            return Response({'code': 200, 'msg': '店铺已被封禁，无法绑定'})

        # 在事务内部进行加锁操作
        try:
            with transaction.atomic():
                # 重新获取加锁的用户对象（确保并发安全）
                user_main_locked = UserMain.objects.select_for_update().get(yonghuid=user.yonghuid)
                dianpu_locked = Dianpu.objects.select_for_update().get(id=dianpu_id)

                # 再次校验店铺状态（防止事务期间被修改）
                if dianpu_locked.zhuangtai != 1:
                    return Response({'code': 200, 'msg': '店铺已被封禁，无法绑定'})

                # 处理绑定关系
                try:
                    existing_binding = YonghuDianpuBangding.objects.select_for_update().get(yonghu=user_main_locked)
                    old_dianpu = existing_binding.dianpu
                    if old_dianpu.id == dianpu_locked.id:
                        return Response({'code': 200, 'msg': '已绑定该店铺'})

                    # 旧店铺绑定人数减1
                    old_dianpu.bangding_yonghushu = F('bangding_yonghushu') - 1
                    old_dianpu.save(update_fields=['bangding_yonghushu'])

                    # 更新绑定关系
                    existing_binding.dianpu = dianpu_locked
                    existing_binding.save()

                except YonghuDianpuBangding.DoesNotExist:
                    YonghuDianpuBangding.objects.create(yonghu=user_main_locked, dianpu=dianpu_locked)

                # 新店铺绑定人数加1
                dianpu_locked.bangding_yonghushu = F('bangding_yonghushu') + 1
                dianpu_locked.save(update_fields=['bangding_yonghushu'])

            logger.info(f"用户 {user.yonghuid} 绑定店铺 {dianpu_id} 成功")
            return Response({'code': 200, 'msg': '绑定成功'})

        except Exception as e:
            logger.exception(f"绑定店铺失败: {e}")
            return Response({'code': 500, 'msg': '绑定失败，服务器内部错误'})







class ShopGoodsView(APIView):
    """
    获取店铺商品数据（类型、专区、商品列表）
    根据用户绑定店铺状态及身份，返回店铺专属或公共商品
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        user = request.user
        try:
            user_main = UserMain.objects.get(yonghuid=user.yonghuid)
        except UserMain.DoesNotExist:
            return Response({'code': 404, 'msg': '用户不存在'})

        # 1. 查询用户店铺绑定关系
        binding = YonghuDianpuBangding.objects.filter(
            yonghu=user_main
        ).select_related('dianpu').first()

        # 2. 判断用户是否拥有特殊身份
        has_special = self._has_special_identity(user_main)

        # 3. 根据绑定关系及店铺状态决定数据获取策略
        if binding:
            dianpu = binding.dianpu
            if dianpu.zhuangtai == 1:
                # 店铺正常 -> 返回店铺专属商品（需通过平台审核）
                data = self._get_shop_goods(dianpu)
            else:
                # 店铺被封 -> 返回公共商品，有特殊身份则返回审核状态为1的，否则返回2的
                data = self._get_public_goods(True)
        else:
            # 无绑定店铺，根据身份决定返回公共商品的审核状态
            data = self._get_public_goods(has_special)

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

    def _has_special_identity(self, user_main):
        """判断用户是否拥有打手/商家/管事/组长任一身份"""
        return (UserDashou.objects.filter(user=user_main).exists() or
                UserShangjia.objects.filter(user=user_main).exists() or
                UserGuanshi.objects.filter(user=user_main).exists() or
                UserZuzhang.objects.filter(user=user_main).exists())

    def _get_shop_goods(self, dianpu):
        """
        获取店铺专属商品
        类型、专区、商品均从店铺相关表中获取，且需通过平台审核（shenhe_zhuangtai=True）
        """
        # 1. 店铺商品类型：未封禁、已上架、平台审核通过
        leixing_queryset = ShangpinLeixingDianpu.objects.filter(
            dianpu=dianpu,
            fengjin_zhuangtai=False,
            shangjia_zhuangtai=True,
            shenhe_zhuangtai=True          # 🔥 必须通过平台审核
        ).select_related('gonggong_leixing').order_by('-paixu', 'id')

        leixing_data = []
        for lx in leixing_queryset:
            leixing_data.append({
                'id': lx.id,                # 店铺类型ID，前端归类用
                'jieshao': lx.jieshao,
                'tupian_url': lx.tupian_url,
                'paixu': lx.paixu
            })

        leixing_ids = [lx['id'] for lx in leixing_data]

        # 2. 专区：根据店铺和店铺类型ID，未封禁、已上架
        if leixing_ids:
            zhuanqu_queryset = ShangpinZhuanqu.objects.filter(
                dianpu=dianpu,
                dianpu_leixing_id__in=leixing_ids,
                fengjin_zhuangtai=False,
                shangjia_zhuangtai=True
            ).order_by('-paixu', 'id')
        else:
            zhuanqu_queryset = ShangpinZhuanqu.objects.none()

        zhuanqu_data = []
        for zq in zhuanqu_queryset:
            zhuanqu_data.append({
                'id': zq.id,
                'mingzi': zq.mingzi,
                'leixing_id': zq.dianpu_leixing_id,   # 对应店铺类型ID
                'paixu': zq.paixu
            })

        # 3. 商品：根据店铺、店铺类型、专区，未封禁、已上架、平台审核通过、原有审核状态为正常
        zhuanqu_ids = [zq['id'] for zq in zhuanqu_data]
        if zhuanqu_ids:
            shangpin_queryset = Shangpin.objects.filter(
                dianpu=dianpu,
                dianpu_leixing_id__in=leixing_ids,
                zhuanqu_id__in=zhuanqu_ids,
                fengjin_zhuangtai=False,
                shangjia_zhuangtai=True,
                shenhe_zhuangtai=True,      # 🔥 平台审核通过
                shenhezhuangtai=1           # 原有审核状态：正常
            ).values(
                'id', 'biaoqian', 'jiage', 'tupian_url', 'duiwai_xiaoliang', 'paixu',
                'dianpu_leixing_id', 'zhuanqu_id'
            ).order_by('-paixu', 'id')

            shangpin_data = list(shangpin_queryset)
            # 字段映射：前端统一使用 leixing_id
            for sp in shangpin_data:
                sp['leixing_id'] = sp.pop('dianpu_leixing_id')
        else:
            shangpin_data = []

        return {
            'shangpinleixing': leixing_data,
            'shangpinzhuanqu': zhuanqu_data,
            'shangpinliebiao': shangpin_data
        }

    def _get_public_goods(self, use_audit_status_one=True):
        """
        获取公共商品
        类型、专区、商品均从公共表获取。
        - 必须满足 dianpu 为空且 dianpu_leixing 为空（即纯公共数据）。
        - 审核状态：若 use_audit_status_one=True，则返回 shenhezhuangtai=1 的数据；
          否则返回 shenhezhuangtai=2 的数据（审核专用）。
        """
        target_shenhe = 1 if use_audit_status_one else 2

        # 1. 公共商品类型：原有审核状态为 target_shenhe
        leixing_queryset = ShangpinLeixing.objects.filter(
            shenhezhuangtai=target_shenhe
        ).order_by('-paixu', 'id')

        leixing_data = []
        for lx in leixing_queryset:
            leixing_data.append({
                'id': lx.id,
                'jieshao': lx.jieshao,
                'tupian_url': lx.tupian_url,
                'paixu': lx.paixu
            })

        visible_leixing_ids = [lx['id'] for lx in leixing_data]

        # 2. 专区：dianpu 为空，且 dianpu_leixing 为空，leixing_id 在可见范围内，原有审核状态为 target_shenhe
        if visible_leixing_ids:
            zhuanqu_queryset = ShangpinZhuanqu.objects.filter(
                dianpu__isnull=True,
                dianpu_leixing__isnull=True,        # 🔥 必须为空
                leixing_id__in=visible_leixing_ids,
                shenhezhuangtai=target_shenhe
            ).order_by('-paixu', 'id')
        else:
            zhuanqu_queryset = ShangpinZhuanqu.objects.none()

        zhuanqu_data = []
        for zq in zhuanqu_queryset:
            zhuanqu_data.append({
                'id': zq.id,
                'mingzi': zq.mingzi,
                'leixing_id': zq.leixing_id,
                'paixu': zq.paixu
            })

        # 3. 商品：dianpu 为空，dianpu_leixing 为空，专区在可见范围内，原有审核状态为 target_shenhe
        zhuanqu_ids = [zq['id'] for zq in zhuanqu_data]
        if zhuanqu_ids:
            shangpin_queryset = Shangpin.objects.filter(
                dianpu__isnull=True,
                dianpu_leixing__isnull=True,        # 🔥 必须为空
                leixing_id__in=visible_leixing_ids,
                zhuanqu_id__in=zhuanqu_ids,
                shenhezhuangtai=target_shenhe
            ).values(
                'id', 'biaoqian', 'jiage', 'tupian_url', 'duiwai_xiaoliang', 'paixu',
                'leixing_id', 'zhuanqu_id'
            ).order_by('-paixu', 'id')
            shangpin_data = list(shangpin_queryset)
        else:
            shangpin_data = []

        return {
            'shangpinleixing': leixing_data,
            'shangpinzhuanqu': zhuanqu_data,
            'shangpinliebiao': shangpin_data
        }


class ShangdianLoginView(APIView):
    """
    商家后台登录接口（通过凭证直接获取店铺）
    POST /shangdian/login
    请求参数：
        yonghuid  : 用户ID（7位）
        zhanghao  : 登录账号
        mima      : 密码
    返回数据：
        code: 0, data: {
            token: "jwt_token",
            yonghuid: "1234567",
            dianpu_mingcheng: "店铺名称",
            dianpu_touxiang: "头像相对URL",
            erweima_url: "二维码相对URL"
        }
    """
    permission_classes = [AllowAny]

    def post(self, request):
        # 1. 获取请求参数
        yonghuid = request.data.get('yonghuid', '').strip()
        zhanghao = request.data.get('zhanghao', '').strip()
        mima = request.data.get('mima', '').strip()

        # 2. 参数完整性校验
        if not yonghuid or not zhanghao or not mima:
            return Response({'code': 400, 'msg': '用户ID、账号、密码均不能为空'})

        # 3. 验证用户主表是否存在
        try:
            user_main = UserMain.objects.get(yonghuid=yonghuid)
        except UserMain.DoesNotExist:
            logger.warning(f"登录失败：用户ID不存在 - {yonghuid}")
            return Response({'code': 404, 'msg': '账号或密码错误'})

        # 4. 通过用户ID + 账号查询凭证表
        try:
            pingzheng = YonghuPingzheng.objects.get(
                yonghu=yonghuid,
                zhanghao=zhanghao,
                is_active=True
            )
        except YonghuPingzheng.DoesNotExist:
            logger.warning(f"登录失败：账号或密码错误 - 用户ID:{yonghuid}, 账号:{zhanghao}")
            return Response({'code': 401, 'msg': '账号或密码错误'})

        # 5. 校验密码
        if pingzheng.mima != mima:
            logger.warning(f"登录失败：密码错误 - 用户ID:{yonghuid}, 账号:{zhanghao}")
            return Response({'code': 401, 'msg': '账号或密码错误'})

        # 6. 【修改点】通过凭证直接获取关联的店铺（利用 OneToOneField 反向关系）
        try:
            dianpu = pingzheng.dianpu
        except Dianpu.DoesNotExist:
            logger.error(f"登录异常：凭证 {zhanghao} 未关联任何店铺")
            return Response({'code': 403, 'msg': '您尚未关联店铺，请联系管理员'})

        # 7. 检查店铺状态
        if dianpu.zhuangtai != 1:
            logger.warning(f"登录失败：店铺已被封禁 - 店铺ID:{dianpu.id}, 用户ID:{yonghuid}")
            return Response({'code': 403, 'msg': '店铺已被封禁，无法登录'})

        # 8. 生成 JWT Token
        refresh = RefreshToken.for_user(user_main)
        token = str(refresh.access_token)

        # 9. 更新最后登录时间
        user_main.last_login_time = timezone.now()
        user_main.save(update_fields=['last_login_time'])

        # 10. 构造返回数据
        data = {
            'token': token,
            'yonghuid': user_main.yonghuid,
            'dianpu_mingcheng': dianpu.dianpu_mingcheng,
            'dianpu_touxiang': dianpu.dianpu_touxiang or '',
            'erweima_url': dianpu.erweima_url or ''
        }

        logger.info(f"商家登录成功：用户ID {yonghuid} - 店铺 {dianpu.dianpu_mingcheng}")
        return Response({'code': 0, 'msg': '登录成功', 'data': data})






'''class ShangdianLoginView(APIView):
    """
    商家后台登录接口（安全修正版）
    POST /shangdian/login
    请求参数：
        yonghuid  : 用户ID（7位）
        zhanghao  : 登录账号
        mima      : 密码
    返回数据：
        code: 0, data: {
            token: "jwt_token",
            yonghuid: "1234567",
            dianpu_mingcheng: "店铺名称",
            dianpu_touxiang: "头像相对URL",
            erweima_url: "二维码相对URL"
        }
    """
    permission_classes = [AllowAny]

    def post(self, request):
        # 1. 获取请求参数
        yonghuid = request.data.get('yonghuid', '').strip()
        zhanghao = request.data.get('zhanghao', '').strip()
        mima = request.data.get('mima', '').strip()

        # 2. 参数完整性校验
        if not yonghuid or not zhanghao or not mima:
            return Response({'code': 400, 'msg': '用户ID、账号、密码均不能为空'})

        # 3. 【第一步】验证用户主表是否存在
        try:
            user_main = UserMain.objects.get(yonghuid=yonghuid)
        except UserMain.DoesNotExist:
            logger.warning(f"登录失败：用户ID不存在 - {yonghuid}")
            return Response({'code': 404, 'msg': '用户不存在'})

        # 4. 【第二步】通过用户ID + 账号查询凭证表（防止用他人账号登录）
        try:
            pingzheng = YonghuPingzheng.objects.get(
                yonghu=yonghuid,           # 关键：必须匹配当前用户ID
                zhanghao=zhanghao,         # 必须匹配账号
                is_active=True             # 必须启用
            )
        except YonghuPingzheng.DoesNotExist:
            logger.warning(f"登录失败：账号或密码错误 - 用户ID:{yonghuid}, 账号:{zhanghao}")
            return Response({'code': 401, 'msg': '账号或密码错误'})

        # 5. 校验密码（明文对比，后续可改为哈希）
        if pingzheng.mima != mima:
            logger.warning(f"登录失败：密码错误 - 用户ID:{yonghuid}, 账号:{zhanghao}")
            return Response({'code': 401, 'msg': '账号或密码错误'})

        # 6. 【第三步】通过用户ID查询绑定关系表，获取店铺ID
        try:
            binding = YonghuDianpuBangding.objects.select_related('dianpu').get(yonghu=user_main)
            dianpu = binding.dianpu
        except YonghuDianpuBangding.DoesNotExist:
            logger.error(f"登录异常：用户 {yonghuid} 未绑定任何店铺")
            return Response({'code': 403, 'msg': '您尚未绑定任何店铺，请联系管理员'})

        # 7. 【第四步】检查店铺状态
        if dianpu.zhuangtai != 1:
            logger.warning(f"登录失败：店铺已被封禁 - 店铺ID:{dianpu.id}, 用户ID:{yonghuid}")
            return Response({'code': 403, 'msg': '店铺已被封禁，无法登录'})

        # 8. 生成 JWT Token（关联 UserMain）
        refresh = RefreshToken.for_user(user_main)
        token = str(refresh.access_token)

        # 9. 更新最后登录时间
        user_main.last_login_time = timezone.now()
        user_main.save(update_fields=['last_login_time'])

        # 10. 构造返回数据
        data = {
            'token': token,
            'yonghuid': user_main.yonghuid,
            'dianpu_mingcheng': dianpu.dianpu_mingcheng,
            'dianpu_touxiang': dianpu.dianpu_touxiang or '',
            'erweima_url': dianpu.erweima_url or ''
        }

        logger.info(f"商家登录成功：用户ID {yonghuid} - 店铺 {dianpu.dianpu_mingcheng}")
        return Response({'code': 0, 'msg': '登录成功', 'data': data})'''



# shangdian/views.py 追加
from django.db.models import Sum

from shangdian.models import DianpuShouzhiMeiriTongji

class DianpuShouzhiTongjiView(APIView):
    """
    店铺每日收支统计接口（支持按日、月、年聚合）
    URL: /dianpu/dpsztj
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 身份验证
        frontend_user_id = request.data.get('userId')
        if not frontend_user_id:
            return Response({'code': 400, 'msg': '缺少 userId'})

        dianpu, error = verify_shop_permission(request, frontend_user_id)
        if error:
            return error

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

        # 查询当前店铺的统计记录
        qs = DianpuShouzhiMeiriTongji.objects.filter(dianpu_id=dianpu.id)

        if granularity == 'day':
            if not year or not month:
                return Response({'code': 400, 'msg': '日颗粒度需要提供年份和月份'})
            try:
                year = int(year)
                month = int(month)
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '年份或月份格式错误'})

            # 取出该月每一天的数据，并按日排序
            daily_data = qs.filter(nian=year, yue=month).values('ri').annotate(
                xiadan_zongliang=Sum('xiadan_zongliang'),
                xiadan_zonge=Sum('xiadan_zonge'),
                chengjiao_zongliang=Sum('chengjiao_zongliang'),
                chengjiao_zonge=Sum('chengjiao_zonge'),
                tuikuan_zongliang=Sum('tuikuan_zongliang'),
                tuikuan_zonge=Sum('tuikuan_zonge'),
                shouyi_zonge=Sum('shouyi_zonge'),
            ).order_by('ri')

            # 补全缺失的日期（默认值为0）
            import calendar
            days_in_month = calendar.monthrange(year, month)[1]
            result = []
            for day in range(1, days_in_month + 1):
                found = next((item for item in daily_data if item['ri'] == day), None)
                if found:
                    result.append(found)
                else:
                    result.append({
                        'ri': day,
                        'xiadan_zongliang': 0, 'xiadan_zonge': 0,
                        'chengjiao_zongliang': 0, 'chengjiao_zonge': 0,
                        'tuikuan_zongliang': 0, 'tuikuan_zonge': 0,
                        'shouyi_zonge': 0,
                    })
            return Response({'code': 0, 'data': result, 'type': 'day'})

        elif granularity == 'month':
            if not year:
                return Response({'code': 400, 'msg': '月颗粒度需要提供年份'})
            try:
                year = int(year)
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '年份格式错误'})

            # 按月份聚合
            monthly_data = qs.filter(nian=year).values('yue').annotate(
                xiadan_zongliang=Sum('xiadan_zongliang'),
                xiadan_zonge=Sum('xiadan_zonge'),
                chengjiao_zongliang=Sum('chengjiao_zongliang'),
                chengjiao_zonge=Sum('chengjiao_zonge'),
                tuikuan_zongliang=Sum('tuikuan_zongliang'),
                tuikuan_zonge=Sum('tuikuan_zonge'),
                shouyi_zonge=Sum('shouyi_zonge'),
            ).order_by('yue')

            # 补全 1~12 月
            result = []
            for m in range(1, 13):
                found = next((item for item in monthly_data if item['yue'] == m), None)
                if found:
                    result.append(found)
                else:
                    result.append({
                        'yue': m,
                        'xiadan_zongliang': 0, 'xiadan_zonge': 0,
                        'chengjiao_zongliang': 0, 'chengjiao_zonge': 0,
                        'tuikuan_zongliang': 0, 'tuikuan_zonge': 0,
                        'shouyi_zonge': 0,
                    })
            return Response({'code': 0, 'data': result, 'type': 'month'})

        elif granularity == 'year':
            # 按年份聚合
            yearly_data = qs.values('nian').annotate(
                xiadan_zongliang=Sum('xiadan_zongliang'),
                xiadan_zonge=Sum('xiadan_zonge'),
                chengjiao_zongliang=Sum('chengjiao_zongliang'),
                chengjiao_zonge=Sum('chengjiao_zonge'),
                tuikuan_zongliang=Sum('tuikuan_zongliang'),
                tuikuan_zonge=Sum('tuikuan_zonge'),
                shouyi_zonge=Sum('shouyi_zonge'),
            ).order_by('nian')
            return Response({'code': 0, 'data': list(yearly_data), 'type': 'year'})

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


class ShopOverviewView(APIView):
    """
    店铺概览数据接口
    URL: /shangdian/sjdpgl
    Method: POST
    权限: JWT认证
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 从请求体中获取前端传递的 userId（用于防越权）
        frontend_user_id = request.data.get('userId')
        if not frontend_user_id:
            return Response(
                {'code': 400, 'msg': '缺少必要参数 userId'}
            )

        # 调用公共验证方法
        dianpu, error_response = verify_shop_permission(request, frontend_user_id)
        if error_response:
            return error_response

        # 验证通过，构造返回数据（新增字段，其余保持不变）
        data = {
            '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 '',
            'create_time': dianpu.create_time.isoformat() if dianpu.create_time else '',
            'update_time': dianpu.update_time.isoformat() if dianpu.update_time else '',
            # 新增字段
            'kaiqi_shouyi_choucheng': dianpu.kaiqi_shouyi_choucheng,
            'shouyi_choucheng_feilv': float(dianpu.shouyi_choucheng_feilv) if dianpu.shouyi_choucheng_feilv else 0.0,
            'ketixian_yue': float(dianpu.ketixian_yue),
            'zhengqu_zonge': float(dianpu.zhengqu_zonge),
            'meiri_tixian_xiane': float(dianpu.meiri_tixian_xiane) if dianpu.meiri_tixian_xiane else 0.0,
            'kaiqi_meiri_xiane': dianpu.kaiqi_meiri_xiane,
        }

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



# 新增导入
from django.http import HttpResponse
from utils.oss_utils import get_oss_client   # 使用您已有的 COS 客户端获取方法

class QrcodeDownloadView(APIView):
    """
    店铺二维码下载接口（通过后端代理下载，解决跨域限制）
    URL: /shangdian/xzewm?dianpu_id=xx
    权限: JWT认证 + 店铺归属验证
    """
    permission_classes = [IsAuthenticated]

    def get(self, request):
        # 1. 身份验证与店铺校验
        frontend_user_id = request.GET.get('userId')   # 前端通过查询参数传递 userId
        if not frontend_user_id:
            return Response({'code': 400, 'msg': '缺少必要参数 userId'}, status=400)

        dianpu, error_response = verify_shop_permission(request, frontend_user_id)
        if error_response:
            return error_response

        # 2. 获取店铺的二维码相对路径
        erweima_relative_url = dianpu.erweima_url
        if not erweima_relative_url:
            return HttpResponse('店铺暂无二维码', status=404)

        # 3. 拼接 OSS 完整访问地址（使用 settings 中的域名）
        oss_domain = getattr(settings, 'COS_DOMAIN', '')
        if not oss_domain:
            logger.error("COS_DOMAIN 未配置")
            return HttpResponse('OSS域名未配置', status=500)

        image_url = oss_domain.rstrip('/') + '/' + erweima_relative_url.lstrip('/')

        # 4. 从腾讯云 COS 获取图片内容
        try:
            client = get_oss_client()
            bucket = getattr(settings, 'COS_BUCKET', '')
            response = client.get_object(Bucket=bucket, Key=erweima_relative_url)
            image_data = response['Body'].get_raw_stream().read()
        except Exception as e:
            logger.exception("从COS获取二维码失败")
            return HttpResponse('下载失败，请稍后重试', status=502)

        # 5. 构造下载响应，强制浏览器保存为文件
        filename = f"店铺二维码_{dianpu.dianpu_mingcheng}.png"
        response = HttpResponse(image_data, content_type='image/png')
        response['Content-Disposition'] = f'attachment; filename="{filename}"'
        response['Content-Length'] = str(len(image_data))
        return response


class ShopQrcodeView(APIView):
    """
    店铺专属二维码生成/更新接口
    URL: /shangdian/sdscdpm
    Method: POST
    权限: JWT认证
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 获取前端传递的 userId
        frontend_user_id = request.data.get('userId')
        if not frontend_user_id:
            return Response(
                {'code': 400, 'msg': '缺少必要参数 userId'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # 公共验证
        dianpu, error_response = verify_shop_permission(request, frontend_user_id)
        if error_response:
            return error_response

        # 获取缓存的微信 access_token
        access_token = self._get_wx_access_token()
        if not access_token:
            return Response(
                {'code': 500, 'msg': '获取微信 access_token 失败'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

        # 构造小程序码请求参数
        # 注意：scene 最大32个字符，我们使用店铺ID
        scene = str(dianpu.id)
        page_path = "pages/index/index"  # 前端小程序首页路径
        wx_url = f'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={access_token}'
        post_data = {
            'scene': scene,
            'page': page_path,
            'width': 430,
            'auto_color': False,
            'line_color': {'r': 0, 'g': 0, 'b': 0},
        }

        # 调用微信接口生成小程序码
        try:
            wx_resp = requests.post(wx_url, json=post_data, timeout=10)
        except Exception as e:
            logger.error(f"请求微信生成二维码接口异常: {e}")
            return Response(
                {'code': 500, 'msg': '调用微信服务失败'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

        # 判断返回是否为图片
        if wx_resp.status_code != 200 or 'image' not in wx_resp.headers.get('content-type', ''):
            error_info = wx_resp.json() if wx_resp.headers.get('content-type') == 'application/json' else {}
            errmsg = error_info.get('errmsg', '未知错误')
            logger.error(f"微信生成二维码失败: {errmsg}")
            return Response(
                {'code': 500, 'msg': f'生成二维码失败: {errmsg}'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

        # 构造文件名和 COS 路径
        timestamp = int(time.time())
        # 三层文件夹结构: dianpu/shangjiadianpu/dianpuerweima/
        filename = f"dianpu_{dianpu.id}_{timestamp}.png"
        oss_path = f"dianpu/shangjiadianpu/dianpuerweima/{filename}"
        file_obj = io.BytesIO(wx_resp.content)

        # 上传到 COS
        try:
            full_url = upload_to_oss(file_obj, oss_path)
        except Exception as e:
            logger.error(f"上传二维码到 COS 失败: {e}")
            return Response(
                {'code': 500, 'msg': '图片上传失败'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

        if not full_url:
            return Response(
                {'code': 500, 'msg': '图片上传失败'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

        # 提取相对路径（根据您的 upload_to_oss 返回值格式调整）
        cos_domain = getattr(settings, 'COS_DOMAIN', '')
        if cos_domain and full_url.startswith(cos_domain):
            relative_path = full_url[len(cos_domain):].lstrip('/')
        else:
            relative_path = oss_path

        # 删除旧二维码文件（如果存在）
        old_relative_path = dianpu.erweima_url
        if old_relative_path:
            try:
                delete_from_oss(old_relative_path)
            except Exception as e:
                logger.warning(f"删除旧二维码文件失败: {old_relative_path}, 错误: {e}")

        # 更新数据库
        with transaction.atomic():
            dianpu.erweima_url = relative_path
            dianpu.save(update_fields=['erweima_url', 'update_time'])

        return Response({
            'code': 0,
            'msg': 'success',
            'data': {'erweima_url': relative_path}
        })

    def _get_wx_access_token(self):
        """
        获取微信小程序 access_token，带缓存
        缓存键: wx_mini_access_token
        """
        cache_key = 'wx_mini_access_token'
        token = cache.get(cache_key)
        if token:
            return token

        appid = getattr(settings, 'WEIXIN_APPID', '')
        secret = getattr(settings, 'WEIXIN_SECRET', '')
        if not appid or not secret:
            logger.error("微信小程序 AppID 或 Secret 未配置")
            return None

        url = f'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}'
        try:
            resp = requests.get(url, timeout=5)
            data = resp.json()
            token = data.get('access_token')
            expires_in = data.get('expires_in', 7200)
            if token:
                # 提前200秒过期，防止边界情况
                cache.set(cache_key, token, expires_in - 200)
            return token
        except Exception as e:
            logger.error(f"获取微信 access_token 异常: {e}")
            return None




from django.db.models import Count
from rest_framework.views import APIView

from shangpin.models import ShangpinLeixing


class ShopProductConfigView(APIView):
    """
    获取商品发布所需配置：公共类型、店铺商品类型、专区及各自商品数量
    URL: /shangdian/dphqspxx
    权限: JWT认证 + 店铺归属验证
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 身份验证，获取店铺对象
        frontend_user_id = request.data.get('userId')
        if not frontend_user_id:
            return Response({'code': 400, 'msg': '缺少必要参数 userId'})

        dianpu, error = verify_shop_permission(request, frontend_user_id)
        if error:
            return error

        # ---------- 2. 公共商品类型（需审核正常） ----------
        try:
            public_types_qs = ShangpinLeixing.objects.filter(shenhezhuangtai=1).values(
                'id', 'jieshao', 'tupian_url'
            )
            public_types = [{
                'id': pt['id'],
                'jieshao': pt['jieshao'] or '',
                'tupian_url': pt['tupian_url'] or '',
            } for pt in public_types_qs]
        except Exception as e:
            logger.exception("查询公共商品类型失败")
            return Response({'code': 500, 'msg': '服务器错误'})

        # ---------- 3. 店铺商品类型 ----------
        try:
            shop_types = ShangpinLeixingDianpu.objects.filter(
                dianpu_id=dianpu.id
            ).select_related('gonggong_leixing').order_by('-paixu')
        except Exception as e:
            logger.exception("查询店铺商品类型失败")
            return Response({'code': 500, 'msg': '服务器错误'})

        type_ids = []
        shop_product_types = []
        for t in shop_types:
            shop_product_types.append({
                'id': t.id,
                'jieshao': t.jieshao or '',
                'tupian_url': t.tupian_url or '',
                'paixu': t.paixu,
                'shangjia_zhuangtai': t.shangjia_zhuangtai,
                'fengjin_zhuangtai': t.fengjin_zhuangtai,
                'shenhe_zhuangtai': t.shenhe_zhuangtai,
                'gonggong_leixing_id': t.gonggong_leixing_id,
            })
            type_ids.append(t.id)

        # ---------- 4. 各类型商品数量统计 ----------
        type_product_counts = {}
        if type_ids:
            counts = Shangpin.objects.filter(
                dianpu_id=dianpu.id,
                dianpu_leixing_id__in=type_ids
            ).values('dianpu_leixing_id').annotate(cnt=Count('id'))
            type_product_counts = {item['dianpu_leixing_id']: item['cnt'] for item in counts}

        # ---------- 5. 专区列表 ----------
        try:
            zhuanqu_qs = ShangpinZhuanqu.objects.filter(
                dianpu_id=dianpu.id
            ).order_by('-paixu').values(
                'id', 'mingzi', 'paixu', 'shangjia_zhuangtai',
                'fengjin_zhuangtai', 'dianpu_leixing_id'
            )
            zhuanqu_list = []
            zhuanqu_ids = []
            for zq in zhuanqu_qs:
                zhuanqu_list.append({
                    'id': zq['id'],
                    'mingzi': zq['mingzi'] or '',
                    'paixu': zq['paixu'],
                    'shangjia_zhuangtai': zq['shangjia_zhuangtai'],
                    'fengjin_zhuangtai': zq['fengjin_zhuangtai'],
                    'dianpu_leixing_id': zq['dianpu_leixing_id'],
                })
                zhuanqu_ids.append(zq['id'])
        except Exception as e:
            logger.exception("查询专区失败")
            return Response({'code': 500, 'msg': '服务器错误'})

        # ---------- 6. 各专区商品数量统计 ----------
        zhuanqu_product_counts = {}
        if zhuanqu_ids:
            counts = Shangpin.objects.filter(
                dianpu_id=dianpu.id,
                zhuanqu_id__in=zhuanqu_ids
            ).values('zhuanqu_id').annotate(cnt=Count('id'))
            zhuanqu_product_counts = {item['zhuanqu_id']: item['cnt'] for item in counts}

        return Response({
            'code': 0,
            'msg': 'success',
            'data': {
                'public_types': public_types,
                'shop_product_types': shop_product_types,
                'zhuanqu_list': zhuanqu_list,
                'type_product_counts': type_product_counts,
                'zhuanqu_product_counts': zhuanqu_product_counts,
            }
        })


from django.db import transaction
from utils.oss_utils import delete_from_oss   # 引入文件删除工具

# shangdian/views.py
import logging
from django.db import models, transaction



class ShopProductModifyView(APIView):
    """店铺商品类型与专区管理（增、改、删）"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        try:
            frontend_user_id = request.data.get('userId') or request.POST.get('userId')
            if not frontend_user_id:
                return Response({'code': 400, 'msg': '缺少必要参数 userId'})

            dianpu, error = verify_shop_permission(request, frontend_user_id)
            if error:
                return error

            action = request.data.get('action') or request.POST.get('action')
            if not action:
                return Response({'code': 400, 'msg': '缺少操作类型 action'})

            handlers = {
                'add_type': self._add_type,
                'update_type': self._update_type,
                'delete_type': self._delete_type,
                'add_zhuanqu': self._add_zhuanqu,
                'update_zhuanqu': self._update_zhuanqu,
                'delete_zhuanqu': self._delete_zhuanqu,
            }
            handler = handlers.get(action)
            if not handler:
                return Response({'code': 400, 'msg': '不支持的操作类型'})

            return handler(dianpu, request)

        except Exception as e:
            logger.exception("ShopProductModifyView 全局异常")
            return Response({'code': 500, 'msg': '服务器内部错误'})

    # ---------- 商品类型操作 ----------
    @transaction.atomic
    def _add_type(self, dianpu, request):
        """添加店铺商品类型"""
        try:
            jieshao = request.data.get('jieshao') or request.POST.get('jieshao')
            gonggong_leixing_id = request.data.get('gonggong_leixing_id') or request.POST.get('gonggong_leixing_id')
            paixu = request.data.get('paixu', 0) or request.POST.get('paixu', 0)

            if not jieshao or not gonggong_leixing_id:
                return Response({'code': 400, 'msg': '缺少介绍或公共类型ID'})

            # 验证公共类型存在且正常
            if not ShangpinLeixing.objects.filter(id=gonggong_leixing_id, shenhezhuangtai=1).exists():
                return Response({'code': 400, 'msg': '所选公共商品类型无效'})

            image_file = request.FILES.get('image')
            if not image_file:
                return Response({'code': 400, 'msg': '必须上传类型图片'})

            # 上传图片
            file_path = f"dianpu/shangpin/shangpinleixing/{dianpu.id}_{int(time.time())}.png"
            full_url = upload_to_oss(image_file, file_path)
            if not full_url:
                return Response({'code': 500, 'msg': '图片上传失败'})

            # 提取相对路径
            tupian_url = full_url.replace(settings.COS_DOMAIN.rstrip('/') + '/', '', 1)

            # 审核状态
            audit_config = DianpuShangpinShenheShezhi.objects.filter(id=1).first()
            shenhe_status = not (audit_config and audit_config.kaiqi_shenhe)

            ShangpinLeixingDianpu.objects.create(
                dianpu_id=dianpu.id,
                gonggong_leixing_id=gonggong_leixing_id,
                jieshao=jieshao,
                tupian_url=tupian_url,
                paixu=paixu,
                shangjia_zhuangtai=True,
                fengjin_zhuangtai=False,
                shenhe_zhuangtai=shenhe_status,
            )
            return Response({'code': 0, 'msg': '商品类型添加成功'})
        except Exception as e:
            logger.exception("添加商品类型失败")
            return Response({'code': 500, 'msg': '添加失败，请稍后重试'})

    @transaction.atomic
    def _update_type(self, dianpu, request):
        """修改店铺商品类型，支持更换图片（先删旧图）"""
        type_id = request.data.get('type_id') or request.POST.get('type_id')
        if not type_id:
            return Response({'code': 400, 'msg': '缺少 type_id'})

        try:
            obj = ShangpinLeixingDianpu.objects.select_for_update().get(
                id=type_id, dianpu_id=dianpu.id
            )
        except ShangpinLeixingDianpu.DoesNotExist:
            return Response({'code': 404, 'msg': '商品类型不存在'})
        except Exception as e:
            logger.exception("查询商品类型异常")
            return Response({'code': 500, 'msg': '服务器错误'})

        try:
            update_fields = []
            content_changed = False

            # 简介
            if 'jieshao' in request.data or 'jieshao' in request.POST:
                obj.jieshao = request.data.get('jieshao') or request.POST.get('jieshao')
                update_fields.append('jieshao')
                content_changed = True

            # 映射公共类型
            if 'gonggong_leixing_id' in request.data or 'gonggong_leixing_id' in request.POST:
                new_id = request.data.get('gonggong_leixing_id') or request.POST.get('gonggong_leixing_id')
                if not ShangpinLeixing.objects.filter(id=new_id, shenhezhuangtai=1).exists():
                    return Response({'code': 400, 'msg': '公共商品类型无效'})
                obj.gonggong_leixing_id = new_id
                update_fields.append('gonggong_leixing_id')
                content_changed = True

            # 排序
            if 'paixu' in request.data or 'paixu' in request.POST:
                obj.paixu = request.data.get('paixu') or request.POST.get('paixu')
                update_fields.append('paixu')

            # 上架状态
            if 'shangjia_zhuangtai' in request.data or 'shangjia_zhuangtai' in request.POST:
                val = request.data.get('shangjia_zhuangtai') or request.POST.get('shangjia_zhuangtai')
                if isinstance(val, str):
                    val = val.lower() in ('true', '1', 'yes')
                obj.shangjia_zhuangtai = bool(val)
                update_fields.append('shangjia_zhuangtai')

            # 图片更换：先删除旧图，再上传新图
            image_file = request.FILES.get('image')
            if image_file:
                if obj.tupian_url:
                    delete_from_oss(obj.tupian_url)                # 删除旧图片
                file_path = f"dianpu/shangpin/shangpinleixing/{dianpu.id}_{int(time.time())}.png"
                full_url = upload_to_oss(image_file, file_path)
                if not full_url:
                    return Response({'code': 500, 'msg': '图片上传失败'})
                obj.tupian_url = full_url.replace(settings.COS_DOMAIN.rstrip('/') + '/', '', 1)
                update_fields.append('tupian_url')
                content_changed = True

            # 审核重置
            if content_changed:
                audit_config = DianpuShangpinShenheShezhi.objects.filter(id=1).first()
                if audit_config and audit_config.kaiqi_shenhe:
                    obj.shenhe_zhuangtai = False
                    update_fields.append('shenhe_zhuangtai')

            if update_fields:
                obj.save(update_fields=update_fields)
            return Response({'code': 0, 'msg': '商品类型更新成功'})
        except Exception as e:
            logger.exception("修改商品类型失败")
            return Response({'code': 500, 'msg': '修改失败，请稍后重试'})

    @transaction.atomic
    def _delete_type(self, dianpu, request):
        """删除店铺商品类型，可选是否同时删除关联商品"""
        type_id = request.data.get('id') or request.POST.get('id')
        if not type_id:
            return Response({'code': 400, 'msg': '缺少类型ID'})

        try:
            obj = ShangpinLeixingDianpu.objects.select_for_update().get(
                id=type_id, dianpu_id=dianpu.id
            )
        except ShangpinLeixingDianpu.DoesNotExist:
            return Response({'code': 404, 'msg': '商品类型不存在'})
        except Exception as e:
            logger.exception("查询商品类型异常")
            return Response({'code': 500, 'msg': '服务器错误'})

        try:
            delete_products = request.data.get('delete_products') or request.POST.get('delete_products')
            delete_products = str(delete_products).lower() in ('true', '1', 'yes')

            if delete_products:
                # 删除该类型下所有关联商品（同时清理图片）
                products = Shangpin.objects.filter(
                    dianpu_leixing_id=obj.id, dianpu_id=dianpu.id
                )
                for p in products:
                    if p.tupian_url:
                        delete_from_oss(p.tupian_url)
                    if p.guize_tupian:
                        delete_from_oss(p.guize_tupian)
                    p.delete()
            else:
                # 仅解除关联，不删商品
                Shangpin.objects.filter(
                    dianpu_leixing_id=obj.id, dianpu_id=dianpu.id
                ).update(dianpu_leixing=None)

            # 删除类型自身图片
            if obj.tupian_url:
                delete_from_oss(obj.tupian_url)
            obj.delete()
            return Response({'code': 0, 'msg': '类型删除成功'})
        except Exception as e:
            logger.exception("删除商品类型失败")
            return Response({'code': 500, 'msg': '删除失败，请稍后重试'})

    # ---------- 专区操作 ----------
    @transaction.atomic
    def _add_zhuanqu(self, dianpu, request):
        """添加专区，自动从店铺商品类型获取 leixing_id（公共商品类型ID）"""
        try:
            mingzi = request.data.get('mingzi')
            dianpu_leixing_id = request.data.get('dianpu_leixing_id')
            paixu = request.data.get('paixu', 0)

            if not mingzi or not dianpu_leixing_id:
                return Response({'code': 400, 'msg': '缺少名称或所属商品类型'})

            # 获取店铺商品类型，并拿到映射的公共类型ID
            try:
                shop_type = ShangpinLeixingDianpu.objects.get(
                    id=dianpu_leixing_id, dianpu_id=dianpu.id
                )
                leixing_id = shop_type.gonggong_leixing_id   # 这就是专区需要的 leixing_id
            except ShangpinLeixingDianpu.DoesNotExist:
                return Response({'code': 400, 'msg': '所选商品类型无效'})

            # 验证公共类型有效
            if not ShangpinLeixing.objects.filter(id=leixing_id, shenhezhuangtai=1).exists():
                return Response({'code': 400, 'msg': '关联的公共商品类型无效'})

            ShangpinZhuanqu.objects.create(
                dianpu_id=dianpu.id,
                mingzi=mingzi,
                dianpu_leixing_id=dianpu_leixing_id,
                leixing_id=leixing_id,                     # 自动填充
                paixu=paixu,
                shangjia_zhuangtai=True,
                fengjin_zhuangtai=False,
            )
            return Response({'code': 0, 'msg': '专区添加成功'})
        except Exception as e:
            logger.exception("添加专区失败")
            return Response({'code': 500, 'msg': '添加失败，请稍后重试'})

    @transaction.atomic
    def _update_zhuanqu(self, dianpu, request):
        """修改专区，若更换所属商品类型则同步更新 leixing_id"""
        zhuanqu_id = request.data.get('zhuanqu_id')
        if not zhuanqu_id:
            return Response({'code': 400, 'msg': '缺少 zhuanqu_id'})

        try:
            obj = ShangpinZhuanqu.objects.select_for_update().get(
                id=zhuanqu_id, dianpu_id=dianpu.id
            )
        except ShangpinZhuanqu.DoesNotExist:
            return Response({'code': 404, 'msg': '专区不存在'})
        except Exception as e:
            logger.exception("查询专区异常")
            return Response({'code': 500, 'msg': '服务器错误'})

        try:
            update_fields = []

            if 'mingzi' in request.data:
                obj.mingzi = request.data.get('mingzi')
                update_fields.append('mingzi')

            if 'dianpu_leixing_id' in request.data:
                new_id = request.data.get('dianpu_leixing_id')
                try:
                    shop_type = ShangpinLeixingDianpu.objects.get(
                        id=new_id, dianpu_id=dianpu.id
                    )
                    obj.dianpu_leixing_id = new_id
                    obj.leixing_id = shop_type.gonggong_leixing_id   # 同步更新公共类型ID
                    update_fields.extend(['dianpu_leixing_id', 'leixing_id'])
                except ShangpinLeixingDianpu.DoesNotExist:
                    return Response({'code': 400, 'msg': '所选商品类型无效'})

            if 'paixu' in request.data:
                obj.paixu = request.data.get('paixu')
                update_fields.append('paixu')

            if 'shangjia_zhuangtai' in request.data:
                val = request.data.get('shangjia_zhuangtai')
                if isinstance(val, str):
                    val = val.lower() in ('true', '1', 'yes')
                obj.shangjia_zhuangtai = bool(val)
                update_fields.append('shangjia_zhuangtai')

            if update_fields:
                obj.save(update_fields=update_fields)
            return Response({'code': 0, 'msg': '专区更新成功'})
        except Exception as e:
            logger.exception("修改专区失败")
            return Response({'code': 500, 'msg': '修改失败，请稍后重试'})

    @transaction.atomic
    def _delete_zhuanqu(self, dianpu, request):
        """删除专区，可选是否同时删除关联商品"""
        zhuanqu_id = request.data.get('id')
        if not zhuanqu_id:
            return Response({'code': 400, 'msg': '缺少专区ID'})

        try:
            obj = ShangpinZhuanqu.objects.select_for_update().get(
                id=zhuanqu_id, dianpu_id=dianpu.id
            )
        except ShangpinZhuanqu.DoesNotExist:
            return Response({'code': 404, 'msg': '专区不存在'})
        except Exception as e:
            logger.exception("查询专区异常")
            return Response({'code': 500, 'msg': '服务器错误'})

        try:
            delete_products = request.data.get('delete_products')
            delete_products = str(delete_products).lower() in ('true', '1', 'yes')

            if delete_products:
                products = Shangpin.objects.filter(
                    zhuanqu_id=obj.id, dianpu_id=dianpu.id
                )
                for p in products:
                    if p.tupian_url:
                        delete_from_oss(p.tupian_url)
                    if p.guize_tupian:
                        delete_from_oss(p.guize_tupian)
                    p.delete()
            else:
                Shangpin.objects.filter(
                    zhuanqu_id=obj.id, dianpu_id=dianpu.id
                ).update(zhuanqu_id=None)

            obj.delete()
            return Response({'code': 0, 'msg': '专区删除成功'})
        except Exception as e:
            logger.exception("删除专区失败")
            return Response({'code': 500, 'msg': '删除失败，请稍后重试'})


from django.core.paginator import Paginator
class ShopProductListView(APIView):
    """
    商品列表接口，支持筛选、分页。
    URL: /shangdian/sdhqspsj
    权限: JWT认证 + 店铺验证
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 身份验证，获取店铺对象
        frontend_user_id = request.data.get('userId')
        if not frontend_user_id:
            return Response({'code': 400, 'msg': '缺少必要参数 userId'})

        dianpu, error = verify_shop_permission(request, frontend_user_id)
        if error:
            return error

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

        keyword = str(request.data.get('keyword', '')).strip()
        shop_product_type_id = request.data.get('shop_product_type_id')  # 店铺商品类型ID
        zhuanqu_id = request.data.get('zhuanqu_id')                     # 专区ID

        # 布尔型筛选字段
        def parse_bool(val):
            if val is None or val == '':
                return None
            if isinstance(val, bool):
                return val
            if isinstance(val, str):
                return val.lower() in ('true', '1', 'yes')
            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'))

        # 3. 构建查询（只查当前店铺的商品）
        products = Shangpin.objects.filter(dianpu_id=dianpu.id).select_related(
            'dianpu_leixing'
        ).only(
            'id', 'biaoqian', 'jiage', 'kucun', 'leixing_id', 'zhuanqu_id',
            'zhenshi_xiaoliang', 'duiwai_xiaoliang', 'jieshao', 'xiadan_xuzhi',
            'guize_tupian', 'yaoqiuleixing', 'huiyuan_id', 'yongjin',
            'tupian_url', 'ewai_dashou_fencheng', 'paixu',
            'dianpu_leixing_id', 'shangjia_zhuangtai', 'fengjin_zhuangtai',
            'shenhe_zhuangtai', 'create_time', 'update_time'
        )

        # 筛选
        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 格式错误'})

        if zhuanqu_id is not None:
            try:
                products = products.filter(zhuanqu_id=int(zhuanqu_id))
            except (ValueError, TypeError):
                return Response({'code': 400, 'msg': 'zhuanqu_id 格式错误'})

        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、标题、介绍）
        if keyword:
            products = products.filter(
                models.Q(id__icontains=keyword) |
                models.Q(biaoqian__icontains=keyword) |
                models.Q(jieshao__icontains=keyword)
            )

        # 4. 分页
        total_count = products.count()
        paginator = Paginator(products, page_size)
        page_obj = paginator.get_page(page)

        # 5. 构造返回数据
        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,
                'zhuanqu_id': p.zhuanqu_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,
                'tupian_url': p.tupian_url or '',
                'ewai_dashou_fencheng': p.ewai_dashou_fencheng,
                'paixu': p.paixu,
                'dianpu_leixing_id': p.dianpu_leixing_id,
                'shangjia_zhuangtai': p.shangjia_zhuangtai,
                'fengjin_zhuangtai': p.fengjin_zhuangtai,
                'shenhe_zhuangtai': p.shenhe_zhuangtai,
                'create_time': p.create_time.isoformat() if p.create_time else '',
                'update_time': p.update_time.isoformat() if p.update_time else '',
            })

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







class ShopProductModifyView1(APIView):
    """
    商品统一操作：修改、添加、删除
    URL: /shangdian/xgdpsplx
    权限: JWT认证 + 店铺验证
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        frontend_user_id = request.data.get('userId')
        if not frontend_user_id:
            return Response({'code': 400, 'msg': '缺少必要参数 userId'})

        dianpu, error = verify_shop_permission(request, frontend_user_id)
        if error:
            return error

        action = request.data.get('action')
        if not action:
            return Response({'code': 400, 'msg': '缺少操作类型 action'})

        # 分发
        if action == 'update_product':
            return self._update_product(dianpu, request.data)
        elif action == 'add_product':
            return self._add_product(dianpu, request.data)
        elif action == 'delete_product':
            return self._delete_product(dianpu, request.data)
        else:
            return Response({'code': 400, 'msg': '不支持的操作类型'})

    # ---------- 修改商品 ----------
    @transaction.atomic
    def _update_product(self, dianpu, data):
        product_id = data.get('product_id')
        if not product_id:
            return Response({'code': 400, 'msg': '缺少 product_id'})

        try:
            product = Shangpin.objects.select_for_update().get(id=product_id, dianpu_id=dianpu.id)
        except Shangpin.DoesNotExist:
            return Response({'code': 404, 'msg': '商品不存在'})

        update_fields = []
        # 标记是否有核心内容修改（会触发审核重置）
        content_changed = False

        # 标题
        if 'biaoqian' in data:
            product.biaoqian = data['biaoqian']
            update_fields.append('biaoqian')
            content_changed = True

        # 价格
        if 'jiage' in data:
            product.jiage = data['jiage']
            update_fields.append('jiage')
            content_changed = True

        # 库存
        if 'kucun' in data:
            product.kucun = data['kucun']
            update_fields.append('kucun')
            content_changed = True

        # 店铺商品类型
        if 'dianpu_leixing_id' in data:
            # 验证类型属于当前店铺
            new_type_id = data['dianpu_leixing_id']
            if not ShangpinLeixingDianpu.objects.filter(id=new_type_id, dianpu_id=dianpu.id).exists():
                return Response({'code': 400, 'msg': '无效的店铺商品类型'})
            product.dianpu_leixing_id = new_type_id
            update_fields.append('dianpu_leixing_id')
            content_changed = True

        # 专区
        if 'zhuanqu_id' in data:
            new_zq_id = data['zhuanqu_id']
            if new_zq_id and not ShangpinZhuanqu.objects.filter(id=new_zq_id, dianpu_id=dianpu.id).exists():
                return Response({'code': 400, 'msg': '无效的专区'})
            product.zhuanqu_id = new_zq_id or None
            update_fields.append('zhuanqu_id')
            content_changed = True

        # 介绍
        if 'jieshao' in data:
            product.jieshao = data['jieshao']
            update_fields.append('jieshao')
            content_changed = True

        # 下单须知
        if 'xiadan_xuzhi' in data:
            product.xiadan_xuzhi = data['xiadan_xuzhi']
            update_fields.append('xiadan_xuzhi')

        # 抢单要求类型
        if 'yaoqiuleixing' in data:
            yqlx = int(data['yaoqiuleixing'])
            if yqlx not in [1, 2]:
                return Response({'code': 400, 'msg': '抢单要求类型无效'})
            product.yaoqiuleixing = yqlx
            update_fields.append('yaoqiuleixing')
            content_changed = True

        # 会员ID
        if 'huiyuan_id' in data:
            # 当抢单要求是会员时可能传
            product.huiyuan_id = data['huiyuan_id'] or None
            update_fields.append('huiyuan_id')
            content_changed = True

        # 佣金
        if 'yongjin' in data:
            product.yongjin = data['yongjin'] or 0
            update_fields.append('yongjin')
            content_changed = True

        # 打手额外分成金额（不能超过商品价格）
        if 'ewai_dashou_fencheng' in data:
            fencheng = data['ewai_dashou_fencheng'] or 0
            current_price = product.jiage
            if fencheng > current_price:
                return Response({'code': 400, 'msg': '分成金额不能超过商品价格'})
            product.ewai_dashou_fencheng = fencheng
            update_fields.append('ewai_dashou_fencheng')
            content_changed = True

        # 排序
        if 'paixu' in data:
            product.paixu = data['paixu']
            update_fields.append('paixu')

        # 上架状态
        if 'shangjia_zhuangtai' in data:
            product.shangjia_zhuangtai = data['shangjia_zhuangtai']
            update_fields.append('shangjia_zhuangtai')

        # 商品图片（上传新图则替换旧图）
        if 'tupian_url' in data and data['tupian_url'] != product.tupian_url:
            # 删除旧图
            if product.tupian_url:
                delete_from_oss(product.tupian_url)
            product.tupian_url = data['tupian_url']
            update_fields.append('tupian_url')
            content_changed = True

        # 规则图片
        if 'guize_tupian' in data:
            new_rule = data['guize_tupian'] or ''
            if new_rule != (product.guize_tupian or ''):
                if product.guize_tupian:
                    delete_from_oss(product.guize_tupian)
                product.guize_tupian = new_rule if new_rule else None
                update_fields.append('guize_tupian')
                content_changed = True

        # 检查全局审核模式：若开启且内容有修改，则标记为未审核
        if content_changed:
            audit_config = DianpuShangpinShenheShezhi.objects.filter(id=1).first()
            if audit_config and audit_config.kaiqi_shenhe:
                product.shenhe_zhuangtai = False
                update_fields.append('shenhe_zhuangtai')

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

    # ---------- 添加商品 ----------
    @transaction.atomic
    def _add_product(self, dianpu, data):
        biaoqian = data.get('biaoqian')
        jiage = data.get('jiage')
        kucun = data.get('kucun')
        dianpu_leixing_id = data.get('dianpu_leixing_id')
        zhuanqu_id = data.get('zhuanqu_id')
        jieshao = data.get('jieshao')
        # 必填校验
        if not all([biaoqian, jiage is not None, kucun is not None, dianpu_leixing_id, zhuanqu_id, jieshao]):
            return Response({'code': 400, 'msg': '必填项缺失'})

        # 验证店铺商品类型
        try:
            shop_type = ShangpinLeixingDianpu.objects.get(id=dianpu_leixing_id, dianpu_id=dianpu.id)
        except ShangpinLeixingDianpu.DoesNotExist:
            return Response({'code': 400, 'msg': '无效的店铺商品类型'})

        # 获取映射的公共商品类型
        try:
            public_type = ShangpinLeixing.objects.get(id=shop_type.gonggong_leixing_id, shenhezhuangtai=1)
        except ShangpinLeixing.DoesNotExist:
            return Response({'code': 400, 'msg': '关联的公共商品类型无效'})

        # 抢单要求：默认会员，若用户选择了会员但未指定，则用公共类型的会员ID
        yaoqiuleixing = data.get('yaoqiuleixing', 1)
        if yaoqiuleixing == 1:
            huiyuan_id = data.get('huiyuan_id') or public_type.huiyuan_id or None
            yongjin = None
        else:
            huiyuan_id = None
            yongjin = data.get('yongjin') or public_type.yongjin or 0

        # 打手额外分成：金额不能大于价格，没传则0

        ewai_fencheng = data.get('ewai_dashou_fencheng') or 0

        try:
            ewai_val = float(ewai_fencheng)
            jiage_val = float(jiage)
            if ewai_val > jiage_val:
                return Response({'code': 400, 'msg': '分成金额不能超过商品价格'})
        except ValueError:
            # 处理转换失败的情况，比如记录日志或设为默认值
            print("无法转换为数字")

        '''if ewai_fencheng > jiage:
            return Response({'code': 400, 'msg': '分成金额不能超过商品价格'})'''

        # 审核状态：根据全局配置决定
        shenhe_status = True  # 默认通过
        audit_config = DianpuShangpinShenheShezhi.objects.filter(id=1).first()
        if audit_config and audit_config.kaiqi_shenhe:
            shenhe_status = False

        # 创建商品
        product = Shangpin.objects.create(
            biaoqian=biaoqian,
            jiage=jiage,
            kucun=kucun,
            dianpu_id=dianpu.id,
            dianpu_leixing_id=dianpu_leixing_id,
            zhuanqu_id=zhuanqu_id,
            leixing_id=public_type.id,          # 公共商品类型ID
            jieshao=jieshao,
            xiadan_xuzhi=data.get('xiadan_xuzhi', ''),
            guize_tupian=data.get('guize_tupian') or None,
            tupian_url=data.get('tupian_url') or '',  # 商品图片必须由前端上传后传入
            yaoqiuleixing=yaoqiuleixing,
            huiyuan_id=huiyuan_id,
            yongjin=yongjin,
            ewai_dashou_fencheng=ewai_fencheng,
            kaioi_ewai_dashou_fencheng=True,   # 强制开启
            paixu=data.get('paixu', 0),
            shangjia_zhuangtai=data.get('shangjia_zhuangtai', True),  # 默认上架
            fengjin_zhuangtai=False,            # 默认未封禁
            shenhe_zhuangtai=shenhe_status,
            shenhezhuangtai=1,                  # 另一个审核字段永远正常
        )
        return Response({'code': 0, 'msg': '商品添加成功', 'product_id': product.id})

    # ---------- 删除商品 ----------
    @transaction.atomic
    def _delete_product(self, dianpu, data):
        product_id = data.get('product_id')
        if not product_id:
            return Response({'code': 400, 'msg': '缺少 product_id'})

        try:
            product = Shangpin.objects.select_for_update().get(id=product_id, dianpu_id=dianpu.id)
        except Shangpin.DoesNotExist:
            return Response({'code': 404, 'msg': '商品不存在'})

        # 删除关联图片（OSS）
        if product.tupian_url:
            delete_from_oss(product.tupian_url)
        if product.guize_tupian:
            delete_from_oss(product.guize_tupian)

        product.delete()
        return Response({'code': 0, 'msg': '商品已删除'})




from shangpin.models import Huiyuan                 # 会员模型，根据实际路径调整
class MemberListView(APIView):
    """
    获取所有会员列表（供商品发布时选择）
    URL: /shangdian/hqhy
    权限：JWT认证 + 店铺归属验证
    说明：前端需传递 userId，接口会进行身份及店铺状态校验
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # ---------- 1. 获取前端传递的用户ID（必须） ----------
        frontend_user_id = request.data.get('userId')
        if not frontend_user_id:
            return Response({'code': 400, 'msg': '缺少必要参数 userId'})

        # ---------- 2. 调用公共验证方法（会校验用户身份、店铺状态） ----------
        dianpu, error_response = verify_shop_permission(request, frontend_user_id)
        if error_response:
            return error_response

        # ---------- 3. 查询所有会员（只返回前端需要的字段） ----------
        try:
            members = Huiyuan.objects.all().values('huiyuan_id', 'jieshao')
            member_list = [
                {
                    'huiyuan_id': m['huiyuan_id'],
                    'jieshao': m['jieshao'] or ''
                }
                for m in members
            ]
        except Exception as e:
            logger.exception("获取会员列表失败")
            return Response({'code': 500, 'msg': '服务器错误'})

        # ---------- 4. 返回成功响应 ----------
        return Response({
            'code': 0,
            'msg': 'success',
            'data': member_list
        })




from django.db import transaction, IntegrityError
from django.core.paginator import Paginator, EmptyPage



class SubShopListView(APIView):
    """
    获取下级店铺列表，同时返回上级分红汇总
    URL: /shangdian/wdxjdp
    方法: POST
    权限: JWT认证 + 上级店铺身份验证
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 身份验证 - 获取上级店铺对象
        user_id = request.data.get('userId')
        if not user_id:
            return Response({'code': 400, 'msg': '缺少 userId'})

        parent_dianpu, error = verify_shop_permission(request, user_id)
        if error:
            return error
        # 此时 parent_dianpu 即为当前上级店铺

        # 2. 读取筛选参数
        dianpu_mingcheng = request.data.get('dianpu_mingcheng', '').strip()
        zhuangtai = request.data.get('zhuangtai')
        bind_min = request.data.get('bind_min')
        bind_max = request.data.get('bind_max')
        page = request.data.get('page', 1)
        page_size = request.data.get('page_size', 10)

        # 3. 查询下级店铺：yaoqing_dianpu_id 等于当前上级店铺ID
        queryset = Dianpu.objects.filter(yaoqing_dianpu_id=parent_dianpu.id)

        # 4. 筛选条件
        if dianpu_mingcheng:
            queryset = queryset.filter(dianpu_mingcheng__icontains=dianpu_mingcheng)
        if zhuangtai is not None:
            try:
                zhuangtai = int(zhuangtai)
                queryset = queryset.filter(zhuangtai=zhuangtai)
            except (ValueError, TypeError):
                pass
        if bind_min is not None:
            try:
                bind_min = int(bind_min)
                queryset = queryset.filter(bangding_yonghushu__gte=bind_min)
            except (ValueError, TypeError):
                pass
        if bind_max is not None:
            try:
                bind_max = int(bind_max)
                queryset = queryset.filter(bangding_yonghushu__lte=bind_max)
            except (ValueError, TypeError):
                pass

        # 5. 排序：按创建时间倒序
        queryset = queryset.order_by('-create_time')

        # 6. 分页
        try:
            paginator = Paginator(queryset, page_size)
            page_obj = paginator.page(page)
        except EmptyPage:
            page_obj = paginator.page(1)

        # 7. 构建列表数据
        shop_list = []
        for shop in page_obj:
            shop_list.append({
                'id': shop.id,
                'dianpu_mingcheng': shop.dianpu_mingcheng,
                'dianpu_touxiang': shop.dianpu_touxiang or '',
                'lianxi_dianhua': shop.lianxi_dianhua or '',
                'zhuangtai': shop.zhuangtai,
                'bangding_yonghushu': shop.bangding_yonghushu,
                'zhengqu_zonge': str(shop.zhengqu_zonge),
                'ketixian_yue': str(shop.ketixian_yue),
                'create_time': shop.create_time.strftime('%Y-%m-%d %H:%M:%S') if shop.create_time else '',
                'update_time': shop.update_time.strftime('%Y-%m-%d %H:%M:%S') if shop.update_time else '',
                'erweima_url': shop.erweima_url or '',
                'fenhong_choucheng_feilv': str(shop.fenhong_choucheng_feilv) if shop.fenhong_choucheng_feilv else '0.0000',
            })

        # 8. 上级分红汇总（从当前店铺主表读取）
        shangji_leiji = str(parent_dianpu.shangji_fenhong_leiji) if parent_dianpu.shangji_fenhong_leiji else '0.00'
        shangji_ketixian = str(parent_dianpu.shangji_ketixian_fenhong) if parent_dianpu.shangji_ketixian_fenhong else '0.00'

        return Response({
            'code': 200,
            'msg': '获取成功',
            'data': {
                'list': shop_list,
                'total': paginator.count,
                'page': page_obj.number,
                'page_size': page_size,
                'shangji_fenhong_leiji': shangji_leiji,
                'shangji_ketixian_fenhong': shangji_ketixian,
            }
        })


class SubShopModifyView(APIView):
    """
    添加/修改下级店铺
    URL: /shangdian/xjdpbj
    方法: POST
    权限: JWT认证 + 上级店铺身份验证
    action字段：
        - 'add'   : 添加新的子店铺
        - 'update': 修改已有子店铺的状态/分红费率
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 身份验证
        user_id = request.data.get('userId')
        if not user_id:
            return Response({'code': 400, 'msg': '缺少 userId'})

        parent_dianpu, error = verify_shop_permission(request, user_id)
        if error:
            return error

        action = request.data.get('action', '').strip().lower()
        if action == 'add':
            return self._add_subshop(request, parent_dianpu)
        elif action == 'update':
            return self._update_subshop(request, parent_dianpu)
        else:
            return Response({'code': 400, 'msg': '未知操作类型，action 应为 add 或 update'})

    def _add_subshop(self, request, parent_dianpu):
        """添加子店铺，需提供用户UID、账号、密码、店铺名称等"""
        user_uid = request.data.get('user_uid', '').strip()
        zhanghao = request.data.get('zhanghao', '').strip()
        mima = request.data.get('mima', '')
        dianpu_mingcheng = request.data.get('dianpu_mingcheng', '').strip()
        lianxi_dianhua = request.data.get('lianxi_dianhua', '').strip() or None
        fenhong_feilv = request.data.get('fenhong_choucheng_feilv', None)

        # 必填校验
        if not all([user_uid, zhanghao, mima, dianpu_mingcheng]):
            return Response({'code': 400, 'msg': '必填字段缺失（user_uid, zhanghao, mima, dianpu_mingcheng）'})

        # 检查用户是否存在
        if not UserMain.objects.filter(yonghuid=user_uid).exists():
            return Response({'code': 404, 'msg': '对应的小程序用户不存在'})

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

        # 费率处理：前端可选，后端默认 0.0500
        if fenhong_feilv is not None:
            try:
                fenhong_feilv = float(fenhong_feilv)
                if fenhong_feilv < 0 or fenhong_feilv > 1:
                    return Response({'code': 400, 'msg': '分红费率需在 0~1 之间'})
            except (ValueError, TypeError):
                return Response({'code': 400, 'msg': '分红费率格式错误'})
        else:
            fenhong_feilv = 0.0500

        try:
            with transaction.atomic():
                # 创建凭证
                pingzheng = YonghuPingzheng.objects.create(
                    yonghu=user_uid,
                    zhanghao=zhanghao,
                    mima=mima,
                    is_active=True
                )
                # 创建店铺，关联上级
                dianpu = Dianpu.objects.create(
                    pingzheng=pingzheng,
                    dianpu_mingcheng=dianpu_mingcheng,
                    lianxi_dianhua=lianxi_dianhua or '',
                    yaoqing_dianpu_id=parent_dianpu.id,      # 关键：绑定上级
                    fenhong_choucheng_feilv=fenhong_feilv,
                    # 其他字段使用默认值
                )
                return Response({
                    'code': 200,
                    'msg': '子店铺添加成功',
                    'dianpu_id': dianpu.id
                })
        except IntegrityError as e:
            logger.exception("创建子店铺违反唯一约束")
            return Response({'code': 400, 'msg': '数据冲突，请检查账号或用户ID'})
        except Exception as e:
            logger.exception("创建子店铺失败")
            return Response({'code': 500, 'msg': '创建失败，请稍后重试'})

    def _update_subshop(self, request, parent_dianpu):
        """修改子店铺状态和上级分红费率"""
        dianpu_id = request.data.get('dianpu_id')
        if not dianpu_id:
            return Response({'code': 400, 'msg': '缺少 dianpu_id'})

        try:
            dianpu_id = int(dianpu_id)
        except (ValueError, TypeError):
            return Response({'code': 400, 'msg': 'dianpu_id 需为数字'})

        try:
            with transaction.atomic():
                sub_dianpu = Dianpu.objects.select_for_update().get(id=dianpu_id)
                # 权限校验：确保该店铺的上级就是当前店铺
                if sub_dianpu.yaoqing_dianpu_id != parent_dianpu.id:
                    return Response({'code': 403, 'msg': '无权修改该店铺，非您的下级'})

                # 更新状态（如果有传）
                if 'zhuangtai' in request.data:
                    zhuangtai = request.data['zhuangtai']
                    if zhuangtai not in (0, 1, '0', '1'):
                        return Response({'code': 400, 'msg': '店铺状态只能为 0(封禁) 或 1(正常)'})
                    sub_dianpu.zhuangtai = int(zhuangtai)

                # 更新上级分红费率（如果有传）
                if 'fenhong_choucheng_feilv' in request.data:
                    feilv = request.data['fenhong_choucheng_feilv']
                    try:
                        feilv = float(feilv)
                        if feilv < 0 or feilv > 1:
                            return Response({'code': 400, 'msg': '分红费率需在 0~1 之间'})
                        sub_dianpu.fenhong_choucheng_feilv = feilv
                    except (ValueError, TypeError):
                        return Response({'code': 400, 'msg': '分红费率格式错误'})

                sub_dianpu.save(update_fields=['zhuangtai', 'fenhong_choucheng_feilv', 'update_time'])
                return Response({'code': 200, 'msg': '修改成功'})

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





# shangdian/views/order_manage_views.py
"""
店铺订单管理接口
包含：
1. 统计数据：商品类型及订单总数、各状态订单数
2. 订单列表：支持筛选、分页、返回订单详情及打手图片（含跨平台图片拉取）
"""

from concurrent.futures import ThreadPoolExecutor, as_completed

from django.db.models import Count, Q, Prefetch

from django.core.paginator import Paginator, EmptyPage
          # 已存在的验证函数
from dingdan.models import Dingdan, DingdanPingtai, Dashoutupian
from peizhi.models import ClubConfig, Club                        # 跨平台相关模型，按实际路径调整

from collections import defaultdict

# 如果有 ClubConfig 模型用于获取存储桶域名，确保导入（如没有可忽略，使用 settings 中的 OSS 域名）
try:
    from kuaping.models import ClubConfig
except ImportError:
    ClubConfig = None




# ================================================================
# 视图1：订单统计接口
# ================================================================
class ShopOrderStatisticsView(APIView):
    """
    店铺订单统计接口
    URL: POST /shangdian/ddltj
    权限: JWT认证 + 店铺身份验证

    请求参数:
        userId (必填): 店铺所属用户的ID

    返回结构:
    {
        "code": 200,
        "msg": "统计成功",
        "data": {
            "types": [                         # 所有商品类型
                {
                    "id": 类型ID,
                    "jieshao": "类型介绍",
                    "tupian_url": "类型图片相对路径",
                    "order_count": 该类型订单总数,
                    "status_counts": {         # 该类型下各状态订单数（键为字符串）
                        "1": 数量,
                        "7": 数量,
                        "2": 数量,
                        "8": 数量,
                        "3": 数量,
                        "4": 数量,
                        "5": 数量,
                        "6": 数量
                    }
                },
                ...
            ],
            "status_counts": {                # 全局各状态订单总数
                "1": 总数,
                "7": 总数,
                "2": 总数,
                "8": 总数,
                "3": 总数,
                "4": 总数,
                "5": 总数,
                "6": 总数
            },
            "total_orders": 订单总量
        }
    }
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 获取身份标识
        user_id = request.data.get('userId', '').strip()
        if not user_id:
            return Response({'code': 400, 'msg': '缺少 userId'})

        # 2. 验证店铺身份，获取店铺对象
        dianpu, error = verify_shop_permission(request, user_id)
        if error:
            return error

        shop_id = dianpu.id

        # 3. 获取所有正常的商品类型（审核状态=1）
        types_qs = ShangpinLeixing.objects.filter(
            shenhezhuangtai=1
        ).order_by('-paixu', 'id')

        # 构建类型列表，初始化各状态计数为 0
        type_list = []
        for t in types_qs:
            type_list.append({
                'id': t.id,
                'jieshao': t.jieshao or '',
                'tupian_url': t.tupian_url or '',
                'order_count': 0,
                'status_counts': {
                    '1': 0, '7': 0, '2': 0, '8': 0,
                    '3': 0, '4': 0, '5': 0, '6': 0
                }
            })

        # 4. 获取本店铺所有订单 ID（通过平台扩展表）
        order_ids = list(
            DingdanPingtai.objects.filter(dianpu_id=shop_id)
            .values_list('dingdan_id', flat=True)
        )

        total_orders = 0                     # 订单总量
        global_counts = defaultdict(int)     # 全局各状态计数

        if order_ids:
            # 5. 按商品类型和订单状态聚合统计
            agg = (
                Dingdan.objects
                .filter(dingdan_id__in=order_ids)
                .values('leixing_id', 'zhuangtai')
                .annotate(cnt=Count('id'))
            )
            for row in agg:
                lid = row['leixing_id']        # 商品类型ID
                st = str(row['zhuangtai'])     # 状态码 -> 字符串
                cnt = row['cnt']

                # 累加总量与全局状态
                total_orders += cnt
                global_counts[st] += cnt

                # 填充到对应商品类型
                for tp in type_list:
                    if tp['id'] == lid:
                        tp['order_count'] += cnt
                        if st in tp['status_counts']:
                            tp['status_counts'][st] += cnt
                        break

        # 6. 保证全局统计返回所有8个状态
        all_status_keys = ['1', '7', '2', '8', '3', '4', '5', '6']
        final_global = {k: global_counts.get(k, 0) for k in all_status_keys}

        return Response({
            'code': 200,
            'msg': '统计成功',
            'data': {
                'types': type_list,
                'status_counts': final_global,
                'total_orders': total_orders
            }
        })





def get_oss_domain():
    """
    获取对象存储的域名（用于拼接本地打手图片完整 URL）
    优先从 ClubConfig 获取，其次从 settings.OSS_URL 获取
    """
    if ClubConfig:
        my_config = ClubConfig.objects.filter(is_self=1).first()
        if my_config and my_config.storage_bucket_domain:
            return my_config.storage_bucket_domain.rstrip('/') + '/'
    # 降级使用 settings 中的 OSS URL
    return getattr(settings, 'OSS_URL', '').rstrip('/') + '/'


def _fetch_partner_images_for_order(order, oss_domain):
    """
    根据单个订单获取打手提交的图片列表（完整绝对 URL）。
    处理本地图片 + 跨平台图片（重试拉取对方平台图片）

    返回: (images_list, is_punished, punishment_info)
    """
    images = []
    is_punished = False
    punishment_info = {}

    # 1. 本地打手图片（我方平台的打手提交）
    if order.jiedan_dashou_id:
        local_imgs = Dashoutupian.objects.filter(
            dingdan_id=order.dingdan_id,
            dashou=order.jiedan_dashou_id
        ).values_list('tupian', flat=True)
        for rel_path in local_imgs:
            if rel_path:
                if rel_path.startswith('http'):
                    images.append(rel_path)
                else:
                    images.append(oss_domain + rel_path.lstrip('/'))

    # 2. 如果订单是跨平台且需要获取对方打手图片
    if order.is_cross and order.partner_club_id and order.partner_order_id:
        try:
            my_club = ClubConfig.objects.filter(is_self=1).first()
            if not my_club:
                logger.error("无法获取本俱乐部配置")
                return images, is_punished, punishment_info
            self_club_id = my_club.club_id

            # 获取合作关系

            club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=order.partner_club_id)
            partner_domain = club_rel.partner_domain.rstrip('/')

            # 构建对方接口URL
            url = partner_domain + '/houtai/kpthqtp'
            payload = {'order_id': order.partner_order_id, 'club_id': self_club_id}
            retries = 2
            for attempt in range(retries):
                try:
                    resp = requests.post(url, json=payload, timeout=5)
                    if resp.status_code == 200:
                        data = resp.json()
                        if data.get('code') == 0:
                            partner_data = data.get('data', {})
                            partner_imgs = partner_data.get('images', [])
                            # 对方返回的图片可能是相对路径，拼接对方域名
                            for img in partner_imgs:
                                if img.startswith('http'):
                                    images.append(img)
                                else:
                                    images.append(partner_domain + '/' + img.lstrip('/'))
                            punishment_info = partner_data.get('punishment', {})
                            is_punished = bool(punishment_info.get('cfliyou'))
                            break
                    else:
                        logger.warning(f"对方接口HTTP错误: {resp.status_code}")
                except Exception as e:
                    logger.warning(f"请求对方图片失败（重试{attempt+1}）: {e}")
        except Exception as e:
            logger.error(f"获取跨平台图片异常: {e}")

    return images, is_punished, punishment_info


def _get_images_for_orders(orders, oss_domain, max_workers=10):
    """
    并发获取多个订单的打手图片，返回字典 {dingdan_id: (images, is_punished, punishment_info)}
    """
    result = {}
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        future_to_order = {
            executor.submit(_fetch_partner_images_for_order, order, oss_domain): order.dingdan_id
            for order in orders
        }
        for future in as_completed(future_to_order):
            dingdan_id = future_to_order[future]
            try:
                images, is_punished, pun_info = future.result()
                result[dingdan_id] = (images, is_punished, pun_info)
            except Exception as e:
                logger.error(f"获取订单 {dingdan_id} 图片失败: {e}")
                result[dingdan_id] = ([], False, {})
    return result


class ShopOrderManageView(APIView):
    """
    店铺订单管理与统计接口

    URL: /shangdian/ddlbhq
    方法: POST
    权限: JWT认证 + 店铺身份验证 (verify_shop_permission)

    请求参数:
        - userId (必填): 当前店铺对应的小程序用户ID
        - action (可选): 值为 'statistics' 时返回统计数据；其他任意值或省略均返回订单列表

    统计模式 (action='statistics') 返回:
        types: 商品类型列表，每个类型包含:
            id, jieshao, tupian_url, order_count, status_counts
        status_counts: 各状态订单总数 (全局)
        total_orders: 订单总量

    列表模式 (默认) 支持筛选、分页、搜索，返回:
        list: 订单列表，每个订单包含:
            dingdan_id, zhuangtai, jine, dashou_fencheng, jiedan_dashou_id, dashou_liuyan,
            zhiding_id, shangpin_id, tupian, jieshao, beizhu, nicheng, leixing_id,
            create_time, update_time, pingtai_kuozhan (laoban_id, laoban_pingjia, dianpu_shouyi),
            dashou_images (绝对URL数组), is_punished, is_fadaned
        total: 总条数
        page: 当前页码
        page_size: 每页条数
    """
    permission_classes = [IsAuthenticated]

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

        # 验证店铺身份
        dianpu, error_response = verify_shop_permission(request, user_id)
        if error_response:
            return error_response

        action = request.data.get('action', '').strip().lower()
        if action == 'statistics':
            return self._handle_statistics(dianpu)
        else:
            return self._handle_order_list(request, dianpu)

    def _handle_statistics(self, dianpu):
        """统计逻辑：商品类型计数 + 各状态订单数 + 总量"""
        shop_id = dianpu.id

        # 1. 获取正常商品类型（审核状态=1）
        types_qs = ShangpinLeixing.objects.filter(shenhezhuangtai=1).order_by('-paixu', 'id')
        types_data = []
        type_ids = []
        for t in types_qs:
            types_data.append({
                'id': t.id,
                'jieshao': t.jieshao or '',
                'tupian_url': t.tupian_url or '',
                'order_count': 0,          # 之后填充
                'status_counts': {}        # 该类型下各状态计数
            })
            type_ids.append(t.id)

        # 2. 获取本店铺所有订单 ID
        order_ids = DingdanPingtai.objects.filter(
            dianpu_id=shop_id
        ).values_list('dingdan_id', flat=True)

        # 统计结构
        type_status_map = defaultdict(lambda: defaultdict(int))
        global_status_counts = defaultdict(int)
        total_orders = 0

        if order_ids:
            # 批量聚合统计
            aggregation = Dingdan.objects.filter(
                dingdan_id__in=order_ids
            ).values('leixing_id', 'zhuangtai').annotate(cnt=Count('id'))

            for row in aggregation:
                lx_id = row['leixing_id']
                zt = row['zhuangtai']
                cnt = row['cnt']
                if lx_id in type_ids:
                    type_status_map[lx_id][zt] += cnt
                global_status_counts[zt] += cnt
                total_orders += cnt

        # 填充统计数据回类型列表
        for t in types_data:
            tid = t['id']
            t['order_count'] = sum(type_status_map[tid].values())
            t['status_counts'] = dict(type_status_map[tid])

        return Response({
            'code': 200,
            'msg': '获取成功',
            'data': {
                'types': types_data,
                'status_counts': dict(global_status_counts),
                'total_orders': total_orders
            }
        })

    def _handle_order_list(self, request, dianpu):
        """列表逻辑：筛选 + 分页 + 搜索 + 图片并发获取"""
        shop_id = dianpu.id

        # ---------- 解析筛选参数 ----------
        leixing_id = request.data.get('leixing_id')
        zhuangtai = request.data.get('zhuangtai')
        dingdan_id = request.data.get('dingdan_id', '').strip()
        jiedan_dashou_id = request.data.get('jiedan_dashou_id', '').strip()
        nicheng = request.data.get('nicheng', '').strip()
        laoban_id = request.data.get('laoban_id', '').strip()
        jieshao = request.data.get('jieshao', '').strip()

        page = request.data.get('page', 1)
        page_size = request.data.get('page_size', 20)

        # ---------- 基础查询：店铺的订单（通过扩展表关联） ----------
        queryset = DingdanPingtai.objects.filter(
            dianpu_id=shop_id
        ).select_related('dingdan').order_by('-dingdan__create_time')

        # 应用筛选
        if leixing_id is not None:
            queryset = queryset.filter(dingdan__leixing_id=leixing_id)
        if zhuangtai is not None:
            queryset = queryset.filter(dingdan__zhuangtai=zhuangtai)
        if dingdan_id:
            queryset = queryset.filter(dingdan__dingdan_id__icontains=dingdan_id)
        if jiedan_dashou_id:
            queryset = queryset.filter(dingdan__jiedan_dashou_id=jiedan_dashou_id)
        if nicheng:
            queryset = queryset.filter(dingdan__nicheng__icontains=nicheng)
        if laoban_id:
            queryset = queryset.filter(laoban_id=laoban_id)       # 扩展表字段
        if jieshao:
            queryset = queryset.filter(dingdan__jieshao__icontains=jieshao)

        # ---------- 分页 ----------
        paginator = Paginator(queryset, page_size)
        try:
            page_obj = paginator.page(page)
        except EmptyPage:
            page_obj = paginator.page(1)

        # 收集当前页订单列表（主表 + 扩展表）
        orders_with_ext = []
        for pt_kuozhan in page_obj:
            orders_with_ext.append((pt_kuozhan.dingdan, pt_kuozhan))

        # 提取所有订单主表对象，用于并发获取打手图片
        dingdan_objs = [order for order, _ in orders_with_ext]
        oss_domain = get_oss_domain()
        images_map = _get_images_for_orders(dingdan_objs, oss_domain)

        # ---------- 组装返回数据 ----------
        result_list = []
        for order, pt_kuozhan in orders_with_ext:
            order_data = {
                'dingdan_id': order.dingdan_id,
                'zhuangtai': order.zhuangtai,
                'jine': float(order.jine) if order.jine else 0.00,
                'dashou_fencheng': float(order.dashou_fencheng) if order.dashou_fencheng else 0.00,
                'jiedan_dashou_id': order.jiedan_dashou_id or '',
                'dashou_liuyan': order.dashou_liuyan or '',  # 打手留言
                'zhiding_id': order.zhiding_id or '',
                'shangpin_id': order.shangpin_id or '',
                'tupian': order.tupian or '',
                'jieshao': order.jieshao or '',
                'beizhu': order.beizhu or '',
                'nicheng': order.nicheng or '',
                'leixing_id': order.leixing_id,
                'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else '',
                'update_time': order.update_time.strftime('%Y-%m-%d %H:%M:%S') if order.update_time else '',
                'pingtai_kuozhan': {
                    'laoban_id': pt_kuozhan.laoban_id or '',
                    'laoban_pingjia': pt_kuozhan.laoban_pingjia or '',
                    'dianpu_shouyi': float(pt_kuozhan.dianpu_shouyi) if pt_kuozhan.dianpu_shouyi else 0.00,
                },
                # 打手图片（已在后端拼接绝对 URL）
                'dashou_images': images_map.get(order.dingdan_id, ([], False, {}))[0],
                'is_punished': images_map.get(order.dingdan_id, ([], False, {}))[1],
                'is_fadaned': False,      # 暂不支持罚款状态，可根据需要扩展
            }
            result_list.append(order_data)

        return Response({
            'code': 200,
            'msg': '获取成功',
            'data': {
                'list': result_list,
                'total': paginator.count,
                'page': page_obj.number,
                'page_size': page_size
            }
        })





# ================================================================
# shangdian/views/refund_view.py
# 店铺退款接口 —— 基于跨平台退款接口二改，只处理平台订单，我方派单
# ================================================================
import json
import time
import random
import string
import hashlib
import hmac
import logging
import traceback
from decimal import Decimal
import xmltodict          # 解析微信退款回执
import requests
from django.db import transaction
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from shangdian.utils import verify_shop_permission          # 店铺身份验证
from dingdan.models import Dingdan, DingdanPingtai, Dashoutupian, Tuikuanjilu
from yonghu.models import UserMain
from shangdian.models import Dianpu

logger = logging.getLogger(__name__)


class ShopRefundView(APIView):
    """
    店铺订单退款接口
    URL: POST /shangdian/tk
    权限: JWT认证 + 店铺身份验证（只处理平台订单，我方派单）

    请求参数:
        userId (必填)   : 店铺所属用户的ID
        dingdan_id (必填): 订单ID
        reason (可选)   : 退款理由
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 获取参数
        user_id = request.data.get('userId', '').strip()
        dingdan_id = request.data.get('dingdan_id', '').strip()
        reason = request.data.get('reason', '').strip()      # 退款理由

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

        # 2. 店铺身份验证
        dianpu, error = verify_shop_permission(request, user_id)
        if error:
            return error

        shop_id = dianpu.id

        # 3. 查询订单，必须属于本店铺（通过扩展表关联）
        try:
            # 通过扩展表确认归属，并 select_related 获取订单主表
            pingtai_ext = DingdanPingtai.objects.select_related('dingdan').get(
                dianpu_id=shop_id,
                dingdan__dingdan_id=dingdan_id
            )
            order = pingtai_ext.dingdan
        except DingdanPingtai.DoesNotExist:
            logger.warning(f"订单 {dingdan_id} 不属于店铺 {shop_id} 或不存在")
            return Response({'code': 404, 'msg': '订单不存在或不属于本店铺'})

        # 4. 订单状态校验（只有这些状态可退款）
        allowed_statuses = [1, 7, 2, 8, 4]
        if order.zhuangtai not in allowed_statuses:
            logger.warning(f"订单状态不允许退款: {order.zhuangtai}")
            return Response({'code': 400, 'msg': '订单状态不允许退款'})

        # 5. 全部视为平台订单（我方派单），调用微信退款
        refund_result = self._call_wechat_refund(order)
        if refund_result['code'] != 0:
            return Response({'code': 400, 'msg': refund_result['msg']})

        # 6. 事务内更新订单、打手、记录、统计
        dashou_id = order.jiedan_dashou_id
        try:
            with transaction.atomic():
                # 更新订单状态为已退款
                order.zhuangtai = 5
                order.clkf = str(shop_id)       # 处理人记录为店铺ID
                if reason:
                    order.tkly = reason
                order.save()

                # 更新打手统计（退款量+1，状态置为空闲）
                if dashou_id:
                    try:
                        dashou_user = UserMain.objects.get(yonghuid=dashou_id)
                        dashou_profile = dashou_user.dashou_profile
                        dashou_profile.tuikuanliang += 1
                        dashou_profile.zhuangtai = 1
                        dashou_profile.save()
                    except (UserMain.DoesNotExist, ObjectDoesNotExist):
                        logger.warning(f"退款：打手 {dashou_id} 不存在，跳过状态更新")

                # 更新退款记录表
                self._update_refund_record(order, dashou_id, str(shop_id), reason)

                # 打手每日统计（退款动作）
                try:
                    from houtai.utils import update_dashou_daily_by_action
                    update_dashou_daily_by_action(
                        yonghuid=dashou_id,
                        amount=order.dashou_fencheng or Decimal('0.00'),
                        action=3   # 3 表示退款
                    )
                except Exception as e:
                    logger.error(f"打手每日统计更新失败: {e}")

                # 更新平台支出记录
                try:
                    from dingdan.utils import update_daily_payout
                    update_daily_payout(order.jine)
                except Exception as e:
                    logger.error(f"更新每日支出失败: {e}")

                # ========== 新增：商品和店铺每日统计 ==========
                try:
                    from shangdian.utils import update_dianpu_daily_stat
                    from shangpin.utils import update_shangpin_daily_stat
                    # 商品每日统计（只要有商品ID就执行）
                    if order.shangpin_id:
                        # 从扩展表获取已计算好的收益值（下单时已存储）
                        pingtai_shouyi = Decimal('0.00')
                        dianpu_shouyi = Decimal('0.00')
                        if hasattr(order, 'pingtai_kuozhan'):
                            pingtai_shouyi = order.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00')
                            dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')

                        update_shangpin_daily_stat(
                            shangpin_id=order.shangpin_id,
                            caozuo_leixing=3,  # 3 = 退款
                            dingdan_jiage=order.jine,
                            pingtai_shouyi=pingtai_shouyi,
                            dianpu_zongshouyi=dianpu_shouyi
                        )
                        logger.info(f"商品统计更新完成，商品ID: {order.shangpin_id}")

                    # 店铺每日统计（仅当存在店铺ID时执行）
                    dianpu_id = None
                    if hasattr(order, 'pingtai_kuozhan'):
                        dianpu_id = order.pingtai_kuozhan.dianpu_id
                    if dianpu_id:
                        dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
                        update_dianpu_daily_stat(
                            dianpu_id=dianpu_id,
                            caozuo_leixing=3,  # 3 = 退款
                            dingdan_jiage=order.jine,
                            dianpu_shouyi=dianpu_shouyi
                        )
                        logger.info(f"店铺统计更新完成，店铺ID: {dianpu_id}")
                except Exception as e:
                    logger.error(f"商品/店铺统计更新失败: {e}")
                    logger.error(traceback.format_exc())
                    # 统计失败不影响主流程
                # =============================================

        except Exception as e:
            logger.exception("退款事务执行失败")
            return Response({'code': 500, 'msg': '退款处理失败，请稍后重试'})

        # 7. 跨平台通知（如果存在对方平台订单ID，异步通知，不影响主流程）
        partner_order_id = order.partner_order_id
        partner_club_id = order.partner_club_id
        if partner_order_id and partner_club_id:
            try:
                self._notify_partner_refund(dingdan_id, partner_club_id, partner_order_id, reason, order.jine)
            except Exception as e:
                logger.error(f"跨平台退款通知异常: {e}")

        return Response({'code': 200, 'msg': '退款成功'})

    # ========== 微信退款 ==========
    def _call_wechat_refund(self, order):
        """调用微信支付退款接口"""
        appid = getattr(settings, 'WEIXIN_APPID', '')
        mch_id = getattr(settings, 'WEIXIN_MCHID', '')
        key = getattr(settings, 'WEIXIN_SHANGHUMIYAO', '')
        cert_path = getattr(settings, 'WEIXIN_CERT_PATH', '')
        key_path = getattr(settings, 'WEIXIN_KEY_PATH', '')

        if not all([appid, mch_id, key, cert_path, key_path]):
            return {'code': 500, 'msg': '微信支付配置不完整'}

        out_refund_no = self._generate_refund_no(order.dingdan_id)
        total_fee = int(float(order.jine or 0) * 100)
        refund_fee = total_fee

        params = {
            'appid': appid,
            'mch_id': mch_id,
            'nonce_str': self._generate_nonce_str(),
            'out_refund_no': out_refund_no,
            'total_fee': total_fee,
            'refund_fee': refund_fee,
            'refund_desc': '店铺退款',
        }
        if order.wechat_transaction_id:
            params['transaction_id'] = order.wechat_transaction_id
        else:
            params['out_trade_no'] = order.dingdan_id

        # 签名
        sorted_keys = sorted(params.keys())
        stringA = '&'.join([f"{k}={params[k]}" for k in sorted_keys])
        stringSignTemp = f"{stringA}&key={key}"
        sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
        params['sign'] = sign

        xml_data = self._dict_to_xml(params)
        url = 'https://api.mch.weixin.qq.com/secapi/pay/refund'

        try:
            response = requests.post(url, data=xml_data, cert=(cert_path, key_path), timeout=10)
            result = xmltodict.parse(response.content)['xml']
            logger.info(f"微信退款响应: {result}")
        except Exception as e:
            logger.error(f"微信退款请求异常: {e}", exc_info=True)
            return {'code': 500, 'msg': '微信退款请求异常'}

        if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
            return {'code': 0, 'msg': '退款成功'}
        else:
            err_msg = result.get('err_code_des', result.get('return_msg', '未知错误'))
            return {'code': 400, 'msg': err_msg}

    # ========== 退款记录 ==========
    def _update_refund_record(self, order, dashou_id, chuli_id, reason):
        """更新退款记录表"""
        try:
            refund_record, created = Tuikuanjilu.objects.get_or_create(
                dingdan_id=order.dingdan_id,
                defaults={
                    'dashouid': dashou_id or '',
                    'qingqiuid': '',
                    'chuliid': chuli_id,
                    'liyou': reason or '店铺退款',
                    'sqzhuangtai': 1,
                    'jine': order.jine or 0,
                    'dashou_fencheng': order.dashou_fencheng or 0,
                    'jieshao': order.jieshao or '',
                    'beizhu': '店铺退款',
                    'nicheng': order.nicheng or '',
                }
            )
            if not created:
                refund_record.sqzhuangtai = 1
                refund_record.chuliid = chuli_id
                if reason:
                    refund_record.liyou = reason
                refund_record.save()
        except Exception as e:
            logger.error(f"更新退款记录异常: {e}")

    # ========== 跨平台通知 ==========
    def _notify_partner_refund(self, dingdan_id, partner_club_id, partner_order_id, reason, amount):
        """
        通知对方平台我方已退款。
        此方法执行不影响主流程，任何异常仅记录日志。
        """
        try:
            from dingdan.models import ClubConfig, Club, CrossPlatformOrderData
            my_config = ClubConfig.objects.filter(is_self=1).first()
            if not my_config:
                logger.error("未配置我方俱乐部信息，无法通知对方")
                return
            self_club_id = my_config.club_id
            club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
            partner_domain = club_rel.partner_domain.rstrip('/')
            notify_url = f"{partner_domain}/houtai/dfddtk"

            payload = {
                'order_id': partner_order_id,
                'club_id': self_club_id,
                'status': 5,
                'reason': reason or '店铺退款'
            }
            resp = requests.post(notify_url, json=payload, timeout=3)
            if resp.status_code == 200:
                resp_json = resp.json()
                if resp_json.get('code') == 0:
                    # 更新跨平台扩展表状态
                    cross_data, _ = CrossPlatformOrderData.objects.get_or_create(
                        dingdan_id=dingdan_id,
                        defaults={
                            'partner_order_id': partner_order_id,
                            'partner_club_id': partner_club_id,
                            'partner_order_status': resp_json.get('data', {}).get('partner_status', 0),
                            'partner_dashou_id': resp_json.get('data', {}).get('partner_dashou_id', ''),
                            'partner_amount': amount or 0,
                        }
                    )
                    if not _:
                        cross_data.partner_order_status = resp_json.get('data', {}).get('partner_status', 0)
                        cross_data.save()
                else:
                    logger.warning(f"对方平台返回错误: {resp_json.get('msg')}")
            else:
                logger.warning(f"通知对方HTTP错误: {resp.status_code}")
        except Exception as e:
            logger.error(f"通知对方平台异常: {e}")

    # ========== 工具方法 ==========
    def _generate_nonce_str(self):
        return ''.join(random.choices(string.ascii_letters + string.digits, k=32))

    def _generate_refund_no(self, dingdan_id):
        timestamp = str(int(time.time()))
        rand = ''.join(random.choices('0123456789', k=4))
        return f"{dingdan_id}REF{timestamp}{rand}"

    def _dict_to_xml(self, data):
        xml = ['<xml>']
        for k, v in data.items():
            xml.append(f'<{k}>{v}</{k}>')
        xml.append('</xml>')
        return ''.join(xml)






class ShopForceCompleteView(APIView):
    """
    店铺强制结单接口
    URL: POST /shangdian/qzjd
    权限: JWT认证 + 店铺身份验证

    请求参数:
        userId    (必填) : 店铺所属用户的ID
        dingdan_id(必填) : 订单ID

    说明:
        - 只处理平台订单（fadan_pingtai == 1）
        - 订单必须属于当前店铺
        - 订单状态必须为 8（结算中）才能强制结单
        - 默认视为我方派单，根据是否有对方订单ID判断是否跨平台
        - 本地订单：给打手结算、更新打手余额和统计、更新店铺收益统计
        - 跨平台订单：仅通知对方平台，不处理打手
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # ========== 1. 获取参数 ==========
        user_id = request.data.get('userId', '').strip()
        dingdan_id = request.data.get('dingdan_id', '').strip()

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

        # ========== 2. 店铺身份验证 ==========
        dianpu, error = verify_shop_permission(request, user_id)
        if error:
            return error

        shop_id = dianpu.id

        # ========== 3. 查询订单并验证归属 ==========
        try:
            # 通过平台扩展表确认订单属于本店铺
            pingtai_ext = DingdanPingtai.objects.select_related('dingdan').get(
                dianpu_id=shop_id,
                dingdan__dingdan_id=dingdan_id
            )
            order = pingtai_ext.dingdan
        except DingdanPingtai.DoesNotExist:
            logger.warning(f"订单 {dingdan_id} 不属于店铺 {shop_id} 或不存在")
            return Response({'code': 404, 'msg': '订单不存在或不属于本店铺'})

        # ========== 4. 校验订单状态必须为 8（结算中） ==========
        if order.zhuangtai != 8:
            logger.warning(f"订单 {dingdan_id} 当前状态为 {order.zhuangtai}，不是结算中")
            return Response({'code': 400, 'msg': f'订单状态不是结算中，当前状态: {order.zhuangtai}'})

        # ========== 5. 判断是否为跨平台订单（有对方订单ID就认为是跨平台） ==========
        is_cross = order.is_cross and (order.partner_order_id or order.partner_club_id)
        dashou_id = order.jiedan_dashou_id
        dashou_fencheng = order.dashou_fencheng or Decimal('0.00')
        jine = order.jine or Decimal('0.00')

        try:
            with transaction.atomic():
                # 锁定订单行，防止并发
                order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id)

                # ============================
                # 情况1：跨平台订单（我方派单给对方平台打手）
                # ============================
                if is_cross:
                    # 只需更新我方订单状态，打手在对方平台，不处理我方打手余额
                    order.zhuangtai = 3              # 已完成
                    order.clkf = str(shop_id)        # 处理人记录为店铺ID
                    order.save()

                    # 店铺结算时，若涉及跨平台收益，这里是否要更新店铺统计？
                    # 根据业务需求，可在此处调用 update_dianpu_daily_stat 等
                    # 但通常跨平台订单的资金已在对方平台流转，店铺仅作为发单方，
                    # 收益可能已在其他环节计算，此处可根据需要添加统计。

                    # 通知对方平台（异步，不影响主流程）
                    if order.partner_club_id and order.partner_order_id:
                        transaction.on_commit(
                            lambda: self._notify_partner_order_complete(
                                order.partner_club_id,
                                order.partner_order_id,
                                shop_id
                            )
                        )

                # ============================
                # 情况2：本地订单（我方打手接单）
                # ============================
                else:
                    if not dashou_id:
                        return Response({'code': 400, 'msg': '订单无接单打手，无法结算'})
                    if dashou_fencheng < 0:
                        return Response({'code': 400, 'msg': '打手分成金额无效'})

                    # ---------- 更新打手余额和统计 ----------
                    try:
                        dashou_user = UserMain.objects.get(yonghuid=dashou_id)
                        dashou_profile = dashou_user.dashou_profile
                        dashou_profile.chengjiaozongliang += 1
                        dashou_profile.yue += dashou_fencheng
                        dashou_profile.zonge += dashou_fencheng
                        dashou_profile.jinrishouyi += dashou_fencheng
                        dashou_profile.jinyueshouyi += dashou_fencheng
                        dashou_profile.zhuangtai = 1       # 空闲
                        dashou_profile.save()
                    except (UserMain.DoesNotExist, ObjectDoesNotExist):
                        logger.warning(f"本地订单结算：打手 {dashou_id} 不存在，跳过打手更新")

                    # ---------- 更新订单状态 ----------
                    order.zhuangtai = 3
                    order.clkf = str(shop_id)
                    order.save()

                    # ---------- 打手每日统计（成交） ----------
                    try:
                        from houtai.utils import update_dashou_daily_by_action
                        update_dashou_daily_by_action(
                            yonghuid=dashou_id,
                            amount=dashou_fencheng,
                            action=2               # 2 表示成交（结算）
                        )
                    except Exception as e:
                        logger.error(f"打手每日统计更新失败: {e}")

                    # ---------- 店铺收益统计（结算） ----------
                    # 根据订单上的店铺收益 (从扩展表取) 更新店铺每日统计及余额
                    # 前提：订单创建时已存储了 dianpu_shouyi 到扩展表
                    if pingtai_ext.dianpu_shouyi:
                        try:
                            from shangpin.utils import update_shangpin_daily_stat
                            # 商品每日统计（只要有商品ID就执行）
                            if order.shangpin_id:
                                # 从扩展表获取已计算好的收益值（下单时已存储）
                                pingtai_shouyi = Decimal('0.00')
                                dianpu_shouyi = Decimal('0.00')
                                if hasattr(order, 'pingtai_kuozhan'):
                                    pingtai_shouyi = order.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00')
                                    dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')

                                update_shangpin_daily_stat(
                                    shangpin_id=order.shangpin_id,
                                    caozuo_leixing=2,  # 2 = 结算
                                    dingdan_jiage=order.jine,
                                    pingtai_shouyi=pingtai_shouyi,
                                    dianpu_zongshouyi=dianpu_shouyi
                                )
                                logger.info(f"商品统计更新完成，商品ID: {order.shangpin_id}")
                            from shangdian.utils import update_dianpu_daily_stat
                            update_dianpu_daily_stat(
                                dianpu_id=shop_id,
                                caozuo_leixing=2,      # 结算操作
                                dingdan_jiage=jine,
                                dianpu_shouyi=pingtai_ext.dianpu_shouyi,
                                # 如果涉及上级分红，可从扩展表取相应字段传入，此处略
                            )
                        except Exception as e:
                            logger.error(f"店铺每日统计更新失败: {e}")

                # ========== 6. 记录操作日志 ==========
                logger.info(f"强制结单成功，订单 {dingdan_id}，店铺 {shop_id}")

                return Response({'code': 200, 'msg': '强制结单成功'})

        except Dingdan.DoesNotExist:
            logger.warning(f"订单不存在: {dingdan_id}")
            return Response({'code': 404, 'msg': '订单不存在'})
        except Exception as e:
            logger.error(f"强制结单接口异常: {str(e)}", exc_info=True)
            return Response({'code': 500, 'msg': '服务器内部错误'})

    # ==================== 通知对方平台（异步） ====================
    def _notify_partner_order_complete(self, partner_club_id, partner_order_id, shop_id):
        """通知对方平台我方订单已完成（状态3）"""
        try:
            # 尝试获取对方域名等配置（与退款接口类似）


            my_config = ClubConfig.objects.filter(is_self=1).first()
            if not my_config:
                logger.error("未配置我方俱乐部信息，无法通知对方")
                return
            self_club_id = my_config.club_id

            club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
            partner_domain = club_rel.partner_domain.rstrip('/')
            notify_url = f"{partner_domain}/houtai/dfddtk"

            import requests
            payload = {
                'order_id': partner_order_id,
                'club_id': self_club_id,
                'status': 3
            }
            resp = requests.post(notify_url, json=payload, timeout=3)
            if resp.status_code == 200:
                resp_data = resp.json()
                if resp_data.get('code') == 0:
                    logger.info(f"通知对方订单已完成: {partner_order_id}")
                else:
                    logger.warning(f"对方平台返回错误: {resp_data.get('msg')}")
            else:
                logger.warning(f"通知对方HTTP错误: {resp.status_code}")
        except Exception as e:
            logger.error(f"通知对方平台异常: {e}")


# ================================================================
# shangdian/views/transfer_hall_view.py
# 店铺转移大厅接口 —— 严格二改，不添加任何原接口没有的逻辑
# ================================================================
# ================================================================
# shangdian/views/transfer_hall_view.py
# 店铺转移大厅接口 —— 添加本地打手退款统计
# ================================================================
import logging
import threading

from django.core.exceptions import ObjectDoesNotExist

from dingdan.models import Dingdan, DingdanPingtai, OrderDashouHistory

from houtai.utils import update_dashou_daily_by_action   # 打手每日统计（退款用）




class ShopTransferHallView(APIView):
    """
    店铺转移大厅接口
    URL: POST /shangdian/zydt
    权限: JWT认证 + 店铺身份验证

    请求参数:
        userId    (必填) : 店铺所属用户的ID
        dingdan_id(必填) : 订单ID

    说明:
        - 只处理平台订单，我方派单
        - 允许转移的状态：2,8,4
        - 决定是否处理本地打手：有对方订单ID或俱乐部ID（跨平台）则不处理本地打手，只通知对方；
          没有对方信息则处理本地打手（释放+退款统计）
        - 转移后清空对方平台信息，订单重回可接单状态
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # ---------- 1. 获取参数 ----------
        user_id = request.data.get('userId', '').strip()
        dingdan_id = request.data.get('dingdan_id', '').strip()

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

        # ---------- 2. 店铺身份验证 ----------
        dianpu, error = verify_shop_permission(request, user_id)
        if error:
            return error

        shop_id = dianpu.id

        # ---------- 3. 验证订单归属 ----------
        try:
            pingtai_ext = DingdanPingtai.objects.select_related('dingdan').get(
                dianpu_id=shop_id,
                dingdan__dingdan_id=dingdan_id
            )
            order = pingtai_ext.dingdan
        except DingdanPingtai.DoesNotExist:
            logger.warning(f"订单 {dingdan_id} 不属于店铺 {shop_id} 或不存在")
            return Response({'code': 404, 'msg': '订单不存在或不属于本店铺'})

        # ---------- 4. 状态校验 ----------
        if order.zhuangtai not in [2, 8, 4]:
            return Response({'code': 400, 'msg': f'订单状态不允许转移大厅，当前状态: {order.zhuangtai}'})

        # ---------- 5. 提取关键字段 ----------
        current_dashou_id = order.jiedan_dashou_id
        zhiding_id = order.zhiding_id
        partner_club_id = order.partner_club_id
        partner_order_id = order.partner_order_id
        dashou_fencheng = order.dashou_fencheng or 0   # 用于退款统计

        # 是否跨平台：只要有对方信息即认为是跨平台订单
        is_cross = bool(partner_club_id or partner_order_id)

        try:
            with transaction.atomic():
                # 锁定订单行
                order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id)

                # ---------- 6. 记录打手历史（原接口保留逻辑） ----------
                if current_dashou_id:
                    last_history = OrderDashouHistory.objects.filter(
                        dingdan_id=dingdan_id
                    ).order_by('-times').first()
                    next_times = (last_history.times + 1) if last_history else 1
                    OrderDashouHistory.objects.create(
                        dingdan_id=dingdan_id,
                        dashou_id=current_dashou_id,
                        times=next_times,
                        operator=str(shop_id)
                    )

                # ---------- 7. 释放本地打手 + 退款统计（仅当非跨平台时处理） ----------
                if not is_cross and current_dashou_id:
                    try:
                        dashou_profile = UserDashou.objects.get(user__yonghuid=current_dashou_id)
                        dashou_profile.zhuangtai = 1      # 设为空闲
                        dashou_profile.save()
                    except ObjectDoesNotExist:
                        logger.warning(f"打手 {current_dashou_id} 不存在，跳过状态更新")
                    else:
                        # 打手释放成功后，记录每日退款统计（转移视为退款）
                        try:
                            update_dashou_daily_by_action(
                                yonghuid=current_dashou_id,
                                amount=dashou_fencheng,
                                action=3              # 3 表示退款
                            )
                            logger.info(f"打手 {current_dashou_id} 转移大厅退款统计成功")
                        except Exception as e:
                            logger.error(f"打手每日统计失败: {e}")

                # ---------- 8. 清空对方平台信息并重置订单 ----------
                order.partner_club_id = None
                order.partner_order_id = None
                order.jiedan_dashou_id = None
                order.clkf = str(shop_id)

                # 根据是否有指定打手决定状态
                order.zhuangtai = 7 if zhiding_id else 1
                order.save()

                # ---------- 9. 异步通知对方平台（如果有对方信息） ----------
                if is_cross and partner_club_id and partner_order_id:
                    # 获取我方俱乐部ID
                    my_club_config = None
                    try:
                        from peizhi.models import ClubConfig
                        my_club_config = ClubConfig.objects.filter(is_self=1).first()
                    except Exception as e:
                        logger.error(f"获取俱乐部配置失败: {e}")

                    if my_club_config:
                        self_club_id = my_club_config.club_id
                        def notify():
                            try:
                                from dingdan.models import CrossPlatformOrderData
                                from peizhi.models import Club        # 确保导入
                                club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
                                partner_domain = club_rel.partner_domain.rstrip('/')
                                notify_url = f"{partner_domain}/houtai/kpttzzydt"
                                payload = {
                                    'order_id': partner_order_id,
                                    'club_id': self_club_id
                                }
                                resp = requests.post(notify_url, json=payload, timeout=3)
                                if resp.status_code == 200:
                                    resp_json = resp.json()
                                    if resp_json.get('code') == 0:
                                        # 更新跨平台订单数据（原接口逻辑）
                                        try:
                                            cross_data, _ = CrossPlatformOrderData.objects.get_or_create(
                                                dingdan_id=dingdan_id,
                                                defaults={
                                                    'partner_order_id': partner_order_id,
                                                    'partner_club_id': partner_club_id,
                                                    'partner_order_status': resp_json.get('data', {}).get('partner_status', 0),
                                                    'partner_dashou_id': '',
                                                    'partner_amount': order.jine or 0,
                                                }
                                            )
                                            if not _:
                                                cross_data.partner_order_status = resp_json.get('data', {}).get('partner_status', 0)
                                                cross_data.save(update_fields=['partner_order_status'])
                                            logger.info(f"转移大厅通知对方成功: {dingdan_id}")
                                        except Exception as e:
                                            logger.error(f"更新跨平台订单数据失败: {e}")
                                    else:
                                        logger.warning(f"对方平台返回错误: {resp_json.get('msg')}")
                                else:
                                    logger.warning(f"对方接口HTTP错误: {resp.status_code}")
                            except Club.DoesNotExist:
                                logger.error(f"未找到对方俱乐部配置: partner_club_id={partner_club_id}")
                            except Exception as e:
                                logger.error(f"转移大厅通知对方异常: {e}")

                        threading.Thread(target=notify).start()

                logger.info(f"转移大厅成功，订单 {dingdan_id}")
                return Response({'code': 200, 'msg': '转移大厅成功'})

        except Dingdan.DoesNotExist:
            return Response({'code': 404, 'msg': '订单不存在'})
        except Exception as e:
            logger.error(f"转移大厅接口异常: {str(e)}", exc_info=True)
            return Response({'code': 500, 'msg': '服务器内部错误'})





# ================================================================
# shangdian/views/fine_apply_view.py
# 店铺罚款申请接口 —— 二改自 FineApplyView（客服罚款）
# ================================================================

from dingdan.models import Dingdan, DingdanPingtai, Fadan       # 订单、罚款记录


class ShopFineApplyView(APIView):
    """
    店铺罚款申请接口
    URL: POST /shangdian/fksq
    权限: JWT认证 + 店铺身份验证

    请求参数:
        userId              (必填) : 店铺所属用户的ID（用于身份验证）
        dingdan_id          (必填) : 订单ID
        reason              (必填) : 罚款原因
        amount              (必填) : 罚款金额（元）
        yingxiang_qiangdan  (可选) : 是否影响抢单，1=是，0=否，默认1

    说明:
        - 仅处理平台订单，需属于当前店铺
        - 本地/对方派单处理逻辑与客服接口一致：
            无对方订单ID 或 对方派单 → 本地生成罚单
            有对方订单ID且我方派单 → 跨平台通知对方，本地也记录（标记对方俱乐部ID）
        - 申请人：店铺ID，身份码固定为4（店铺管理员）
        - 被处罚人：接单打手，身份码固定为1
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # ---------- 1. 提取参数 ----------
        user_id = request.data.get('userId', '').strip()               # 店铺用户ID
        dingdan_id = request.data.get('dingdan_id', '').strip()
        chufaliyou = request.data.get('reason', '').strip()            # 罚款原因（对应前端的reason）
        fakuanjine = request.data.get('amount', 0)                     # 罚款金额（对应前端的amount）
        yingxiang_qiangdan = request.data.get('yingxiang_qiangdan', 1) # 影响抢单

        if not all([user_id, dingdan_id, chufaliyou, fakuanjine]):
            return Response({'code': 400, 'msg': '参数不完整'})

        try:
            # ---------- 2. 店铺身份验证 ----------
            dianpu, error = verify_shop_permission(request, user_id)
            if error:
                return error
            shop_id = dianpu.id   # 店铺ID，作为罚款申请人

            # ---------- 3. 验证订单归属 ----------
            try:
                pingtai_ext = DingdanPingtai.objects.select_related('dingdan').get(
                    dianpu_id=shop_id,
                    dingdan__dingdan_id=dingdan_id
                )
                order = pingtai_ext.dingdan
            except DingdanPingtai.DoesNotExist:
                logger.warning(f"罚款申请：订单 {dingdan_id} 不属于店铺 {shop_id}")
                return Response({'code': 404, 'msg': '订单不存在或不属于本店铺'})

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

            # ---------- 5. 重复罚款检查 ----------
            if Fadan.objects.filter(beichufa_id=dashou_id, guanliandingdan_id=dingdan_id).exists():
                return Response({'code': 400, 'msg': '该打手已被罚款过'})

            # ---------- 6. 跨平台相关字段 ----------
            partner_order_id = order.partner_order_id or ''
            partner_club_id = order.partner_club_id or ''
            dispatch_type = order.dispatch_type or 0          # 1=我方派单，2=对方派单

            # 本地创建罚单的通用函数
            def create_local_fadan(partner_club=''):
                Fadan.objects.create(
                    beichufa_id=dashou_id,
                    shenqing_chufa=str(shop_id),             # 申请人记为店铺ID
                    shenfen=1,                                # 被处罚人身份：1 打手
                    chufaliyou=chufaliyou,
                    fakuanjine=fakuanjine,
                    guanliandingdan_id=dingdan_id,
                    zhuangtai=1,                              # 待缴纳
                    yingxiang_qiangdan=yingxiang_qiangdan,
                    shenqingren_shenfen=4,                    # 申请人身份：4 店铺管理员
                    partner_club_id=partner_club,
                )

            # ---------- 7. 判断处罚流向 ----------
            # 情况A：没有对方订单ID，或对方派单 → 本地处罚
            if not partner_order_id or dispatch_type == 2:
                with transaction.atomic():
                    create_local_fadan()
                logger.info(f"本地罚款成功: 订单 {dingdan_id}, 打手 {dashou_id}, 金额 {fakuanjine}")
                return Response({'code': 200, 'msg': '罚款已生成'})

            # ---------- 8. 情况B：有对方订单ID且我方派单 → 跨平台通知对方 ----------
            # 获取我方俱乐部ID
            try:
                my_club = ClubConfig.objects.filter(is_self=1).first()
                if not my_club:
                    return Response({'code': 500, 'msg': '系统配置错误，缺少本俱乐部信息'})
                self_club_id = my_club.club_id
                club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
                partner_domain = club_rel.partner_domain
            except Club.DoesNotExist:
                return Response({'code': 500, 'msg': '未找到对方平台配置，无法通知'})
            except Exception as e:
                logger.error(f"获取对方平台信息失败: {e}")
                return Response({'code': 500, 'msg': '获取对方平台信息失败'})

            # 构造通知 payload
            url = partner_domain.rstrip('/') + '/houtai/kffkdssq_notify'
            payload = {
                'order_id': partner_order_id,               # 对方平台的订单ID
                'club_id': self_club_id,                    # 我方俱乐部ID
                'chufaliyou': chufaliyou,
                'fakuanjine': fakuanjine,
                'yingxiang_qiangdan': yingxiang_qiangdan,
                'shenqingren_shenfen': 4,                   # 店铺管理员身份
                'operator': str(shop_id),
            }

            # 带重试的通知
            success = False
            for _ in range(2):
                try:
                    resp = requests.post(url, json=payload, timeout=3)
                    if resp.status_code == 200 and resp.json().get('code') == 0:
                        success = True
                        break
                except Exception:
                    continue

            if not success:
                return Response({'code': 500, 'msg': '通知对方平台失败，请稍后重试'})

            # 通知成功后，本地也记录一笔（标记为跨平台）
            with transaction.atomic():
                create_local_fadan(partner_club=partner_club_id)

            logger.info(f"跨平台罚款通知成功: 订单 {dingdan_id}")
            return Response({'code': 200, 'msg': '罚款已提交至对方平台'})

        except Exception as e:
            logger.error(f"店铺罚款申请异常: {traceback.format_exc()}")
            return Response({'code': 500, 'msg': '服务器内部错误'})