后台按查看权限收紧菜单与「只看自己」数据范围;跨境整车报价填表更稳,协商中顺延不再误报销售超时。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-10-11 13:04:09 +08:00
co-authored by Cursor
parent ae818f26b1
commit b070a9b7a7
23 changed files with 1383 additions and 498 deletions
@@ -123,6 +123,35 @@ def notify_app_flag(override: Optional[bool]) -> bool:
return notify_app_ready()
def _skip_sales_timeout(
*,
status: str,
wait_kind: str,
next_followup_at: str,
lost_reason: str,
now: datetime,
) -> str:
"""
成交跟进相关等待在终态/顺延窗内不应再告警。
返回跳过原因;空串表示继续按小时判定。
协商中:只要还排着再问时刻(next_followup_at > now),就不发销售超时;
再问认领后会清空该时刻并重新 start deal,那时才重新计时。
"""
st = (status or "").strip()
_ = wait_kind
if st == "未成交" and (lost_reason or "").strip():
return "lost_reason_recorded"
if st == "协商中":
raw = (next_followup_at or "").strip()
if not raw:
return ""
due = _parse_started(raw)
if due is None or due > now:
return "negotiate_followup_pending"
return ""
def _handle_one(
row: dict[str, Any],
*,
@@ -139,6 +168,14 @@ def _handle_one(
kind = str(row.get("event_wait_kind") or row.get("eventWaitKind") or "").strip()
if not no or not kind:
return False
status = str(row.get("status") or "").strip()
followup = str(row.get("next_followup_at") or row.get("nextFollowupAt") or "").strip()
lost_reason = str(row.get("lost_reason") or row.get("lostReason") or "").strip()
# 产品口径:协商中顺延再问期间不算「销售无响应」;未成交已写原因不再催。
# 即使 clear_wait 失败留下陈旧 deal 等待,这里也要挡住(WO202610060024 / WO202610070034)。
skip_reason = _skip_sales_timeout(
status=status, wait_kind=kind, next_followup_at=followup, lost_reason=lost_reason, now=now
)
line = str(row.get("business_line") or row.get("businessLine") or "").strip()
facts = dict(row.get("facts") or {})
klass = str(facts.get("运输分类") or facts.get("transportType") or "").strip()
@@ -154,6 +191,9 @@ def _handle_one(
started = _parse_started(row.get("event_wait_started_at") or row.get("eventWaitStartedAt"))
if started is None:
return False
if skip_reason:
logger.info("事件异常跳过 wo=%s kind=%s reason=%s", no, kind, skip_reason)
return False
if workday_elapsed_hours(started, now) < hours:
return False
watchers = [dict(x) for x in (row.get("watchers") or []) if isinstance(x, dict)]