feat: 后台添加商家 + 龙先生 lsx 俱乐部/邀请链种子

支持客服按用户UID开通商家(分俱乐部校验);新增 seed_club_lsx、seed_lsx_invite_data。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-23 21:29:22 +08:00
parent 46d64fbf92
commit cddd231dc2
6 changed files with 313 additions and 1 deletions

View File

@@ -4,7 +4,7 @@ from django.views.decorators.csrf import csrf_exempt
from .view import (GetRolePermissionView,
ModifyRolePermissionView, AddRoleView, GetAdminRolesView, GetAdminUserListView, AddAdminUserView,
ModifyAdminUserView, KefuGetDashouListView, KefuGetDashouDetailView, KefuUpdateDashouView, KefuGetShangjiaListView,
KefuGetShangjiaDetailView, KefuUpdateShangjiaView, GetProductBaseDataView, GetProductListView, SaveProductOrderView,
KefuGetShangjiaDetailView, KefuUpdateShangjiaView, KefuAddShangjiaView, GetProductBaseDataView, GetProductListView, SaveProductOrderView,
AddProductView, DeleteProductView, GetProductDetailView, UpdateProductView, UpdateZhiKanShenheView, UpdateMemberView, GetMemberListView,
AddMemberView, GetGuanliListView, GetGuanliDetailView, PromoteGuanshiToZuzhangView, ChangeInviterView, UpdateGuanliView, GetWithdrawSettingsView,
UpdateWithdrawSettingsView, GetZuzhangListView, GetZuzhangDetailView, UpdateZuzhangView, GetOperationLogListView, GetProductTypeZoneView,
@@ -36,6 +36,7 @@ urlpatterns = [
path('hqsjgllb', KefuGetShangjiaListView.as_view(), name='后台获取商家列表'),
path('hthqsjxq', KefuGetShangjiaDetailView.as_view(), name='后台获取商家详情'),
path('xgsjxx', KefuUpdateShangjiaView.as_view(), name='后台修改商家数据'),
path('tjsj', KefuAddShangjiaView.as_view(), name='后台添加商家'),
path('htsphqjcsx', GetProductBaseDataView.as_view(), name='后台获取商品类型专区'),
path('hthqsplb', GetProductListView.as_view(), name='后台获取商品数据'),

View File

@@ -35,6 +35,7 @@ from .views.roles import (
from .views.kefu import (
KefuGetDashouListView, KefuGetDashouDetailView, KefuUpdateDashouView,
KefuGetShangjiaListView, KefuGetShangjiaDetailView, KefuUpdateShangjiaView,
KefuAddShangjiaView,
KefuChatPermissionsView,
DashouGiftHuiyuanSaveView, DashouGiftHuiyuanRemoveView,
)
@@ -100,6 +101,7 @@ __all__ = [
'KefuGetDashouListView', 'KefuGetDashouDetailView', 'KefuUpdateDashouView',
'DashouGiftHuiyuanSaveView', 'DashouGiftHuiyuanRemoveView',
'KefuGetShangjiaListView', 'KefuGetShangjiaDetailView', 'KefuUpdateShangjiaView',
'KefuAddShangjiaView',
'KefuChatPermissionsView',
# products
'GetProductBaseDataView', 'GetProductListView', 'SaveProductOrderView',

View File

@@ -1110,9 +1110,115 @@ class KefuUpdateShangjiaView(APIView):
return Response({'code': 400, 'msg': '未知操作'})
class KefuAddShangjiaView(APIView):
"""
后台按用户ID开通商家分俱乐部禁止前端自助注册场景使用
POST /houtai/tjsj 或 /jituan/houtai/tjsj
参数username, yonghuid用户业务ID, nicheng可选
权限1100a
"""
permission_classes = []
parser_classes = [JSONParser]
def post(self, request):
username = request.data.get('username', '').strip()
yonghuid = str(request.data.get('yonghuid', '')).strip()
nicheng_in = (request.data.get('nicheng') or '').strip()
if not username:
return Response({'code': 401, 'msg': '缺少username'})
if not yonghuid:
return Response({'code': 400, 'msg': '请填写用户ID'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
if '1100a' not in permissions:
return Response({'code': 403, 'msg': '无权限添加商家需要1100a'})
try:
user = User.query.get(UserUID=yonghuid)
except User.DoesNotExist:
return Response({'code': 404, 'msg': '用户不存在请确认用户ID'})
denied = forbid_if_user_out_of_scope(request, user)
if denied:
return denied
if not getattr(user, 'IsActive', True):
return Response({'code': 400, 'msg': '该账号已被封禁,无法开通商家'})
try:
dashou = user.DashouProfile
if int(getattr(dashou, 'zhanghaozhuangtai', 1) or 0) != 1:
return Response({'code': 400, 'msg': '该账号已被封禁,无法开通商家'})
except UserDashou.DoesNotExist:
pass
except Exception:
pass
if UserShangjia.query.filter(user=user).exists():
return Response({'code': 400, 'msg': '该用户已是商家,无需重复添加'})
from merchant_ops.services.authz import assert_no_active_staff_when_merchant_register, StaffAuthError
try:
assert_no_active_staff_when_merchant_register(user)
except StaffAuthError as e:
return Response({'code': 403, 'msg': getattr(e, 'msg', None) or str(e)})
except Exception:
pass
boss_nickname = '普通商家'
try:
boss = user.BossProfile
if boss.nickname and boss.nickname.strip():
boss_nickname = boss.nickname.strip()
except UserBoss.DoesNotExist:
pass
shangjia_nicheng = nicheng_in or boss_nickname
if UserShangjia.query.filter(nicheng=shangjia_nicheng).exists():
shangjia_nicheng = f"{shangjia_nicheng}{random.randint(1000, 9999)}"
try:
with transaction.atomic():
shop = UserShangjia.query.create(
user=user,
nicheng=shangjia_nicheng,
zhuangtai=1,
yue=0.00,
fabu=0,
tuikuan=0,
chengjiao=0,
jinridingdan=0,
jinriliushui=0.00,
jinyuedingdan=0,
jinyueliushui=0.00,
dianhua='',
wechat='',
)
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_SHANGJIA,
qitashuoming=(
f'【后台】添加商家用户ID={yonghuid},昵称={shop.nicheng}'
f'操作人={kefu.user.Phone}'
),
)
except Exception as e:
logger.exception('后台添加商家失败')
return Response({'code': 500, 'msg': f'添加失败:{e}'})
return Response({
'code': 0,
'msg': '商家添加成功',
'data': {
'yonghuid': yonghuid,
'nicheng': shop.nicheng,
'zhuangtai': shop.zhuangtai,
},
})
class KefuChatPermissionsView(APIView):

View File

@@ -0,0 +1,66 @@
"""一键初始化龙先生电竞(lsx)俱乐部(默认不批量给所有客服加任职)。"""
from django.core.management import call_command
from django.core.management.base import BaseCommand
from jituan.models import Club
class Command(BaseCommand):
help = '创建龙先生电竞俱乐部(lsx);任职请单独 seed_admin_assignments --phone ...'
def add_arguments(self, parser):
parser.add_argument(
'--wx-appid', default='wx7ff90e9d024fcdb8', help='龙先生电竞小程序 appid',
)
parser.add_argument(
'--assignments',
action='store_true',
help='同时给 --phone 指定客服加 lsx 任职(需配合 --phone',
)
parser.add_argument(
'--phone', type=str, default='', help='与 --assignments 联用:客服手机号',
)
def handle(self, *args, **options):
wx_appid = (options.get('wx_appid') or '').strip()
club_id = 'lsx'
name = '龙先生电竞'
if Club.query.filter(club_id=club_id).exists():
self.stdout.write(self.style.WARNING(f'俱乐部 {club_id} 已存在,跳过 create_club'))
club = Club.query.filter(club_id=club_id).first()
if club and wx_appid and (club.wx_appid or '') != wx_appid:
club.wx_appid = wx_appid
club.save(update_fields=['wx_appid'])
self.stdout.write(f'已更新 {club_id}.wx_appid = {wx_appid}')
else:
self.stdout.write(f'创建俱乐部 {club_id} ...')
call_command(
'create_club',
club_id,
name=name,
wx_appid=wx_appid,
)
if options.get('assignments'):
phone = (options.get('phone') or '').strip()
if not phone:
self.stdout.write(self.style.WARNING(
'未指定 --phone跳过任职。示例'
'python manage.py seed_club_lsx --assignments --phone 你的手机号'
))
else:
call_command(
'seed_admin_assignments',
club_id=club_id,
phone=phone,
)
else:
self.stdout.write(
'未加任职。需要时在后台「数据范围」配置,或:'
'python manage.py seed_admin_assignments --phone 手机号 --club-id lsx'
)
self.stdout.write(self.style.SUCCESS(
f'龙先生电竞 {club_id} 就绪。请在后台「俱乐部配置」检查支付密钥与 wx_appid。'
))

View File

@@ -0,0 +1,135 @@
"""龙先生电竞(lsx)邀请链种子:自动插入组长+管事(含邀请码),无需先登录小程序。"""
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 = 'lsx'
WX_APPID = 'wx7ff90e9d024fcdb8'
# 固定种子账号可重复执行幂等UID/手机避开 xzj 的 9900001/2
SEED_ZUZHANG = {
'UserUID': '9910001',
'OpenID': 'lsx_seed_openid_zuzhang_v1',
'Phone': '19910000001',
'UserName': 'lsx_seed_zuzhang',
'nickname': '龙先生种子组长',
}
SEED_GUANSHI = {
'UserUID': '9910002',
'OpenID': 'lsx_seed_openid_guanshi_v1',
'Phone': '19910000002',
'UserName': 'lsx_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('龙先生电竞 lsx 邀请链种子数据已就绪'))
self.stdout.write(self.style.SUCCESS('=' * 60))
self.stdout.write(f'club_huiyuan_price 条数: {price_cnt}')
self.stdout.write('')
self.stdout.write('【组长】管事注册用这个邀请码guanshizhuce:')
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('商家:请在后台「商家管理 → 添加商家」按用户UID开通不再用前端邀请码')
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))

View File

@@ -18,6 +18,7 @@ from backend.view import (
KhgglView,
KefuGetDashouListView,
KefuGetShangjiaListView,
KefuAddShangjiaView,
PopupNoticeListAPIView,
PopupNoticeModifyAPIView,
ShgxgsjView,
@@ -123,6 +124,7 @@ urlpatterns = [
path('houtai/kefuhqdslb', KefuGetDashouListView.as_view(), name='jituan_dashou_list'),
path('houtai/hthqgslb', GetGuanliListView.as_view(), name='jituan_guanshi_list'),
path('houtai/hqsjgllb', KefuGetShangjiaListView.as_view(), name='jituan_shangjia_list'),
path('houtai/tjsj', KefuAddShangjiaView.as_view(), name='jituan_shangjia_add'),
path('houtai/hthqzzlb', GetZuzhangListView.as_view(), name='jituan_zuzhang_list'),
path('houtai/lunbo-list', ClubLunboListView.as_view(), name='jituan_lunbo_list'),
path('houtai/lunbo-manage', ClubLunboManageView.as_view(), name='jituan_lunbo_manage'),