Files
Django/products/views/product_query.py
XingQue 37bc8f960a feat: 总会员(包含式)模型、迁移、抢单校验与后台配置
- Huiyuan.is_bundle + club_huiyuan_bundle_include 表
- 购买/到期逻辑不变,抢单时总会员覆盖子会员类型
- dshyhq/clumber 返回 is_bundle 与 included_huiyuan_ids

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 18:12:57 +08:00

985 lines
37 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""商品/会员/分红 数据查询视图。
原 products/views.py 查询类视图聚合:商品数据获取、商品详情、打手会员列表、
管事分红记录、管理员全量数据获取、会员类型/记录查询、组长分红记录。
"""
# ==================== 标准库 ====================
import os
import json
import time
import random
import string
import uuid
import hashlib
import requests
import logging
import decimal
from datetime import timedelta
from decimal import Decimal, InvalidOperation, ROUND_CEILING
import defusedxml.ElementTree as ET
# ==================== Django 内置 ====================
from django.conf import settings
from django.db import transaction, connection
from django.db.models import Q
from gvsdsdk.fluent import db, func, FQ
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from django.utils import timezone
# ==================== DRF 框架 ====================
from rest_framework.views import APIView, View
from rest_framework.response import Response
from rest_framework import permissions, status
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from rest_framework.throttling import AnonRateThrottle
# ==================== 项目工具函数 ====================
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
from utils.money import format_yuan, quantize_yuan, yuan_to_fen
from backend.utils import (
update_guanshi_daily_by_action,
update_guanshi_xufei_daily,
update_zuzhang_daily_by_action
)
from users.fadan_fenhong_utils import process_fadan_fenhong
# ==================== 各App模型 ====================
from ..models import (
ShangpinLeixing, ShangpinZhuanqu, Shangpin, Huiyuan,
Czjilu, Gsfenhong, Huiyuangoumai, DuociFenhong
)
from users.models import (
AdminProfile, UserDashou, UserGuanshi, UserZuzhang, UserShangjia
)
from users.business_models import User
from orders.models import CommissionRate
from config.models import Gonggao, Lunbo
from orders.models import Penalty # 用于罚款类型
from jituan.services.club_config import (
get_commission_rate,
get_commission_rate_object,
get_huiyuan_price,
get_huiyuan_fenchong,
)
from jituan.services.club_context import resolve_effective_club_id
from jituan.services.club_write import resolve_club_id_for_write
from jituan.services.club_user import get_payment_openid
from jituan.services.club_penalty import resolve_gsfenhong_club_id
import traceback
# ==================== 日志实例 ====================
logger = logging.getLogger(__name__)
class ShangpinHuoquView(APIView):
"""
商品数据获取接口
一次性返回商品类型、商品专区、商品列表
"""
# 应用节流(限制频率)
throttle_classes = [AnonRateThrottle]
permission_classes = [AllowAny]
def post(self, request):
"""
处理POST请求返回所有商品相关数据
"""
try:
logger.info("收到商品数据获取请求")
from jituan.services.club_context import resolve_effective_club_id
from jituan.services.club_shangpin_leixing import build_club_leixing_list_for_api
club_id = resolve_effective_club_id(request, getattr(request, 'user', None))
leixing_rows = build_club_leixing_list_for_api(club_id)
shangpinleixing_data = [
{
'id': row['id'],
'jieshao': row['jieshao'],
'tupian_url': row['tupian_url'],
'paixu': row['paixu'],
}
for row in leixing_rows
]
# 2. 查询商品专区(只查询可见类型下的专区,并排序)
# 先获取可见的类型ID列表
visible_leixing_ids = [leixing['id'] for leixing in shangpinleixing_data]
if visible_leixing_ids:
zhuanqu_queryset = ShangpinZhuanqu.query.filter(
leixing_id__in=visible_leixing_ids,
shenhezhuangtai=1 # 🔴【添加】只筛选审核状态为2的专区
).order_by('-paixu', 'id') # 先按paixu降序再按id升序
else:
zhuanqu_queryset = ShangpinZhuanqu.query.none()
shangpinzhuanqu_data = []
for zhuanqu in zhuanqu_queryset:
shangpinzhuanqu_data.append({
"id": zhuanqu.id,
"mingzi": zhuanqu.mingzi,
"leixing_id": zhuanqu.leixing_id,
"paixu": zhuanqu.paixu
})
# 3. 查询商品列表(只查询可见专区下的商品,并排序)
# 先获取可见的专区ID列表
visible_zhuanqu_ids = [zhuanqu['id'] for zhuanqu in shangpinzhuanqu_data]
if visible_zhuanqu_ids:
# 注意这里使用values()只获取指定字段,提高查询效率
shangpin_queryset = Shangpin.query.filter(
zhuanqu_id__in=visible_zhuanqu_ids,
shenhezhuangtai=1 # 🔴【添加】只筛选审核状态为2的商品
).values(
'id', 'biaoqian', 'jiage', 'leixing_id',
'zhuanqu_id', 'tupian_url', 'duiwai_xiaoliang', 'paixu'
).order_by('-paixu', 'id') # 先按paixu降序再按id升序
# 将QuerySet转换为列表
shangpinliebiao_data = list(shangpin_queryset)
else:
shangpinliebiao_data = []
# 4. 构建返回数据
response_data = {
"shangpinleixing": shangpinleixing_data,
"shangpinzhuanqu": shangpinzhuanqu_data,
"shangpinliebiao": shangpinliebiao_data
}
logger.info(f"返回数据:类型{len(shangpinleixing_data)}个,"
f"专区{len(shangpinzhuanqu_data)}个,"
f"商品{len(shangpinliebiao_data)}")
# 5. 返回响应
return Response(response_data, status=status.HTTP_200_OK)
except Exception as e:
# 记录异常
logger.error(f"获取商品数据时发生错误:{str(e)}", exc_info=True)
# 返回错误响应
error_data = {
"error": "服务器内部错误",
"detail": "获取商品数据失败,请稍后重试"
}
return Response(error_data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class ShangpinXiangqingHuoquView(APIView):
"""
商品详情接口 - 极简版
假设所有字段都存在,直接返回
"""
throttle_classes = [AnonRateThrottle]
permission_classes = [AllowAny]
def post(self, request):
try:
shangpin_id = request.data.get('shangpin_id')
if not shangpin_id:
return Response({
"code": 1,
"msg": "商品ID不能为空",
"data": None
}, status=400)
# 直接查询,数据量小,性能没问题
shangpin = Shangpin.query.filter(id=shangpin_id).first()
if not shangpin:
return Response({
"code": 2,
"msg": "商品不存在",
"data": None
}, status=404)
# 直接构建数据,不处理空值
data = {
"tupian": shangpin.tupian_url,
"biaoti": shangpin.biaoqian,
"jiage": shangpin.jiage,
"xiangqing": shangpin.jieshao,
"xiadanxuzhi": shangpin.xiadan_xuzhi,
"guize": shangpin.guize_tupian,
"xiaoliang": shangpin.duiwai_xiaoliang,
"kucun": shangpin.kucun,
}
return Response({
"code": 0,
"msg": "success",
"data": data
})
except Exception as e:
logger.error(f"接口异常: {e}")
return Response({
"code": 500,
"msg": "服务器错误",
"data": None
}, status=500)
class DashouHuiyuanList(APIView):
"""
获取打手会员商品列表
路径:/shangpin/dshyhq
请求方式POST
权限JWT认证
返回:会员商品列表,字段对应前端需求
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 1. 检查用户是否是打手(通过反向查询)
try:
dashou = request.user.DashouProfile
except UserDashou.DoesNotExist:
logger.warning(f"用户{request.user.UserUID}不是打手,尝试访问打手接口")
return Response({
'code': 400,
'message': '用户不是打手,无法访问此接口'
}, status=status.HTTP_400_BAD_REQUEST)
# 2. 检查打手账号状态
if dashou.zhanghaozhuangtai != 1:
logger.warning(f"打手{request.user.UserUID}账号状态异常: {dashou.zhanghaozhuangtai}")
return Response({
'code': 400,
'message': '账号状态异常,无法购买会员'
}, status=status.HTTP_400_BAD_REQUEST)
# 3. 仅返回本俱乐部可售会员club_huiyuan_price 已配置且启用)
from jituan.services.member_recharge import club_huiyuan_sellable, can_purchase_trial
club_id = resolve_effective_club_id(request, request.user)
huiyuan_queryset = Huiyuan.query.all().only(
'huiyuan_id', 'jieshao', 'jtjieshao', 'jiage',
).order_by('jiage')
from jituan.models import ClubHuiyuanPrice
club_rows = {
r.huiyuan_id: r
for r in ClubHuiyuanPrice.query.filter(club_id=club_id, is_enabled=True)
}
huiyuan_list = []
yonghuid = request.user.UserUID
for huiyuan in huiyuan_queryset:
sellable, club_price, formal_days = club_huiyuan_sellable(
club_id, huiyuan.huiyuan_id, is_trial=False,
)
if not sellable or not club_price:
continue
trial_ok, _ = can_purchase_trial(yonghuid, huiyuan.huiyuan_id, club_id)
trial_sellable, trial_price, trial_days = club_huiyuan_sellable(
club_id, huiyuan.huiyuan_id, is_trial=True,
)
club_row = club_rows.get(huiyuan.huiyuan_id)
card_image = (club_row.card_image or '').strip() if club_row else ''
card_bg = (club_row.card_bg or '').strip() if club_row else ''
is_bundle = bool(getattr(huiyuan, 'is_bundle', False))
huiyuan_list.append({
'id': huiyuan.huiyuan_id,
'mingzi': huiyuan.jieshao,
'jiage': format_yuan(club_price),
'formal_days': formal_days,
'jieshao': huiyuan.jtjieshao,
'is_bundle': is_bundle,
'trial_enabled': False if is_bundle else bool(trial_sellable),
'trial_price': '0.00' if is_bundle else (format_yuan(trial_price) if trial_sellable else '0.00'),
'trial_days': 0 if is_bundle else (trial_days if trial_sellable else 0),
'can_buy_trial': False if is_bundle else bool(trial_ok and trial_sellable),
'card_image': card_image,
'card_bg': card_bg,
})
# 5. 返回数据
return Response({
'code': 200,
'message': '获取会员列表成功',
'data': huiyuan_list,
'club_id': club_id,
}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"获取会员列表接口异常: {str(e)}", exc_info=True)
return Response({
'code': 500,
'message': '服务器内部错误,请稍后重试'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class GuanshiFenhongListAPI(APIView):
"""
管事分红记录查询接口
权限需要JWT认证
请求方式POST
分页参数page, page_size
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 获取当前管事用户ID
yonghuid = request.user.UserUID
# 获取分页参数
page = int(request.data.get('page', 1))
page_size = int(request.data.get('page_size', 50))
if page < 1:
page = 1
if page_size < 1 or page_size > 100: # 限制最大100条
page_size = 50
# 计算偏移量
offset = (page - 1) * page_size
# 查询条件管事ID匹配
query = Q(guanshi=yonghuid)
# 先查询总数(使用分页查询优化)
# 查询总记录数
zong_tiaoshu = Gsfenhong.query.filter(query).count()
# 查询去重后的打手数量
chongzhi_dashou_shuliang = Gsfenhong.query.filter(query).values('dashouid').distinct().count()
# 查询当前页数据,按创建时间倒序排列
fenhong_list = Gsfenhong.query.filter(query) \
.order_by('-CreateTime') \
.values('dashouid', 'avatar', 'nicheng', 'fenhong', 'CreateTime')[offset:offset + page_size]
# 格式化数据
formatted_list = []
for item in fenhong_list:
formatted_list.append({
'yonghuid': item['dashouid'], # 打手ID
'avatar': item['avatar'] or '', # 打手头像
'nicheng': item['nicheng'] or '未知用户', # 打手昵称
'fenhong': str(item['fenhong']), # 分红金额
'CreateTime': item['CreateTime'].strftime('%Y-%m-%d %H:%M:%S') if item['CreateTime'] else ''
})
# 返回数据
return Response({
'code': 200,
'msg': '成功',
'data': {
'list': formatted_list,
'total': zong_tiaoshu, # 总记录数
'chongzhi_dashou_shuliang': chongzhi_dashou_shuliang, # 充值打手数量(去重)
'page': page,
'page_size': page_size,
'has_more': len(formatted_list) == page_size # 是否还有更多数据
}
})
except Exception as e:
logger.error(f"查询管事分红记录失败: {str(e)}", exc_info=True)
return Response({
'code': 500,
'msg': f'服务器错误: {str(e)}',
'data': {}
})
# views.py - 商品模块视图
class AdGetAllDataView(APIView):
"""
获取商品类型、商品专区、会员数据的接口
接口地址:/shangpin/adhqlx
请求方法POST
权限要求JWT Token认证 + 管理员权限
"""
# 需要JWT Token认证
permission_classes = [IsAuthenticated]
def post(self, request):
"""
处理POST请求返回商品相关数据
"""
try:
# 1. 获取请求数据
zhanghao = request.data.get('zhanghao')
if not zhanghao:
logger.warning(f"管理员接口:未接收到账号参数,请求用户:{request.user.UserUID}")
return Response({
'code': 400,
'message': '缺少账号参数',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 2. 获取当前登录用户
current_user = request.user
# 3. 验证用户类型是否为管理员
if current_user.UserType != 'admin':
logger.warning(
f"权限验证失败:用户 {current_user.UserUID} 的用户类型为 {current_user.UserType},非管理员")
return Response({
'code': 401,
'message': '无管理员权限',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
# 4. 验证手机号与传递的账号是否一致
if current_user.Phone != zhanghao:
logger.warning(
f"账号验证失败:用户 {current_user.UserUID} 的phone={current_user.Phone}传递的zhanghao={zhanghao}")
return Response({
'code': 401,
'message': '账号验证失败',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
# 5. 验证管理员扩展表是否存在
try:
admin_profile = current_user.AdminProfile
logger.info(f"管理员验证通过:用户 {current_user.UserUID},手机号 {current_user.Phone}")
except AdminProfile.DoesNotExist:
logger.error(
f"管理员扩展表不存在:用户 {current_user.UserUID} 虽然user_type=admin但没有AdminProfile记录")
return Response({
'code': 401,
'message': '管理员信息不完整',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
# 6. 查询商品类型数据
try:
# 使用select_related或prefetch_related优化查询如果需要关联查询
# 这里直接查询所有字段,然后提取需要的字段
shangpinleixing_queryset = ShangpinLeixing.query.all()
# 转换为前端需要的格式
shangpinleixing_list = []
for leixing in shangpinleixing_queryset:
leixing_data = {
'id': leixing.id,
'yaoqiuleixing': leixing.yaoqiuleixing if leixing.yaoqiuleixing is not None else None,
'huiyuan_id': leixing.huiyuan_id if leixing.huiyuan_id else None,
'yongjin': str(leixing.yongjin) if leixing.yongjin is not None else None,
'jieshao': leixing.jieshao if leixing.jieshao else '',
'tupian_url': leixing.tupian_url if leixing.tupian_url else ''
}
shangpinleixing_list.append(leixing_data)
#logger.info(f"成功查询到 {len(shangpinleixing_list)} 条商品类型数据")
except Exception as e:
logger.error(f"查询商品类型数据时出错:{str(e)}")
shangpinleixing_list = []
# 7. 查询商品专区数据
try:
# 查询所有商品专区
zhuanqu_queryset = ShangpinZhuanqu.query.all()
# 转换为前端需要的格式
zhuanqu_list = []
for zhuanqu in zhuanqu_queryset:
zhuanqu_data = {
'id': zhuanqu.id,
'mingzi': zhuanqu.mingzi if zhuanqu.mingzi else '',
'leixing_id': zhuanqu.leixing_id
}
zhuanqu_list.append(zhuanqu_data)
# logger.info(f"成功查询到 {len(zhuanqu_list)} 条商品专区数据")
except Exception as e:
logger.error(f"查询商品专区数据时出错:{str(e)}")
zhuanqu_list = []
# 8. 查询会员数据
try:
# 查询所有会员
huiyuan_queryset = Huiyuan.query.all()
# 转换为前端需要的格式
huiyuan_list = []
for huiyuan in huiyuan_queryset:
huiyuan_data = {
'huiyuan_id': huiyuan.huiyuan_id,
'jieshao': huiyuan.jieshao,
'jiage': str(huiyuan.jiage) # Decimal转为字符串
}
huiyuan_list.append(huiyuan_data)
# logger.info(f"成功查询到 {len(huiyuan_list)} 条会员数据")
except Exception as e:
logger.error(f"查询会员数据时出错:{str(e)}")
huiyuan_list = []
# 9. 检查会员列表是否为空
if not huiyuan_list:
logger.warning("会员列表为空,可能会影响商品发布功能")
# 10. 组织返回数据
response_data = {
'code': 0,
'message': '获取数据成功',
'data': {
'shangpinleixingList': shangpinleixing_list,
'zhuanquList': zhuanqu_list,
'huiyuanList': huiyuan_list
}
}
return Response(response_data, status=status.HTTP_200_OK)
except Exception as e:
# 捕获未预期的异常
logger.error(f"接口处理异常:{str(e)}", exc_info=True)
return Response({
'code': 500,
'message': f'服务器内部错误:{str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# views.py - shangpin模块
class HuiyuanTypeListView(APIView):
"""
获取会员类型列表接口
路径:/shangpin/adhyjllxcx
方法POST
权限JWT Token + 管理员验证
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 🔥 1. 身份验证
user = request.user
# 检查是否是管理员
if not hasattr(user, 'AdminProfile'):
return Response({
'code': 401,
'message': '无权限访问',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
# 🔥 2. 获取所有会员类型
huiyuan_list = Huiyuan.query.all().order_by('CreateTime')
# 🔥 3. 构建返回数据
huiyuan_data = []
for huiyuan in huiyuan_list:
huiyuan_data.append({
'id': huiyuan.huiyuan_id, # 会员ID
'jieshao': huiyuan.jieshao, # 会员介绍
'jiage': str(huiyuan.jiage), # 价格
'goumai_cishu': huiyuan.goumai_cishu, # 购买次数
'CreateTime': huiyuan.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if huiyuan.CreateTime else None
})
return Response({
'code': 0,
'message': '获取成功',
'data': huiyuan_data
}, status=status.HTTP_200_OK)
except Exception as e:
print(f"获取会员类型列表失败: {str(e)}")
return Response({
'code': 500,
'message': '系统错误',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class HuiyuanRecordListView(APIView):
"""
获取会员记录列表接口
路径:/shangpin/adhyjlcxhq
方法POST
权限JWT Token + 管理员验证
参数:
- zhanghao: 管理员账号
- huiyuan_type: 会员类型ID
- jiluzhuangtai: 记录状态1=未过期, 2=已过期)
- page: 页码
- pagesize: 每页条数
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 🔥 1. 身份验证
user = request.user
# 检查是否是管理员
if not hasattr(user, 'AdminProfile'):
return Response({
'code': 401,
'message': '无权限访问',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
# 🔥 2. 获取参数
zhanghao = request.data.get('zhanghao', '').strip()
huiyuan_type = request.data.get('huiyuan_type', '').strip()
jiluzhuangtai = request.data.get('jiluzhuangtai', 1) # 默认未过期
page = int(request.data.get('page', 1))
pagesize = int(request.data.get('pagesize', 5))
# 🔥 3. 参数验证
if not zhanghao:
return Response({
'code': 400,
'message': '管理员账号不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
if not huiyuan_type:
return Response({
'code': 400,
'message': '会员类型不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 🔥 4. 检查会员类型是否存在
huiyuan_obj = Huiyuan.query.filter(huiyuan_id=huiyuan_type).first()
if not huiyuan_obj:
return Response({
'code': 404,
'message': '会员类型不存在',
'data': None
}, status=status.HTTP_404_NOT_FOUND)
# 🔥 5. 构建基础查询一次性获取所有符合条件的记录ID
now = timezone.now()
# 🔥 使用原生SQL优化查询避免N+1问题
with connection.cursor() as cursor:
# 先查询符合条件的总记录数
count_sql = """
SELECT COUNT(*)
FROM huiyuangoumai hg
WHERE hg.huiyuan_id = %s
"""
params = [huiyuan_type]
if jiluzhuangtai == 1: # 未过期
count_sql += " AND hg.daoqi_time > %s"
params.append(now)
else: # 已过期
count_sql += " AND hg.daoqi_time <= %s"
params.append(now)
cursor.execute(count_sql, params)
total_count = cursor.fetchone()[0]
# 🔥 6. 分页查询(使用窗口函数提高性能)
offset = (page - 1) * pagesize
record_sql = """
SELECT
hg.id,
hg.yonghu_id,
hg.huiyuan_id,
hg.jieshao,
hg.daoqi_time,
hg.CreateTime,
um.avatar,
ud.nicheng
FROM huiyuangoumai hg
LEFT JOIN user_main um ON hg.yonghu_id = um.yonghuid
LEFT JOIN user_dashou ud ON um.id = ud.user_id
WHERE hg.huiyuan_id = %s
"""
record_params = [huiyuan_type]
if jiluzhuangtai == 1: # 未过期
record_sql += " AND hg.daoqi_time > %s"
record_params.append(now)
else: # 已过期
record_sql += " AND hg.daoqi_time <= %s"
record_params.append(now)
record_sql += " ORDER BY hg.CreateTime DESC LIMIT %s OFFSET %s"
record_params.extend([pagesize, offset])
cursor.execute(record_sql, record_params)
records = cursor.fetchall()
# 🔥 7. 处理查询结果
record_list = []
for record in records:
record_list.append({
'id': record[0],
'yonghuid': record[1], # 用户ID
'huiyuan_id': record[2],
'jieshao': record[3],
'daoqi_time': record[4].strftime('%Y-%m-%d %H:%M:%S') if record[4] else None,
'CreateTime': record[5].strftime('%Y-%m-%d %H:%M:%S') if record[5] else None,
'touxiang': record[6], # 头像相对URL
'nicheng': record[7] or record[1], # 昵称或用户ID
'yonghu_type': 2 # 🔥 默认打手类型实际应该根据user_type判断
})
# 🔥 8. 判断是否还有更多数据
has_more = (page * pagesize) < total_count
# 🔥 9. 获取会员类型统计信息(所有会员类型的当前状态总数)
huiyuan_types = Huiyuan.query.all()
huiyuan_stats = []
for huiyuan in huiyuan_types:
# 使用COUNT查询统计
stats_sql = """
SELECT COUNT(*)
FROM huiyuangoumai
WHERE huiyuan_id = %s
"""
stats_params = [huiyuan.huiyuan_id]
if jiluzhuangtai == 1: # 未过期
stats_sql += " AND daoqi_time > %s"
stats_params.append(now)
else: # 已过期
stats_sql += " AND daoqi_time <= %s"
stats_params.append(now)
with connection.cursor() as cursor:
cursor.execute(stats_sql, stats_params)
count = cursor.fetchone()[0]
huiyuan_stats.append({
'id': huiyuan.huiyuan_id,
'jieshao': huiyuan.jieshao,
'count': count
})
return Response({
'code': 0,
'message': '获取成功',
'data': {
'list': record_list,
'total': total_count,
'current_page': page,
'page_size': pagesize,
'has_more': has_more,
'huiyuan_stats': huiyuan_stats, # 🔥 所有会员类型的统计
'current_huiyuan': {
'id': huiyuan_obj.huiyuan_id,
'jieshao': huiyuan_obj.jieshao,
'count': total_count
}
}
}, status=status.HTTP_200_OK)
except ValueError as e:
print(f"参数错误: {str(e)}")
return Response({
'code': 400,
'message': '参数错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
print(f"获取会员记录失败: {str(e)}")
traceback.print_exc()
return Response({
'code': 500,
'message': '系统错误',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class ZuzhangFenhongView(APIView):
"""
组长分红记录接口
路径:/shangpin/zuzhangfenhong
方法POST
权限JWT认证仅限组长身份
请求参数:
{
"page": 1, # 页码默认1
"page_size": 5 # 每页条数默认5
}
返回:
{
"code": 200,
"msg": "success",
"data": {
"fenhong_zonge": "1234.56", # 分红总额
"yaoqing_guanshi": 10, # 邀请管事数
"fenhong_cishu": 50, # 分红次数
"total": 50, # 总记录数
"has_more": false, # 是否有下一页
"list": [...] # 记录列表
}
}
"""
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
current_user = request.user
# 1. 验证组长身份
try:
zuzhang = UserZuzhang.query.get(user=current_user)
except UserZuzhang.DoesNotExist:
return Response({
'code': 403,
'msg': '当前用户不是组长身份',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 2. 请求参数
page = int(request.data.get('page', 1))
page_size = int(request.data.get('page_size', 5))
page = max(page, 1)
page_size = max(page_size, 1)
# 3. 统计数据
# 3.1 分红总额(从组长扩展表)
fenhong_zonge = zuzhang.fenyong_zonge
# 3.2 邀请管事数统计邀请人等于当前用户ID的管事记录
yaoqing_guanshi = UserGuanshi.query.filter(yaoqingren=current_user.UserUID).count()
# 3.3 分红次数(总记录数)
base_queryset = Gsfenhong.query.filter(zuzhang_id=current_user.UserUID).order_by('-CreateTime')
fenhong_cishu = base_queryset.count()
# 4. 分页查询
start = (page - 1) * page_size
end = start + page_size
records = base_queryset[start:end]
# 5. 批量获取管事用户信息(只查 boss_profile 获取昵称)
guanshi_ids = list(set(rec.guanshi for rec in records if rec.guanshi))
guanshi_info_map = self._get_guanshi_info(guanshi_ids)
# 6. 组装列表数据
list_data = []
for rec in records:
# 打手信息(直接从记录中取快照)
dashou_avatar = rec.avatar or ''
dashou_nicheng = rec.nicheng or ''
dashou_yonghuid = rec.dashouid or ''
# 管事信息
guanshi_id = rec.guanshi
guanshi_info = guanshi_info_map.get(guanshi_id, {'avatar': '', 'nicheng': ''})
guanshi_avatar = guanshi_info['avatar']
guanshi_nicheng = guanshi_info['nicheng']
# 组长分红金额
fenhong = rec.zuzhang_fenhong
if fenhong is None:
fenhong = decimal.Decimal('0.00')
fenhong_str = str(fenhong.quantize(decimal.Decimal('0.00')))
# 时间格式化
CreateTime = rec.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if rec.CreateTime else ''
list_data.append({
'dashou_avatar': dashou_avatar,
'dashou_nicheng': dashou_nicheng,
'dashou_yonghuid': dashou_yonghuid,
'guanshi_avatar': guanshi_avatar,
'guanshi_nicheng': guanshi_nicheng,
'guanshi_yonghuid': guanshi_id,
'fenhong': fenhong_str,
'CreateTime': CreateTime,
})
# 7. 是否有更多数据
has_more = (end < fenhong_cishu)
return Response({
'code': 200,
'msg': 'success',
'data': {
'fenhong_zonge': str(fenhong_zonge.quantize(decimal.Decimal('0.00'))),
'yaoqing_guanshi': yaoqing_guanshi,
'fenhong_cishu': fenhong_cishu,
'total': fenhong_cishu,
'has_more': has_more,
'list': list_data,
}
})
def _get_guanshi_info(self, guanshi_ids):
"""
批量获取管事用户的头像和昵称
昵称从老板扩展表boss_profile获取头像从主表获取
返回字典:{用户ID: {'avatar': 相对路径, 'nicheng': 昵称}}
"""
if not guanshi_ids:
return {}
# 查询用户主表,并预加载 boss_profile
users = User.query.filter(UserUID__in=guanshi_ids).select_related(
'BossProfile'
).only(
'UserUID', 'Avatar',
'BossProfile__nickname'
)
info = {}
for user in users:
avatar = user.Avatar or ''
# 从 boss_profile 获取昵称
if hasattr(user, 'BossProfile') and user.BossProfile:
nicheng = user.BossProfile.nickname or ''
else:
# 如果没有 boss_profile使用默认昵称
nicheng = f"用户{user.UserUID[-4:]}"
info[user.UserUID] = {'avatar': avatar, 'nicheng': nicheng}
# 对于未查询到的用户,提供默认值
for gid in guanshi_ids:
if gid not in info:
info[gid] = {'avatar': '', 'nicheng': f"用户{gid[-4:]}"}
return info
# 常量定义(可根据需要调整)