调价后的新价格写入报价单;多段段前的货物带到各段,协同群不再把体积里的星号吃掉。海运字段齐了先核对再查价。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,7 +14,7 @@ if ROOT not in sys.path:
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
from agent.channel.aibot import normalize_aibot_payload
|
||||
from agent.channel.aibot.reply import AibotReplyClient
|
||||
from agent.channel.aibot.reply import AibotReplyClient, protect_markdown_asterisks
|
||||
|
||||
|
||||
class AibotInboundNormalizeTests(unittest.TestCase):
|
||||
@@ -51,6 +51,14 @@ class AibotInboundNormalizeTests(unittest.TestCase):
|
||||
|
||||
|
||||
class AibotReplyTests(unittest.TestCase):
|
||||
def test_group_markdown_keeps_dimension_asterisks(self) -> None:
|
||||
raw = "体积(CBM)(必填):1.1*2.1*2.1\n箱型箱量:40HQ*1"
|
||||
shown = protect_markdown_asterisks(raw)
|
||||
self.assertEqual(
|
||||
shown,
|
||||
"体积(CBM)(必填):`1.1*2.1*2.1`\n箱型箱量:`40HQ*1`",
|
||||
)
|
||||
self.assertEqual(protect_markdown_asterisks(shown), shown)
|
||||
def test_empty_content_skips(self) -> None:
|
||||
client = AibotReplyClient("")
|
||||
out = client.send_group(chat_id="wr_1", content=" ")
|
||||
|
||||
@@ -1091,6 +1091,31 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
self.assertTrue(any(f"工单{wo}的1个附件已保存到后台工单" 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:
|
||||
"""航线附件已报价后,再发工单号回到成交跟进,不再要航线报价。"""
|
||||
wo = self._create_air()
|
||||
handle_air_group(
|
||||
self._msg(sender="air1", text=wo, chat="wr_air_1", mid="af3b"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
)
|
||||
handle_air_group(
|
||||
self._file(sender="air1", filename="航线报价.pdf", chat="wr_air_1", mid="af4b"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
)
|
||||
before = len(self.groups.sent)
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="sales1", text=wo, chat="wr_air_1", mid="af4c"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
)
|
||||
self.assertEqual(phase, "activated_deal")
|
||||
added = self.groups.sent[before:]
|
||||
texts = "\n".join(x.get("content") or "" for x in added)
|
||||
self.assertIn("请确认成交状态", texts)
|
||||
self.assertFalse(any(x.get("kind") == "file" for x in added))
|
||||
|
||||
def test_image_rejected(self) -> None:
|
||||
wo = self._create_air()
|
||||
handle_air_group(
|
||||
@@ -1376,6 +1401,45 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
self.assertIn("锁舱失败的原因:该航班日期不存在可锁定的配载记录", joined)
|
||||
self.assertNotIn("TMS接口异常,IT运维排查中", joined)
|
||||
|
||||
def test_paused_after_lock_still_records_lost_reason(self) -> None:
|
||||
"""锁舱失败标了暂停后,未成交和原因仍要入账,不能回 TMS 异常。"""
|
||||
wo = self._create_air()
|
||||
handle_air_group(
|
||||
self._msg(sender="sales1", text=wo, chat="wr_air_paused_lost", mid="pl0"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
injected_intent="air_activate",
|
||||
)
|
||||
self.ledger.mark_system_exception(
|
||||
work_order_no=wo,
|
||||
system_exception="TMS",
|
||||
system_exception_reason="空运锁舱接口调用失败",
|
||||
step="tms_lock",
|
||||
payload={},
|
||||
)
|
||||
self.groups.sent.clear()
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="sales1", text="未成交", chat="wr_air_paused_lost", mid="pl1"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
injected_intent="deal_lost",
|
||||
)
|
||||
self.assertEqual(phase, "wait_lost_reason")
|
||||
self.assertNotIn("TMS接口异常", "\n".join(self._texts()))
|
||||
self.groups.sent.clear()
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="sales1", text="客户不需要了", chat="wr_air_paused_lost", mid="pl2"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
injected_intent="other",
|
||||
)
|
||||
self.assertEqual(phase, "done")
|
||||
joined = "\n".join(self._texts())
|
||||
self.assertIn("已记下未成交原因:客户不需要了", joined)
|
||||
self.assertNotIn("TMS接口异常", joined)
|
||||
ticket = self.ledger.get_ticket(work_order_no=wo)
|
||||
self.assertEqual(ticket.lost_reason, "客户不需要了")
|
||||
|
||||
def test_release_fail_tells_operator_and_tms_reason(self) -> None:
|
||||
"""释放失败同理:操作人、原因、已通知IT,不要系统异常兜底。"""
|
||||
wo = self._create_air()
|
||||
|
||||
@@ -213,6 +213,15 @@ class AttachmentInquiryTests(unittest.TestCase):
|
||||
self.assertIn(copy.ATTACH_RECOGNIZING, self._bodies())
|
||||
sess = get_sea_text_flow().session_of("s1")
|
||||
self.assertIsNotNone(sess)
|
||||
self.assertEqual(sess.phase, "wait_confirm")
|
||||
self.assertFalse((sess.work_order_no or "").strip())
|
||||
self.assertTrue(sess.pending_files)
|
||||
self.assertIn(copy.SEA_CONFIRM_TAIL, self._bodies()[-1])
|
||||
handle_text_inquiry(
|
||||
InboundMessage(sender_id="s1", message_id="sea-ok", content="确定"),
|
||||
store=self.store,
|
||||
)
|
||||
sess = get_sea_text_flow().session_of("s1")
|
||||
self.assertTrue((sess.work_order_no or "").strip())
|
||||
ticket = get_sea_text_flow().ledger.get_ticket(work_order_no=sess.work_order_no)
|
||||
self.assertTrue(ticket.attachments)
|
||||
@@ -267,6 +276,13 @@ class AttachmentInquiryTests(unittest.TestCase):
|
||||
self.assertEqual(len(acks), 1)
|
||||
self.assertEqual(acks[0], copy.IMAGE_RECOGNIZING)
|
||||
sess = get_sea_text_flow().session_of("s1")
|
||||
self.assertTrue(sess and sess.phase == "wait_confirm")
|
||||
self.assertIn(copy.SEA_CONFIRM_TAIL, self._bodies()[-1])
|
||||
handle_text_inquiry(
|
||||
InboundMessage(sender_id="s1", message_id="sea-ok", content="确定"),
|
||||
store=self.store,
|
||||
)
|
||||
sess = get_sea_text_flow().session_of("s1")
|
||||
self.assertTrue(sess and sess.work_order_no)
|
||||
|
||||
def test_mid_clarify_then_attach_is_new_ticket(self) -> None:
|
||||
@@ -276,6 +292,10 @@ class AttachmentInquiryTests(unittest.TestCase):
|
||||
injected_facts=COMPLETE_SEA,
|
||||
injected_mode="SEA",
|
||||
)
|
||||
handle_text_inquiry(
|
||||
InboundMessage(sender_id="s1", message_id="old-ok", content="确定"),
|
||||
store=self.store,
|
||||
)
|
||||
wo_old = get_sea_text_flow().session_of("s1").work_order_no
|
||||
handle_attachment_inquiry(
|
||||
InboundMessage(sender_id="s1", message_id="m2", content="我有个excel要询价"),
|
||||
@@ -292,6 +312,10 @@ class AttachmentInquiryTests(unittest.TestCase):
|
||||
"empty": False,
|
||||
},
|
||||
)
|
||||
handle_text_inquiry(
|
||||
InboundMessage(sender_id="s1", message_id="new-ok", content="确定"),
|
||||
store=self.store,
|
||||
)
|
||||
wo_new = get_sea_text_flow().session_of("s1").work_order_no
|
||||
self.assertNotEqual(wo_old, wo_new)
|
||||
self.assertIsNotNone(get_sea_text_flow().ledger.get_ticket(work_order_no=wo_old))
|
||||
|
||||
@@ -265,6 +265,58 @@ class ContinueTicketTests(unittest.TestCase):
|
||||
self.assertTrue(any("已记录为「已成交」" in x for x in replies))
|
||||
self.assertIn(wo, "\n".join(replies))
|
||||
|
||||
def test_resume_waiting_lost_reason_saves_sentence(self) -> None:
|
||||
"""等未成交原因时继续工单,下一句要写入原因,不能再要一遍。"""
|
||||
ledger = MemoryLedger()
|
||||
created = ledger.create_ticket(
|
||||
sender_id="u7", business_line="AIR", facts=COMPLETE_AIR
|
||||
)
|
||||
wo = created["work_order_no"]
|
||||
ledger.upsert_quote(
|
||||
work_order_no=wo,
|
||||
quote={"total": "USD 1", "eta": "-", "valid_until": "-"},
|
||||
to_status="已报价",
|
||||
)
|
||||
ledger.render_quote(
|
||||
work_order_no=wo,
|
||||
business_line="AIR",
|
||||
facts=COMPLETE_AIR,
|
||||
quote={"total": "USD 1"},
|
||||
)
|
||||
flow = AirTextInquiryFlow(ledger)
|
||||
replies: list[str] = []
|
||||
|
||||
def reply(text: str, extra=None) -> None:
|
||||
replies.append(text)
|
||||
_ = extra
|
||||
|
||||
self.assertEqual(
|
||||
flow.resume_historical(sender_id="u7", work_order_no=wo, reply=reply),
|
||||
"wait_deal",
|
||||
)
|
||||
self.assertEqual(
|
||||
flow.on_button(sender_id="u7", action=copy.BTN_LOST, reply=reply),
|
||||
"wait_lost_reason",
|
||||
)
|
||||
replies.clear()
|
||||
phase = flow.resume_historical(
|
||||
sender_id="u7", work_order_no=wo, reply=reply, text=""
|
||||
)
|
||||
self.assertEqual(phase, "wait_lost_reason")
|
||||
self.assertTrue(any("请补充未成交原因" in x for x in replies))
|
||||
replies.clear()
|
||||
phase = flow.resume_historical(
|
||||
sender_id="u7",
|
||||
work_order_no=wo,
|
||||
reply=reply,
|
||||
text="客户不需要了",
|
||||
)
|
||||
self.assertEqual(phase, "done")
|
||||
self.assertTrue(any("已记下未成交原因:客户不需要了" in x for x in replies))
|
||||
self.assertFalse(any("请补充未成交原因" in x for x in replies))
|
||||
view = ledger.get_for_agent(work_order_no=wo, sender_id="u7") or {}
|
||||
self.assertEqual(view.get("lost_reason"), "客户不需要了")
|
||||
|
||||
def test_resume_missing_wo_asks(self) -> None:
|
||||
flow = AirTextInquiryFlow(MemoryLedger())
|
||||
replies: list[str] = []
|
||||
|
||||
@@ -59,6 +59,37 @@ class OralExtractTests(unittest.TestCase):
|
||||
out = invoke_shell(payload={"a": 1})
|
||||
self.assertEqual(out.get("error"), "extract_fields_shell_not_implemented")
|
||||
|
||||
def test_city_pair_without_mode_does_not_default_air(self) -> None:
|
||||
"""没写运输方式时,模型猜的空运不能当成已选。港口和日期仍留下。"""
|
||||
text = "查一下10月9日郑州-吉隆坡的报价"
|
||||
guessed = parse_extract_payload(
|
||||
{
|
||||
"transport_mode": "AIR",
|
||||
"fields": {
|
||||
"起运港": "郑州",
|
||||
"目的港": "吉隆坡",
|
||||
"报价日期": "2026-10-09",
|
||||
},
|
||||
}
|
||||
)
|
||||
snap = extract_inquiry_snapshot(text, b_result=guessed)
|
||||
self.assertEqual(snap["business_line"], "")
|
||||
self.assertEqual(snap["facts"].get("起运港"), "郑州")
|
||||
self.assertEqual(snap["facts"].get("目的港"), "吉隆坡")
|
||||
self.assertEqual(snap["facts"].get("报价日期"), "2026-10-09")
|
||||
flow = AirTextInquiryFlow(MemoryLedger())
|
||||
replies: list[str] = []
|
||||
phase = flow.on_text(
|
||||
sender_id="u-mode",
|
||||
text=text,
|
||||
reply=lambda body, extra=None: replies.append(body),
|
||||
b_result=guessed,
|
||||
allow_b=False,
|
||||
)
|
||||
self.assertEqual(phase, "need_mode")
|
||||
self.assertTrue(any("告知我询价的运输方式" in x for x in replies))
|
||||
self.assertFalse(any(x.startswith("运输方式:空运") or "运输方式:空运" in x for x in replies))
|
||||
|
||||
def test_oral_snapshot_uses_b_not_regex(self) -> None:
|
||||
snap = extract_inquiry_snapshot(ORAL, b_result=parse_extract_payload(B_JSON))
|
||||
self.assertEqual(snap["business_line"], "AIR")
|
||||
@@ -304,6 +335,14 @@ class OralExtractTests(unittest.TestCase):
|
||||
self.assertIn("重量(KG):1.5KG", shown)
|
||||
self.assertNotIn("1.5KGS", shown)
|
||||
|
||||
def test_dimension_text_is_volume_without_multiplying(self) -> None:
|
||||
from agent.schema.field_validate import harvest_oral_measures
|
||||
|
||||
facts = harvest_oral_measures("茶叶,1.1*2.1*2.2,11kg,21件", {})
|
||||
self.assertEqual(facts["体积"], "1.1*2.1*2.2")
|
||||
self.assertEqual(facts["毛重"], "11kg")
|
||||
self.assertEqual(facts["件数"], "21件")
|
||||
|
||||
def test_oral_units_fill_weight_volume_without_b(self) -> None:
|
||||
snap = extract_inquiry_snapshot(
|
||||
"珠海到克拉克空运,茶叶 10件 100KGS 2CBM 托盘"
|
||||
|
||||
@@ -134,6 +134,15 @@ class ImageInquiryFlowTests(unittest.TestCase):
|
||||
sea = get_sea_text_flow()
|
||||
sess = sea.session_of("s1")
|
||||
self.assertIsNotNone(sess)
|
||||
self.assertEqual(sess.phase, "wait_confirm")
|
||||
self.assertFalse((sess.work_order_no or "").strip())
|
||||
self.assertTrue(sess.pending_files)
|
||||
self.assertIn(copy.SEA_CONFIRM_TAIL, self._bodies()[-1])
|
||||
handle_text_inquiry(
|
||||
InboundMessage(sender_id="s1", message_id="sea-ok", content="确定"),
|
||||
store=self.store,
|
||||
)
|
||||
sess = sea.session_of("s1")
|
||||
self.assertTrue((sess.work_order_no or "").strip())
|
||||
ticket = sea.ledger.get_ticket(work_order_no=sess.work_order_no)
|
||||
self.assertTrue(ticket.attachments)
|
||||
@@ -163,7 +172,8 @@ class ImageInquiryFlowTests(unittest.TestCase):
|
||||
acks = [c for c in self._bodies() if c == copy.IMAGE_RECOGNIZING]
|
||||
self.assertEqual(len(acks), 1)
|
||||
sess = get_sea_text_flow().session_of("s1")
|
||||
self.assertTrue(sess and sess.work_order_no)
|
||||
self.assertTrue(sess and sess.phase == "wait_confirm")
|
||||
self.assertFalse((sess.work_order_no or "").strip())
|
||||
|
||||
def test_precede_text_joins_new_ticket(self) -> None:
|
||||
handle_text_inquiry(
|
||||
@@ -207,6 +217,10 @@ class ImageInquiryFlowTests(unittest.TestCase):
|
||||
injected_facts=COMPLETE_SEA,
|
||||
injected_mode="SEA",
|
||||
)
|
||||
handle_text_inquiry(
|
||||
InboundMessage(sender_id="s1", message_id="old-ok", content="确定"),
|
||||
store=self.store,
|
||||
)
|
||||
wo_old = get_sea_text_flow().session_of("s1").work_order_no
|
||||
self.assertTrue(wo_old)
|
||||
handle_image_inquiry(
|
||||
@@ -215,6 +229,10 @@ class ImageInquiryFlowTests(unittest.TestCase):
|
||||
vision_fn=_vision(SEA_ORAL),
|
||||
download_fn=lambda _mid: (b"x", ""),
|
||||
)
|
||||
handle_text_inquiry(
|
||||
InboundMessage(sender_id="s1", message_id="new-ok", content="确定"),
|
||||
store=self.store,
|
||||
)
|
||||
wo_new = get_sea_text_flow().session_of("s1").work_order_no
|
||||
self.assertTrue(wo_new)
|
||||
self.assertNotEqual(wo_old, wo_new)
|
||||
|
||||
@@ -257,7 +257,10 @@ class LandDispatchTests(unittest.TestCase):
|
||||
last = list(self.store._outbox.values())[-1].content
|
||||
self.assertIn(SYS_EXC_TMS_FALLBACK, last)
|
||||
|
||||
def test_sea_still_creates_immediately(self) -> None:
|
||||
def test_sea_holds_confirm_before_quote(self) -> None:
|
||||
"""海运私聊字段齐了先出核对卡,回复确定后才查价。"""
|
||||
from agent.policy import inquiry_copy as copy
|
||||
|
||||
msg = InboundMessage(sender_id="s-sea", message_id="m-sea-keep", content="海运询价")
|
||||
phase = handle_text_inquiry(
|
||||
msg,
|
||||
@@ -265,6 +268,14 @@ class LandDispatchTests(unittest.TestCase):
|
||||
injected_mode="SEA",
|
||||
store=self.store,
|
||||
)
|
||||
self.assertEqual(phase, "wait_confirm")
|
||||
held = "\n".join(item.content for item in self.store._outbox.values())
|
||||
self.assertIn(copy.SEA_CONFIRM_TAIL, held)
|
||||
self.assertNotIn("工单号", held)
|
||||
phase = handle_text_inquiry(
|
||||
InboundMessage(sender_id="s-sea", message_id="m-sea-ok", content="确定"),
|
||||
store=self.store,
|
||||
)
|
||||
self.assertEqual(phase, "wait_collab")
|
||||
|
||||
def _land_to_quoted(self, sender_id: str) -> None:
|
||||
|
||||
@@ -243,6 +243,44 @@ class LandRouteOptionsTests(unittest.TestCase):
|
||||
valid = text.find("报价有效期:", text.find("费用合计:"))
|
||||
self.assertGreater(valid, text.find("费用合计:"))
|
||||
|
||||
def test_one_leg_detail_does_not_repeat_route_and_card_is_summary(self) -> None:
|
||||
"""一段陆运:目的站名里已有横杠,明细只写始发→目的;卡片引用条只留合计和项数。"""
|
||||
from agent.policy import inquiry_copy as copy
|
||||
|
||||
dest = "谅山Lạng Sơn-春疆货场Huu Nghi"
|
||||
quote = {
|
||||
"source": "TMS",
|
||||
"source_label": "TMS标准报价",
|
||||
"currency": "CNY",
|
||||
"valid_until": "2026-10-31",
|
||||
"landSegments": [
|
||||
{
|
||||
"segmentSequence": 1,
|
||||
"routeLegSequence": 1,
|
||||
"origin": "上海",
|
||||
"destination": dest,
|
||||
"costPrice": 7500,
|
||||
"currency": "CNY",
|
||||
"transitDays": "N+2",
|
||||
}
|
||||
],
|
||||
"fee_lines": [{"name": "运输费", "amount": "7500", "currency": "CNY"}],
|
||||
}
|
||||
projected = project_selected_route(quote, group_land_routes(quote)[0])
|
||||
text = copy.sea_quote_detail_text(
|
||||
work_order_no="WO202610030035",
|
||||
quote=projected,
|
||||
business_line="LAND",
|
||||
)
|
||||
self.assertNotIn(f"上海-{dest}", text)
|
||||
self.assertIn(f"上海 → {dest}", text)
|
||||
self.assertEqual(text.count(dest), 1)
|
||||
self.assertIn("运输费:CNY 7500", text)
|
||||
card = copy.quote_area_safe_text(projected, business_line="LAND")
|
||||
self.assertEqual(card, "费用合计:CNY 7500\n共1项费用,详见上一条明细")
|
||||
self.assertNotIn("运输费", card)
|
||||
self.assertNotIn(dest, card)
|
||||
|
||||
def test_land_detail_and_group_use_selected_route_valid_until(self) -> None:
|
||||
"""选定广西这条后:私聊明细和群 TMS 段同一套正文,不摊开没选的线路。"""
|
||||
from agent.policy import inquiry_copy as copy
|
||||
|
||||
@@ -133,6 +133,29 @@ class SplitTests(unittest.TestCase):
|
||||
self.assertTrue(outcome.ok)
|
||||
self.assertEqual([seg.mode for seg in outcome.segments], ["LAND", "SEA"])
|
||||
|
||||
def test_cargo_before_ordinal_segments_fills_both(self) -> None:
|
||||
"""段前写一次的品名、尺寸、重量、件数,陆运和空运都要带上。"""
|
||||
oral = (
|
||||
"茶叶,1.1*2.1*2.2,11kg,21件\n"
|
||||
"第一段陆运,北京到上海\n"
|
||||
"第二段空运,上海到北宁"
|
||||
)
|
||||
outcome = split_segments(oral)
|
||||
self.assertTrue(outcome.ok)
|
||||
self.assertEqual([seg.index for seg in outcome.segments], [1, 2])
|
||||
self.assertEqual([seg.mode for seg in outcome.segments], ["LAND", "AIR"])
|
||||
land, air = outcome.segments
|
||||
self.assertEqual(land.facts.get("品名"), "茶叶")
|
||||
self.assertEqual(air.facts.get("品名"), "茶叶")
|
||||
self.assertEqual(air.facts.get("件数"), "21件")
|
||||
self.assertEqual(air.facts.get("毛重"), "11kg")
|
||||
self.assertEqual(air.facts.get("体积"), "1.1*2.1*2.2")
|
||||
_, _, gaps = refresh_segments(outcome.segments)
|
||||
self.assertNotIn("品名(必填):\n(如:茶叶)", gaps)
|
||||
self.assertNotIn("重量(KG)(必填):\n(如:", gaps)
|
||||
self.assertNotIn("体积(CBM)(必填):\n(如:", gaps)
|
||||
self.assertNotIn("件数(必填):\n(如:", gaps)
|
||||
|
||||
def test_group_brief_has_no_blank_under_mentions(self) -> None:
|
||||
"""拉群摘要 @人后直接接段1,中间不要空行。"""
|
||||
segs = split_segments(SEA_AIR).segments
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
@@ -618,5 +619,102 @@ class QuoteAdjustTests(unittest.TestCase):
|
||||
self.assertIn("费用合计:CNY 100.00", text.split("销售调价:", 1)[1])
|
||||
|
||||
|
||||
class SheetPriceStampTests(unittest.TestCase):
|
||||
"""报价单读的是线路表和舱位明细,不是只读 fee_lines。"""
|
||||
|
||||
def test_sales_adjust_writes_land_route_copies(self) -> None:
|
||||
src = {
|
||||
"source": "TMS",
|
||||
"source_label": "TMS标准报价",
|
||||
"currency": "CNY",
|
||||
"total": "CNY 16500",
|
||||
"fee_lines": [{"name": "运输费", "amount": "16500.00", "currency": "CNY"}],
|
||||
"feeItems": [{"feeName": "运输费", "amount": 16500.0, "currency": "CNY"}],
|
||||
"landSegments": [
|
||||
{"origin": "上海", "destination": "谅山", "costPrice": 16500.0, "currency": "CNY"}
|
||||
],
|
||||
"route_rows": [
|
||||
{
|
||||
"origin": "上海",
|
||||
"destination": "谅山",
|
||||
"costPrice": "16500.00",
|
||||
"amount": "16500.00",
|
||||
}
|
||||
],
|
||||
"selected_land_route": json.dumps(
|
||||
{"legs": [{"origin": "上海", "destination": "谅山", "costPrice": 16500.0}]},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
}
|
||||
got = apply_adjust(
|
||||
src,
|
||||
extract_adjust("运输费改为28150", fee_names=["运输费"], allow_model=False),
|
||||
business_line="LAND",
|
||||
tms_cost="16500",
|
||||
tms_sale="16500",
|
||||
)
|
||||
self.assertTrue(got.ok)
|
||||
self.assertIn("28150", str(got.quote["route_rows"][0]["amount"]))
|
||||
self.assertIn("28150", str(got.quote["route_rows"][0]["costPrice"]))
|
||||
self.assertIn("28150", str(got.quote["landSegments"][0]["costPrice"]))
|
||||
selected = json.loads(got.quote["selected_land_route"])
|
||||
self.assertIn("28150", str(selected["legs"][0]["costPrice"]))
|
||||
self.assertIn("28150", str(got.quote["feeItems"][0]["amount"]))
|
||||
|
||||
def test_product_and_airline_sheet_copies_follow_new_fee(self) -> None:
|
||||
from agent.policy.sea_group_ops import merge_quote_fees
|
||||
from agent.schema.quote_adjust import stamp_prices_for_sheet
|
||||
|
||||
base = {
|
||||
"source": "TMS",
|
||||
"source_label": "TMS标准报价",
|
||||
"currency": "CNY",
|
||||
"total": "16500",
|
||||
"fee_lines": [{"name": "运输费", "amount": "16500", "currency": "CNY"}],
|
||||
"route_rows": [{"costPrice": "16500", "amount": "16500"}],
|
||||
"oceanCostItems": [{"name": "海运费", "amount": "100", "currency": "USD"}],
|
||||
}
|
||||
merged = merge_quote_fees(
|
||||
base,
|
||||
{"fee_lines": [{"name": "运输费", "amount": "18000"}], "source_label": "产品报价"},
|
||||
)
|
||||
self.assertIn("18000", str(merged["route_rows"][0]["amount"]))
|
||||
self.assertEqual(merged["source_label"], "产品报价")
|
||||
|
||||
air = stamp_prices_for_sheet(
|
||||
{
|
||||
"source_label": "航线报价",
|
||||
"total": "900",
|
||||
"currency": "CNY",
|
||||
"fee_lines": [{"name": "空运费", "amount": "900", "currency": "CNY"}],
|
||||
"airOptions": [
|
||||
{
|
||||
"salePrice": "7918.25",
|
||||
"departureCharges": [
|
||||
{"chargeName": "空运费", "unitPrice": "1000", "amount": "1000"}
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(air["airOptions"][0]["salePrice"], "900")
|
||||
self.assertIn("900", str(air["airOptions"][0]["departureCharges"][0]["amount"]))
|
||||
kept = stamp_prices_for_sheet(
|
||||
{
|
||||
"source_label": "航线确认TMS",
|
||||
"total": "200",
|
||||
"fee_lines": [{"name": "增点费", "amount": "200"}],
|
||||
"airOptions": [
|
||||
{
|
||||
"salePrice": "7918.25",
|
||||
"departureCharges": [{"chargeName": "增点费", "unitPrice": "100", "amount": "100"}],
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(kept["airOptions"][0]["salePrice"], "7918.25")
|
||||
self.assertEqual(kept["airOptions"][0]["departureCharges"][0]["amount"], "100")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -71,6 +71,32 @@ class SeaOptionsTest(unittest.TestCase):
|
||||
example = apply_spoken_sea_trade_reply("(如:门到门、港到港)", {})
|
||||
self.assertNotIn("贸易条款", example)
|
||||
|
||||
def test_door_inside_long_sentence_is_trade_term(self):
|
||||
"""先报货再选海运:长句里的门到门是贸易条款,港到港仍是运输分类。"""
|
||||
from agent.schema.sea_options import apply_spoken_sea_trade_reply
|
||||
|
||||
text = "广州港到巴生港,机器设备,40HQ*1,整柜,1.5KG,15CBM,15件,Port to Port,门到门"
|
||||
got = apply_spoken_sea_trade_reply(text, {"运输分类": "Port to Port"})
|
||||
self.assertEqual(got.get("贸易条款"), "门到门")
|
||||
self.assertEqual(got.get("运输分类"), "Port to Port")
|
||||
absent = apply_spoken_sea_trade_reply(
|
||||
"广州港到巴生港,机器设备,40HQ*1,整柜,Port to Port",
|
||||
{"贸易条款": "门到门", "运输分类": "Port to Port"},
|
||||
)
|
||||
self.assertNotIn("贸易条款", absent)
|
||||
self.assertEqual(absent.get("运输分类"), "Port to Port")
|
||||
port_term = apply_spoken_sea_trade_reply(
|
||||
"广州港到巴生港,机器设备,40HQ*1,整柜,1.5KG,15CBM,15件,Port to Port,港到港",
|
||||
{"运输分类": "Port to Port"},
|
||||
)
|
||||
self.assertEqual(port_term.get("贸易条款"), "港到港")
|
||||
self.assertEqual(port_term.get("运输分类"), "Port to Port")
|
||||
invented = apply_spoken_sea_trade_reply(
|
||||
"广州港到巴生港,机器设备,40HQ*1,整柜,Port to Port",
|
||||
{"贸易条款": "港到港"},
|
||||
)
|
||||
self.assertNotIn("贸易条款", invented)
|
||||
|
||||
def test_wo012_trade_port_class_other(self):
|
||||
"""条款写 Port to Port、分类写其它:二者各归各栏,禁止互盖。"""
|
||||
text = (
|
||||
|
||||
@@ -46,6 +46,14 @@ COMPLETE_SEA = {
|
||||
}
|
||||
|
||||
|
||||
def drive_sea(flow, **kwargs):
|
||||
"""字段齐时先出核对卡;要看到查价结果的旧用例再自动回一句确定。"""
|
||||
phase = flow.on_text(**kwargs)
|
||||
if phase == "wait_confirm":
|
||||
phase = flow.on_text(sender_id=kwargs["sender_id"], text="确定", reply=kwargs["reply"])
|
||||
return phase
|
||||
|
||||
|
||||
class SeaCollabContractTests(unittest.TestCase):
|
||||
def test_collab_no_longer_asks_trade_terms(self):
|
||||
self.assertNotIn("贸易条款", SEA_COLLAB_FIELDS)
|
||||
@@ -127,6 +135,80 @@ class SeaTextInquiryTests(unittest.TestCase):
|
||||
self.replies.append(text)
|
||||
self.extras.append(dict(extra) if extra else {})
|
||||
|
||||
def test_complete_fields_hold_confirm_before_tms(self) -> None:
|
||||
"""字段齐了只出核对卡,不建单、不查价。"""
|
||||
phase = self.flow.on_text(
|
||||
sender_id="s-hold",
|
||||
text="海运完整",
|
||||
reply=self._reply,
|
||||
injected_facts=COMPLETE_SEA,
|
||||
injected_mode="SEA",
|
||||
)
|
||||
self.assertEqual(phase, "wait_confirm")
|
||||
card = self.replies[-1]
|
||||
self.assertIn(copy.SEA_CONFIRM_TAIL, card)
|
||||
self.assertIn("运输方式(必填):海运", card)
|
||||
self.assertIn("货物数量(必填):20吨", card)
|
||||
self.assertIn("贸易条款(必填):FOB", card)
|
||||
self.assertNotIn("工单号", card)
|
||||
self.assertNotIn("正在查询TMS报价", card)
|
||||
sess = self.flow.session_of("s-hold")
|
||||
self.assertEqual(sess.phase, "wait_confirm")
|
||||
self.assertFalse((sess.work_order_no or "").strip())
|
||||
self.assertEqual(self.ledger._tickets, {})
|
||||
|
||||
def test_confirm_phrases_then_query_tms(self) -> None:
|
||||
"""确定、确认、查价、可以查了 都建单并查价。"""
|
||||
for word in ("确定", "确认", "查价", "可以查了"):
|
||||
with self.subTest(word=word):
|
||||
self.replies.clear()
|
||||
sender = f"s-go-{word}"
|
||||
held = self.flow.on_text(
|
||||
sender_id=sender,
|
||||
text="海运完整",
|
||||
reply=self._reply,
|
||||
injected_facts=COMPLETE_SEA,
|
||||
injected_mode="SEA",
|
||||
)
|
||||
self.assertEqual(held, "wait_confirm")
|
||||
self.replies.clear()
|
||||
phase = self.flow.on_text(sender_id=sender, text=word, reply=self._reply)
|
||||
self.assertEqual(phase, "wait_collab")
|
||||
joined = "\n".join(self.replies)
|
||||
self.assertIn("正在查询TMS报价", joined)
|
||||
self.assertIn("工单号:", joined)
|
||||
sess = self.flow.session_of(sender)
|
||||
self.assertTrue((sess.work_order_no or "").startswith("WO"))
|
||||
|
||||
def test_resend_while_waiting_merges_and_does_not_quote(self) -> None:
|
||||
"""核对卡出来后改一处,并进原字段再出卡,仍不建单。"""
|
||||
phase = self.flow.on_text(
|
||||
sender_id="s-fix",
|
||||
text="海运完整",
|
||||
reply=self._reply,
|
||||
injected_facts=COMPLETE_SEA,
|
||||
injected_mode="SEA",
|
||||
)
|
||||
self.assertEqual(phase, "wait_confirm")
|
||||
self.replies.clear()
|
||||
fixed = dict(COMPLETE_SEA)
|
||||
fixed["目的港"] = "纽约"
|
||||
phase = self.flow.on_text(
|
||||
sender_id="s-fix",
|
||||
text="目的港改成纽约",
|
||||
reply=self._reply,
|
||||
injected_facts=fixed,
|
||||
injected_mode="SEA",
|
||||
)
|
||||
self.assertEqual(phase, "wait_confirm")
|
||||
self.assertIn("目的港(必填):纽约", self.replies[-1])
|
||||
self.assertIn(copy.SEA_CONFIRM_TAIL, self.replies[-1])
|
||||
sess = self.flow.session_of("s-fix")
|
||||
self.assertEqual(sess.facts.get("目的港"), "纽约")
|
||||
self.assertEqual(sess.facts.get("起运港"), "上海")
|
||||
self.assertFalse((sess.work_order_no or "").strip())
|
||||
self.assertEqual(self.ledger._tickets, {})
|
||||
|
||||
def test_sea_pieces_fill_cargo_qty(self) -> None:
|
||||
facts = {
|
||||
"起运港": "上海",
|
||||
@@ -287,7 +369,7 @@ class SeaTextInquiryTests(unittest.TestCase):
|
||||
def test_incomplete_without_trade_terms_does_not_create_ticket(self) -> None:
|
||||
facts = dict(COMPLETE_SEA)
|
||||
facts.pop("贸易条款")
|
||||
phase = self.flow.on_text(
|
||||
phase = drive_sea(self.flow,
|
||||
sender_id="s-miss",
|
||||
text="海运缺条款",
|
||||
reply=self._reply,
|
||||
@@ -318,7 +400,7 @@ class SeaTextInquiryTests(unittest.TestCase):
|
||||
facts=leftover,
|
||||
)
|
||||
)
|
||||
phase = self.flow.on_text(
|
||||
phase = drive_sea(self.flow,
|
||||
sender_id="s-door",
|
||||
text="海运、纽约港到巴生西港,机器设备,40HQ*1,整柜,1.5KG,15CBM,15件",
|
||||
reply=self._reply,
|
||||
@@ -335,7 +417,7 @@ class SeaTextInquiryTests(unittest.TestCase):
|
||||
"""补问后只回门到门,要记成贸易条款,不能回参数提取不全。"""
|
||||
first = dict(COMPLETE_SEA)
|
||||
first.pop("贸易条款")
|
||||
phase = self.flow.on_text(
|
||||
phase = drive_sea(self.flow,
|
||||
sender_id="s-door-reply",
|
||||
text="先缺条款",
|
||||
reply=self._reply,
|
||||
@@ -344,7 +426,7 @@ class SeaTextInquiryTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(phase, "clarify")
|
||||
self.replies.clear()
|
||||
phase = self.flow.on_text(
|
||||
phase = drive_sea(self.flow,
|
||||
sender_id="s-door-reply",
|
||||
text="门到门",
|
||||
reply=self._reply,
|
||||
@@ -354,10 +436,92 @@ class SeaTextInquiryTests(unittest.TestCase):
|
||||
self.assertIn("贸易条款:门到门", joined)
|
||||
self.assertNotIn("参数提取不全", joined)
|
||||
|
||||
def test_door_in_sentence_kept_after_choosing_sea(self) -> None:
|
||||
"""没写海运、句中有门到门,回「2」后贸易条款要带上,不能再追问。"""
|
||||
from agent.channel.queue import MemoryMessageStore
|
||||
from agent.policy.air_text_flow import reset_air_text_flow_for_test
|
||||
from agent.policy.land_text_flow import reset_land_text_flow_for_test
|
||||
from agent.policy.sea_text_flow import get_sea_text_flow
|
||||
|
||||
reset_sea_text_flow_for_test()
|
||||
reset_land_text_flow_for_test()
|
||||
reset_air_text_flow_for_test()
|
||||
store = MemoryMessageStore()
|
||||
text = "广州港到巴生港,机器设备,40HQ*1,整柜,1.5KG,15CBM,15件,Port to Port,门到门"
|
||||
facts = {
|
||||
"起运港": "广州港",
|
||||
"目的港": "巴生港",
|
||||
"品名": "机器设备",
|
||||
"货量": "15件",
|
||||
"整柜或拼柜": "整柜",
|
||||
"箱型箱量": "40HQ*1",
|
||||
"运输分类": "Port to Port",
|
||||
}
|
||||
phase = handle_text_inquiry(
|
||||
InboundMessage(sender_id="s-door-mode", message_id="m-door-1", content=text),
|
||||
injected_facts=facts,
|
||||
store=store,
|
||||
)
|
||||
self.assertEqual(phase, "need_mode")
|
||||
phase = handle_text_inquiry(
|
||||
InboundMessage(sender_id="s-door-mode", message_id="m-door-2", content="2"),
|
||||
store=store,
|
||||
)
|
||||
body = "\n".join(item.content for item in store._outbox.values())
|
||||
sess = get_sea_text_flow().session_of("s-door-mode")
|
||||
self.assertEqual(sess.facts.get("贸易条款"), "门到门")
|
||||
self.assertEqual(sess.facts.get("运输分类"), "Port to Port")
|
||||
self.assertIn("贸易条款(必填):门到门", body)
|
||||
self.assertIn(copy.SEA_CONFIRM_TAIL, body)
|
||||
self.assertEqual(phase, "wait_confirm")
|
||||
self.assertNotIn("1.贸易条款(必填)", body)
|
||||
self.assertNotEqual(phase, "need_mode")
|
||||
|
||||
def test_port_to_port_phrase_kept_after_choosing_sea(self) -> None:
|
||||
"""没写海运、句中有港到港,回「2」后贸易条款是港到港,运输分类仍是 Port to Port。"""
|
||||
from agent.channel.queue import MemoryMessageStore
|
||||
from agent.policy.air_text_flow import reset_air_text_flow_for_test
|
||||
from agent.policy.land_text_flow import reset_land_text_flow_for_test
|
||||
from agent.policy.sea_text_flow import get_sea_text_flow
|
||||
|
||||
reset_sea_text_flow_for_test()
|
||||
reset_land_text_flow_for_test()
|
||||
reset_air_text_flow_for_test()
|
||||
store = MemoryMessageStore()
|
||||
text = "广州港到巴生港,机器设备,40HQ*1,整柜,1.5KG,15CBM,15件,Port to Port,港到港"
|
||||
facts = {
|
||||
"起运港": "广州港",
|
||||
"目的港": "巴生港",
|
||||
"品名": "机器设备",
|
||||
"货量": "15件",
|
||||
"整柜或拼柜": "整柜",
|
||||
"箱型箱量": "40HQ*1",
|
||||
"运输分类": "Port to Port",
|
||||
}
|
||||
phase = handle_text_inquiry(
|
||||
InboundMessage(sender_id="s-port-mode", message_id="m-port-1", content=text),
|
||||
injected_facts=facts,
|
||||
store=store,
|
||||
)
|
||||
self.assertEqual(phase, "need_mode")
|
||||
phase = handle_text_inquiry(
|
||||
InboundMessage(sender_id="s-port-mode", message_id="m-port-2", content="2"),
|
||||
store=store,
|
||||
)
|
||||
body = "\n".join(item.content for item in store._outbox.values())
|
||||
sess = get_sea_text_flow().session_of("s-port-mode")
|
||||
self.assertEqual(sess.facts.get("贸易条款"), "港到港")
|
||||
self.assertEqual(sess.facts.get("运输分类"), "Port to Port")
|
||||
self.assertIn("贸易条款(必填):港到港", body)
|
||||
self.assertIn(copy.SEA_CONFIRM_TAIL, body)
|
||||
self.assertEqual(phase, "wait_confirm")
|
||||
self.assertNotIn("1.贸易条款(必填)", body)
|
||||
self.assertNotEqual(phase, "need_mode")
|
||||
|
||||
def test_harvest_class_from_reply_then_quotes(self) -> None:
|
||||
first = dict(COMPLETE_SEA)
|
||||
first.pop("运输分类")
|
||||
phase = self.flow.on_text(
|
||||
phase = drive_sea(self.flow,
|
||||
sender_id="s-class",
|
||||
text="先缺分类",
|
||||
reply=self._reply,
|
||||
@@ -366,7 +530,7 @@ class SeaTextInquiryTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(phase, "clarify")
|
||||
self.replies.clear()
|
||||
phase = self.flow.on_text(
|
||||
phase = drive_sea(self.flow,
|
||||
sender_id="s-class",
|
||||
text="Port to Port",
|
||||
reply=self._reply,
|
||||
@@ -378,7 +542,7 @@ class SeaTextInquiryTests(unittest.TestCase):
|
||||
self.assertIn("贸易条款:FOB", joined)
|
||||
|
||||
def test_complete_sea_inquiry_card_uses_ten_fields(self) -> None:
|
||||
phase = self.flow.on_text(
|
||||
phase = drive_sea(self.flow,
|
||||
sender_id="s1",
|
||||
text="海运完整",
|
||||
reply=self._reply,
|
||||
@@ -404,7 +568,7 @@ class SeaTextInquiryTests(unittest.TestCase):
|
||||
|
||||
def test_new_round_after_ticket_does_not_reuse_old_facts(self) -> None:
|
||||
"""上一单已出号,新一轮不能带上旧贸易条款。"""
|
||||
first = self.flow.on_text(
|
||||
first = drive_sea(self.flow,
|
||||
sender_id="s-round",
|
||||
text="海运第一轮",
|
||||
reply=self._reply,
|
||||
@@ -424,7 +588,7 @@ class SeaTextInquiryTests(unittest.TestCase):
|
||||
"箱型箱量": "40HQ*1",
|
||||
"运输分类": "Port to Port",
|
||||
}
|
||||
phase = self.flow.on_text(
|
||||
phase = drive_sea(self.flow,
|
||||
sender_id="s-round",
|
||||
text="海运、纽约港到巴生西港,机器设备,40HQ*1,整柜,1.5KG,15CBM,15件,Port to Port",
|
||||
reply=self._reply,
|
||||
@@ -457,7 +621,7 @@ class SeaTextInquiryTests(unittest.TestCase):
|
||||
flow=self.flow,
|
||||
store=store,
|
||||
)
|
||||
self.assertEqual(phase, "wait_collab")
|
||||
self.assertEqual(phase, "wait_confirm")
|
||||
self.assertNotEqual(phase, "sea_deferred")
|
||||
|
||||
|
||||
@@ -569,9 +733,19 @@ class SeaTmsCardTests(unittest.TestCase):
|
||||
self.assertNotIn("时效", text)
|
||||
self.assertNotIn("报价有效期", text)
|
||||
card_text = copy.quote_area_safe_text(long_quote, business_line="SEA")
|
||||
self.assertIn("费用合计:CNY 2287.65", card_text)
|
||||
self.assertNotIn("海运费:USD 100.00\n燃油附加费", card_text)
|
||||
self.assertLessEqual(len(card_text), 80)
|
||||
self.assertEqual(
|
||||
card_text,
|
||||
"费用合计:CNY 2287.65\n共7项费用,详见上一条明细",
|
||||
)
|
||||
short_quote = {
|
||||
"total": "100",
|
||||
"currency": "CNY",
|
||||
"fee_lines": [{"name": "海运费", "amount": "100", "currency": "CNY"}],
|
||||
}
|
||||
self.assertEqual(
|
||||
copy.quote_area_safe_text(short_quote, business_line="SEA"),
|
||||
"费用合计:CNY 100\n共1项费用,详见上一条明细",
|
||||
)
|
||||
payload = copy.sea_tms_hit_wecom_payload(
|
||||
work_order_no="WO202609150030", quote=long_quote
|
||||
)
|
||||
@@ -614,7 +788,7 @@ class SeaTmsCardTests(unittest.TestCase):
|
||||
self.assertIn("card", steps)
|
||||
|
||||
def test_sea_hit_card_has_price_and_two_buttons(self) -> None:
|
||||
phase = self.flow.on_text(
|
||||
phase = drive_sea(self.flow,
|
||||
sender_id="s1",
|
||||
text="海运有价",
|
||||
reply=self._reply,
|
||||
@@ -639,7 +813,7 @@ class SeaTmsCardTests(unittest.TestCase):
|
||||
def test_sea_miss_one_card_with_pull_button(self) -> None:
|
||||
facts = dict(COMPLETE_SEA)
|
||||
facts["目的港"] = "无价港"
|
||||
phase = self.flow.on_text(
|
||||
phase = drive_sea(self.flow,
|
||||
sender_id="s1",
|
||||
text="海运无价",
|
||||
reply=self._reply,
|
||||
@@ -664,7 +838,7 @@ class SeaTmsCardTests(unittest.TestCase):
|
||||
def test_sea_tech_fail_is_not_miss(self) -> None:
|
||||
facts = dict(COMPLETE_SEA)
|
||||
facts["tms_force"] = "tech"
|
||||
phase = self.flow.on_text(
|
||||
phase = drive_sea(self.flow,
|
||||
sender_id="s1",
|
||||
text="海运技术失败",
|
||||
reply=self._reply,
|
||||
@@ -708,7 +882,7 @@ class SeaSkipAndPullTests(unittest.TestCase):
|
||||
return ""
|
||||
|
||||
def _quote_first(self, sender: str = "s1") -> None:
|
||||
self.flow.on_text(
|
||||
drive_sea(self.flow,
|
||||
sender_id=sender,
|
||||
text="海运有价",
|
||||
reply=self._reply,
|
||||
@@ -820,7 +994,7 @@ class SeaSkipAndPullTests(unittest.TestCase):
|
||||
)
|
||||
facts = dict(COMPLETE_SEA)
|
||||
facts["是否含电"] = "否"
|
||||
self.flow.on_text(
|
||||
drive_sea(self.flow,
|
||||
sender_id="s1",
|
||||
text="海运不含电",
|
||||
reply=self._reply,
|
||||
@@ -857,7 +1031,7 @@ class SeaSkipAndPullTests(unittest.TestCase):
|
||||
)
|
||||
with patch("agent.policy.sea_text_flow._default_group_client", return_value=live):
|
||||
flow = SeaTextInquiryFlow(self.ledger, group_client=None)
|
||||
flow.on_text(
|
||||
drive_sea(flow,
|
||||
sender_id="s9",
|
||||
text="海运有价",
|
||||
reply=self._reply,
|
||||
@@ -964,7 +1138,7 @@ class SeaSkipAndPullTests(unittest.TestCase):
|
||||
)
|
||||
facts = dict(COMPLETE_SEA)
|
||||
facts["目的港"] = "无价港"
|
||||
self.flow.on_text(
|
||||
drive_sea(self.flow,
|
||||
sender_id="s1",
|
||||
text="海运无价",
|
||||
reply=self._reply,
|
||||
@@ -1018,7 +1192,7 @@ class SeaGroupCollabFieldTests(unittest.TestCase):
|
||||
}
|
||||
]
|
||||
)
|
||||
self.flow.on_text(
|
||||
drive_sea(self.flow,
|
||||
sender_id="sales1",
|
||||
text="海运有价",
|
||||
reply=self._reply,
|
||||
@@ -1074,7 +1248,7 @@ class SeaGroupCollabFieldTests(unittest.TestCase):
|
||||
}
|
||||
]
|
||||
)
|
||||
self.flow.on_text(
|
||||
drive_sea(self.flow,
|
||||
sender_id="sales1",
|
||||
text="海运有价",
|
||||
reply=self._reply,
|
||||
@@ -1258,7 +1432,7 @@ class SeaContinueTests(unittest.TestCase):
|
||||
from agent.handlers.continue_thread import handle_continue_thread
|
||||
from agent.policy.air_text_flow import AirTextInquiryFlow
|
||||
|
||||
self.flow.on_text(
|
||||
drive_sea(self.flow,
|
||||
sender_id="c1",
|
||||
text="海运有价",
|
||||
reply=self._reply,
|
||||
@@ -1358,7 +1532,7 @@ class SeaContinueTests(unittest.TestCase):
|
||||
from agent.handlers.continue_thread import handle_continue_thread
|
||||
from agent.policy.air_text_flow import AirTextInquiryFlow
|
||||
|
||||
self.flow.on_text(
|
||||
drive_sea(self.flow,
|
||||
sender_id="c3",
|
||||
text="海运有价",
|
||||
reply=self._reply,
|
||||
@@ -1590,7 +1764,7 @@ class SeaGroupOpsTests(unittest.TestCase):
|
||||
self.replies.append(text)
|
||||
|
||||
def _open_group(self) -> tuple[str, str]:
|
||||
self.flow.on_text(
|
||||
drive_sea(self.flow,
|
||||
sender_id="sales1",
|
||||
text="海运有价",
|
||||
reply=self._reply,
|
||||
@@ -2175,7 +2349,7 @@ class SeaGroupOpsTests(unittest.TestCase):
|
||||
"""TMS 无价时产品文字报价:摘要 TMS 仍是暂无,人工报价两行齐全。"""
|
||||
facts = dict(COMPLETE_SEA)
|
||||
facts["目的港"] = "无价港"
|
||||
self.flow.on_text(
|
||||
drive_sea(self.flow,
|
||||
sender_id="sales1",
|
||||
text="海运无价",
|
||||
reply=self._reply,
|
||||
|
||||
Reference in New Issue
Block a user