尝试将 backend 做了视图拆分,来保证可维护性,若此版本存在生产环境问题应该回退
This commit is contained in:
968
backend/views/finance.py
Normal file
968
backend/views/finance.py
Normal file
@@ -0,0 +1,968 @@
|
||||
import hmac
|
||||
import threading
|
||||
import traceback
|
||||
import uuid
|
||||
import hashlib
|
||||
import xmltodict
|
||||
import time
|
||||
import logging
|
||||
import requests
|
||||
import os
|
||||
import secrets
|
||||
import random
|
||||
import string
|
||||
from calendar import monthrange
|
||||
from decimal import Decimal
|
||||
from datetime import date, datetime, timedelta
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.paginator import Paginator
|
||||
from django.db import models, transaction, IntegrityError
|
||||
from django.db.models import Q, Count, Sum, F, Exists, OuterRef
|
||||
from gvsdsdk.fluent import db, func, FQ
|
||||
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils.decorators import method_decorator
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
|
||||
|
||||
# 工具类导入
|
||||
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
|
||||
from backend.utils import (
|
||||
update_dashou_daily_by_action,
|
||||
update_shangjia_daily
|
||||
)
|
||||
from jituan.services.club_context import filter_club_char_field, filter_queryset_by_club, filter_user_related_by_club, filter_user_qs_by_club, orders_for_request
|
||||
from jituan.services.catalog_scope import block_non_group_catalog_scope
|
||||
from jituan.services.club_penalty import (
|
||||
filter_penalty_qs, resolve_penalty_club_id, filter_gsfenhong_qs,
|
||||
forbid_penalty_out_of_scope, resolve_penalty_record_club_id,
|
||||
)
|
||||
from jituan.services.club_user_access import forbid_if_user_out_of_scope, list_response_meta
|
||||
from shop.utils import update_dianpu_daily_stat
|
||||
from products.utils import update_shangpin_daily_stat
|
||||
from orders.notice_tasks import dingdan_guangbo
|
||||
from ..utils import (
|
||||
verify_kefu_permission,
|
||||
PERMISSION_TO_SHENFEN,
|
||||
SHENFEN_PROFILE_MAP,
|
||||
has_fadan_view_permission,
|
||||
has_merchant_order_permission,
|
||||
check_fadan_permission,
|
||||
pick_order_id,
|
||||
pick_penalty_create_params,
|
||||
write_xiugai_log,
|
||||
XIUGAI_LEIXING_DASHOU,
|
||||
XIUGAI_LEIXING_GUANSHI,
|
||||
XIUGAI_LEIXING_SHANGJIA,
|
||||
XIUGAI_LEIXING_ZUZHANG,
|
||||
XIUGAI_LEIXING_LABEL,
|
||||
)
|
||||
|
||||
# models 集中导入
|
||||
## gvsdsdk RBAC 模型(使用 PascalCase 字段名,匹配实际数据库表结构)
|
||||
from gvsdsdk.models import Role, Permission, RolePermission, UserRole
|
||||
## backend
|
||||
from backend.models import WithdrawalDailyStats
|
||||
|
||||
## users
|
||||
from users.models import (
|
||||
UserGuanshi, UserBoss, UserZuzhang,
|
||||
UserKefu, UserDashou, Xiugaijilu, UserShangjia, UserShenheguan
|
||||
)
|
||||
from users.business_models import User
|
||||
|
||||
## orders
|
||||
from orders.models import (
|
||||
CommissionRate, PlayerDeliveryImage, PenaltyRecord,
|
||||
OrderPlayerHistory, Order, RefundRecord,
|
||||
MerchantOrderExt, Penalty, PenaltyAppealImage, PenaltyBonus
|
||||
)
|
||||
|
||||
## shop
|
||||
from shop.models import Dianpu, DianpuMorenPeizhi, YonghuPingzheng, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi
|
||||
|
||||
## products
|
||||
from products.models import (
|
||||
Shangpin, ShangpinLeixing, ShangpinZhuanqu, Huiyuan,
|
||||
DuociFenhong, Czjilu, Huiyuangoumai, Gsfenhong
|
||||
)
|
||||
|
||||
## config
|
||||
from config.models import (
|
||||
WithdrawConfig, ShangjiaLianjie, AccountPermission,
|
||||
TixianQuotaDefault,
|
||||
DailyIncomeStat, DailyPayoutStat, PopupPage, PopupConfig, PopupImage
|
||||
)
|
||||
|
||||
## rank
|
||||
from rank.models import (
|
||||
KaoheguanBankuai, Bankuai, Chenghao, KaoheCishuFeiyong,
|
||||
YonghuChenghao, ShenheJilu, KaoheJiluFeiyong, KaoheJujueJilu
|
||||
)
|
||||
|
||||
# 序列化器
|
||||
from ..serializers import PopupPageSerializer
|
||||
|
||||
# 全局常量、日志对象
|
||||
logger = logging.getLogger('houtai')
|
||||
SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd']
|
||||
|
||||
class CaiwuView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
username_from_frontend = request.data.get('phone', None)
|
||||
kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)
|
||||
|
||||
if kefu_obj is None:
|
||||
return permissions
|
||||
|
||||
if 'caiwu' not in permissions:
|
||||
return Response({'code': 403, 'msg': '无财务查看权限'}, status=403)
|
||||
|
||||
from jituan.services.caiwu_stats import build_caiwu_payload
|
||||
|
||||
try:
|
||||
data = build_caiwu_payload(request)
|
||||
return Response({'code': 0, 'msg': 'ok', 'data': data})
|
||||
except Exception:
|
||||
logger.exception('CaiwuView 异常')
|
||||
return Response({'code': 99, 'msg': '统计失败'}, status=500)
|
||||
|
||||
|
||||
class CwhybkhqView(APIView):
|
||||
"""
|
||||
获取所有板块及对应会员列表
|
||||
POST /houtai/cwhybkhq
|
||||
Body: { "phone": "管理员手机号" }
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
username_from_frontend = request.data.get('phone', None)
|
||||
kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)
|
||||
if kefu_obj is None:
|
||||
return permissions
|
||||
|
||||
if 'huiyuancaiwu' not in permissions:
|
||||
return Response({'code': 403, 'msg': '无会员财务查看权限'}, status=403)
|
||||
|
||||
bankuai_list = []
|
||||
for bk in Bankuai.query.all():
|
||||
members = Huiyuan.query.filter(bankuai=bk).values('huiyuan_id', 'jieshao', 'bankuai_id')
|
||||
bankuai_list.append({
|
||||
'bankuai_id': bk.bankuai_id,
|
||||
'mingcheng': bk.mingcheng,
|
||||
'members': list(members)
|
||||
})
|
||||
|
||||
return Response({'code': 0, 'msg': 'ok', 'data': bankuai_list})
|
||||
except Exception as e:
|
||||
logger.exception("CwhybkhqView 接口错误")
|
||||
return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
||||
|
||||
|
||||
class HybkjtsjView(APIView):
|
||||
"""
|
||||
会员详细统计数据
|
||||
POST /houtai/hybkjtsj
|
||||
Body: {
|
||||
"phone": "管理员手机号",
|
||||
"huiyuan_id": "会员ID",
|
||||
"granularity": "day" | "month" | "year",
|
||||
"year": 2026,
|
||||
"month": 5,
|
||||
"include_guanshi": true,
|
||||
"include_zuzhang": true,
|
||||
"summary_date": "2026-05-16"
|
||||
}
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
username_from_frontend = request.data.get('phone', None)
|
||||
kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)
|
||||
if kefu_obj is None:
|
||||
return permissions
|
||||
|
||||
if 'huiyuancaiwu' not in permissions:
|
||||
return Response({'code': 403, 'msg': '无会员财务查看权限'}, status=403)
|
||||
|
||||
huiyuan_id = request.data.get('huiyuan_id')
|
||||
granularity = request.data.get('granularity', 'day')
|
||||
year = request.data.get('year')
|
||||
month = request.data.get('month')
|
||||
include_guanshi = request.data.get('include_guanshi', False)
|
||||
include_zuzhang = request.data.get('include_zuzhang', False)
|
||||
summary_date = request.data.get('summary_date')
|
||||
|
||||
if not huiyuan_id:
|
||||
return Response({'code': 1, 'msg': '缺少会员ID'}, status=400)
|
||||
|
||||
# 🔧 直接使用 date.today() 避免模块名冲突
|
||||
now = date.today()
|
||||
|
||||
# ---------- 构造时间范围 ----------
|
||||
if granularity == 'day':
|
||||
if not year or not month:
|
||||
return Response({'code': 1, 'msg': '按日统计需要年份和月份'}, status=400)
|
||||
start_date = date(year, month, 1)
|
||||
if month == 12:
|
||||
end_date = date(year + 1, 1, 1)
|
||||
else:
|
||||
end_date = date(year, month + 1, 1)
|
||||
trunc_func = TruncDate('CreateTime')
|
||||
if not summary_date:
|
||||
summary_date = now.strftime('%Y-%m-%d')
|
||||
summary_q = datetime.strptime(summary_date, '%Y-%m-%d').date()
|
||||
elif granularity == 'month':
|
||||
if not year:
|
||||
return Response({'code': 1, 'msg': '按月统计需要年份'}, status=400)
|
||||
start_date = date(year, 1, 1)
|
||||
end_date = date(year + 1, 1, 1)
|
||||
trunc_func = TruncMonth('CreateTime')
|
||||
if not summary_date:
|
||||
summary_date = now.strftime('%Y-%m')
|
||||
summary_q = datetime.strptime(summary_date + '-01', '%Y-%m-%d').date()
|
||||
elif granularity == 'year':
|
||||
year = year or now.year
|
||||
start_date = date(year, 1, 1)
|
||||
end_date = date(year + 1, 1, 1)
|
||||
trunc_func = TruncYear('CreateTime')
|
||||
if not summary_date:
|
||||
summary_date = str(year)
|
||||
summary_q = date(year, 1, 1)
|
||||
else:
|
||||
return Response({'code': 1, 'msg': '无效的颗粒度'}, status=400)
|
||||
|
||||
# 生成 naive datetime(避免 USE_TZ=False 报错)
|
||||
start_dt = datetime.combine(start_date, datetime.min.time())
|
||||
end_dt = datetime.combine(end_date, datetime.min.time())
|
||||
|
||||
# ---------- 充值过滤 ----------
|
||||
chongzhi_filter = {
|
||||
'huiyuan_id': huiyuan_id, # 会员ID筛选
|
||||
'leixing': 1,
|
||||
'zhuangtai': 3,
|
||||
'CreateTime__gte': start_dt,
|
||||
'CreateTime__lt': end_dt
|
||||
}
|
||||
|
||||
# ---------- 分红过滤 ----------
|
||||
fenhong_filter = {
|
||||
'huiyuan_id': huiyuan_id, # 分红表会员ID筛选
|
||||
'CreateTime__gte': start_dt,
|
||||
'CreateTime__lt': end_dt
|
||||
}
|
||||
|
||||
# 充值分组查询
|
||||
chongzhi_qs = filter_queryset_by_club(
|
||||
Czjilu.query.filter(**chongzhi_filter), request,
|
||||
) \
|
||||
.annotate(period=trunc_func) \
|
||||
.values('period') \
|
||||
.annotate(
|
||||
chongzhi_count=Count('id'),
|
||||
chongzhi_amount=Sum('jine')
|
||||
).order_by('period')
|
||||
|
||||
# 分红分组查询
|
||||
fenhong_qs = None
|
||||
if include_guanshi or include_zuzhang:
|
||||
fenhong_agg = {}
|
||||
if include_guanshi:
|
||||
fenhong_agg['guanshi_amount'] = Sum('fenhong')
|
||||
if include_zuzhang:
|
||||
fenhong_agg['zuzhang_amount'] = Sum('zuzhang_fenhong')
|
||||
fenhong_qs = filter_gsfenhong_qs(
|
||||
Gsfenhong.query.filter(**fenhong_filter), request,
|
||||
) \
|
||||
.annotate(period=trunc_func) \
|
||||
.values('period') \
|
||||
.annotate(**fenhong_agg) \
|
||||
.order_by('period')
|
||||
|
||||
# 合并数据
|
||||
data_map = {}
|
||||
for item in chongzhi_qs:
|
||||
period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
|
||||
item['period'].strftime('%Y-%m') if granularity == 'month' else \
|
||||
item['period'].strftime('%Y')
|
||||
data_map[period_str] = {
|
||||
'chongzhi_count': item['chongzhi_count'],
|
||||
'chongzhi_amount': float(item['chongzhi_amount'] or 0)
|
||||
}
|
||||
|
||||
if fenhong_qs:
|
||||
for item in fenhong_qs:
|
||||
period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
|
||||
item['period'].strftime('%Y-%m') if granularity == 'month' else \
|
||||
item['period'].strftime('%Y')
|
||||
if period_str not in data_map:
|
||||
data_map[period_str] = {'chongzhi_count': 0, 'chongzhi_amount': 0.0}
|
||||
if include_guanshi:
|
||||
data_map[period_str]['guanshi_amount'] = float(item.get('guanshi_amount') or 0)
|
||||
if include_zuzhang:
|
||||
data_map[period_str]['zuzhang_amount'] = float(item.get('zuzhang_amount') or 0)
|
||||
|
||||
time_series = []
|
||||
for period_str, vals in data_map.items():
|
||||
guanshi = vals.get('guanshi_amount', 0) if include_guanshi else 0
|
||||
zuzhang = vals.get('zuzhang_amount', 0) if include_zuzhang else 0
|
||||
vals['shouyi'] = round(vals['chongzhi_amount'] - guanshi - zuzhang, 2)
|
||||
vals['date'] = period_str
|
||||
time_series.append(vals)
|
||||
time_series.sort(key=lambda x: x['date'])
|
||||
|
||||
# ---------- 汇总数据 ----------
|
||||
summary_chongzhi_filter = chongzhi_filter.copy()
|
||||
if granularity == 'day':
|
||||
summary_chongzhi_filter['CreateTime__date'] = summary_q
|
||||
elif granularity == 'month':
|
||||
summary_chongzhi_filter['CreateTime__year'] = summary_q.year
|
||||
summary_chongzhi_filter['CreateTime__month'] = summary_q.month
|
||||
else:
|
||||
summary_chongzhi_filter['CreateTime__year'] = summary_q.year
|
||||
|
||||
summary_chongzhi = Czjilu.query.filter(**summary_chongzhi_filter).aggregate(
|
||||
total_count=Count('id'),
|
||||
total_amount=Sum('jine')
|
||||
)
|
||||
total_chongzhi_count = summary_chongzhi['total_count'] or 0
|
||||
total_chongzhi_amount = float(summary_chongzhi['total_amount'] or 0)
|
||||
|
||||
total_guanshi = 0.0
|
||||
total_zuzhang = 0.0
|
||||
if include_guanshi or include_zuzhang:
|
||||
summary_fenhong_filter = fenhong_filter.copy()
|
||||
if granularity == 'day':
|
||||
summary_fenhong_filter['CreateTime__date'] = summary_q
|
||||
elif granularity == 'month':
|
||||
summary_fenhong_filter['CreateTime__year'] = summary_q.year
|
||||
summary_fenhong_filter['CreateTime__month'] = summary_q.month
|
||||
else:
|
||||
summary_fenhong_filter['CreateTime__year'] = summary_q.year
|
||||
|
||||
fenhong_agg = {}
|
||||
if include_guanshi:
|
||||
fenhong_agg['guanshi_sum'] = Sum('fenhong')
|
||||
if include_zuzhang:
|
||||
fenhong_agg['zuzhang_sum'] = Sum('zuzhang_fenhong')
|
||||
summary_fenhong = Gsfenhong.query.filter(**summary_fenhong_filter).aggregate(**fenhong_agg)
|
||||
total_guanshi = float(summary_fenhong.get('guanshi_sum') or 0)
|
||||
total_zuzhang = float(summary_fenhong.get('zuzhang_sum') or 0)
|
||||
|
||||
total_shouyi = round(total_chongzhi_amount - total_guanshi - total_zuzhang, 2)
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'time_series': time_series,
|
||||
'summary': {
|
||||
'total_chongzhi_count': total_chongzhi_count,
|
||||
'total_chongzhi_amount': total_chongzhi_amount,
|
||||
'total_guanshi_fenhong': total_guanshi,
|
||||
'total_zuzhang_fenhong': total_zuzhang,
|
||||
'total_shouyi': total_shouyi
|
||||
}
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
logger.exception("HybkjtsjView 接口错误")
|
||||
return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class SzxxView(APIView):
|
||||
"""
|
||||
每日收支详细统计数据
|
||||
POST /houtai/szxx
|
||||
只读 daily_income_stat / daily_payout_stat,按 date 字段汇总。
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
from jituan.services.szxx_stats import build_szxx_payload
|
||||
|
||||
username_from_frontend = request.data.get('phone', None)
|
||||
kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)
|
||||
if kefu_obj is None:
|
||||
return permissions
|
||||
|
||||
if 'caiwu' not in permissions:
|
||||
return Response({'code': 403, 'msg': '无财务查看权限'}, status=403)
|
||||
|
||||
granularity = request.data.get('granularity', 'day')
|
||||
year = request.data.get('year')
|
||||
month = request.data.get('month')
|
||||
summary_date = request.data.get('summary_date')
|
||||
|
||||
data, err = build_szxx_payload(request, granularity, year, month, summary_date)
|
||||
if err:
|
||||
return Response({'code': 1, 'msg': err}, status=400)
|
||||
|
||||
return Response({'code': 0, 'msg': 'ok', 'data': data})
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("SzxxView 接口错误")
|
||||
return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class CwddhqlxView(APIView):
|
||||
"""
|
||||
获取订单类型(商品类型)用于筛选
|
||||
POST /houtai/cwddhqlx
|
||||
Body: { "phone": "管理员手机号" }
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
username_from_frontend = request.data.get('phone', None)
|
||||
kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)
|
||||
if kefu_obj is None:
|
||||
return permissions
|
||||
|
||||
if 'dingdancaiwu' not in permissions:
|
||||
return Response({'code': 403, 'msg': '无订单财务查看权限'}, status=403)
|
||||
|
||||
# 获取非审核状态(shenhezhuangtai=1)的商品类型,只返回ID和名称
|
||||
types = ShangpinLeixing.query.filter(shenhezhuangtai=1).values('id', 'jieshao')
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': 'ok',
|
||||
'data': list(types)
|
||||
})
|
||||
except Exception as e:
|
||||
logger.exception("CwddhqlxView 接口错误")
|
||||
return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
||||
|
||||
|
||||
|
||||
|
||||
class CwhqjtddsjView(APIView):
|
||||
"""
|
||||
订单详细统计数据(修正版)
|
||||
POST /houtai/cwhqjtddsj
|
||||
Body: {
|
||||
"phone": "管理员手机号",
|
||||
"granularity": "day" | "month" | "year",
|
||||
"year": 2026,
|
||||
"month": 5,
|
||||
"type_id": 1, # 商品类型ID,0表示全部
|
||||
"order_source": 1, # 1=平台单,2=商家单,0=全部
|
||||
"summary_date": "2026-05-16"
|
||||
}
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
username_from_frontend = request.data.get('phone', None)
|
||||
kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)
|
||||
if kefu_obj is None:
|
||||
return permissions
|
||||
if 'dingdancaiwu' not in permissions:
|
||||
return Response({'code': 403, 'msg': '无订单财务查看权限'}, status=403)
|
||||
|
||||
granularity = request.data.get('granularity', 'day')
|
||||
year = request.data.get('year')
|
||||
month = request.data.get('month')
|
||||
type_id = request.data.get('type_id', 0)
|
||||
order_source = request.data.get('order_source', 0)
|
||||
summary_date = request.data.get('summary_date')
|
||||
|
||||
now = date.today()
|
||||
|
||||
# ---------- 时间范围 ----------
|
||||
if granularity == 'day':
|
||||
if not year or not month:
|
||||
return Response({'code': 1, 'msg': '按日统计需要年份和月份'}, status=400)
|
||||
start_date = date(year, month, 1)
|
||||
end_date = date(year, month + 1, 1) if month < 12 else date(year + 1, 1, 1)
|
||||
trunc_func = TruncDate('CreateTime')
|
||||
if not summary_date:
|
||||
summary_date = now.strftime('%Y-%m-%d')
|
||||
summary_q = datetime.strptime(summary_date, '%Y-%m-%d').date()
|
||||
elif granularity == 'month':
|
||||
if not year:
|
||||
return Response({'code': 1, 'msg': '按月统计需要年份'}, status=400)
|
||||
start_date = date(year, 1, 1)
|
||||
end_date = date(year + 1, 1, 1)
|
||||
trunc_func = TruncMonth('CreateTime')
|
||||
if not summary_date:
|
||||
summary_date = now.strftime('%Y-%m')
|
||||
summary_q = datetime.strptime(summary_date + '-01', '%Y-%m-%d').date()
|
||||
elif granularity == 'year':
|
||||
year = year or now.year
|
||||
start_date = date(year, 1, 1)
|
||||
end_date = date(year + 1, 1, 1)
|
||||
trunc_func = TruncYear('CreateTime')
|
||||
if not summary_date:
|
||||
summary_date = str(year)
|
||||
summary_q = date(year, 1, 1)
|
||||
else:
|
||||
return Response({'code': 1, 'msg': '无效的颗粒度'}, status=400)
|
||||
|
||||
start_dt = datetime.combine(start_date, datetime.min.time())
|
||||
end_dt = datetime.combine(end_date, datetime.min.time())
|
||||
|
||||
# ---------- 基础过滤 ----------
|
||||
filters = {
|
||||
'CreateTime__gte': start_dt,
|
||||
'CreateTime__lt': end_dt,
|
||||
'Status__in': [1, 2, 3, 4, 5, 6, 7, 8] # 只统计有效订单状态
|
||||
}
|
||||
if int(type_id) != 0:
|
||||
filters['ProductTypeID'] = type_id
|
||||
if int(order_source) in [1, 2]:
|
||||
filters['Platform'] = order_source
|
||||
|
||||
# ---------- 时间序列分组查询 ----------
|
||||
order_base = filter_queryset_by_club(
|
||||
Order.query.filter(**filters), request, club_field='ClubID',
|
||||
)
|
||||
qs = order_base \
|
||||
.annotate(period=trunc_func) \
|
||||
.values('period') \
|
||||
.annotate(
|
||||
# 订单总量(所有符合条件的状态)
|
||||
order_count=Count('id'),
|
||||
# 订单总额
|
||||
order_amount=Sum('Amount'),
|
||||
# 成交单量 (Status=3)
|
||||
success_count=Count('id', filter=Q(Status=3)),
|
||||
# 成交订单总额
|
||||
success_amount=Sum('Amount', filter=Q(Status=3)),
|
||||
# 退款单量 (Status=5)
|
||||
refund_count=Count('id', filter=Q(Status=5)),
|
||||
# 打手分成总额(仅成交订单)
|
||||
PlayerCommission_sum=Sum('PlayerCommission', filter=Q(Status=3)),
|
||||
# 平台成交订单的店铺分红
|
||||
dianpu_fenhong_sum=Sum('pingtai_kuozhan__ShopIncome',
|
||||
filter=Q(Status=3, Platform=1)),
|
||||
# 成交订单中:平台单金额、商家单金额、各自打手分成
|
||||
platform_success_amount=Sum('Amount', filter=Q(Status=3, Platform=1)),
|
||||
merchant_success_amount=Sum('Amount', filter=Q(Status=3, Platform=2)),
|
||||
platform_dashou=Sum('PlayerCommission', filter=Q(Status=3, Platform=1)),
|
||||
merchant_dashou=Sum('PlayerCommission', filter=Q(Status=3, Platform=2)),
|
||||
).order_by('period')
|
||||
|
||||
time_series = []
|
||||
for item in qs:
|
||||
period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
|
||||
item['period'].strftime('%Y-%m') if granularity == 'month' else \
|
||||
item['period'].strftime('%Y')
|
||||
|
||||
# 平台收益计算
|
||||
platform_profit = (item['platform_success_amount'] or 0) \
|
||||
- (item['platform_dashou'] or 0) \
|
||||
- (item['dianpu_fenhong_sum'] or 0)
|
||||
merchant_profit = (item['merchant_success_amount'] or 0) \
|
||||
- (item['merchant_dashou'] or 0)
|
||||
total_profit = platform_profit + merchant_profit
|
||||
|
||||
time_series.append({
|
||||
'date': period_str,
|
||||
'order_count': item['order_count'],
|
||||
'order_amount': float(item['order_amount'] or 0),
|
||||
'success_count': item['success_count'],
|
||||
'success_amount': float(item['success_amount'] or 0),
|
||||
'refund_count': item['refund_count'],
|
||||
'dashou_fencheng': float(item['PlayerCommission_sum'] or 0),
|
||||
'dianpu_fenhong': float(item['dianpu_fenhong_sum'] or 0),
|
||||
'profit': round(total_profit, 2)
|
||||
})
|
||||
|
||||
# ---------- 汇总数据 ----------
|
||||
summary_qs = filter_queryset_by_club(
|
||||
Order.query.filter(**filters), request, club_field='ClubID',
|
||||
)
|
||||
total_order_count = summary_qs.count()
|
||||
total_order_amount = summary_qs.aggregate(s=Sum('Amount'))['s'] or 0
|
||||
total_success_count = summary_qs.filter(Status=3).count()
|
||||
total_success_amount = summary_qs.filter(Status=3).aggregate(s=Sum('Amount'))['s'] or 0
|
||||
total_refund_count = summary_qs.filter(Status=5).count()
|
||||
total_dashou = summary_qs.filter(Status=3).aggregate(s=Sum('PlayerCommission'))['s'] or 0
|
||||
total_dianpu_fenhong = summary_qs.filter(Status=3, Platform=1).aggregate(
|
||||
s=Sum('pingtai_kuozhan__ShopIncome'))['s'] or 0
|
||||
|
||||
platform_amount = summary_qs.filter(Status=3, Platform=1).aggregate(s=Sum('Amount'))['s'] or 0
|
||||
merchant_amount = summary_qs.filter(Status=3, Platform=2).aggregate(s=Sum('Amount'))['s'] or 0
|
||||
platform_dashou_sum = \
|
||||
summary_qs.filter(Status=3, Platform=1).aggregate(s=Sum('PlayerCommission'))['s'] or 0
|
||||
merchant_dashou_sum = \
|
||||
summary_qs.filter(Status=3, Platform=2).aggregate(s=Sum('PlayerCommission'))['s'] or 0
|
||||
|
||||
profit_platform = platform_amount - platform_dashou_sum - total_dianpu_fenhong
|
||||
profit_merchant = merchant_amount - merchant_dashou_sum
|
||||
total_profit = profit_platform + profit_merchant
|
||||
|
||||
summary = {
|
||||
'total_order_count': total_order_count,
|
||||
'total_order_amount': round(float(total_order_amount), 2),
|
||||
'total_success_count': total_success_count,
|
||||
'total_success_amount': round(float(total_success_amount), 2),
|
||||
'total_refund_count': total_refund_count,
|
||||
'total_PlayerCommission': round(float(total_dashou), 2),
|
||||
'total_dianpu_fenhong': round(float(total_dianpu_fenhong), 2),
|
||||
'total_profit': round(total_profit, 2)
|
||||
}
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'time_series': time_series,
|
||||
'summary': summary
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
logger.exception("CwhqjtddsjView 接口错误")
|
||||
return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class CwqtczhqView(APIView):
|
||||
"""
|
||||
其他充值/罚款数据统计(非会员充值)
|
||||
POST /houtai/cwqtczhq
|
||||
Body: {
|
||||
"phone": "管理员手机号",
|
||||
"granularity": "day" | "month" | "year",
|
||||
"year": 2026,
|
||||
"month": 5, // day时必填
|
||||
"types": [2,3,4,5,6], // 2=押金,3=积分,4=商家,5=考核,6=罚款
|
||||
"summary_date": "2026-05-17" // 汇总日期,格式随颗粒度(day:YYYY-MM-DD, month:YYYY-MM, year:YYYY)
|
||||
}
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
username_from_frontend = request.data.get('phone', None)
|
||||
kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)
|
||||
if kefu_obj is None:
|
||||
return permissions
|
||||
if 'qtczcaiwu' not in permissions:
|
||||
return Response({'code': 403, 'msg': '无其他充值财务查看权限'}, status=403)
|
||||
|
||||
from jituan.services.finance_detail_stats import build_chongzhi_finance_payload
|
||||
|
||||
granularity = request.data.get('granularity', 'day')
|
||||
year = request.data.get('year')
|
||||
month = request.data.get('month')
|
||||
types = request.data.get('types', [2, 3, 4, 5, 6])
|
||||
summary_date = request.data.get('summary_date')
|
||||
|
||||
if isinstance(types, list) and len(types) == 0:
|
||||
types = [2, 3, 4, 5, 6]
|
||||
|
||||
data, err = build_chongzhi_finance_payload(
|
||||
request, granularity, year, month, types, summary_date,
|
||||
)
|
||||
if err:
|
||||
return Response({'code': 1, 'msg': err}, status=400)
|
||||
|
||||
return Response({'code': 0, 'msg': 'ok', 'data': data})
|
||||
except Exception as e:
|
||||
logger.exception("CwqtczhqView 接口错误")
|
||||
return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
||||
|
||||
'''class CwqtczhqView_LEGACY(APIView):
|
||||
"""
|
||||
其他充值/罚款数据统计(非会员充值)
|
||||
POST /houtai/cwqtczhq
|
||||
Body: {
|
||||
"phone": "管理员手机号",
|
||||
"granularity": "day" | "month" | "year",
|
||||
"year": 2026,
|
||||
"month": 5, // day时必填
|
||||
"types": [2,3,4,5,6], // 2=押金,3=积分,4=商家,5=考核,6=罚款
|
||||
"summary_date": "2026-05-17" // 汇总日期,格式随颗粒度(day:YYYY-MM-DD, month:YYYY-MM, year:YYYY)
|
||||
}
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
username_from_frontend = request.data.get('phone', None)
|
||||
kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)
|
||||
if kefu_obj is None:
|
||||
return permissions
|
||||
if 'qtczcaiwu' not in permissions:
|
||||
return Response({'code': 403, 'msg': '无其他充值财务查看权限'}, status=403)
|
||||
|
||||
granularity = request.data.get('granularity', 'day')
|
||||
year = request.data.get('year')
|
||||
month = request.data.get('month')
|
||||
types = request.data.get('types', [2, 3, 4, 5, 6]) # 默认全部
|
||||
summary_date = request.data.get('summary_date')
|
||||
|
||||
if isinstance(types, list) and len(types) == 0:
|
||||
types = [2, 3, 4, 5, 6]
|
||||
|
||||
now = date.today()
|
||||
|
||||
# ---------- 时间范围(用于曲线图) ----------
|
||||
if granularity == 'day':
|
||||
if not year or not month:
|
||||
return Response({'code': 1, 'msg': '按日统计需要年份和月份'}, status=400)
|
||||
start_date = date(year, month, 1)
|
||||
end_date = date(year, month + 1, 1) if month < 12 else date(year + 1, 1, 1)
|
||||
trunc_func = TruncDate('CreateTime')
|
||||
# 默认汇总日期
|
||||
if not summary_date:
|
||||
summary_date = now.strftime('%Y-%m-%d')
|
||||
summary_q = datetime.strptime(summary_date, '%Y-%m-%d').date()
|
||||
elif granularity == 'month':
|
||||
if not year:
|
||||
return Response({'code': 1, 'msg': '按月统计需要年份'}, status=400)
|
||||
start_date = date(year, 1, 1)
|
||||
end_date = date(year + 1, 1, 1)
|
||||
trunc_func = TruncMonth('CreateTime')
|
||||
if not summary_date:
|
||||
summary_date = now.strftime('%Y-%m')
|
||||
summary_q = datetime.strptime(summary_date + '-01', '%Y-%m-%d').date()
|
||||
elif granularity == 'year':
|
||||
year = year or now.year
|
||||
start_date = date(year, 1, 1)
|
||||
end_date = date(year + 1, 1, 1)
|
||||
trunc_func = TruncYear('CreateTime')
|
||||
if not summary_date:
|
||||
summary_date = str(year)
|
||||
summary_q = date(year, 1, 1)
|
||||
else:
|
||||
return Response({'code': 1, 'msg': '无效的颗粒度'}, status=400)
|
||||
|
||||
start_dt = datetime.combine(start_date, datetime.min.time())
|
||||
end_dt = datetime.combine(end_date, datetime.min.time())
|
||||
|
||||
# ---------- 构建时间序列 ----------
|
||||
time_series_map = {} # period_str -> { type_code: {count, amount, profit, fenhong_count, fenhong_amount} }
|
||||
|
||||
def ensure_date(ds):
|
||||
if ds not in time_series_map:
|
||||
time_series_map[ds] = {}
|
||||
return time_series_map[ds]
|
||||
|
||||
# 1. 处理充值记录(Czjilu)
|
||||
cz_types = [t for t in types if t in [2, 3, 4, 5]]
|
||||
if cz_types:
|
||||
cz_qs = Czjilu.query.filter(
|
||||
CreateTime__gte=start_dt,
|
||||
CreateTime__lt=end_dt,
|
||||
leixing__in=cz_types,
|
||||
zhuangtai=3 # 只统计支付成功的
|
||||
).annotate(
|
||||
period=trunc_func
|
||||
).values('period', 'leixing').annotate(
|
||||
total_count=Count('id'),
|
||||
total_amount=Sum('jine')
|
||||
).order_by('period', 'leixing')
|
||||
|
||||
for item in cz_qs:
|
||||
period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
|
||||
item['period'].strftime('%Y-%m') if granularity == 'month' else \
|
||||
item['period'].strftime('%Y')
|
||||
lx = item['leixing']
|
||||
amount = float(item['total_amount'] or 0)
|
||||
count = item['total_count']
|
||||
# 收益:积分(3)收益为金额本身,其他为0
|
||||
profit = amount if lx == 3 else 0.0
|
||||
|
||||
day_data = ensure_date(period_str)
|
||||
day_data[str(lx)] = {
|
||||
'count': count,
|
||||
'amount': amount,
|
||||
'profit': profit,
|
||||
'fenhong_count': 0, # 充值类型无分红
|
||||
'fenhong_amount': 0.0
|
||||
}
|
||||
|
||||
# 2. 处理罚款(Penalty)—— 包含分红
|
||||
if 6 in types:
|
||||
# 罚款记录
|
||||
penalty_qs = Penalty.query.filter(
|
||||
CreateTime__gte=start_dt,
|
||||
CreateTime__lt=end_dt,
|
||||
Status=2 # 已缴纳
|
||||
).annotate(
|
||||
period=trunc_func
|
||||
).values('period').annotate(
|
||||
penalty_count=Count('id'),
|
||||
penalty_amount=Sum('FineAmount')
|
||||
).order_by('period')
|
||||
|
||||
# 分红记录
|
||||
fenhong_qs = PenaltyBonus.query.filter(
|
||||
CreateTime__gte=start_dt,
|
||||
CreateTime__lt=end_dt
|
||||
).annotate(
|
||||
period=trunc_func
|
||||
).values('period').annotate(
|
||||
fh_count=Count('id'),
|
||||
fh_amount=Sum('WithdrawableAmount')
|
||||
).order_by('period')
|
||||
|
||||
# 合并
|
||||
for item in penalty_qs:
|
||||
period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
|
||||
item['period'].strftime('%Y-%m') if granularity == 'month' else \
|
||||
item['period'].strftime('%Y')
|
||||
day_data = ensure_date(period_str)
|
||||
day_data['6'] = {
|
||||
'count': item['penalty_count'],
|
||||
'amount': float(item['penalty_amount'] or 0),
|
||||
'profit': 0.0, # 先设为0,后续计算
|
||||
'fenhong_count': 0,
|
||||
'fenhong_amount': 0.0
|
||||
}
|
||||
|
||||
for item in fenhong_qs:
|
||||
period_str = item['period'].strftime('%Y-%m-%d') if granularity == 'day' else \
|
||||
item['period'].strftime('%Y-%m') if granularity == 'month' else \
|
||||
item['period'].strftime('%Y')
|
||||
day_data = ensure_date(period_str)
|
||||
if '6' not in day_data:
|
||||
day_data['6'] = {'count': 0, 'amount': 0.0, 'profit': 0.0, 'fenhong_count': 0, 'fenhong_amount': 0.0}
|
||||
day_data['6']['fenhong_count'] += item['fh_count']
|
||||
day_data['6']['fenhong_amount'] += float(item['fh_amount'] or 0)
|
||||
|
||||
# 计算罚款收益 = 罚款金额 - 分红金额
|
||||
for period_str, day_data in time_series_map.items():
|
||||
if '6' in day_data:
|
||||
penalty = day_data['6']
|
||||
penalty['profit'] = round(penalty['amount'] - penalty['fenhong_amount'], 2)
|
||||
|
||||
# 构造有序时间序列列表
|
||||
time_series = []
|
||||
for period_str in sorted(time_series_map.keys()):
|
||||
entry = {'date': period_str, 'types': {}}
|
||||
for type_code in sorted(time_series_map[period_str].keys()):
|
||||
entry['types'][type_code] = time_series_map[period_str][type_code]
|
||||
time_series.append(entry)
|
||||
|
||||
# ---------- 汇总指定日期 ----------
|
||||
summary_result = {'by_type': {}, 'total_count': 0, 'total_amount': 0.0, 'total_profit': 0.0}
|
||||
|
||||
# 汇总充值类型
|
||||
for lx in cz_types:
|
||||
filter_kwargs = {
|
||||
'leixing': lx,
|
||||
'zhuangtai': 3,
|
||||
}
|
||||
if granularity == 'day':
|
||||
filter_kwargs['CreateTime__date'] = summary_q
|
||||
elif granularity == 'month':
|
||||
filter_kwargs['CreateTime__year'] = summary_q.year
|
||||
filter_kwargs['CreateTime__month'] = summary_q.month
|
||||
else:
|
||||
filter_kwargs['CreateTime__year'] = summary_q.year
|
||||
agg = filter_queryset_by_club(
|
||||
Czjilu.query.filter(**filter_kwargs), request,
|
||||
).aggregate(count=Count('id'), amount=Sum('jine'))
|
||||
count = agg['count'] or 0
|
||||
amount = float(agg['amount'] or 0)
|
||||
profit = amount if lx == 3 else 0.0
|
||||
summary_result['by_type'][str(lx)] = {
|
||||
'count': count,
|
||||
'amount': round(amount, 2),
|
||||
'profit': round(profit, 2),
|
||||
'fenhong_count': 0,
|
||||
'fenhong_amount': 0.0
|
||||
}
|
||||
summary_result['total_count'] += count
|
||||
summary_result['total_amount'] += amount
|
||||
summary_result['total_profit'] += profit
|
||||
|
||||
# 汇总罚款
|
||||
if 6 in types:
|
||||
penalty_filter = {
|
||||
'Status': 2,
|
||||
}
|
||||
fenhong_filter = {}
|
||||
if granularity == 'day':
|
||||
penalty_filter['CreateTime__date'] = summary_q
|
||||
fenhong_filter['CreateTime__date'] = summary_q
|
||||
elif granularity == 'month':
|
||||
penalty_filter['CreateTime__year'] = summary_q.year
|
||||
penalty_filter['CreateTime__month'] = summary_q.month
|
||||
fenhong_filter['CreateTime__year'] = summary_q.year
|
||||
fenhong_filter['CreateTime__month'] = summary_q.month
|
||||
else:
|
||||
penalty_filter['CreateTime__year'] = summary_q.year
|
||||
fenhong_filter['CreateTime__year'] = summary_q.year
|
||||
|
||||
penalty_agg = Penalty.query.filter(**penalty_filter).aggregate(
|
||||
count=Count('id'), amount=Sum('FineAmount'))
|
||||
penalty_count = penalty_agg['count'] or 0
|
||||
penalty_amount = float(penalty_agg['amount'] or 0)
|
||||
|
||||
fenhong_agg = PenaltyBonus.query.filter(**fenhong_filter).aggregate(
|
||||
fh_count=Count('id'), fh_amount=Sum('WithdrawableAmount'))
|
||||
fh_count = fenhong_agg['fh_count'] or 0
|
||||
fh_amount = float(fenhong_agg['fh_amount'] or 0)
|
||||
|
||||
profit = round(penalty_amount - fh_amount, 2)
|
||||
summary_result['by_type']['6'] = {
|
||||
'count': penalty_count,
|
||||
'amount': round(penalty_amount, 2),
|
||||
'profit': profit,
|
||||
'fenhong_count': fh_count,
|
||||
'fenhong_amount': round(fh_amount, 2)
|
||||
}
|
||||
summary_result['total_count'] += penalty_count
|
||||
summary_result['total_amount'] += penalty_amount
|
||||
summary_result['total_profit'] += profit
|
||||
|
||||
summary_result['total_amount'] = round(summary_result['total_amount'], 2)
|
||||
summary_result['total_profit'] = round(summary_result['total_profit'], 2)
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'time_series': time_series,
|
||||
'summary': summary_result
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
logger.exception("CwqtczhqView 接口错误")
|
||||
return Response({'code': 1, 'msg': f'服务器内部错误: {str(e)}'}, status=500)'''
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ==================== 后端接口 ====================
|
||||
# 文件:views.py (添加以下两个视图类)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user