空运附件报价锁舱前先提示航线改发纯文字报价,避免附件金额拆不出导致锁舱失败。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-10-13 14:56:09 +08:00
co-authored by Cursor
parent 0b2f0b76c4
commit dda210c219
3 changed files with 105 additions and 3 deletions
+43 -2
View File
@@ -699,6 +699,9 @@ def apply_air_file(
from agent.policy.event_exception import clear_wait
clear_wait(flow.ledger, work_order_no=no)
# 附件不拆金额:锁舱常失败。产品口径改为提示航线改发纯文字报价后再锁。
tip = copy.air_attachment_quote_lock_tip(work_order_no=no)
send_group_text(client, chat_id, tip)
emit_group_deal(flow=flow, ticket=view, quote=quote)
return "airline_file_quote"
@@ -952,7 +955,8 @@ def apply_air_lock(
return "no_quote"
# 主账在出站前发现三项不齐:只请补,不当 TMS 故障,不通知 IT。
reason_text = str(out.get("failReason") or "")
if str(out.get("error") or "") == "lock_params_incomplete" or "锁舱参数不完整" in reason_text:
err_code = str(out.get("error") or "")
if err_code == "lock_params_incomplete" or "锁舱参数不完整" in reason_text:
labels = missing or _labels_from_incomplete_reason(reason_text)
_remember_partial_lock(
flow,
@@ -964,8 +968,45 @@ def apply_air_lock(
)
send_group_text(group_client_of(flow), chat_id, copy.air_lock_need_params(labels))
return "wait_lock_params"
# 附件/手工报价、运价库无舱位:引导纯文字报价,不算系统异常、不通知 IT。
src_label = str((quote or {}).get("source_label") or (quote or {}).get("source") or "")
need_text_quote = (
err_code in {"lock_incomplete", "lock_fail"}
and (
"航线附件报价" in src_label
or "附件" in src_label
or any(
x in reason_text
for x in (
"手工报价",
"无匹配舱位",
"无匹配报价",
"运价库无匹配",
"标准运价库无匹配",
)
)
)
)
if need_text_quote:
send_group_text(
group_client_of(flow),
chat_id,
copy.air_lock_need_text_quote(
work_order_no=no, operator=name, reason=reason_text
),
)
return "need_text_quote"
if err_code == "lock_incomplete":
send_group_text(
group_client_of(flow),
chat_id,
copy.air_lock_incomplete(
work_order_no=no, operator=name, reason=reason_text
),
)
return "lock_incomplete"
# 多段是产品限制,不是 TMS 故障。只回原因,不标系统异常、不暂停工单。
if str(out.get("error") or "") == "multi_no_lock":
if err_code == "multi_no_lock":
send_group_text(
group_client_of(flow),
chat_id,
+60 -1
View File
@@ -3273,13 +3273,72 @@ def air_lock_fail(*, work_order_no: str, operator: str, reason: str) -> str:
)
def air_text_quote_for_lock_example(work_order_no: str = "") -> str:
"""
需要锁舱时,请航线用纯文字报价的示例句。
附件报价不拆金额、常锁不成;产品口径改为引导航线改发文字价。
"""
no = (work_order_no or "").strip() or "工单号"
return f"{no},空运段,空运费 单价CNY99.00,数量100 ,金额CNY9900.00@询价小助手"
def air_attachment_quote_lock_tip(*, work_order_no: str) -> str:
"""
航线刚发完 Excel/PDF 附件报价后的提示。
附件只存档、不拆费用;若还要锁舱,必须再发一版纯文字报价。
"""
no = (work_order_no or "").strip() or "WO"
example = air_text_quote_for_lock_example(no)
return (
f"工单{no}附件报价已收。\n"
"如需锁舱,请航线人员改用纯文字报价(不要只发附件),例如:\n"
f"{example}"
)
def air_lock_need_text_quote(*, work_order_no: str, operator: str = "", reason: str = "") -> str:
"""
附件/手工报价锁舱走不通时:引导航线先发文字报价,不算系统异常、不通知 IT。
故意不回主账/TMS 原文「原因」(如 AIR-OPT / 运价库无匹配),只留文字报价指引。
reason 参数保留兼容旧调用,出站话术不用。
"""
_ = reason
no = (work_order_no or "").strip() or "WO"
who = (operator or "").strip()
lines = [f"工单{no} 还不能锁舱。"]
if who:
lines.append(f"操作人:{who}")
lines.append("如需锁舱,请航线人员先发纯文字报价,例如:")
lines.append(air_text_quote_for_lock_example(no))
lines.append("文字报价写入后再发锁舱口令(可带舱位、航司、航班号)。")
return "\n".join(lines)
def air_lock_incomplete(*, work_order_no: str, operator: str, reason: str = "") -> str:
"""
锁舱参数不齐(纯手工价、运价库无舱位等):业务补问,不算系统异常,不惊动 IT。
航线按示例回舱位+航司+航班后即可再锁。
附件/无舱位场景改引导纯文字报价;有 TMS 舱位时仍可补 AIR-OPT 三项。
"""
why = (reason or "").strip()
blob = why
if any(
x in blob
for x in (
"手工报价",
"附件",
"无匹配舱位",
"无匹配报价",
"运价库无匹配",
"标准运价库无匹配",
)
):
return air_lock_need_text_quote(
work_order_no=work_order_no, operator=operator, reason=why
)
lines = [
f"工单{work_order_no} 还不能锁舱。",
f"操作人:{operator}",
@@ -1476,6 +1476,8 @@ class AirGroupCollabTests(unittest.TestCase):
self.assertEqual(ticket.quote.get("quoteId"), "QT-AIR-1")
self.assertEqual(ticket.status, "已报价")
self.assertTrue(any(f"工单{wo}的1个附件已保存到后台工单" in t for t in self._texts()))
self.assertTrue(any("如需锁舱,请航线人员改用纯文字报价" in t for t in self._texts()))
self.assertTrue(any("空运费 单价CNY99.00" in t for t in self._texts()))
self.assertTrue(any("报价单已发送,请确认成交状态" in t for t in self._texts()))
def test_reactivate_after_airline_file_is_deal_not_blank_sheet(self) -> None: