补齐成交跟进:按线路推成交通知、收下未成交原因、协商中按工作日再问。

成交走询价消息服务;陆运/海运私聊下一句能记下原因;协商中回执写明顺延三个工作日和下次 9:00。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-19 16:08:48 +08:00
co-authored by Cursor
parent 9bb69ae72e
commit 1a2e1d6cb2
31 changed files with 1638 additions and 30 deletions
+66 -7
View File
@@ -64,6 +64,7 @@ def ticket_view(ticket: object) -> dict[str, Any]:
view.get("productNames") or view.get("product_names") or [],
)
view.setdefault("sales_name", view.get("salesName") or view.get("sales_name") or "")
view.setdefault("lost_reason", view.get("lostReason") or view.get("lost_reason") or "")
facts = dict(view.get("collab_facts") or view.get("collabFacts") or {})
view.setdefault("collab_facts", facts)
flag = str(facts.get("__ended") or view.get("collab_ended") or view.get("collabEnded") or "").strip().lower()
@@ -89,6 +90,7 @@ def ticket_view(ticket: object) -> dict[str, Any]:
"product_ids": list(getattr(ticket, "product_ids", []) or []),
"product_names": list(getattr(ticket, "product_names", []) or []),
"sales_name": str(getattr(ticket, "sales_name", "") or ""),
"lost_reason": str(getattr(ticket, "lost_reason", "") or ""),
}
@@ -987,18 +989,75 @@ def apply_group_deal(
if not (quote.get("total") or _quote_fee_rows(quote) or status_now in {"已报价", "协商中"}):
send_group_text(client, chat_id, copy.group_deal_need_quote())
return "deal_need_quote"
writer = getattr(flow.ledger, "transition", None)
if callable(writer):
result = writer(work_order_no=no, to_status=outcome) or {}
if not result.get("ok"):
send_group_text(client, chat_id, "主账状态更新失败,成交结果未入账。")
return "ledger_fail"
from agent.policy.deal_outcome import finish_deal_outcome
result = finish_deal_outcome(
ledger=flow.ledger,
work_order_no=no,
outcome=outcome,
facts=facts_of_group(view, sess),
quote=quote,
business_line=str(view.get("business_line") or (sess.business_line if sess else "SEA") or "SEA"),
sales_name=str(view.get("sales_name") or (getattr(sess, "invited_sales_name", "") if sess else "") or ""),
)
if not result.get("ok"):
send_group_text(client, chat_id, "主账状态更新失败,成交结果未入账。")
return "ledger_fail"
phase = str(result.get("phase") or "done")
if sess is not None:
sess.status = outcome
sess.phase = phase
if phase == "wait_deal":
sess.allowed = (copy.BTN_DEAL, copy.BTN_LOST, copy.BTN_NEGOTIATE)
else:
sess.allowed = ()
flow._save(sess)
send_group_text(client, chat_id, copy.deal_ack(outcome, no, str(result.get("next_followup_at") or "")))
return phase
def facts_of_group(view: dict[str, Any], sess: Any = None) -> dict[str, str]:
"""主账字段打底,书签覆盖;给负责人匹配和成交通知卡用。"""
facts = dict(view.get("facts") or {})
if sess is not None and getattr(sess, "facts", None):
facts.update(dict(sess.facts or {}))
return facts
def capture_group_lost_reason(
*,
flow: Any,
ticket: object,
sender_id: str,
text: str,
sess: Any = None,
) -> str:
"""
群里未成交后,本单销售下一句任意文字即原因。
空运群是否要 @ 由入站通道先拦,这里不再判。
"""
ticket = hydrate_ticket(flow, ticket)
view = ticket_view(ticket)
no = ticket_no(ticket)
chat_id = str((sess.collab_chat_id if sess else "") or view.get("collab_chat_id") or "")
client = group_client_of(flow)
if ticket_status(ticket) != "未成交":
return ""
if str(view.get("lost_reason") or "").strip():
return ""
if not is_sales(ticket, sender_id):
return ""
from agent.policy.deal_outcome import capture_lost_reason
result = capture_lost_reason(ledger=flow.ledger, work_order_no=no, reason=text)
if not result.get("ok"):
send_group_text(client, chat_id, "未成交原因没有记下,请再发一句。")
return "wait_lost_reason"
if sess is not None:
sess.phase = "done"
sess.allowed = ()
flow._save(sess)
send_group_text(client, chat_id, copy.deal_ack(outcome, no))
send_group_text(client, chat_id, copy.lost_reason_ack(text))
return "done"