diff --git a/inquiry-agent/agent/policy/sea_text_flow.py b/inquiry-agent/agent/policy/sea_text_flow.py index eca3423..b5db649 100644 --- a/inquiry-agent/agent/policy/sea_text_flow.py +++ b/inquiry-agent/agent/policy/sea_text_flow.py @@ -249,7 +249,8 @@ class SeaTextInquiryFlow(AirTextInquiryFlow): ) or "" 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( sender_id=sender_id, thread_id=sender_id, diff --git a/inquiry-agent/agent/schema/tms_sea_query.py b/inquiry-agent/agent/schema/tms_sea_query.py index 7bf668f..79d1547 100644 --- a/inquiry-agent/agent/schema/tms_sea_query.py +++ b/inquiry-agent/agent/schema/tms_sea_query.py @@ -1,8 +1,9 @@ """ 海运查价参数:对照 TMS V2 /quote/v2/query 是否带全。 -本文件职责:询价卡片出现前,确认海运七项能拼出查价正文。 +本文件职责:询价卡片出现前,确认海运十项里查出站项能拼出查价正文。 港口只带名称,不做港口字典对应;箱型箱量保持原文字段,containerItems 只透传模型投影。 +贸易条款必带出站;运输分类只留在工单 facts,不进 tms_facts。 禁止:本地拆箱型;因对不上港口字典跳过 TMS;改六态。 """ @@ -62,6 +63,8 @@ def assemble_sea_query(facts: dict[str, Any] | None) -> dict[str, Any]: load = _load_type(str(src.get("整柜或拼柜") or "")) box = 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_display: list[str] = [] @@ -72,6 +75,7 @@ def assemble_sea_query(facts: dict[str, Any] | None) -> dict[str, Any]: ("货量", "货物数量", qty), ("整柜或拼柜", "整柜或拼柜", load), ("箱型箱量", "箱型箱量", box), + ("贸易条款", "贸易条款", trade), ("报价日期", "报价日期", quote_date), ) 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 if box: out_facts["箱型箱量"] = box + if trade: + out_facts["贸易条款"] = trade + if klass: + out_facts["运输分类"] = klass if quote_date: out_facts["报价日期"] = quote_date items = parse_container_items(src) if items: out_facts["containerItems"] = items + tms_facts = dict(out_facts) + tms_facts.pop("运输分类", None) logger.info( "tms.sea.assemble ok=%s origin=%s dest=%s load=%s missing=%s", ok, @@ -112,4 +122,5 @@ def assemble_sea_query(facts: dict[str, Any] | None) -> dict[str, Any]: "missing": missing, "missing_display": missing_display, "facts": out_facts, + "tms_facts": tms_facts, } diff --git a/inquiry-agent/tests/test_sea_text_inquiry.py b/inquiry-agent/tests/test_sea_text_inquiry.py index e0863ae..477b94a 100644 --- a/inquiry-agent/tests/test_sea_text_inquiry.py +++ b/inquiry-agent/tests/test_sea_text_inquiry.py @@ -273,11 +273,33 @@ class SeaTmsCardTests(unittest.TestCase): self.extras.append(dict(extra) if extra else {}) 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.assertEqual(ready["missing"], []) 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: """海运多项费用:完整明细必须在文本里,卡片引用条不得半截。""" long_quote = { diff --git a/inquiry-api/jeecg-module-inquiry/src/main/java/org/jeecg/modules/inquiry/tms/InquiryTmsQueryService.java b/inquiry-api/jeecg-module-inquiry/src/main/java/org/jeecg/modules/inquiry/tms/InquiryTmsQueryService.java index 29b8dc9..1390012 100644 --- a/inquiry-api/jeecg-module-inquiry/src/main/java/org/jeecg/modules/inquiry/tms/InquiryTmsQueryService.java +++ b/inquiry-api/jeecg-module-inquiry/src/main/java/org/jeecg/modules/inquiry/tms/InquiryTmsQueryService.java @@ -93,6 +93,7 @@ public class InquiryTmsQueryService { if (items != null) { segment.put("containerItems", items); } + segment.put("tradeTerms", first(facts.get("贸易条款"), facts.get("trade_terms"), facts.get("tradeTerms"))); } else if ("LAND".equals(line)) { // 陆运不套空运包装/件数;分类和中港装载只在工单,mapper 不会放进出站。 segment.putAll(TmsLandSegmentMapper.build(facts)); @@ -623,8 +624,8 @@ public class InquiryTmsQueryService { * 对不上机场也要查,TMS 自己回无价或 1003。 */ /** - * 海运出站必填:工单号、报价日期、起运/目的名称、品名、货量、整柜或拼柜、箱型箱量。 - * 不要求机场三字码、件数、重量、体积、包装。 + * 海运出站必填:工单号、报价日期、起运/目的名称、品名、货量、整柜或拼柜、箱型箱量、贸易条款。 + * 不要求机场三字码、件数、重量、体积、包装;运输分类不进出站。 */ private static List missingSeaQuery(String workOrderNo, String quoteDate, Map segment, Map facts) { @@ -649,6 +650,9 @@ public class InquiryTmsQueryService { if (blank(facts.get("箱型箱量"))) { missing.add("箱型箱量"); } + if (blank(facts.get("贸易条款")) && blank(facts.get("trade_terms")) && blank(facts.get("tradeTerms"))) { + missing.add("贸易条款"); + } return missing; } diff --git a/inquiry-api/jeecg-module-inquiry/src/main/java/org/jeecg/modules/inquiry/tms/TmsV2RequestMapper.java b/inquiry-api/jeecg-module-inquiry/src/main/java/org/jeecg/modules/inquiry/tms/TmsV2RequestMapper.java index 33260a3..94a3b69 100644 --- a/inquiry-api/jeecg-module-inquiry/src/main/java/org/jeecg/modules/inquiry/tms/TmsV2RequestMapper.java +++ b/inquiry-api/jeecg-module-inquiry/src/main/java/org/jeecg/modules/inquiry/tms/TmsV2RequestMapper.java @@ -113,6 +113,7 @@ public final class TmsV2RequestMapper { } put(target, "containerType", type); put(target, "containerQty", asInteger(qty)); + put(target, "tradeTerms", first(source, "tradeTerms", "贸易条款", "trade_terms")); } /** diff --git a/inquiry-api/jeecg-module-inquiry/src/test/java/org/jeecg/modules/inquiry/tms/TmsV2RequestMapperTest.java b/inquiry-api/jeecg-module-inquiry/src/test/java/org/jeecg/modules/inquiry/tms/TmsV2RequestMapperTest.java index 5eedda1..dc7b19e 100644 --- a/inquiry-api/jeecg-module-inquiry/src/test/java/org/jeecg/modules/inquiry/tms/TmsV2RequestMapperTest.java +++ b/inquiry-api/jeecg-module-inquiry/src/test/java/org/jeecg/modules/inquiry/tms/TmsV2RequestMapperTest.java @@ -130,4 +130,27 @@ public class TmsV2RequestMapperTest { assertFalse(segs.get(0).containsKey("landRouteCategory")); assertFalse(segs.get(0).containsKey("landClass")); } + + @Test + public void seaSendsTradeTermsOmitsClass() { + Map 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 internal = new HashMap<>(); + internal.put("transportMode", "SEA"); + internal.put("quoteDate", "2026-09-15"); + internal.put("segments", Collections.singletonList(seg)); + Map body = TmsV2RequestMapper.quote(internal); + @SuppressWarnings("unchecked") + List> segs = (List>) body.get("segments"); + assertEquals("FOB", segs.get(0).get("tradeTerms")); + assertFalse(segs.get(0).containsKey("运输分类")); + assertFalse(body.containsKey("运输分类")); + } }