feat: 会员体验版、俱乐部分离上架与分红计数加固
扩展 club_huiyuan_price/huiyuangoumai/czjilu/gsfenhong 字段;微信购会员强制写 czjilu+收支入账;formal_cishu 仅统计正式已支付单;关闭余额购会员;俱乐部上架/下架 API。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -72,6 +72,12 @@ class Command(BaseCommand):
|
||||
jiage=row.jiage or Decimal('0'),
|
||||
guanshifc=row.guanshifc or Decimal('0'),
|
||||
zuzhangfc=row.zuzhangfc or Decimal('0'),
|
||||
formal_days=row.formal_days or 30,
|
||||
trial_enabled=bool(row.trial_enabled),
|
||||
trial_price=row.trial_price or Decimal('0'),
|
||||
trial_days=row.trial_days or 0,
|
||||
trial_guanshifc=row.trial_guanshifc or Decimal('0'),
|
||||
trial_zuzhangfc=row.trial_zuzhangfc or Decimal('0'),
|
||||
is_enabled=row.is_enabled,
|
||||
bankuai_id=row.bankuai_id,
|
||||
)
|
||||
|
||||
48
jituan/migrations/0006_club_huiyuan_trial_fields.py
Normal file
48
jituan/migrations/0006_club_huiyuan_trial_fields.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from decimal import Decimal
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('jituan', '0005_identity_tag'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='clubhuiyuanprice',
|
||||
name='formal_days',
|
||||
field=models.PositiveIntegerField(default=30, verbose_name='正式会员有效天数'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='clubhuiyuanprice',
|
||||
name='trial_enabled',
|
||||
field=models.BooleanField(default=False, verbose_name='是否开启体验会员'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='clubhuiyuanprice',
|
||||
name='trial_price',
|
||||
field=models.DecimalField(
|
||||
decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name='体验会员价格',
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='clubhuiyuanprice',
|
||||
name='trial_days',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='体验会员有效天数'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='clubhuiyuanprice',
|
||||
name='trial_guanshifc',
|
||||
field=models.DecimalField(
|
||||
decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name='体验管事分成',
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='clubhuiyuanprice',
|
||||
name='trial_zuzhangfc',
|
||||
field=models.DecimalField(
|
||||
decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name='体验组长分成',
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -99,6 +99,18 @@ class ClubHuiyuanPrice(QModel):
|
||||
jiage = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00'))
|
||||
guanshifc = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00'))
|
||||
zuzhangfc = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00'))
|
||||
formal_days = models.PositiveIntegerField(default=30, verbose_name='正式会员有效天数')
|
||||
trial_enabled = models.BooleanField(default=False, verbose_name='是否开启体验会员')
|
||||
trial_price = models.DecimalField(
|
||||
max_digits=10, decimal_places=2, default=Decimal('0.00'), verbose_name='体验会员价格',
|
||||
)
|
||||
trial_days = models.PositiveIntegerField(default=0, verbose_name='体验会员有效天数')
|
||||
trial_guanshifc = models.DecimalField(
|
||||
max_digits=10, decimal_places=2, default=Decimal('0.00'), verbose_name='体验管事分成',
|
||||
)
|
||||
trial_zuzhangfc = models.DecimalField(
|
||||
max_digits=10, decimal_places=2, default=Decimal('0.00'), verbose_name='体验组长分成',
|
||||
)
|
||||
is_enabled = models.BooleanField(default=True)
|
||||
bankuai_id = models.IntegerField(null=True, blank=True)
|
||||
CreateTime = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""后台会员管理:全局 Huiyuan 定义 + 各俱乐部 ClubHuiyuanPrice 售价/分成。"""
|
||||
"""后台会员管理:全局 Huiyuan 定义 + 各俱乐部 ClubHuiyuanPrice 售价/分成/体验配置。"""
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
@@ -14,13 +14,26 @@ from products.models import Huiyuan
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CLUB_WRITE_SCOPE_MSG = '当前为集团汇总视图,请先在顶栏切换到具体俱乐部(如星阙、星之界)后再操作'
|
||||
CLUB_WRITE_SCOPE_MSG = '当前为集团汇总视图,请先在顶栏切换到具体俱乐部后再操作'
|
||||
|
||||
|
||||
def club_write_blocked(request):
|
||||
return resolve_club_scope(request) == DATA_SCOPE_ALL
|
||||
|
||||
|
||||
def _price_row_to_dict(row):
|
||||
if not row:
|
||||
return {}
|
||||
return {
|
||||
'formal_days': int(row.formal_days or 30),
|
||||
'trial_enabled': bool(row.trial_enabled),
|
||||
'trial_price': str(row.trial_price or 0),
|
||||
'trial_days': int(row.trial_days or 0),
|
||||
'trial_guanshifc': str(row.trial_guanshifc or 0),
|
||||
'trial_zuzhangfc': str(row.trial_zuzhangfc or 0),
|
||||
}
|
||||
|
||||
|
||||
def _format_member(m, price_row=None):
|
||||
global_jiage = str(m.jiage)
|
||||
global_guanshifc = str(m.guanshifc)
|
||||
@@ -41,6 +54,12 @@ def _format_member(m, price_row=None):
|
||||
'bankuai_id': m.bankuai_id,
|
||||
'club_enabled': True,
|
||||
'price_source': 'global',
|
||||
'formal_days': 30,
|
||||
'trial_enabled': False,
|
||||
'trial_price': '0.00',
|
||||
'trial_days': 0,
|
||||
'trial_guanshifc': '0.00',
|
||||
'trial_zuzhangfc': '0.00',
|
||||
}
|
||||
if price_row is not None:
|
||||
item['jiage'] = str(price_row.jiage)
|
||||
@@ -48,6 +67,7 @@ def _format_member(m, price_row=None):
|
||||
item['zuzhangfc'] = str(price_row.zuzhangfc)
|
||||
item['club_enabled'] = bool(price_row.is_enabled)
|
||||
item['price_source'] = 'club'
|
||||
item.update(_price_row_to_dict(price_row))
|
||||
return item
|
||||
|
||||
|
||||
@@ -81,20 +101,70 @@ def build_member_list_payload(request):
|
||||
'club_id': club_id,
|
||||
'scope': scope,
|
||||
'price_note': (
|
||||
f'当前俱乐部 {club_id} 专属售价(表 club_huiyuan_price)。'
|
||||
'若与星阙相同,是创建时从 xq 复制的默认值,请在本俱乐部视图下修改价格。'
|
||||
f'当前俱乐部 {club_id} 专属售价(club_huiyuan_price)。'
|
||||
if scope != DATA_SCOPE_ALL else
|
||||
'集团汇总:展示全局默认价。请切换到具体俱乐部查看/编辑专属价。'
|
||||
'集团汇总:展示全局默认价;切换具体俱乐部可配置售价与体验会员。'
|
||||
),
|
||||
**list_response_meta(request),
|
||||
}
|
||||
|
||||
|
||||
def update_member_for_club(request, huiyuan_id, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao):
|
||||
def build_member_catalog_payload(request):
|
||||
"""集团会员类型 + 本俱乐部未上架的可选列表。"""
|
||||
club_id = resolve_club_id_from_request(request)
|
||||
enabled_ids = set(
|
||||
ClubHuiyuanPrice.query.filter(club_id=club_id, is_enabled=True).values_list('huiyuan_id', flat=True)
|
||||
)
|
||||
all_members = Huiyuan.query.all().order_by('-CreateTime')
|
||||
catalog = []
|
||||
for m in all_members:
|
||||
if m.huiyuan_id in enabled_ids:
|
||||
continue
|
||||
catalog.append({
|
||||
'huiyuan_id': m.huiyuan_id,
|
||||
'jieshao': m.jieshao,
|
||||
'jtjieshao': m.jtjieshao,
|
||||
})
|
||||
return {'club_id': club_id, 'catalog': catalog}
|
||||
|
||||
|
||||
def _parse_trial_fields(data, jiage, guanshifc, zuzhangfc):
|
||||
formal_days = int(data.get('formal_days') or 30)
|
||||
trial_enabled = bool(data.get('trial_enabled'))
|
||||
trial_price = Decimal(str(data.get('trial_price') or 0))
|
||||
trial_days = int(data.get('trial_days') or 0)
|
||||
trial_guanshifc = Decimal(str(data.get('trial_guanshifc') or 0))
|
||||
trial_zuzhangfc = Decimal(str(data.get('trial_zuzhangfc') or 0))
|
||||
if formal_days < 1 or formal_days > 3650:
|
||||
raise ValueError('正式会员天数无效')
|
||||
if trial_enabled:
|
||||
if trial_price <= 0 or trial_days <= 0:
|
||||
raise ValueError('开启体验会员须配置体验价格与天数')
|
||||
if trial_guanshifc + trial_zuzhangfc > trial_price:
|
||||
raise ValueError('体验管事+组长分成不能大于体验价格')
|
||||
if guanshifc + zuzhangfc > jiage:
|
||||
raise ValueError('管事分成+组长分成不能大于会员价格')
|
||||
return {
|
||||
'formal_days': formal_days,
|
||||
'trial_enabled': trial_enabled,
|
||||
'trial_price': trial_price,
|
||||
'trial_days': trial_days,
|
||||
'trial_guanshifc': trial_guanshifc,
|
||||
'trial_zuzhangfc': trial_zuzhangfc,
|
||||
}
|
||||
|
||||
|
||||
def update_member_for_club(request, huiyuan_id, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao, extra=None):
|
||||
if club_write_blocked(request):
|
||||
return None, CLUB_WRITE_SCOPE_MSG
|
||||
|
||||
club_id = club_id_for_write(request)
|
||||
extra = extra or {}
|
||||
|
||||
try:
|
||||
trial_fields = _parse_trial_fields(extra, jiage, guanshifc, zuzhangfc)
|
||||
except ValueError as e:
|
||||
return None, str(e)
|
||||
|
||||
with transaction.atomic():
|
||||
try:
|
||||
@@ -115,6 +185,7 @@ def update_member_for_club(request, huiyuan_id, jieshao, jiage, guanshifc, zuzha
|
||||
'zuzhangfc': zuzhangfc,
|
||||
'is_enabled': True,
|
||||
'bankuai_id': member.bankuai_id,
|
||||
**trial_fields,
|
||||
},
|
||||
)
|
||||
if not created:
|
||||
@@ -122,6 +193,8 @@ def update_member_for_club(request, huiyuan_id, jieshao, jiage, guanshifc, zuzha
|
||||
row.guanshifc = guanshifc
|
||||
row.zuzhangfc = zuzhangfc
|
||||
row.is_enabled = True
|
||||
for k, v in trial_fields.items():
|
||||
setattr(row, k, v)
|
||||
row.save()
|
||||
|
||||
if club_id == CLUB_ID_DEFAULT:
|
||||
@@ -133,11 +206,66 @@ def update_member_for_club(request, huiyuan_id, jieshao, jiage, guanshifc, zuzha
|
||||
return {'huiyuan_id': huiyuan_id}, None
|
||||
|
||||
|
||||
def add_member_for_club(request, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao, bankuai_id=None):
|
||||
def enable_member_for_club(request, huiyuan_id, jiage, guanshifc, zuzhangfc, extra=None):
|
||||
"""俱乐部上架集团会员类型。"""
|
||||
if club_write_blocked(request):
|
||||
return None, CLUB_WRITE_SCOPE_MSG
|
||||
|
||||
club_id = club_id_for_write(request)
|
||||
extra = extra or {}
|
||||
|
||||
try:
|
||||
member = Huiyuan.query.get(huiyuan_id=huiyuan_id)
|
||||
except Huiyuan.DoesNotExist:
|
||||
return None, '集团会员类型不存在'
|
||||
|
||||
try:
|
||||
jiage = Decimal(str(jiage))
|
||||
guanshifc = Decimal(str(guanshifc))
|
||||
zuzhangfc = Decimal(str(zuzhangfc))
|
||||
if jiage <= 0:
|
||||
raise ValueError
|
||||
trial_fields = _parse_trial_fields(extra, jiage, guanshifc, zuzhangfc)
|
||||
except ValueError as e:
|
||||
return None, str(e) if str(e) else '金额或体验配置错误'
|
||||
|
||||
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,
|
||||
**trial_fields,
|
||||
},
|
||||
)
|
||||
if not created:
|
||||
row.jiage = jiage
|
||||
row.guanshifc = guanshifc
|
||||
row.zuzhangfc = zuzhangfc
|
||||
row.is_enabled = True
|
||||
for k, v in trial_fields.items():
|
||||
setattr(row, k, v)
|
||||
row.save()
|
||||
return {'huiyuan_id': huiyuan_id}, None
|
||||
|
||||
|
||||
def disable_member_for_club(request, huiyuan_id):
|
||||
"""俱乐部软下架。"""
|
||||
if club_write_blocked(request):
|
||||
return None, CLUB_WRITE_SCOPE_MSG
|
||||
club_id = club_id_for_write(request)
|
||||
updated = ClubHuiyuanPrice.objects.filter(
|
||||
club_id=club_id, huiyuan_id=huiyuan_id, is_enabled=True,
|
||||
).update(is_enabled=False)
|
||||
if not updated:
|
||||
return None, '该会员在本俱乐部未上架或已下架'
|
||||
return {'huiyuan_id': huiyuan_id}, None
|
||||
|
||||
|
||||
def add_global_member(jieshao, jiage, guanshifc, zuzhangfc, jtjieshao, bankuai_id=None):
|
||||
"""集团创建全局会员类型(仅集团后台)。"""
|
||||
|
||||
def generate_huiyuan_id():
|
||||
while True:
|
||||
@@ -160,20 +288,18 @@ def add_member_for_club(request, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao
|
||||
if bankuai_id is not None:
|
||||
create_kwargs['bankuai_id'] = bankuai_id
|
||||
member = Huiyuan.query.create(**create_kwargs)
|
||||
ClubHuiyuanPrice.query.create(
|
||||
club_id=club_id,
|
||||
huiyuan_id=new_id,
|
||||
jiage=jiage,
|
||||
guanshifc=guanshifc,
|
||||
zuzhangfc=zuzhangfc,
|
||||
is_enabled=True,
|
||||
bankuai_id=bankuai_id,
|
||||
)
|
||||
except IntegrityError as e:
|
||||
logger.exception('添加会员失败 club_id=%s', club_id)
|
||||
return None, f'添加失败:数据冲突({e})'
|
||||
logger.exception('创建全局会员失败')
|
||||
return None, f'创建失败:{e}'
|
||||
except Exception as e:
|
||||
logger.exception('添加会员失败 club_id=%s', club_id)
|
||||
return None, f'添加失败:{e}'
|
||||
logger.exception('创建全局会员失败')
|
||||
return None, f'创建失败:{e}'
|
||||
|
||||
return {'huiyuan_id': member.huiyuan_id}, None
|
||||
|
||||
|
||||
# 兼容旧名:俱乐部不应再调用
|
||||
def add_member_for_club(request, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao, bankuai_id=None):
|
||||
if club_write_blocked(request):
|
||||
return None, CLUB_WRITE_SCOPE_MSG
|
||||
return None, '俱乐部不可新建会员类型,请从集团会员 catalog 上架'
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"""
|
||||
会员微信充值履约(回调 / 前端确认共用,幂等)。
|
||||
|
||||
分红策略(定稿):
|
||||
- 第几次购买 cishu:本俱乐部该打手该会员,已支付成功 czjilu 笔数(不含本单)+ 1。
|
||||
与换管事无关,不看 gsfenhong,不看管事被分了几次。
|
||||
- cishu=1:先 duoci_fenhong(club_id+受益人+会员+cishu);无则 club_huiyuan_price 默认分成。
|
||||
- cishu>=2:仅 duoci_fenhong;无配置则不分(不读管事/组长用户表二次或永久分红字段)。
|
||||
- 价格与默认分成:仅 club_huiyuan_price + order.club_id,不读全局 huiyuan 表。
|
||||
分红策略(定稿 v2):
|
||||
- formal_cishu:本俱乐部 + 打手 + 会员,czjilu 已支付正式单(is_trial=false)笔数 + 1。
|
||||
与换管事无关;体验单不计入。
|
||||
- formal_cishu=1:duoci_fenhong 或 club 默认正式分成。
|
||||
- formal_cishu>=2:仅 duoci_fenhong;无配置则不分。
|
||||
- 体验单:固定 trial_guanshifc / trial_zuzhangfc;不计入 formal_cishu。
|
||||
|
||||
财务:每笔成功履约必须 czjilu.zhuangtai=3 + 收支入账(record_wechat_income_once)。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -40,6 +42,9 @@ from users.models import UserDashou, UserGuanshi, UserZuzhang
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MEMBER_FENHONG_LEIXING = 1
|
||||
MEMBER_LEIXING = 1
|
||||
MEMBER_PAID_STATUS = 3
|
||||
MEMBER_PENDING_STATUS = 9
|
||||
|
||||
|
||||
class MemberRechargeError(Exception):
|
||||
@@ -48,35 +53,110 @@ class MemberRechargeError(Exception):
|
||||
self.code = code
|
||||
|
||||
|
||||
def club_huiyuan_sellable(club_id, huiyuan_id):
|
||||
"""仅 club_huiyuan_price 可售;未配置则不可购买。"""
|
||||
def get_club_huiyuan_row(club_id, huiyuan_id, require_enabled=False):
|
||||
club_id = club_id or CLUB_ID_DEFAULT
|
||||
row = ClubHuiyuanPrice.query.filter(
|
||||
club_id=club_id, huiyuan_id=huiyuan_id, is_enabled=True,
|
||||
qs = ClubHuiyuanPrice.query.filter(club_id=club_id, huiyuan_id=huiyuan_id)
|
||||
if require_enabled:
|
||||
qs = qs.filter(is_enabled=True)
|
||||
return qs.first()
|
||||
|
||||
|
||||
def club_huiyuan_sellable(club_id, huiyuan_id, is_trial=False):
|
||||
"""正式/体验是否可售及价格、天数。"""
|
||||
club_id = club_id or CLUB_ID_DEFAULT
|
||||
row = get_club_huiyuan_row(club_id, huiyuan_id, require_enabled=True)
|
||||
if not row:
|
||||
return False, Decimal('0'), 0
|
||||
|
||||
if is_trial:
|
||||
if not row.trial_enabled:
|
||||
return False, Decimal('0'), 0
|
||||
price = Decimal(str(row.trial_price or 0))
|
||||
days = int(row.trial_days or 0)
|
||||
if price <= 0 or days <= 0:
|
||||
return False, Decimal('0'), 0
|
||||
return True, price, days
|
||||
|
||||
price = Decimal(str(row.jiage or 0))
|
||||
days = int(row.formal_days or 30)
|
||||
if price <= 0:
|
||||
return False, Decimal('0'), 0
|
||||
return True, price, days
|
||||
|
||||
|
||||
def has_formal_member_purchase(yonghuid, huiyuan_id, club_id):
|
||||
"""是否已有成功支付的正式会员单(用于禁止再买体验)。"""
|
||||
club_id = club_id or CLUB_ID_DEFAULT
|
||||
return Czjilu.query.filter(
|
||||
yonghuid=yonghuid,
|
||||
huiyuan_id=huiyuan_id,
|
||||
leixing=MEMBER_LEIXING,
|
||||
zhuangtai=MEMBER_PAID_STATUS,
|
||||
club_id=club_id,
|
||||
is_trial=False,
|
||||
).exists()
|
||||
|
||||
|
||||
def can_purchase_trial(yonghuid, huiyuan_id, club_id):
|
||||
"""体验会员是否允许购买。"""
|
||||
club_id = club_id or CLUB_ID_DEFAULT
|
||||
sellable, _, _ = club_huiyuan_sellable(club_id, huiyuan_id, is_trial=True)
|
||||
if not sellable:
|
||||
return False, '本俱乐部未开启体验会员'
|
||||
|
||||
goumai = Huiyuangoumai.query.filter(
|
||||
yonghu_id=yonghuid, huiyuan_id=huiyuan_id,
|
||||
).first()
|
||||
if row and row.jiage and Decimal(str(row.jiage)) > 0:
|
||||
return True, Decimal(str(row.jiage))
|
||||
return False, Decimal('0')
|
||||
if goumai and goumai.has_used_trial:
|
||||
return False, '体验会员仅可购买一次'
|
||||
|
||||
if has_formal_member_purchase(yonghuid, huiyuan_id, club_id):
|
||||
return False, '已购买过正式会员,不可再购买体验版'
|
||||
|
||||
return True, None
|
||||
|
||||
|
||||
def resolve_member_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id):
|
||||
def validate_member_purchase(yonghuid, huiyuan_id, club_id, is_trial):
|
||||
"""下单前校验。"""
|
||||
is_trial = bool(is_trial)
|
||||
if is_trial:
|
||||
ok, msg = can_purchase_trial(yonghuid, huiyuan_id, club_id)
|
||||
if not ok:
|
||||
return False, msg, Decimal('0'), 0
|
||||
sellable, price, days = club_huiyuan_sellable(club_id, huiyuan_id, is_trial=True)
|
||||
else:
|
||||
sellable, price, days = club_huiyuan_sellable(club_id, huiyuan_id, is_trial=False)
|
||||
if not sellable:
|
||||
return False, '该俱乐部未配置此会员或已下架', Decimal('0'), 0
|
||||
|
||||
if not sellable:
|
||||
return False, '会员不可购买', Decimal('0'), 0
|
||||
return True, None, price, days
|
||||
|
||||
|
||||
def resolve_formal_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id):
|
||||
"""
|
||||
打手第几次购买该会员(= duoci_fenhong.cishu)。
|
||||
同俱乐部 czjilu 已支付笔数(不含本单)+ 1。
|
||||
打手第几次正式购买该会员(duoci_fenhong.cishu)。
|
||||
仅统计 is_trial=false 的已支付 czjilu。
|
||||
"""
|
||||
club_id = club_id or CLUB_ID_DEFAULT
|
||||
prior_paid = Czjilu.query.filter(
|
||||
yonghuid=yonghuid,
|
||||
huiyuan_id=huiyuan_id,
|
||||
leixing=1,
|
||||
zhuangtai=3,
|
||||
leixing=MEMBER_LEIXING,
|
||||
zhuangtai=MEMBER_PAID_STATUS,
|
||||
club_id=club_id,
|
||||
is_trial=False,
|
||||
).exclude(dingdan_id=current_dingdan_id).count()
|
||||
return prior_paid + 1
|
||||
|
||||
|
||||
# 兼容旧名
|
||||
def resolve_member_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id):
|
||||
return resolve_formal_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id)
|
||||
|
||||
|
||||
def _lookup_duoci_fenhong(club_id, huiyuan_id, beneficiary_id, cishu, field):
|
||||
"""有行则返回金额(含 0);无行返回 None 表示未配置。"""
|
||||
try:
|
||||
row = DuociFenhong.query.get(
|
||||
club_id=club_id or CLUB_ID_DEFAULT,
|
||||
@@ -89,40 +169,102 @@ def _lookup_duoci_fenhong(club_id, huiyuan_id, beneficiary_id, cishu, field):
|
||||
return None
|
||||
|
||||
|
||||
def calc_guanshi_member_fenhong(
|
||||
huiyuan_id, guanshi_id, cishu, club_id, default_guanshi_fc,
|
||||
):
|
||||
"""cishu>=2 只认 duoci_fenhong;cishu=1 可回落 club 默认分成。"""
|
||||
def calc_guanshi_member_fenhong(huiyuan_id, guanshi_id, cishu, club_id, default_guanshi_fc):
|
||||
club_id = club_id or CLUB_ID_DEFAULT
|
||||
custom = _lookup_duoci_fenhong(club_id, huiyuan_id, guanshi_id, cishu, 'guanshi_fenhong')
|
||||
if custom is not None:
|
||||
return custom if custom > 0 else None
|
||||
|
||||
if cishu == 1:
|
||||
base = default_guanshi_fc if default_guanshi_fc is not None else Decimal('0')
|
||||
return base if base > 0 else None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def calc_zuzhang_member_fenhong(
|
||||
huiyuan_id, zuzhang_id, cishu, club_id, default_zuzhang_fc,
|
||||
):
|
||||
def calc_zuzhang_member_fenhong(huiyuan_id, zuzhang_id, cishu, club_id, default_zuzhang_fc):
|
||||
if not zuzhang_id:
|
||||
return None
|
||||
club_id = club_id or CLUB_ID_DEFAULT
|
||||
custom = _lookup_duoci_fenhong(club_id, huiyuan_id, zuzhang_id, cishu, 'zuzhang_fenhong')
|
||||
if custom is not None:
|
||||
return custom if custom > 0 else None
|
||||
|
||||
if cishu == 1:
|
||||
base = default_zuzhang_fc if default_zuzhang_fc is not None else Decimal('0')
|
||||
return base if base > 0 else None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _apply_member_fenhong(order, huiyuan, purchase_cishu):
|
||||
def _calc_new_daoqi(existing, days):
|
||||
"""未过期叠加;已过期从今天算。"""
|
||||
now = timezone.now()
|
||||
days = max(1, int(days))
|
||||
if existing and existing.daoqi_time and existing.daoqi_time > now and existing.huiyuan_zhuangtai == 1:
|
||||
return existing.daoqi_time + timedelta(days=days)
|
||||
return now + timedelta(days=days)
|
||||
|
||||
|
||||
def _record_member_income(order, club_id):
|
||||
"""会员充值收入入账(幂等 biz_ref=cz:dingdan_id)。"""
|
||||
jine_decimal = Decimal(str(order.jine))
|
||||
try:
|
||||
from jituan.services.szjilu_accounting import record_wechat_income_once
|
||||
ok = record_wechat_income_once(jine_decimal, club_id, biz_ref=f'cz:{order.dingdan_id}')
|
||||
if not ok:
|
||||
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
|
||||
repair_wechat_income_if_missing(jine_decimal, club_id, biz_ref=f'cz:{order.dingdan_id}')
|
||||
logger.info(
|
||||
'会员收入入账 dingdan=%s club=%s jine=%s is_trial=%s',
|
||||
order.dingdan_id, club_id, jine_decimal, getattr(order, 'is_trial', False),
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
'会员收入入账失败 dingdan=%s club=%s jine=%s err=%s',
|
||||
order.dingdan_id, club_id, order.jine, exc, exc_info=True,
|
||||
)
|
||||
raise MemberRechargeError('收入统计失败,请联系客服', 'income_error')
|
||||
|
||||
|
||||
def _apply_huiyuan_entitlement(yonghuid, huiyuan_id, huiyuan, club_id, is_trial, purchase_days):
|
||||
"""开通/续期会员权益。"""
|
||||
is_trial = bool(is_trial)
|
||||
purchase_days = max(1, int(purchase_days))
|
||||
existing = Huiyuangoumai.query.filter(
|
||||
yonghu_id=yonghuid, huiyuan_id=huiyuan_id,
|
||||
).first()
|
||||
is_first_buy_any = False
|
||||
new_daoqi = _calc_new_daoqi(existing, purchase_days)
|
||||
|
||||
if existing:
|
||||
existing.daoqi_time = new_daoqi
|
||||
existing.huiyuan_zhuangtai = 1
|
||||
existing.jieshao = huiyuan.jieshao
|
||||
if is_trial:
|
||||
existing.is_trial = True
|
||||
existing.has_used_trial = True
|
||||
else:
|
||||
existing.is_trial = False
|
||||
existing.save(update_fields=[
|
||||
'daoqi_time', 'huiyuan_zhuangtai', 'jieshao', 'is_trial', 'has_used_trial', 'UpdateTime',
|
||||
])
|
||||
record = existing
|
||||
else:
|
||||
other = Huiyuangoumai.query.filter(yonghu_id=yonghuid).exclude(huiyuan_id=huiyuan_id).count()
|
||||
if other == 0:
|
||||
is_first_buy_any = True
|
||||
resolved_club = club_id or get_user_club_id(User.query.filter(UserUID=yonghuid).first())
|
||||
record = Huiyuangoumai.query.create(
|
||||
yonghu_id=yonghuid,
|
||||
huiyuan_id=huiyuan_id,
|
||||
jieshao=huiyuan.jieshao,
|
||||
huiyuan_zhuangtai=1,
|
||||
daoqi_time=new_daoqi,
|
||||
club_id=resolved_club,
|
||||
is_trial=is_trial,
|
||||
has_used_trial=is_trial,
|
||||
)
|
||||
return record, is_first_buy_any
|
||||
|
||||
|
||||
def _apply_member_fenhong(order, huiyuan, formal_cishu=None, trial_guanshi_fc=None, trial_zuzhang_fc=None):
|
||||
if Gsfenhong.query.filter(dingdan_id=order.dingdan_id).exists():
|
||||
logger.info('会员分红已处理,跳过: %s', order.dingdan_id)
|
||||
return
|
||||
@@ -154,25 +296,30 @@ def _apply_member_fenhong(order, huiyuan, purchase_cishu):
|
||||
|
||||
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
|
||||
huiyuan_id = huiyuan.huiyuan_id
|
||||
cishu = purchase_cishu
|
||||
guanshi_fc, zuzhang_fc = get_huiyuan_fenchong(club_id, huiyuan_id)
|
||||
order_amount = Decimal(str(order.jine))
|
||||
is_trial_order = bool(getattr(order, 'is_trial', False))
|
||||
|
||||
guanshi_fenhong = calc_guanshi_member_fenhong(
|
||||
huiyuan_id, guanshi_id, cishu, club_id, guanshi_fc,
|
||||
)
|
||||
guanshi_fenhong = guanshi_fenhong if guanshi_fenhong is not None else Decimal('0')
|
||||
|
||||
zuzhang_fenhong = calc_zuzhang_member_fenhong(
|
||||
huiyuan_id, zuzhang_id, cishu, club_id, zuzhang_fc,
|
||||
)
|
||||
zuzhang_fenhong = zuzhang_fenhong if zuzhang_fenhong is not None else Decimal('0')
|
||||
if is_trial_order:
|
||||
guanshi_fenhong = Decimal(str(trial_guanshi_fc or 0))
|
||||
zuzhang_fenhong = Decimal(str(trial_zuzhang_fc or 0)) if zuzhang_id else Decimal('0')
|
||||
cishu = None
|
||||
else:
|
||||
cishu = formal_cishu or resolve_formal_purchase_cishu(
|
||||
order.yonghuid, huiyuan_id, order.dingdan_id, club_id,
|
||||
)
|
||||
guanshi_fc, zuzhang_fc = get_huiyuan_fenchong(club_id, huiyuan_id)
|
||||
guanshi_fenhong = calc_guanshi_member_fenhong(
|
||||
huiyuan_id, guanshi_id, cishu, club_id, guanshi_fc,
|
||||
)
|
||||
guanshi_fenhong = guanshi_fenhong if guanshi_fenhong is not None else Decimal('0')
|
||||
zuzhang_fenhong = calc_zuzhang_member_fenhong(
|
||||
huiyuan_id, zuzhang_id, cishu, club_id, zuzhang_fc,
|
||||
)
|
||||
zuzhang_fenhong = zuzhang_fenhong if zuzhang_fenhong is not None else Decimal('0')
|
||||
|
||||
total = guanshi_fenhong + zuzhang_fenhong
|
||||
if total > order_amount:
|
||||
logger.warning(
|
||||
'分红总额%s超订单%s,组长置0', total, order_amount,
|
||||
)
|
||||
logger.warning('分红总额%s超订单%s,组长置0', total, order_amount)
|
||||
zuzhang_fenhong = Decimal('0')
|
||||
total = guanshi_fenhong
|
||||
if total > order_amount:
|
||||
@@ -181,8 +328,8 @@ def _apply_member_fenhong(order, huiyuan, purchase_cishu):
|
||||
|
||||
if guanshi_fenhong <= 0 and zuzhang_fenhong <= 0:
|
||||
logger.info(
|
||||
'本次无会员分红 dingdan=%s club=%s cishu=%s',
|
||||
order.dingdan_id, club_id, cishu,
|
||||
'本次无会员分红 dingdan=%s club=%s cishu=%s is_trial=%s',
|
||||
order.dingdan_id, club_id, cishu, is_trial_order,
|
||||
)
|
||||
return
|
||||
|
||||
@@ -218,66 +365,30 @@ def _apply_member_fenhong(order, huiyuan, purchase_cishu):
|
||||
huiyuan_id=huiyuan_id,
|
||||
fenhong_leixing=MEMBER_FENHONG_LEIXING,
|
||||
club_id=resolve_gsfenhong_club_id(dingdan_id=order.dingdan_id, czjilu=order),
|
||||
is_trial=is_trial_order,
|
||||
)
|
||||
|
||||
if guanshi_fenhong > 0:
|
||||
if guanshi_fenhong > 0 and not is_trial_order and cishu is not None:
|
||||
try:
|
||||
if cishu == 1:
|
||||
update_guanshi_daily_by_action(
|
||||
yonghuid=guanshi_id, action=2, amount=guanshi_fenhong,
|
||||
)
|
||||
update_guanshi_daily_by_action(yonghuid=guanshi_id, action=2, amount=guanshi_fenhong)
|
||||
else:
|
||||
update_guanshi_xufei_daily(
|
||||
yonghuid=guanshi_id, xufei_jine=guanshi_fenhong,
|
||||
)
|
||||
update_guanshi_xufei_daily(yonghuid=guanshi_id, xufei_jine=guanshi_fenhong)
|
||||
except Exception as exc:
|
||||
logger.error('管事日统计更新失败: %s', exc)
|
||||
|
||||
if zuzhang_fenhong > 0 and zuzhang_id:
|
||||
try:
|
||||
update_zuzhang_daily_by_action(
|
||||
yonghuid=zuzhang_id, action=2, amount=zuzhang_fenhong,
|
||||
)
|
||||
update_zuzhang_daily_by_action(yonghuid=zuzhang_id, action=2, amount=zuzhang_fenhong)
|
||||
except Exception as exc:
|
||||
logger.error('组长日统计更新失败: %s', exc)
|
||||
|
||||
logger.info(
|
||||
'会员分红完成 dingdan=%s club=%s cishu=%s 管事=%s 组长=%s',
|
||||
order.dingdan_id, club_id, cishu, guanshi_fenhong, zuzhang_fenhong,
|
||||
'会员分红完成 dingdan=%s club=%s formal_cishu=%s is_trial=%s 管事=%s 组长=%s',
|
||||
order.dingdan_id, club_id, cishu, is_trial_order, guanshi_fenhong, zuzhang_fenhong,
|
||||
)
|
||||
|
||||
|
||||
def _apply_huiyuan_goumai(yonghuid, huiyuan_id, huiyuan, club_id):
|
||||
"""返回 (record, is_first_purchase_of_member, is_first_buy_any_member)。"""
|
||||
existing = Huiyuangoumai.query.filter(
|
||||
yonghu_id=yonghuid, huiyuan_id=huiyuan_id,
|
||||
).first()
|
||||
is_first_purchase = existing is None
|
||||
is_first_buy_any = False
|
||||
|
||||
if existing:
|
||||
existing.daoqi_time += timedelta(days=30)
|
||||
existing.huiyuan_zhuangtai = 1
|
||||
existing.save(update_fields=['daoqi_time', 'huiyuan_zhuangtai', 'UpdateTime'])
|
||||
return existing, False, False
|
||||
|
||||
other = Huiyuangoumai.query.filter(yonghu_id=yonghuid).exclude(
|
||||
huiyuan_id=huiyuan_id,
|
||||
).count()
|
||||
if other == 0:
|
||||
is_first_buy_any = True
|
||||
|
||||
record = Huiyuangoumai.query.create(
|
||||
yonghu_id=yonghuid,
|
||||
huiyuan_id=huiyuan_id,
|
||||
jieshao=huiyuan.jieshao,
|
||||
huiyuan_zhuangtai=1,
|
||||
daoqi_time=timezone.now() + timedelta(days=30),
|
||||
club_id=club_id or get_user_club_id(User.query.filter(UserUID=yonghuid).first()),
|
||||
)
|
||||
return record, is_first_purchase, is_first_buy_any
|
||||
|
||||
|
||||
def _add_first_buy_jifen(yonghuid):
|
||||
try:
|
||||
dashou = UserDashou.query.get(user__UserUID=yonghuid)
|
||||
@@ -288,17 +399,12 @@ def _add_first_buy_jifen(yonghuid):
|
||||
|
||||
|
||||
def repair_member_entitlement_if_paid(dingdan_id):
|
||||
"""
|
||||
订单已标为已支付(zhuangtai=3)但 huiyuangoumai 缺失时补开通会员。
|
||||
用于修复历史脏数据或履约中断。
|
||||
"""
|
||||
order = Czjilu.query.filter(dingdan_id=dingdan_id, leixing=1).first()
|
||||
if not order or order.zhuangtai != 3 or not order.huiyuan_id:
|
||||
order = Czjilu.query.filter(dingdan_id=dingdan_id, leixing=MEMBER_LEIXING).first()
|
||||
if not order or order.zhuangtai != MEMBER_PAID_STATUS or not order.huiyuan_id:
|
||||
return False
|
||||
|
||||
exists = Huiyuangoumai.query.filter(
|
||||
yonghu_id=order.yonghuid,
|
||||
huiyuan_id=order.huiyuan_id,
|
||||
yonghu_id=order.yonghuid, huiyuan_id=order.huiyuan_id,
|
||||
).exists()
|
||||
if exists:
|
||||
return True
|
||||
@@ -306,44 +412,40 @@ def repair_member_entitlement_if_paid(dingdan_id):
|
||||
try:
|
||||
huiyuan = Huiyuan.query.get(huiyuan_id=order.huiyuan_id)
|
||||
except Huiyuan.DoesNotExist:
|
||||
logger.error('补开通会员失败,会员不存在 dingdan=%s huiyuan=%s', dingdan_id, order.huiyuan_id)
|
||||
logger.error('补开通会员失败 dingdan=%s huiyuan=%s', dingdan_id, order.huiyuan_id)
|
||||
return False
|
||||
|
||||
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
|
||||
days = order.purchase_days or 30
|
||||
with transaction.atomic():
|
||||
_apply_huiyuan_goumai(order.yonghuid, order.huiyuan_id, huiyuan, club_id)
|
||||
logger.warning('已补开通会员 dingdan=%s yonghu=%s huiyuan=%s', dingdan_id, order.yonghuid, order.huiyuan_id)
|
||||
_apply_huiyuan_entitlement(
|
||||
order.yonghuid, order.huiyuan_id, huiyuan, club_id,
|
||||
bool(getattr(order, 'is_trial', False)), days,
|
||||
)
|
||||
logger.warning('已补开通会员 dingdan=%s', dingdan_id)
|
||||
return True
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback'):
|
||||
"""
|
||||
幂等履约:仅当 czjilu.zhuangtai==9 时执行。
|
||||
已处理 (zhuangtai!=9) 直接返回 already_done=True。
|
||||
"""
|
||||
locked = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update()
|
||||
order = locked.first()
|
||||
if not order:
|
||||
raise MemberRechargeError('订单不存在', 'not_found')
|
||||
|
||||
if order.leixing != 1:
|
||||
if order.leixing != MEMBER_LEIXING:
|
||||
raise MemberRechargeError('订单类型不是会员充值', 'type_error')
|
||||
|
||||
if order.zhuangtai != 9:
|
||||
if order.zhuangtai != MEMBER_PENDING_STATUS:
|
||||
logger.info(
|
||||
'会员订单已处理,跳过履约 dingdan=%s zhuangtai=%s source=%s',
|
||||
dingdan_id, order.zhuangtai, source,
|
||||
)
|
||||
try:
|
||||
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
|
||||
repair_wechat_income_if_missing(
|
||||
Decimal(str(order.jine)),
|
||||
getattr(order, 'club_id', None) or CLUB_ID_DEFAULT,
|
||||
biz_ref=f'cz:{dingdan_id}',
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error('会员补记收支失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True)
|
||||
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
|
||||
_record_member_income(order, club_id)
|
||||
except MemberRechargeError:
|
||||
pass
|
||||
return {
|
||||
'already_done': True,
|
||||
'dingdan_id': dingdan_id,
|
||||
@@ -363,62 +465,66 @@ def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback'
|
||||
raise MemberRechargeError(f'会员不存在: {order.huiyuan_id}', 'huiyuan_not_found')
|
||||
|
||||
club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
|
||||
is_trial = bool(getattr(order, 'is_trial', False))
|
||||
purchase_days = order.purchase_days or (30 if not is_trial else 0)
|
||||
if purchase_days <= 0:
|
||||
_, _, purchase_days = club_huiyuan_sellable(club_id, order.huiyuan_id, is_trial=is_trial)
|
||||
|
||||
is_first_purchase_of_member = not Huiyuangoumai.query.filter(
|
||||
yonghu_id=order.yonghuid,
|
||||
huiyuan_id=order.huiyuan_id,
|
||||
).exists()
|
||||
formal_cishu = None
|
||||
trial_guanshi_fc = Decimal('0')
|
||||
trial_zuzhang_fc = Decimal('0')
|
||||
if is_trial:
|
||||
row = get_club_huiyuan_row(club_id, order.huiyuan_id)
|
||||
if row:
|
||||
trial_guanshi_fc = Decimal(str(row.trial_guanshifc or 0))
|
||||
trial_zuzhang_fc = Decimal(str(row.trial_zuzhangfc or 0))
|
||||
else:
|
||||
formal_cishu = resolve_formal_purchase_cishu(
|
||||
order.yonghuid, order.huiyuan_id, order.dingdan_id, club_id,
|
||||
)
|
||||
|
||||
# 必须在改 zhuangtai 之前算清第几次购买
|
||||
purchase_cishu = resolve_member_purchase_cishu(
|
||||
order.yonghuid, order.huiyuan_id, order.dingdan_id, club_id,
|
||||
)
|
||||
|
||||
# 先开通会员,再记账;分红失败不得回滚会员
|
||||
goumai_record, _, is_first_buy_any = _apply_huiyuan_goumai(
|
||||
order.yonghuid, order.huiyuan_id, huiyuan, club_id,
|
||||
goumai_record, is_first_buy_any = _apply_huiyuan_entitlement(
|
||||
order.yonghuid, order.huiyuan_id, huiyuan, club_id, is_trial, purchase_days,
|
||||
)
|
||||
if is_first_buy_any:
|
||||
_add_first_buy_jifen(order.yonghuid)
|
||||
|
||||
order.zhuangtai = 3
|
||||
order.zhuangtai = MEMBER_PAID_STATUS
|
||||
order.save(update_fields=['zhuangtai'])
|
||||
|
||||
huiyuan.goumai_cishu = (huiyuan.goumai_cishu or 0) + 1
|
||||
huiyuan.save(update_fields=['goumai_cishu'])
|
||||
|
||||
jine_decimal = Decimal(str(order.jine))
|
||||
try:
|
||||
from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
|
||||
repair_wechat_income_if_missing(jine_decimal, club_id, biz_ref=f'cz:{dingdan_id}')
|
||||
except Exception as exc:
|
||||
logger.error('会员收支统计更新失败: %s', exc, exc_info=True)
|
||||
_record_member_income(order, club_id)
|
||||
|
||||
try:
|
||||
_apply_member_fenhong(order, huiyuan, purchase_cishu)
|
||||
_apply_member_fenhong(
|
||||
order, huiyuan,
|
||||
formal_cishu=formal_cishu,
|
||||
trial_guanshi_fc=trial_guanshi_fc,
|
||||
trial_zuzhang_fc=trial_zuzhang_fc,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
'会员分红失败但会员已开通 dingdan=%s err=%s',
|
||||
dingdan_id, exc, exc_info=True,
|
||||
'会员分红失败但会员已开通 dingdan=%s err=%s', dingdan_id, exc, exc_info=True,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
'会员充值履约完成 dingdan=%s source=%s club=%s purchase_cishu=%s first_member=%s',
|
||||
dingdan_id, source, club_id, purchase_cishu, is_first_purchase_of_member,
|
||||
'会员充值履约完成 dingdan=%s source=%s club=%s is_trial=%s formal_cishu=%s jine=%s',
|
||||
dingdan_id, source, club_id, is_trial, formal_cishu, order.jine,
|
||||
)
|
||||
return {
|
||||
'already_done': False,
|
||||
'dingdan_id': dingdan_id,
|
||||
'zhuangtai': 3,
|
||||
'purchase_cishu': purchase_cishu,
|
||||
'is_first_purchase_of_member': is_first_purchase_of_member,
|
||||
'zhuangtai': MEMBER_PAID_STATUS,
|
||||
'formal_cishu': formal_cishu,
|
||||
'is_trial': is_trial,
|
||||
'daoqi_time': goumai_record.daoqi_time,
|
||||
'huiyuan_id': order.huiyuan_id,
|
||||
}
|
||||
|
||||
|
||||
def query_wechat_v2_trade_state(out_trade_no, club_id=None):
|
||||
"""微信 V2 查单,返回 (trade_state, transaction_id) 或 (None, None)。"""
|
||||
club_id = club_id or club_id_from_czjilu(out_trade_no)
|
||||
cfg = get_wechat_v2_config(club_id)
|
||||
nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
|
||||
@@ -456,19 +562,15 @@ def query_wechat_v2_trade_state(out_trade_no, club_id=None):
|
||||
|
||||
|
||||
def confirm_member_recharge_paid(dingdan_id, yonghuid):
|
||||
"""
|
||||
前端支付成功确认:先查微信 SUCCESS 再幂等履约。
|
||||
已履约则直接返回成功,不与回调重复入账。
|
||||
"""
|
||||
try:
|
||||
order = Czjilu.query.get(dingdan_id=dingdan_id, yonghuid=yonghuid)
|
||||
except Czjilu.DoesNotExist:
|
||||
raise MemberRechargeError('订单不存在或不属于当前用户', 'not_found')
|
||||
|
||||
if order.leixing != 1:
|
||||
if order.leixing != MEMBER_LEIXING:
|
||||
raise MemberRechargeError('不是会员订单', 'type_error')
|
||||
|
||||
if order.zhuangtai == 3:
|
||||
if order.zhuangtai == MEMBER_PAID_STATUS:
|
||||
goumai = Huiyuangoumai.query.filter(
|
||||
yonghu_id=yonghuid, huiyuan_id=order.huiyuan_id,
|
||||
).first()
|
||||
@@ -482,12 +584,13 @@ def confirm_member_recharge_paid(dingdan_id, yonghuid):
|
||||
return {
|
||||
'fulfilled': False,
|
||||
'already_done': True,
|
||||
'zhuangtai': 3,
|
||||
'zhuangtai': MEMBER_PAID_STATUS,
|
||||
'daoqi_time': goumai.daoqi_time,
|
||||
'huiyuan_id': order.huiyuan_id,
|
||||
'is_trial': goumai.is_trial,
|
||||
}
|
||||
|
||||
if order.zhuangtai != 9:
|
||||
if order.zhuangtai != MEMBER_PENDING_STATUS:
|
||||
raise MemberRechargeError(f'订单状态异常: {order.zhuangtai}', 'status_error')
|
||||
|
||||
trade_state, _ = query_wechat_v2_trade_state(
|
||||
@@ -502,3 +605,27 @@ def confirm_member_recharge_paid(dingdan_id, yonghuid):
|
||||
)
|
||||
result['fulfilled'] = not result.get('already_done')
|
||||
return result
|
||||
|
||||
|
||||
def dashou_in_active_trial_period(yonghuid):
|
||||
"""打手是否处于未过期的体验会员期(限制佣金提现)。"""
|
||||
now = timezone.now()
|
||||
return Huiyuangoumai.query.filter(
|
||||
yonghu_id=yonghuid,
|
||||
is_trial=True,
|
||||
huiyuan_zhuangtai=1,
|
||||
daoqi_time__gt=now,
|
||||
).exists()
|
||||
|
||||
|
||||
def extend_member_entitlement_admin(yonghuid, huiyuan_id, days, club_id=None):
|
||||
"""后台赠送正式会员(无 czjilu、无分红)。"""
|
||||
try:
|
||||
huiyuan = Huiyuan.query.get(huiyuan_id=huiyuan_id)
|
||||
except Huiyuan.DoesNotExist:
|
||||
return None, '会员类型不存在'
|
||||
days = max(1, int(days))
|
||||
record, _ = _apply_huiyuan_entitlement(
|
||||
yonghuid, huiyuan_id, huiyuan, club_id, is_trial=False, purchase_days=days,
|
||||
)
|
||||
return record, None
|
||||
|
||||
@@ -2,6 +2,9 @@ from django.urls import path
|
||||
|
||||
from backend.view import (
|
||||
AddMemberView,
|
||||
ClubMemberCatalogView,
|
||||
ClubMemberDisableView,
|
||||
ClubMemberEnableView,
|
||||
FaKuanChuLiView,
|
||||
FaKuanChuangJianView,
|
||||
FaKuanLieBiaoView,
|
||||
@@ -96,5 +99,8 @@ urlpatterns = [
|
||||
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('houtai/hthycatalog', ClubMemberCatalogView.as_view(), name='jituan_member_catalog'),
|
||||
path('houtai/hthysj', ClubMemberEnableView.as_view(), name='jituan_member_enable'),
|
||||
path('houtai/hthyxj', ClubMemberDisableView.as_view(), name='jituan_member_disable'),
|
||||
path('club/list', ClubListView.as_view(), name='jituan_club_list'),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user