feat(jituan): 订单列表修复、财务 club 过滤、数据范围 API

This commit is contained in:
XingQue
2026-06-24 06:07:41 +08:00
parent 32486a6e77
commit c42f979082
6 changed files with 161 additions and 66 deletions

View File

@@ -0,0 +1,41 @@
"""回填订单 ClubID空值设为 xq"""
from django.core.management.base import BaseCommand
from django.db import transaction
from jituan.constants import CLUB_ID_DEFAULT
from orders.models import Order
class Command(BaseCommand):
help = '回填 Order.ClubID空或缺省设为 xq'
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help='仅统计不落库',
)
def handle(self, *args, **options):
dry_run = options.get('dry_run', False)
updated = 0
skipped = 0
for order in Order.query.all().only('id', 'OrderID', 'ClubID'):
current = getattr(order, 'ClubID', None) or ''
if current:
skipped += 1
continue
if dry_run:
self.stdout.write(f'[dry-run] {order.OrderID} -> ClubID={CLUB_ID_DEFAULT}')
updated += 1
continue
with transaction.atomic():
order.ClubID = CLUB_ID_DEFAULT
order.save(update_fields=['ClubID'])
updated += 1
suffix = 'dry-run' if dry_run else ''
self.stdout.write(self.style.SUCCESS(f'完成{suffix}:更新 {updated},跳过 {skipped}'))

View File

@@ -5,8 +5,13 @@ from decimal import Decimal
from django.db.models import Sum
from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
from jituan.constants import DATA_SCOPE_ALL, CLUB_ID_DEFAULT
from jituan.services.club_context import (
filter_club_char_field,
filter_queryset_by_club,
resolve_club_id_from_request,
resolve_club_scope,
)
from jituan.constants import DATA_SCOPE_ALL
from config.models import DailyIncomeStat, DailyPayoutStat, Szjilu
from orders.models import Order
@@ -50,53 +55,23 @@ def _build_szjilu_payload(request):
def _order_qs(request):
qs = Order.query.all()
if resolve_club_scope(request) == DATA_SCOPE_ALL:
return qs
club_id = resolve_club_id_from_request(request)
if hasattr(Order, 'ClubID'):
return qs.filter(ClubID=club_id)
return qs
return filter_queryset_by_club(Order.query.all(), request, club_field='ClubID')
def _czjilu_qs(request):
qs = Czjilu.query.all()
if resolve_club_scope(request) == DATA_SCOPE_ALL:
return qs
club_id = resolve_club_id_from_request(request)
if hasattr(Czjilu, 'club_id'):
return qs.filter(club_id=club_id)
return qs
return filter_club_char_field(Czjilu.query.all(), request, field='club_id')
def _huiyuangoumai_qs(request):
qs = Huiyuangoumai.query.all()
if resolve_club_scope(request) == DATA_SCOPE_ALL:
return qs
club_id = resolve_club_id_from_request(request)
if hasattr(Huiyuangoumai, 'club_id'):
return qs.filter(club_id=club_id)
return qs
return filter_club_char_field(Huiyuangoumai.query.all(), request, field='club_id')
def _daily_income_qs(request):
qs = DailyIncomeStat.query.all()
if resolve_club_scope(request) == DATA_SCOPE_ALL:
return qs
club_id = resolve_club_id_from_request(request)
if hasattr(DailyIncomeStat, 'club_id'):
return qs.filter(club_id=club_id)
return qs
return filter_club_char_field(DailyIncomeStat.query.all(), request, field='club_id')
def _daily_payout_qs(request):
qs = DailyPayoutStat.query.all()
if resolve_club_scope(request) == DATA_SCOPE_ALL:
return qs
club_id = resolve_club_id_from_request(request)
if hasattr(DailyPayoutStat, 'club_id'):
return qs.filter(club_id=club_id)
return qs
return filter_club_char_field(DailyPayoutStat.query.all(), request, field='club_id')
def build_caiwu_payload(request):

View File

@@ -46,18 +46,40 @@ def get_active_club(club_id):
return None
def filter_club_char_field(qs, request, field='club_id'):
"""按 club_id 字符字段过滤xq 视图兼容历史空值。"""
scope = resolve_club_scope(request)
if scope == DATA_SCOPE_ALL:
return qs
if not hasattr(qs, 'filter'):
return qs
club_id = resolve_club_id_from_request(request)
if club_id == CLUB_ID_DEFAULT:
from django.db.models import Q
return qs.filter(
Q(**{field: club_id}) | Q(**{f'{field}__isnull': True}) | Q(**{field: ''})
)
return qs.filter(**{field: club_id})
def filter_queryset_by_club(qs, request, club_field='club_id'):
"""
按请求上下文过滤 QuerySet / FluentQuery。
集团 ALL_CLUBS 视图不过滤;子公司 SINGLE_CLUB 按 club_id 过滤。
历史订单 ClubID 为空时视为 xq避免列表被滤空。
"""
scope = resolve_club_scope(request)
club_id = resolve_club_id_from_request(request)
if scope == DATA_SCOPE_ALL:
return qs
if hasattr(qs, 'filter'):
return qs.filter(**{club_field: club_id})
return qs
if not hasattr(qs, 'filter'):
return qs
if club_field == 'ClubID' and club_id == CLUB_ID_DEFAULT:
from django.db.models import Q
return qs.filter(
Q(ClubID=club_id) | Q(ClubID__isnull=True) | Q(ClubID='')
)
return qs.filter(**{club_field: club_id})
def club_id_for_write(request):

View File

@@ -18,6 +18,7 @@ from backend.view import (
from users.views import KefuPunishmentListView
from jituan.views_display import ClubGonggaoView, ClubLunboListView, ClubLunboManageView
from jituan.views import (
ClubAdminAssignmentListView,
ClubAuditLogListView,
ClubCaiwuView,
ClubChongzhiFinanceView,
@@ -39,6 +40,7 @@ urlpatterns = [
path('auth/kefu-login', ClubKefuLoginView.as_view(), name='jituan_kefu_login'),
path('auth/me-context', ClubMeContextView.as_view(), name='jituan_me_context'),
path('auth/dashou-register', ClubDashouRegisterView.as_view(), name='jituan_dashou_register'),
path('houtai/admin-assignments', ClubAdminAssignmentListView.as_view(), name='jituan_admin_assignments'),
path('houtai/caiwu', ClubCaiwuView.as_view(), name='jituan_caiwu'),
path('houtai/szxx', ClubSzxxView.as_view(), name='jituan_szxx'),
path('houtai/audit-log', ClubAuditLogListView.as_view(), name='jituan_audit_log'),

View File

@@ -9,7 +9,8 @@ from rest_framework.views import APIView
from rest_framework.throttling import AnonRateThrottle
from rest_framework_simplejwt.tokens import RefreshToken
from jituan.models import Club
from jituan.models import Club, AdminAssignment
from jituan.constants import ADMIN_ROLE_LABELS
from jituan.services.admin_context import build_admin_club_context
from jituan.services.wechat_login import login_or_register_by_club
from jituan.services.caiwu_stats import build_caiwu_payload
@@ -193,6 +194,64 @@ class ClubMeContextView(APIView):
})
class ClubAdminAssignmentListView(APIView):
"""POST /jituan/houtai/admin-assignments — 查看数据范围任职(非 gvsdsdk 功能权限)。"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = (request.data.get('phone') or request.data.get('username') or '').strip()
if not username:
return Response({'code': 401, 'msg': '缺少账号'}, status=401)
kefu_obj, permissions = verify_kefu_permission(request, username)
if kefu_obj is None:
return permissions
user = request.user
is_super = bool(user.IsSuperuser) or user.UserType == 'admin'
if is_super:
qs = AdminAssignment.query.filter(status=1).order_by('yonghuid', '-is_primary', 'club_id')
else:
qs = AdminAssignment.query.filter(yonghuid=user.UserUID, status=1).order_by('-is_primary')
club_name_map = {
c.club_id: c.name for c in Club.query.filter(status=1)
}
uids = list({a.yonghuid for a in qs})
user_map = {}
if uids:
for u in User.query.filter(UserUID__in=uids).only('UserUID', 'Phone', 'UserName'):
user_map[u.UserUID] = u.Phone or u.UserName or u.UserUID
rows = []
for a in qs:
club_label = '集团(全部子公司)'
if a.club_id:
club_label = club_name_map.get(a.club_id, a.club_id)
rows.append({
'yonghuid': a.yonghuid,
'phone': user_map.get(a.yonghuid, ''),
'club_id': a.club_id,
'club_label': club_label,
'role_code': a.role_code,
'role_name': ADMIN_ROLE_LABELS.get(a.role_code, a.role_code),
'data_scope': a.data_scope,
'is_primary': a.is_primary,
})
return Response({
'code': 0,
'msg': 'ok',
'data': {
'list': rows,
'perm_note': (
'功能菜单权限(能进哪些页面)在「角色管理」配置;'
'本表 admin_assignment 仅控制集团/子公司数据范围。'
),
'current_context': build_admin_club_context(user),
},
})
class ClubCaiwuView(APIView):
"""
俱乐部维度财务(新接口)。

View File

@@ -7100,19 +7100,17 @@ class KefuGetOrderListView(APIView):
'6': platform_orders.filter(Status=6).count(),
}
# ---------- 分页查询 ----------
total_count = club_orders.filter(q_conditions).count()
orders_qs = club_orders.filter(q_conditions).select_related(
'pingtai_kuozhan'
).only(
'OrderID', 'Description', 'Status', 'ProductTypeID', 'Platform',
'Amount', 'ImageURL', 'CreateTime', 'Nickname', 'PlayerID',
'pingtai_kuozhan__BossID'
).order_by('-CreateTime')[(page-1)*page_size : page*page_size]
# ---------- 分页查询(避免 only+select_related 导致列表为空) ----------
list_qs = club_orders.filter(q_conditions).select_related('pingtai_kuozhan').order_by('-CreateTime')
total_count = list_qs.count()
orders_page = list_qs[(page - 1) * page_size: page * page_size].to_list()
# ---------- 获取老板昵称 ----------
laoban_ids = [o.pingtai_kuozhan.BossID for o in orders_qs if hasattr(o, 'pingtai_kuozhan') and o.pingtai_kuozhan.BossID]
laoban_ids = [
o.pingtai_kuozhan.BossID
for o in orders_page
if getattr(o, 'pingtai_kuozhan', None) and o.pingtai_kuozhan.BossID
]
nickname_map = {}
if laoban_ids:
users = User.query.filter(UserUID__in=laoban_ids).select_related('BossProfile').only(
@@ -7122,7 +7120,7 @@ class KefuGetOrderListView(APIView):
nickname_map[user.UserUID] = (user.BossProfile.nickname or '') if getattr(user, 'BossProfile', None) else ''
result_list = []
for order in orders_qs:
for order in orders_page:
ext = getattr(order, 'pingtai_kuozhan', None)
laoban_id = ext.BossID if ext else ''
youxi_nicheng = nickname_map.get(laoban_id, order.Nickname or '')
@@ -7183,8 +7181,8 @@ class KefuGetShangjiaOrderListView(APIView):
def post(self, request):
start_total = time.time()
# 1. 获取前端参数
username = request.data.get('username', '').strip()
# 1. 获取前端参数(兼容 phone / username
username = request.data.get('username', '').strip() or request.data.get('phone', '').strip()
if not username:
return Response({'code': 400, 'msg': '缺少username'})
@@ -7277,18 +7275,16 @@ class KefuGetShangjiaOrderListView(APIView):
}
# ---------- 分页查询 ----------
total_count = club_orders.filter(q_conditions).count()
orders_qs = club_orders.filter(q_conditions).select_related(
'shangjia_kuozhan'
).only(
'OrderID', 'Description', 'Status', 'ProductTypeID', 'Platform',
'Amount', 'ImageURL', 'CreateTime', 'Nickname', 'PlayerID',
'shangjia_kuozhan__MerchantID'
).order_by('-CreateTime')[(page-1)*page_size : page*page_size]
list_qs = club_orders.filter(q_conditions).select_related('shangjia_kuozhan').order_by('-CreateTime')
total_count = list_qs.count()
orders_page = list_qs[(page - 1) * page_size: page * page_size].to_list()
# ---------- 获取商家昵称 ----------
shangjia_ids = [o.shangjia_kuozhan.MerchantID for o in orders_qs if hasattr(o, 'shangjia_kuozhan') and o.shangjia_kuozhan.MerchantID]
shangjia_ids = [
o.shangjia_kuozhan.MerchantID
for o in orders_page
if getattr(o, 'shangjia_kuozhan', None) and o.shangjia_kuozhan.MerchantID
]
nickname_map = {}
if shangjia_ids:
users = User.query.filter(UserUID__in=shangjia_ids).select_related('ShopProfile').only(
@@ -7298,7 +7294,7 @@ class KefuGetShangjiaOrderListView(APIView):
nickname_map[user.UserUID] = user.ShopProfile.nicheng or '' if hasattr(user, 'ShopProfile') else ''
result_list = []
for order in orders_qs:
for order in orders_page:
ext = getattr(order, 'shangjia_kuozhan', None)
shangjia_id = ext.MerchantID if ext else ''
youxi_nicheng = order.Nickname or '' # 商家订单的玩家昵称