feat(jituan): 集团多俱乐部改造 — club 隔离、财务/提现/分红/展示配置
This commit is contained in:
24
orders/migrations/0004_order_clubid.py
Normal file
24
orders/migrations/0004_order_clubid.py
Normal 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='所属俱乐部',
|
||||
),
|
||||
),
|
||||
]
|
||||
22
orders/migrations/0005_penalty_clubid.py
Normal file
22
orders/migrations/0005_penalty_clubid.py
Normal 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='所属俱乐部',
|
||||
),
|
||||
),
|
||||
]
|
||||
22
orders/migrations/0006_penaltybonus_clubid.py
Normal file
22
orders/migrations/0006_penaltybonus_clubid.py
Normal 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='所属俱乐部',
|
||||
),
|
||||
),
|
||||
]
|
||||
22
orders/migrations/0007_penaltyrecord_clubid.py
Normal file
22
orders/migrations/0007_penaltyrecord_clubid.py
Normal 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='所属俱乐部',
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -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='创建时间',
|
||||
|
||||
@@ -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}, "
|
||||
|
||||
114
orders/views.py
114
orders/views.py
@@ -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': '罚款申请已提交'})
|
||||
|
||||
Reference in New Issue
Block a user