fix: 迁移积分/分红次数/冻结入账,并修正无计划误导文案
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -41,6 +41,21 @@ OPEN_ORDER_STATUSES = (1, 2, 4, 7, 8, 9)
|
|||||||
# 未缴清罚单:待缴纳/申诉中/平台审核中
|
# 未缴清罚单:待缴纳/申诉中/平台审核中
|
||||||
UNPAID_PENALTY_STATUSES = (1, 3, 5)
|
UNPAID_PENALTY_STATUSES = (1, 3, 5)
|
||||||
|
|
||||||
|
# 目标店新建打手时绑定的默认管事邀请码(与小程序 PLATFORM 默认码一致)
|
||||||
|
CLUB_MIGRATE_DEFAULT_GUANSHI_INVITE = {
|
||||||
|
'lxs': '0000008VfJKPu4Spmcjt',
|
||||||
|
'xq': '0000008RffVgKHMj7kQC',
|
||||||
|
}
|
||||||
|
|
||||||
|
# 可提现类字段:迁入走目标店冻结网关(押金/积分/已冻结池除外)
|
||||||
|
_FREEZE_CREDIT_FIELDS = frozenset({
|
||||||
|
('dashou', 'yue'),
|
||||||
|
('guanshi', 'yue'),
|
||||||
|
('zuzhang', 'ketixian_jine'),
|
||||||
|
('shenheguan', 'yue'),
|
||||||
|
})
|
||||||
|
MEMBER_JIFEN_CAP = 10
|
||||||
|
|
||||||
|
|
||||||
def _money(v) -> Decimal:
|
def _money(v) -> Decimal:
|
||||||
return Decimal(str(v or 0)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
|
return Decimal(str(v or 0)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
|
||||||
@@ -129,8 +144,25 @@ def _load_user(uid: str):
|
|||||||
return User.query.filter(UserUID=str(uid)).first()
|
return User.query.filter(UserUID=str(uid)).first()
|
||||||
|
|
||||||
|
|
||||||
def _get_or_create_profile(user, role: str):
|
def _resolve_default_guanshi_uid(club_id: str) -> Optional[str]:
|
||||||
"""目标店无角色时创建空扩展行(仅迁移模块调用)。"""
|
"""按俱乐部硬编码邀请码解析默认管事 UID;解析失败返回 None。"""
|
||||||
|
code = CLUB_MIGRATE_DEFAULT_GUANSHI_INVITE.get((club_id or '').strip())
|
||||||
|
if not code:
|
||||||
|
return None
|
||||||
|
from users.models import UserGuanshi
|
||||||
|
gs = UserGuanshi.query.filter(yaoqingma=code).select_related('user').first()
|
||||||
|
if not gs or not getattr(gs, 'user', None):
|
||||||
|
logger.warning('migrate default guanshi invite not found club=%s code=%s', club_id, code)
|
||||||
|
return None
|
||||||
|
return str(gs.user.UserUID)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_or_create_profile(user, role: str, *, to_club_id: str = ''):
|
||||||
|
"""目标店无角色时创建空扩展行(仅迁移模块调用)。
|
||||||
|
|
||||||
|
新建打手:绑定目标店默认管事(源店邀请人在新店无效)。
|
||||||
|
已有打手资料:不改 yaoqingren(曾在新店注册过的情况保留原绑定)。
|
||||||
|
"""
|
||||||
from users.models import UserDashou, UserShangjia, UserGuanshi, UserZuzhang, UserShenheguan
|
from users.models import UserDashou, UserShangjia, UserGuanshi, UserZuzhang, UserShenheguan
|
||||||
|
|
||||||
mapping = {
|
mapping = {
|
||||||
@@ -150,6 +182,9 @@ def _get_or_create_profile(user, role: str):
|
|||||||
defaults['nicheng'] = f'SJ{user.UserUID}'
|
defaults['nicheng'] = f'SJ{user.UserUID}'
|
||||||
elif role == 'dashou':
|
elif role == 'dashou':
|
||||||
defaults['nicheng'] = f'DS{user.UserUID}'
|
defaults['nicheng'] = f'DS{user.UserUID}'
|
||||||
|
gs_uid = _resolve_default_guanshi_uid(to_club_id)
|
||||||
|
if gs_uid:
|
||||||
|
defaults['yaoqingren'] = gs_uid
|
||||||
obj = model.query.create(**defaults)
|
obj = model.query.create(**defaults)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
@@ -425,6 +460,15 @@ def _transfer_huiyuan(*, from_uid, to_uid, from_club, to_club, role) -> List[dic
|
|||||||
if tid != r.huiyuan_id:
|
if tid != r.huiyuan_id:
|
||||||
chy = Huiyuan.query.filter(huiyuan_id=tid).first()
|
chy = Huiyuan.query.filter(huiyuan_id=tid).first()
|
||||||
child_name = (chy.jieshao if chy else '') or tid
|
child_name = (chy.jieshao if chy else '') or tid
|
||||||
|
# 正式会员迁入:继承源店正式购买次数,且至少 1(再充不可能算首次分红)
|
||||||
|
formal_floor = 0
|
||||||
|
if not bool(r.is_trial):
|
||||||
|
from jituan.services.member_recharge import count_formal_paid_member_orders
|
||||||
|
src_paid = count_formal_paid_member_orders(
|
||||||
|
from_uid, r.huiyuan_id, from_club,
|
||||||
|
)
|
||||||
|
formal_floor = max(int(src_paid or 0), 1)
|
||||||
|
|
||||||
exp, before_dst = _add_huiyuan_duration(
|
exp, before_dst = _add_huiyuan_duration(
|
||||||
to_uid=to_uid,
|
to_uid=to_uid,
|
||||||
to_club=to_club,
|
to_club=to_club,
|
||||||
@@ -450,6 +494,8 @@ def _transfer_huiyuan(*, from_uid, to_uid, from_club, to_club, role) -> List[dic
|
|||||||
'src_huiyuan_id': r.huiyuan_id,
|
'src_huiyuan_id': r.huiyuan_id,
|
||||||
'name': child_name,
|
'name': child_name,
|
||||||
'mapped_from_bundle': tid != r.huiyuan_id,
|
'mapped_from_bundle': tid != r.huiyuan_id,
|
||||||
|
'formal_purchase_floor': formal_floor,
|
||||||
|
'is_trial': bool(r.is_trial),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return out
|
return out
|
||||||
@@ -510,6 +556,7 @@ def gate_for_user(*, club_id: str, user_uid: str, roles: Optional[List[str]] = N
|
|||||||
"""小程序启动/进打手端调用。无计划 → 全 false,现网无感。"""
|
"""小程序启动/进打手端调用。无计划 → 全 false,现网无感。"""
|
||||||
plan = find_active_plan_for_source(club_id)
|
plan = find_active_plan_for_source(club_id)
|
||||||
if not plan:
|
if not plan:
|
||||||
|
target = find_active_plan_for_target(club_id)
|
||||||
return {
|
return {
|
||||||
'active': False,
|
'active': False,
|
||||||
'force': False,
|
'force': False,
|
||||||
@@ -517,6 +564,8 @@ def gate_for_user(*, club_id: str, user_uid: str, roles: Optional[List[str]] = N
|
|||||||
'completed': False,
|
'completed': False,
|
||||||
'plan_id': '',
|
'plan_id': '',
|
||||||
'to_club_id': '',
|
'to_club_id': '',
|
||||||
|
'is_target': bool(target),
|
||||||
|
'target_from_club_id': (target.from_club_id if target else ''),
|
||||||
}
|
}
|
||||||
done = not can_migrate_again(plan, user_uid)
|
done = not can_migrate_again(plan, user_uid)
|
||||||
force_roles = plan.force_roles or ['dashou']
|
force_roles = plan.force_roles or ['dashou']
|
||||||
@@ -536,6 +585,7 @@ def gate_for_user(*, club_id: str, user_uid: str, roles: Optional[List[str]] = N
|
|||||||
'force_roles': force_roles,
|
'force_roles': force_roles,
|
||||||
'success_count': success_count(plan.plan_id, user_uid),
|
'success_count': success_count(plan.plan_id, user_uid),
|
||||||
'max_allowed': max_allowed_success(plan, user_uid),
|
'max_allowed': max_allowed_success(plan, user_uid),
|
||||||
|
'is_target': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -662,10 +712,11 @@ def confirm_migrate(
|
|||||||
# 目标店:白名单涉及角色先建空扩展行
|
# 目标店:白名单涉及角色先建空扩展行
|
||||||
for role in roles_from_whitelist(keys):
|
for role in roles_from_whitelist(keys):
|
||||||
if _profile_of(to_user, role) is None:
|
if _profile_of(to_user, role) is None:
|
||||||
_get_or_create_profile(to_user, role)
|
_get_or_create_profile(to_user, role, to_club_id=plan.to_club_id)
|
||||||
|
|
||||||
items_payload = []
|
items_payload = []
|
||||||
migrate_seq = int(cons.success_count or 0) + 1
|
migrate_seq = int(cons.success_count or 0) + 1
|
||||||
|
freeze_biz_prefix = f'cm:{token_row.token}:{migrate_seq}'
|
||||||
|
|
||||||
for key in keys:
|
for key in keys:
|
||||||
meta = ASSET_CATALOG[key]
|
meta = ASSET_CATALOG[key]
|
||||||
@@ -689,6 +740,8 @@ def confirm_migrate(
|
|||||||
kind=meta['kind'],
|
kind=meta['kind'],
|
||||||
key=key,
|
key=key,
|
||||||
label=meta['label'],
|
label=meta['label'],
|
||||||
|
to_club_id=plan.to_club_id,
|
||||||
|
freeze_biz_id=f'{freeze_biz_prefix}:{key}'[:64],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -735,7 +788,18 @@ def confirm_migrate(
|
|||||||
return serialize_ledger(ledger), ''
|
return serialize_ledger(ledger), ''
|
||||||
|
|
||||||
|
|
||||||
def _transfer_balance_field(*, from_user, to_user, role, field, kind, key, label) -> dict:
|
def _transfer_balance_field(
|
||||||
|
*,
|
||||||
|
from_user,
|
||||||
|
to_user,
|
||||||
|
role,
|
||||||
|
field,
|
||||||
|
kind,
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
to_club_id: str = '',
|
||||||
|
freeze_biz_id: str = '',
|
||||||
|
) -> dict:
|
||||||
src_prof = _lock_profile(from_user, role)
|
src_prof = _lock_profile(from_user, role)
|
||||||
amount = _read_field(src_prof, field, kind)
|
amount = _read_field(src_prof, field, kind)
|
||||||
before_src = amount
|
before_src = amount
|
||||||
@@ -750,17 +814,53 @@ def _transfer_balance_field(*, from_user, to_user, role, field, kind, key, label
|
|||||||
dst_prof = _lock_profile(to_user, role)
|
dst_prof = _lock_profile(to_user, role)
|
||||||
created = False
|
created = False
|
||||||
if dst_prof is None:
|
if dst_prof is None:
|
||||||
dst_prof = _get_or_create_profile(to_user, role)
|
dst_prof = _get_or_create_profile(to_user, role, to_club_id=to_club_id)
|
||||||
created = True
|
created = True
|
||||||
dst_prof = _lock_profile(to_user, role)
|
dst_prof = _lock_profile(to_user, role)
|
||||||
|
|
||||||
before_dst = _read_field(dst_prof, field, kind) if dst_prof else (0 if kind == 'int' else Decimal('0.00'))
|
before_dst = _read_field(dst_prof, field, kind) if dst_prof else (0 if kind == 'int' else Decimal('0.00'))
|
||||||
after_dst = before_dst
|
after_dst = before_dst
|
||||||
|
meta_extra = {}
|
||||||
|
|
||||||
if dst_prof is not None and amount:
|
if dst_prof is not None and amount:
|
||||||
if kind == 'int':
|
# 积分:目标已满 10 保持 10;相加后也不得超过 10
|
||||||
|
if field == 'jifen' and kind == 'int':
|
||||||
|
if int(before_dst) >= MEMBER_JIFEN_CAP:
|
||||||
|
after_dst = MEMBER_JIFEN_CAP
|
||||||
|
else:
|
||||||
|
after_dst = min(MEMBER_JIFEN_CAP, int(before_dst) + int(amount))
|
||||||
|
type(dst_prof).objects.filter(pk=dst_prof.pk).update(jifen=int(after_dst))
|
||||||
|
meta_extra['jifen_capped'] = True
|
||||||
|
meta_extra['jifen_cap'] = MEMBER_JIFEN_CAP
|
||||||
|
elif (role, field) in _FREEZE_CREDIT_FIELDS:
|
||||||
|
# 可提现入账:走目标店冻结网关(开了冻结则按配置拆分)
|
||||||
|
from jituan.services.fund_freeze import credit_role_balance, SOURCE_CLUB_MIGRATE
|
||||||
|
to_uid = str(getattr(to_user, 'UserUID', '') or '')
|
||||||
|
credit_res = credit_role_balance(
|
||||||
|
club_id=to_club_id,
|
||||||
|
user_id=to_uid,
|
||||||
|
role=role,
|
||||||
|
amount=_money(amount),
|
||||||
|
source_type=SOURCE_CLUB_MIGRATE,
|
||||||
|
biz_id=(freeze_biz_id or f'cm:{to_uid}:{key}')[:64],
|
||||||
|
profile=dst_prof,
|
||||||
|
freeze_reason='跨店资产迁移入账',
|
||||||
|
ignore_source_scope=True,
|
||||||
|
)
|
||||||
|
dst_prof = _lock_profile(to_user, role)
|
||||||
|
after_dst = _read_field(dst_prof, field, kind) if dst_prof else before_dst
|
||||||
|
meta_extra.update({
|
||||||
|
'freeze_available': str(credit_res.get('available') or 0),
|
||||||
|
'freeze_frozen': str(credit_res.get('frozen') or 0),
|
||||||
|
'freeze_ledger_id': credit_res.get('ledger_id'),
|
||||||
|
'freeze_skipped': bool(credit_res.get('skipped_freeze')),
|
||||||
|
'freeze_skip_reason': credit_res.get('skip_reason'),
|
||||||
|
})
|
||||||
|
elif kind == 'int':
|
||||||
type(dst_prof).objects.filter(pk=dst_prof.pk).update(**{field: F(field) + int(amount)})
|
type(dst_prof).objects.filter(pk=dst_prof.pk).update(**{field: F(field) + int(amount)})
|
||||||
after_dst = int(before_dst) + int(amount)
|
after_dst = int(before_dst) + int(amount)
|
||||||
else:
|
else:
|
||||||
|
# 押金 / 已冻结池等:原样累加,不再二次冻结
|
||||||
type(dst_prof).objects.filter(pk=dst_prof.pk).update(**{field: F(field) + _money(amount)})
|
type(dst_prof).objects.filter(pk=dst_prof.pk).update(**{field: F(field) + _money(amount)})
|
||||||
after_dst = _money(before_dst) + _money(amount)
|
after_dst = _money(before_dst) + _money(amount)
|
||||||
|
|
||||||
@@ -773,7 +873,7 @@ def _transfer_balance_field(*, from_user, to_user, role, field, kind, key, label
|
|||||||
'after_src': after_src if amount else before_src,
|
'after_src': after_src if amount else before_src,
|
||||||
'before_dst': before_dst,
|
'before_dst': before_dst,
|
||||||
'after_dst': after_dst,
|
'after_dst': after_dst,
|
||||||
'meta_json': {'key': key, 'label': label, 'dst_created': created},
|
'meta_json': {'key': key, 'label': label, 'dst_created': created, **meta_extra},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ SOURCE_ORDER_SETTLE = 'order_settle'
|
|||||||
SOURCE_FENHONG = 'fenhong'
|
SOURCE_FENHONG = 'fenhong'
|
||||||
SOURCE_KAOHE_FEE = 'kaohe_fee'
|
SOURCE_KAOHE_FEE = 'kaohe_fee'
|
||||||
SOURCE_WITHDRAW_REJECT = 'withdraw_reject'
|
SOURCE_WITHDRAW_REJECT = 'withdraw_reject'
|
||||||
|
SOURCE_CLUB_MIGRATE = 'club_migrate'
|
||||||
|
|
||||||
_CFG_CACHE_TTL = 60
|
_CFG_CACHE_TTL = 60
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ def save_config(request, data: dict, operator: str = '') -> Tuple[Optional[dict]
|
|||||||
source_scope = data.get('source_scope')
|
source_scope = data.get('source_scope')
|
||||||
if source_scope is None:
|
if source_scope is None:
|
||||||
# 默认同时覆盖订单结算与分红(管事/组长主要靠分红入账)
|
# 默认同时覆盖订单结算与分红(管事/组长主要靠分红入账)
|
||||||
source_scope = ['order_settle', 'fenhong', 'withdraw_reject']
|
source_scope = ['order_settle', 'fenhong', 'withdraw_reject', 'club_migrate']
|
||||||
if isinstance(source_scope, str):
|
if isinstance(source_scope, str):
|
||||||
try:
|
try:
|
||||||
source_scope = json.loads(source_scope)
|
source_scope = json.loads(source_scope)
|
||||||
|
|||||||
@@ -138,13 +138,58 @@ def has_paid_trial_purchase(yonghuid, huiyuan_id, club_id, exclude_dingdan_id=No
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def count_formal_paid_member_orders(yonghuid, huiyuan_id, club_id, exclude_dingdan_id=None):
|
||||||
|
"""本俱乐部该会员已成功支付的正式单数(不含体验)。"""
|
||||||
|
club_id = club_id or CLUB_ID_DEFAULT
|
||||||
|
n = 0
|
||||||
|
for order in _iter_paid_member_orders(yonghuid, huiyuan_id, club_id, exclude_dingdan_id):
|
||||||
|
if not _order_is_trial_like(order, club_id):
|
||||||
|
n += 1
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_formal_purchase_floor(yonghuid, huiyuan_id, club_id):
|
||||||
|
"""
|
||||||
|
跨店迁入该会员时写入的正式购买次数下限。
|
||||||
|
保证迁入后再充至少按第 2 次起算(不可能走首次管事分红),
|
||||||
|
并能尽量继承源店正式购买次数。
|
||||||
|
"""
|
||||||
|
club_id = club_id or CLUB_ID_DEFAULT
|
||||||
|
from jituan.models import ClubMigrateLedger, ClubMigrateLedgerItem
|
||||||
|
|
||||||
|
ledger_ids = list(
|
||||||
|
ClubMigrateLedger.query.filter(
|
||||||
|
to_uid=str(yonghuid),
|
||||||
|
to_club_id=str(club_id),
|
||||||
|
status=ClubMigrateLedger.STATUS_SUCCESS,
|
||||||
|
).values_list('id', flat=True)
|
||||||
|
)
|
||||||
|
if not ledger_ids:
|
||||||
|
return 0
|
||||||
|
floor = 0
|
||||||
|
for it in ClubMigrateLedgerItem.query.filter(
|
||||||
|
ledger_id__in=ledger_ids,
|
||||||
|
field='huiyuan',
|
||||||
|
huiyuan_id=str(huiyuan_id),
|
||||||
|
item_type=ClubMigrateLedgerItem.ITEM_HUIYUAN,
|
||||||
|
):
|
||||||
|
meta = it.meta_json or {}
|
||||||
|
try:
|
||||||
|
v = int(meta.get('formal_purchase_floor') or 0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
v = 0
|
||||||
|
if v > floor:
|
||||||
|
floor = v
|
||||||
|
return floor
|
||||||
|
|
||||||
|
|
||||||
def has_formal_member_purchase(yonghuid, huiyuan_id, club_id, exclude_dingdan_id=None):
|
def has_formal_member_purchase(yonghuid, huiyuan_id, club_id, exclude_dingdan_id=None):
|
||||||
"""是否已有成功支付的正式会员单(用于禁止再买体验)。"""
|
"""是否已有成功支付的正式会员单(用于禁止再买体验)。"""
|
||||||
club_id = club_id or CLUB_ID_DEFAULT
|
club_id = club_id or CLUB_ID_DEFAULT
|
||||||
for order in _iter_paid_member_orders(yonghuid, huiyuan_id, club_id, exclude_dingdan_id):
|
if count_formal_paid_member_orders(yonghuid, huiyuan_id, club_id, exclude_dingdan_id) > 0:
|
||||||
if not _order_is_trial_like(order, club_id):
|
return True
|
||||||
return True
|
# 跨店迁入正式会员视同已购正式(不可再买体验)
|
||||||
return False
|
return migrate_formal_purchase_floor(yonghuid, huiyuan_id, club_id) >= 1
|
||||||
|
|
||||||
|
|
||||||
def has_any_prior_paid_member_recharge(yonghuid, exclude_dingdan_id=None):
|
def has_any_prior_paid_member_recharge(yonghuid, exclude_dingdan_id=None):
|
||||||
@@ -218,13 +263,14 @@ def resolve_formal_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club
|
|||||||
"""
|
"""
|
||||||
打手第几次正式购买该会员(duoci_fenhong.cishu)。
|
打手第几次正式购买该会员(duoci_fenhong.cishu)。
|
||||||
体验单(含历史脏数据)不计入。
|
体验单(含历史脏数据)不计入。
|
||||||
|
跨店迁入会员:按 formal_purchase_floor 抬高下限,迁入后再充不可能算首次。
|
||||||
"""
|
"""
|
||||||
club_id = club_id or CLUB_ID_DEFAULT
|
club_id = club_id or CLUB_ID_DEFAULT
|
||||||
prior_paid = 0
|
prior_paid = count_formal_paid_member_orders(
|
||||||
for order in _iter_paid_member_orders(yonghuid, huiyuan_id, club_id, current_dingdan_id):
|
yonghuid, huiyuan_id, club_id, current_dingdan_id,
|
||||||
if not _order_is_trial_like(order, club_id):
|
)
|
||||||
prior_paid += 1
|
floor = migrate_formal_purchase_floor(yonghuid, huiyuan_id, club_id)
|
||||||
return prior_paid + 1
|
return max(prior_paid, floor) + 1
|
||||||
|
|
||||||
|
|
||||||
def _truncate_shuoming(text, max_len=100):
|
def _truncate_shuoming(text, max_len=100):
|
||||||
|
|||||||
@@ -38,6 +38,34 @@ def _user_club(user) -> str:
|
|||||||
return str(getattr(user, 'ClubID', '') or '').strip()
|
return str(getattr(user, 'ClubID', '') or '').strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_club_id(request) -> str:
|
||||||
|
"""小程序 club 优先:body.club_id → X-Club-Id → 用户 ClubID。"""
|
||||||
|
club_id = str(request.data.get('club_id') or '').strip()
|
||||||
|
if not club_id:
|
||||||
|
club_id = (
|
||||||
|
request.META.get('HTTP_X_CLUB_ID')
|
||||||
|
or request.headers.get('X-Club-Id')
|
||||||
|
or ''
|
||||||
|
).strip()
|
||||||
|
if not club_id:
|
||||||
|
club_id = _user_club(request.user)
|
||||||
|
return club_id
|
||||||
|
|
||||||
|
|
||||||
|
def _no_source_plan_response(club_id: str):
|
||||||
|
"""无源店计划时给可区分文案(避免前端笼统「暂无迁移计划」)。"""
|
||||||
|
cid = (club_id or '').strip() or '(空)'
|
||||||
|
target = migrate_svc.find_active_plan_for_target(club_id) if club_id else None
|
||||||
|
if target:
|
||||||
|
msg = (
|
||||||
|
f'本店({cid})是迁入目标店,请到源店小程序'
|
||||||
|
f'({target.from_club_id})完成迁出'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
msg = f'当前俱乐部({cid})无有效迁出计划,请确认后台源店/启用/时间窗'
|
||||||
|
return Response({'code': 400, 'msg': msg, 'data': {'club_id': cid, 'is_target': bool(target)}})
|
||||||
|
|
||||||
|
|
||||||
def _user_roles(user) -> list:
|
def _user_roles(user) -> list:
|
||||||
roles = []
|
roles = []
|
||||||
for role, attr in (
|
for role, attr in (
|
||||||
@@ -76,16 +104,13 @@ class ClubMigrateGateView(APIView):
|
|||||||
parser_classes = [JSONParser]
|
parser_classes = [JSONParser]
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
club_id = (request.data.get('club_id') or '').strip()
|
club_id = _resolve_club_id(request)
|
||||||
if not club_id:
|
|
||||||
club_id = (request.META.get('HTTP_X_CLUB_ID') or request.headers.get('X-Club-Id') or '').strip()
|
|
||||||
if not club_id:
|
|
||||||
club_id = _user_club(request.user)
|
|
||||||
data = migrate_svc.gate_for_user(
|
data = migrate_svc.gate_for_user(
|
||||||
club_id=club_id,
|
club_id=club_id,
|
||||||
user_uid=str(request.user.UserUID),
|
user_uid=str(request.user.UserUID),
|
||||||
roles=_user_roles(request.user),
|
roles=_user_roles(request.user),
|
||||||
)
|
)
|
||||||
|
data['resolved_club_id'] = club_id or ''
|
||||||
return Response({'code': 0, 'msg': 'ok', 'data': data})
|
return Response({'code': 0, 'msg': 'ok', 'data': data})
|
||||||
|
|
||||||
|
|
||||||
@@ -95,27 +120,29 @@ class ClubMigratePreviewView(APIView):
|
|||||||
parser_classes = [JSONParser]
|
parser_classes = [JSONParser]
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
club_id = (request.data.get('club_id') or '').strip()
|
club_id = _resolve_club_id(request)
|
||||||
if not club_id:
|
|
||||||
club_id = (request.META.get('HTTP_X_CLUB_ID') or request.headers.get('X-Club-Id') or '').strip()
|
|
||||||
if not club_id:
|
|
||||||
club_id = _user_club(request.user)
|
|
||||||
plan = migrate_svc.find_active_plan_for_source(club_id)
|
plan = migrate_svc.find_active_plan_for_source(club_id)
|
||||||
if not plan:
|
if not plan:
|
||||||
return Response({'code': 400, 'msg': '当前俱乐部无有效迁移计划'})
|
return _no_source_plan_response(club_id)
|
||||||
uid = str(request.user.UserUID)
|
uid = str(request.user.UserUID)
|
||||||
ok, msg = migrate_svc.check_conditions(plan, uid)
|
try:
|
||||||
|
ok, msg = migrate_svc.check_conditions(plan, uid)
|
||||||
|
assets = migrate_svc.preview_assets(plan, uid)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception('migrate preview failed club=%s uid=%s: %s', club_id, uid, e)
|
||||||
|
return Response({'code': 500, 'msg': f'预览加载失败:{e}'})
|
||||||
return Response({
|
return Response({
|
||||||
'code': 0,
|
'code': 0,
|
||||||
'msg': 'ok',
|
'msg': 'ok',
|
||||||
'data': {
|
'data': {
|
||||||
'plan': migrate_svc.serialize_plan(plan),
|
'plan': migrate_svc.serialize_plan(plan),
|
||||||
'assets': migrate_svc.preview_assets(plan, uid),
|
'assets': assets,
|
||||||
'condition_ok': ok,
|
'condition_ok': ok,
|
||||||
'condition_msg': msg,
|
'condition_msg': msg,
|
||||||
'can_migrate': migrate_svc.can_migrate_again(plan, uid),
|
'can_migrate': migrate_svc.can_migrate_again(plan, uid),
|
||||||
'success_count': migrate_svc.success_count(plan.plan_id, uid),
|
'success_count': migrate_svc.success_count(plan.plan_id, uid),
|
||||||
'max_allowed': migrate_svc.max_allowed_success(plan, uid),
|
'max_allowed': migrate_svc.max_allowed_success(plan, uid),
|
||||||
|
'resolved_club_id': club_id or '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -126,18 +153,14 @@ class ClubMigrateSetPasswordView(APIView):
|
|||||||
parser_classes = [JSONParser]
|
parser_classes = [JSONParser]
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
club_id = (request.data.get('club_id') or '').strip()
|
club_id = _resolve_club_id(request)
|
||||||
if not club_id:
|
|
||||||
club_id = (request.META.get('HTTP_X_CLUB_ID') or request.headers.get('X-Club-Id') or '').strip()
|
|
||||||
if not club_id:
|
|
||||||
club_id = _user_club(request.user)
|
|
||||||
password = str(request.data.get('password') or '').strip()
|
password = str(request.data.get('password') or '').strip()
|
||||||
password2 = str(request.data.get('password2') or '').strip()
|
password2 = str(request.data.get('password2') or '').strip()
|
||||||
if password != password2:
|
if password != password2:
|
||||||
return Response({'code': 400, 'msg': '两次密码不一致'})
|
return Response({'code': 400, 'msg': '两次密码不一致'})
|
||||||
plan = migrate_svc.find_active_plan_for_source(club_id)
|
plan = migrate_svc.find_active_plan_for_source(club_id)
|
||||||
if not plan:
|
if not plan:
|
||||||
return Response({'code': 400, 'msg': '当前俱乐部无有效迁移计划'})
|
return _no_source_plan_response(club_id)
|
||||||
|
|
||||||
token_row, err = migrate_svc.set_password_and_issue(
|
token_row, err = migrate_svc.set_password_and_issue(
|
||||||
plan=plan,
|
plan=plan,
|
||||||
@@ -171,11 +194,11 @@ class ClubMigrateIssueQrView(APIView):
|
|||||||
from jituan.models import ClubMigrateToken
|
from jituan.models import ClubMigrateToken
|
||||||
from jituan.services.club_migrate_qr import generate_migrate_qrcode
|
from jituan.services.club_migrate_qr import generate_migrate_qrcode
|
||||||
|
|
||||||
club_id = (request.data.get('club_id') or _user_club(request.user)).strip()
|
club_id = _resolve_club_id(request)
|
||||||
uid = str(request.user.UserUID)
|
uid = str(request.user.UserUID)
|
||||||
plan = migrate_svc.find_active_plan_for_source(club_id)
|
plan = migrate_svc.find_active_plan_for_source(club_id)
|
||||||
if not plan:
|
if not plan:
|
||||||
return Response({'code': 400, 'msg': '当前俱乐部无有效迁移计划'})
|
return _no_source_plan_response(club_id)
|
||||||
token_row = (
|
token_row = (
|
||||||
ClubMigrateToken.query.filter(
|
ClubMigrateToken.query.filter(
|
||||||
plan_id=plan.plan_id,
|
plan_id=plan.plan_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user