diff --git a/a_long_dianjing/settings.py b/a_long_dianjing/settings.py index db5a9ac..4f3aeab 100644 --- a/a_long_dianjing/settings.py +++ b/a_long_dianjing/settings.py @@ -371,7 +371,8 @@ CELERY_TASK_QUEUES = { # 订单超时自动结算(Celery process_expired_order / check_order_expire_task)总开关:False=彻底关闭 ORDER_AUTO_SETTLEMENT_ENABLED = False # 打手抢单大厅假单展示:False=仅返回真实订单(后台假单管理仍可用;改 True 可恢复假单池) -FAKE_GRAB_ORDER_POOL_ENABLED = False +# 假单功能总闸(紧急关闭用)。实际是否对打手生效由各俱乐部 config_json.fake_grab_order_enabled 控制。 +FAKE_GRAB_ORDER_POOL_ENABLED = True ORDER_EXPIRE_SECONDS = 48 * 60 * 60 # 48小时 = 172800秒(开关关闭时不生效) DAILY_TASK_HOUR = 0 DAILY_TASK_MINUTE = 30 diff --git a/jituan/services/fake_grab_order_admin.py b/jituan/services/fake_grab_order_admin.py index 153f8ce..b98bd4b 100644 --- a/jituan/services/fake_grab_order_admin.py +++ b/jituan/services/fake_grab_order_admin.py @@ -113,6 +113,7 @@ def build_fake_order_list_payload(request, leixing_id=None, page=1, page_size=20 'scope': resolve_club_scope(request), 'scope_error': CLUB_READ_SCOPE_MSG, 'leixing_options': build_leixing_options(), + 'fake_grab_enabled': False, **list_response_meta(request), } @@ -129,6 +130,8 @@ def build_fake_order_list_payload(request, leixing_id=None, page=1, page_size=20 rows = list(qs[offset:offset + page_size]) names = _leixing_name_map() + from jituan.services.fake_grab_switch import is_club_fake_grab_enabled + return { 'list': [_row_to_dict(r, names) for r in rows], 'total': total, @@ -137,10 +140,35 @@ def build_fake_order_list_payload(request, leixing_id=None, page=1, page_size=20 'club_id': club_id, 'scope': resolve_club_scope(request), 'leixing_options': build_leixing_options(), + 'fake_grab_enabled': is_club_fake_grab_enabled(club_id), **list_response_meta(request), } +def set_fake_grab_enabled(request, enabled): + """开启/关闭本俱乐部打手侧假单功能。""" + if club_write_blocked(request): + return None, CLUB_WRITE_SCOPE_MSG + + from jituan.models import Club + from jituan.services.fake_grab_switch import ( + is_club_fake_grab_enabled, + set_club_fake_grab_enabled, + ) + + club_id = club_id_for_write(request) + club = Club.query.filter(club_id=club_id).first() + if not club: + return None, '俱乐部不存在' + + set_club_fake_grab_enabled(club, bool(enabled)) + club.save(update_fields=['config_json']) + return { + 'club_id': club_id, + 'fake_grab_enabled': is_club_fake_grab_enabled(club_id), + }, None + + def _parse_decimal(value, field_name='金额'): try: val = Decimal(str(value)) diff --git a/jituan/services/fake_grab_switch.py b/jituan/services/fake_grab_switch.py new file mode 100644 index 0000000..2dd2949 --- /dev/null +++ b/jituan/services/fake_grab_switch.py @@ -0,0 +1,36 @@ +"""俱乐部级假单功能开关(config_json.fake_grab_order_enabled)。""" +import logging + +logger = logging.getLogger(__name__) + +CONFIG_KEY = 'fake_grab_order_enabled' + + +def is_club_fake_grab_enabled(club_id: str) -> bool: + """某俱乐部是否对打手侧开启假单池。默认关闭。""" + cid = (club_id or '').strip() + if not cid: + return False + try: + from django.conf import settings + if not getattr(settings, 'FAKE_GRAB_ORDER_POOL_ENABLED', True): + return False + except Exception: + pass + try: + from jituan.models import Club + club = Club.query.filter(club_id=cid).first() + if not club: + return False + cfg = club.config_json or {} + return bool(cfg.get(CONFIG_KEY)) + except Exception: + logger.exception('read fake_grab_order_enabled failed club=%s', cid) + return False + + +def set_club_fake_grab_enabled(club, enabled: bool) -> None: + """写入 club.config_json,调用方负责 save。""" + cfg = dict(getattr(club, 'config_json', None) or {}) + cfg[CONFIG_KEY] = bool(enabled) + club.config_json = cfg diff --git a/jituan/views_fake_grab_order.py b/jituan/views_fake_grab_order.py index 4430d7a..de257ee 100644 --- a/jituan/views_fake_grab_order.py +++ b/jituan/views_fake_grab_order.py @@ -59,6 +59,7 @@ class FakeGrabOrderManageView(APIView): from jituan.services.fake_grab_order_admin import ( create_fake_order, delete_fake_order, + set_fake_grab_enabled, update_fake_order, ) @@ -82,6 +83,26 @@ class FakeGrabOrderManageView(APIView): return Response({'code': 400, 'msg': err_msg}) return Response({'code': 0, 'msg': '已删除', 'data': data}) + if action in ('set_enabled', 'enable', 'disable'): + if action == 'enable': + enabled = True + elif action == 'disable': + enabled = False + else: + raw = request.data.get('enabled', request.data.get('fake_grab_enabled')) + if isinstance(raw, str): + enabled = raw.strip().lower() in ('1', 'true', 'yes', 'on') + else: + enabled = bool(raw) + data, err_msg = set_fake_grab_enabled(request, enabled) + if err_msg: + return Response({'code': 400, 'msg': err_msg}) + return Response({ + 'code': 0, + 'msg': '假单功能已开启' if data.get('fake_grab_enabled') else '假单功能已关闭', + 'data': data, + }) + return Response({'code': 400, 'msg': '无效的 action'}) diff --git a/orders/services/fake_order_pool.py b/orders/services/fake_order_pool.py index 267088b..0ae0545 100644 --- a/orders/services/fake_order_pool.py +++ b/orders/services/fake_order_pool.py @@ -1,7 +1,6 @@ -"""抢单大厅假单:无资格/封禁打手仅展示假数据。""" +"""抢单大厅假单:按俱乐部开关 + 按商品类型会员资格展示假数据。""" import random from datetime import datetime, timedelta -from decimal import Decimal from django.core.exceptions import ObjectDoesNotExist from django.db import transaction @@ -11,19 +10,41 @@ from rest_framework.response import Response from backend.utils import fmt_datetime, pick_leixing_id from jituan.services.club_context import resolve_effective_club_id -from jituan.services.dashou_exam import user_has_active_member +from jituan.services.fake_grab_switch import is_club_fake_grab_enabled +from jituan.services.huiyuan_bundle import normalize_huiyuan_id, user_has_huiyuan_access from orders.models import FakeGrabOrder, FakeGrabOrderShuffleState DEFAULT_DISPATCHER_AVATAR = 'beijing/morentouxiang.jpg' SHUFFLE_STATE_PK = 1 -def dashou_should_use_fake_pool(user): +def _leixing_required_huiyuan_id(leixing_id): + """商品类型绑定的会员 ID;无绑定则视为不要求会员。""" + if leixing_id is None: + return '' + try: + from products.models import ShangpinLeixing + row = ShangpinLeixing.query.filter(id=int(leixing_id)).values('huiyuan_id').first() + if not row: + return '' + return normalize_huiyuan_id(row.get('huiyuan_id')) + except Exception: + return '' + + +def dashou_should_use_fake_pool(user, club_id=None, leixing_id=None): """ - 打手是否只能看假单: - - 账号封禁(zhanghaozhuangtai != 1) - - 或未开通有效会员 且 押金不大于 5 + 当前 Tab(商品类型)是否只展示假单: + - 俱乐部未开启假单功能 → 否(看真实单) + - 账号封禁 → 是 + - 该类型要求的会员:已充(含总会员覆盖子类型)→ 否 + - 未充对应会员 → 是 + 不同类型互不影响;假单在独立表,抢不了。 """ + cid = (club_id or '').strip() + if not cid or not is_club_fake_grab_enabled(cid): + return False + try: dashou = user.DashouProfile except ObjectDoesNotExist: @@ -32,11 +53,16 @@ def dashou_should_use_fake_pool(user): if dashou.zhanghaozhuangtai != 1: return True - yajin = Decimal(str(dashou.yajin or 0)) - if yajin > Decimal('5'): + if leixing_id is None: + # 未指定类型时不整池切假单,走真实单列表 return False - if user_has_active_member(user.UserUID): + required = _leixing_required_huiyuan_id(leixing_id) + if not required: + # 类型未绑会员 → 不推假单 + return False + + if user_has_huiyuan_access(user.UserUID, required, club_id=cid): return False return True diff --git a/orders/views/dashou.py b/orders/views/dashou.py index e6d9b5b..2a63c87 100644 --- a/orders/views/dashou.py +++ b/orders/views/dashou.py @@ -97,14 +97,10 @@ class DashouDingdanHuoquView(APIView): def post(self, request): try: - from django.conf import settings from orders.services.fake_order_pool import ( dashou_should_use_fake_pool, build_fake_order_pool_response, ) - if getattr(settings, 'FAKE_GRAB_ORDER_POOL_ENABLED', False): - if dashou_should_use_fake_pool(request.user): - return build_fake_order_pool_response(request) # 1. 获取请求参数 data = request.data @@ -119,6 +115,11 @@ class DashouDingdanHuoquView(APIView): resolve_grab_pool_member_display, ) club_id = resolve_effective_club_id(request, getattr(request, 'user', None)) + + # 假单:按俱乐部开关 + 当前商品类型是否具备对应会员(含总会员覆盖) + if dashou_should_use_fake_pool(request.user, club_id=club_id, leixing_id=leixing_id): + return build_fake_order_pool_response(request) + yonghuid = request.user.UserUID _has_member, user_clumber = refresh_grab_pool_membership_state( yonghuid, club_id=club_id,