Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20c27ae293 | ||
|
|
6140eaf932 |
@@ -11,6 +11,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Optional
|
||||
|
||||
from agent.ledger.http_ledger import HttpLedger
|
||||
@@ -41,6 +42,8 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
||||
"""
|
||||
|
||||
bookmark_kind = "sea"
|
||||
# 价格明细文字发出后稍等再发卡,避免企微先画出卡片。
|
||||
_detail_before_card_gap_sec = 0.8
|
||||
|
||||
def __init__(self, ledger: Any = None, group_client: Any = None) -> None:
|
||||
super().__init__(ledger=ledger)
|
||||
@@ -376,16 +379,18 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
||||
business_line: str = "",
|
||||
) -> None:
|
||||
"""
|
||||
先发完整价格明细文本,再发带按钮的卡。
|
||||
先发完整价格明细文本,等它真正发出,再发带按钮的卡。
|
||||
|
||||
企微只发卡时,引用条超长会被截断;文本不受限。
|
||||
测服 WO202609200020:文字和卡同一拍入队,销售先看到卡,
|
||||
「详见上一条明细」对不上。只等这一条出站,不锁其它工单。
|
||||
"""
|
||||
line = (business_line or getattr(self, "business_line", "") or "SEA").upper()
|
||||
if line not in {"SEA", "LAND"}:
|
||||
line = "SEA"
|
||||
src = dict(quote or {})
|
||||
src["business_line"] = line
|
||||
reply(
|
||||
out_id = reply(
|
||||
copy.sea_quote_detail_text(
|
||||
work_order_no=work_order_no,
|
||||
quote=src,
|
||||
@@ -394,7 +399,13 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
||||
status=status,
|
||||
business_line=line,
|
||||
)
|
||||
) or ""
|
||||
self._wait_outbound_sent(
|
||||
str(out_id), work_order_no=work_order_no, what="价格明细文本"
|
||||
)
|
||||
gap = float(getattr(self, "_detail_before_card_gap_sec", 0.8) or 0)
|
||||
if out_id and gap > 0:
|
||||
time.sleep(gap)
|
||||
reply(
|
||||
copy.sea_tms_hit_card(
|
||||
work_order_no=work_order_no,
|
||||
|
||||
@@ -535,6 +535,37 @@ class SeaTmsCardTests(unittest.TestCase):
|
||||
self.assertIn("时效:1天", sub)
|
||||
self.assertIn("报价有效期:2026-09-30", sub)
|
||||
|
||||
def test_sea_hit_sends_detail_text_before_card(self) -> None:
|
||||
"""
|
||||
有价必须先发出价格明细文字,再发卡。
|
||||
|
||||
测服 WO202609200020:卡上写「详见上一条明细」,销售却先看到卡。
|
||||
"""
|
||||
steps: list[str] = []
|
||||
|
||||
def reply(text: str, extra=None) -> str:
|
||||
kind = "card" if extra else "text"
|
||||
steps.append(kind)
|
||||
return f"out-{len(steps)}"
|
||||
|
||||
def wait(out_id: str, *, work_order_no: str, what: str) -> None:
|
||||
steps.append(f"wait:{out_id}")
|
||||
|
||||
self.flow._wait_outbound_sent = wait # type: ignore[method-assign]
|
||||
self.flow._detail_before_card_gap_sec = 0.0
|
||||
self.flow._emit_sea_hit(
|
||||
reply,
|
||||
work_order_no="WO202609200020",
|
||||
quote={
|
||||
"total": "14298.53",
|
||||
"currency": "CNY",
|
||||
"source_label": "TMS标准报价",
|
||||
"fee_lines": [{"name": "操作费", "amount": "1220", "currency": "CNY"}],
|
||||
},
|
||||
)
|
||||
self.assertEqual(steps[:3], ["text", "wait:out-1", "card"])
|
||||
self.assertIn("card", steps)
|
||||
|
||||
def test_sea_hit_card_has_price_and_two_buttons(self) -> None:
|
||||
phase = self.flow.on_text(
|
||||
sender_id="s1",
|
||||
|
||||
+7
-2
@@ -93,7 +93,11 @@ public class InquiryTmsQueryService {
|
||||
if (items != null) {
|
||||
segment.put("containerItems", items);
|
||||
}
|
||||
segment.put("tradeTerms", first(facts.get("贸易条款"), facts.get("trade_terms"), facts.get("tradeTerms")));
|
||||
segment.put("transportTerms", first(
|
||||
facts.get("贸易条款"),
|
||||
facts.get("trade_terms"),
|
||||
facts.get("transportTerms"),
|
||||
facts.get("tradeTerms")));
|
||||
} else if ("LAND".equals(line)) {
|
||||
// 陆运不套空运包装/件数;分类和中港装载只在工单,mapper 不会放进出站。
|
||||
segment.putAll(TmsLandSegmentMapper.build(facts));
|
||||
@@ -654,7 +658,8 @@ public class InquiryTmsQueryService {
|
||||
if (blank(facts.get("箱型箱量"))) {
|
||||
missing.add("箱型箱量");
|
||||
}
|
||||
if (blank(facts.get("贸易条款")) && blank(facts.get("trade_terms")) && blank(facts.get("tradeTerms"))) {
|
||||
if (blank(facts.get("贸易条款")) && blank(facts.get("trade_terms"))
|
||||
&& blank(facts.get("transportTerms")) && blank(facts.get("tradeTerms"))) {
|
||||
missing.add("贸易条款");
|
||||
}
|
||||
return missing;
|
||||
|
||||
+2
-1
@@ -113,7 +113,8 @@ public final class TmsV2RequestMapper {
|
||||
}
|
||||
put(target, "containerType", type);
|
||||
put(target, "containerQty", asInteger(qty));
|
||||
put(target, "tradeTerms", first(source, "tradeTerms", "贸易条款", "trade_terms"));
|
||||
// TMS 查询字段是 transportTerms。工单/卡片仍写「贸易条款」,这里只改出站名。
|
||||
put(target, "transportTerms", first(source, "transportTerms", "tradeTerms", "贸易条款", "trade_terms"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-3
@@ -132,7 +132,7 @@ public class TmsV2RequestMapperTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seaSendsTradeTermsOmitsClass() {
|
||||
public void seaSendsTransportTermsOmitsClass() {
|
||||
Map<String, Object> seg = new HashMap<>();
|
||||
seg.put("origin", "上海");
|
||||
seg.put("destination", "洛杉矶");
|
||||
@@ -140,7 +140,8 @@ public class TmsV2RequestMapperTest {
|
||||
seg.put("cargoQty", "20吨");
|
||||
seg.put("loadType", "整柜");
|
||||
seg.put("containerSpec", "1x40HQ");
|
||||
seg.put("tradeTerms", "FOB");
|
||||
// 测服 WO202609200019:工单仍是贸易条款,出站必须写成 transportTerms,不能写 tradeTerms。
|
||||
seg.put("贸易条款", "4");
|
||||
seg.put("运输分类", "Port to Port");
|
||||
Map<String, Object> internal = new HashMap<>();
|
||||
internal.put("transportMode", "SEA");
|
||||
@@ -149,7 +150,9 @@ public class TmsV2RequestMapperTest {
|
||||
Map<String, Object> body = TmsV2RequestMapper.quote(internal);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> segs = (List<Map<String, Object>>) body.get("segments");
|
||||
assertEquals("FOB", segs.get(0).get("tradeTerms"));
|
||||
assertEquals("4", segs.get(0).get("transportTerms"));
|
||||
assertFalse(segs.get(0).containsKey("tradeTerms"));
|
||||
assertFalse(segs.get(0).containsKey("贸易条款"));
|
||||
assertFalse(segs.get(0).containsKey("运输分类"));
|
||||
assertFalse(body.containsKey("运输分类"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user