完成后台多俱乐部数据隔离:订单/会员/财务/板块,并添加星之界初始化命令

This commit is contained in:
XingQue committed 2026-06-24 22:59:08 +08:00
1 parent 8154b2639d
commit 65613af195
7 files changed
+273 -184

No files matched your search

+51 -175
View File
@@ -2726,21 +2726,10 @@ class GetMemberListView(APIView):
if '3300a' not in permissions:
return Response({'code': 403, 'msg': '无权限查看会员列表'})
members = Huiyuan.query.all().order_by('-CreateTime')
member_list = []
for m in members:
member_list.append({
'huiyuan_id': m.huiyuan_id,
'jieshao': m.jieshao,
'jtjieshao': m.jtjieshao,
'jiage': str(m.jiage),
'guanshifc': str(m.guanshifc),
'zuzhangfc': str(m.zuzhangfc),
'goumai_cishu': m.goumai_cishu,
'CreateTime': m.CreateTime.isoformat() if m.CreateTime else None,
'UpdateTime': m.UpdateTime.isoformat() if m.UpdateTime else None,
})
return Response({'code': 0, 'data': {'list': member_list}})
from jituan.services.club_member_admin import build_member_list_payload
payload = build_member_list_payload(request)
return Response({'code': 0, 'data': {'list': payload['list']}, 'club_id': payload.get('club_id'), 'scope': payload.get('scope')})
class UpdateMemberView(APIView):
@@ -2790,19 +2779,13 @@ class UpdateMemberView(APIView):
if '3300a' not in permissions:
return Response({'code': 403, 'msg': '无权限修改会员'})
with transaction.atomic():
try:
member = Huiyuan.objects.select_for_update().get(huiyuan_id=huiyuan_id)
except Huiyuan.DoesNotExist:
return Response({'code': 404, 'msg': '会员不存在'})
member.jieshao = jieshao
member.jiage = jiage
member.guanshifc = guanshifc
member.zuzhangfc = zuzhangfc
member.jtjieshao = jtjieshao
member.save()
from jituan.services.club_member_admin import update_member_for_club
data, err = update_member_for_club(
request, huiyuan_id, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao,
)
if err:
return Response({'code': 400, 'msg': err})
return Response({'code': 0, 'msg': '修改成功'})
@@ -2851,26 +2834,14 @@ class AddMemberView(APIView):
if '3300a' not in permissions:
return Response({'code': 403, 'msg': '无权限添加会员'})
# 生成唯一6位会员ID(数字+字母)
def generate_huiyuan_id():
while True:
new_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
if not Huiyuan.query.filter(huiyuan_id=new_id).exists():
return new_id
from jituan.services.club_member_admin import add_member_for_club
with transaction.atomic():
new_id = generate_huiyuan_id()
member = Huiyuan.query.create(
huiyuan_id=new_id,
jieshao=jieshao,
jiage=jiage,
guanshifc=guanshifc,
zuzhangfc=zuzhangfc,
jtjieshao=jtjieshao,
goumai_cishu=0
)
return Response({'code': 0, 'msg': '添加成功', 'data': {'huiyuan_id': member.huiyuan_id}})
data, err = add_member_for_club(
request, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao,
)
if err:
return Response({'code': 400, 'msg': err})
return Response({'code': 0, 'msg': '添加成功', 'data': data})
@@ -6755,14 +6726,34 @@ class HqbkxxView(APIView):
'bankuai_id': sp.bankuai_id
})
# 7. 查询所有会员
# 7. 查询会员(附带当前俱乐部售价)
from jituan.constants import CLUB_ID_DEFAULT, DATA_SCOPE_ALL
from jituan.models import ClubHuiyuanPrice
from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
scope = resolve_club_scope(request)
club_id = resolve_club_id_from_request(request)
price_map = {}
if scope != DATA_SCOPE_ALL:
for row in ClubHuiyuanPrice.query.filter(club_id=club_id):
price_map[row.huiyuan_id] = row
huiyuan_list = []
for hy in Huiyuan.query.all():
huiyuan_list.append({
row = price_map.get(hy.huiyuan_id)
if scope != DATA_SCOPE_ALL and club_id != CLUB_ID_DEFAULT and row is None:
continue
if row is not None and not row.is_enabled:
continue
item = {
'huiyuan_id': hy.huiyuan_id,
'jieshao': hy.jieshao,
'bankuai_id': hy.bankuai_id
})
'bankuai_id': hy.bankuai_id,
'jiage': str(row.jiage) if row else str(hy.jiage),
'guanshifc': str(row.guanshifc) if row else str(hy.guanshifc),
'zuzhangfc': str(row.zuzhangfc) if row else str(hy.zuzhangfc),
}
huiyuan_list.append(item)
return Response({
'code': 0,
@@ -6770,7 +6761,9 @@ class HqbkxxView(APIView):
'data': {
'bankuai': bankuai_list,
'shangpin_leixing': shangpin_leixing_list,
'huiyuan': huiyuan_list
'huiyuan': huiyuan_list,
'club_id': club_id,
'scope': scope,
}
})
@@ -6935,131 +6928,14 @@ class CaiwuView(APIView):
if 'caiwu' not in permissions:
return Response({'code': 403, 'msg': '无财务查看权限'}, status=403)
today = date.today()
today_start = datetime.combine(today, datetime.min.time())
today_end = today_start + timedelta(days=1)
from jituan.services.club_context import (
filter_club_char_field,
filter_queryset_by_club,
resolve_club_id_from_request,
resolve_club_scope,
)
from jituan.services.daily_finance_display import (
cumulative_income_payout,
daily_stats_for_request,
sum_payout_stat_for_date,
today_income_for_dashboard,
)
order_qs = filter_queryset_by_club(Order.query.all(), request, club_field='ClubID')
# 四个板子:按 Header 俱乐部范围读 daily_income_stat / daily_payout_stat
today_income_amount, today_income_count = today_income_for_dashboard(today, request)
today_payout_amount, today_payout_count = sum_payout_stat_for_date(today, request)
# 今日订单(已付款及之后的状态)
today_orders = order_qs.filter(
CreateTime__gte=today_start,
CreateTime__lt=today_end,
Status__in=[1,2,3,4,5,6,7,8]
)
today_order_count = today_orders.count()
today_order_amount = float(today_orders.aggregate(total=Sum('Amount'))['total'] or 0.00)
# 今日成交(Status=3)
today_completed = Order.query.filter(
CreateTime__gte=today_start,
CreateTime__lt=today_end,
Status=3
)
today_completed_count = today_completed.count()
today_completed_amount = float(today_completed.aggregate(total=Sum('Amount'))['total'] or 0.00)
# 今日退款(Status=5)
today_tuikuan = Order.query.filter(
CreateTime__gte=today_start,
CreateTime__lt=today_end,
Status=5
)
today_tuikuan_count = today_tuikuan.count()
today_tuikuan_amount = float(today_tuikuan.aggregate(total=Sum('Amount'))['total'] or 0.00)
# 今日充值(会员充值,leixing=1, zhuangtai=3)
cz_qs = filter_club_char_field(Czjilu.query.all(), request, field='club_id')
today_chongzhi = cz_qs.filter(
CreateTime__gte=today_start,
CreateTime__lt=today_end,
leixing=1,
zhuangtai=3
)
today_chongzhi_count = today_chongzhi.count()
today_chongzhi_amount = float(today_chongzhi.aggregate(total=Sum('jine'))['total'] or 0.00)
# 今日新增会员
hy_qs = filter_club_char_field(Huiyuangoumai.query.all(), request, field='club_id')
new_huiyuan_user_ids = hy_qs.filter(
CreateTime__gte=today_start,
CreateTime__lt=today_end
).values_list('yonghu_id', flat=True).distinct()
today_new_huiyuan = len(new_huiyuan_user_ids)
# 新增会员充值总额(这些用户今天的会员充值金额)
today_new_huiyuan_amount = 0.0
if new_huiyuan_user_ids:
amount_sum = cz_qs.filter(
CreateTime__gte=today_start,
CreateTime__lt=today_end,
yonghuid__in=new_huiyuan_user_ids,
leixing=1,
zhuangtai=3
).aggregate(total=Sum('jine'))
today_new_huiyuan_amount = float(amount_sum['total'] or 0.0)
# 各角色可提现余额总和
all_guanshi_yue = float(UserGuanshi.query.aggregate(total=Sum('yue'))['total'] or 0.00)
all_dashou_yue = float(UserDashou.query.aggregate(total=Sum('yue'))['total'] or 0.00)
all_zuzhang_yue = float(UserZuzhang.query.aggregate(total=Sum('ketixian_jine'))['total'] or 0.00)
all_shangjia_yue = float(UserShangjia.query.aggregate(total=Sum('yue'))['total'] or 0.00)
# 累计收支 / 利润(当前俱乐部视图范围)
total_income, total_payout, platform_profit = cumulative_income_payout(request)
daily_stats = daily_stats_for_request(request, days=30)
return Response({
'code': 0,
'msg': 'ok',
'data': {
'club_id': resolve_club_id_from_request(request),
'scope': resolve_club_scope(request),
'today_income': round(today_income_amount, 2),
'today_income_count': today_income_count,
'today_payout': round(today_payout_amount, 2),
'today_payout_count': today_payout_count,
'today_order_count': today_order_count,
'today_order_amount': round(today_order_amount, 2),
'today_completed_count': today_completed_count,
'today_completed_amount': round(today_completed_amount, 2),
'today_tuikuan_count': today_tuikuan_count,
'today_tuikuan_amount': round(today_tuikuan_amount, 2),
'today_chongzhi_count': today_chongzhi_count,
'today_chongzhi_amount': round(today_chongzhi_amount, 2),
'today_new_huiyuan': today_new_huiyuan,
'today_new_huiyuan_amount': round(today_new_huiyuan_amount, 2),
'all_guanshi_yue': round(all_guanshi_yue, 2),
'all_dashou_yue': round(all_dashou_yue, 2),
'all_zuzhang_yue': round(all_zuzhang_yue, 2),
'all_shangjia_yue': round(all_shangjia_yue, 2),
'total_income': round(total_income, 2),
'total_payout': round(total_payout, 2),
'platform_profit': platform_profit,
'daily_stats': daily_stats
}
})
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):
@@ -0,0 +1,41 @@
"""一键初始化星之界(xzj)俱乐部及客服数据范围。"""
from django.core.management import call_command
from django.core.management.base import BaseCommand, CommandError
from jituan.models import Club
class Command(BaseCommand):
help = '创建星之界电竞俱乐部(xzj)并分配客服数据范围(需先 seed_club_xq)'
def add_arguments(self, parser):
parser.add_argument(
'--wx-appid', default='wxdefa454152e78a03', help='星之界小程序 appid',
)
parser.add_argument(
'--skip-assignments', action='store_true', help='跳过客服 admin_assignment',
)
def handle(self, *args, **options):
wx_appid = (options.get('wx_appid') or '').strip()
club_id = 'xzj'
name = '星之界电竞'
if Club.query.filter(club_id=club_id).exists():
self.stdout.write(self.style.WARNING(f'俱乐部 {club_id} 已存在,跳过 create_club'))
else:
self.stdout.write(f'创建俱乐部 {club_id} ...')
call_command(
'create_club',
club_id,
name=name,
wx_appid=wx_appid,
)
if not options.get('skip_assignments'):
self.stdout.write('分配客服数据范围 ...')
call_command('seed_admin_assignments', club_id=club_id)
self.stdout.write(self.style.SUCCESS(
f'星之界 {club_id} 就绪。请在后台「俱乐部配置」检查支付密钥,并在 xzj 视图下单独改轮播/会员价。'
))
+1
View File
@@ -3,6 +3,7 @@ from datetime import date, datetime, timedelta
from django.db.models import Sum
from jituan.constants import DATA_SCOPE_ALL
from jituan.services.club_context import (
filter_club_char_field,
filter_queryset_by_club,
+144
View File
@@ -0,0 +1,144 @@
"""后台会员管理:全局 Huiyuan 定义 + 各俱乐部 ClubHuiyuanPrice 售价/分成。"""
import random
import string
from decimal import Decimal
from django.db import transaction
from jituan.constants import CLUB_ID_DEFAULT, DATA_SCOPE_ALL
from jituan.models import ClubHuiyuanPrice
from jituan.services.club_context import club_id_for_write, resolve_club_id_from_request, resolve_club_scope
from jituan.services.club_user_access import list_response_meta
from products.models import Huiyuan
def _format_member(m, price_row=None):
item = {
'huiyuan_id': m.huiyuan_id,
'jieshao': m.jieshao,
'jtjieshao': m.jtjieshao,
'jiage': str(m.jiage),
'guanshifc': str(m.guanshifc),
'zuzhangfc': str(m.zuzhangfc),
'goumai_cishu': m.goumai_cishu,
'CreateTime': m.CreateTime.isoformat() if m.CreateTime else None,
'UpdateTime': m.UpdateTime.isoformat() if m.UpdateTime else None,
'bankuai_id': m.bankuai_id,
'club_enabled': True,
}
if price_row is not None:
item['jiage'] = str(price_row.jiage)
item['guanshifc'] = str(price_row.guanshifc)
item['zuzhangfc'] = str(price_row.zuzhangfc)
item['club_enabled'] = bool(price_row.is_enabled)
return item
def build_member_list_payload(request):
scope = resolve_club_scope(request)
club_id = resolve_club_id_from_request(request)
members = Huiyuan.query.all().order_by('-CreateTime')
member_list = []
if scope == DATA_SCOPE_ALL:
for m in members:
member_list.append(_format_member(m))
else:
price_map = {
r.huiyuan_id: r
for r in ClubHuiyuanPrice.query.filter(club_id=club_id)
}
for m in members:
row = price_map.get(m.huiyuan_id)
if row is None and club_id != CLUB_ID_DEFAULT:
continue
if row is not None and not row.is_enabled:
continue
member_list.append(_format_member(m, row))
return {
'list': member_list,
'club_id': club_id,
'scope': scope,
**list_response_meta(request),
}
def update_member_for_club(request, huiyuan_id, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao):
if resolve_club_scope(request) == DATA_SCOPE_ALL:
return None, '集团汇总视图下不可修改,请切换到具体俱乐部'
club_id = club_id_for_write(request)
with transaction.atomic():
try:
member = Huiyuan.objects.select_for_update().get(huiyuan_id=huiyuan_id)
except Huiyuan.DoesNotExist:
return None, '会员不存在'
member.jieshao = jieshao
member.jtjieshao = jtjieshao
member.save(update_fields=['jieshao', 'jtjieshao', 'UpdateTime'])
row, created = ClubHuiyuanPrice.query.get_or_create(
club_id=club_id,
huiyuan_id=huiyuan_id,
defaults={
'jiage': jiage,
'guanshifc': guanshifc,
'zuzhangfc': zuzhangfc,
'is_enabled': True,
'bankuai_id': member.bankuai_id,
},
)
if not created:
row.jiage = jiage
row.guanshifc = guanshifc
row.zuzhangfc = zuzhangfc
row.is_enabled = True
row.save()
if club_id == CLUB_ID_DEFAULT:
member.jiage = jiage
member.guanshifc = guanshifc
member.zuzhangfc = zuzhangfc
member.save(update_fields=['jiage', 'guanshifc', 'zuzhangfc', 'UpdateTime'])
return {'huiyuan_id': huiyuan_id}, None
def add_member_for_club(request, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao, bankuai_id=None):
if resolve_club_scope(request) == DATA_SCOPE_ALL:
return None, '集团汇总视图下不可添加,请切换到具体俱乐部'
club_id = club_id_for_write(request)
def generate_huiyuan_id():
while True:
new_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
if not Huiyuan.query.filter(huiyuan_id=new_id).exists():
return new_id
with transaction.atomic():
new_id = generate_huiyuan_id()
member = Huiyuan.query.create(
huiyuan_id=new_id,
jieshao=jieshao,
jiage=jiage,
guanshifc=guanshifc,
zuzhangfc=zuzhangfc,
jtjieshao=jtjieshao,
goumai_cishu=0,
bankuai_id=bankuai_id,
)
ClubHuiyuanPrice.query.create(
club_id=club_id,
huiyuan_id=new_id,
jiage=jiage,
guanshifc=guanshifc,
zuzhangfc=zuzhangfc,
is_enabled=True,
bankuai_id=bankuai_id,
)
return {'huiyuan_id': member.huiyuan_id}, None
+18 -2
View File
@@ -1,7 +1,7 @@
"""后台按俱乐部校验用户是否在当前数据范围内。"""
"""后台按俱乐部校验用户/订单是否在当前数据范围内。"""
from rest_framework.response import Response
from jituan.constants import DATA_SCOPE_ALL
from jituan.constants import CLUB_ID_DEFAULT, DATA_SCOPE_ALL
from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
@@ -25,3 +25,19 @@ def list_response_meta(request):
'club_id': resolve_club_id_from_request(request),
'scope': resolve_club_scope(request),
}
def order_belongs_to_request(order, request):
if resolve_club_scope(request) == DATA_SCOPE_ALL:
return True
club_id = resolve_club_id_from_request(request)
order_club = (getattr(order, 'ClubID', None) or '').strip()
if club_id == CLUB_ID_DEFAULT:
return order_club in (club_id, '', None) or not order_club
return order_club == club_id
def forbid_if_order_out_of_scope(request, order):
if order_belongs_to_request(order, request):
return None
return Response({'code': 403, 'msg': '该订单不属于当前俱乐部数据范围'})
+6
View File
@@ -1,11 +1,13 @@
from django.urls import path
from backend.view import (
AddMemberView,
FaKuanChuLiView,
FaKuanChuangJianView,
FaKuanLieBiaoView,
FaKuanTongJiView,
GetGuanliListView,
GetMemberListView,
GetOperationLogListView,
GetWithdrawSettingsView,
GetZuzhangListView,
@@ -13,6 +15,7 @@ from backend.view import (
KefuGetShangjiaListView,
PopupNoticeListAPIView,
PopupNoticeModifyAPIView,
UpdateMemberView,
UpdateWithdrawSettingsView,
)
from users.views import KefuPunishmentListView
@@ -70,5 +73,8 @@ urlpatterns = [
path('houtai/glyclfk', FaKuanChuLiView.as_view(), name='jituan_penalty_action'),
path('houtai/htfksc', FaKuanChuangJianView.as_view(), name='jituan_penalty_create'),
path('houtai/kefu-cfgl', KefuPunishmentListView.as_view(), name='jituan_kefu_cfgl'),
path('houtai/hthqhylb', GetMemberListView.as_view(), name='jituan_member_list'),
path('houtai/htxghyxx', UpdateMemberView.as_view(), name='jituan_member_update'),
path('houtai/httjhy', AddMemberView.as_view(), name='jituan_member_add'),
path('club/list', ClubListView.as_view(), name='jituan_club_list'),
]
+12 -7
View File
@@ -7084,17 +7084,17 @@ class KefuGetOrderListView(APIView):
)
from orders.models import PlatformOrderExt
# 旧 /yonghu/kfhqddlb 不按俱乐部过滤,与改造前行为一致
stats_qs = order_scope_qs(None).filter(q_conditions)
# 按俱乐部过滤订单列表
stats_qs = order_scope_qs(request).filter(q_conditions)
stats = stats_qs.aggregate(
total_orders=Count('id'),
completed_orders=Count('id', filter=Q(Status=3)),
refund_orders=Count('id', filter=Q(Status=5)),
pending_orders=Count('id', filter=Q(Status__in=[4, 8]))
)
status_counts = count_order_status_buckets(q_conditions, request=None)
status_counts = count_order_status_buckets(q_conditions, request)
list_qs = order_scope_qs(None).filter(q_conditions).order_by('-CreateTime')
list_qs = order_scope_qs(request).filter(q_conditions).order_by('-CreateTime')
total_count, orders_page = paginate_fluent_query(list_qs, page, page_size)
if total_count > 0 and not orders_page:
start = (max(1, page) - 1) * page_size
@@ -7256,16 +7256,16 @@ class KefuGetShangjiaOrderListView(APIView):
)
from orders.models import MerchantOrderExt
stats_qs = order_scope_qs(None).filter(q_conditions)
stats_qs = order_scope_qs(request).filter(q_conditions)
stats = stats_qs.aggregate(
total_orders=Count('id'),
completed_orders=Count('id', filter=Q(Status=3)),
refund_orders=Count('id', filter=Q(Status=5)),
pending_orders=Count('id', filter=Q(Status__in=[4, 8]))
)
status_counts = count_order_status_buckets(q_conditions, request=None)
status_counts = count_order_status_buckets(q_conditions, request)
list_qs = order_scope_qs(None).filter(q_conditions).order_by('-CreateTime')
list_qs = order_scope_qs(request).filter(q_conditions).order_by('-CreateTime')
total_count, orders_page = paginate_fluent_query(list_qs, page, page_size)
if total_count > 0 and not orders_page:
start = (max(1, page) - 1) * page_size
@@ -9118,6 +9118,11 @@ class KefuGetOrderDetailView(APIView):
logger.exception(f"[kefuhqddxq] 订单查询异常 dingdan_id={dingdan_id}")
return Response({'code': 500, 'msg': '服务器错误'}, status=500)
from jituan.services.club_user_access import forbid_if_order_out_of_scope
deny = forbid_if_order_out_of_scope(request, dingdan_obj)
if deny:
return deny
# 4. 构建基础数据
try:
response_data = {