落地海运私聊文字询价全流程,海运多项费用改走文本明细,避免企微卡片引用条截断。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -102,10 +102,20 @@ class HttpLedger:
|
||||
return {"ok": False, "found": False, "owned": False, "work_order_no": work_order_no}
|
||||
return out
|
||||
|
||||
def query_tms(self, *, work_order_no: str, facts: dict[str, str]) -> dict[str, Any]:
|
||||
def query_tms(
|
||||
self,
|
||||
*,
|
||||
work_order_no: str,
|
||||
facts: dict[str, str],
|
||||
business_line: str = "",
|
||||
) -> dict[str, Any]:
|
||||
body: dict[str, Any] = {"work_order_no": work_order_no, "facts": facts}
|
||||
line = (business_line or str((facts or {}).get("业务线") or "")).strip()
|
||||
if line:
|
||||
body["businessLine"] = line
|
||||
out = self._post(
|
||||
"/inquiry/agent/tms/query",
|
||||
{"work_order_no": work_order_no, "facts": facts},
|
||||
body,
|
||||
idem=f"tms:{work_order_no}",
|
||||
)
|
||||
if "classification" not in out and not out.get("ok"):
|
||||
@@ -182,3 +192,72 @@ class HttpLedger:
|
||||
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 match_sea_staff(self, *, origin: str, destination: str) -> list[dict[str, Any]]:
|
||||
"""主账按线路返回启用的海运产品岗。"""
|
||||
out = self._post(
|
||||
"/inquiry/agent/staff/matchSea",
|
||||
{"origin": origin, "destination": destination},
|
||||
)
|
||||
rows = out.get("staff") or out.get("result") or []
|
||||
if isinstance(rows, list):
|
||||
return [dict(x) for x in rows if isinstance(x, dict)]
|
||||
return []
|
||||
|
||||
def get_staff_by_wecom_id(self, *, wecom_id: str) -> dict[str, Any]:
|
||||
out = self._post("/inquiry/agent/staff/byWecom", {"wecomId": wecom_id})
|
||||
if out.get("name") or out.get("wecomId"):
|
||||
return out
|
||||
return {}
|
||||
|
||||
def find_staff_by_name(self, *, name: str) -> dict[str, Any]:
|
||||
"""按后台员工姓名取企微 userid。会话存档账号「询价机器人」走这里。"""
|
||||
out = self._post("/inquiry/agent/staff/byName", {"name": name})
|
||||
if out.get("name") or out.get("wecomId"):
|
||||
return out
|
||||
return {}
|
||||
|
||||
def bind_collab_group(
|
||||
self,
|
||||
*,
|
||||
work_order_no: str,
|
||||
chat_id: str,
|
||||
member_ids: list[str],
|
||||
product_names: list[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
out = self._post(
|
||||
"/inquiry/agent/ticket/bindCollabGroup",
|
||||
{
|
||||
"workOrderNo": work_order_no,
|
||||
"chatId": chat_id,
|
||||
"memberIds": member_ids,
|
||||
"productNames": product_names or [],
|
||||
},
|
||||
idem=f"bind-group:{work_order_no}",
|
||||
)
|
||||
out.setdefault("ok", bool(out.get("chat_id") or out.get("chatId")))
|
||||
return out
|
||||
|
||||
def patch_collab_facts(
|
||||
self,
|
||||
*,
|
||||
work_order_no: str,
|
||||
facts: dict[str, str],
|
||||
sender_id: str = "",
|
||||
) -> dict[str, Any]:
|
||||
return self._post(
|
||||
"/inquiry/agent/ticket/patchCollabFacts",
|
||||
{
|
||||
"workOrderNo": work_order_no,
|
||||
"facts": facts,
|
||||
"senderId": sender_id,
|
||||
},
|
||||
idem=f"collab-facts:{work_order_no}:{sorted(facts)}",
|
||||
)
|
||||
|
||||
def find_by_collab_chat(self, *, chat_id: str) -> dict[str, Any] | None:
|
||||
"""用协同群 chat_id 找回工单。对不上返回 None。"""
|
||||
out = self._post("/inquiry/agent/ticket/byCollabChat", {"chatId": chat_id})
|
||||
if not out.get("found") and not out.get("work_order_no"):
|
||||
return None
|
||||
return out
|
||||
|
||||
@@ -33,6 +33,10 @@ class MemoryTicket:
|
||||
status: str = "询价中"
|
||||
quote: dict[str, Any] = field(default_factory=dict)
|
||||
quote_version: int = 0
|
||||
collab_chat_id: str = ""
|
||||
collab_facts: dict[str, str] = field(default_factory=dict)
|
||||
collab_member_ids: list[str] = field(default_factory=list)
|
||||
quote_file_ready: bool = False
|
||||
|
||||
|
||||
class MemoryLedger:
|
||||
@@ -57,6 +61,9 @@ class MemoryLedger:
|
||||
}
|
||||
]
|
||||
self.last_render: dict[str, Any] = {}
|
||||
self._sea_staff: list[dict[str, Any]] = []
|
||||
self._staff_by_wecom: dict[str, dict[str, Any]] = {}
|
||||
self._staff_by_name: dict[str, dict[str, Any]] = {}
|
||||
|
||||
def _next_no(self) -> str:
|
||||
self._seq += 1
|
||||
@@ -135,7 +142,14 @@ class MemoryLedger:
|
||||
"status": "询价中",
|
||||
}
|
||||
|
||||
def query_tms(self, *, work_order_no: str, facts: dict[str, str]) -> dict[str, Any]:
|
||||
def query_tms(
|
||||
self,
|
||||
*,
|
||||
work_order_no: str,
|
||||
facts: dict[str, str],
|
||||
business_line: str = "",
|
||||
) -> dict[str, Any]:
|
||||
_ = business_line
|
||||
dest = str(facts.get("目的港") or "")
|
||||
force = str(facts.get("tms_force") or "")
|
||||
if force == "1002" or force == "1003" or "无价" in dest or "歧义" in dest:
|
||||
@@ -243,6 +257,10 @@ class MemoryLedger:
|
||||
"fileBase64": "UEsDBAoAAAAAA",
|
||||
"needsPdf": False,
|
||||
}
|
||||
with self._lock:
|
||||
ticket = self._tickets.get((work_order_no or "").strip())
|
||||
if ticket is not None:
|
||||
ticket.quote_file_ready = True
|
||||
self.last_render = out
|
||||
return out
|
||||
|
||||
@@ -261,7 +279,13 @@ class MemoryLedger:
|
||||
if status in {"已成交", "未成交", "协商中", "已关闭"}:
|
||||
wait_phase = "done"
|
||||
elif status == "已报价" and quote:
|
||||
wait_phase = "wait_adopt"
|
||||
# 文件已发给销售 → 成交跟进;否则海运协同卡、空运方案卡
|
||||
if ticket.quote_file_ready or quote.get("quoteFileIssued"):
|
||||
wait_phase = "wait_deal"
|
||||
elif (ticket.business_line or "").upper() == "SEA":
|
||||
wait_phase = "wait_collab"
|
||||
else:
|
||||
wait_phase = "wait_adopt"
|
||||
else:
|
||||
wait_phase = "tms_miss"
|
||||
return {
|
||||
@@ -274,4 +298,111 @@ class MemoryLedger:
|
||||
"facts": dict(ticket.facts),
|
||||
"quote": quote,
|
||||
"wait_phase": wait_phase,
|
||||
"collab_chat_id": ticket.collab_chat_id,
|
||||
"collab_facts": dict(ticket.collab_facts),
|
||||
}
|
||||
|
||||
def upsert_staff(self, row: dict[str, Any]) -> None:
|
||||
"""单测注入任意岗位(含会话存档账号「询价机器人」)。"""
|
||||
data = dict(row)
|
||||
uid = str(data.get("wecomId") or data.get("wecom_id") or "").strip()
|
||||
name = str(data.get("name") or "").strip()
|
||||
with self._lock:
|
||||
if uid:
|
||||
self._staff_by_wecom[uid] = data
|
||||
if name:
|
||||
self._staff_by_name[name] = data
|
||||
|
||||
def set_sea_staff(self, rows: list[dict[str, Any]]) -> None:
|
||||
"""单测注入海运产品岗。"""
|
||||
with self._lock:
|
||||
self._sea_staff = [dict(x) for x in rows]
|
||||
for row in self._sea_staff:
|
||||
uid = str(row.get("wecomId") or row.get("wecom_id") or "").strip()
|
||||
name = str(row.get("name") or "").strip()
|
||||
if uid:
|
||||
self._staff_by_wecom[uid] = dict(row)
|
||||
if name:
|
||||
self._staff_by_name[name] = dict(row)
|
||||
|
||||
def match_sea_staff(self, *, origin: str, destination: str) -> list[dict[str, Any]]:
|
||||
"""
|
||||
岗位=产品·海运、启用:写了「海运·全部线路」必拉;
|
||||
写了具体港口的,对得上本单起运港/目的港才拉。
|
||||
"""
|
||||
origin = (origin or "").strip()
|
||||
dest = (destination or "").strip()
|
||||
hits: list[dict[str, Any]] = []
|
||||
with self._lock:
|
||||
for row in self._sea_staff:
|
||||
if str(row.get("roleCode") or row.get("role_code") or "") != "sea":
|
||||
continue
|
||||
if str(row.get("status") or "active") != "active":
|
||||
continue
|
||||
routes = str(row.get("routes") or "")
|
||||
if "海运·全部线路" in routes or "海运全线" in routes:
|
||||
hits.append(dict(row))
|
||||
continue
|
||||
if origin and origin in routes:
|
||||
hits.append(dict(row))
|
||||
continue
|
||||
if dest and dest in routes:
|
||||
hits.append(dict(row))
|
||||
return hits
|
||||
|
||||
def get_staff_by_wecom_id(self, *, wecom_id: str) -> dict[str, Any]:
|
||||
"""按企微 userid 取员工展示名。"""
|
||||
with self._lock:
|
||||
return dict(self._staff_by_wecom.get((wecom_id or "").strip()) or {})
|
||||
|
||||
def find_staff_by_name(self, *, name: str) -> dict[str, Any]:
|
||||
"""按员工姓名取企微 userid。"""
|
||||
with self._lock:
|
||||
return dict(self._staff_by_name.get((name or "").strip()) or {})
|
||||
|
||||
def bind_collab_group(
|
||||
self,
|
||||
*,
|
||||
work_order_no: str,
|
||||
chat_id: str,
|
||||
member_ids: list[str],
|
||||
product_names: list[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""记下协同群,不改六态。"""
|
||||
_ = product_names
|
||||
with self._lock:
|
||||
ticket = self._tickets.get(work_order_no)
|
||||
if ticket is None:
|
||||
return {"ok": False, "error": "ticket_not_found"}
|
||||
ticket.collab_chat_id = (chat_id or "").strip()
|
||||
ticket.collab_member_ids = [str(x) for x in member_ids]
|
||||
return {"ok": True, "chat_id": ticket.collab_chat_id}
|
||||
|
||||
def patch_collab_facts(
|
||||
self,
|
||||
*,
|
||||
work_order_no: str,
|
||||
facts: dict[str, str],
|
||||
sender_id: str = "",
|
||||
) -> dict[str, Any]:
|
||||
"""合并协同字段。不改六态、不写报价版本。"""
|
||||
_ = sender_id
|
||||
with self._lock:
|
||||
ticket = self._tickets.get(work_order_no)
|
||||
if ticket is None:
|
||||
return {"ok": False, "error": "ticket_not_found"}
|
||||
ticket.collab_facts.update(
|
||||
{k: v for k, v in (facts or {}).items() if str(v or "").strip()}
|
||||
)
|
||||
return {"ok": True, "facts": dict(ticket.collab_facts)}
|
||||
|
||||
def find_by_collab_chat(self, *, chat_id: str) -> MemoryTicket | None:
|
||||
"""用工微群 chat_id 找回工单。"""
|
||||
raw = (chat_id or "").strip()
|
||||
if not raw:
|
||||
return None
|
||||
with self._lock:
|
||||
for ticket in self._tickets.values():
|
||||
if ticket.collab_chat_id == raw:
|
||||
return ticket
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user