340 lines
12 KiB
Python
340 lines
12 KiB
Python
"""组队 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
|
||
],
|
||
},
|
||
})
|