空运群超时找不到航线成员时写成了「相关人员」。改为 @ 航线同事;销售超时只 @ 本单销售。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -18,6 +18,11 @@ from agent.policy.event_exception import (
|
||||
card_title,
|
||||
conversation_of,
|
||||
exception_type,
|
||||
WAIT_ADOPT,
|
||||
WAIT_AIR_QUOTE,
|
||||
WAIT_DEAL,
|
||||
WAIT_LOST_REASON,
|
||||
mentions_for_role,
|
||||
pick_app_targets,
|
||||
pick_mentions,
|
||||
timeout_code,
|
||||
@@ -25,6 +30,9 @@ from agent.policy.event_exception import (
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 采用、成交跟进、未成交原因都是在等销售。点名只找本单销售。
|
||||
_SALES_WAIT_KINDS = {WAIT_ADOPT, WAIT_DEAL, WAIT_LOST_REASON}
|
||||
|
||||
SendGroupFn = Callable[..., bool]
|
||||
EnqueueFn = Callable[..., tuple[bool, str]]
|
||||
TimeoutLookupFn = Callable[..., dict[str, Any]]
|
||||
@@ -149,19 +157,17 @@ def _handle_one(
|
||||
if workday_elapsed_hours(started, now) < hours:
|
||||
return False
|
||||
watchers = [dict(x) for x in (row.get("watchers") or []) if isinstance(x, dict)]
|
||||
sales = next((dict(w) for w in watchers if str(w.get("roleCode") or "").lower() in {"sales", ""}), None)
|
||||
if sales is None:
|
||||
sales = {
|
||||
"wecomId": str(row.get("sales_wecom_id") or row.get("salesWecomId") or "").strip(),
|
||||
"name": str(row.get("sales_name") or row.get("salesName") or "销售").strip() or "销售",
|
||||
"roleCode": "sales",
|
||||
"notifyEventException": 0,
|
||||
}
|
||||
sales = _ticket_sales(row, watchers)
|
||||
people = pick_mentions(wait_kind=kind, sales=sales, members=watchers, business_line=line)
|
||||
# 空运群往往只绑了群号,成员列表是空的,群里就找不到航线岗,话术会落成「相关人员」。
|
||||
# 只补航线超时。销售超时不能改去 @ 航线,只 @ 本单销售。
|
||||
if kind == WAIT_AIR_QUOTE and not people:
|
||||
people = _airline_staff_mentions(book)
|
||||
names = [str(p.get("name") or "").strip() for p in people if str(p.get("name") or "").strip()]
|
||||
chat_id = str(row.get("collab_chat_id") or row.get("collabChatId") or "").strip()
|
||||
where = conversation_of(wait_kind=kind, collab_chat_id=chat_id)
|
||||
mention = where == "group"
|
||||
# 群里用 @。销售超时即使发到私聊,也写成 @销售姓名,不写「您」或「相关人员」。
|
||||
mention = where == "group" or kind in _SALES_WAIT_KINDS
|
||||
text = copy.event_exception_session_text(
|
||||
exception_type=exception_type(kind),
|
||||
work_order_no=no,
|
||||
@@ -208,6 +214,59 @@ def _handle_one(
|
||||
return True
|
||||
|
||||
|
||||
def _ticket_sales(row: dict[str, Any], watchers: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"""
|
||||
这张工单的销售。销售超时只 @ 这个人。
|
||||
|
||||
先按工单上的销售企微对上关注人,对不上再用岗位是销售的那条。
|
||||
姓名用工单上的销售名,避免点成「销售」两个字或点到航线。
|
||||
"""
|
||||
sid = str(row.get("sales_wecom_id") or row.get("salesWecomId") or "").strip()
|
||||
sname = str(row.get("sales_name") or row.get("salesName") or "").strip()
|
||||
matched: dict[str, Any] | None = None
|
||||
for raw in watchers:
|
||||
uid = str(raw.get("wecomId") or raw.get("wecom_id") or "").strip()
|
||||
role = str(raw.get("roleCode") or raw.get("role_code") or "").strip().lower()
|
||||
if sid and uid == sid:
|
||||
matched = dict(raw)
|
||||
break
|
||||
if matched is None and role == "sales":
|
||||
matched = dict(raw)
|
||||
if matched is None:
|
||||
matched = {
|
||||
"wecomId": sid,
|
||||
"roleCode": "sales",
|
||||
"notifyEventException": 0,
|
||||
}
|
||||
name = str(matched.get("name") or "").strip()
|
||||
if sname and (not name or name in {sid, "销售"}):
|
||||
matched["name"] = sname
|
||||
elif not name:
|
||||
matched["name"] = sname or "销售"
|
||||
if sid and not str(matched.get("wecomId") or "").strip():
|
||||
matched["wecomId"] = sid
|
||||
matched.setdefault("roleCode", "sales")
|
||||
return matched
|
||||
|
||||
|
||||
def _airline_staff_mentions(book: Any) -> list[dict[str, Any]]:
|
||||
"""
|
||||
主账按岗位列出启用的航线同事。
|
||||
|
||||
只在群成员里没有航线岗时调用。主账失败或没有这个接口时返回空,话术仍用「相关人员」。
|
||||
不写工单。可与巡检线程并发,每次只读这一单的员工名单。
|
||||
"""
|
||||
lister = getattr(book, "list_staff_by_role", None)
|
||||
if not callable(lister):
|
||||
return []
|
||||
try:
|
||||
rows = list(lister(role_code="air") or [])
|
||||
except Exception:
|
||||
logger.exception("事件异常列航线岗失败")
|
||||
return []
|
||||
return mentions_for_role(rows, role_code="air")
|
||||
|
||||
|
||||
def _send_app_cards(
|
||||
*,
|
||||
people: list[dict[str, Any]],
|
||||
|
||||
@@ -164,7 +164,7 @@ def pick_mentions(
|
||||
"""
|
||||
本单这次应回而未回、需要被点名的人。
|
||||
|
||||
航线只盯群里航线岗;海运/陆运产品盯对应产品岗;销售三类只盯本单销售。
|
||||
航线只盯群里航线岗;海运/陆运产品盯对应产品岗;销售三类只盯本单销售,不点航线。
|
||||
不要询价机器人、不要群里其他人。
|
||||
"""
|
||||
kind = (wait_kind or "").strip()
|
||||
@@ -206,6 +206,48 @@ def pick_mentions(
|
||||
return out
|
||||
|
||||
|
||||
def mentions_for_role(
|
||||
rows: list[dict[str, Any]] | None,
|
||||
*,
|
||||
role_code: str,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""
|
||||
从员工行里取出启用、有企微、有姓名的指定岗位。
|
||||
|
||||
空运群激活只记群号,协同成员经常是空的。超时巡检在群成员里找不到航线岗时,
|
||||
用本函数点名员工管理里的航线·空运,和进群摘要 @ 的是同一批人。
|
||||
不发消息、不写主账。
|
||||
"""
|
||||
want = (role_code or "").strip().lower()
|
||||
if not want:
|
||||
return []
|
||||
out: list[dict[str, Any]] = []
|
||||
seen: set[str] = set()
|
||||
for raw in rows or []:
|
||||
if not isinstance(raw, dict):
|
||||
continue
|
||||
role = str(raw.get("roleCode") or raw.get("role_code") or "").strip().lower()
|
||||
if role != want:
|
||||
continue
|
||||
status = str(raw.get("status") or "active").strip().lower()
|
||||
if status in {"disabled", "0", "停用", "inactive"}:
|
||||
continue
|
||||
uid = str(raw.get("wecomId") or raw.get("wecom_id") or "").strip()
|
||||
name = str(raw.get("name") or "").strip()
|
||||
if not uid or not name or uid in seen:
|
||||
continue
|
||||
seen.add(uid)
|
||||
out.append(
|
||||
{
|
||||
"wecomId": uid,
|
||||
"name": name,
|
||||
"roleCode": want,
|
||||
"notifyEventException": raw.get("notifyEventException", 0),
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def pick_app_targets(people: list[dict[str, Any]] | None) -> list[dict[str, Any]]:
|
||||
"""勾了「接收事件异常消息提醒」且绑了企微的人。"""
|
||||
out: list[dict[str, Any]] = []
|
||||
|
||||
Reference in New Issue
Block a user