feat(jituan): 集团多俱乐部改造 — club 隔离、财务/提现/分红/展示配置

This commit is contained in:
XingQue
2026-06-24 05:02:18 +08:00
parent ce9b09f096
commit dcc4936428
82 changed files with 5748 additions and 924 deletions

View File

@@ -0,0 +1,47 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('config', '0003_remove_shangjialianjie_shangjia_li_yonghu__9f2b25_idx_and_more'),
]
operations = [
migrations.AddField(
model_name='dailyincomestat',
name='club_id',
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='所属俱乐部'),
),
migrations.AddField(
model_name='dailypayoutstat',
name='club_id',
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='所属俱乐部'),
),
migrations.AlterField(
model_name='dailyincomestat',
name='date',
field=models.DateField(db_index=True, verbose_name='统计日期'),
),
migrations.AlterField(
model_name='dailypayoutstat',
name='date',
field=models.DateField(db_index=True, verbose_name='统计日期'),
),
migrations.AddIndex(
model_name='dailyincomestat',
index=models.Index(fields=['club_id', 'date'], name='idx_income_club_date'),
),
migrations.AddIndex(
model_name='dailypayoutstat',
index=models.Index(fields=['club_id', 'date'], name='idx_payout_club_date'),
),
migrations.AddConstraint(
model_name='dailyincomestat',
constraint=models.UniqueConstraint(fields=('date', 'club_id'), name='uniq_daily_income_club_date'),
),
migrations.AddConstraint(
model_name='dailypayoutstat',
constraint=models.UniqueConstraint(fields=('date', 'club_id'), name='uniq_daily_payout_club_date'),
),
]

View File

@@ -0,0 +1,37 @@
from django.db import migrations, models
def backfill_szjilu_club_id(apps, schema_editor):
Szjilu = apps.get_model('config', 'Szjilu')
for row in Szjilu.objects.all():
if not getattr(row, 'club_id', None):
row.club_id = 'xq'
row.save(update_fields=['club_id'])
class Migration(migrations.Migration):
dependencies = [
('config', '0004_daily_stat_club_id'),
]
operations = [
migrations.AddField(
model_name='szjilu',
name='club_id',
field=models.CharField(
db_index=True,
default='xq',
max_length=16,
verbose_name='俱乐部ID',
),
),
migrations.RunPython(backfill_szjilu_club_id, migrations.RunPython.noop),
migrations.AddConstraint(
model_name='szjilu',
constraint=models.UniqueConstraint(
fields=('club_id',),
name='uniq_szjilu_club_id',
),
),
]

View File

@@ -0,0 +1,60 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('config', '0005_szjilu_club_id'),
]
operations = [
migrations.AddField(
model_name='gonggao',
name='club_id',
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
),
migrations.AddField(
model_name='lunbo',
name='club_id',
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
),
migrations.AddField(
model_name='lunbo',
name='page_key',
field=models.CharField(
db_index=True, default='order_pool', max_length=32,
verbose_name='页面标识(order_pool/accept_order/merchant_home/dashou_center)',
),
),
migrations.AddField(
model_name='popuppage',
name='club_id',
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
),
migrations.AddField(
model_name='tupianpeizhi',
name='club_id',
field=models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID'),
),
migrations.AlterUniqueTogether(
name='popuppage',
unique_together=set(),
),
migrations.AlterField(
model_name='popuppage',
name='page_key',
field=models.CharField(db_index=True, max_length=50, verbose_name='页面标识'),
),
migrations.AlterUniqueTogether(
name='popuppage',
unique_together={('club_id', 'page_key')},
),
migrations.AlterUniqueTogether(
name='tupianpeizhi',
unique_together={('club_id', 'ImageType')},
),
migrations.AddIndex(
model_name='lunbo',
index=models.Index(fields=['club_id', 'page_key', 'ImageType'], name='lunbo_club_page_type_idx'),
),
]

View File

@@ -3,6 +3,11 @@ from gvsdsdk.model_base import QModel
class Lunbo(QModel):
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
page_key = models.CharField(
max_length=32, default='order_pool', db_index=True,
verbose_name='页面标识(order_pool/accept_order/merchant_home/dashou_center)',
)
ImageURL = models.CharField(max_length=500, null=True, blank=True, verbose_name='轮播图片URL')
ImageType = models.IntegerField(null=True, blank=True, verbose_name='图片类型:1轮播2个人中心背景')
@@ -12,10 +17,13 @@ class Lunbo(QModel):
db_table = 'lunbo'
verbose_name = '轮播'
verbose_name_plural = '轮播'
# 不要索引!数据量太小
indexes = [
models.Index(fields=['club_id', 'page_key', 'ImageType']),
]
class Gonggao(QModel):
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
NoticeType = models.IntegerField(null=True, blank=True, verbose_name='公告类型')
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更改时间')
@@ -37,6 +45,7 @@ class Qunpeizhi(QModel):
db_table = 'qunpeizhi'
class Tupianpeizhi(QModel):
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
ImageURL = models.CharField(max_length=500, null=True, blank=True, verbose_name='配置图片URL')
ImageType = models.PositiveIntegerField(null=True, blank=True, verbose_name='提现类型1:打手规则图片2默认头像图片')
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
@@ -46,10 +55,12 @@ class Tupianpeizhi(QModel):
db_table = 'tupianpeizhi'
verbose_name = '配置图片'
verbose_name_plural = '配置图片'
unique_together = [['club_id', 'ImageType']]
class Szjilu(QModel):
id = models.AutoField(primary_key=True, verbose_name='记录ID')
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
TotalIncome = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='总收益')
TotalFlow = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='总流水')
TotalExpense = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='总支出')
@@ -61,6 +72,9 @@ class Szjilu(QModel):
db_table = 'szjilu'
verbose_name = '收支记录'
verbose_name_plural = '收支记录'
constraints = [
models.UniqueConstraint(fields=['club_id'], name='uniq_szjilu_club_id'),
]
@@ -277,7 +291,10 @@ class DailyIncomeStat(QModel):
记录平台每天通过微信支付获得的总收入和总笔数
"""
# 日期相关字段(便于快速筛选和分组)
date = models.DateField(verbose_name='统计日期', db_index=True, unique=True)
date = models.DateField(verbose_name='统计日期', db_index=True)
club_id = models.CharField(
max_length=16, default='xq', db_index=True, verbose_name='所属俱乐部',
)
year = models.PositiveSmallIntegerField(verbose_name='年份')
month = models.PositiveSmallIntegerField(verbose_name='月份')
day = models.PositiveSmallIntegerField(verbose_name='日期')
@@ -297,6 +314,10 @@ class DailyIncomeStat(QModel):
indexes = [
models.Index(fields=['date']),
models.Index(fields=['year', 'month', 'day']),
models.Index(fields=['club_id', 'date']),
]
constraints = [
models.UniqueConstraint(fields=['date', 'club_id'], name='uniq_daily_income_club_date'),
]
def __str__(self):
@@ -310,7 +331,10 @@ class DailyPayoutStat(QModel):
每日出款统计表
记录平台每天通过提现/结算等方式的出款总额和总笔数
"""
date = models.DateField(verbose_name='统计日期', db_index=True, unique=True)
date = models.DateField(verbose_name='统计日期', db_index=True)
club_id = models.CharField(
max_length=16, default='xq', db_index=True, verbose_name='所属俱乐部',
)
year = models.PositiveSmallIntegerField(verbose_name='年份')
month = models.PositiveSmallIntegerField(verbose_name='月份')
day = models.PositiveSmallIntegerField(verbose_name='日期')
@@ -328,6 +352,10 @@ class DailyPayoutStat(QModel):
indexes = [
models.Index(fields=['date']),
models.Index(fields=['year', 'month', 'day']),
models.Index(fields=['club_id', 'date']),
]
constraints = [
models.UniqueConstraint(fields=['date', 'club_id'], name='uniq_daily_payout_club_date'),
]
def __str__(self):
@@ -337,7 +365,8 @@ class DailyPayoutStat(QModel):
class PopupPage(QModel):
page_key = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='页面标识')
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
page_key = models.CharField(max_length=50, db_index=True, verbose_name='页面标识')
name = models.CharField(max_length=100, verbose_name='页面名称')
description = models.TextField(blank=True, verbose_name='描述')
is_active = models.BooleanField(default=True, verbose_name='是否启用')
@@ -349,6 +378,7 @@ class PopupPage(QModel):
db_table = 'popup_page'
verbose_name = '弹窗页面'
verbose_name_plural = '弹窗页面'
unique_together = [['club_id', 'page_key']]
def __str__(self):
return f'{self.name} ({self.page_key})'

View File

@@ -1,76 +1,34 @@
"""
阿龙电竞 - 收支记录定时任务
处理收支记录表的每日清零
固定使用ID=1的记录
收支记录定时任务:每日清零各俱乐部今日流水/今日支出。
"""
from celery import shared_task
from django.db import transaction
from django.utils import timezone
from datetime import datetime
from decimal import Decimal
import logging
# 导入模型
from config.models import Szjilu
from jituan.services.szjilu_accounting import reset_all_daily_szjilu
from utils.celery_utils import log_task_execution, rollback_on_failure
logger = logging.getLogger(__name__)
@shared_task
@rollback_on_failure("收支记录每日清零")
def daily_sz_reset_task():
"""
收支记录每日清零 - 每天凌晨0点5分执行
清零收支记录表的今日相关字段固定使用ID=1的记录
"""
"""收支记录每日清零 - 每天凌晨0点5分执行。"""
try:
with transaction.atomic():
# 获取今天的日期
today = datetime.now().strftime('%Y-%m-%d')
today = datetime.now().strftime('%Y-%m-%d')
logger.info(f"开始执行收支记录清零任务,日期: {today}")
logger.info(f"开始执行收支记录清零任务,日期: {today}")
updated = reset_all_daily_szjilu()
result_msg = f"收支记录清零完成 - 日期: {today}, 俱乐部记录数: {updated}"
log_task_execution("收支记录每日清零", True, result_msg)
# 查询ID=1的收支记录如果不存在则创建
try:
szjilu = Szjilu.objects.get(id=1)
logger.info(f"找到ID=1的收支记录")
except Szjilu.DoesNotExist:
# 创建ID=1的初始记录
szjilu = Szjilu.objects.create(
id=1,
TotalIncome=Decimal('0.00'),
TotalFlow=Decimal('0.00'),
TotalExpense=Decimal('0.00'),
DailyExpense=Decimal('0.00'),
DailyFlow=Decimal('0.00')
)
logger.info(f"创建ID=1的收支记录")
# 记录清零前的值(用于日志)
old_jrzc = szjilu.DailyExpense
old_jrls = szjilu.DailyFlow
# 清零今日字段
szjilu.DailyExpense = Decimal('0.00')
szjilu.DailyFlow = Decimal('0.00')
szjilu.save(update_fields=['DailyExpense', 'DailyFlow', 'UpdateTime'])
logger.info(f"收支记录清零完成,今日支出原值: {old_jrzc},今日流水原值: {old_jrls}")
result_msg = f"收支记录清零完成 - 日期: {today}, 记录ID: {szjilu.id}"
log_task_execution("收支记录每日清零", True, result_msg)
return {
"success": True,
"date": today,
"record_id": szjilu.id,
"jrzc_cleared": True,
"jrls_cleared": True,
"old_jrzc": str(old_jrzc),
"old_jrls": str(old_jrls),
"message": result_msg
}
return {
"success": True,
"date": today,
"clubs_updated": updated,
"message": result_msg,
}
except Exception as e:
error_msg = f"收支记录每日清零失败: {str(e)}"
@@ -81,5 +39,5 @@ def daily_sz_reset_task():
"success": False,
"error": str(e),
"date": datetime.now().strftime('%Y-%m-%d'),
"message": error_msg
"message": error_msg,
}

View File

@@ -132,20 +132,17 @@ class ShangpinGonggaoView(APIView):
"""
try:
logger.info("收到商品公告和轮播图请求")
# 1. 查询公告类型为1
gonggao_obj = Gonggao.query.filter(NoticeType=1).first()
# 获取公告内容,如果没有则返回空字符串
gonggao_content = gonggao_obj.Content if gonggao_obj else ""
# 2. 查询轮播图类型为1按显示顺序排序
lunbo_queryset = Lunbo.query.filter(ImageType=1)
# 提取轮播图URL列表
lunbo_urls = [lunbo.ImageURL for lunbo in lunbo_queryset]
# 3. 构建返回数据
from jituan.services.display_config import get_gonggao_content, get_lunbo_urls, normalize_page_key
page_key = normalize_page_key(request.data.get('page_key'), image_type=1)
gonggao_content = get_gonggao_content(request, notice_type=1)
lunbo_urls = get_lunbo_urls(request, page_key=page_key, image_type=1)
response_data = {
"shangpingonggao": gonggao_content,
"shangpinlunbo": lunbo_urls
"shangpinlunbo": lunbo_urls,
"page_key": page_key,
}
logger.info(f"返回数据:公告长度{len(gonggao_content)},轮播图数量{len(lunbo_urls)}")
logger.info(f"返回数据:公告长度{len(gonggao_content)},轮播图数量{len(lunbo_urls)} page_key={page_key}")
# 4. 返回响应
return Response(response_data, status=status.HTTP_200_OK)
except Exception as e:
@@ -3158,9 +3155,13 @@ class PopupConfigView(APIView):
'msg': '缺少参数 pageKey'
}, status=status.HTTP_400_BAD_REQUEST)
# 3. 查询页面配置
# 3. 查询页面配置(按俱乐部)
from jituan.services.display_config import get_gonggao_content
from jituan.services.club_context import resolve_club_id_from_request
club_id = resolve_club_id_from_request(request)
try:
popup_page = PopupPage.query.get(page_key=page_key, is_active=True)
popup_page = PopupPage.query.get(club_id=club_id, page_key=page_key, is_active=True)
except PopupPage.DoesNotExist:
# 该页面没有配置弹窗,返回空
return Response({
@@ -3206,15 +3207,21 @@ class GetWithdrawModeView(APIView):
permission_classes = [IsAuthenticated] # JWT 认证
def post(self, request, *args, **kwargs):
# 尝试获取 ID=1 的记录
config = WithdrawConfig.query.filter(id=1).first()
# 如果存在则取实际值,否则默认 2手动提现
mode = config.mode if config else 2
from jituan.services.club_context import resolve_club_id_from_request
from jituan.services.club_user import get_user_club_id
from jituan.services.withdraw_config import get_withdraw_mode
user = getattr(request, 'user', None)
club_id = get_user_club_id(user) if user and getattr(user, 'is_authenticated', False) else None
if not club_id:
club_id = resolve_club_id_from_request(request)
mode = get_withdraw_mode(club_id)
return Response({
'code': 0,
'data': {
'mode': mode
'mode': mode,
'club_id': club_id,
}
})