From 4f8c373121066166aa84aeb2e3d439b7fa245c5b Mon Sep 17 00:00:00 2001 From: jillion <439912596@qq.com> Date: Mon, 12 Oct 2026 14:59:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E5=8F=B0=E5=88=97=E8=A1=A8=E6=80=BB?= =?UTF-8?q?=E6=95=B0=E6=94=B9=E5=88=B0=E6=AF=8F=E9=A1=B5=E5=B7=A6=E8=BE=B9?= =?UTF-8?q?=EF=BC=8C=E6=8A=A5=E4=BB=B7=E6=A8=A1=E6=9D=BF=E9=A1=B5=E5=90=8C?= =?UTF-8?q?=E6=A0=B7=E9=9D=A0=E5=8F=B3=E3=80=82=E9=99=86=E8=BF=90=E9=80=89?= =?UTF-8?q?=E7=BA=BF=E8=A1=A5=E6=AF=8F=E6=AE=B5=E8=BD=A6=E5=9E=8B=E3=80=82?= =?UTF-8?q?=E5=A4=9A=E6=AE=B5=E7=BE=A4=E6=AF=8F=E6=AC=A1=E5=B8=A6=E5=87=BA?= =?UTF-8?q?=E8=AF=A2=E4=BB=B7=E5=AD=97=E6=AE=B5=E6=97=B6=EF=BC=8C=E5=BA=95?= =?UTF-8?q?=E9=83=A8=E8=A1=A5=E4=B8=8A=E5=8F=A6=E6=8A=A5=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../agent/channel/h5/land_route_page.py | 25 +++++++++++-- inquiry-agent/agent/policy/inquiry_copy.py | 14 ++++++++ inquiry-agent/agent/policy/multi_segments.py | 3 ++ .../tests/test_land_route_options.py | 35 +++++++++++++++++++ inquiry-agent/tests/test_multi_group.py | 5 +++ inquiry-agent/tests/test_multi_segments.py | 9 +++++ .../inquiry/components/AdminPagination.vue | 19 ++++++---- .../views/inquiry/styles/admin-fidelity.css | 4 +++ .../src/views/inquiry/template/index.vue | 2 +- 9 files changed, 106 insertions(+), 10 deletions(-) diff --git a/inquiry-agent/agent/channel/h5/land_route_page.py b/inquiry-agent/agent/channel/h5/land_route_page.py index 6d336cb..0b0d513 100644 --- a/inquiry-agent/agent/channel/h5/land_route_page.py +++ b/inquiry-agent/agent/channel/h5/land_route_page.py @@ -1,7 +1,7 @@ """ 陆运选线路 H5 页。 -本文件职责:按完整线路列出各段成本与合计,提交所选下标。 +本文件职责:按完整线路列出各段车型、成本与合计,提交所选下标。 样式对齐空运选线路页。禁止 URL userid;禁止本页查 TMS。 """ @@ -64,19 +64,40 @@ def render_land_route_ok() -> str: ) +def _segment_vehicle(leg: dict[str, Any] | None) -> str: + """每段车型用 TMS 的 vehicleType 原文。没有台数,空值显示 -。""" + src = dict(leg or {}) + raw = str( + src.get("vehicleType") or src.get("vehicle_type") or src.get("truckType") or "" + ).strip() + if raw.lower() in {"none", "null"}: + raw = "" + return raw or "-" + + def _route_card(index: int, route: dict[str, Any], quote: dict[str, Any] | None = None) -> str: - """一张线路:航线、各段路段+TMS 费目、合计。""" + """一张线路:航线、各段路段、车型、TMS 费目、合计。""" line = html.escape(route_line(route)) blocks = land_leg_fee_blocks_for_route(quote, route) + shown_legs = [ + leg + for leg in list(route.get("legs") or []) + if str(leg.get("origin") or "").strip() or str(leg.get("destination") or "").strip() + ] etas = {i: d for i, (_, d) in enumerate(option_eta_bits(route), 1)} fee_bits: list[str] = [] items = "" if blocks: for i, (title, fees, eta) in enumerate(blocks, 1): + vehicle = _segment_vehicle(shown_legs[i - 1] if i - 1 < len(shown_legs) else None) items += ( f"
  • {html.escape(title)}" "
  • " ) + items += ( + f"
  • 车型 {html.escape(vehicle)}" + "
  • " + ) for fee in fees: amount = _money(fee.get("amount"), str(fee.get("currency") or "").strip()) name = str(fee.get("name") or "").strip() diff --git a/inquiry-agent/agent/policy/inquiry_copy.py b/inquiry-agent/agent/policy/inquiry_copy.py index bf2e7c7..a06cf1e 100644 --- a/inquiry-agent/agent/policy/inquiry_copy.py +++ b/inquiry-agent/agent/policy/inquiry_copy.py @@ -2624,6 +2624,20 @@ def group_tms_confirm_hint(*, business_line: str = "") -> str: return f"请{who}回复“确认报价”确定采用TMS报价。\n{_GROUP_QUOTE_TAIL}" +def multi_group_field_hint(*, includes_air: bool) -> str: + """ + 多段群里每次带出各段询价字段时,贴在最后一行字段下面。 + + 任一段是空运,喊航线或产品;没有空运只喊产品。成交跟进不走这里。 + 注里说的是附件报价单只存档,不改报价写入规则。 + """ + who = "航线或产品人员" if includes_air else "产品人员" + return ( + f"{who}可通过“纯文字”告知新报价,或直接将新报价excel表发给智能体。\n" + "(注:附件报价单仅作为文件存储于后台,智能体不读取且不将报价内容写进系统)" + ) + + def group_no_tms_quote_hint(*, business_line: str = "") -> str: """ TMS 无价时写在「暂无匹配」下方:谁可以用文字或 Excel 另报。 diff --git a/inquiry-agent/agent/policy/multi_segments.py b/inquiry-agent/agent/policy/multi_segments.py index ff2e93c..d0335ca 100644 --- a/inquiry-agent/agent/policy/multi_segments.py +++ b/inquiry-agent/agent/policy/multi_segments.py @@ -385,6 +385,7 @@ def group_brief( 结构对齐私聊核对:@销售和各段产品,下一行直接接段1询价字段。 不写 TMS,不写协同段。工单号已在私聊提示过,群里不再重复一行。 + 每次拼这段字段都在底部加另报提示;成交跟进是另一条消息,不加。 """ _ = work_order_no names: list[str] = [] @@ -406,6 +407,8 @@ def group_brief( body = _segment_inquiry_block(seg).strip() if body: lines.extend(body.splitlines()) + # 紧挨最后一行字段,中间不空行。含空运与不含空运用不同开头。 + lines.append(copy.multi_group_field_hint(includes_air=includes_air(segments))) return "\n".join(lines) diff --git a/inquiry-agent/tests/test_land_route_options.py b/inquiry-agent/tests/test_land_route_options.py index 2a42847..85a4f56 100644 --- a/inquiry-agent/tests/test_land_route_options.py +++ b/inquiry-agent/tests/test_land_route_options.py @@ -433,6 +433,41 @@ class LandRouteOptionsTests(unittest.TestCase): self.assertIn("CNY 7500", page) self.assertIn("成本价合计", page) + def test_h5_shows_vehicle_type_on_each_leg(self) -> None: + """多条线路的 H5 卡片按段展示 TMS 车型,没有就写 -。""" + from agent.channel.h5.land_route_page import render_land_route_page + + quote = _three_routes() + for leg in quote["landSegments"]: + dest = str(leg.get("destination") or "") + origin = str(leg.get("origin") or "") + if dest == "深圳": + leg["vehicleType"] = "9米6" + elif origin == "深圳": + leg["vehicleType"] = "13米" + elif dest == "广西": + leg["vehicleType"] = None + quote["fee_lines"] = _tms_land_fees( + (1, 1, "运输费", 7500), + (1, 2, "运输费", 9600), + (2, 1, "运输费", 7500), + (2, 2, "运输费", 9600), + (3, 1, "运输费", 7500), + (3, 2, "运输费", 9600), + ) + page = render_land_route_page( + {"work_order_no": "WO202609220010", "quote": quote} + ) + od = page.find("上海 → 深圳") + vehicle = page.find("车型 9米6", od) + fee = page.find("运输费", vehicle) + self.assertGreaterEqual(od, 0) + self.assertGreater(vehicle, od) + self.assertGreater(fee, vehicle) + self.assertIn("车型 13米", page) + self.assertIn("车型 -", page) + self.assertNotIn("9米6/1", page) + def test_h5_and_detail_use_tms_fees_when_java_sends_json_strings(self) -> None: """主账 quote 常是 JSON 串,且 fee_lines 可能没带段号。""" from agent.channel.h5.land_route_page import render_land_route_page diff --git a/inquiry-agent/tests/test_multi_group.py b/inquiry-agent/tests/test_multi_group.py index 0f15db9..bdae173 100644 --- a/inquiry-agent/tests/test_multi_group.py +++ b/inquiry-agent/tests/test_multi_group.py @@ -177,6 +177,7 @@ class QuoteTests(unittest.TestCase): ) self.assertEqual(phase, "multi_deal_sent") self.assertIn("120", self.client.texts[-1]) + self.assertNotIn("纯文字", self.client.texts[-1]) self.assertNotIn("100", deal_text( work_order_no=self.ticket.work_order_no, segments=[Segment(index=1, mode="SEA"), Segment(index=2, mode="LAND")], @@ -198,6 +199,8 @@ class QuoteTests(unittest.TestCase): brief = self.client.texts[-1] self.assertIn("段1:海运", brief) self.assertIn("段2:陆运", brief) + self.assertIn("产品人员可通过“纯文字”告知新报价", brief) + self.assertNotIn("航线或产品人员", brief) self.assertNotIn("TMS报价", brief) self.assertNotIn("其他段报完", brief) self.assertEqual(self.ledger.get_ticket(work_order_no=no).collab_chat_id, "room-9") @@ -265,6 +268,8 @@ class QuoteTests(unittest.TestCase): self.assertIn("段2:空运", brief) self.assertIn("目的地(必填):CRK", brief) self.assertIn("贸易条款(非必填):DAP", brief) + self.assertTrue(brief.endswith("(注:附件报价单仅作为文件存储于后台,智能体不读取且不将报价内容写进系统)")) + self.assertIn("航线或产品人员可通过“纯文字”告知新报价", brief) self.assertNotIn("其他段报完", "\n".join(self.client.texts)) self.assertNotIn("询价字段:", brief) diff --git a/inquiry-agent/tests/test_multi_segments.py b/inquiry-agent/tests/test_multi_segments.py index fcf8fa7..6a16508 100644 --- a/inquiry-agent/tests/test_multi_segments.py +++ b/inquiry-agent/tests/test_multi_segments.py @@ -167,6 +167,10 @@ class SplitTests(unittest.TestCase): ) self.assertIn("@吴季林@林绵助\n段1:", text) self.assertNotIn("@林绵助\n\n段1", text) + self.assertTrue( + text.endswith(copy.multi_group_field_hint(includes_air=True)), + text, + ) def test_missing_fields_use_single_chat_examples(self) -> None: outcome = split_segments("段1:海运\n起运港:广州港\n段2:空运\n目的地:CRK") @@ -694,6 +698,11 @@ class PullTests(unittest.TestCase): segments=flow._segments(flow.session_of("sales")), ) self.assertNotIn("TMS", brief_text) + self.assertTrue( + brief_text.endswith(copy.multi_group_field_hint(includes_air=False)), + brief_text, + ) + self.assertNotIn("航线或产品人员", brief_text) class CardPullTests(unittest.TestCase): diff --git a/inquiry-backend/src/views/inquiry/components/AdminPagination.vue b/inquiry-backend/src/views/inquiry/components/AdminPagination.vue index bd586ea..ba6fb1d 100644 --- a/inquiry-backend/src/views/inquiry/components/AdminPagination.vue +++ b/inquiry-backend/src/views/inquiry/components/AdminPagination.vue @@ -1,6 +1,6 @@