Files
Django/jituan/views.py

742 lines
30 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""集团平台 API 视图。"""
import logging
from django.utils import timezone
from rest_framework import status
from rest_framework.parsers import JSONParser
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.throttling import AnonRateThrottle
from rest_framework_simplejwt.tokens import RefreshToken
from jituan.constants import SUPER_ADMIN_PHONES, ADMIN_ROLE_LABELS, CLUB_ID_DEFAULT, DATA_SCOPE_ALL, DATA_SCOPE_SINGLE
from jituan.models import Club, AdminAssignment
from jituan.services.admin_context import build_admin_club_context, can_manage_admin_assignments
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_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,
build_huiyuan_bankuai_payload,
build_huiyuan_stats_payload,
build_order_finance_payload,
build_order_types_payload,
)
from backend.utils import verify_kefu_permission
from users.business_models import User
logger = logging.getLogger(__name__)
def _client_ip(request):
xff = request.META.get('HTTP_X_FORWARDED_FOR')
if xff:
return xff.split(',')[0].strip()
return request.META.get('REMOTE_ADDR', '')
class ClubWechatLoginView(APIView):
"""
多俱乐部微信小程序登录(新接口,旧 /yonghu/wechatlogin 不变)。
POST /jituan/auth/wechat-login
body: { code, club_id?, app_id? }
"""
throttle_classes = [AnonRateThrottle]
permission_classes = [AllowAny]
def post(self, request):
code = (request.data.get('code') or '').strip()
club_id = (request.data.get('club_id') or '').strip() or None
app_id = (request.data.get('app_id') or '').strip() or None
if not code:
return Response({'code': 1, 'msg': '微信授权码不能为空', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
try:
data, err = login_or_register_by_club(
code, club_id=club_id, app_id=app_id, client_ip=_client_ip(request),
)
if err:
return Response({'code': 2, 'msg': f'微信登录失败: {err}', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
return Response({'code': 0, 'msg': '登录成功', 'data': data})
except Exception as e:
logger.exception('ClubWechatLoginView 异常')
return Response({'code': 99, 'msg': '系统繁忙,请稍后重试', 'data': None},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class ClubKefuLoginView(APIView):
"""
多俱乐部客服/集团后台登录(新接口,旧 /yonghu/kefujinru 不变)。
POST /jituan/auth/kefu-login
body: { phone, password, erjimima }
"""
throttle_classes = [AnonRateThrottle]
permission_classes = [AllowAny]
def post(self, request):
phone = (request.data.get('phone') or '').strip()
password = request.data.get('password') or ''
erjimima = request.data.get('erjimima') or ''
if not phone or not password:
return Response({'code': 1, 'msg': '请填写手机号和密码', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
user_mains = list(
User.query.filter(Phone=phone, KefuProfile__isnull=False).select_related('KefuProfile')
)
if not user_mains:
admin_candidates = User.query.filter(Phone=phone)
for admin_user in admin_candidates:
if admin_user.IsSuperuser or admin_user.UserType == 'admin' or admin_user.Phone in SUPER_ADMIN_PHONES:
user_mains = [admin_user]
break
if not user_mains:
return Response({'code': 1, 'msg': '用户不存在', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
target_user = None
target_kefu = None
password_matched = False
for user in user_mains:
if not user.CheckPassword(password):
continue
password_matched = True
is_admin = user.IsSuperuser or user.UserType == 'admin' or user.Phone in SUPER_ADMIN_PHONES
if is_admin:
target_user = user
target_kefu = None
break
kefu = user.KefuProfile
stored_erji = kefu.erjimima or ''
if stored_erji.startswith(('$2b$', '$2a$')) and len(stored_erji) == 60:
import bcrypt as _bcrypt
if not _bcrypt.checkpw(erjimima.encode('utf-8'), stored_erji.encode('utf-8')):
continue
elif stored_erji != erjimima:
continue
if kefu.zhuangtai != 1:
if target_user is None:
target_user = user
target_kefu = kefu
continue
target_user = user
target_kefu = kefu
break
else:
if target_user and target_kefu:
return Response({'code': 4, 'msg': '账号已被封禁', 'data': None},
status=status.HTTP_403_FORBIDDEN)
if password_matched:
return Response({'code': 1, 'msg': '二级密码错误', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
return Response({'code': 1, 'msg': '密码错误', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
target_user.IP = _client_ip(request)
target_user.UserLastLoginDate = timezone.now()
target_user.save(update_fields=['IP', 'UserLastLoginDate'])
refresh = RefreshToken.for_user(target_user)
token = str(refresh.access_token)
nicheng = target_kefu.nicheng if target_kefu else (target_user.UserName or target_user.Phone)
club_context = build_admin_club_context(target_user)
return Response({
'code': 0,
'msg': '登录成功',
'data': {
'token': token,
'nicheng': nicheng,
'yonghuid': target_user.UserUID,
'club_context': club_context,
},
})
class ClubListView(APIView):
"""GET /jituan/club/list — 启用中的俱乐部列表(登录前后均可读)。"""
permission_classes = [AllowAny]
def get(self, request):
clubs = Club.query.filter(status=1).order_by('sort_order', 'club_id')
data = [
{
'club_id': c.club_id,
'name': c.name,
'wx_appid': c.wx_appid,
'h5_domain': c.h5_domain,
}
for c in clubs
]
return Response({'code': 0, 'msg': 'ok', 'data': data})
class ClubMeContextView(APIView):
"""GET /jituan/auth/me-context — 已登录客服刷新俱乐部上下文。"""
permission_classes = [IsAuthenticated]
def get(self, request):
user = request.user
if user.UserType not in ('kefu', 'admin') and not user.IsSuperuser:
return Response({'code': 403, 'msg': '非后台账号', 'data': None}, status=403)
return Response({
'code': 0,
'msg': 'ok',
'data': build_admin_club_context(user),
})
class ClubAdminAssignmentListView(APIView):
"""POST /jituan/houtai/admin-assignments — 数据范围任职(非 gvsdsdk 功能权限)。"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def _is_super(self, user):
return (
bool(user.IsSuperuser)
or user.UserType == 'admin'
or getattr(user, 'Phone', '') in SUPER_ADMIN_PHONES
)
def _list_rows(self, assignments):
club_name_map = {c.club_id: c.name for c in Club.query.filter(status=1)}
uids = list({a.yonghuid for a in assignments})
user_map = {}
if uids:
for u in User.query.filter(UserUID__in=uids).only('UserUID', 'Phone', 'UserName'):
user_map[u.UserUID] = u.Phone or u.UserName or u.UserUID
rows = []
for a in assignments:
club_label = '集团(全部子公司)'
if a.club_id:
club_label = club_name_map.get(a.club_id, a.club_id)
rows.append({
'id': a.id,
'yonghuid': a.yonghuid,
'phone': user_map.get(a.yonghuid, ''),
'club_id': a.club_id,
'club_label': club_label,
'role_code': a.role_code,
'role_name': ADMIN_ROLE_LABELS.get(a.role_code, a.role_code),
'data_scope': a.data_scope,
'is_primary': a.is_primary,
})
return rows
def post(self, request):
try:
username = (request.data.get('phone') or request.data.get('username') or '').strip()
if not username:
return Response({'code': 401, 'msg': '缺少账号'}, status=401)
kefu_obj, permissions = verify_kefu_permission(request, username)
if kefu_obj is None:
return permissions
user = request.user
is_super = self._is_super(user)
action = (request.data.get('action') or 'list').strip()
if action in ('create', 'update', 'delete'):
if not can_manage_admin_assignments(user, permissions):
return Response({'code': 403, 'msg': '无权限维护任职数据(需超管或集团超管任职)'}, status=403)
if action == 'create':
yonghuid = (request.data.get('yonghuid') or '').strip()
phone = (request.data.get('target_phone') or '').strip()
if not yonghuid and phone:
target = User.query.filter(Phone=phone).first()
if not target:
return Response({'code': 404, 'msg': f'未找到手机号 {phone}'})
yonghuid = target.UserUID
if not yonghuid:
return Response({'code': 400, 'msg': '需要 yonghuid 或 target_phone'})
club_id = request.data.get('club_id')
if club_id is not None and str(club_id).strip() == '':
club_id = None
elif club_id is not None:
club_id = str(club_id).strip()
role_code = (request.data.get('role_code') or 'CLUB_ADMIN').strip()
data_scope = (request.data.get('data_scope') or DATA_SCOPE_SINGLE).strip()
if club_id is None:
data_scope = DATA_SCOPE_ALL
is_primary = bool(request.data.get('is_primary', False))
_, 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,
)
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'})
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':
assignment_id = request.data.get('id')
if not assignment_id:
return Response({'code': 400, 'msg': '缺少任职 id'})
row = AdminAssignment.query.filter(id=assignment_id, status=1).first()
if not row:
return Response({'code': 404, 'msg': '任职记录不存在'})
if 'role_code' in request.data:
row.role_code = (request.data.get('role_code') or '').strip() or row.role_code
if 'data_scope' in request.data:
row.data_scope = request.data.get('data_scope') or row.data_scope
if 'is_primary' in request.data:
row.is_primary = bool(request.data.get('is_primary'))
if 'club_id' in request.data:
cid = request.data.get('club_id')
row.club_id = None if cid is None or str(cid).strip() == '' else str(cid).strip()
if row.club_id is None:
row.data_scope = DATA_SCOPE_ALL
row.granted_by = user.UserUID
row.save()
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')
return Response({
'code': 0,
'msg': 'ok',
'data': {
'list': self._list_rows(qs.to_list()),
'can_manage': can_manage_admin_assignments(user, permissions),
'perm_note': (
'【功能权限】在「角色管理」里给账号绑 gvsdsdk 角色perm_code 如 caiwu、002ab、000001'
'【数据范围】在本页维护 admin_assignment能看哪个俱乐部/集团汇总)。'
'两套独立:集团高管需在本页配「集团全部」任职 + 角色管理里勾功能菜单。'
),
'current_context': build_admin_club_context(user),
},
})
except Exception:
logger.exception('ClubAdminAssignmentListView 异常')
return Response({'code': 99, 'msg': '任职数据操作失败,请确认已执行 migrate jituan'}, status=500)
class ClubManageView(APIView):
"""POST /jituan/houtai/club-manage — 俱乐部主数据与密钥配置(超级管理员)。"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
_EDITABLE_FIELDS = (
'name', 'status', 'wx_appid', 'wx_secret', 'mch_id', 'pay_app_id', 'api_v3_key',
'cert_serial_no', 'private_key_path', 'platform_cert_dir',
'official_appid', 'official_secret', 'official_token', 'encoding_aes_key',
'template_id', 'template_max_per_minute', 'h5_domain', 'oss_prefix',
'goeasy_appkey', 'goeasy_secret', 'sort_order',
)
def _serialize(self, club, full=False):
base = {
'club_id': club.club_id,
'name': club.name,
'status': club.status,
'wx_appid': club.wx_appid,
'h5_domain': club.h5_domain,
'oss_prefix': club.oss_prefix,
'mch_id': club.mch_id,
'pay_app_id': club.pay_app_id,
'goeasy_appkey': club.goeasy_appkey,
'template_id': club.template_id,
'sort_order': club.sort_order,
}
if full:
base.update({
'wx_secret': club.wx_secret,
'api_v3_key': club.api_v3_key,
'cert_serial_no': club.cert_serial_no,
'private_key_path': club.private_key_path,
'platform_cert_dir': club.platform_cert_dir,
'official_appid': club.official_appid,
'official_secret': club.official_secret,
'official_token': club.official_token,
'encoding_aes_key': club.encoding_aes_key,
'goeasy_secret': club.goeasy_secret,
'template_max_per_minute': club.template_max_per_minute,
})
return base
def post(self, request):
from jituan.constants import CLUB_ID_DEFAULT
try:
username = (request.data.get('phone') or request.data.get('username') or '').strip()
if not username:
return Response({'code': 401, 'msg': '缺少账号'}, status=401)
kefu_obj, permissions = verify_kefu_permission(request, username)
if kefu_obj is None:
return permissions
if '000001' not in permissions:
return Response({'code': 403, 'msg': '仅超级管理员可管理俱乐部配置'}, status=403)
action = (request.data.get('action') or 'get').strip()
club_id = (request.data.get('club_id') or '').strip() or CLUB_ID_DEFAULT
if action == 'list':
clubs = Club.query.order_by('sort_order', 'club_id')
return Response({
'code': 0,
'data': [self._serialize(c, full=False) for c in clubs],
})
club = Club.query.filter(club_id=club_id).first()
if not club and action != 'create':
return Response({'code': 1, 'msg': f'俱乐部 {club_id} 不存在'})
if action == 'get':
return Response({'code': 0, 'data': self._serialize(club, full=True)})
if action == 'update':
update_fields = []
for field in self._EDITABLE_FIELDS:
if field in request.data:
setattr(club, field, request.data[field])
update_fields.append(field)
if update_fields:
club.save(update_fields=update_fields)
return Response({
'code': 0,
'msg': '保存成功',
'data': self._serialize(club, full=True),
})
if action == 'create':
from jituan.services.club_bootstrap import bootstrap_club_from_template
new_club_id = (request.data.get('new_club_id') or request.data.get('club_id') or '').strip()
name = (request.data.get('name') or '').strip()
if not new_club_id or not name:
return Response({'code': 400, 'msg': '新建俱乐部需要 new_club_id 与 name'})
try:
club = bootstrap_club_from_template(
new_club_id,
name,
wx_appid=(request.data.get('wx_appid') or '').strip(),
template_club_id=(request.data.get('from_club') or CLUB_ID_DEFAULT).strip(),
)
except ValueError as exc:
return Response({'code': 400, 'msg': str(exc)})
return Response({
'code': 0,
'msg': '俱乐部创建成功',
'data': self._serialize(club, full=True),
})
return Response({'code': 400, 'msg': '无效 action支持 list/get/update/create'})
except Exception:
logger.exception('ClubManageView 异常')
return Response({'code': 99, 'msg': '俱乐部配置操作失败'}, status=500)
class ClubCaiwuView(APIView):
"""
俱乐部维度财务(新接口)。
旧 POST /houtai/caiwu 完全不变kefu 切换俱乐部后应调本接口。
Header: X-Club-Id, 集团汇总 X-Club-Scope: all
"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('phone', None)
kefu_obj, permissions = verify_kefu_permission(request, username)
if kefu_obj is None:
return permissions
if 'caiwu' not in permissions:
return Response({'code': 403, 'msg': '无财务查看权限'}, status=403)
try:
data = build_caiwu_payload(request)
return Response({'code': 0, 'msg': 'ok', 'data': data})
except Exception:
logger.exception('ClubCaiwuView 异常')
return Response({'code': 99, 'msg': '统计失败', 'data': None}, status=500)
class ClubSzxxView(APIView):
"""
俱乐部维度每日收支详细统计(新接口)。
旧 POST /houtai/szxx 不变;参数与旧接口一致。
"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('phone', None)
kefu_obj, permissions = verify_kefu_permission(request, username)
if kefu_obj is None:
return permissions
if 'caiwu' not in permissions:
return Response({'code': 403, 'msg': '无财务查看权限'}, status=403)
granularity = request.data.get('granularity', 'day')
year = request.data.get('year')
month = request.data.get('month')
summary_date = request.data.get('summary_date')
try:
if year is not None:
year = int(year)
if month is not None:
month = int(month)
except (ValueError, TypeError):
return Response({'code': 1, 'msg': '年份或月份格式错误'}, status=400)
try:
data, err = build_szxx_payload(request, granularity, year, month, summary_date)
if err:
return Response({'code': 1, 'msg': err}, status=400)
return Response({'code': 0, 'msg': 'ok', 'data': data})
except Exception:
logger.exception('ClubSzxxView 异常')
return Response({'code': 99, 'msg': '统计失败'}, status=500)
class ClubAuditLogListView(APIView):
"""
后台审计日志admin_audit_log
POST /jituan/houtai/audit-log
权限000001 或 czrz666与操作日志一致
"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = (request.data.get('username') or request.data.get('phone') or '').strip()
if not username:
return Response({'code': 401, 'msg': '缺少username'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
if '000001' not in permissions and 'czrz666' not in permissions:
return Response({'code': 403, 'msg': '无权限查看审计日志'})
try:
page = int(request.data.get('page', 1))
except (ValueError, TypeError):
page = 1
try:
page_size = int(request.data.get('page_size', 20))
except (ValueError, TypeError):
page_size = 20
data = list_admin_audit_logs(
request,
page=page,
page_size=page_size,
operator_yonghuid=request.data.get('operator_yonghuid'),
target_yonghuid=request.data.get('target_yonghuid'),
action=request.data.get('action'),
keyword=request.data.get('keyword'),
)
return Response({'code': 0, 'msg': 'ok', 'data': data})
def _parse_year_month(data):
year = data.get('year')
month = data.get('month')
try:
if year is not None:
year = int(year)
if month is not None:
month = int(month)
except (ValueError, TypeError):
return None, None, Response({'code': 1, 'msg': '年份或月份格式错误'}, status=400)
return year, month, None
class ClubOrderTypesView(APIView):
"""POST /jituan/houtai/cwddhqlx — 订单财务商品类型筛选。"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('phone', None)
kefu_obj, permissions = verify_kefu_permission(request, username)
if kefu_obj is None:
return permissions
if 'dingdancaiwu' not in permissions:
return Response({'code': 403, 'msg': '无订单财务查看权限'}, status=403)
return Response({'code': 0, 'msg': 'ok', 'data': build_order_types_payload()})
class ClubOrderFinanceView(APIView):
"""POST /jituan/houtai/cwhqjtddsj — 订单财务详细统计(按俱乐部)。"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('phone', None)
kefu_obj, permissions = verify_kefu_permission(request, username)
if kefu_obj is None:
return permissions
if 'dingdancaiwu' not in permissions:
return Response({'code': 403, 'msg': '无订单财务查看权限'}, status=403)
year, month, err_resp = _parse_year_month(request.data)
if err_resp:
return err_resp
try:
data, err = build_order_finance_payload(
request,
request.data.get('granularity', 'day'),
year,
month,
request.data.get('type_id', 0),
request.data.get('order_source', 0),
request.data.get('summary_date'),
)
if err:
return Response({'code': 1, 'msg': err}, status=400)
return Response({'code': 0, 'msg': 'ok', 'data': data})
except Exception:
logger.exception('ClubOrderFinanceView 异常')
return Response({'code': 99, 'msg': '统计失败'}, status=500)
class ClubChongzhiFinanceView(APIView):
"""POST /jituan/houtai/cwqtczhq — 其他充值/罚款财务统计(按俱乐部)。"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('phone', None)
kefu_obj, permissions = verify_kefu_permission(request, username)
if kefu_obj is None:
return permissions
if 'qtczcaiwu' not in permissions:
return Response({'code': 403, 'msg': '无其他充值财务查看权限'}, status=403)
year, month, err_resp = _parse_year_month(request.data)
if err_resp:
return err_resp
try:
data, err = build_chongzhi_finance_payload(
request,
request.data.get('granularity', 'day'),
year,
month,
request.data.get('types', [2, 3, 4, 5, 6]),
request.data.get('summary_date'),
)
if err:
return Response({'code': 1, 'msg': err}, status=400)
return Response({'code': 0, 'msg': 'ok', 'data': data})
except Exception:
logger.exception('ClubChongzhiFinanceView 异常')
return Response({'code': 99, 'msg': '统计失败'}, status=500)
class ClubHuiyuanBankuaiView(APIView):
"""POST /jituan/houtai/cwhybkhq — 板块及会员列表(附带俱乐部售价)。"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('phone', None)
kefu_obj, permissions = verify_kefu_permission(request, username)
if kefu_obj is None:
return permissions
if 'huiyuancaiwu' not in permissions:
return Response({'code': 403, 'msg': '无会员财务查看权限'}, status=403)
payload = build_huiyuan_bankuai_payload(request)
return Response({
'code': 0,
'msg': 'ok',
'data': payload['bankuai_list'],
'club_id': payload['club_id'],
'scope': payload['scope'],
})
class ClubHuiyuanStatsView(APIView):
"""POST /jituan/houtai/hybkjtsj — 会员财务详细统计(按俱乐部)。"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('phone', None)
kefu_obj, permissions = verify_kefu_permission(request, username)
if kefu_obj is None:
return permissions
if 'huiyuancaiwu' not in permissions:
return Response({'code': 403, 'msg': '无会员财务查看权限'}, status=403)
year, month, err_resp = _parse_year_month(request.data)
if err_resp:
return err_resp
try:
data, err = build_huiyuan_stats_payload(
request,
request.data.get('huiyuan_id'),
request.data.get('granularity', 'day'),
year,
month,
request.data.get('include_guanshi', False),
request.data.get('include_zuzhang', False),
request.data.get('summary_date'),
)
if err:
return Response({'code': 1, 'msg': err}, status=400)
return Response({'code': 0, 'msg': 'ok', 'data': data})
except Exception:
logger.exception('ClubHuiyuanStatsView 异常')
return Response({'code': 99, 'msg': '统计失败'}, status=500)
class ClubUserSummaryView(APIView):
"""
用户管理汇总(打手/管事/商家/组长数量)。
集团视图 X-Club-Scope: all 时额外返回 by_club 各公司拆分。
POST /jituan/houtai/user-summary
"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = (request.data.get('phone') or request.data.get('username') or '').strip()
if not username:
return Response({'code': 401, 'msg': '缺少账号'}, status=401)
kefu_obj, permissions = verify_kefu_permission(request, username)
if kefu_obj is None:
return permissions
try:
data = build_user_summary(request)
return Response({'code': 0, 'msg': 'ok', 'data': data})
except Exception:
logger.exception('ClubUserSummaryView 异常')
return Response({'code': 99, 'msg': '统计失败'}, status=500)
class ClubDashouRegisterView(APIView):
"""
打手邀请码注册(带俱乐部校验)。
POST /jituan/auth/dashou-register body: { inviteCode }
与 /yonghu/dashouzhuce 逻辑一致,推荐新小程序使用。
"""
permission_classes = [IsAuthenticated]
def post(self, request):
from users.views import DashouZhuceView
return DashouZhuceView().post(request)