"""抢单大厅假单:无资格/封禁打手仅展示假数据。""" import random from datetime import datetime, timedelta from decimal import Decimal from django.core.exceptions import ObjectDoesNotExist from django.db import transaction from django.utils import timezone from django.utils.dateparse import parse_datetime 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 orders.models import FakeGrabOrder, FakeGrabOrderShuffleState DEFAULT_DISPATCHER_AVATAR = 'beijing/morentouxiang.jpg' SHUFFLE_STATE_PK = 1 def dashou_should_use_fake_pool(user): """ 打手是否只能看假单: - 账号封禁(zhanghaozhuangtai != 1) - 或未开通有效会员 且 押金不大于 5 """ try: dashou = user.DashouProfile except ObjectDoesNotExist: return True if dashou.zhanghaozhuangtai != 1: return True yajin = Decimal(str(dashou.yajin or 0)) if yajin > Decimal('5'): return False if user_has_active_member(user.UserUID): return False return True def _scope_key(club_id, leixing_id): lid = leixing_id if leixing_id is not None else 'all' return f'{club_id}|{lid}' def _ensure_aware(dt): if dt is None: return None if timezone.is_naive(dt): return timezone.make_aware(dt, timezone.get_current_timezone()) return dt def _to_unix_ts(dt): """统一转为 Unix 时间戳,避免 naive/aware 直接相减报错。""" if dt is None: return None if timezone.is_aware(dt): return dt.timestamp() try: return timezone.make_aware(dt, timezone.get_current_timezone()).timestamp() except Exception: return dt.timestamp() def _elapsed_seconds(since_dt, until_dt): since_ts = _to_unix_ts(since_dt) until_ts = _to_unix_ts(until_dt) if since_ts is None or until_ts is None: return None return until_ts - since_ts def _parse_shuffle_time(value): if not value: return None if isinstance(value, str): dt = parse_datetime(value) return _ensure_aware(dt) return _ensure_aware(value) def _display_time_str_for_order(order_id, anchor_dt): """同一假单在打乱窗口内展示时间固定。""" seed = abs(hash(str(order_id))) % (2 ** 32) seconds_ago = 120 + (seed % 181) base_ts = _to_unix_ts(_ensure_aware(anchor_dt) or timezone.now()) if base_ts is None: base_ts = _to_unix_ts(timezone.now()) or 0 display_dt = datetime.fromtimestamp( base_ts - seconds_ago, tz=timezone.get_current_timezone(), ) return fmt_datetime(display_dt) def _load_fake_rows_in_saved_order(club_id, leixing_id): """ 读取/更新 id=1 打乱状态表: - 未满 IntervalSeconds:按库中 order_ids 顺序返回(全用户一致) - 已满:重新打乱并写回 order_ids - 新增假单:追加到顺序末尾并写回;已删假单:自动跳过 返回 (rows, anchor_dt) 供展示时间固定。 """ qs = FakeGrabOrder.query.filter(ClubID=club_id) if leixing_id is not None: qs = qs.filter(ProductTypeID=leixing_id) all_rows = list(qs) id_to_row = {r.OrderID: r for r in all_rows if r.OrderID} all_ids = list(id_to_row.keys()) scope = _scope_key(club_id, leixing_id) now = timezone.now() with transaction.atomic(): state, created = FakeGrabOrderShuffleState.objects.select_for_update().get_or_create( pk=SHUFFLE_STATE_PK, defaults={ 'LastShuffleTime': now - timedelta(days=1), 'IntervalSeconds': 120, 'OrderSequence': {}, }, ) interval = max(1, int(state.IntervalSeconds or 120)) seq_map = dict(state.OrderSequence or {}) entry = dict(seq_map.get(scope) or {}) stored_ids = list(entry.get('order_ids') or []) last_dt = _parse_shuffle_time(entry.get('last_shuffle_time')) if not last_dt and not created: last_dt = _ensure_aware(state.LastShuffleTime) elapsed = _elapsed_seconds(last_dt, now) need_reshuffle = ( not stored_ids or last_dt is None or elapsed is None or elapsed >= interval ) if need_reshuffle: stored_ids = list(all_ids) random.shuffle(stored_ids) entry = { 'last_shuffle_time': now.isoformat(), 'order_ids': stored_ids, } seq_map[scope] = entry state.OrderSequence = seq_map state.LastShuffleTime = now state.save(update_fields=['OrderSequence', 'LastShuffleTime', 'UpdateTime']) else: valid_ids = [oid for oid in stored_ids if oid in id_to_row] new_ids = [oid for oid in all_ids if oid not in set(valid_ids)] if len(valid_ids) != len(stored_ids) or new_ids: valid_ids.extend(new_ids) entry['order_ids'] = valid_ids seq_map[scope] = entry state.OrderSequence = seq_map state.save(update_fields=['OrderSequence', 'UpdateTime']) stored_ids = valid_ids anchor_dt = _parse_shuffle_time(entry.get('last_shuffle_time')) or _ensure_aware(now) ordered = [id_to_row[oid] for oid in stored_ids if oid in id_to_row] return ordered, anchor_dt def _format_fake_row(row, anchor_dt=None): """字段与 DashouDingdanHuoquView 真实单列表保持一致。""" avatar = DEFAULT_DISPATCHER_AVATAR platform = int(row.Platform or 1) oid = row.OrderID price = float(row.Amount or 0) ct = _display_time_str_for_order(oid, anchor_dt) zid = (row.DispatcherUserID or '').strip() sj_name = (getattr(row, 'MerchantNickname', None) or '').strip() return { 'dingdan_id': oid, 'zhuangtai': 1, 'pingtai': platform, 'dashou_fencheng': price, 'leixing_id': row.ProductTypeID or 0, 'tupian': avatar, 'yaoqiuleixing': 0, 'huiyuan_id': '', 'yajin': 0.0, 'jieshao': row.Description or '', 'beizhu': row.Remark or '', 'creat_time': ct, 'create_time': ct, 'sjnicheng': sj_name if platform == 2 else '', 'sj_avatar': avatar if platform == 2 else '', 'zhiding_uid': zid, 'zhiding_avatar': avatar, 'zhiding_nicheng': '', 'xuqiu_biaoqian': [], 'dashou_biaoqian': [], 'shangjia_biaoqian': [], 'shangjia_identity_biaoqian': [], 'zhiding_identity_biaoqian': [], 'shangjia_youzhi': bool(row.IsPremiumMerchant) if platform == 2 else False, } def build_fake_order_pool_response(request): data = request.data leixing_id = pick_leixing_id(data) page = max(1, int(data.get('page', 1))) page_size = max(1, min(int(data.get('page_size', 10)), 50)) club_id = resolve_effective_club_id(request, getattr(request, 'user', None)) rows, anchor_dt = _load_fake_rows_in_saved_order(club_id, leixing_id) total = len(rows) offset = (page - 1) * page_size page_rows = rows[offset:offset + page_size] formatted_list = [_format_fake_row(r, anchor_dt) for r in page_rows] has_more = (page * page_size) < total return Response({ 'code': 200, 'msg': '获取成功', 'data': { 'list': formatted_list, 'has_more': has_more, 'total': total, 'current_page': page, 'page_size': page_size, 'total_pages': (total + page_size - 1) // page_size if page_size else 0, }, })