空运成交后可在本群继续锁舱;调价同步舱位费用桶;出单按空运段骨架落费并保留陆运车型尺寸;导出文件名乱码修复。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1055,6 +1055,8 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(phase, "done")
|
||||
self.assertEqual(self.ledger.get_ticket(work_order_no=wo).status, "已成交")
|
||||
self.assertTrue(any(f"{wo}请锁舱@HangXian" in t for t in self._texts()))
|
||||
self.assertTrue(any("已记录为「已成交」" in t for t in self._texts()))
|
||||
|
||||
def test_lock_requires_work_order(self) -> None:
|
||||
phase = handle_air_group(
|
||||
@@ -1074,8 +1076,9 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
ledger=self.ledger,
|
||||
injected_intent="air_lock",
|
||||
)
|
||||
self.assertEqual(phase, "no_quote")
|
||||
self.assertTrue(any("还没有报价,不能锁舱" in t for t in self._texts()))
|
||||
self.assertEqual(phase, "lock_need_deal")
|
||||
self.assertTrue(any("请销售@WuJiLin先确认工单「已成交」" in t for t in self._texts()))
|
||||
self.assertFalse(any("还没有报价,不能锁舱" in t for t in self._texts()))
|
||||
self.assertFalse(any("AIR-OPT-01" in t for t in self._texts()))
|
||||
self.assertFalse(any("执行成功" in t for t in self._texts()))
|
||||
|
||||
@@ -1087,7 +1090,7 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
"source": "manual",
|
||||
"source_label": "产品报价",
|
||||
},
|
||||
status="已报价",
|
||||
status="已成交",
|
||||
)
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo}锁舱", chat="wr_air_lock_manual", mid="lm1"),
|
||||
@@ -1136,7 +1139,7 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
},
|
||||
],
|
||||
},
|
||||
status="已报价",
|
||||
status="已成交",
|
||||
)
|
||||
calls: list[dict] = []
|
||||
orig = self.ledger.lock_cabin
|
||||
@@ -1167,7 +1170,8 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
"source": "TMS",
|
||||
"source_label": "TMS 标准报价",
|
||||
"quoteId": "QT-AIR-23",
|
||||
}
|
||||
},
|
||||
status="已成交",
|
||||
)
|
||||
calls: list[dict] = []
|
||||
orig = self.ledger.lock_cabin
|
||||
@@ -1217,7 +1221,8 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
"source": "TMS",
|
||||
"source_label": "TMS 标准报价",
|
||||
"quoteId": "QT-AIR-24",
|
||||
}
|
||||
},
|
||||
status="已成交",
|
||||
)
|
||||
calls: list[dict] = []
|
||||
orig = self.ledger.lock_cabin
|
||||
@@ -1257,7 +1262,8 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
"total": "CNY 1800",
|
||||
"source": "manual",
|
||||
"source_label": "产品报价",
|
||||
}
|
||||
},
|
||||
status="已成交",
|
||||
)
|
||||
calls: list[dict] = []
|
||||
orig = self.ledger.lock_cabin
|
||||
@@ -1285,7 +1291,8 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
self.assertEqual(calls[0].get("flight_no"), "MU5123")
|
||||
self.assertFalse(any("锁舱参数不完整" in t for t in self._texts()))
|
||||
|
||||
def test_lock_success_and_release_after_deal_blocked(self) -> None:
|
||||
def test_deal_then_lock_then_release(self) -> None:
|
||||
"""成交前不能锁。成交后点名航线,锁成功可取消,不再问一次是否成交。"""
|
||||
wo = self._create_air()
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo}锁舱", chat="wr_air_1", mid="l2"),
|
||||
@@ -1293,36 +1300,64 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
ledger=self.ledger,
|
||||
injected_intent="air_lock",
|
||||
)
|
||||
self.assertEqual(phase, "lock_ok")
|
||||
self.assertTrue(any("执行成功" in t for t in self._texts()))
|
||||
handle_air_group(
|
||||
self.assertEqual(phase, "lock_need_deal")
|
||||
self.assertTrue(any("请销售@WuJiLin先确认工单「已成交」" in t for t in self._texts()))
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="sales1", text="成交", chat="wr_air_1", mid="l3"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
injected_intent="deal_won",
|
||||
)
|
||||
self.assertEqual(phase, "done")
|
||||
self.assertTrue(any(f"{wo}请锁舱@HangXian" in t for t in self._texts()))
|
||||
self.groups.sent.clear()
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo}释放舱位", chat="wr_air_1", mid="l4"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
injected_intent="air_release",
|
||||
)
|
||||
self.assertEqual(phase, "release_deal_blocked")
|
||||
self.assertTrue(any("已成交" in t for t in self._texts()))
|
||||
|
||||
def test_lost_can_release(self) -> None:
|
||||
wo = self._create_air()
|
||||
handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo}锁舱", chat="wr_air_1", mid="r0"),
|
||||
self._msg(sender="air1", text=f"{wo}锁舱", chat="wr_air_1", mid="l4"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
injected_intent="air_lock",
|
||||
)
|
||||
handle_air_group(
|
||||
self._msg(sender="sales1", text="未成交", chat="wr_air_1", mid="r1"),
|
||||
self.assertEqual(phase, "lock_ok")
|
||||
self.assertTrue(any("执行成功" in t for t in self._texts()))
|
||||
self.assertFalse(any("请确认成交状态" in t for t in self._texts()))
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo}取消舱位", chat="wr_air_1", mid="l5"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
injected_intent="deal_lost",
|
||||
)
|
||||
self.assertEqual(phase, "release_ok")
|
||||
self.assertTrue(any("取消舱位成功" in t for t in self._texts()))
|
||||
|
||||
def test_negotiating_and_lost_cannot_lock(self) -> None:
|
||||
wo_talk = self._create_air(status="协商中")
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo_talk}锁舱", chat="wr_air_talk", mid="ng1"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
injected_intent="air_lock",
|
||||
)
|
||||
self.assertEqual(phase, "lock_negotiating")
|
||||
self.assertTrue(any("还在协商中,成交后才能锁舱" in t for t in self._texts()))
|
||||
self.assertFalse(any("先确认工单「已成交」" in t for t in self._texts()))
|
||||
wo_lost = self._create_air(status="未成交")
|
||||
self.groups.sent.clear()
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo_lost}锁舱", chat="wr_air_lost", mid="ls1"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
injected_intent="air_lock",
|
||||
)
|
||||
self.assertEqual(phase, "lock_lost")
|
||||
self.assertTrue(any("已是「未成交」,不能锁舱" in t for t in self._texts()))
|
||||
self.assertFalse(any("先确认工单「已成交」" in t for t in self._texts()))
|
||||
|
||||
def test_lost_can_release(self) -> None:
|
||||
"""未成交不能新锁,但改规则前已经锁上的仍可取消。"""
|
||||
wo = self._create_air(status="未成交")
|
||||
self.ledger.patch_collab_facts(
|
||||
work_order_no=wo,
|
||||
facts={"__air_lock_id": "CA-OLD-LOCK"},
|
||||
sender_id="air1",
|
||||
)
|
||||
before = len(self._texts())
|
||||
phase = handle_air_group(
|
||||
@@ -1338,7 +1373,7 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
|
||||
def test_release_wording_asks_before_cancel(self) -> None:
|
||||
"""说出释放舱位只提问。销售回「是」不算,航线确认后才取消。"""
|
||||
wo = self._create_air()
|
||||
wo = self._create_air(status="已成交")
|
||||
handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo}锁舱", chat="wr_air_ask", mid="ask0"),
|
||||
flow=self.flow,
|
||||
@@ -1370,7 +1405,7 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
self.assertIn("已通知TMS执行空运取消舱位成功", "\n".join(self._texts()))
|
||||
|
||||
def test_release_wording_can_be_declined(self) -> None:
|
||||
wo = self._create_air()
|
||||
wo = self._create_air(status="已成交")
|
||||
handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo}锁舱", chat="wr_air_ask_no", mid="no0"),
|
||||
flow=self.flow,
|
||||
@@ -1393,7 +1428,7 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
self.assertNotIn("已通知TMS执行空运取消舱位成功", "\n".join(self._texts()))
|
||||
|
||||
def test_cancel_cabin_words_release_immediately(self) -> None:
|
||||
wo = self._create_air()
|
||||
wo = self._create_air(status="已成交")
|
||||
handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo}锁舱", chat="wr_air_now", mid="now0"),
|
||||
flow=self.flow,
|
||||
@@ -1430,7 +1465,7 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
"status": "active",
|
||||
}
|
||||
)
|
||||
wo = self._create_air()
|
||||
wo = self._create_air(status="已成交")
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="sales1", text=f"{wo}锁舱", chat="wr_air_1", mid="s2"),
|
||||
flow=self.flow,
|
||||
@@ -1439,7 +1474,8 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(phase, "lock_ok")
|
||||
|
||||
def test_multi_option_asks(self) -> None:
|
||||
def test_multi_tms_option_locks_first_until_named(self) -> None:
|
||||
"""TMS 有多条舱位且没点名时,直接锁第一条,不再追问三项。"""
|
||||
wo = self._create_air(
|
||||
quote={
|
||||
"total": "USD 800",
|
||||
@@ -1449,7 +1485,8 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
{"optionNo": "AIR-OPT-01", "airlineCode": "CZ", "flightNo": "CZ327"},
|
||||
{"optionNo": "AIR-OPT-02", "airlineCode": "MU", "flightNo": "MU5123"},
|
||||
],
|
||||
}
|
||||
},
|
||||
status="已成交",
|
||||
)
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo}锁舱", chat="wr_air_1", mid="m1"),
|
||||
@@ -1457,16 +1494,9 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
ledger=self.ledger,
|
||||
injected_intent="air_lock",
|
||||
)
|
||||
self.assertEqual(phase, "wait_lock_params")
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="air1", text="AIR-OPT-02", chat="wr_air_1", mid="m2"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
injected_intent="air_pick_option",
|
||||
)
|
||||
self.assertEqual(phase, "lock_ok")
|
||||
ticket = self.ledger.get_ticket(work_order_no=wo)
|
||||
self.assertEqual(ticket.collab_facts.get("__air_lock_option"), "AIR-OPT-02")
|
||||
self.assertEqual(ticket.collab_facts.get("__air_lock_option"), "AIR-OPT-01")
|
||||
|
||||
def _file(self, *, sender: str, filename: str, chat: str, mid: str, text: str = "") -> InboundMessage:
|
||||
return InboundMessage(
|
||||
@@ -1787,7 +1817,7 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
|
||||
def test_lock_fail_tells_operator_and_tms_reason(self) -> None:
|
||||
"""锁舱失败群里要回操作人和 TMS 原因,不能被系统异常兜底盖掉。"""
|
||||
wo = self._create_air()
|
||||
wo = self._create_air(status="已成交")
|
||||
self.ledger._cabin_lock_result = {
|
||||
"ok": False,
|
||||
"error": "lock_fail",
|
||||
@@ -1810,25 +1840,20 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
self.assertNotIn("与工单不匹配", joined)
|
||||
|
||||
def test_lock_fail_does_not_block_airline_quote(self) -> None:
|
||||
"""锁舱失败后,航线再报手工价必须收下,不能回 TMS 接口异常。"""
|
||||
"""成交前锁舱不会打 TMS,也不标系统异常,航线仍可继续报价。"""
|
||||
wo = self._create_air()
|
||||
handle_air_group(
|
||||
self._msg(sender="air1", text=wo, chat="wr_air_lock_quote", mid="lq0"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
)
|
||||
self.ledger._cabin_lock_result = {
|
||||
"ok": False,
|
||||
"error": "lock_fail",
|
||||
"failReason": "该航班日期不存在可锁定的配载记录",
|
||||
}
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo}锁舱", chat="wr_air_lock_quote", mid="lq1"),
|
||||
flow=self.flow,
|
||||
ledger=self.ledger,
|
||||
injected_intent="air_lock",
|
||||
)
|
||||
self.assertEqual(phase, "system_exception")
|
||||
self.assertEqual(phase, "lock_need_deal")
|
||||
self.groups.sent.clear()
|
||||
phase = handle_air_group(
|
||||
self._msg(sender="air1", text="空运费改为900USD", chat="wr_air_lock_quote", mid="lq2"),
|
||||
@@ -1843,7 +1868,7 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
|
||||
def test_lock_without_quote_still_shows_tms_reason(self) -> None:
|
||||
"""TMS 拒绝锁舱时原样回失败原因,不要改写成选项不匹配。"""
|
||||
wo = self._create_air()
|
||||
wo = self._create_air(status="已成交")
|
||||
self.ledger._cabin_lock_result = {
|
||||
"ok": False,
|
||||
"error": "lock_fail",
|
||||
@@ -1863,7 +1888,7 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
|
||||
def test_paused_lock_retries_and_shows_tms_reason(self) -> None:
|
||||
"""上次锁舱失败标了系统异常后,再锁仍要打 TMS,不能只回兜底。"""
|
||||
wo = self._create_air()
|
||||
wo = self._create_air(status="已成交")
|
||||
self.ledger.mark_system_exception(
|
||||
work_order_no=wo,
|
||||
system_exception="TMS",
|
||||
@@ -1937,7 +1962,7 @@ class AirGroupCollabTests(unittest.TestCase):
|
||||
|
||||
def test_release_fail_tells_operator_and_tms_reason(self) -> None:
|
||||
"""释放失败同理:操作人、原因、已通知IT,不要系统异常兜底。"""
|
||||
wo = self._create_air()
|
||||
wo = self._create_air(status="已成交")
|
||||
handle_air_group(
|
||||
self._msg(sender="air1", text=f"{wo}锁舱", chat="wr_air_rl_fail", mid="rf0"),
|
||||
flow=self.flow,
|
||||
|
||||
Reference in New Issue
Block a user