"""shop.views.shop_member_views - 会员列表与下级店铺管理视图.""" # ==================== 标准库 ==================== import io import json import time import random import string import hmac import hashlib import requests import logging import traceback import calendar import threading import xmltodict from decimal import Decimal from collections import defaultdict from concurrent.futures import ThreadPoolExecutor, as_completed from django.conf import settings from django.db import models, transaction, IntegrityError from django.db.models import F, Sum, Count, Q, Prefetch from gvsdsdk.fluent import db, func, FQ from django.core.cache import cache from django.core.paginator import Paginator, EmptyPage from django.core.exceptions import ObjectDoesNotExist from django.utils import timezone from django.http import HttpResponse from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework_simplejwt.tokens import RefreshToken from utils.oss_utils import upload_to_oss, delete_from_oss, get_oss_client from utils.money import yuan_to_fen from ..utils import verify_shop_permission from shop.utils import verify_shop_permission, update_dianpu_daily_stat from products.utils import update_shangpin_daily_stat from orders.utils import update_daily_payout from backend.utils import update_dashou_daily_by_action from ..models import YonghuPingzheng, Dianpu, DianpuShouzhiMeiriTongji from users.models import UserDashou, UserShangjia, UserGuanshi, UserZuzhang from users.business_models import User from shop.models import YonghuDianpuBangding, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi from products.models import ShangpinLeixing, ShangpinZhuanqu, Shangpin, Huiyuan from orders.models import ( Order, PlatformOrderExt, PlayerDeliveryImage, RefundRecord, OrderPlayerHistory, Penalty ) logger = logging.getLogger(__name__) 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.query.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 }) 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.query.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('-CreateTime') # 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), 'CreateTime': shop.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if shop.CreateTime else '', 'UpdateTime': shop.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if shop.UpdateTime 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 User.query.filter(UserUID=user_uid).exists(): return Response({'code': 404, 'msg': '对应的小程序用户不存在'}) # 检查账号是否已占用 if YonghuPingzheng.query.filter(zhanghao=zhanghao).exists(): return Response({'code': 400, 'msg': '登录账号已存在'}) if YonghuPingzheng.query.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.query.create( yonghu=user_uid, zhanghao=zhanghao, mima=mima, is_active=True ) # 创建店铺,关联上级(俱乐部与上级一致) dianpu = Dianpu.query.create( pingzheng=pingzheng, dianpu_mingcheng=dianpu_mingcheng, lianxi_dianhua=lianxi_dianhua or '', yaoqing_dianpu_id=parent_dianpu.id, # 关键:绑定上级 fenhong_choucheng_feilv=fenhong_feilv, club_id=getattr(parent_dianpu, 'club_id', None) or 'xq', ) 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', 'UpdateTime']) 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. 订单列表:支持筛选、分页、返回订单详情及打手图片(含跨平台图片拉取) """ # ---------- 图片域名 ---------- """ 店铺订单管理接口 ================= 功能1:获取统计数据(action='statistics') - 每个商品类型的订单总数(order_count) - 每个商品类型下各状态的订单数量(status_counts) - 全局各状态的订单总数(status_counts) - 店铺订单总量(total_orders) 功能2:获取订单列表(默认,不带action参数时执行) - 支持商品类型、订单状态、订单ID、打手ID、游戏昵称、老板ID、订单介绍筛选 - 支持分页、自定义每页大小 - 返回打手提交图片(绝对URL)、老板信息、店铺收益等 接口地址:POST /shangdian/ddlbhq 权限:JWT认证 + verify_shop_permission 验证店铺身份 """