落地海运协同群:测服靠会话存档听群话,报价单能带上协同字段。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-15 18:12:45 +08:00
co-authored by Cursor
parent 235ecd8c83
commit 12219cacb1
39 changed files with 4436 additions and 256 deletions
+100 -18
View File
@@ -13,7 +13,12 @@ import time
import uuid
from typing import Any, Iterable
from agent.schema.field_validate import AIR_REQUIRED_FIELDS, SEA_COLLAB_FIELDS, SEA_REQUIRED_FIELDS
from agent.schema.field_validate import (
AIR_REQUIRED_FIELDS,
SEA_COLLAB_DISPLAY,
SEA_COLLAB_FIELDS,
SEA_REQUIRED_FIELDS,
)
# 空运补问:与合同 8 项一致。报价日期默认当天,不进待补充。
_AIR_EXAMPLES = {
@@ -379,6 +384,20 @@ def work_order_from_card_meta(
return ""
def deal_card_version(card_meta: dict[str, Any] | None = None, *, text: str = "") -> int:
"""从成交卡 EventKey 取出 vN;没有则 0(旧私聊卡)。"""
src = card_meta or {}
for raw in (
str(src.get("event_key") or ""),
str(src.get("task_id") or ""),
text or "",
):
hit = re.search(r":v(\d+)\s*$", raw)
if hit:
return int(hit.group(1))
return 0
def _work_order_in_token(raw: str) -> str:
"""从 TaskId / EventKey 里抽出 WO 工单号;没有则空。"""
text = (raw or "").strip()
@@ -505,19 +524,18 @@ def sea_quote_detail_text(
history_work_order_no: str = "",
status: str = "已报价",
) -> str:
"""海运完整价格明细文本。不受卡片引用条字数限制。"""
"""
海运完整价格明细文本:来源、工单号、费用行、合计。
时效、报价有效期只在卡片上出现,这里不写,避免和卡重复。
不受卡片引用条字数限制。first_or_same 只兼容调用方。
"""
_ = first_or_same, history_work_order_no, status
src = dict(quote or {})
source = str(src.get("source_label") or "TMS标准报价").replace(" ", "")
lines = [source, f"{work_order_no}价格明细"]
lines.extend(
quote_detail_lines(
src,
first_or_same=first_or_same,
history_work_order_no=history_work_order_no,
status=status,
)
return "\n".join(
[source, f"{work_order_no}价格明细", quote_area_fee_text(src)]
)
return "\n".join(lines)
def collab_hint(work_order_no: str) -> str:
@@ -957,6 +975,11 @@ def sea_group_no_staff() -> str:
return "没有可拉的产品人员。请先在后台「员工管理」维护本线路的海运产品岗后再试。"
def group_sheet_send_fail() -> str:
"""模板已填好但没发进群。"""
return "报价单已生成,发到群里失败,请稍后重试。"
def sea_group_create_fail(*, reason: str = "") -> str:
"""建群失败,按钮保持可点。"""
extra = (reason or "").strip()
@@ -978,7 +1001,8 @@ def sea_group_brief(
"""
群里自动摘要:@销售 @产品 + 工单号 + 询价字段 + 协同字段 + TMS 报价。
协同没填写 -。无价写明暂无匹配报价。
三段之间空一行,方便群里扫。协同没填写 -。
HS编码对外写成商品海关编码。无价只写「TMS 暂无匹配报价」。
"""
no = (work_order_no or "").strip()
sales = (sales_name or "").strip() or "销售"
@@ -988,16 +1012,19 @@ def sea_group_brief(
lines.extend(
inquiry_field_lines(work_order_no=no, facts=facts, transport_mode="海运")[1:]
)
lines.append("")
lines.append("协同补充字段:")
collab = dict(collab_facts or {})
for key in SEA_COLLAB_FIELDS:
val = (collab.get(key) or "").strip() or "-"
lines.append(f"{key}:{val}")
label = SEA_COLLAB_DISPLAY.get(key, key)
val = (collab.get(key) or collab.get(label) or "").strip() or "-"
lines.append(f"{label}:{val}")
lines.append("")
lines.append("TMS报价:")
if has_price and quote:
lines.extend(quote_detail_lines(quote))
else:
lines.append("TMS 暂无匹配报价 / 未查到合适报价")
lines.append("TMS 暂无匹配报价")
return "\n".join(lines)
@@ -1244,6 +1271,7 @@ def deal_wecom_payload(
work_order_no: str,
quote: dict[str, Any] | None = None,
facts: dict[str, str] | None = None,
card_version: int = 0,
) -> dict[str, Any]:
"""
成交跟进卡:对齐现网 button_interaction。
@@ -1254,6 +1282,8 @@ def deal_wecom_payload(
src = dict(quote or {})
source = str(src.get("source_label") or "TMS标准报价").replace(" ", "")
no = (work_order_no or "").strip()
ver = int(card_version or 0)
suffix = f":v{ver}" if ver > 0 else ""
return {
"msgtype": "template_card",
"template_card": {
@@ -1270,23 +1300,75 @@ def deal_wecom_payload(
{
"text": BTN_DEAL,
"style": 1,
"key": f"{BTN_DEAL_KEY}:{no}",
"key": f"{BTN_DEAL_KEY}:{no}{suffix}",
},
{
"text": BTN_LOST,
"style": 1,
"key": f"{BTN_LOST_KEY}:{no}",
"key": f"{BTN_LOST_KEY}:{no}{suffix}",
},
{
"text": BTN_NEGOTIATE,
"style": 1,
"key": f"{BTN_NEGOTIATE_KEY}:{no}",
"key": f"{BTN_NEGOTIATE_KEY}:{no}{suffix}",
},
],
},
}
def ticket_locked(*, work_order_no: str, status: str) -> str:
"""已成交/未成交/已关闭后,群里改字段或再报价。"""
no = (work_order_no or "").strip() or "该工单"
st = (status or "").strip() or "已关闭"
return f"工单{no}{st},不能修改工单信息"
def ticket_locked_file(*, work_order_no: str, status: str) -> str:
"""终态工单再发 Excel/PDF。"""
no = (work_order_no or "").strip() or "该工单"
st = (status or "").strip() or "已关闭"
return f"工单{no}{st},不能再上传附件"
def group_only_excel_pdf() -> str:
return "群里只收 Excel 和 PDF,其它文件不会存入工单"
def group_quote_not_recognized() -> str:
return "没有识别到报价,请产品再发一次价格或 Excel/PDF"
def group_fields_not_recognized() -> str:
return "听到了。请直接说贸易条款、货好时间、商品海关编码、是否含油/含电/含磁。"
def group_deal_stale(*, work_order_no: str) -> str:
"""点了上一版成交卡。"""
no = (work_order_no or "").strip()
return f"这张成交确认已过期。工单{no}已有更新的报价,请点击群里最新一条成交跟进。"
def group_deal_sales_only() -> str:
return "只有负责该工单的销售可以确认成交状态。"
def group_dissolved(*, work_order_no: str) -> str:
no = (work_order_no or "").strip()
return f"群已解散。工单{no}协同已结束。"
def group_cannot_recreate(*, work_order_no: str) -> str:
"""解散后再点拉群。"""
no = (work_order_no or "").strip() or "该工单"
return f"工单{no}协同已结束,不能再拉群。"
def group_deal_mention(*, sales_name: str, work_order_no: str) -> str:
name = (sales_name or "").strip() or "销售"
return f"@{name}\n{deal_card(work_order_no=work_order_no)}"
def deal_ack(outcome: str, work_order_no: str) -> str:
if outcome == "已成交":
return f"已记录为「已成交」。工单 {work_order_no} 已关闭智能体跟进。"