fix: 总会员按子会员迁出;我的会员去掉展开重复展示

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-29 04:37:08 +08:00
parent 2f684a0203
commit 07ae350d55
2 changed files with 139 additions and 44 deletions

View File

@@ -477,11 +477,43 @@ def _resolve_migrate_target_huiyuan_ids(
*, src_huiyuan_id: str, from_club: str = '', to_club: str = '',
) -> List[str]:
"""
会员 ID 集团公共(含总会员):原样迁同 ID。
不要求目标店 ClubHuiyuanPrice 上架,不拆成子会员。
普通会员:原样迁同 ID。
总会员:按子会员格式迁出(写入各子会员开通记录),不在目标店再挂一条总会员。
未配置子项时才退回迁总会员本身,避免丢权益。
"""
src_id = str(src_huiyuan_id or '').strip()
return [src_id] if src_id else []
from jituan.services.huiyuan_bundle import (
is_bundle_huiyuan,
normalize_huiyuan_id,
resolve_bundle_included_ids,
)
src_id = normalize_huiyuan_id(src_huiyuan_id)
if not src_id:
return []
if not is_bundle_huiyuan(src_id):
return [src_id]
kids = []
seen = set()
for cid in (from_club, to_club):
for hid in resolve_bundle_included_ids(
src_id,
club_id=(cid or '').strip() or None,
record_club_id=(from_club or '').strip() or None,
) or []:
kid = normalize_huiyuan_id(hid)
if not kid:
continue
core = kid.lstrip('0') or '0'
if core in seen:
continue
seen.add(core)
kids.append(kid)
if kids:
break
if kids:
return kids
return [src_id]
def _revoke_source_member_czjilu(from_uid: str, huiyuan_id: str):
@@ -627,14 +659,18 @@ def _add_huiyuan_duration(*, to_uid: str, to_club: str, huiyuan_id: str, remain,
def _transfer_huiyuan(*, from_uid, to_uid, from_club, to_club, role) -> List[dict]:
"""
未到期会员:目标店加同等剩余时长(同会员 ID含总会员源店置已到期并撤销充值权益。
数据来源与打手端 clumber 一致(开通表 + 充值单)。
未到期会员:目标店加同等剩余时长源店置已到期并撤销充值权益。
总会员按子会员拆分写入(不用总会员 ID 在目标店再挂一条)。
"""
from products.models import Huiyuan
from jituan.services.huiyuan_bundle import is_bundle_huiyuan, normalize_huiyuan_id
now = _now_aware()
entries = _collect_migratable_huiyuan(from_uid, from_club, for_update=True)
out = []
# 同一迁移内按子会员去重,避免源店「总会员+子会员」重复加时长
transferred_cores = set()
for e in entries:
src_exp = _aware(e.get('daoqi_time'))
name = (e.get('jieshao') or '').strip()
@@ -654,8 +690,12 @@ def _transfer_huiyuan(*, from_uid, to_uid, from_club, to_club, role) -> List[dic
if remain is None:
continue
tid = (_resolve_migrate_target_huiyuan_ids(src_huiyuan_id=e['huiyuan_id']) or [None])[0]
if not tid:
target_ids = _resolve_migrate_target_huiyuan_ids(
src_huiyuan_id=e['huiyuan_id'],
from_club=from_club,
to_club=to_club,
)
if not target_ids:
raise ValueError(f'会员 ID 无效,无法迁移: {e["huiyuan_id"]}')
formal_floor = 0
@@ -666,23 +706,40 @@ def _transfer_huiyuan(*, from_uid, to_uid, from_club, to_club, role) -> List[dic
)
formal_floor = max(int(src_paid or 0), 1)
wrote_any = False
src_is_bundle = is_bundle_huiyuan(e['huiyuan_id'])
for tid in target_ids:
tid = normalize_huiyuan_id(tid) or tid
core = (tid.lstrip('0') or '0') if tid else ''
if core and core in transferred_cores:
continue
child_name = name
if src_is_bundle or tid != normalize_huiyuan_id(e['huiyuan_id']):
chy = Huiyuan.query.filter(huiyuan_id=tid).first()
if not chy:
from jituan.services.huiyuan_bundle import expand_huiyuan_id_variants
for vid in expand_huiyuan_id_variants(tid):
chy = Huiyuan.query.filter(huiyuan_id=vid).first()
if chy:
break
child_name = ((chy.jieshao if chy else '') or '').strip() or tid
exp, before_dst = _add_huiyuan_duration(
to_uid=to_uid,
to_club=to_club,
huiyuan_id=tid,
remain=remain,
jieshao=name,
jieshao=child_name,
is_trial=bool(e.get('is_trial')),
has_used_trial=bool(e.get('has_used_trial')),
)
if exp is None:
raise ValueError(f'会员过户失败(加时长): {name}({tid})')
raise ValueError(f'会员过户失败(加时长): {child_name}({tid})')
_expire_source_membership(
from_uid=from_uid,
huiyuan_id=e['huiyuan_id'],
goumai_row=e.get('goumai_row'),
)
if core:
transferred_cores.add(core)
wrote_any = True
out.append({
'item_type': ClubMigrateLedgerItem.ITEM_HUIYUAN,
'role': role,
@@ -697,13 +754,22 @@ def _transfer_huiyuan(*, from_uid, to_uid, from_club, to_club, role) -> List[dic
'before_src': before,
'before_dst': before_dst,
'src_huiyuan_id': e['huiyuan_id'],
'name': name,
'mapped_from_bundle': False,
'name': child_name,
'mapped_from_bundle': bool(src_is_bundle and tid != normalize_huiyuan_id(e['huiyuan_id'])),
'formal_purchase_floor': formal_floor,
'is_trial': bool(e.get('is_trial')),
'source': e.get('source') or '',
},
})
if not wrote_any:
raise ValueError(f'会员过户失败: {name}({e["huiyuan_id"]})')
_expire_source_membership(
from_uid=from_uid,
huiyuan_id=e['huiyuan_id'],
goumai_row=e.get('goumai_row'),
)
return out

View File

@@ -365,6 +365,7 @@ def enrich_clumber_item(record, club_id=None):
'daoqi': daoqi_str,
'huiyuan_zhuangtai': 1,
'shifou_daoqi': False,
'display_owned': True,
}
if is_bundle_huiyuan(record.huiyuan_id):
item['is_bundle'] = True
@@ -379,16 +380,30 @@ def enrich_clumber_item(record, club_id=None):
def _append_clumber_id_variants(items, seen, base_item, huiyuan_id):
"""为老端 == 比较补齐多种会员 ID 写法。"""
"""为老端 == 比较补齐多种会员 ID 写法。仅首条供「我的会员」展示。"""
hide_owned = (
base_item.get('display_owned') is False
or bool(base_item.get('from_bundle'))
or bool(base_item.get('id_variant'))
)
first = True
for vid in expand_huiyuan_id_variants(huiyuan_id):
if vid in seen:
continue
seen.add(vid)
items.append({
row = {
**base_item,
'huiyuanid': vid,
'huiyuan_id': vid,
})
}
if hide_owned or not first:
row['display_owned'] = False
if not first:
row['id_variant'] = True
else:
row['display_owned'] = True
first = False
items.append(row)
def _append_bundle_sub_clumber(items, seen, base_item):
@@ -405,10 +420,23 @@ def _append_bundle_sub_clumber(items, seen, base_item):
sid = str(sub_id).strip()
if not sid:
continue
# 子项用子会员自己的名称;不进「我的会员」主列表(仅抢单匹配)
child_name = ''
try:
hy = Huiyuan.query.filter(huiyuan_id=sid).first()
if not hy:
for vid in expand_huiyuan_id_variants(sid):
hy = Huiyuan.query.filter(huiyuan_id=vid).first()
if hy:
break
child_name = ((hy.jieshao if hy else '') or '').strip()
except Exception:
child_name = ''
sub_base = {
'huiyuanming': base_item.get('huiyuanming', ''),
'huiyuanming': child_name or sid,
'daoqi': base_item.get('daoqi', ''),
'from_bundle': bundle_hid,
'display_owned': False,
'huiyuan_zhuangtai': 1,
'shifou_daoqi': False,
}
@@ -486,6 +514,7 @@ def _build_clumber_from_paid_czjilu(yonghu_id, club_id=None):
'daoqi': format_legacy_daoqi(daoqi),
'huiyuan_zhuangtai': 1,
'shifou_daoqi': False,
'display_owned': True,
}
if is_bundle_huiyuan(hid):
base['is_bundle'] = True