补齐成交跟进:按线路推成交通知、收下未成交原因、协商中按工作日再问。
成交走询价消息服务;陆运/海运私聊下一句能记下原因;协商中回执写明顺延三个工作日和下次 9:00。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -193,15 +193,46 @@ class HttpLedger:
|
||||
out.setdefault("needsPdf", str(format).lower() == "pdf")
|
||||
return out
|
||||
|
||||
def transition(self, *, work_order_no: str, to_status: str) -> dict[str, Any]:
|
||||
out = self._post(
|
||||
"/inquiry/agent/ticket/transition",
|
||||
{"workOrderNo": work_order_no, "toStatus": to_status},
|
||||
)
|
||||
def transition(
|
||||
self,
|
||||
*,
|
||||
work_order_no: str,
|
||||
to_status: str,
|
||||
remark: str = "",
|
||||
lost_reason: str = "",
|
||||
next_followup_at: str = "",
|
||||
deal_notify_summary: str = "",
|
||||
) -> dict[str, Any]:
|
||||
body: dict[str, Any] = {"workOrderNo": work_order_no, "toStatus": to_status}
|
||||
if remark:
|
||||
body["remark"] = remark
|
||||
if lost_reason:
|
||||
body["lostReason"] = lost_reason
|
||||
if next_followup_at:
|
||||
body["nextFollowupAt"] = next_followup_at
|
||||
if deal_notify_summary:
|
||||
body["dealNotifySummary"] = deal_notify_summary
|
||||
out = self._post("/inquiry/agent/ticket/transition", body)
|
||||
if not out.get("ok") and out.get("status"):
|
||||
out["ok"] = True
|
||||
return out if out.get("ok") else {"ok": False, "error": out.get("error") or "transition_failed"}
|
||||
|
||||
def patch_lost_reason(self, *, work_order_no: str, lost_reason: str) -> dict[str, Any]:
|
||||
"""未成交后记下原因。不改六态。"""
|
||||
out = self._post(
|
||||
"/inquiry/agent/ticket/patchLostReason",
|
||||
{"workOrderNo": work_order_no, "lostReason": lost_reason},
|
||||
)
|
||||
return out if out.get("ok") else {"ok": False, "error": out.get("error") or "patch_lost_reason_failed"}
|
||||
|
||||
def claim_due_negotiate(self) -> list[dict[str, Any]]:
|
||||
"""认领到期协商再问,主账会清空再问时刻防重发。"""
|
||||
out = self._post("/inquiry/agent/ticket/claimDueNegotiate", {})
|
||||
rows = out.get("tickets") or out.get("result") or []
|
||||
if isinstance(rows, list):
|
||||
return [dict(x) for x in rows if isinstance(x, dict)]
|
||||
return []
|
||||
|
||||
def match_sea_staff(self, *, origin: str, destination: str) -> list[dict[str, Any]]:
|
||||
"""主账按线路返回启用的海运产品岗。"""
|
||||
out = self._post(
|
||||
|
||||
@@ -41,6 +41,9 @@ class MemoryTicket:
|
||||
collab_ended: bool = False
|
||||
attachments: list[dict[str, Any]] = field(default_factory=list)
|
||||
quote_file_ready: bool = False
|
||||
lost_reason: str = ""
|
||||
next_followup_at: str = ""
|
||||
deal_notify_summary: str = ""
|
||||
|
||||
|
||||
class MemoryLedger:
|
||||
@@ -207,14 +210,70 @@ class MemoryLedger:
|
||||
"status": ticket.status,
|
||||
}
|
||||
|
||||
def transition(self, *, work_order_no: str, to_status: str) -> dict[str, Any]:
|
||||
def transition(
|
||||
self,
|
||||
*,
|
||||
work_order_no: str,
|
||||
to_status: str,
|
||||
remark: str = "",
|
||||
lost_reason: str = "",
|
||||
next_followup_at: str = "",
|
||||
deal_notify_summary: str = "",
|
||||
) -> dict[str, Any]:
|
||||
_ = remark
|
||||
with self._lock:
|
||||
ticket = self._tickets.get(work_order_no)
|
||||
if ticket is None:
|
||||
return {"ok": False, "error": "ticket_not_found"}
|
||||
ticket.status = to_status
|
||||
if to_status == "未成交":
|
||||
ticket.next_followup_at = ""
|
||||
if lost_reason:
|
||||
ticket.lost_reason = lost_reason
|
||||
elif to_status == "已成交":
|
||||
ticket.next_followup_at = ""
|
||||
if deal_notify_summary:
|
||||
ticket.deal_notify_summary = deal_notify_summary
|
||||
elif to_status == "协商中":
|
||||
ticket.next_followup_at = next_followup_at
|
||||
return {"ok": True, "status": ticket.status}
|
||||
|
||||
def patch_lost_reason(self, *, work_order_no: str, lost_reason: str) -> dict[str, Any]:
|
||||
reason = (lost_reason or "").strip()
|
||||
with self._lock:
|
||||
ticket = self._tickets.get(work_order_no)
|
||||
if ticket is None:
|
||||
return {"ok": False, "error": "ticket_not_found"}
|
||||
if ticket.status != "未成交":
|
||||
return {"ok": False, "error": "not_lost", "status": ticket.status}
|
||||
if not reason:
|
||||
return {"ok": False, "error": "reason_blank"}
|
||||
ticket.lost_reason = reason
|
||||
return {"ok": True, "work_order_no": work_order_no, "lost_reason": reason}
|
||||
|
||||
def claim_due_negotiate(self) -> list[dict[str, Any]]:
|
||||
now = datetime.now(_SHANGHAI).strftime("%Y-%m-%d %H:%M:%S")
|
||||
hits: list[dict[str, Any]] = []
|
||||
with self._lock:
|
||||
for ticket in self._tickets.values():
|
||||
due = (ticket.next_followup_at or "").strip()
|
||||
if ticket.status != "协商中" or not due or due > now:
|
||||
continue
|
||||
ticket.next_followup_at = ""
|
||||
hits.append(
|
||||
{
|
||||
"work_order_no": ticket.work_order_no,
|
||||
"sales_wecom_id": ticket.sales_wecom_id,
|
||||
"sales_name": "",
|
||||
"business_line": ticket.business_line,
|
||||
"collab_chat_id": ticket.collab_chat_id,
|
||||
"status": ticket.status,
|
||||
"facts": dict(ticket.facts),
|
||||
"quote": dict(ticket.quote),
|
||||
}
|
||||
)
|
||||
return hits
|
||||
|
||||
def get_ticket(self, *, work_order_no: str) -> MemoryTicket | None:
|
||||
with self._lock:
|
||||
return self._tickets.get(work_order_no)
|
||||
@@ -282,8 +341,12 @@ class MemoryLedger:
|
||||
status = ticket.status
|
||||
quote = dict(ticket.quote or {})
|
||||
wait_phase = "done"
|
||||
if status in {"已成交", "未成交", "协商中", "已关闭"}:
|
||||
if status in {"已成交", "已关闭"}:
|
||||
wait_phase = "done"
|
||||
elif status == "未成交":
|
||||
wait_phase = "wait_lost_reason" if not (ticket.lost_reason or "").strip() else "done"
|
||||
elif status == "协商中":
|
||||
wait_phase = "wait_deal"
|
||||
elif status == "已报价" and quote:
|
||||
# 文件已发给销售 → 成交跟进;否则海运协同卡、空运方案卡
|
||||
if ticket.quote_file_ready or quote.get("quoteFileIssued"):
|
||||
@@ -306,6 +369,10 @@ class MemoryLedger:
|
||||
"wait_phase": wait_phase,
|
||||
"collab_chat_id": ticket.collab_chat_id,
|
||||
"collab_facts": dict(ticket.collab_facts),
|
||||
"lost_reason": ticket.lost_reason,
|
||||
"lostReason": ticket.lost_reason,
|
||||
"deal_notify_summary": ticket.deal_notify_summary,
|
||||
"next_followup_at": ticket.next_followup_at,
|
||||
}
|
||||
|
||||
def upsert_staff(self, row: dict[str, Any]) -> None:
|
||||
|
||||
Reference in New Issue
Block a user