协商中再问按后台超时小时顺延并可强制清跟进时刻;工单与账号支持按筛选导出 Excel。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -75,13 +75,14 @@ class BotReplyRenderTests(unittest.TestCase):
|
||||
override_catalog(
|
||||
{
|
||||
"negotiating.recorded": (
|
||||
"已记录为「协商中」。已顺延三个工作日,{{followup_when}}的9:00再次发起成交状态询问。"
|
||||
"已记录为「协商中」。已顺延 {{followup_hours}} 个工作小时,"
|
||||
"将于 {{followup_when}} 再次发起成交状态询问。"
|
||||
)
|
||||
}
|
||||
)
|
||||
text = copy.deal_ack("协商中", "WO1", next_followup_at="")
|
||||
self.assertIn("第四个工作日", text)
|
||||
self.assertIn("9:00", text)
|
||||
text = copy.deal_ack("协商中", "WO1", next_followup_at="", followup_hours=48)
|
||||
self.assertIn("48", text)
|
||||
self.assertIn("工作小时", text)
|
||||
|
||||
def test_group_add_fail_empty_names_reads_admin(self) -> None:
|
||||
override_catalog(
|
||||
|
||||
@@ -10,6 +10,7 @@ from zoneinfo import ZoneInfo
|
||||
|
||||
from agent.policy.deal_followup import (
|
||||
SHANGHAI,
|
||||
add_workday_hours,
|
||||
air_route_keys,
|
||||
is_workday,
|
||||
leader_matches,
|
||||
@@ -41,12 +42,18 @@ class TestWorkdays(unittest.TestCase):
|
||||
self.assertTrue(is_workday(date(2026, 9, 20)))
|
||||
self.assertFalse(is_workday(date(2026, 10, 7)))
|
||||
|
||||
def test_next_followup_is_9am(self) -> None:
|
||||
when = next_followup_at(datetime(2026, 9, 18, 15, 30, tzinfo=SHANGHAI))
|
||||
self.assertEqual(when.hour, 9)
|
||||
self.assertEqual(when.minute, 0)
|
||||
def test_next_followup_48_work_hours_skips_weekend(self) -> None:
|
||||
# 周五 15:30 + 48 工作小时 → 跳过周末 → 周二 15:30
|
||||
when = next_followup_at(
|
||||
datetime(2026, 11, 13, 15, 30, tzinfo=SHANGHAI), hours=48
|
||||
)
|
||||
self.assertEqual(when.tzinfo, SHANGHAI)
|
||||
self.assertEqual(when.date(), date(2026, 9, 23))
|
||||
self.assertEqual(when, datetime(2026, 11, 17, 15, 30, tzinfo=SHANGHAI))
|
||||
|
||||
def test_add_workday_hours_from_rest_day(self) -> None:
|
||||
# 周六起点:从下周一 00:00 起算 10 小时 → 周一 10:00
|
||||
when = add_workday_hours(datetime(2026, 11, 14, 10, 0, tzinfo=SHANGHAI), 10)
|
||||
self.assertEqual(when, datetime(2026, 11, 16, 10, 0, tzinfo=SHANGHAI))
|
||||
|
||||
def test_skip_weekend_keep_weekday_night(self) -> None:
|
||||
start = datetime(2026, 11, 13, 17, 0, tzinfo=SHANGHAI) # 周五
|
||||
|
||||
@@ -277,15 +277,22 @@ class TestDealOutcome(unittest.TestCase):
|
||||
self.assertNotIn("请补充未成交原因", joined)
|
||||
|
||||
def test_negotiate_ack_names_followup_day(self) -> None:
|
||||
text = copy.deal_ack("协商中", "WO202609190007", "2026-09-24 09:00:00")
|
||||
text = copy.deal_ack(
|
||||
"协商中",
|
||||
"WO202609190007",
|
||||
"2026-09-24 15:30:00",
|
||||
followup_hours=48,
|
||||
)
|
||||
self.assertEqual(
|
||||
text,
|
||||
"已记录为「协商中」。已顺延三个工作日,9月24日的9:00再次发起成交状态询问。",
|
||||
"已记录为「协商中」。已顺延 48 个工作小时,将于 9月24日15:30 再次发起成交状态询问。",
|
||||
)
|
||||
self.assertNotIn("跟进周期已顺延", text)
|
||||
self.assertNotIn("三个工作日", text)
|
||||
|
||||
def test_negotiate_keeps_wait_deal(self) -> None:
|
||||
self.ledger.start_event_wait(work_order_no=self.wo, wait_kind="deal")
|
||||
self.ledger._timeouts["deal_negotiate"] = 48
|
||||
out = finish_deal_outcome(
|
||||
ledger=self.ledger,
|
||||
work_order_no=self.wo,
|
||||
@@ -295,6 +302,7 @@ class TestDealOutcome(unittest.TestCase):
|
||||
business_line="SEA",
|
||||
)
|
||||
self.assertEqual(out.get("phase"), "wait_deal")
|
||||
self.assertEqual(out.get("negotiate_hours"), 48)
|
||||
ticket = self.ledger.get_ticket(work_order_no=self.wo)
|
||||
self.assertEqual(ticket.status, "协商中")
|
||||
self.assertEqual(ticket.event_wait_kind, "")
|
||||
@@ -313,6 +321,22 @@ class TestDealOutcome(unittest.TestCase):
|
||||
self.assertEqual(self.ledger.get_ticket(work_order_no=self.wo).status, "协商中")
|
||||
self.assertTrue(again.get("next_followup_at"))
|
||||
|
||||
def test_negotiate_zero_hours_skips_schedule(self) -> None:
|
||||
"""后台把 deal_negotiate 设为 0:仍记协商中,但不排再问时刻。"""
|
||||
self.ledger._timeouts["deal_negotiate"] = 0
|
||||
out = finish_deal_outcome(
|
||||
ledger=self.ledger,
|
||||
work_order_no=self.wo,
|
||||
outcome="协商中",
|
||||
facts={},
|
||||
quote={},
|
||||
business_line="SEA",
|
||||
)
|
||||
self.assertEqual(out.get("phase"), "wait_deal")
|
||||
self.assertEqual(out.get("negotiate_hours"), 0)
|
||||
self.assertEqual(out.get("next_followup_at"), "")
|
||||
self.assertEqual(self.ledger.get_ticket(work_order_no=self.wo).next_followup_at, "")
|
||||
|
||||
def test_private_next_text_is_lost_reason(self) -> None:
|
||||
flow = AirTextInquiryFlow(self.ledger)
|
||||
sess = FlowSession(
|
||||
|
||||
@@ -1376,8 +1376,24 @@ class SeaGroupCollabFieldTests(unittest.TestCase):
|
||||
self.assertEqual(str(rows["空运费"].get("amount") or ""), "199.00")
|
||||
self.assertEqual(str(rows["空运费"].get("currency") or ""), "CNY")
|
||||
self.assertEqual(str(rows["空运费"].get("unit_price") or ""), "99.00")
|
||||
self.assertEqual(str(rows["空运费"].get("quantity") or ""), "100")
|
||||
self.assertEqual(str(quote.get("total") or ""), "199.0")
|
||||
|
||||
def test_air_fee_priced_with_unit_between_name_and_unit_price(self) -> None:
|
||||
"""WO003:空运费, KG, 单价…数量…金额… 中间夹单位时不能把单价当行金额。"""
|
||||
from agent.llm.extract_text import extract_product_quote
|
||||
|
||||
quote = extract_product_quote(
|
||||
"空运段: 空运费, KG, 单价CNY99.00, 数量100, 金额CNY9900.00@询价小助手",
|
||||
business_line="AIR",
|
||||
)
|
||||
rows = {str(x.get("name") or ""): x for x in (quote.get("fee_rows") or [])}
|
||||
self.assertIn("空运费", rows)
|
||||
self.assertEqual(str(rows["空运费"].get("amount") or ""), "9900.00")
|
||||
self.assertEqual(str(rows["空运费"].get("unit_price") or ""), "99.00")
|
||||
self.assertEqual(str(rows["空运费"].get("quantity") or ""), "100")
|
||||
self.assertEqual(str(quote.get("total") or ""), "9900.0")
|
||||
|
||||
def test_fee_line_text_is_a_product_quote(self) -> None:
|
||||
"""产品按行改价:码头操作费100CNY / 报关费改为300CNY,必须抽出费用名,不能只当海运费合计。"""
|
||||
from agent.llm.extract_text import extract_product_quote
|
||||
|
||||
Reference in New Issue
Block a user