"""俱乐部种子组长 / 管事(邀请链)。 规则: - 若该俱乐部在组长表或管事表已有归属用户,则对应角色不再造种子; - 两者都已有 → 整单跳过; - 缺哪个补哪个;管事邀请人挂到该俱乐部现有/新建的组长 UID。 """ from __future__ import annotations import hashlib import logging import random import time from typing import Any, Dict, Optional, Tuple from django.db import transaction from jituan.models import Club from jituan.services.default_invite import ( get_club_default_dashou_invite, set_club_default_dashou_invite, ) from users.business_models import User from users.models import UserBoss, UserGuanshi, UserZuzhang from utils.invitationcode_utils import CreateInvitationCode logger = logging.getLogger(__name__) def _club_zuzhang_qs(club_id: str): return UserZuzhang.query.filter(user__ClubID=club_id) def _club_guanshi_qs(club_id: str): return UserGuanshi.query.filter(user__ClubID=club_id) def club_invite_seed_status(club_id: str) -> Dict[str, Any]: club_id = (club_id or '').strip() zz_cnt = _club_zuzhang_qs(club_id).count() if club_id else 0 gs_cnt = _club_guanshi_qs(club_id).count() if club_id else 0 return { 'club_id': club_id, 'zuzhang_count': zz_cnt, 'guanshi_count': gs_cnt, 'need_seed': zz_cnt == 0 or gs_cnt == 0, 'has_zuzhang': zz_cnt > 0, 'has_guanshi': gs_cnt > 0, } def _alloc_user_uid() -> str: for _ in range(30): timestamp_part = str(int(time.time()))[-5:].zfill(5) random_part = str(random.randint(0, 99)).zfill(2) user_id = timestamp_part + random_part if len(user_id) == 7 and user_id.isdigit(): if not User.query.filter(UserUID=user_id).exists(): return user_id raise RuntimeError('生成种子用户UID失败') def _phone_for(club_id: str, role: str) -> str: digest = hashlib.md5(f'{club_id}:{role}:seed_phone_v1'.encode('utf-8')).hexdigest() # 199 + 8 位数字,尽量不与真实号冲突 return '199' + ''.join(str(int(ch, 16) % 10) for ch in digest[:8]) def _ensure_seed_user(club_id: str, role: str, nickname: str) -> Tuple[User, bool]: openid = f'{club_id}_seed_openid_{role}_v1'[:64] phone = _phone_for(club_id, role) user = User.query.filter(OpenID=openid).first() if not user: user = User.query.filter(Phone=phone, ClubID=club_id).first() if user: changed = False if (user.ClubID or '') != club_id: user.ClubID = club_id changed = True if changed: user.save(update_fields=['ClubID']) return user, False uid = _alloc_user_uid() user = User.query.create( UserUID=uid, UserName=f'{club_id}_seed_{role}', OpenID=openid, Phone=phone, ClubID=club_id, ) UserBoss.query.create(user=user, nickname=nickname) return user, True def _ensure_zuzhang(user: User) -> Tuple[UserZuzhang, bool]: try: return user.ZuzhangProfile, False except UserZuzhang.DoesNotExist: pass z = UserZuzhang.query.create( user=user, yaoqingma=CreateInvitationCode(str(user.UserUID)), zhuangtai=1, ) return z, True def _ensure_guanshi(user: User, zuzhang_uid: str) -> Tuple[UserGuanshi, bool]: try: return user.GuanshiProfile, False except UserGuanshi.DoesNotExist: pass g = UserGuanshi.query.create( user=user, yaoqingma=CreateInvitationCode(str(user.UserUID)), yaoqingren=zuzhang_uid, zhuangtai=1, ) return g, True def ensure_club_seed_invite(club_id: str, set_default_invite: bool = True) -> Dict[str, Any]: """ 为俱乐部补齐种子组长/管事。 已有对应角色数据则跳过该角色;两者都有则整单 skipped。 """ club_id = (club_id or '').strip() if not club_id: return {'ok': False, 'msg': '缺少 club_id'} club = Club.query.filter(club_id=club_id).first() if not club: return {'ok': False, 'msg': f'俱乐部 {club_id} 不存在'} status_before = club_invite_seed_status(club_id) if not status_before['need_seed']: return { 'ok': True, 'skipped': True, 'msg': '该俱乐部已有组长与管事,无需种子', 'status': status_before, 'default_dashou_invite': get_club_default_dashou_invite(club_id), } created = {'zuzhang': False, 'guanshi': False} zz_profile: Optional[UserZuzhang] = None gs_profile: Optional[UserGuanshi] = None zz_user: Optional[User] = None gs_user: Optional[User] = None with transaction.atomic(): if status_before['has_zuzhang']: zz_profile = _club_zuzhang_qs(club_id).select_related('user').first() zz_user = zz_profile.user if zz_profile else None else: zz_user, _ = _ensure_seed_user(club_id, 'zuzhang', f'{club.name or club_id}种子组长') zz_profile, zz_new = _ensure_zuzhang(zz_user) created['zuzhang'] = zz_new if not zz_user or not zz_profile: return {'ok': False, 'msg': '无法确定种子组长'} if status_before['has_guanshi']: gs_profile = _club_guanshi_qs(club_id).select_related('user').first() gs_user = gs_profile.user if gs_profile else None else: gs_user, _ = _ensure_seed_user(club_id, 'guanshi', f'{club.name or club_id}种子管事') gs_profile, gs_new = _ensure_guanshi(gs_user, str(zz_user.UserUID)) created['guanshi'] = gs_new invite = '' if gs_profile: invite = (gs_profile.yaoqingma or '').strip() # config_json 里还没有显式默认邀请码时,写入管事邀请码 raw_cfg_invite = '' try: raw_cfg_invite = ((club.config_json or {}).get('default_dashou_invite') or '').strip() except Exception: raw_cfg_invite = '' if set_default_invite and invite and not raw_cfg_invite: set_club_default_dashou_invite(club, invite) club.save(update_fields=['config_json']) status_after = club_invite_seed_status(club_id) return { 'ok': True, 'skipped': False, 'msg': '种子组长/管事已就绪', 'created': created, 'status': status_after, 'zuzhang': { 'user_uid': str(zz_user.UserUID) if zz_user else '', 'invite_code': (zz_profile.yaoqingma if zz_profile else '') or '', }, 'guanshi': { 'user_uid': str(gs_user.UserUID) if gs_user else '', 'invite_code': (gs_profile.yaoqingma if gs_profile else '') or '', }, 'default_dashou_invite': get_club_default_dashou_invite(club_id), }