修复任职停用;星之界种子邀请链 seed_xzj_invite_data
This commit is contained in:
@@ -2729,7 +2729,13 @@ class GetMemberListView(APIView):
|
||||
from jituan.services.club_member_admin import build_member_list_payload
|
||||
|
||||
payload = build_member_list_payload(request)
|
||||
return Response({'code': 0, 'data': {'list': payload['list']}, 'club_id': payload.get('club_id'), 'scope': payload.get('scope')})
|
||||
return Response({
|
||||
'code': 0,
|
||||
'data': {'list': payload['list']},
|
||||
'club_id': payload.get('club_id'),
|
||||
'scope': payload.get('scope'),
|
||||
'price_note': payload.get('price_note'),
|
||||
})
|
||||
|
||||
|
||||
class UpdateMemberView(APIView):
|
||||
|
||||
75
jituan/management/commands/bootstrap_xzj_invite.py
Normal file
75
jituan/management/commands/bootstrap_xzj_invite.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""清理重复任职 + 可选为星之界创建组长邀请链。"""
|
||||
import time
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.constants import CLUB_ID_DEFAULT
|
||||
from jituan.models import Club, ClubHuiyuanPrice
|
||||
from jituan.services.admin_assignments import dedupe_active_assignments
|
||||
from jituan.services.club_user import ensure_user_club_id
|
||||
from users.business_models import User
|
||||
from users.models import UserGuanshi, UserZuzhang
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '清理重复 admin_assignment;可选为 xzj 用户创建组长并输出邀请码'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--dedupe-only', action='store_true', help='只清理重复有效任职')
|
||||
parser.add_argument('--club-id', default='xzj', help='目标俱乐部')
|
||||
parser.add_argument('--phone', type=str, default='', help='用户手机号(须已微信登录过)')
|
||||
parser.add_argument('--make-zuzhang', action='store_true', help='将该用户设为 xzj 组长并生成邀请码')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
removed = dedupe_active_assignments()
|
||||
self.stdout.write(self.style.SUCCESS(f'已停用重复任职 {removed} 条'))
|
||||
|
||||
if options.get('dedupe_only'):
|
||||
return
|
||||
|
||||
club_id = (options.get('club_id') or 'xzj').strip()
|
||||
if not Club.query.filter(club_id=club_id).exists():
|
||||
raise CommandError(f'俱乐部 {club_id} 不存在')
|
||||
|
||||
price_cnt = ClubHuiyuanPrice.query.filter(club_id=club_id).count()
|
||||
self.stdout.write(f'club_huiyuan_price({club_id}) 共 {price_cnt} 条(创建俱乐部时从 xq 复制,价相同需后台单独改)')
|
||||
|
||||
phone = (options.get('phone') or '').strip()
|
||||
if options.get('make_zuzhang'):
|
||||
if not phone:
|
||||
raise CommandError('请指定 --phone')
|
||||
user = User.query.filter(Phone=phone).first()
|
||||
if not user:
|
||||
raise CommandError(f'未找到手机号 {phone},请先在星之界小程序登录一次')
|
||||
ensure_user_club_id(user, club_id)
|
||||
user.ClubID = club_id
|
||||
user.save(update_fields=['ClubID'])
|
||||
|
||||
if hasattr(user, 'ZuzhangProfile'):
|
||||
z = user.ZuzhangProfile
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'已是组长,邀请码: {z.yaoqingma}(管事注册用组长码)'
|
||||
))
|
||||
return
|
||||
|
||||
yaoqingma = f'ZZ{int(time.time())}{str(user.UserUID)[-4:]}'
|
||||
with transaction.atomic():
|
||||
UserZuzhang.query.create(
|
||||
user=user,
|
||||
yaoqingma=yaoqingma,
|
||||
zhuangtai=1,
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'已创建 xzj 组长 UserUID={user.UserUID},组长邀请码: {yaoqingma}\n'
|
||||
f'管事注册: 小程序内 guanshizhuce,inviteCode={yaoqingma}'
|
||||
))
|
||||
else:
|
||||
self.stdout.write(
|
||||
'查邀请码 SQL(表名 User 大写):\n'
|
||||
f"SELECT u.UserUID,u.Phone,z.yaoqingma FROM `User` u "
|
||||
f"JOIN user_zuzhang z ON z.UserUUID=u.UserUUID WHERE u.ClubID='{club_id}';\n"
|
||||
f"SELECT u.UserUID,u.Phone,g.yaoqingma FROM `User` u "
|
||||
f"JOIN user_guanshi g ON g.UserUUID=u.UserUUID WHERE u.ClubID='{club_id}';\n"
|
||||
'创建组长: python manage.py bootstrap_xzj_invite --phone 手机号 --make-zuzhang'
|
||||
)
|
||||
133
jituan/management/commands/seed_xzj_invite_data.py
Normal file
133
jituan/management/commands/seed_xzj_invite_data.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""星之界(xzj)邀请链种子数据:自动插入组长+管事(含邀请码),无需先登录小程序。"""
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from jituan.models import Club, ClubHuiyuanPrice
|
||||
from users.business_models import User
|
||||
from users.models import UserBoss, UserGuanshi, UserZuzhang
|
||||
from utils.invitationcode_utils import CreateInvitationCode
|
||||
|
||||
CLUB_ID = 'xzj'
|
||||
WX_APPID = 'wxdefa454152e78a03'
|
||||
|
||||
# 固定种子账号(可重复执行,幂等)
|
||||
SEED_ZUZHANG = {
|
||||
'UserUID': '9900001',
|
||||
'OpenID': 'xzj_seed_openid_zuzhang_v1',
|
||||
'Phone': '19900000001',
|
||||
'UserName': 'xzj_seed_zuzhang',
|
||||
'nickname': '星之界种子组长',
|
||||
}
|
||||
SEED_GUANSHI = {
|
||||
'UserUID': '9900002',
|
||||
'OpenID': 'xzj_seed_openid_guanshi_v1',
|
||||
'Phone': '19900000002',
|
||||
'UserName': 'xzj_seed_guanshi',
|
||||
'nickname': '星之界种子管事',
|
||||
}
|
||||
|
||||
|
||||
def _ensure_club():
|
||||
if Club.query.filter(club_id=CLUB_ID).exists():
|
||||
return
|
||||
call_command(
|
||||
'create_club',
|
||||
CLUB_ID,
|
||||
name='星之界电竞',
|
||||
wx_appid=WX_APPID,
|
||||
)
|
||||
|
||||
|
||||
def _ensure_user(meta):
|
||||
user = User.query.filter(UserUID=meta['UserUID']).first()
|
||||
if not user:
|
||||
user = User.query.filter(OpenID=meta['OpenID']).first()
|
||||
if user:
|
||||
if user.ClubID != CLUB_ID:
|
||||
user.ClubID = CLUB_ID
|
||||
user.save(update_fields=['ClubID'])
|
||||
return user, False
|
||||
|
||||
user = User.query.create(
|
||||
UserUID=meta['UserUID'],
|
||||
UserName=meta['UserName'],
|
||||
OpenID=meta['OpenID'],
|
||||
Phone=meta['Phone'],
|
||||
ClubID=CLUB_ID,
|
||||
)
|
||||
UserBoss.query.create(user=user, nickname=meta['nickname'])
|
||||
return user, True
|
||||
|
||||
|
||||
def _ensure_zuzhang(user):
|
||||
if hasattr(user, 'ZuzhangProfile'):
|
||||
return user.ZuzhangProfile, False
|
||||
yaoqingma = CreateInvitationCode(str(user.UserUID))
|
||||
z = UserZuzhang.query.create(
|
||||
user=user,
|
||||
yaoqingma=yaoqingma,
|
||||
zhuangtai=1,
|
||||
)
|
||||
return z, True
|
||||
|
||||
|
||||
def _ensure_guanshi(user, zuzhang_uid):
|
||||
if hasattr(user, 'GuanshiProfile'):
|
||||
return user.GuanshiProfile, False
|
||||
yaoqingma = CreateInvitationCode(str(user.UserUID))
|
||||
g = UserGuanshi.query.create(
|
||||
user=user,
|
||||
yaoqingma=yaoqingma,
|
||||
yaoqingren=zuzhang_uid,
|
||||
zhuangtai=1,
|
||||
)
|
||||
return g, True
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '插入星之界种子组长+管事(含邀请码),一条命令即可邀请注册'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--force-new-codes',
|
||||
action='store_true',
|
||||
help='已存在时重新生成邀请码(慎用)',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
with transaction.atomic():
|
||||
_ensure_club()
|
||||
|
||||
zz_user, zz_new = _ensure_user(SEED_ZUZHANG)
|
||||
zz_profile, zz_profile_new = _ensure_zuzhang(zz_user)
|
||||
if options.get('force_new_codes') and not zz_profile_new:
|
||||
zz_profile.yaoqingma = CreateInvitationCode(str(zz_user.UserUID))
|
||||
zz_profile.save(update_fields=['yaoqingma'])
|
||||
|
||||
gs_user, gs_new = _ensure_user(SEED_GUANSHI)
|
||||
gs_profile, gs_profile_new = _ensure_guanshi(gs_user, zz_user.UserUID)
|
||||
if options.get('force_new_codes') and not gs_profile_new:
|
||||
gs_profile.yaoqingma = CreateInvitationCode(str(gs_user.UserUID))
|
||||
gs_profile.save(update_fields=['yaoqingma'])
|
||||
|
||||
price_cnt = ClubHuiyuanPrice.query.filter(club_id=CLUB_ID).count()
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('=' * 60))
|
||||
self.stdout.write(self.style.SUCCESS('星之界 xzj 邀请链种子数据已就绪'))
|
||||
self.stdout.write(self.style.SUCCESS('=' * 60))
|
||||
self.stdout.write(f'club_huiyuan_price 条数: {price_cnt}(与星阙相同则需后台 xzj 视图下单独改价)')
|
||||
self.stdout.write('')
|
||||
self.stdout.write('【组长】管事注册用这个邀请码(guanshizhuce / zuzhangyqmzc):')
|
||||
self.stdout.write(f' UserUID={zz_user.UserUID} Phone={SEED_ZUZHANG["Phone"]}')
|
||||
self.stdout.write(f' 组长邀请码 inviteCode = {zz_profile.yaoqingma}')
|
||||
self.stdout.write('')
|
||||
self.stdout.write('【管事】打手注册用这个邀请码(dashouzhuce):')
|
||||
self.stdout.write(f' UserUID={gs_user.UserUID} Phone={SEED_GUANSHI["Phone"]}')
|
||||
self.stdout.write(f' 管事邀请码 inviteCode = {gs_profile.yaoqingma}')
|
||||
self.stdout.write('')
|
||||
self.stdout.write('新建: 组长=%s 管事=%s' % (
|
||||
'是' if zz_new or zz_profile_new else '否(已存在)',
|
||||
'是' if gs_new or gs_profile_new else '否(已存在)',
|
||||
))
|
||||
self.stdout.write(self.style.SUCCESS('=' * 60))
|
||||
97
jituan/services/admin_assignments.py
Normal file
97
jituan/services/admin_assignments.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""admin_assignment 任职 CRUD(修复停用不生效、重复插入)。"""
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
|
||||
from jituan.constants import DATA_SCOPE_ALL, DATA_SCOPE_SINGLE
|
||||
from jituan.models import AdminAssignment
|
||||
|
||||
|
||||
def _club_filter(qs, club_id):
|
||||
if club_id is None:
|
||||
return qs.filter(Q(club_id__isnull=True) | Q(club_id=''))
|
||||
return qs.filter(club_id=club_id)
|
||||
|
||||
|
||||
def find_assignment(yonghuid, club_id, role_code):
|
||||
qs = AdminAssignment.query.filter(yonghuid=yonghuid, role_code=role_code)
|
||||
return _club_filter(qs, club_id).first()
|
||||
|
||||
|
||||
def find_active_assignment(yonghuid, club_id, role_code):
|
||||
qs = AdminAssignment.query.filter(yonghuid=yonghuid, role_code=role_code, status=1)
|
||||
return _club_filter(qs, club_id).first()
|
||||
|
||||
|
||||
def create_or_reactivate_assignment(
|
||||
yonghuid,
|
||||
club_id,
|
||||
role_code,
|
||||
data_scope,
|
||||
is_primary,
|
||||
granted_by,
|
||||
):
|
||||
active = find_active_assignment(yonghuid, club_id, role_code)
|
||||
if active:
|
||||
return None, '该任职记录已存在且有效'
|
||||
|
||||
inactive = find_assignment(yonghuid, club_id, role_code)
|
||||
if inactive and inactive.status != 1:
|
||||
AdminAssignment.objects.filter(pk=inactive.id).update(
|
||||
status=1,
|
||||
data_scope=data_scope,
|
||||
is_primary=is_primary,
|
||||
granted_by=granted_by,
|
||||
UpdateTime=timezone.now(),
|
||||
)
|
||||
return inactive.id, None
|
||||
|
||||
row = AdminAssignment.query.create(
|
||||
yonghuid=yonghuid,
|
||||
club_id=club_id,
|
||||
role_code=role_code,
|
||||
data_scope=data_scope,
|
||||
is_primary=is_primary,
|
||||
granted_by=granted_by,
|
||||
status=1,
|
||||
)
|
||||
return row.id, None
|
||||
|
||||
|
||||
def deactivate_assignment(assignment_id):
|
||||
try:
|
||||
pk = int(assignment_id)
|
||||
except (TypeError, ValueError):
|
||||
return False, '任职 id 无效'
|
||||
|
||||
updated = AdminAssignment.objects.filter(pk=pk, status=1).update(
|
||||
status=0,
|
||||
UpdateTime=timezone.now(),
|
||||
)
|
||||
if updated:
|
||||
return True, None
|
||||
|
||||
exists = AdminAssignment.objects.filter(pk=pk).exists()
|
||||
if exists:
|
||||
return True, None
|
||||
return False, '任职记录不存在'
|
||||
|
||||
|
||||
def dedupe_active_assignments():
|
||||
"""停用重复的有效任职,每组 (yonghuid, club_id, role_code) 只保留一条。"""
|
||||
rows = list(
|
||||
AdminAssignment.query.filter(status=1).order_by('yonghuid', 'club_id', 'role_code', 'id')
|
||||
)
|
||||
seen = {}
|
||||
dup_ids = []
|
||||
for row in rows:
|
||||
key = (row.yonghuid, row.club_id or '', row.role_code)
|
||||
if key in seen:
|
||||
dup_ids.append(row.id)
|
||||
else:
|
||||
seen[key] = row.id
|
||||
if dup_ids:
|
||||
AdminAssignment.objects.filter(pk__in=dup_ids).update(
|
||||
status=0,
|
||||
UpdateTime=timezone.now(),
|
||||
)
|
||||
return len(dup_ids)
|
||||
@@ -13,24 +13,32 @@ from products.models import Huiyuan
|
||||
|
||||
|
||||
def _format_member(m, price_row=None):
|
||||
global_jiage = str(m.jiage)
|
||||
global_guanshifc = str(m.guanshifc)
|
||||
global_zuzhangfc = str(m.zuzhangfc)
|
||||
item = {
|
||||
'huiyuan_id': m.huiyuan_id,
|
||||
'jieshao': m.jieshao,
|
||||
'jtjieshao': m.jtjieshao,
|
||||
'jiage': str(m.jiage),
|
||||
'guanshifc': str(m.guanshifc),
|
||||
'zuzhangfc': str(m.zuzhangfc),
|
||||
'jiage': global_jiage,
|
||||
'guanshifc': global_guanshifc,
|
||||
'zuzhangfc': global_zuzhangfc,
|
||||
'global_jiage': global_jiage,
|
||||
'global_guanshifc': global_guanshifc,
|
||||
'global_zuzhangfc': global_zuzhangfc,
|
||||
'goumai_cishu': m.goumai_cishu,
|
||||
'CreateTime': m.CreateTime.isoformat() if m.CreateTime else None,
|
||||
'UpdateTime': m.UpdateTime.isoformat() if m.UpdateTime else None,
|
||||
'bankuai_id': m.bankuai_id,
|
||||
'club_enabled': True,
|
||||
'price_source': 'global',
|
||||
}
|
||||
if price_row is not None:
|
||||
item['jiage'] = str(price_row.jiage)
|
||||
item['guanshifc'] = str(price_row.guanshifc)
|
||||
item['zuzhangfc'] = str(price_row.zuzhangfc)
|
||||
item['club_enabled'] = bool(price_row.is_enabled)
|
||||
item['price_source'] = 'club'
|
||||
return item
|
||||
|
||||
|
||||
@@ -40,14 +48,17 @@ def build_member_list_payload(request):
|
||||
members = Huiyuan.query.all().order_by('-CreateTime')
|
||||
member_list = []
|
||||
|
||||
if scope == DATA_SCOPE_ALL:
|
||||
for m in members:
|
||||
member_list.append(_format_member(m))
|
||||
else:
|
||||
price_map = {}
|
||||
if scope != DATA_SCOPE_ALL:
|
||||
price_map = {
|
||||
r.huiyuan_id: r
|
||||
for r in ClubHuiyuanPrice.query.filter(club_id=club_id)
|
||||
}
|
||||
|
||||
if scope == DATA_SCOPE_ALL:
|
||||
for m in members:
|
||||
member_list.append(_format_member(m))
|
||||
else:
|
||||
for m in members:
|
||||
row = price_map.get(m.huiyuan_id)
|
||||
if row is None and club_id != CLUB_ID_DEFAULT:
|
||||
@@ -60,6 +71,12 @@ def build_member_list_payload(request):
|
||||
'list': member_list,
|
||||
'club_id': club_id,
|
||||
'scope': scope,
|
||||
'price_note': (
|
||||
'当前展示俱乐部专属售价(club_huiyuan_price);与星阙相同说明尚未单独改价,'
|
||||
'在本俱乐部视图下编辑即可覆盖。'
|
||||
if scope != DATA_SCOPE_ALL else
|
||||
'集团汇总视图展示全局默认价;切换具体俱乐部可查看/编辑各俱乐部售价。'
|
||||
),
|
||||
**list_response_meta(request),
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,11 @@ from jituan.services.admin_context import build_admin_club_context, can_manage_a
|
||||
from jituan.services.wechat_login import login_or_register_by_club
|
||||
from jituan.services.caiwu_stats import build_caiwu_payload
|
||||
from jituan.services.szxx_stats import build_szxx_payload
|
||||
from jituan.services.admin_audit import list_admin_audit_logs
|
||||
from jituan.services.admin_assignments import (
|
||||
create_or_reactivate_assignment,
|
||||
deactivate_assignment,
|
||||
dedupe_active_assignments,
|
||||
)
|
||||
from jituan.services.user_stats import build_user_summary
|
||||
from jituan.services.finance_detail_stats import (
|
||||
build_chongzhi_finance_payload,
|
||||
@@ -270,30 +274,26 @@ class ClubAdminAssignmentListView(APIView):
|
||||
if club_id is None:
|
||||
data_scope = DATA_SCOPE_ALL
|
||||
is_primary = bool(request.data.get('is_primary', False))
|
||||
if AdminAssignment.query.filter(
|
||||
yonghuid=yonghuid, club_id=club_id, role_code=role_code,
|
||||
).exists():
|
||||
return Response({'code': 400, 'msg': '该任职记录已存在'})
|
||||
AdminAssignment.query.create(
|
||||
_, err = create_or_reactivate_assignment(
|
||||
yonghuid=yonghuid,
|
||||
club_id=club_id,
|
||||
role_code=role_code,
|
||||
data_scope=data_scope,
|
||||
is_primary=is_primary,
|
||||
granted_by=user.UserUID,
|
||||
status=1,
|
||||
)
|
||||
if err:
|
||||
return Response({'code': 400, 'msg': err})
|
||||
return Response({'code': 0, 'msg': '任职已添加'})
|
||||
|
||||
if action == 'delete':
|
||||
assignment_id = request.data.get('id')
|
||||
if not assignment_id:
|
||||
return Response({'code': 400, 'msg': '缺少任职 id'})
|
||||
row = AdminAssignment.query.filter(id=assignment_id).first()
|
||||
if not row:
|
||||
return Response({'code': 404, 'msg': '任职记录不存在'})
|
||||
row.status = 0
|
||||
row.save(update_fields=['status', 'UpdateTime'])
|
||||
ok, err = deactivate_assignment(assignment_id)
|
||||
if not ok:
|
||||
return Response({'code': 404, 'msg': err or '停用失败'})
|
||||
dedupe_active_assignments()
|
||||
return Response({'code': 0, 'msg': '任职已停用'})
|
||||
|
||||
if action == 'update':
|
||||
@@ -319,6 +319,7 @@ class ClubAdminAssignmentListView(APIView):
|
||||
return Response({'code': 0, 'msg': '任职已更新'})
|
||||
|
||||
if is_super:
|
||||
dedupe_active_assignments()
|
||||
qs = AdminAssignment.query.filter(status=1).order_by('yonghuid', '-is_primary', 'club_id')
|
||||
else:
|
||||
qs = AdminAssignment.query.filter(yonghuid=user.UserUID, status=1).order_by('-is_primary')
|
||||
|
||||
Reference in New Issue
Block a user