Files
Django/jituan/views.py
T

770 lines
32 KiB
Python

"""集团平台 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, is_kefu_backend_account, can_manage_admin_assignments
from jituan.services.kefu_menu import build_menu_access_payload
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)
# 登录请求尚未带 JWT,需用本次登录用户计算菜单
request.user = target_user
menu_access, _menu_err = build_menu_access_payload(request, phone)
return Response({
'code': 0,
'msg': '登录成功',
'data': {
'token': token,
'nicheng': nicheng,
'yonghuid': target_user.UserUID,
'club_context': club_context,
'menu_access': menu_access if menu_access else {
'visible_page_ids': [],
'visible_paths': [],
'permission_codes': [],
'role_names': [],
'yonghuid': target_user.UserUID or '',
'can_switch_club': False,
'is_group_admin': False,
'menu_ready': False,
},
},
})
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 not is_kefu_backend_account(user):
return Response({'code': 403, 'msg': '非后台账号', 'data': None}, status=403)
return Response({
'code': 0,
'msg': 'ok',
'data': build_admin_club_context(user),
})
class ClubKefuMenuAccessView(APIView):
"""GET /jituan/auth/menu-access — 已登录客服可见侧栏菜单。"""
permission_classes = [IsAuthenticated]
def get(self, request):
user = request.user
if not is_kefu_backend_account(user):
return Response({'code': 403, 'msg': '非后台账号', 'data': None}, status=403)
payload, err = build_menu_access_payload(request)
if err is not None:
return err
return Response({'code': 0, 'msg': 'ok', 'data': payload})
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 not can_manage_admin_assignments(request.user):
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)