修好多段含空运的群跟单、锁舱话术、相册报价通道,以及附件上传成功后电脑端关不掉 H5。
多段激活后无需再带工单号;含空运群回复走机器人;复制补问清单时空货好时间不再吃成下一行编号。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -614,6 +614,45 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(phase, "need_work_order")
|
||||
|
||||
def test_activated_multi_quote_follows_without_work_order(self) -> None:
|
||||
"""多段激活后,报价不必再带工单号,也不要回「请先 @我」。"""
|
||||
import json
|
||||
|
||||
created = self.ledger.create_ticket(
|
||||
sender_id="sales1",
|
||||
business_line="MULTI",
|
||||
facts={
|
||||
"segments_json": json.dumps(
|
||||
[
|
||||
{"index": 1, "mode": "LAND", "facts": {"起运港": "北京", "目的港": "上海"}},
|
||||
{"index": 2, "mode": "AIR", "facts": {"起运港": "上海", "目的港": "北宁"}},
|
||||
],
|
||||
ensure_ascii=False,
|
||||
)
|
||||
},
|
||||
)
|
||||
wo = created["work_order_no"]
|
||||
self.ledger.bind_collab_group(
|
||||
work_order_no=wo,
|
||||
chat_id="wr_multi",
|
||||
member_ids=["sales1", "air1"],
|
||||
product_ids=["air1"],
|
||||
product_names=["HangXian"],
|
||||
)
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="air1", text="空运费 100USD", chat="wr_multi", mid="mq1"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
injected_intent="product_quote",
|
||||
)
|
||||
self.assertEqual(phase, "segment_quoted")
|
||||
joined = "\n".join(self._texts())
|
||||
self.assertNotIn("请先 @我", joined)
|
||||
self.assertNotIn("并发送要跟的工单号", joined)
|
||||
ticket = self.ledger.get_ticket(work_order_no=wo)
|
||||
air_quote = (ticket.quote or {}).get("segment_quotes", {}).get("AIR") or {}
|
||||
self.assertTrue(air_quote.get("total") or air_quote.get("fee_rows"))
|
||||
|
||||
def test_sales_can_fill_battery(self) -> None:
|
||||
wo = self._create_air()
|
||||
handle_air_group(
|
||||
|
||||
@@ -108,6 +108,8 @@ class AttachmentUploadH5Tests(unittest.TestCase):
|
||||
self.assertIn("closeWindow", ok)
|
||||
self.assertIn("wecom-jssdk", ok)
|
||||
self.assertIn("jsapi-signature", ok)
|
||||
self.assertIn("function isPhone()", ok)
|
||||
self.assertNotIn("window.close()", ok)
|
||||
self.assertNotIn("closeWindow", page)
|
||||
self.assertNotIn("closeWindow", dead)
|
||||
|
||||
|
||||
@@ -436,6 +436,29 @@ class ClarifyPasteTests(unittest.TestCase):
|
||||
self.assertNotIn("包装方式", facts)
|
||||
self.assertNotEqual(facts.get("包装方式"), "托盘、散货")
|
||||
|
||||
def test_blank_cargo_ready_does_not_take_next_item_number(self) -> None:
|
||||
"""货好时间空着时,下一行「4.提货地址」的编号不能写进货好时间。示例行也不算填了。"""
|
||||
text = """
|
||||
1.包装类型(必填):托盘
|
||||
(如:托盘、散货)
|
||||
2.货源地(非必填):
|
||||
(如:东莞)
|
||||
3.货好时间(非必填):
|
||||
(如:随时可提)
|
||||
4.提货地址(非必填):
|
||||
(如:东莞仓库)
|
||||
5.收货地址(非必填):
|
||||
(如:马尼拉)
|
||||
6.贸易条款(非必填):
|
||||
(如:DDP、DAP、CIF、CIP)
|
||||
"""
|
||||
facts = extract_inquiry_snapshot(text, allow_b=False)["facts"]
|
||||
self.assertEqual(facts.get("包装方式") or facts.get("包装类型"), "托盘")
|
||||
self.assertNotIn("货好时间", facts)
|
||||
self.assertNotIn("提货地址", facts)
|
||||
self.assertNotIn("货源地", facts)
|
||||
self.assertNotIn("贸易条款", facts)
|
||||
|
||||
|
||||
class ImageOptionalClipTests(unittest.TestCase):
|
||||
def test_numbered_extra_lines_do_not_enter_fields(self) -> None:
|
||||
|
||||
@@ -327,7 +327,13 @@ class LandGroupCollabTests(unittest.TestCase):
|
||||
injected_intent=INTENT_ADJUST,
|
||||
)
|
||||
self.assertEqual(phase, "adjust_not_sales")
|
||||
self.assertIn("只有这张单的销售能调价", self._group_texts())
|
||||
self.assertIn("只有这张单的销售能调价。产品请继续报价,价格未改。", self._group_texts())
|
||||
|
||||
def test_air_room_adjust_names_airline(self) -> None:
|
||||
self.assertEqual(
|
||||
copy.adjust_only_sales(air_room=True),
|
||||
"只有这张单的销售能调价。航线请继续报价,价格未改。",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -11,6 +11,10 @@ ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if ROOT not in sys.path:
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from agent.channel.wecom.models import InboundMessage
|
||||
from agent.handlers.group_collab import handle_group_inbound
|
||||
from agent.ledger.memory_ledger import MemoryLedger
|
||||
from agent.policy.multi_group_ops import deal_text, handle_multi_inbound
|
||||
from agent.policy.multi_segments import Segment
|
||||
@@ -387,7 +391,8 @@ class QuoteTests(unittest.TestCase):
|
||||
self.assertEqual(len(self.client.files), 2)
|
||||
self.assertTrue(self.ledger.last_render.get("ok"))
|
||||
|
||||
def test_lock_on_air_segment_does_not_wait_for_other_quotes(self) -> None:
|
||||
def test_lock_on_air_segment_says_multi_unsupported(self) -> None:
|
||||
"""多段含空运时航线锁舱,原因写成暂不支持,不再说不是空运单。"""
|
||||
created = self.ledger.create_ticket(
|
||||
sender_id="sales",
|
||||
business_line="MULTI",
|
||||
@@ -412,9 +417,128 @@ class QuoteTests(unittest.TestCase):
|
||||
text=f"{no}锁舱",
|
||||
sheet_fn=self.sheet,
|
||||
)
|
||||
self.assertEqual(phase, "lock_ok")
|
||||
self.assertEqual(phase, "multi_lock_unsupported")
|
||||
joined = "\n".join(self.client.texts)
|
||||
self.assertIn(f"工单{no} 空运锁舱失败。", joined)
|
||||
self.assertIn("操作人:航线甲", joined)
|
||||
self.assertIn("锁舱失败的原因:多段询价,暂时不支持锁舱", joined)
|
||||
self.assertNotIn("不是空运单,不能锁舱", joined)
|
||||
self.assertNotIn("已通知IT", joined)
|
||||
self.assertNotIn("请稍等", joined)
|
||||
self.assertFalse(any("其他段报完" in text for text in self.client.texts))
|
||||
|
||||
|
||||
class _Bot:
|
||||
"""替掉真实桥,记下这一次群里实际会发出去的字。"""
|
||||
|
||||
made: list["_Bot"] = []
|
||||
|
||||
def __init__(self, *args: object, **kwargs: object) -> None:
|
||||
_ = args, kwargs
|
||||
self.texts: list[str] = []
|
||||
_Bot.made.append(self)
|
||||
|
||||
def send_group(self, *, chat_id: str, content: str, mention_userids: list[str] | None = None) -> dict:
|
||||
_ = chat_id, mention_userids
|
||||
self.texts.append(content)
|
||||
return {"ok": True}
|
||||
|
||||
def send_group_file(self, **kwargs: object) -> dict:
|
||||
_ = kwargs
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
class ArchiveSenderTests(unittest.TestCase):
|
||||
"""会话存档听到的报价:含空运走机器人,海运陆运仍走应用自己建的群。"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
_Bot.made = []
|
||||
self.ledger = MemoryLedger()
|
||||
self.app = _Client()
|
||||
self.flow = _Flow(self.ledger, self.app)
|
||||
|
||||
def _message(self, chat_id: str, sender_id: str, text: str) -> InboundMessage:
|
||||
return InboundMessage(
|
||||
sender_id=sender_id,
|
||||
message_id="m1",
|
||||
content=text,
|
||||
chat_id=chat_id,
|
||||
chat_type="group",
|
||||
)
|
||||
|
||||
def test_air_multi_quote_goes_through_bot(self) -> None:
|
||||
created = self.ledger.create_ticket(
|
||||
sender_id="sales",
|
||||
business_line="MULTI",
|
||||
facts={
|
||||
"segments_json": json.dumps(
|
||||
[
|
||||
{"index": 1, "mode": "LAND", "facts": {"线路类别": "国内长途/零担"}},
|
||||
{"index": 2, "mode": "AIR", "facts": {"目的港": "北宁"}},
|
||||
],
|
||||
ensure_ascii=False,
|
||||
)
|
||||
},
|
||||
)
|
||||
self.ledger.upsert_staff({"wecomId": "sales", "name": "销售甲", "roleCode": "sales", "status": "active"})
|
||||
self.ledger.upsert_staff({"wecomId": "air1", "name": "航线甲", "roleCode": "air", "status": "active"})
|
||||
no = created["work_order_no"]
|
||||
self.ledger.bind_collab_group(
|
||||
work_order_no=no,
|
||||
chat_id="bot-room",
|
||||
member_ids=["sales", "air1"],
|
||||
product_names=["航线甲"],
|
||||
product_ids=["air1"],
|
||||
)
|
||||
with patch("agent.policy.multi_group_ops.send_filled_sheet", return_value={"ok": True}):
|
||||
with patch("agent.channel.aibot.reply.AibotReplyClient", _Bot):
|
||||
phase = handle_group_inbound(
|
||||
self._message("bot-room", "air1", "空运费 9900CNY"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
)
|
||||
self.assertEqual(phase, "segment_quoted")
|
||||
self.assertFalse(self.app.texts)
|
||||
self.assertTrue(_Bot.made)
|
||||
self.assertTrue(any("报价单已发出" in text for text in _Bot.made[-1].texts))
|
||||
|
||||
def test_sea_land_quote_stays_on_app_client(self) -> None:
|
||||
created = self.ledger.create_ticket(
|
||||
sender_id="sales",
|
||||
business_line="MULTI",
|
||||
facts={
|
||||
"segments_json": json.dumps(
|
||||
[
|
||||
{"index": 1, "mode": "SEA", "facts": {"起运港": "广州港", "目的港": "巴生港"}},
|
||||
{"index": 2, "mode": "LAND", "facts": {"线路类别": "国内长途/零担"}},
|
||||
],
|
||||
ensure_ascii=False,
|
||||
)
|
||||
},
|
||||
)
|
||||
self.ledger.set_sea_staff(
|
||||
[{"wecomId": "sea1", "name": "海产", "roleCode": "sea", "status": "active", "routes": "海运·全部线路"}]
|
||||
)
|
||||
self.ledger.upsert_staff({"wecomId": "sales", "name": "销售甲", "roleCode": "sales", "status": "active"})
|
||||
no = created["work_order_no"]
|
||||
self.ledger.bind_collab_group(
|
||||
work_order_no=no,
|
||||
chat_id="app-room",
|
||||
member_ids=["sales", "sea1"],
|
||||
product_names=["海产"],
|
||||
product_ids=["sea1"],
|
||||
)
|
||||
with patch("agent.policy.multi_group_ops.send_filled_sheet", return_value={"ok": True}):
|
||||
with patch("agent.channel.aibot.reply.AibotReplyClient", _Bot):
|
||||
phase = handle_group_inbound(
|
||||
self._message("app-room", "sea1", "海运费 100CNY"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
)
|
||||
self.assertEqual(phase, "segment_quoted")
|
||||
self.assertFalse(_Bot.made)
|
||||
self.assertTrue(any("报价单已发出" in text for text in self.app.texts))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -379,6 +379,9 @@ class TestConfirmSystemException(unittest.TestCase):
|
||||
)
|
||||
card = queued[0]["payload"]["template_card"]
|
||||
values = {row["keyname"]: row["value"] for row in card["horizontal_content_list"]}
|
||||
self.assertEqual(values["报错原因"], "LOCK_FAIL")
|
||||
self.assertNotIn("错误码", values)
|
||||
self.assertIn("报错原因:LOCK_FAIL", queued[0]["content"])
|
||||
self.assertEqual(values["影响范围"], "锁舱失败,工单状态保持不变")
|
||||
self.assertEqual(card["main_title"]["title"], "系统异常通知")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user