fix: 成交指标始终记账并回填;抢单建聊移出事务并重试

抢单池刷新触发历史回填,旧单也能出成交率/罚款率/均时;建聊失败重试并订阅组队成员,避免消息列表空白。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-27 08:46:22 +08:00
parent 5e38a3d49a
commit e3f9654002
4 changed files with 143 additions and 25 deletions

View File

@@ -15,14 +15,62 @@ def _cfg(club_id: str):
def _stats_on(cfg, order) -> bool:
if not cfg or not cfg.stats_enabled or not cfg.stats_since:
return False
ct = getattr(order, 'CreateTime', None)
if not ct or ct < cfg.stats_since:
"""
成交指标始终记账(抢单池要展示成交率/罚款率/均时)。
若配置了 stats_since只统计该时间点之后的订单。
"""
if not order:
return False
since = getattr(cfg, 'stats_since', None) if cfg else None
if since:
ct = getattr(order, 'CreateTime', None)
if ct and ct < since:
return False
return True
def ensure_club_stats_bootstrapped(club_id: str, *, lookback_days: int = 180):
"""
俱乐部首次拉取抢单池商家指标时,若从未记账则回填近 N 天历史订单。
幂等:用 DealStatEvent 打标记。
"""
cid = (club_id or '').strip()
if not cid:
return
marker = f'{cid}:stats_bootstrap_v1'
from jituan.models import DealStatEvent, MerchantDealStat
if DealStatEvent.query.filter(event_key=marker).exists():
return
# 已有商家样本则只打标,避免重复重算
if MerchantDealStat.query.filter(club_id=cid, order_count__gt=0).exists():
_claim_event(cid, marker, 'stats_bootstrap', subject_uid='system')
return
try:
from orders.models import Order
from datetime import timedelta
since = timezone.now() - timedelta(days=max(7, int(lookback_days or 180)))
qs = (
Order.query.filter(ClubID=cid, CreateTime__gte=since)
.exclude(PlayerID__isnull=True)
.exclude(PlayerID='')
.filter(Status__in=[2, 3, 4, 5, 6, 8])
.order_by('CreateTime')[:800]
)
for order in qs:
try:
on_order_enter_pool(order, force=True)
if int(order.Status or 0) == 3:
on_order_completed(order, settle_started_at=getattr(order, 'SettlementTime', None), force=True)
elif int(order.Status or 0) == 5:
on_order_refunded(order, force=True)
except Exception as e:
logger.warning('回填单条成交指标失败 order=%s: %s', getattr(order, 'OrderID', ''), e)
_claim_event(cid, marker, 'stats_bootstrap', subject_uid='system', meta={'n': len(qs)})
logger.info('俱乐部 %s 成交指标回填完成 n=%s', cid, len(qs))
except Exception as e:
logger.error('俱乐部成交指标回填失败 club=%s: %s', cid, e, exc_info=True)
def _claim_event(club_id: str, event_key: str, event_type: str, order_id='', subject_uid='', meta=None) -> bool:
from jituan.models import DealStatEvent
if DealStatEvent.query.filter(event_key=event_key).exists():
@@ -68,11 +116,11 @@ def _merchant_uid(order):
@transaction.atomic
def on_order_enter_pool(order):
"""接单入池:商家/打手 order_count+1。"""
def on_order_enter_pool(order, *, force=False):
"""接单入池:商家/打手 order_count+1。force=True 用于历史回填(忽略 since"""
club_id = (getattr(order, 'ClubID', None) or '').strip()
cfg = _cfg(club_id)
if not _stats_on(cfg, order):
if not force and not _stats_on(cfg, order):
return
oid = order.OrderID
dashou = (order.PlayerID or '').strip()
@@ -93,16 +141,16 @@ def on_order_enter_pool(order):
@transaction.atomic
def on_order_completed(order, settle_started_at=None):
def on_order_completed(order, settle_started_at=None, *, force=False):
club_id = (getattr(order, 'ClubID', None) or '').strip()
cfg = _cfg(club_id)
if not _stats_on(cfg, order):
if not force and not _stats_on(cfg, order):
return
oid = order.OrderID
if not _claim_event(club_id, f'{oid}:completed', 'completed', oid):
return
# 确保入池
on_order_enter_pool(order)
on_order_enter_pool(order, force=force)
st = settle_started_at or getattr(order, '_deal_settle_started_at', None)
duration = None
@@ -134,10 +182,10 @@ def on_order_completed(order, settle_started_at=None):
@transaction.atomic
def on_order_refunded(order):
def on_order_refunded(order, *, force=False):
club_id = (getattr(order, 'ClubID', None) or '').strip()
cfg = _cfg(club_id)
if not _stats_on(cfg, order) or order.Platform != 2:
if (not force and not _stats_on(cfg, order)) or order.Platform != 2:
return
oid = order.OrderID
mid = _merchant_uid(order)
@@ -145,7 +193,7 @@ def on_order_refunded(order):
return
if not _claim_event(club_id, f'{oid}:refunded', 'refunded', oid, mid):
return
on_order_enter_pool(order)
on_order_enter_pool(order, force=force)
_ensure_merchant(club_id, mid)
from jituan.models import MerchantDealStat
MerchantDealStat.objects.filter(club_id=club_id, merchant_uid=mid).update(
@@ -160,8 +208,7 @@ def on_penalty_paid(penalty):
if int(getattr(penalty, 'Status', 0) or 0) != PENALTY_PAID:
return
club_id = (getattr(penalty, 'ClubID', None) or '').strip()
cfg = _cfg(club_id)
if not cfg or not cfg.stats_enabled or not cfg.stats_since:
if not club_id:
return
pid = getattr(penalty, 'id', None) or getattr(penalty, 'pk', None)
key = f'penalty:{pid}:paid'
@@ -246,6 +293,16 @@ def batch_pool_merchant_stats(club_id: str, merchant_uids, order_club_map=None)
if not uids:
return {}
default_club = (club_id or '').strip()
# 首次无样本时回填历史,保证旧单刷新也能看到成交率/罚款率/均时
try:
if default_club:
ensure_club_stats_bootstrapped(default_club)
if order_club_map:
for cid in set((c or '').strip() for c in order_club_map.values() if c):
if cid and cid != default_club:
ensure_club_stats_bootstrapped(cid)
except Exception as e:
logger.warning('抢单池成交指标回填触发失败: %s', e)
club_ids = {default_club} if default_club else set()
if order_club_map:
for cid in order_club_map.values():

View File

@@ -17,12 +17,18 @@ logger = logging.getLogger(__name__)
def get_or_create_deal_config(club_id: str):
from jituan.models import ClubOrderDealConfig
from datetime import datetime
cid = (club_id or '').strip()
if not cid:
return None
row = ClubOrderDealConfig.query.filter(club_id=cid).first()
if not row:
row = ClubOrderDealConfig(club_id=cid)
# 新建俱乐部默认开启成交指标,便于抢单池立刻展示成交率/罚款率/均时
row = ClubOrderDealConfig(
club_id=cid,
stats_enabled=True,
stats_since=timezone.make_aware(datetime(2020, 1, 1)),
)
row.save()
return row
@@ -118,7 +124,9 @@ def save_deal_config(club_id: str, data: dict, updated_by: str = '') -> dict:
enabled = bool(data['stats_enabled'])
row.stats_enabled = enabled
if enabled and not was_stats:
row.stats_since = now
# 开启时默认从较早起点计,便于旧单刷新也能出成交率
from datetime import datetime
row.stats_since = timezone.make_aware(datetime(2020, 1, 1))
if not enabled:
# 停止写入;保留 since 供只读理解历史起点;再次开启会刷新 since
pass
@@ -127,10 +135,19 @@ def save_deal_config(club_id: str, data: dict, updated_by: str = '') -> dict:
# 若刚打开统计且 since 空
if row.stats_enabled and not row.stats_since:
row.stats_since = now
from datetime import datetime
row.stats_since = timezone.make_aware(datetime(2020, 1, 1))
row.updated_by = (updated_by or '')[:32]
row.save()
# 保存后触发一次历史回填,保证抢单池马上能刷到成交率/罚款率
try:
from jituan.models import DealStatEvent
from jituan.services.deal_stats import ensure_club_stats_bootstrapped
DealStatEvent.objects.filter(event_key=f'{row.club_id}:stats_bootstrap_v1').delete()
ensure_club_stats_bootstrapped(row.club_id)
except Exception as e:
logger.warning('保存成交配置后回填失败 club=%s: %s', row.club_id, e)
return config_to_dict(row)