后台按查看权限收紧菜单与「只看自己」数据范围;跨境整车报价填表更稳,协商中顺延不再误报销售超时。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -222,6 +222,9 @@ class TestPoll(unittest.TestCase):
|
||||
from agent.ledger.memory_ledger import MemoryLedger
|
||||
from agent.policy.deal_followup import SHANGHAI
|
||||
|
||||
# 钉在周五,避免周日跑单测时工作日折算为 0
|
||||
now = datetime(2026, 10, 9, 18, 0, 0, tzinfo=SHANGHAI)
|
||||
|
||||
path = os.path.join(ROOT, "agent", "jobs", "event_exception_poll.py")
|
||||
spec = importlib.util.spec_from_file_location("ee_poll", path)
|
||||
self.assertIsNotNone(spec)
|
||||
@@ -248,9 +251,7 @@ class TestPoll(unittest.TestCase):
|
||||
t = book.get_ticket(work_order_no=wo)
|
||||
t.collab_chat_id = "wr1"
|
||||
t.collab_member_ids = ["p1"]
|
||||
t.event_wait_started_at = (datetime.now(SHANGHAI) - timedelta(hours=3)).strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
t.event_wait_started_at = (now - timedelta(hours=3)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
book._timeouts["sea_product_other"] = 0.25
|
||||
groups: list[dict] = []
|
||||
|
||||
@@ -258,7 +259,6 @@ class TestPoll(unittest.TestCase):
|
||||
groups.append({"chat_id": chat_id, "content": content, "line": business_line})
|
||||
return True
|
||||
|
||||
now = datetime.now(SHANGHAI)
|
||||
n = poll_event_exceptions(
|
||||
ledger=book, now=now, send_group=send_group, notify_ready=False
|
||||
)
|
||||
@@ -305,13 +305,12 @@ class TestPoll(unittest.TestCase):
|
||||
)
|
||||
created = book.create_ticket(sender_id="sales1", business_line="AIR", facts={"起运港": "南京"})
|
||||
wo = created["work_order_no"]
|
||||
now = datetime(2026, 10, 9, 18, 0, 0, tzinfo=SHANGHAI)
|
||||
book.start_event_wait(work_order_no=wo, wait_kind="air_quote")
|
||||
ticket = book.get_ticket(work_order_no=wo)
|
||||
ticket.collab_chat_id = "wr-air"
|
||||
ticket.collab_member_ids = []
|
||||
ticket.event_wait_started_at = (datetime.now(SHANGHAI) - timedelta(hours=3)).strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
ticket.event_wait_started_at = (now - timedelta(hours=3)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
book._timeouts["air_route"] = 0.25
|
||||
groups: list[dict] = []
|
||||
|
||||
@@ -321,7 +320,7 @@ class TestPoll(unittest.TestCase):
|
||||
|
||||
n = mod.poll_event_exceptions(
|
||||
ledger=book,
|
||||
now=datetime.now(SHANGHAI),
|
||||
now=now,
|
||||
send_group=send_group,
|
||||
notify_ready=False,
|
||||
)
|
||||
@@ -355,13 +354,12 @@ class TestPoll(unittest.TestCase):
|
||||
{"wecomId": "air1", "name": "吴季林", "roleCode": "air", "status": "active"},
|
||||
]
|
||||
)
|
||||
now = datetime(2026, 10, 9, 18, 0, 0, tzinfo=SHANGHAI)
|
||||
created = book.create_ticket(sender_id="sales1", business_line="AIR", facts={})
|
||||
wo = created["work_order_no"]
|
||||
book.start_event_wait(work_order_no=wo, wait_kind="adopt")
|
||||
ticket = book.get_ticket(work_order_no=wo)
|
||||
ticket.event_wait_started_at = (datetime.now(SHANGHAI) - timedelta(hours=3)).strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
ticket.event_wait_started_at = (now - timedelta(hours=3)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
book._timeouts["air_sales"] = 0.25
|
||||
private: list[dict] = []
|
||||
|
||||
@@ -374,7 +372,7 @@ class TestPoll(unittest.TestCase):
|
||||
|
||||
n = mod.poll_event_exceptions(
|
||||
ledger=book,
|
||||
now=datetime.now(SHANGHAI),
|
||||
now=now,
|
||||
enqueue=enqueue,
|
||||
send_group=send_group,
|
||||
notify_ready=False,
|
||||
@@ -402,7 +400,7 @@ class TestPoll(unittest.TestCase):
|
||||
|
||||
n_deal = mod.poll_event_exceptions(
|
||||
ledger=book,
|
||||
now=datetime.now(SHANGHAI),
|
||||
now=now,
|
||||
enqueue=enqueue,
|
||||
send_group=send_deal_group,
|
||||
notify_ready=False,
|
||||
@@ -429,5 +427,193 @@ class TestMentionsForRole(unittest.TestCase):
|
||||
self.assertEqual([row["name"] for row in rows], ["吴季林"])
|
||||
|
||||
|
||||
class TestDealOutcomeStopsTimeout(unittest.TestCase):
|
||||
"""对应现网截图:协商中顺延 / 未成交已写原因后不应再发销售响应超时。"""
|
||||
|
||||
def _poll_mod(self):
|
||||
import importlib.util
|
||||
|
||||
path = os.path.join(ROOT, "agent", "jobs", "event_exception_poll.py")
|
||||
spec = importlib.util.spec_from_file_location("ee_poll_deal_stop", path)
|
||||
self.assertIsNotNone(spec)
|
||||
self.assertIsNotNone(spec.loader)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
def _book_with_deal_wait(self):
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from agent.ledger.memory_ledger import MemoryLedger
|
||||
from agent.policy.deal_followup import SHANGHAI
|
||||
|
||||
book = MemoryLedger()
|
||||
book.set_sea_staff(
|
||||
[
|
||||
{
|
||||
"wecomId": "sales1",
|
||||
"name": "罗小华",
|
||||
"roleCode": "sales",
|
||||
"status": "active",
|
||||
}
|
||||
]
|
||||
)
|
||||
created = book.create_ticket(sender_id="sales1", business_line="AIR", facts={})
|
||||
wo = created["work_order_no"]
|
||||
book.transition(work_order_no=wo, to_status="已报价")
|
||||
book.start_event_wait(work_order_no=wo, wait_kind="deal")
|
||||
ticket = book.get_ticket(work_order_no=wo)
|
||||
ticket.collab_chat_id = "wr-deal"
|
||||
ticket.event_wait_started_at = (datetime.now(SHANGHAI) - timedelta(hours=72)).strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
book._timeouts["air_sales"] = 48
|
||||
return book, wo
|
||||
|
||||
def test_negotiate_clears_wait_no_timeout(self) -> None:
|
||||
from datetime import datetime
|
||||
|
||||
from agent.policy.deal_followup import SHANGHAI
|
||||
from agent.policy.deal_outcome import finish_deal_outcome
|
||||
|
||||
book, wo = self._book_with_deal_wait()
|
||||
result = finish_deal_outcome(
|
||||
ledger=book,
|
||||
work_order_no=wo,
|
||||
outcome="协商中",
|
||||
facts={},
|
||||
quote={"total": "100"},
|
||||
business_line="AIR",
|
||||
sales_name="罗小华",
|
||||
notify_ready=False,
|
||||
)
|
||||
self.assertTrue(result.get("ok"))
|
||||
ticket = book.get_ticket(work_order_no=wo)
|
||||
self.assertEqual(ticket.status, "协商中")
|
||||
self.assertEqual(ticket.event_wait_kind, "")
|
||||
groups: list[dict] = []
|
||||
|
||||
n = self._poll_mod().poll_event_exceptions(
|
||||
ledger=book,
|
||||
now=datetime.now(SHANGHAI),
|
||||
send_group=lambda **kwargs: groups.append(kwargs) or True,
|
||||
notify_ready=False,
|
||||
)
|
||||
self.assertEqual(n, 0)
|
||||
self.assertEqual(groups, [])
|
||||
|
||||
def test_lost_with_reason_clears_wait_no_timeout(self) -> None:
|
||||
from datetime import datetime
|
||||
|
||||
from agent.policy.deal_followup import SHANGHAI
|
||||
from agent.policy.deal_outcome import finish_deal_outcome
|
||||
|
||||
book, wo = self._book_with_deal_wait()
|
||||
result = finish_deal_outcome(
|
||||
ledger=book,
|
||||
work_order_no=wo,
|
||||
outcome="未成交",
|
||||
facts={},
|
||||
quote={"total": "100"},
|
||||
business_line="AIR",
|
||||
sales_name="罗小华",
|
||||
lost_reason="客户不要了",
|
||||
notify_ready=False,
|
||||
)
|
||||
self.assertTrue(result.get("ok"))
|
||||
self.assertEqual(result.get("phase"), "done")
|
||||
ticket = book.get_ticket(work_order_no=wo)
|
||||
self.assertEqual(ticket.status, "未成交")
|
||||
self.assertEqual(ticket.lost_reason, "客户不要了")
|
||||
self.assertEqual(ticket.event_wait_kind, "")
|
||||
groups: list[dict] = []
|
||||
|
||||
n = self._poll_mod().poll_event_exceptions(
|
||||
ledger=book,
|
||||
now=datetime.now(SHANGHAI),
|
||||
send_group=lambda **kwargs: groups.append(kwargs) or True,
|
||||
notify_ready=False,
|
||||
)
|
||||
self.assertEqual(n, 0)
|
||||
self.assertEqual(groups, [])
|
||||
|
||||
def test_stale_deal_wait_during_negotiate_is_skipped(self) -> None:
|
||||
"""截图回归:协商中已顺延,即使库里仍留着陈旧 deal 等待,也不再发超时。"""
|
||||
from datetime import datetime
|
||||
|
||||
from agent.policy.deal_followup import SHANGHAI
|
||||
from agent.policy.deal_outcome import finish_deal_outcome
|
||||
|
||||
book, wo = self._book_with_deal_wait()
|
||||
ticket = book.get_ticket(work_order_no=wo)
|
||||
started = ticket.event_wait_started_at
|
||||
result = finish_deal_outcome(
|
||||
ledger=book,
|
||||
work_order_no=wo,
|
||||
outcome="协商中",
|
||||
facts={},
|
||||
quote={"total": "100"},
|
||||
business_line="AIR",
|
||||
sales_name="罗小华",
|
||||
notify_ready=False,
|
||||
)
|
||||
self.assertTrue(result.get("ok"))
|
||||
ticket = book.get_ticket(work_order_no=wo)
|
||||
self.assertEqual(ticket.status, "协商中")
|
||||
self.assertTrue((ticket.next_followup_at or "").strip())
|
||||
# 人为留下陈旧等待(模拟 clear/迁态未清干净),巡检必须跳过
|
||||
ticket.event_wait_kind = "deal"
|
||||
ticket.event_wait_started_at = started
|
||||
ticket.event_wait_alerted = 0
|
||||
groups: list[dict] = []
|
||||
|
||||
n = self._poll_mod().poll_event_exceptions(
|
||||
ledger=book,
|
||||
now=datetime.now(SHANGHAI),
|
||||
send_group=lambda **kwargs: groups.append(kwargs) or True,
|
||||
notify_ready=False,
|
||||
)
|
||||
self.assertEqual(n, 0)
|
||||
self.assertEqual(groups, [])
|
||||
|
||||
def test_stale_deal_wait_after_lost_reason_is_skipped(self) -> None:
|
||||
"""截图回归:未成交原因已记,陈旧 deal 等待不再触发销售超时。"""
|
||||
from datetime import datetime
|
||||
|
||||
from agent.policy.deal_followup import SHANGHAI
|
||||
from agent.policy.deal_outcome import finish_deal_outcome
|
||||
|
||||
book, wo = self._book_with_deal_wait()
|
||||
ticket = book.get_ticket(work_order_no=wo)
|
||||
started = ticket.event_wait_started_at
|
||||
result = finish_deal_outcome(
|
||||
ledger=book,
|
||||
work_order_no=wo,
|
||||
outcome="未成交",
|
||||
facts={},
|
||||
quote={"total": "100"},
|
||||
business_line="AIR",
|
||||
sales_name="罗小华",
|
||||
lost_reason="客户不要了",
|
||||
notify_ready=False,
|
||||
)
|
||||
self.assertTrue(result.get("ok"))
|
||||
ticket = book.get_ticket(work_order_no=wo)
|
||||
self.assertEqual(ticket.lost_reason, "客户不要了")
|
||||
ticket.event_wait_kind = "deal"
|
||||
ticket.event_wait_started_at = started
|
||||
ticket.event_wait_alerted = 0
|
||||
groups: list[dict] = []
|
||||
|
||||
n = self._poll_mod().poll_event_exceptions(
|
||||
ledger=book,
|
||||
now=datetime.now(SHANGHAI),
|
||||
send_group=lambda **kwargs: groups.append(kwargs) or True,
|
||||
notify_ready=False,
|
||||
)
|
||||
self.assertEqual(n, 0)
|
||||
self.assertEqual(groups, [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user