角色业务范围收紧工单与模板;报价日期不当费用;未选舱不迁已报价;另开一票可清违禁 hold

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-10-15 21:00:01 +08:00
co-authored by Cursor
parent a0c705683f
commit 277cdc1c28
39 changed files with 2083 additions and 217 deletions
+40 -6
View File
@@ -320,7 +320,10 @@ def send_air_brief_and_sheet(
必须先发出工单信息,模板等摘要发出后再发,禁止后台线程抢先发文件。
填表报价必须带上 TMS 总销售价:群票上的 latest quote 常只有增点费/拆板费明细,
若直接拿去出单,预估费用会落成明细加总(如 200)而不是 salePrice(如 7918.25)。
调用方刚写回的询价字段(如报价日期)优先,不能被 hydrate 旧快照盖掉。
"""
incoming_view = ticket_view(ticket) if ticket is not None else {}
incoming_facts = dict(incoming_view.get("facts") or {})
ticket = hydrate_ticket(flow, ticket)
view = ticket_view(ticket)
no = ticket_no(ticket)
@@ -328,6 +331,12 @@ def send_air_brief_and_sheet(
if room and not view.get("collab_chat_id"):
view["collab_chat_id"] = room
facts = dict(view.get("facts") or {})
# 群里刚补的报价日期等:有值就盖到摘要用的 facts 上。
for key, raw in incoming_facts.items():
val = str(raw or "").strip()
if key and val:
facts[key] = val
view["facts"] = facts
collab = _seed_air_activate_collab(flow, view)
view["collab_facts"] = collab
view_q = dict(view.get("quote") or {})
@@ -617,11 +626,19 @@ def resolve_cabin_ticket(
def apply_air_fields(*, flow: Any, ticket: object, text: str, sender_id: str, chat_id: str) -> str:
"""
补含电/含磁。对齐海运:写入后重发群摘要 + 空运模板,不当一句「已记下」。
补含电/含磁,以及报价日期。
含电含磁走协同字段;报价日期写入询价 facts(主账 quote_date 列)。
货好时间、贸易条款、客户名称不在群里补(产品口径)。
写入后重发群摘要 + 空运模板,不当一句「已记下」。
"""
from agent.llm.extract_text import labeled_quote_date
ticket = hydrate_ticket(flow, ticket)
no = ticket_no(ticket)
client = group_client_of(flow)
fields = extract_air_collab_fields(text, allow_b=True)
quote_date = labeled_quote_date(text)
if not is_air_ticket(ticket):
send_group_text(client, chat_id, copy.air_need_work_order())
return "need_work_order"
@@ -631,11 +648,23 @@ def apply_air_fields(*, flow: Any, ticket: object, text: str, sender_id: str, ch
if not can_fill_fields(flow, ticket, sender_id):
send_group_text(client, chat_id, copy.air_fields_denied())
return "fields_denied"
fields = extract_air_collab_fields(text, allow_b=True)
if not fields:
if not fields and not quote_date:
send_group_text(client, chat_id, copy.air_fields_not_recognized())
return "fields_empty"
flow.ledger.patch_collab_facts(work_order_no=no, facts=fields, sender_id=sender_id)
if fields:
flow.ledger.patch_collab_facts(work_order_no=no, facts=fields, sender_id=sender_id)
write_ok = True
if quote_date:
# 只打补丁键:主账侧合并,避免整包 facts 误伤。
patch = {"报价日期": quote_date}
writer = getattr(flow.ledger, "update_facts", None)
if callable(writer):
try:
out = writer(work_order_no=no, facts=patch) or {}
write_ok = bool(out.get("ok", True))
except Exception:
write_ok = False
logger.exception("空运群写报价日期失败 wo=%s", no)
# 必须回读本单。按群再查会拿到同群更早的陆运/海运旧单(测服 WO202609230007)。
getter = getattr(flow.ledger, "get_ticket", None)
reloaded = getter(work_order_no=no) if callable(getter) else None
@@ -644,6 +673,10 @@ def apply_air_fields(*, flow: Any, ticket: object, text: str, sender_id: str, ch
collab = dict(view.get("collab_facts") or {})
collab.update(fields)
view["collab_facts"] = collab
if quote_date:
facts_view = dict(view.get("facts") or {})
facts_view["报价日期"] = quote_date
view["facts"] = facts_view
view["collab_chat_id"] = chat_id
send_air_brief_and_sheet(flow=flow, ticket=view, chat_id=chat_id)
return "fields_saved"
@@ -1012,8 +1045,9 @@ def apply_air_lock(
航线锁舱。只有工单已是「已成交」才打主账。
还没回复成交:点名销售先确认。协商中、未成交各用一句,不催销售改口。
舱位编号、航司、航班号没齐时只请补全,不调 TMS 锁舱,不标系统异常。
三项齐了(话里写的优先,报价里已有的可以补上)才打主账锁舱。
话里写的优先,报价里已有的舱位可以补上;补不上也要打主账——
主账会合并历史 TMS / 现拉运价库(产品报价无舱位时不能在智能体侧先挡住)。
主账回 lock_params_incomplete 再群里补问,不标系统异常。
上次锁舱失败标过暂停的,航线再锁仍要打 TMS。锁成功不再推成交跟进。
"""
if not is_airline(flow, sender_id):