feat: 会员体验版、俱乐部分离上架与分红计数加固

扩展 club_huiyuan_price/huiyuangoumai/czjilu/gsfenhong 字段;微信购会员强制写 czjilu+收支入账;formal_cishu 仅统计正式已支付单;关闭余额购会员;俱乐部上架/下架 API。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-06-26 01:32:33 +08:00
parent deb1688d0c
commit 2f0838e552
11 changed files with 689 additions and 221 deletions

View File

@@ -0,0 +1,38 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0005_duoci_fenhong_club_id'),
]
operations = [
migrations.AddField(
model_name='huiyuangoumai',
name='is_trial',
field=models.BooleanField(default=False, verbose_name='当前是否为体验权益'),
),
migrations.AddField(
model_name='huiyuangoumai',
name='has_used_trial',
field=models.BooleanField(default=False, verbose_name='是否已购买过体验版'),
),
migrations.AddField(
model_name='czjilu',
name='is_trial',
field=models.BooleanField(default=False, verbose_name='是否体验会员单'),
),
migrations.AddField(
model_name='czjilu',
name='purchase_days',
field=models.PositiveIntegerField(
blank=True, null=True, verbose_name='下单锁定有效天数',
),
),
migrations.AddField(
model_name='gsfenhong',
name='is_trial',
field=models.BooleanField(default=False, verbose_name='是否体验会员分红'),
),
]

View File

@@ -172,6 +172,8 @@ class Huiyuangoumai(QModel):
club_id = models.CharField(
max_length=16, default='xq', db_index=True, verbose_name='所属俱乐部',
)
is_trial = models.BooleanField(default=False, verbose_name='当前是否为体验权益')
has_used_trial = models.BooleanField(default=False, verbose_name='是否已购买过体验版')
class Meta:
db_table = 'huiyuangoumai'
@@ -218,6 +220,10 @@ class Czjilu(QModel):
club_id = models.CharField(
max_length=16, default='xq', db_index=True, verbose_name='所属俱乐部',
)
is_trial = models.BooleanField(default=False, verbose_name='是否体验会员单')
purchase_days = models.PositiveIntegerField(
null=True, blank=True, verbose_name='下单锁定有效天数',
)
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
@@ -258,6 +264,7 @@ class Gsfenhong(QModel):
club_id = models.CharField(
max_length=16, default='xq', db_index=True, verbose_name='所属俱乐部',
)
is_trial = models.BooleanField(default=False, verbose_name='是否体验会员分红')
class Meta:
db_table = 'gsfenhong'

View File

@@ -267,7 +267,7 @@ class DashouHuiyuanList(APIView):
}, status=status.HTTP_400_BAD_REQUEST)
# 3. 仅返回本俱乐部可售会员club_huiyuan_price 已配置且启用)
from jituan.services.member_recharge import club_huiyuan_sellable
from jituan.services.member_recharge import club_huiyuan_sellable, can_purchase_trial
club_id = resolve_effective_club_id(request, request.user)
huiyuan_queryset = Huiyuan.query.all().only(
@@ -275,15 +275,27 @@ class DashouHuiyuanList(APIView):
).order_by('jiage')
huiyuan_list = []
yonghuid = request.user.UserUID
for huiyuan in huiyuan_queryset:
sellable, club_price = club_huiyuan_sellable(club_id, huiyuan.huiyuan_id)
sellable, club_price, formal_days = club_huiyuan_sellable(
club_id, huiyuan.huiyuan_id, is_trial=False,
)
if not sellable or not club_price:
continue
trial_ok, _ = can_purchase_trial(yonghuid, huiyuan.huiyuan_id, club_id)
trial_sellable, trial_price, trial_days = club_huiyuan_sellable(
club_id, huiyuan.huiyuan_id, is_trial=True,
)
huiyuan_list.append({
'id': huiyuan.huiyuan_id,
'mingzi': huiyuan.jieshao,
'jiage': float(club_price),
'formal_days': formal_days,
'jieshao': huiyuan.jtjieshao,
'trial_enabled': bool(trial_sellable),
'trial_price': float(trial_price) if trial_sellable else 0,
'trial_days': trial_days if trial_sellable else 0,
'can_buy_trial': bool(trial_ok and trial_sellable),
})
# 5. 返回数据
@@ -1381,6 +1393,7 @@ class HuiyuanGoumai(APIView):
try:
# 1. 获取会员ID参数
huiyuanid = request.data.get('huiyuanid')
is_trial = bool(request.data.get('is_trial') or request.data.get('isTrial'))
if not huiyuanid:
return Response({
'code': 400,
@@ -1413,12 +1426,15 @@ class HuiyuanGoumai(APIView):
}, status=status.HTTP_400_BAD_REQUEST)
club_id = resolve_effective_club_id(request, request.user)
from jituan.services.member_recharge import club_huiyuan_sellable
sellable, club_price = club_huiyuan_sellable(club_id, huiyuanid)
if not sellable:
from jituan.services.member_recharge import validate_member_purchase
ok, err_msg, club_price, purchase_days = validate_member_purchase(
request.user.UserUID, huiyuanid, club_id, is_trial,
)
if not ok:
return Response({
'code': 400,
'message': '该俱乐部未配置此会员价格,请联系管理员'
'message': err_msg or '不可购买',
}, status=status.HTTP_400_BAD_REQUEST)
# 5. 生成订单ID前缀CzHY表示会员充值
@@ -1442,11 +1458,14 @@ class HuiyuanGoumai(APIView):
shuoming=shuoming,
huiyuan_id=huiyuanid, # 存储会员ID
club_id=club_id,
is_trial=is_trial,
purchase_days=int(purchase_days),
)
logger.info(
f"创建会员充值订单: 用户{request.user.UserUID}, 订单{dingdanid}, "
f"会员{huiyuanid}, 俱乐部{club_id}, 金额{jine}")
f"会员{huiyuanid}, 俱乐部{club_id}, 体验={is_trial}, "
f"金额{jine}元, 天数{purchase_days}")
# 7. 生成微信支付参数
try:
@@ -1477,6 +1496,8 @@ class HuiyuanGoumai(APIView):
'dingdanid': dingdanid,
'club_id': club_id,
'jine': jine,
'is_trial': is_trial,
'purchase_days': purchase_days,
}, status=status.HTTP_200_OK)
except Exception as e:
@@ -5862,8 +5883,11 @@ class DsqrgmdhView(APIView):
if shenfen_id not in [1, 2, 3, 4]:
return Response({'code': 400, 'msg': 'shenfen_id 必须为 1/2/3/4'}, status=status.HTTP_400_BAD_REQUEST)
if leixing == 1 and not huiyuan_id:
return Response({'code': 400, 'msg': '购买会员时必须提供 huiyuan_id'}, status=status.HTTP_400_BAD_REQUEST)
if leixing == 1:
return Response({
'code': 400,
'msg': '会员购买仅支持微信支付,请使用会员充值页面',
}, status=status.HTTP_400_BAD_REQUEST)
if leixing == 2:
if not yajin_jine:
return Response({'code': 400, 'msg': '购买押金时必须提供 yajin_jine'}, status=status.HTTP_400_BAD_REQUEST)