"""组队 API:/zudui/*""" from __future__ import annotations import logging from rest_framework.parsers import JSONParser from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from jituan.services.club_context import resolve_club_id_from_request from orders.models import Order, TeamRecruit, TeamMember from orders.services import team_recruit as team_svc logger = logging.getLogger(__name__) def _club(request): return (resolve_club_id_from_request(request) or '').strip() def _uid(request): return getattr(request.user, 'UserUID', None) or '' def _require_club(request): club = _club(request) if not club: return None, Response({'code': 400, 'msg': '缺少俱乐部'}) return club, None def _assert_recruit_same_club(rec, club): """组队永不走跨店互通:帖/订单/当前俱乐部必须一致。""" if not rec: return Response({'code': 404, 'msg': '组队不存在'}) if (rec.club_id or '') != club: return Response({'code': 403, 'msg': '仅本俱乐部可操作组队'}) order = Order.query.filter(OrderID=rec.order_id).first() if not order: return Response({'code': 404, 'msg': '订单不存在'}) if (order.ClubID or '') != club: return Response({'code': 403, 'msg': '订单不属于本俱乐部'}) return None class ZuduiKaqiView(APIView): """开启组队 POST /zudui/kaqi(仅本俱乐部订单,与跨店抢单无关)""" permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): uid = _uid(request) club, err = _require_club(request) if err: return err oid = str(request.data.get('dingdan_id') or request.data.get('order_id') or '').strip() if not oid: return Response({'code': 400, 'msg': '缺少订单ID'}) order = Order.query.filter(OrderID=oid).first() if not order: return Response({'code': 404, 'msg': '订单不存在'}) if (order.ClubID or '') != club: return Response({'code': 403, 'msg': '仅本俱乐部订单可开启组队'}) try: rec = team_svc.open_recruit( order=order, leader_uid=uid, need_count=int(request.data.get('need_count') or 1), share_mode=request.data.get('share_mode') or 'fixed', per_person_amount=request.data.get('per_person_amount'), remark=request.data.get('remark') or '', leader_game_id=request.data.get('leader_game_id') or request.data.get('game_id') or '', min_deal_rate=request.data.get('min_deal_rate'), max_fine_rate=request.data.get('max_fine_rate'), ) return Response({ 'code': 0, 'msg': '已开启组队', 'data': team_svc.serialize_recruit_public(rec, order, include_game_ids=True), }) except ValueError as e: return Response({'code': 400, 'msg': str(e)}) except Exception as e: logger.exception('开启组队失败') return Response({'code': 500, 'msg': '服务器错误'}) class ZuduiGaiyaoqiuView(APIView): permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): uid = _uid(request) club, err = _require_club(request) if err: return err rid = request.data.get('recruit_id') try: rid = int(rid) except (TypeError, ValueError): return Response({'code': 400, 'msg': '缺少 recruit_id'}) rec = TeamRecruit.query.filter(id=rid).first() deny = _assert_recruit_same_club(rec, club) if deny: return deny try: kw = {'recruit_id': rid, 'leader_uid': uid} if 'need_count' in request.data: kw['need_count'] = request.data.get('need_count') if 'remark' in request.data: kw['remark'] = request.data.get('remark') if 'min_deal_rate' in request.data: kw['min_deal_rate'] = request.data.get('min_deal_rate') if 'max_fine_rate' in request.data: kw['max_fine_rate'] = request.data.get('max_fine_rate') rec = team_svc.update_recruit(**kw) order = Order.query.filter(OrderID=rec.order_id).first() return Response({ 'code': 0, 'msg': '已更新', 'data': team_svc.serialize_recruit_public(rec, order, include_game_ids=True), }) except ValueError as e: return Response({'code': 400, 'msg': str(e)}) class ZuduiGuantingView(APIView): permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): uid = _uid(request) club, err = _require_club(request) if err: return err rid = request.data.get('recruit_id') try: rid = int(rid) except (TypeError, ValueError): return Response({'code': 400, 'msg': '缺少 recruit_id'}) rec = TeamRecruit.query.filter(id=rid).first() deny = _assert_recruit_same_club(rec, club) if deny: return deny if rec.leader_uid != uid: return Response({'code': 403, 'msg': '仅队长可关停'}) try: rec = team_svc.lock_recruit(rid, actor_uid=uid, reason='manual') order = Order.query.filter(OrderID=rec.order_id).first() return Response({ 'code': 0, 'msg': '已关停招募', 'data': team_svc.serialize_recruit_public(rec, order, include_game_ids=True), }) except ValueError as e: return Response({'code': 400, 'msg': str(e)}) class ZuduiDatingView(APIView): """组队大厅:仅本俱乐部招募中(不读跨店互通)。""" permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): club, err = _require_club(request) if err: return err page = max(1, int(request.data.get('page') or 1)) page_size = min(50, max(1, int(request.data.get('page_size') or 10))) leixing = request.data.get('leixing_id') pingtai = request.data.get('fadanpingtai') qs = TeamRecruit.query.filter( club_id=club, status=TeamRecruit.STATUS_RECRUITING, ).order_by('-opened_at') # 商品类型必选过滤(与前端大厅一致) if leixing not in (None, ''): try: lid = int(leixing) except (TypeError, ValueError): return Response({'code': 400, 'msg': '商品类型无效'}) oq = Order.query.filter(ClubID=club, Status=2, ProductTypeID=lid) if pingtai not in (None, ''): try: oq = oq.filter(Platform=int(pingtai)) except (TypeError, ValueError): pass order_ids = list(oq.values_list('OrderID', flat=True)[:800]) qs = qs.filter(order_id__in=order_ids or ['__none__']) elif pingtai not in (None, ''): try: oq = Order.query.filter(ClubID=club, Status=2, Platform=int(pingtai)) order_ids = list(oq.values_list('OrderID', flat=True)[:800]) qs = qs.filter(order_id__in=order_ids or ['__none__']) except (TypeError, ValueError): pass total = qs.count() start = (page - 1) * page_size rows = list(qs[start:start + page_size]) oids = [r.order_id for r in rows] orders = {o.OrderID: o for o in Order.query.filter(OrderID__in=oids)} if oids else {} data_list = [ team_svc.serialize_recruit_public(r, orders.get(r.order_id), include_game_ids=False) for r in rows ] return Response({ 'code': 0, 'msg': 'ok', 'data': { 'list': data_list, 'total': total, 'page': page, 'page_size': page_size, 'has_more': start + len(rows) < total, }, }) class ZuduiXiangqingView(APIView): permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): uid = _uid(request) club, err = _require_club(request) if err: return err rid = request.data.get('recruit_id') oid = str(request.data.get('dingdan_id') or request.data.get('order_id') or '').strip() rec = None if rid: try: rec = TeamRecruit.query.filter(id=int(rid)).first() except (TypeError, ValueError): pass if not rec and oid: rec = TeamRecruit.query.filter(order_id=oid, club_id=club).order_by('-id').first() if not rec: return Response({'code': 404, 'msg': '组队不存在'}) deny = _assert_recruit_same_club(rec, club) if deny: return deny is_member = TeamMember.query.filter( recruit_id=rec.id, user_uid=uid, join_status=TeamMember.JOIN_ACTIVE, ).exists() include_game = is_member or rec.leader_uid == uid order = Order.query.filter(OrderID=rec.order_id).first() return Response({ 'code': 0, 'msg': 'ok', 'data': team_svc.serialize_recruit_public(rec, order, include_game_ids=include_game), }) class ZuduiRuiduiView(APIView): permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): uid = _uid(request) club, err = _require_club(request) if err: return err try: rid = int(request.data.get('recruit_id')) except (TypeError, ValueError): return Response({'code': 400, 'msg': '缺少 recruit_id'}) rec0 = TeamRecruit.query.filter(id=rid).first() deny = _assert_recruit_same_club(rec0, club) if deny: return deny try: rec = team_svc.join_recruit( recruit_id=rid, user_uid=uid, game_id=request.data.get('game_id') or '', request=request, require_club_id=club, ) order = Order.query.filter(OrderID=rec.order_id).first() return Response({ 'code': 0, 'msg': '加入成功', 'data': team_svc.serialize_recruit_public(rec, order, include_game_ids=True), }) except ValueError as e: return Response({'code': 400, 'msg': str(e)}) except Exception: logger.exception('加入组队失败') return Response({'code': 500, 'msg': '服务器错误'}) class ZuduiWodeView(APIView): permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): uid = _uid(request) club, err = _require_club(request) if err: return err as_leader = TeamRecruit.query.filter( club_id=club, leader_uid=uid, status__in=[TeamRecruit.STATUS_RECRUITING, TeamRecruit.STATUS_LOCKED], ).order_by('-opened_at')[:50] member_rids = list( TeamMember.query.filter( user_uid=uid, join_status=TeamMember.JOIN_ACTIVE, role=TeamMember.ROLE_MEMBER, ).values_list('recruit_id', flat=True)[:50] ) as_member_qs = TeamRecruit.query.filter( id__in=member_rids or [0], club_id=club, status__in=[TeamRecruit.STATUS_RECRUITING, TeamRecruit.STATUS_LOCKED], ) if member_rids else [] as_member = list(as_member_qs) if as_member_qs else [] as_leader_list = list(as_leader) oids = [r.order_id for r in as_leader_list + as_member] orders = {o.OrderID: o for o in Order.query.filter(OrderID__in=oids)} if oids else {} return Response({ 'code': 0, 'msg': 'ok', 'data': { 'as_leader': [ team_svc.serialize_recruit_public(r, orders.get(r.order_id), include_game_ids=True) for r in as_leader_list ], 'as_member': [ team_svc.serialize_recruit_public(r, orders.get(r.order_id), include_game_ids=True) for r in as_member ], }, })