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,24 @@
# Generated manually for jituan multi-club
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0003_delete_dingdan_delete_dingdanpingtai_and_more'),
]
operations = [
migrations.AddField(
model_name='order',
name='ClubID',
field=models.CharField(
db_column='club_id',
db_index=True,
default='xq',
max_length=16,
verbose_name='所属俱乐部',
),
),
]

View File

@@ -0,0 +1,22 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0004_order_clubid'),
]
operations = [
migrations.AddField(
model_name='penalty',
name='ClubID',
field=models.CharField(
db_column='club_id',
db_index=True,
default='xq',
max_length=16,
verbose_name='所属俱乐部',
),
),
]

View File

@@ -0,0 +1,22 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0005_penalty_clubid'),
]
operations = [
migrations.AddField(
model_name='penaltybonus',
name='ClubID',
field=models.CharField(
db_column='club_id',
db_index=True,
default='xq',
max_length=16,
verbose_name='所属俱乐部',
),
),
]

View File

@@ -0,0 +1,22 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0006_penaltybonus_clubid'),
]
operations = [
migrations.AddField(
model_name='penaltyrecord',
name='ClubID',
field=models.CharField(
db_column='club_id',
db_index=True,
default='xq',
max_length=16,
verbose_name='所属俱乐部',
),
),
]

View File

@@ -51,6 +51,10 @@ class Order(QModel):
null=True, blank=True,
db_column='fadan_pingtai', verbose_name='发单平台',
)
ClubID = models.CharField(
max_length=16, default='xq', db_index=True,
db_column='club_id', verbose_name='所属俱乐部',
)
# ---- 金额 ----
Amount = models.DecimalField(
max_digits=10, decimal_places=2, null=True, blank=True,
@@ -502,6 +506,10 @@ class Penalty(QModel):
max_digits=12, decimal_places=2, default=0.00,
db_column='shenqingren_fenhong_jine', verbose_name='申请人分红金额',
)
ClubID = models.CharField(
max_length=16, default='xq', db_index=True,
db_column='club_id', verbose_name='所属俱乐部',
)
class Meta:
db_table = 'fadan'
@@ -599,6 +607,10 @@ class PenaltyBonus(QModel):
auto_now=True,
verbose_name='更新时间',
)
ClubID = models.CharField(
max_length=16, default='xq', db_index=True,
db_column='club_id', verbose_name='所属俱乐部',
)
class Meta:
db_table = 'fadan_fenhong'
@@ -762,6 +774,10 @@ class PenaltyRecord(QModel):
max_length=32, null=True, db_index=True,
db_column='dingdan_id', verbose_name='订单ID',
)
ClubID = models.CharField(
max_length=16, default='xq', db_index=True,
db_column='club_id', verbose_name='所属俱乐部',
)
CreateTime = models.DateTimeField(
auto_now_add=True,
verbose_name='创建时间',

View File

@@ -7,7 +7,9 @@ from django.db.models import F
from config.models import DailyIncomeStat, DailyPayoutStat
from orders.models import Order, PlatformOrderExt, MerchantOrderExt, CommissionRate
from jituan.services.club_config import get_commission_rate
from products.models import Gsfenhong
from jituan.services.club_penalty import resolve_gsfenhong_club_id
from users.models import UserDashou, UserGuanshi
from backend.utils import update_guanshi_daily_by_action
@@ -31,71 +33,66 @@ def get_order_user_id(order):
return None
def update_daily_income(amount):
def update_daily_income(amount, club_id=None):
"""
原子更新当日收入统计金额累加笔数加1
用于微信支付成功回调。
参数:
amount: Decimal 本次收入金额
"""
from jituan.constants import CLUB_ID_DEFAULT
cid = club_id or CLUB_ID_DEFAULT
today = date.today()
year, month, day = today.year, today.month, today.day
with transaction.atomic():
stat, created = DailyIncomeStat.objects.select_for_update().get_or_create(
date=today,
club_id=cid,
defaults={
'year': year,
'month': month,
'day': day,
'total_amount': amount,
'total_count': 1
}
'total_count': 1,
},
)
if not created:
# 原子累加
stat.total_amount = F('total_amount') + amount
stat.total_count = F('total_count') + 1
stat.save(update_fields=['total_amount', 'total_count'])
logger.info(
f"每日收入更新: {today}, +{amount}, 总金额={stat.total_amount if created else stat.total_amount + amount}, 总笔数={stat.total_count if created else stat.total_count + 1}")
f"每日收入更新[{cid}]: {today}, +{amount}"
)
def update_daily_payout(amount):
def update_daily_payout(amount, club_id=None):
"""
原子更新当日出款统计金额累加笔数加1
用于提现成功、结算打款等场景。
参数:
amount: Decimal 本次出款金额
"""
from jituan.constants import CLUB_ID_DEFAULT
cid = club_id or CLUB_ID_DEFAULT
today = date.today()
year, month, day = today.year, today.month, today.day
with transaction.atomic():
stat, created = DailyPayoutStat.objects.select_for_update().get_or_create(
date=today,
club_id=cid,
defaults={
'year': year,
'month': month,
'day': day,
'total_amount': amount,
'total_count': 1
}
'total_count': 1,
},
)
if not created:
# 原子累加
stat.total_amount = F('total_amount') + amount
stat.total_count = F('total_count') + 1
stat.save(update_fields=['total_amount', 'total_count'])
logger.info(f"每日出款更新: {today}, +{amount}元, 总金额={stat.total_amount if created else stat.total_amount + amount}, 总笔数={stat.total_count if created else stat.total_count + 1}")
logger.info(f"每日出款更新[{cid}]: {today}, +{amount}")
@@ -104,24 +101,19 @@ def update_daily_payout(amount):
def calc_shangjia_order_fencheng(jine):
def calc_shangjia_order_fencheng(jine, club_id=None):
"""
商家发单时计算打手/管事分成。
打手优先:打手+管事 > 订单金额时,管事分成为 0。
"""
from jituan.constants import CLUB_ID_DEFAULT
jine = Decimal(str(jine))
try:
lilu_obj = CommissionRate.objects.get(Platform='3')
rate_dashou = Decimal(str(lilu_obj.Rate)) if lilu_obj.Rate and lilu_obj.Rate > 0 else Decimal('1')
except CommissionRate.DoesNotExist:
cid = club_id or CLUB_ID_DEFAULT
rate_dashou = get_commission_rate(cid, '3')
if not rate_dashou or rate_dashou <= 0:
rate_dashou = Decimal('1')
lilu_guanshi_obj = CommissionRate.objects.filter(Platform='13').first()
rate_guanshi = (
Decimal(str(lilu_guanshi_obj.Rate))
if lilu_guanshi_obj and lilu_guanshi_obj.Rate is not None
else Decimal('0')
)
rate_guanshi = get_commission_rate(cid, '13')
dashou_fencheng = (jine * rate_dashou).quantize(Decimal('0.01'))
guanshi_raw = (jine * rate_guanshi).quantize(Decimal('0.01'))
@@ -182,6 +174,7 @@ def settle_shangjia_order_guanshi_fenhong(order, dashou_id):
avatar=user_main.Avatar if user_main else None,
nicheng=dashou.nicheng or '未知打手',
fenhong_leixing=3,
club_id=resolve_gsfenhong_club_id(dingdan_id=order.OrderID, dashouid=dashou_id, order=order),
)
logger.info(
f"商家订单管事分红成功: 订单{order.OrderID}, 管事{guanshi_id}, "

View File

@@ -56,17 +56,18 @@ from orders.notice_tasks import dingdan_guangbo
from .models import (
Order, MerchantOrderExt, PlatformOrderExt, PlayerDeliveryImage,
PenaltyRecord, PenaltyEvidenceImage, Penalty, PenaltyBonus
)
from orders.models import (
CommissionRate, PlayerRating, RefundRecord
PenaltyRecord, PenaltyEvidenceImage, Penalty, PenaltyBonus,
CommissionRate, PlayerRating, RefundRecord,
)
from jituan.services.club_config import get_commission_rate, get_commission_rate_object
from jituan.services.club_context import resolve_club_id_from_request
from jituan.services.club_penalty import resolve_penalty_club_id
from products.models import Shangpin, ShangpinLeixing, Huiyuangoumai
from users.models import UserDashou, UserShangjia, UserBoss
from users.business_models import User
from rank.models import DashouBiaoxian, Chenghao, DingdanBiaoqian, YonghuChenghao
from config.models import (
Szjilu, ShangjiaLianjie
ShangjiaLianjie
)
@@ -133,8 +134,11 @@ class WechatPayNotifyView(APIView):
out_trade_no = root.find('out_trade_no').text if root.find('out_trade_no') is not None else ''
transaction_id = root.find('transaction_id').text if root.find('transaction_id') is not None else ''
from jituan.services.wechat_pay import club_id_from_order, verify_wechat_v2_signature
notify_club_id = club_id_from_order(out_trade_no)
# 3. 签名验证
if not self._verify_signature(xml_data):
if not verify_wechat_v2_signature(xml_data, notify_club_id):
logger.warning("签名验证失败")
return self._gen_response_xml('FAIL', '签名验证失败')
@@ -303,10 +307,10 @@ class WechatPayNotifyView(APIView):
# 更新收支记录(总累计)
self._update_szjilu(dingdan.Amount)
self._update_szjilu(dingdan.Amount, getattr(dingdan, 'ClubID', None))
# 更新每日收入统计
update_daily_income(dingdan.Amount)
update_daily_income(dingdan.Amount, getattr(dingdan, 'ClubID', None))
# 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用)
order_info = {
@@ -346,28 +350,12 @@ class WechatPayNotifyView(APIView):
logger.error(f"处理支付失败异常: {e}")
logger.error(traceback.format_exc())
def _update_szjilu(self, jine):
"""更新收支记录表(ID=1的记录"""
def _update_szjilu(self, jine, club_id=None):
"""更新收支记录表(按俱乐部"""
from jituan.services.szjilu_accounting import apply_szjilu_income
try:
jine_decimal = Decimal(str(jine))
with transaction.atomic():
szjilu, created = Szjilu.objects.select_for_update().get_or_create(
id=1,
defaults={
'TotalIncome': Decimal('0.00'),
'TotalFlow': Decimal('0.00'),
'TotalExpense': Decimal('0.00'),
'DailyExpense': Decimal('0.00'),
'DailyFlow': Decimal('0.00')
}
)
if created:
logger.info("创建ID=1的收支记录")
szjilu.TotalIncome += jine_decimal
szjilu.TotalFlow += jine_decimal
szjilu.DailyFlow += jine_decimal
szjilu.save()
logger.info(f"收支记录更新成功,增加金额: {jine}")
apply_szjilu_income(jine, club_id)
logger.info(f"收支记录更新成功,增加金额: {jine}, club={club_id}")
except Exception as e:
logger.error(f"更新收支记录失败: {e}")
@@ -580,10 +568,10 @@ class PaymentVerifyView(APIView):
# 更新收支记录
self._update_szjilu(dingdan.Amount)
self._update_szjilu(dingdan.Amount, getattr(dingdan, 'ClubID', None))
# 更新每日收入统计
update_daily_income(dingdan.Amount)
update_daily_income(dingdan.Amount, getattr(dingdan, 'ClubID', None))
def _query_wechat_payment(self, out_trade_no):
"""
@@ -620,28 +608,12 @@ class PaymentVerifyView(APIView):
err_msg = result.get('return_msg', '未知错误')
raise Exception(f"微信订单查询失败: {err_msg}")
def _update_szjilu(self, jine):
"""更新收支记录表(ID=1的记录"""
def _update_szjilu(self, jine, club_id=None):
"""更新收支记录表(按俱乐部"""
from jituan.services.szjilu_accounting import apply_szjilu_income
try:
jine_decimal = Decimal(str(jine))
with transaction.atomic():
szjilu, created = Szjilu.objects.select_for_update().get_or_create(
id=1,
defaults={
'TotalIncome': Decimal('0.00'),
'TotalFlow': Decimal('0.00'),
'TotalExpense': Decimal('0.00'),
'DailyExpense': Decimal('0.00'),
'DailyFlow': Decimal('0.00')
}
)
if created:
logger.info("创建ID=1的收支记录")
szjilu.TotalIncome += jine_decimal
szjilu.TotalFlow += jine_decimal
szjilu.DailyFlow += jine_decimal
szjilu.save()
logger.info(f"收支记录更新成功,增加金额: {jine}")
apply_szjilu_income(jine, club_id)
logger.info(f"收支记录更新成功,增加金额: {jine}, club={club_id}")
except Exception as e:
logger.error(f"更新收支记录失败: {e}")
# 不抛出异常,因为收支记录不是核心流程,但记录错误供排查
@@ -738,8 +710,8 @@ class CreateOrderView(APIView):
dingdanid = f"PT{timestamp:011d}{timestamp_ns:06d}{random_num:02d}"
# 6. 查询利率并计算分成
# 查询打手利率(字符'1'
dashou_lilu = CommissionRate.query.filter(Platform='1').first()
club_id = resolve_club_id_from_request(request)
dashou_lilu = get_commission_rate_object(club_id, '1')
# 🔥 新增:判断是否使用商品配置的额外分成
@@ -754,11 +726,9 @@ class CreateOrderView(APIView):
# 查询管事利率(字符'2'
#guanshi_lilu = CommissionRate.query.filter(Platform='2').first()
if not dashou_lilu: #or not guanshi_lilu:
return Response({'code': 9, 'msg': '系统利率配置不完整', 'data': None})
# 计算打手分成
#dashou_fencheng = Decimal(str(shangpin_jiage)) * Decimal(str(dashou_lilu.Rate))
if not (shangpin.kaioi_ewai_dashou_fencheng and shangpin.ewai_dashou_fencheng is not None):
if get_commission_rate(club_id, '1') <= 0 and not CommissionRate.query.filter(Platform='1').exists():
return Response({'code': 9, 'msg': '系统利率配置不完整', 'data': None})
# 开始数据库事务
@@ -788,7 +758,8 @@ class CreateOrderView(APIView):
'Nickname': nicheng,
'ImageURL':shangpin_tupian,
'User1ID':f"Boss{request.user.UserUID}",
'ProductTypeID':shangpin.leixing_id
'ProductTypeID':shangpin.leixing_id,
'ClubID': getattr(request, 'club_id', None) or 'xq',
}
# 创建订单记录
@@ -1943,7 +1914,8 @@ class ShangjiaPaifaView(APIView):
return Response({'code': 400, 'msg': '指定用户不是打手'})
# ----- 10. 利率(打手+管事分成,打手优先) -----
dashou_fencheng, guanshi_fencheng = calc_shangjia_order_fencheng(jiage)
club_id = resolve_club_id_from_request(request)
dashou_fencheng, guanshi_fencheng = calc_shangjia_order_fencheng(jiage, club_id=club_id)
# ----- 11. 生成订单ID -----
def generate_dingdan_id():
@@ -1973,7 +1945,8 @@ class ShangjiaPaifaView(APIView):
Remark=dingdan_beizhu[:1000],
Nickname=laoban_name[:50],
ImageURL=request.user.Avatar,
User1ID=f"Sj{request.user.UserUID}"
User1ID=f"Sj{request.user.UserUID}",
ClubID=club_id,
)
logger.info(f"订单主表创建: {dingdan.OrderID}")
@@ -5579,19 +5552,11 @@ class AdTongYiTuiKuanPingTai(APIView):
except (User.DoesNotExist, AttributeError):
logger.warning(f"平台退款:老板{laoban_id}不存在或扩展信息缺失,跳过")
# 16. 更新收支记录表(id=1
# 16. 更新收支记录表(按俱乐部
try:
update_daily_payout(jine)
szjilu_obj = Szjilu.query.get(id=1)
szjilu_obj.TotalIncome -= jine
szjilu_obj.TotalExpense += jine
szjilu_obj.DailyExpense += jine
szjilu_obj.save()
except Szjilu.DoesNotExist:
logger.error(f"平台退款:收支记录表(id=1)不存在,跳过")
from jituan.services.szjilu_accounting import apply_szjilu_expense
update_daily_payout(jine, getattr(dingdan_obj, 'ClubID', None))
apply_szjilu_expense(jine, getattr(dingdan_obj, 'ClubID', None))
except Exception as e:
logger.error(f"更新收支记录表异常: {str(e)}")
@@ -6450,6 +6415,7 @@ class ShangjiaFakuanApplyView(APIView):
Status=1, # 待缴纳
AffectsGrabbing=yingxiang_qiangdan,
ApplicantIdentity=5, # 申请人是商家
ClubID=resolve_penalty_club_id(order=order, penalized_user_id=dashou_id),
)
logger.info(f"商家{shangjia_id}对订单{dingdan_id}打手{dashou_id}罚款成功,金额{fakuanjine}")
return Response({'code': 0, 'msg': '罚款申请已提交'})