@@ -249,7 +249,8 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
|||||||
) or ""
|
) or ""
|
||||||
self._wait_inquiry_sent(str(out_id), work_order_no=no)
|
self._wait_inquiry_sent(str(out_id), work_order_no=no)
|
||||||
|
|
||||||
tms = self._ledger.query_tms(work_order_no=no, facts=facts, business_line="SEA")
|
tms_facts = dict(assemble_sea_query(facts).get("tms_facts") or facts)
|
||||||
|
tms = self._ledger.query_tms(work_order_no=no, facts=tms_facts, business_line="SEA")
|
||||||
sess = FlowSession(
|
sess = FlowSession(
|
||||||
sender_id=sender_id,
|
sender_id=sender_id,
|
||||||
thread_id=sender_id,
|
thread_id=sender_id,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
海运查价参数:对照 TMS V2 /quote/v2/query 是否带全。
|
海运查价参数:对照 TMS V2 /quote/v2/query 是否带全。
|
||||||
|
|
||||||
本文件职责:询价卡片出现前,确认海运七项能拼出查价正文。
|
本文件职责:询价卡片出现前,确认海运十项里查出站项能拼出查价正文。
|
||||||
港口只带名称,不做港口字典对应;箱型箱量保持原文字段,containerItems 只透传模型投影。
|
港口只带名称,不做港口字典对应;箱型箱量保持原文字段,containerItems 只透传模型投影。
|
||||||
|
贸易条款必带出站;运输分类只留在工单 facts,不进 tms_facts。
|
||||||
禁止:本地拆箱型;因对不上港口字典跳过 TMS;改六态。
|
禁止:本地拆箱型;因对不上港口字典跳过 TMS;改六态。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -62,6 +63,8 @@ def assemble_sea_query(facts: dict[str, Any] | None) -> dict[str, Any]:
|
|||||||
load = _load_type(str(src.get("整柜或拼柜") or ""))
|
load = _load_type(str(src.get("整柜或拼柜") or ""))
|
||||||
box = str(src.get("箱型箱量") or "").strip()
|
box = str(src.get("箱型箱量") or "").strip()
|
||||||
quote_date = str(src.get("报价日期") or "").strip()
|
quote_date = str(src.get("报价日期") or "").strip()
|
||||||
|
trade = str(src.get("贸易条款") or src.get("trade_terms") or src.get("tradeTerms") or "").strip()
|
||||||
|
klass = str(src.get("运输分类") or "").strip()
|
||||||
|
|
||||||
missing: list[str] = []
|
missing: list[str] = []
|
||||||
missing_display: list[str] = []
|
missing_display: list[str] = []
|
||||||
@@ -72,6 +75,7 @@ def assemble_sea_query(facts: dict[str, Any] | None) -> dict[str, Any]:
|
|||||||
("货量", "货物数量", qty),
|
("货量", "货物数量", qty),
|
||||||
("整柜或拼柜", "整柜或拼柜", load),
|
("整柜或拼柜", "整柜或拼柜", load),
|
||||||
("箱型箱量", "箱型箱量", box),
|
("箱型箱量", "箱型箱量", box),
|
||||||
|
("贸易条款", "贸易条款", trade),
|
||||||
("报价日期", "报价日期", quote_date),
|
("报价日期", "报价日期", quote_date),
|
||||||
)
|
)
|
||||||
for key, label, val in pairs:
|
for key, label, val in pairs:
|
||||||
@@ -93,12 +97,18 @@ def assemble_sea_query(facts: dict[str, Any] | None) -> dict[str, Any]:
|
|||||||
out_facts["整柜或拼柜"] = load
|
out_facts["整柜或拼柜"] = load
|
||||||
if box:
|
if box:
|
||||||
out_facts["箱型箱量"] = box
|
out_facts["箱型箱量"] = box
|
||||||
|
if trade:
|
||||||
|
out_facts["贸易条款"] = trade
|
||||||
|
if klass:
|
||||||
|
out_facts["运输分类"] = klass
|
||||||
if quote_date:
|
if quote_date:
|
||||||
out_facts["报价日期"] = quote_date
|
out_facts["报价日期"] = quote_date
|
||||||
items = parse_container_items(src)
|
items = parse_container_items(src)
|
||||||
if items:
|
if items:
|
||||||
out_facts["containerItems"] = items
|
out_facts["containerItems"] = items
|
||||||
|
|
||||||
|
tms_facts = dict(out_facts)
|
||||||
|
tms_facts.pop("运输分类", None)
|
||||||
logger.info(
|
logger.info(
|
||||||
"tms.sea.assemble ok=%s origin=%s dest=%s load=%s missing=%s",
|
"tms.sea.assemble ok=%s origin=%s dest=%s load=%s missing=%s",
|
||||||
ok,
|
ok,
|
||||||
@@ -112,4 +122,5 @@ def assemble_sea_query(facts: dict[str, Any] | None) -> dict[str, Any]:
|
|||||||
"missing": missing,
|
"missing": missing,
|
||||||
"missing_display": missing_display,
|
"missing_display": missing_display,
|
||||||
"facts": out_facts,
|
"facts": out_facts,
|
||||||
|
"tms_facts": tms_facts,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -273,11 +273,33 @@ class SeaTmsCardTests(unittest.TestCase):
|
|||||||
self.extras.append(dict(extra) if extra else {})
|
self.extras.append(dict(extra) if extra else {})
|
||||||
|
|
||||||
def test_assemble_sea_query_does_not_need_air_measures(self) -> None:
|
def test_assemble_sea_query_does_not_need_air_measures(self) -> None:
|
||||||
ready = assemble_sea_query(COMPLETE_SEA)
|
ready = assemble_sea_query({**COMPLETE_SEA, "贸易条款": "FOB", "运输分类": "Port to Port"})
|
||||||
self.assertTrue(ready["ok"])
|
self.assertTrue(ready["ok"])
|
||||||
self.assertEqual(ready["missing"], [])
|
self.assertEqual(ready["missing"], [])
|
||||||
self.assertNotIn("件数", ready.get("missing") or [])
|
self.assertNotIn("件数", ready.get("missing") or [])
|
||||||
|
|
||||||
|
def test_assemble_requires_trade_terms_not_class(self) -> None:
|
||||||
|
src = {
|
||||||
|
"起运港": "上海",
|
||||||
|
"目的港": "洛杉矶",
|
||||||
|
"品名": "普货",
|
||||||
|
"货量": "20吨",
|
||||||
|
"整柜或拼柜": "整柜",
|
||||||
|
"箱型箱量": "1x40HQ",
|
||||||
|
"运输分类": "Port to Port",
|
||||||
|
}
|
||||||
|
missing = assemble_sea_query(src)
|
||||||
|
self.assertFalse(missing["ok"])
|
||||||
|
self.assertIn("贸易条款", missing["missing"])
|
||||||
|
self.assertNotIn("运输分类", missing["missing"])
|
||||||
|
|
||||||
|
src["贸易条款"] = "FOB"
|
||||||
|
ready = assemble_sea_query(src)
|
||||||
|
self.assertTrue(ready["ok"])
|
||||||
|
self.assertEqual(ready["tms_facts"]["贸易条款"], "FOB")
|
||||||
|
self.assertNotIn("运输分类", ready["tms_facts"])
|
||||||
|
self.assertEqual(ready["facts"]["运输分类"], "Port to Port")
|
||||||
|
|
||||||
def test_long_fee_lines_go_to_text_not_clipped_card(self) -> None:
|
def test_long_fee_lines_go_to_text_not_clipped_card(self) -> None:
|
||||||
"""海运多项费用:完整明细必须在文本里,卡片引用条不得半截。"""
|
"""海运多项费用:完整明细必须在文本里,卡片引用条不得半截。"""
|
||||||
long_quote = {
|
long_quote = {
|
||||||
|
|||||||
+6
-2
@@ -93,6 +93,7 @@ public class InquiryTmsQueryService {
|
|||||||
if (items != null) {
|
if (items != null) {
|
||||||
segment.put("containerItems", items);
|
segment.put("containerItems", items);
|
||||||
}
|
}
|
||||||
|
segment.put("tradeTerms", first(facts.get("贸易条款"), facts.get("trade_terms"), facts.get("tradeTerms")));
|
||||||
} else if ("LAND".equals(line)) {
|
} else if ("LAND".equals(line)) {
|
||||||
// 陆运不套空运包装/件数;分类和中港装载只在工单,mapper 不会放进出站。
|
// 陆运不套空运包装/件数;分类和中港装载只在工单,mapper 不会放进出站。
|
||||||
segment.putAll(TmsLandSegmentMapper.build(facts));
|
segment.putAll(TmsLandSegmentMapper.build(facts));
|
||||||
@@ -623,8 +624,8 @@ public class InquiryTmsQueryService {
|
|||||||
* 对不上机场也要查,TMS 自己回无价或 1003。
|
* 对不上机场也要查,TMS 自己回无价或 1003。
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 海运出站必填:工单号、报价日期、起运/目的名称、品名、货量、整柜或拼柜、箱型箱量。
|
* 海运出站必填:工单号、报价日期、起运/目的名称、品名、货量、整柜或拼柜、箱型箱量、贸易条款。
|
||||||
* 不要求机场三字码、件数、重量、体积、包装。
|
* 不要求机场三字码、件数、重量、体积、包装;运输分类不进出站。
|
||||||
*/
|
*/
|
||||||
private static List<String> missingSeaQuery(String workOrderNo, String quoteDate,
|
private static List<String> missingSeaQuery(String workOrderNo, String quoteDate,
|
||||||
Map<String, Object> segment, Map<String, Object> facts) {
|
Map<String, Object> segment, Map<String, Object> facts) {
|
||||||
@@ -649,6 +650,9 @@ public class InquiryTmsQueryService {
|
|||||||
if (blank(facts.get("箱型箱量"))) {
|
if (blank(facts.get("箱型箱量"))) {
|
||||||
missing.add("箱型箱量");
|
missing.add("箱型箱量");
|
||||||
}
|
}
|
||||||
|
if (blank(facts.get("贸易条款")) && blank(facts.get("trade_terms")) && blank(facts.get("tradeTerms"))) {
|
||||||
|
missing.add("贸易条款");
|
||||||
|
}
|
||||||
return missing;
|
return missing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -113,6 +113,7 @@ public final class TmsV2RequestMapper {
|
|||||||
}
|
}
|
||||||
put(target, "containerType", type);
|
put(target, "containerType", type);
|
||||||
put(target, "containerQty", asInteger(qty));
|
put(target, "containerQty", asInteger(qty));
|
||||||
|
put(target, "tradeTerms", first(source, "tradeTerms", "贸易条款", "trade_terms"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+23
@@ -130,4 +130,27 @@ public class TmsV2RequestMapperTest {
|
|||||||
assertFalse(segs.get(0).containsKey("landRouteCategory"));
|
assertFalse(segs.get(0).containsKey("landRouteCategory"));
|
||||||
assertFalse(segs.get(0).containsKey("landClass"));
|
assertFalse(segs.get(0).containsKey("landClass"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seaSendsTradeTermsOmitsClass() {
|
||||||
|
Map<String, Object> seg = new HashMap<>();
|
||||||
|
seg.put("origin", "上海");
|
||||||
|
seg.put("destination", "洛杉矶");
|
||||||
|
seg.put("cargoName", "普货");
|
||||||
|
seg.put("cargoQty", "20吨");
|
||||||
|
seg.put("loadType", "整柜");
|
||||||
|
seg.put("containerSpec", "1x40HQ");
|
||||||
|
seg.put("tradeTerms", "FOB");
|
||||||
|
seg.put("运输分类", "Port to Port");
|
||||||
|
Map<String, Object> internal = new HashMap<>();
|
||||||
|
internal.put("transportMode", "SEA");
|
||||||
|
internal.put("quoteDate", "2026-09-15");
|
||||||
|
internal.put("segments", Collections.singletonList(seg));
|
||||||
|
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"));
|
||||||
|
assertFalse(segs.get(0).containsKey("运输分类"));
|
||||||
|
assertFalse(body.containsKey("运输分类"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user