落地空运群内协同与锁舱出站。

群里按 BOT 通道激活当前工单、航线报价和成交跟进;没有 TMS 报价编号时用工单字段补齐仍调锁舱,避免被本地规则拦住。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-16 17:25:42 +08:00
co-authored by Cursor
parent 12219cacb1
commit d9029dec89
62 changed files with 6772 additions and 159 deletions
+591 -1
View File
@@ -83,6 +83,10 @@ class FakeGroupClient:
self.sent.append({"chat_id": chat_id, "kind": "card", "template_card": template_card})
return {"ok": True}
def send_text(self, *, touser: str, content: str) -> dict:
self.sent.append({"touser": touser, "content": content, "kind": "app_text"})
return {"ok": True}
def dismiss_group(self, *, chat_id: str, userids: list) -> dict:
self.sent.append({"chat_id": chat_id, "kind": "dismiss", "userids": list(userids)})
return {"ok": True}
@@ -591,6 +595,7 @@ class SeaSkipAndPullTests(unittest.TestCase):
self.assertNotIn("未查到合适报价", group_text)
self.assertIn("\n\n协同补充字段:\n", group_text)
self.assertIn("\n\nTMS报价:\n", group_text)
self.assertNotIn("确认报价", group_text)
class SeaGroupCollabFieldTests(unittest.TestCase):
@@ -694,11 +699,115 @@ class SeaGroupCollabFieldTests(unittest.TestCase):
flow=self.flow,
ledger=self.ledger,
)
self.assertEqual(phase, "wait_tms_choice")
self.assertNotEqual(str(ticket.quote.get("total") or ""), "1200")
texts = [x.get("content") or "" for x in self.groups.sent if x.get("kind") == "text"]
self.assertTrue(any("在TMS基础上改" in t and "不用TMS报价" in t for t in texts))
phase = handle_group_collab(
InboundMessage(
sender_id="prod4",
message_id="g2b",
content="按TMS改一下就行",
chat_id=ticket.collab_chat_id,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
)
self.assertEqual(phase, "product_quote_sent")
self.assertEqual(str(ticket.quote.get("total") or ""), "1200")
ticket = self.ledger.get_ticket(work_order_no=wo)
names = [str(x.get("name") or "") for x in (ticket.quote.get("fee_rows") or ticket.quote.get("fee_lines") or [])]
self.assertTrue(any("空运费" in n or "海运费" in n or "附加" in n for n in names) or "1280" in str(ticket.quote.get("total") or ""))
deal_texts = [x["content"] for x in self.groups.sent if x.get("kind") == "text" and "成交" in (x.get("content") or "")]
self.assertTrue(deal_texts)
def test_keep_tms_reads_json_string_fee_lines(self) -> None:
"""TMS fee_lines 常是 JSON 字符串,在TMS基础上改不能当成没明细。"""
from agent.policy.sea_group_ops import merge_quote_fees
base = {
"source_label": "TMS 标准报价",
"total": "2287.65",
"currency": "CNY",
"fee_lines": (
'[{"name":"海运费","amount":"100","currency":"USD","unit_price":"100"},'
'{"name":"操作费","amount":"680","currency":"CNY","unit_price":"680"},'
'{"name":"文件费","amount":"450","currency":"CNY","unit_price":"450"}]'
),
}
overlay = {
"source_label": "产品报价",
"total": "1200",
"currency": "CNY",
"fee_rows": [
{"name": "操作费", "amount": "700", "currency": "CNY"},
{"name": "文件发", "amount": "500", "currency": "CNY"},
],
}
merged = merge_quote_fees(base, overlay)
by_name = {str(x.get("name") or ""): x for x in (merged.get("fee_rows") or [])}
self.assertEqual(str(by_name["海运费"].get("amount") or ""), "100")
self.assertEqual(str(by_name["操作费"].get("amount") or ""), "700")
self.assertEqual(str(by_name["操作费"].get("unit_price") or ""), "700")
self.assertEqual(str(by_name["文件费"].get("amount") or ""), "500")
self.assertEqual(str(by_name["文件费"].get("unit_price") or ""), "500")
self.assertEqual(str(by_name["海运费"].get("unit_price") or ""), "100")
self.assertNotIn("文件发", by_name)
self.assertEqual(str(merged.get("total") or ""), "2357.65")
self.assertNotEqual(str(merged.get("total") or ""), "1200")
def test_usd_prefix_fee_lines_are_both_kept(self) -> None:
"""海运费:USD 100.00 / 燃油附加费:USD 15.00 两行都要抽出,不能只剩海运费。"""
from agent.llm.extract_text import extract_product_quote
quote = extract_product_quote("海运费:USD 100.00\n燃油附加费:USD 15.00")
rows = {str(x.get("name") or ""): x for x in (quote.get("fee_rows") or [])}
self.assertIn("海运费", rows)
self.assertIn("燃油附加费", rows)
self.assertEqual(str(rows["海运费"].get("amount") or ""), "100.00")
self.assertEqual(str(rows["燃油附加费"].get("amount") or ""), "15.00")
def test_fee_line_text_is_a_product_quote(self) -> None:
"""产品按行改价:码头操作费100CNY / 报关费改为300CNY,必须抽出费用名,不能只当海运费合计。"""
from agent.llm.extract_text import extract_product_quote
quote = extract_product_quote("码头操作费100CNY\n报关费改为300CNY")
rows = {str(x.get("name") or ""): x for x in (quote.get("fee_rows") or [])}
self.assertIn("码头操作费", rows)
self.assertIn("报关费", rows)
self.assertEqual(str(rows["码头操作费"].get("amount") or ""), "100")
self.assertEqual(str(rows["报关费"].get("amount") or ""), "300")
self.assertNotEqual(
[str(x.get("name") or "") for x in (quote.get("fee_rows") or [])],
["海运费"],
)
def test_fee_change_typo_and_colon(self) -> None:
"""操作费改为700 + 文件发改为:500,两行都要抽出,文件发要对上文件费。"""
from agent.llm.extract_text import extract_product_quote
from agent.policy.sea_group_ops import _fee_name_same, merge_quote_fees
quote = extract_product_quote("操作费改为700CNY\n文件发改为:500CNY")
rows = {str(x.get("name") or ""): x for x in (quote.get("fee_rows") or [])}
self.assertEqual(str(rows["操作费"].get("amount") or ""), "700")
self.assertEqual(str(rows["文件发"].get("amount") or ""), "500")
self.assertTrue(_fee_name_same("文件发", "文件费"))
self.assertFalse(_fee_name_same("操作费", "码头操作费"))
merged = merge_quote_fees(
{
"source_label": "TMS标准报价",
"fee_rows": [
{"name": "操作费", "amount": "680", "currency": "CNY"},
{"name": "文件费", "amount": "450", "currency": "CNY"},
],
},
quote,
)
by_name = {str(x.get("name") or ""): x for x in (merged.get("fee_rows") or [])}
self.assertEqual(str(by_name["操作费"].get("amount") or ""), "700")
self.assertEqual(str(by_name["文件费"].get("amount") or ""), "500")
self.assertNotIn("文件发", by_name)
class SeaContinueTests(unittest.TestCase):
def setUp(self) -> None:
@@ -912,6 +1021,53 @@ class SeaGroupCopyTests(unittest.TestCase):
self.assertNotIn("HS编码", text)
self.assertIn("\n\nTMS报价:\nTMS 暂无匹配报价", text)
self.assertNotIn("未查到合适报价", text)
self.assertNotIn("确认报价", text)
def test_group_brief_has_confirm_hint_when_tms_priced(self) -> None:
text = copy.sea_group_brief(
work_order_no="WO202609160004",
facts={"起运港": "DONGGUAN", "目的港": "海防", "品名": "机器设备"},
quote={"total": "2287.65", "currency": "CNY", "valid_until": "2026-09-30"},
collab_facts={},
sales_name="吴季林",
product_names=["林绵勋"],
has_price=True,
)
self.assertIn("报价有效期:2026-09-30", text)
self.assertIn("有TMS报价时可回复“确认报价”沿用该价", text)
self.assertIn("也可直接回复协同报价或上传报价附件", text)
self.assertNotIn("人工报价:", text)
def test_group_brief_manual_quote_stays_off_tms(self) -> None:
text = copy.sea_group_brief(
work_order_no="WO202609160006",
facts={"起运港": "上海港", "目的港": "海防", "品名": "机器设备"},
quote={
"source_label": "产品报价",
"total": "115",
"currency": "USD",
"fee_rows": [
{"name": "海运费", "amount": "100.00", "currency": "USD"},
{"name": "燃油附加费", "amount": "15.00", "currency": "USD"},
],
},
collab_facts={},
sales_name="吴季林",
product_names=["林绵勋", "罗小华", "吴季林"],
has_price=True,
tms_quote={},
product_quote={
"source_label": "产品报价",
"fee_rows": [
{"name": "海运费", "amount": "100.00", "currency": "USD"},
{"name": "燃油附加费", "amount": "15.00", "currency": "USD"},
],
},
)
self.assertIn("TMS报价:\nTMS 暂无匹配报价", text)
self.assertIn("人工报价:\n海运费:USD 100.00\n燃油附加费:USD 15.00", text)
self.assertNotIn("确认报价", text)
self.assertNotIn("总销售价合计:USD 100.00", text)
def test_scheme_card_uses_sea_fields(self) -> None:
facts = {
@@ -950,6 +1106,11 @@ class SeaGroupCopyTests(unittest.TestCase):
self.assertIn("1x40HQ", rows["箱型"])
self.assertNotIn("包装", rows)
def test_lost_ack_asks_reason_only(self) -> None:
text = copy.deal_ack("未成交", "WO202609160004")
self.assertEqual(text, "已记录为「未成交」。请补充未成交原因。")
self.assertNotIn("将关闭智能体跟进", text)
def test_invited_names_carry_role(self) -> None:
text = copy.sea_group_created(
work_order_no="WO202609150019",
@@ -1080,6 +1241,11 @@ class SeaGroupOpsTests(unittest.TestCase):
ticket = self.ledger.get_ticket(work_order_no=wo)
self.assertEqual(ticket.attachments[0]["file_name"], "产品报价.pdf")
self.assertEqual(ticket.status, "已报价")
texts = [x.get("content") or "" for x in self.groups.sent if x.get("kind") == "text"]
self.assertIn(f"工单{wo}的1个附件已保存到后台工单", texts)
self.assertNotIn("附件已保存后台", texts)
ask = [t for t in texts if "在TMS基础上改" in t]
self.assertFalse(ask)
self.assertTrue(any(x.get("kind") == "card" for x in self.groups.sent))
def test_terminal_file_speaks(self) -> None:
@@ -1236,6 +1402,135 @@ class SeaGroupOpsTests(unittest.TestCase):
self.assertEqual(sheet["B2"].value, "8479")
self.assertIn("油:否", str(sheet["B3"].value or ""))
def test_group_deal_text_asks_to_type(self) -> None:
"""群里成交跟进是文字版:@销售、请确认、请输入,不要报价合计和请选择。"""
text = copy.group_deal_followup(sales_name="吴季林", work_order_no="WO202609160001")
self.assertEqual(
text,
(
"@吴季林\n"
"询价工单 WO202609160001\n"
"报价单已发送,请确认成交状态\n"
"咨询成交状态:请输入成交 / 未成交 / 协商中(未成交需补充原因)"
),
)
wo, chat = self._open_group()
handle_group_collab(
InboundMessage(
sender_id="prod4",
message_id="xls-deal-copy",
content="",
msg_type="file",
chat_id=chat,
chat_type="group",
media={"filename": "产品报价.pdf", "media_id": "mid-deal-copy"},
),
flow=self.flow,
ledger=self.ledger,
)
texts = [x.get("content") or "" for x in self.groups.sent if x.get("kind") == "text"]
follow = [t for t in texts if "请确认成交状态" in t and "请输入成交" in t]
self.assertTrue(follow)
self.assertTrue(any(t.startswith("@") and f"询价工单 {wo}" in t for t in follow))
self.assertFalse(any("请选择成交" in t or "请跟进成交状态" in t or "总销售价合计" in t for t in follow))
def test_group_text_deal_does_not_go_to_private_card(self) -> None:
"""群里打「成交」不能当私聊点卡丢掉,必须走群成交。"""
from unittest.mock import patch
from agent.routing.decision import RouteDecision
from agent.routing.dispatch import dispatch_inbound
wo, chat = self._open_group()
with patch("agent.handlers.group_collab.get_sea_text_flow", return_value=self.flow):
result = dispatch_inbound(
InboundMessage(
sender_id="sales1",
message_id="deal-dispatch-1",
content="成交",
chat_id=chat,
chat_type="group",
),
RouteDecision(intent="ordinary_text_other"),
)
self.assertEqual(result, "group_collab")
ticket = self.ledger.get_ticket(work_order_no=wo)
self.assertEqual(ticket.status, "已成交")
def test_sales_can_close_deal_after_skinny_snapshot(self) -> None:
"""按群找回的工单只有字段、没有报价时,未成交也要能入账。"""
wo, chat = self._open_group()
real_find = self.ledger.find_by_collab_chat
def skinny(*, chat_id: str):
ticket = real_find(chat_id=chat_id)
if ticket is None:
return None
return {
"found": True,
"work_order_no": ticket.work_order_no,
"sales_wecom_id": ticket.sales_wecom_id,
"collab_chat_id": ticket.collab_chat_id,
"collab_member_ids": list(ticket.collab_member_ids),
"facts": dict(ticket.facts),
}
self.ledger.find_by_collab_chat = skinny # type: ignore[method-assign]
self.flow._by_ticket.clear()
self.flow._by_sender.clear()
phase = handle_group_collab(
InboundMessage(
sender_id="sales1",
message_id="lost-skinny-1",
content="未成交",
chat_id=chat,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
)
self.assertEqual(phase, "done")
self.assertEqual(self.ledger.get_ticket(work_order_no=wo).status, "未成交")
def test_sales_can_close_deal_by_text(self) -> None:
"""群里销售说成交,要入账并回话,不能装没听见。相近说法也认。"""
wo, chat = self._open_group()
phase = handle_group_collab(
InboundMessage(
sender_id="sales1",
message_id="deal-text-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, "已成交")
texts = [x.get("content") or "" for x in self.groups.sent if x.get("kind") == "text"]
self.assertTrue(any("已记录为「已成交」" in t for t in texts))
def test_product_cannot_close_deal_by_text(self) -> None:
wo, chat = self._open_group()
phase = handle_group_collab(
InboundMessage(
sender_id="prod4",
message_id="deal-text-2",
content="这个成了",
chat_id=chat,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
)
self.assertEqual(phase, "not_sales")
ticket = self.ledger.get_ticket(work_order_no=wo)
self.assertNotEqual(ticket.status, "已成交")
texts = [x.get("content") or "" for x in self.groups.sent if x.get("kind") == "text"]
self.assertTrue(any("只有负责该工单的销售" in t for t in texts))
def test_stale_deal_card_speaks(self) -> None:
from agent.policy.sea_group_ops import explain_deal_click
@@ -1254,6 +1549,301 @@ class SeaGroupOpsTests(unittest.TestCase):
texts = [x.get("content") or "" for x in self.groups.sent if x.get("kind") == "text"]
self.assertTrue(any("已过期" in t for t in texts))
def test_sales_who_is_also_product_can_quote(self) -> None:
"""拉群销售若后台也是产品·海运,群里按行改价必须收下,不能装没看见。"""
self.ledger.set_sea_staff(
[
{
"name": "吴吉林",
"wecomId": "sales1",
"roleCode": "sea",
"routes": "海运·全部线路",
"status": "active",
},
{
"name": "赵产品",
"wecomId": "prod4",
"roleCode": "sea",
"routes": "海运·全部线路",
"status": "active",
},
]
)
wo, chat = self._open_group()
ticket = self.ledger.get_ticket(work_order_no=wo)
self.assertIn("sales1", ticket.product_ids)
phase = handle_group_collab(
InboundMessage(
sender_id="sales1",
message_id="dual-role-quote",
content="码头操作费100CNY\n报关费改为300CNY",
chat_id=chat,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
injected_intent="product_quote",
)
self.assertEqual(phase, "wait_tms_choice")
def test_existing_ticket_sales_later_product_role_can_quote(self) -> None:
"""已建群后销售才挂上产品岗:仍按后台岗位认,不能只看建群时的产品名单。"""
wo, chat = self._open_group()
ticket = self.ledger.get_ticket(work_order_no=wo)
self.assertNotIn("sales1", ticket.product_ids)
self.ledger.set_sea_staff(
[
{
"name": "吴吉林",
"wecomId": "sales1",
"roleCode": "sea",
"routes": "海运·全部线路",
"status": "active",
}
]
)
phase = handle_group_collab(
InboundMessage(
sender_id="sales1",
message_id="later-product-quote",
content="码头操作费100CNY\n报关费改为300CNY",
chat_id=chat,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
injected_intent="product_quote",
)
self.assertEqual(phase, "wait_tms_choice")
def test_product_fee_line_quote_asks_then_keeps_tms(self) -> None:
"""有 TMS 价时先问;选「在基础上改」才出单,没点名的 TMS 费用保留。"""
wo, chat = self._open_group()
ticket = self.ledger.get_ticket(work_order_no=wo)
ticket.quote = {
"total": "2287.65",
"currency": "CNY",
"source_label": "TMS 标准报价",
"fee_lines": [
{"name": "海运费", "amount": "100.00", "currency": "USD"},
{"name": "操作费", "amount": "680.00", "currency": "CNY"},
{"name": "报关费", "amount": "250.00", "currency": "CNY"},
],
}
phase = handle_group_collab(
InboundMessage(
sender_id="prod4",
message_id="fee-line-1",
content="码头操作费100CNY\n报关费改为300CNY",
chat_id=chat,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
injected_intent="product_quote",
)
self.assertEqual(phase, "wait_tms_choice")
texts = [x.get("content") or "" for x in self.groups.sent if x.get("kind") == "text"]
self.assertTrue(any("在TMS基础上改" in t and "不用TMS报价" in t for t in texts))
self.assertFalse(any("没有识别到报价" in t for t in texts))
ticket = self.ledger.get_ticket(work_order_no=wo)
before = {str(x.get("name") or ""): x for x in (ticket.quote.get("fee_lines") or [])}
self.assertEqual(str(before["报关费"].get("amount") or ""), "250.00")
phase = handle_group_collab(
InboundMessage(
sender_id="prod4",
message_id="fee-line-2",
content="在TMS基础上改",
chat_id=chat,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
)
self.assertEqual(phase, "product_quote_sent")
ticket = self.ledger.get_ticket(work_order_no=wo)
names = {
str(x.get("name") or ""): x
for x in (ticket.quote.get("fee_lines") or ticket.quote.get("fee_rows") or [])
}
self.assertEqual(str(names["报关费"].get("amount") or ""), "300")
self.assertEqual(str(names["操作费"].get("amount") or ""), "680.00")
self.assertEqual(str(names["码头操作费"].get("amount") or ""), "100")
self.assertEqual(str(names["海运费"].get("amount") or ""), "100.00")
def test_product_text_on_miss_is_manual_not_tms(self) -> None:
"""TMS 无价时产品文字报价:摘要 TMS 仍是暂无,人工报价两行齐全。"""
facts = dict(COMPLETE_SEA)
facts["目的港"] = "无价港"
self.flow.on_text(
sender_id="sales1",
text="海运无价",
reply=self._reply,
injected_facts=facts,
injected_mode="SEA",
)
self.flow.on_button(
sender_id="sales1",
action=copy.BTN_PULL_COLLAB,
reply=self._reply,
)
sess = self.flow.session_of("sales1")
chat = sess.collab_chat_id
phase = handle_group_collab(
InboundMessage(
sender_id="prod4",
message_id="miss-manual-1",
content="海运费:USD 100.00\n燃油附加费:USD 15.00",
chat_id=chat,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
)
self.assertEqual(phase, "product_quote_sent")
briefs = [x.get("content") or "" for x in self.groups.sent if x.get("kind") == "text"]
text = "\n".join(briefs)
self.assertIn("TMS 暂无匹配报价", text)
self.assertIn("人工报价:", text)
self.assertIn("海运费:USD 100.00", text)
self.assertIn("燃油附加费:USD 15.00", text)
self.assertNotIn("确认报价", text)
def test_confirm_tms_saves_sheet_and_notifies_app(self) -> None:
"""有 TMS 价时产品回确认报价:模板进后台,群里告诉大家已保存。"""
from agent.routing.group_intent import INTENT_CONFIRM_TMS, classify_group_text
self.assertEqual(classify_group_text("确认报价", allow_network=False), INTENT_CONFIRM_TMS)
wo, chat = self._open_group()
phase = handle_group_collab(
InboundMessage(
sender_id="prod4",
message_id="confirm-tms-1",
content="确认报价",
chat_id=chat,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
)
self.assertEqual(phase, "quote_confirmed")
ticket = self.ledger.get_ticket(work_order_no=wo)
self.assertEqual(ticket.status, "已报价")
self.assertTrue(ticket.attachments)
self.assertIn("quote", str(ticket.attachments[0].get("file_name") or "").lower())
texts = [x.get("content") or "" for x in self.groups.sent if x.get("kind") == "text"]
self.assertIn(f"工单{wo}的1个附件已保存到后台工单", texts)
self.assertTrue(any("成交" in t for t in texts))
def test_first_option_text_keeps_tms(self) -> None:
"""问句列了 1/2 时,回「第一个」要按在 TMS 基础上改,不能装没听见。"""
from agent.routing.group_intent import INTENT_KEEP_TMS, classify_group_text
self.assertEqual(classify_group_text("第一个", allow_network=False), INTENT_KEEP_TMS)
wo, chat = self._open_group()
ticket = self.ledger.get_ticket(work_order_no=wo)
ticket.quote = {
"total": "2287.65",
"currency": "CNY",
"source_label": "TMS 标准报价",
"fee_lines": [
{"name": "操作费", "amount": "680", "currency": "CNY", "unit_price": "680"},
{"name": "文件费", "amount": "450", "currency": "CNY", "unit_price": "450"},
],
}
handle_group_collab(
InboundMessage(
sender_id="prod4",
message_id="first-opt-1",
content="操作费改为700CNY / 文件费改为500CNY",
chat_id=chat,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
injected_intent="product_quote",
)
phase = handle_group_collab(
InboundMessage(
sender_id="prod4",
message_id="first-opt-2",
content="第一个",
chat_id=chat,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
)
self.assertEqual(phase, "product_quote_sent")
ticket = self.ledger.get_ticket(work_order_no=wo)
names = {
str(x.get("name") or ""): x
for x in (ticket.quote.get("fee_lines") or ticket.quote.get("fee_rows") or [])
}
self.assertEqual(str(names["操作费"].get("amount") or ""), "700")
self.assertEqual(str(names["文件费"].get("amount") or ""), "500")
def test_other_product_can_drop_tms(self) -> None:
"""任一产品都能选;说不用 TMS 就只留刚报的价。"""
self.ledger.set_sea_staff(
[
{
"name": "赵产品",
"wecomId": "prod4",
"roleCode": "sea",
"routes": "海运·全部线路",
"status": "active",
},
{
"name": "钱产品",
"wecomId": "prod5",
"roleCode": "sea",
"routes": "海运·全部线路",
"status": "active",
},
]
)
wo, chat = self._open_group()
ticket = self.ledger.get_ticket(work_order_no=wo)
ticket.quote = {
"source_label": "TMS 标准报价",
"fee_lines": [
{"name": "海运费", "amount": "100.00", "currency": "USD"},
{"name": "报关费", "amount": "250.00", "currency": "CNY"},
],
}
handle_group_collab(
InboundMessage(
sender_id="prod4",
message_id="drop-1",
content="报关费改为300CNY",
chat_id=chat,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
injected_intent="product_quote",
)
phase = handle_group_collab(
InboundMessage(
sender_id="prod5",
message_id="drop-2",
content="不要TMS的价,按我刚才报的",
chat_id=chat,
chat_type="group",
),
flow=self.flow,
ledger=self.ledger,
)
self.assertEqual(phase, "product_quote_sent")
ticket = self.ledger.get_ticket(work_order_no=wo)
names = {
str(x.get("name") or ""): x
for x in (ticket.quote.get("fee_lines") or ticket.quote.get("fee_rows") or [])
}
self.assertEqual(list(names), ["报关费"])
self.assertEqual(str(names["报关费"].get("amount") or ""), "300")
def test_parse_group_file_fills_media(self) -> None:
from agent.channel.wecom.parse import parse_plain_xml