重启后仍能跳过协同出方案,未成交可同一句带原因;海陆有价卡改为直接生成报价单。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -182,6 +182,9 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
self.assertIn("是否含磁:否", general)
|
||||
self.assertNotIn("是否含电:-", general)
|
||||
self.assertIn("\n\nTMS报价:\nTMS 暂无匹配报价", text)
|
||||
self.assertIn("航线人员可通过“纯文字”告知新报价,或直接将新报价excel表发给智能体。", text)
|
||||
self.assertIn("附件报价单仅作为文件存储于后台,智能体不读取且不将报价内容写进系统", text)
|
||||
self.assertNotIn("产品人员可通过", text)
|
||||
self.assertNotIn("当前工单:", text)
|
||||
self.assertNotIn("贸易条款", text)
|
||||
|
||||
@@ -211,6 +214,10 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
self.assertIn("总销售价:CNY 183370.0", tms_block)
|
||||
self.assertNotIn("时效", tms_block)
|
||||
self.assertIn("报价有效期:2026-09-30", tms_block)
|
||||
self.assertIn("请航线人员回复“确认报价”确定采用TMS报价。", text)
|
||||
self.assertIn("亦可通过“纯文字”告知新报价,或直接将新报价excel表发给智能体。", text)
|
||||
self.assertIn("附件报价单仅作为文件存储于后台,智能体不读取且不将报价内容写进系统", text)
|
||||
self.assertNotIn("请产品人员回复", text)
|
||||
|
||||
def test_air_group_brief_uses_route_fees_not_air_freight_total(self) -> None:
|
||||
"""
|
||||
|
||||
@@ -1123,7 +1123,7 @@ class AirTextInquiryTests(unittest.TestCase):
|
||||
text = copy.deal_card(work_order_no="WO202608210017", quote=quote, business_line="AIR")
|
||||
self.assertIn("询价工单 WO202608210017", text)
|
||||
self.assertIn("报价单已发送,请跟进成交状态", text)
|
||||
self.assertIn("咨询成交状态:请选择成交 / 未成交 / 协商中(未成交需补充原因)。", text)
|
||||
self.assertIn("咨询成交状态:请输入成交 / 未成交 (未成交需补充原因)/ 协商中。", text)
|
||||
self.assertIn("总销售价:CNY 722.5", text)
|
||||
self.assertNotIn("时效", text)
|
||||
self.assertNotIn("- / -", text)
|
||||
|
||||
@@ -269,6 +269,54 @@ class ContinueTicketTests(unittest.TestCase):
|
||||
self.assertEqual(decision.intent, "continue_thread")
|
||||
_ = dispatch_inbound
|
||||
|
||||
def test_skip_collab_after_restart_from_ledger(self) -> None:
|
||||
"""
|
||||
测服 WO202609230031:进程重启后主账把空运已报价收成 wait_adopt,
|
||||
点「不需要协同,直接生成报价单」不能再静默丢掉。
|
||||
"""
|
||||
ledger = MemoryLedger()
|
||||
live = AirTextInquiryFlow(ledger)
|
||||
sink: list[str] = []
|
||||
|
||||
def reply(text: str, extra=None) -> None:
|
||||
sink.append(text)
|
||||
_ = extra
|
||||
|
||||
created = ledger.create_ticket(
|
||||
sender_id="u-skip-restart", business_line="AIR", facts=COMPLETE_AIR
|
||||
)
|
||||
wo = created["work_order_no"]
|
||||
ledger.upsert_quote(
|
||||
work_order_no=wo,
|
||||
quote={
|
||||
"total": "USD 1,280",
|
||||
"eta": "3-5 天",
|
||||
"valid_until": "2026-09-20",
|
||||
"source_label": "TMS 标准报价",
|
||||
},
|
||||
to_status="已报价",
|
||||
)
|
||||
view = ledger.get_for_agent(work_order_no=wo, sender_id="u-skip-restart")
|
||||
self.assertEqual(view.get("wait_phase"), "wait_adopt")
|
||||
|
||||
restarted = AirTextInquiryFlow(ledger)
|
||||
phase = restarted.on_button(
|
||||
sender_id="u-skip-restart",
|
||||
action=copy.BTN_SKIP_COLLAB,
|
||||
reply=reply,
|
||||
card_meta={
|
||||
"event_key": f"skip_collab:{wo}",
|
||||
"task_id": f"price-{wo}-deadbeef",
|
||||
"response_code": "rc-skip-restart",
|
||||
},
|
||||
)
|
||||
self.assertEqual(phase, "wait_adopt")
|
||||
joined = "\n".join(sink)
|
||||
self.assertIn("已跳过内部协同", joined)
|
||||
self.assertIn(f"{wo}报价方案", joined)
|
||||
self.assertEqual(restarted.session_of("u-skip-restart").phase, "wait_adopt")
|
||||
self.assertNotIn(copy.BTN_SKIP_COLLAB, restarted.session_of("u-skip-restart").allowed)
|
||||
|
||||
def test_adopt_excel_after_restart_stays_on_card_ticket(self) -> None:
|
||||
"""点 0040 方案卡不能落到刚续办的 0037:进程无书签时按卡上工单号从主账收回。"""
|
||||
ledger = MemoryLedger()
|
||||
|
||||
@@ -9,7 +9,11 @@ import unittest
|
||||
from agent.ledger.memory_ledger import MemoryLedger
|
||||
from agent.policy import inquiry_copy as copy
|
||||
from agent.policy.air_text_flow import AirTextInquiryFlow, FlowSession
|
||||
from agent.policy.deal_outcome import capture_lost_reason, finish_deal_outcome
|
||||
from agent.policy.deal_outcome import (
|
||||
capture_lost_reason,
|
||||
finish_deal_outcome,
|
||||
harvest_inline_lost_reason,
|
||||
)
|
||||
from agent.policy.sea_text_flow import SeaTextInquiryFlow
|
||||
|
||||
|
||||
@@ -144,6 +148,61 @@ class TestDealOutcome(unittest.TestCase):
|
||||
self.assertEqual(copy.lost_reason_ack("客户不要了"), "已记下未成交原因:客户不要了。")
|
||||
self.assertEqual(copy.lost_reason_ack("价格高。"), "已记下未成交原因:价格高。")
|
||||
|
||||
def test_harvest_inline_lost_reason(self) -> None:
|
||||
self.assertEqual(harvest_inline_lost_reason("未成交,原因客户不想要了"), "客户不想要了")
|
||||
self.assertEqual(harvest_inline_lost_reason("未成交,客户不想要了"), "客户不想要了")
|
||||
self.assertEqual(harvest_inline_lost_reason("未成交:价格太高"), "价格太高")
|
||||
self.assertEqual(harvest_inline_lost_reason("未成交原因客户不想要了"), "客户不想要了")
|
||||
self.assertEqual(harvest_inline_lost_reason("未成交"), "")
|
||||
self.assertEqual(harvest_inline_lost_reason("未成交吗"), "")
|
||||
self.assertEqual(harvest_inline_lost_reason("成交"), "")
|
||||
|
||||
def test_lost_with_inline_reason_finishes(self) -> None:
|
||||
out = finish_deal_outcome(
|
||||
ledger=self.ledger,
|
||||
work_order_no=self.wo,
|
||||
outcome="未成交",
|
||||
facts={},
|
||||
quote={},
|
||||
business_line="SEA",
|
||||
lost_reason="客户不想要了",
|
||||
)
|
||||
self.assertEqual(out.get("phase"), "done")
|
||||
ticket = self.ledger.get_ticket(work_order_no=self.wo)
|
||||
self.assertEqual(ticket.status, "未成交")
|
||||
self.assertEqual(ticket.lost_reason, "客户不想要了")
|
||||
self.assertEqual(ticket.event_wait_kind, "")
|
||||
self.assertEqual(
|
||||
copy.deal_ack("未成交", self.wo, lost_reason="客户不想要了"),
|
||||
"已记录为「未成交」。已记下未成交原因:客户不想要了。",
|
||||
)
|
||||
|
||||
def test_private_lost_and_reason_same_message(self) -> None:
|
||||
flow = AirTextInquiryFlow(self.ledger)
|
||||
flow._save(
|
||||
FlowSession(
|
||||
sender_id="sales1",
|
||||
thread_id="sales1",
|
||||
phase="wait_deal",
|
||||
business_line="SEA",
|
||||
work_order_no=self.wo,
|
||||
allowed=(copy.BTN_DEAL, copy.BTN_LOST, copy.BTN_NEGOTIATE),
|
||||
)
|
||||
)
|
||||
replies: list[str] = []
|
||||
phase = flow.on_text(
|
||||
sender_id="sales1",
|
||||
text="未成交,原因客户不想要了",
|
||||
reply=lambda t, extra=None: replies.append(t),
|
||||
)
|
||||
self.assertEqual(phase, "done")
|
||||
self.assertEqual(
|
||||
self.ledger.get_ticket(work_order_no=self.wo).lost_reason, "客户不想要了"
|
||||
)
|
||||
joined = "\n".join(replies)
|
||||
self.assertIn("客户不想要了", joined)
|
||||
self.assertNotIn("请补充未成交原因", joined)
|
||||
|
||||
def test_negotiate_ack_names_followup_day(self) -> None:
|
||||
text = copy.deal_ack("协商中", "WO202609190007", "2026-09-24 09:00:00")
|
||||
self.assertEqual(
|
||||
|
||||
@@ -338,7 +338,7 @@ class LandRouteOptionsTests(unittest.TestCase):
|
||||
tms_quote=raw,
|
||||
transport_mode="陆运",
|
||||
)
|
||||
tms_block = brief.split("TMS报价:", 1)[1].split("有TMS报价时", 1)[0]
|
||||
tms_block = brief.split("TMS报价:", 1)[1].split("请产品人员回复", 1)[0]
|
||||
self.assertIn("上海-广西壮族自治区-胡志明", tms_block)
|
||||
self.assertIn("上海 → 广西壮族自治区", tms_block)
|
||||
self.assertIn("运输费:CNY 12500.00", tms_block)
|
||||
@@ -350,6 +350,8 @@ class LandRouteOptionsTests(unittest.TestCase):
|
||||
self.assertNotIn("总成本价:-", tms_block)
|
||||
self.assertNotIn("时效:-", tms_block)
|
||||
self.assertNotIn("时效:N+3 / N+3", tms_block)
|
||||
self.assertIn("请产品人员回复“确认报价”确定采用TMS报价。", brief)
|
||||
self.assertNotIn("请航线人员回复", brief)
|
||||
|
||||
def test_h5_lists_segment_costs(self) -> None:
|
||||
from agent.channel.h5.land_route_page import render_land_route_page
|
||||
|
||||
@@ -659,5 +659,57 @@ class LandLostReasonTests(unittest.TestCase):
|
||||
self.assertTrue(any(t == "已记下未成交原因:价格高。" for t in replies))
|
||||
|
||||
|
||||
class LandSkipAfterRestartTests(unittest.TestCase):
|
||||
def test_skip_collab_after_restart_from_ledger(self) -> None:
|
||||
"""
|
||||
陆运已报价主账也会收成 wait_adopt。进程无书签时点「不需要协同」仍须出方案卡。
|
||||
"""
|
||||
ledger = MemoryLedger()
|
||||
facts = {
|
||||
"起运港": "广州",
|
||||
"目的港": "深圳",
|
||||
"品名": "衣服",
|
||||
"体积": "100CBM",
|
||||
"毛重": "50kg",
|
||||
"运输类型": "国内运输拼车",
|
||||
"线路类别": "国内长途/零担",
|
||||
"运输分类": "急件",
|
||||
}
|
||||
created = ledger.create_ticket(
|
||||
sender_id="u-land-skip", business_line="LAND", facts=facts
|
||||
)
|
||||
wo = created["work_order_no"]
|
||||
ledger.upsert_quote(
|
||||
work_order_no=wo,
|
||||
quote={"total": "CNY 800", "source_label": "TMS 标准报价"},
|
||||
to_status="已报价",
|
||||
)
|
||||
view = ledger.get_for_agent(work_order_no=wo, sender_id="u-land-skip")
|
||||
self.assertEqual(view.get("wait_phase"), "wait_adopt")
|
||||
|
||||
restarted = LandTextInquiryFlow(ledger)
|
||||
replies: list[str] = []
|
||||
|
||||
def reply(text: str, extra=None) -> str:
|
||||
replies.append(text)
|
||||
_ = extra
|
||||
return ""
|
||||
|
||||
phase = restarted.on_button(
|
||||
sender_id="u-land-skip",
|
||||
action=copy.BTN_SKIP_COLLAB,
|
||||
reply=reply,
|
||||
card_meta={
|
||||
"event_key": f"skip_collab:{wo}",
|
||||
"task_id": f"price-{wo}-land",
|
||||
"response_code": "rc-land-skip",
|
||||
},
|
||||
)
|
||||
self.assertEqual(phase, "wait_adopt")
|
||||
joined = "\n".join(replies)
|
||||
self.assertIn("已跳过内部协同", joined)
|
||||
self.assertIn(f"{wo}报价方案", joined)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -490,6 +490,7 @@ class QuoteAdjustTests(unittest.TestCase):
|
||||
transport_mode="陆运",
|
||||
)
|
||||
self.assertIn("TMS报价:\nTMS 暂无匹配报价", text)
|
||||
self.assertIn("产品人员可通过“纯文字”告知新报价,或直接将新报价excel表发给智能体。", text)
|
||||
self.assertIn("销售调价:", text)
|
||||
self.assertIn("总销售价:CNY 100.00", text.split("销售调价:", 1)[1])
|
||||
|
||||
|
||||
@@ -579,13 +579,15 @@ class SeaTmsCardTests(unittest.TestCase):
|
||||
self.assertIn("价格明细", joined)
|
||||
self.assertIn("是否拉产品人员进群协同", joined)
|
||||
self.assertIn(copy.BTN_PULL_COLLAB, joined)
|
||||
self.assertIn(copy.BTN_SKIP_COLLAB, joined)
|
||||
self.assertIn(copy.BTN_SKIP_COLLAB_TMS, joined)
|
||||
self.assertNotIn(copy.BTN_SKIP_COLLAB, joined)
|
||||
self.assertEqual(copy.canonical_button(copy.BTN_SKIP_COLLAB_TMS), copy.BTN_SKIP_COLLAB)
|
||||
self.assertNotIn("请手动拉相关人员", joined)
|
||||
self.assertNotIn("若需要咨询内部航线人员", joined)
|
||||
cards = [x for x in self.extras if x.get("msgtype") == "template_card"]
|
||||
self.assertTrue(cards)
|
||||
buttons = [b["text"] for b in cards[-1]["template_card"]["button_list"]]
|
||||
self.assertEqual(buttons, [copy.BTN_PULL_COLLAB, copy.BTN_SKIP_COLLAB])
|
||||
self.assertEqual(buttons, [copy.BTN_PULL_COLLAB, copy.BTN_SKIP_COLLAB_TMS])
|
||||
|
||||
def test_sea_miss_one_card_with_pull_button(self) -> None:
|
||||
facts = dict(COMPLETE_SEA)
|
||||
@@ -1374,6 +1376,9 @@ class SeaGroupCopyTests(unittest.TestCase):
|
||||
self.assertNotIn("贸易条款", collab_block)
|
||||
self.assertNotIn("HS编码", text)
|
||||
self.assertIn("\n\nTMS报价:\nTMS 暂无匹配报价", text)
|
||||
self.assertIn("产品人员可通过“纯文字”告知新报价,或直接将新报价excel表发给智能体。", text)
|
||||
self.assertIn("附件报价单仅作为文件存储于后台,智能体不读取且不将报价内容写进系统", text)
|
||||
self.assertNotIn("航线人员可通过", text)
|
||||
self.assertNotIn("未查到合适报价", text)
|
||||
self.assertNotIn("确认报价", text)
|
||||
|
||||
@@ -1388,8 +1393,10 @@ class SeaGroupCopyTests(unittest.TestCase):
|
||||
has_price=True,
|
||||
)
|
||||
self.assertIn("报价有效期:2026-09-30", text)
|
||||
self.assertIn("有TMS报价时可回复“确认报价”沿用该价", text)
|
||||
self.assertIn("也可直接回复协同报价或上传报价附件", text)
|
||||
self.assertIn("请产品人员回复“确认报价”确定采用TMS报价。", text)
|
||||
self.assertIn("亦可通过“纯文字”告知新报价,或直接将新报价excel表发给智能体。", text)
|
||||
self.assertIn("附件报价单仅作为文件存储于后台,智能体不读取且不将报价内容写进系统", text)
|
||||
self.assertNotIn("请航线人员回复", text)
|
||||
self.assertNotIn("人工报价:", text)
|
||||
|
||||
def test_group_brief_manual_quote_stays_off_tms(self) -> None:
|
||||
@@ -1419,6 +1426,7 @@ class SeaGroupCopyTests(unittest.TestCase):
|
||||
},
|
||||
)
|
||||
self.assertIn("TMS报价:\nTMS 暂无匹配报价", text)
|
||||
self.assertIn("产品人员可通过“纯文字”告知新报价,或直接将新报价excel表发给智能体。", text)
|
||||
self.assertIn("人工报价:\n海运费:USD 100.00\n燃油附加费:USD 15.00", text)
|
||||
self.assertNotIn("确认报价", text)
|
||||
self.assertNotIn("总销售价合计:USD 100.00", text)
|
||||
@@ -1795,7 +1803,7 @@ class SeaGroupOpsTests(unittest.TestCase):
|
||||
"@吴季林\n"
|
||||
"询价工单 WO202609160001\n"
|
||||
"报价单已发送,请确认成交状态\n"
|
||||
"咨询成交状态:请输入成交 / 未成交 / 协商中(未成交需补充原因)"
|
||||
"咨询成交状态:请输入成交 / 未成交 (未成交需补充原因)/ 协商中"
|
||||
),
|
||||
)
|
||||
wo, chat = self._open_group()
|
||||
@@ -1903,6 +1911,25 @@ class SeaGroupOpsTests(unittest.TestCase):
|
||||
self.assertEqual(phase, "done")
|
||||
self.assertEqual(self.ledger.get_ticket(work_order_no=wo).lost_reason, "客人比价了")
|
||||
|
||||
def test_sales_lost_and_reason_same_message(self) -> None:
|
||||
"""群里一句同时说未成交和原因,不再追问。"""
|
||||
wo, chat = self._open_group()
|
||||
phase = handle_group_collab(
|
||||
InboundMessage(
|
||||
sender_id="sales1",
|
||||
message_id="lost-inline-1",
|
||||
content="未成交,原因客户不想要了",
|
||||
chat_id=chat,
|
||||
chat_type="group",
|
||||
),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
)
|
||||
self.assertEqual(phase, "done")
|
||||
ticket = self.ledger.get_ticket(work_order_no=wo)
|
||||
self.assertEqual(ticket.status, "未成交")
|
||||
self.assertEqual(ticket.lost_reason, "客户不想要了")
|
||||
|
||||
def test_sales_can_close_deal_by_text(self) -> None:
|
||||
"""群里销售说成交,要入账并回话,不能装没听见。相近说法也认。"""
|
||||
wo, chat = self._open_group()
|
||||
|
||||
Reference in New Issue
Block a user