721 lines
33 KiB
Python
721 lines
33 KiB
Python
"""
|
|
调价规则单测:比例、改到金额、币种合计、盖价、利润点。
|
|
不连企微 / TMS / 主账。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import unittest
|
|
|
|
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
if ROOT not in sys.path:
|
|
sys.path.insert(0, ROOT)
|
|
|
|
from agent.policy import inquiry_copy as copy
|
|
from agent.schema.quote_adjust import (
|
|
AdjustExtract,
|
|
AdjustItem,
|
|
adjustable_rows,
|
|
apply_adjust,
|
|
effective_quote,
|
|
extract_adjust,
|
|
private_session_can_adjust,
|
|
total_price_label,
|
|
)
|
|
|
|
|
|
def _quote(*rows, total="", currency="CNY"):
|
|
lines = [{"name": n, "amount": a, "currency": c} for n, a, c in rows]
|
|
return {
|
|
"total": total,
|
|
"currency": currency,
|
|
"fee_lines": lines,
|
|
"fee_rows": lines,
|
|
}
|
|
|
|
|
|
class QuoteAdjustTests(unittest.TestCase):
|
|
def test_percent_up_single_currency_updates_total(self) -> None:
|
|
src = _quote(("提货费", "1000", "CNY"), ("送货费", "200", "CNY"), total="1200")
|
|
ext = extract_adjust("提货费加 10%", fee_names=["提货费", "送货费"])
|
|
got = apply_adjust(src, ext, business_line="SEA", tms_cost="1200", tms_sale="1200")
|
|
self.assertTrue(got.ok)
|
|
self.assertEqual(got.code, "applied")
|
|
self.assertEqual(got.quote["fee_rows"][0]["amount"], "1100.00")
|
|
self.assertEqual(str(got.quote["total"]), "1300.00")
|
|
self.assertFalse(got.currency_hint)
|
|
|
|
def test_percent_down_and_target_in_one_sentence(self) -> None:
|
|
src = _quote(("提货费", "1000", "CNY"), ("送货费", "200", "CNY"), total="1200")
|
|
ext = extract_adjust("提货费减 5%,送货费提升到 180 元", fee_names=["提货费", "送货费"])
|
|
got = apply_adjust(src, ext, business_line="LAND", tms_cost="1200")
|
|
self.assertTrue(got.ok)
|
|
names = {r["name"]: r["amount"] for r in got.quote["fee_rows"]}
|
|
self.assertEqual(names["提货费"], "950.00")
|
|
self.assertEqual(names["送货费"], "180.00")
|
|
|
|
def test_unspecified_quote_does_not_change(self) -> None:
|
|
src = _quote(("提货费", "1000", "CNY"), total="1000")
|
|
ext = extract_adjust("报价加 10%", fee_names=["提货费"])
|
|
got = apply_adjust(src, ext, business_line="AIR")
|
|
self.assertFalse(got.ok)
|
|
self.assertEqual(got.code, "need_fee")
|
|
self.assertEqual(src["fee_rows"][0]["amount"], "1000")
|
|
|
|
def test_single_line_same_currency_total_moves_line(self) -> None:
|
|
"""明细只有一项且币种与总价一致:改总价也改明细,不看出价是否相等。"""
|
|
src = _quote(("运输费", "110", "CNY"), total="200")
|
|
ext = extract_adjust("总销售价改为180CNY", fee_names=["运输费"], allow_model=False)
|
|
got = apply_adjust(src, ext, business_line="LAND")
|
|
self.assertTrue(got.ok)
|
|
self.assertEqual(str(got.quote["total"]), "180.00")
|
|
self.assertEqual(got.quote["fee_rows"][0]["amount"], "180.00")
|
|
self.assertFalse(got.lines_hint)
|
|
|
|
def test_single_line_same_currency_line_moves_total(self) -> None:
|
|
src = _quote(("运输费", "110", "CNY"), total="200")
|
|
ext = extract_adjust("运输费提升到 180", fee_names=["运输费"], allow_model=False)
|
|
got = apply_adjust(src, ext, business_line="LAND")
|
|
self.assertTrue(got.ok)
|
|
self.assertEqual(got.quote["fee_rows"][0]["amount"], "180.00")
|
|
self.assertEqual(str(got.quote["total"]), "180.00")
|
|
|
|
def test_single_line_other_currency_does_not_sync(self) -> None:
|
|
src = _quote(("运输费", "110", "USD"), total="200", currency="CNY")
|
|
ext = extract_adjust("总销售价改为180CNY", fee_names=["运输费"], allow_model=False)
|
|
got = apply_adjust(src, ext, business_line="LAND")
|
|
self.assertTrue(got.ok)
|
|
self.assertEqual(str(got.quote["total"]), "180.00")
|
|
self.assertEqual(got.quote["fee_rows"][0]["amount"], "110")
|
|
self.assertTrue(got.currency_hint)
|
|
self.assertFalse(got.lines_hint)
|
|
|
|
def test_multi_line_total_does_not_allocate(self) -> None:
|
|
src = _quote(("提货费", "1000", "CNY"), ("送货费", "200", "CNY"), total="1200")
|
|
ext = extract_adjust("总销售价改为180CNY", fee_names=["提货费", "送货费"], allow_model=False)
|
|
got = apply_adjust(src, ext, business_line="LAND")
|
|
self.assertTrue(got.ok)
|
|
self.assertEqual(str(got.quote["total"]), "180.00")
|
|
names = {r["name"]: r["amount"] for r in got.quote["fee_rows"]}
|
|
self.assertEqual(names["提货费"], "1000")
|
|
self.assertEqual(names["送货费"], "200")
|
|
self.assertTrue(got.lines_hint)
|
|
|
|
def test_multi_currency_named_usd_does_not_change_cny_total(self) -> None:
|
|
src = _quote(
|
|
("提货费", "100", "USD"),
|
|
("空运费", "2000", "CNY"),
|
|
total="2000",
|
|
currency="CNY",
|
|
)
|
|
ext = extract_adjust("提货费加 10%", fee_names=["提货费", "空运费"])
|
|
got = apply_adjust(src, ext, business_line="AIR", tms_sale="2000")
|
|
self.assertTrue(got.ok)
|
|
names = {r["name"]: r["amount"] for r in got.quote["fee_rows"]}
|
|
self.assertEqual(names["提货费"], "110.00")
|
|
self.assertEqual(names["空运费"], "2000")
|
|
self.assertEqual(str(got.quote["total"]), "2000")
|
|
self.assertTrue(got.currency_hint)
|
|
|
|
def test_multi_currency_line_does_not_change_total(self) -> None:
|
|
src = _quote(
|
|
("提货费", "100", "USD"),
|
|
("空运费", "2000", "CNY"),
|
|
total="2000",
|
|
currency="CNY",
|
|
)
|
|
ext = extract_adjust("空运费加 10%", fee_names=["提货费", "空运费"], allow_model=False)
|
|
got = apply_adjust(src, ext, business_line="AIR")
|
|
self.assertTrue(got.ok)
|
|
names = {r["name"]: r["amount"] for r in got.quote["fee_rows"]}
|
|
self.assertEqual(names["空运费"], "2200.00")
|
|
self.assertEqual(names["提货费"], "100")
|
|
self.assertEqual(str(got.quote["total"]), "2000")
|
|
self.assertTrue(got.currency_hint)
|
|
|
|
def test_multi_currency_total_only_changes_total(self) -> None:
|
|
src = _quote(
|
|
("提货费", "100", "USD"),
|
|
("空运费", "2000", "CNY"),
|
|
total="2000",
|
|
currency="CNY",
|
|
)
|
|
ext = extract_adjust("总销售价改为180CNY", fee_names=["提货费", "空运费"], allow_model=False)
|
|
got = apply_adjust(src, ext, business_line="AIR")
|
|
self.assertTrue(got.ok)
|
|
names = {r["name"]: r["amount"] for r in got.quote["fee_rows"]}
|
|
self.assertEqual(names["提货费"], "100")
|
|
self.assertEqual(names["空运费"], "2000")
|
|
self.assertEqual(str(got.quote["total"]), "180.00")
|
|
self.assertTrue(got.currency_hint)
|
|
self.assertFalse(got.lines_hint)
|
|
|
|
def test_partial_unmatched_still_applies_matched(self) -> None:
|
|
src = _quote(("卸货费", "300", "CNY"), total="300")
|
|
ext = extract_adjust("卸货费加 10%,报关费减 5%", fee_names=["卸货费"])
|
|
got = apply_adjust(src, ext, business_line="SEA")
|
|
self.assertTrue(got.ok)
|
|
self.assertIn("报关费", got.unmatched)
|
|
self.assertEqual(got.quote["fee_rows"][0]["amount"], "330.00")
|
|
|
|
def test_none_matched(self) -> None:
|
|
src = _quote(("卸货费", "300", "CNY"), total="300")
|
|
ext = extract_adjust("报关费减 5%", fee_names=["卸货费"])
|
|
got = apply_adjust(src, ext, business_line="SEA")
|
|
self.assertFalse(got.ok)
|
|
self.assertEqual(got.code, "none_matched")
|
|
|
|
def test_sea_subtotal_and_bucket_lines(self) -> None:
|
|
src = {
|
|
"currency": "CNY",
|
|
"total": "2000",
|
|
"fee_lines": [
|
|
{"name": "操作费", "amount": "1000", "currency": "CNY"},
|
|
{"name": "箱单费", "amount": "200", "currency": "CNY"},
|
|
],
|
|
"polCostItems": [
|
|
{"name": "操作费", "amount": "1000", "currency": "CNY"},
|
|
{"name": "箱单费", "amount": "200", "currency": "CNY"},
|
|
],
|
|
"fee_total_polCostItems": "1200",
|
|
}
|
|
ext = AdjustExtract(
|
|
is_adjust=True,
|
|
items=[
|
|
AdjustItem("操作费", "percent", "up", 10),
|
|
AdjustItem("箱单费", "percent", "up", 5),
|
|
AdjustItem("始发港费用小计", "percent", "up", 12),
|
|
],
|
|
)
|
|
got = apply_adjust(src, ext, business_line="SEA")
|
|
self.assertTrue(got.ok)
|
|
self.assertEqual(got.unmatched, [])
|
|
names = {row["name"]: row["amount"] for row in got.quote["polCostItems"]}
|
|
self.assertEqual(names["操作费"], "1100.00")
|
|
self.assertEqual(names["箱单费"], "210.00")
|
|
self.assertEqual(got.quote["fee_total_polCostItems"], "1344.00")
|
|
|
|
def test_sea_pol_subtotal_and_total_up_ten_percent(self) -> None:
|
|
"""协同话术:始发港费用小计 + 费用合计 各提升 10%。"""
|
|
src = {
|
|
"currency": "CNY",
|
|
"total": "14300",
|
|
"fee_lines": [
|
|
{"name": "操作费", "amount": "1220", "currency": "CNY"},
|
|
{"name": "海运费", "amount": "USD 1250", "currency": "USD"},
|
|
],
|
|
"polCostItems": [
|
|
{"name": "操作费", "amount": "1220", "currency": "CNY"},
|
|
{"name": "箱单费", "amount": "50", "currency": "CNY"},
|
|
],
|
|
"oceanCostItems": [
|
|
{"name": "海运费", "amount": "1250", "currency": "USD"},
|
|
],
|
|
"podCostItems": [
|
|
{"name": "操作费", "amount": "240", "currency": "USD"},
|
|
],
|
|
"fee_total_polCostItems": "2335",
|
|
"fee_total_oceanCostItems": "8778.75",
|
|
"fee_total_podCostItems": "2985",
|
|
}
|
|
names = [str(r.get("name") or "") for r in adjustable_rows(src)]
|
|
text = "将始发港费用小计提升10%\n费用合计提升10%"
|
|
ext = extract_adjust(text, fee_names=names, allow_model=False)
|
|
self.assertTrue(ext.is_adjust)
|
|
fee_names = [x.fee_name for x in ext.items]
|
|
self.assertIn("始发港费用小计", fee_names)
|
|
self.assertIn("费用合计", fee_names)
|
|
# 即使模型误标 wants_total,有点名费目时也不能只改合计
|
|
ext.wants_total = True
|
|
got = apply_adjust(src, ext, business_line="SEA")
|
|
self.assertTrue(got.ok)
|
|
self.assertEqual(got.quote["fee_total_polCostItems"], "2568.50")
|
|
self.assertEqual(got.quote["total"], "15730.00")
|
|
body = copy.adjust_group_body(got.quote, business_line="SEA")
|
|
self.assertIn("始发港费用小计", body)
|
|
self.assertIn("海运费小计", body)
|
|
self.assertIn("目的港费用小计", body)
|
|
|
|
def test_same_fee_name_keeps_its_own_amount(self) -> None:
|
|
src = {
|
|
"currency": "USD",
|
|
"total": "500",
|
|
"fee_lines": [
|
|
{"name": "文件费", "amount": "450", "currency": "USD"},
|
|
{"name": "文件费", "amount": "50", "currency": "USD"},
|
|
],
|
|
"polCostItems": [
|
|
{"name": "文件费", "amount": "450", "currency": "USD"},
|
|
{"name": "文件费", "amount": "50", "currency": "USD"},
|
|
],
|
|
}
|
|
ext = AdjustExtract(is_adjust=True, items=[AdjustItem("文件费", "percent", "up", 10)])
|
|
got = apply_adjust(src, ext, business_line="SEA")
|
|
self.assertTrue(got.ok)
|
|
amounts = [row["amount"] for row in got.quote["polCostItems"]]
|
|
self.assertEqual(amounts, ["495.00", "55.00"])
|
|
|
|
def test_land_same_fee_name_each_leg_and_total(self) -> None:
|
|
src = {
|
|
"business_line": "LAND",
|
|
"currency": "CNY",
|
|
"total": "10000",
|
|
"fee_lines": [
|
|
{"name": "运输费", "amount": "7000", "currency": "CNY"},
|
|
{"name": "运输费", "amount": "3000", "currency": "CNY"},
|
|
],
|
|
"landSegments": [
|
|
{"origin": "上海", "destination": "谅山", "costPrice": "7000", "segmentSequence": 1, "routeLegSequence": 1},
|
|
{"origin": "谅山", "destination": "河内", "costPrice": "3000", "segmentSequence": 1, "routeLegSequence": 2},
|
|
],
|
|
}
|
|
ext = AdjustExtract(
|
|
is_adjust=True,
|
|
items=[
|
|
AdjustItem("运输费", "percent", "up", 10),
|
|
AdjustItem("总成本价", "percent", "up", 10),
|
|
],
|
|
)
|
|
got = apply_adjust(src, ext, business_line="LAND")
|
|
self.assertTrue(got.ok)
|
|
self.assertEqual(got.unmatched, [])
|
|
amounts = sorted(row["amount"] for row in got.quote["fee_lines"])
|
|
self.assertEqual(amounts, ["3300.00", "7700.00"])
|
|
costs = [leg["costPrice"] for leg in got.quote["landSegments"]]
|
|
self.assertEqual(costs, ["7700.00", "3300.00"])
|
|
self.assertEqual(got.quote["total"], "11000.00")
|
|
|
|
def test_overlay_manual_wins(self) -> None:
|
|
tms = _quote(("运输费", "1000", "CNY"), ("卸货费", "200", "CNY"), total="1200")
|
|
manual = _quote(("卸货费", "300", "CNY"), ("配送费", "20", "CNY"), total="320")
|
|
merged = effective_quote(tms, manual)
|
|
names = {r["name"]: r["amount"] for r in merged["fee_rows"]}
|
|
self.assertEqual(names["运输费"], "1000")
|
|
self.assertEqual(names["卸货费"], "300")
|
|
self.assertEqual(names["配送费"], "20")
|
|
ext = extract_adjust("卸货费加 10%", fee_names=list(names))
|
|
got = apply_adjust(merged, ext, business_line="SEA", tms_cost="1200")
|
|
after = {r["name"]: r["amount"] for r in got.quote["fee_rows"]}
|
|
self.assertEqual(after["卸货费"], "330.00")
|
|
self.assertEqual(after["运输费"], "1000")
|
|
self.assertEqual(after["配送费"], "20")
|
|
|
|
def test_profit_sea_uses_cost(self) -> None:
|
|
src = _quote(("运输费", "1000", "CNY"), total="1000")
|
|
ext = extract_adjust("运输费提升到 1300 元", fee_names=["运输费"])
|
|
got = apply_adjust(src, ext, business_line="SEA", tms_cost="1000", tms_sale="1100")
|
|
self.assertAlmostEqual(got.profit_point or 0, 30.0)
|
|
|
|
def test_profit_air_uses_sale(self) -> None:
|
|
src = _quote(("空运费", "1000", "CNY"), total="1000")
|
|
ext = extract_adjust("空运费提升到 1100 元", fee_names=["空运费"])
|
|
got = apply_adjust(src, ext, business_line="AIR", tms_cost="800", tms_sale="1000")
|
|
self.assertAlmostEqual(got.profit_point or 0, 10.0)
|
|
|
|
def test_profit_uses_manual_not_tms_when_manual_exists(self) -> None:
|
|
"""有手工报价:利润点对手工价,不对 TMS。"""
|
|
src = _quote(("运输费", "800", "CNY"), total="800")
|
|
ext = extract_adjust("运输费提升到 1040 元", fee_names=["运输费"])
|
|
got = apply_adjust(src, ext, business_line="SEA", tms_cost="1000", tms_sale="1100")
|
|
self.assertAlmostEqual(got.profit_point or 0, 30.0)
|
|
|
|
def test_profit_air_manual_uses_manual(self) -> None:
|
|
src = _quote(("空运费", "2000", "CNY"), total="2000")
|
|
ext = extract_adjust("空运费提升到 2200 元", fee_names=["空运费"])
|
|
got = apply_adjust(src, ext, business_line="AIR", tms_cost="800", tms_sale="1000")
|
|
self.assertAlmostEqual(got.profit_point or 0, 10.0)
|
|
|
|
def test_profit_second_adjust_keeps_first_baseline(self) -> None:
|
|
src = _quote(("运输费", "1000", "CNY"), total="1000")
|
|
first = apply_adjust(
|
|
src,
|
|
extract_adjust("运输费提升到 1100 元", fee_names=["运输费"]),
|
|
business_line="LAND",
|
|
tms_cost="1000",
|
|
)
|
|
second = apply_adjust(
|
|
first.quote,
|
|
extract_adjust("运输费提升到 1300 元", fee_names=["运输费"]),
|
|
business_line="LAND",
|
|
)
|
|
self.assertAlmostEqual(second.profit_point or 0, 30.0)
|
|
|
|
def test_total_label_is_fee_total_for_every_line(self) -> None:
|
|
"""销售侧汇总一律叫费用合计,空运/海运/陆运、TMS/调价都一样。"""
|
|
self.assertEqual(total_price_label("AIR"), "费用合计")
|
|
self.assertEqual(total_price_label("SEA"), "费用合计")
|
|
self.assertEqual(total_price_label("LAND"), "费用合计")
|
|
self.assertEqual(total_price_label("", transport_mode="空运"), "费用合计")
|
|
self.assertEqual(total_price_label("", transport_mode="陆运"), "费用合计")
|
|
tms = {"source": "TMS", "source_label": "TMS标准报价"}
|
|
self.assertEqual(total_price_label("LAND", quote=tms), "费用合计")
|
|
self.assertEqual(total_price_label("SEA", quote={"source": "PRODUCT"}), "费用合计")
|
|
self.assertEqual(total_price_label("LAND", quote={"source": "SALES_ADJUST"}), "费用合计")
|
|
self.assertEqual(total_price_label("AIR", quote=tms), "费用合计")
|
|
|
|
def test_tms_land_card_uses_cost_not_sale(self) -> None:
|
|
q = _quote(("运输费", "121.00", "CNY"), total="121.00")
|
|
q["source"] = "TMS"
|
|
q["source_label"] = "TMS标准报价"
|
|
detail = copy.sea_tms_hit_wecom_payload(
|
|
work_order_no="WO202609200005", quote=q, business_line="LAND"
|
|
)
|
|
self.assertEqual(detail["template_card"]["source"]["desc"], "TMS标准报价")
|
|
self.assertIn("费用合计:CNY 121.00", detail["template_card"]["quote_area"]["quote_text"])
|
|
self.assertNotIn("总销售价", detail["template_card"]["quote_area"]["quote_text"])
|
|
scheme = copy.scheme_wecom_payload(
|
|
work_order_no="WO202609200005",
|
|
facts={"origin": "泉州市", "destination": "南京市"},
|
|
quote=q,
|
|
transport_mode="陆运",
|
|
)
|
|
self.assertEqual(scheme["template_card"]["source"]["desc"], "TMS标准报价")
|
|
self.assertIn("费用合计:CNY 121.00", scheme["template_card"]["quote_area"]["quote_text"])
|
|
deal = copy.deal_horizontal_rows(quote=q, business_line="LAND")
|
|
self.assertEqual(deal[0]["keyname"], "费用合计")
|
|
|
|
def test_adjust_confirm_uses_sale_total_name(self) -> None:
|
|
src = _quote(("运输费", "110", "CNY"), total="110")
|
|
land = apply_adjust(
|
|
src,
|
|
extract_adjust("总成本价改为100CNY", fee_names=["运输费"], allow_model=False),
|
|
business_line="LAND",
|
|
)
|
|
self.assertTrue(land.ok)
|
|
names = [x.get("name") for x in land.applied]
|
|
self.assertIn("费用合计", names)
|
|
self.assertNotIn("总销售价", names)
|
|
self.assertNotIn("总成本价", names)
|
|
text = copy.adjust_confirm(land.applied)
|
|
self.assertIn("费用合计在当前报价基础上", text)
|
|
body = copy.adjust_group_body(land.quote, business_line="LAND")
|
|
self.assertIn("费用合计:", body)
|
|
air = apply_adjust(
|
|
src,
|
|
extract_adjust("总销售价改为100CNY", fee_names=["运输费"], allow_model=False),
|
|
business_line="AIR",
|
|
)
|
|
self.assertIn("费用合计", [x.get("name") for x in air.applied])
|
|
note = copy.adjust_ledger_remark(land.applied)
|
|
self.assertTrue(note.startswith("销售调价:"))
|
|
self.assertIn("费用合计", note)
|
|
|
|
def test_quote_copy_uses_line_and_source_total_name(self) -> None:
|
|
sea = {
|
|
"total": "2287.65",
|
|
"currency": "CNY",
|
|
"source": "TMS",
|
|
"fee_rows": [{"name": "海运费", "amount": "100", "currency": "USD"}],
|
|
}
|
|
air = {
|
|
"total": "722.5",
|
|
"currency": "CNY",
|
|
"source": "TMS",
|
|
"fee_rows": [{"name": "空运费", "amount": "700", "currency": "CNY"}],
|
|
}
|
|
self.assertIn("费用合计:CNY 2287.65", copy.quote_area_fee_text(sea, business_line="SEA"))
|
|
self.assertIn("费用合计:CNY 722.5", copy.quote_area_fee_text(air, business_line="AIR"))
|
|
self.assertNotIn("总销售价合计", copy.quote_area_fee_text(air, business_line="AIR"))
|
|
self.assertNotIn("总销售价", copy.quote_area_fee_text(sea, business_line="SEA"))
|
|
self.assertNotIn("总成本价", copy.quote_area_fee_text(sea, business_line="SEA"))
|
|
self.assertIn("费用合计:CNY 2287.65", "\n".join(copy.quote_detail_lines(sea, business_line="SEA")))
|
|
rows = copy.deal_horizontal_rows(quote=air, business_line="AIR")
|
|
self.assertEqual(rows[0]["keyname"], "费用合计")
|
|
self.assertNotIn("时效", [x["keyname"] for x in rows])
|
|
land_rows = copy.deal_horizontal_rows(quote=sea, business_line="LAND")
|
|
self.assertEqual(land_rows[0]["keyname"], "费用合计")
|
|
self.assertIn("时效", [x["keyname"] for x in land_rows])
|
|
|
|
def test_adjust_cards_use_sales_adjust_not_tms(self) -> None:
|
|
"""调价后价格明细卡、方案卡必须写销售调价,禁止再套 TMS 标准报价。"""
|
|
q = _quote(("运输费", "121.00", "CNY"), total="121.00")
|
|
q["source"] = "SALES_ADJUST"
|
|
q["source_label"] = "销售调价"
|
|
detail = copy.sea_tms_hit_wecom_payload(
|
|
work_order_no="WO202609200004", quote=q, business_line="LAND"
|
|
)
|
|
self.assertEqual(detail["template_card"]["source"]["desc"], "销售调价")
|
|
self.assertIn("费用合计:CNY 121.00", detail["template_card"]["quote_area"]["quote_text"])
|
|
self.assertNotIn("总成本价", detail["template_card"]["quote_area"]["quote_text"])
|
|
scheme = copy.scheme_wecom_payload(
|
|
work_order_no="WO202609200004",
|
|
facts={"origin": "泉州市", "destination": "南京市"},
|
|
quote=q,
|
|
transport_mode="陆运",
|
|
)
|
|
self.assertEqual(scheme["template_card"]["source"]["desc"], "销售调价")
|
|
self.assertIn("费用合计:CNY 121.00", scheme["template_card"]["quote_area"]["quote_text"])
|
|
|
|
def test_private_eligibility(self) -> None:
|
|
self.assertEqual(private_session_can_adjust("已报价", True, ""), "")
|
|
self.assertEqual(private_session_can_adjust("协商中", True, ""), "")
|
|
self.assertEqual(private_session_can_adjust("已报价", True, "chat1"), "already_collab")
|
|
self.assertEqual(private_session_can_adjust("已成交", True, ""), "closed")
|
|
self.assertEqual(private_session_can_adjust("询价中", False, ""), "no_quote")
|
|
|
|
def test_oral_aliases_not_hardcoded_phrase(self) -> None:
|
|
src = _quote(("提货费", "1000", "CNY"), total="1000")
|
|
ext = extract_adjust("提货费在现有报价基础上上调 8%", fee_names=["提货费"])
|
|
self.assertTrue(ext.is_adjust)
|
|
got = apply_adjust(src, ext, business_line="SEA")
|
|
self.assertEqual(got.quote["fee_rows"][0]["amount"], "1080.00")
|
|
|
|
def test_model_phrasing_not_in_local_fallback(self) -> None:
|
|
"""口令不写死:模型抽「往上抬一成」也能算;没注入时本地不得靠固定词硬认。"""
|
|
spoken = "把运费往上抬一成"
|
|
local = extract_adjust(spoken, fee_names=["运输费"], allow_model=False)
|
|
self.assertFalse(local.is_adjust)
|
|
ext = extract_adjust(
|
|
spoken,
|
|
fee_names=["运输费"],
|
|
injected={
|
|
"is_adjust": True,
|
|
"items": [{"fee_name": "运输费", "kind": "percent", "direction": "up", "value": 10}],
|
|
},
|
|
)
|
|
got = apply_adjust(_quote(("运输费", "1000", "CNY"), total="1000"), ext, business_line="LAND")
|
|
self.assertTrue(got.ok)
|
|
self.assertEqual(got.quote["fee_rows"][0]["amount"], "1100.00")
|
|
|
|
def test_a_route_contract_accepts_quote_adjust(self) -> None:
|
|
from agent.routing.deepseek_a import DeepSeekARouter
|
|
from agent.routing.ordered_actions import ACTION_INTENTS, parse_ordered_actions_arguments
|
|
|
|
self.assertEqual(ACTION_INTENTS.get("QUOTE_ADJUST"), "quote_adjust")
|
|
parsed = parse_ordered_actions_arguments(
|
|
{
|
|
"decision_id": "d-adj",
|
|
"intent": "quote_adjust",
|
|
"ordered_action": {
|
|
"action_type": "QUOTE_ADJUST",
|
|
"order": 1,
|
|
"target": {"work_order_no": "WO202609200002"},
|
|
"evidence": {"source_ref": "inbound:u1", "content_digest": "a" * 64},
|
|
},
|
|
}
|
|
)
|
|
self.assertEqual(parsed.to_route_decision().intent, "quote_adjust")
|
|
router = DeepSeekARouter.from_settings()
|
|
sys_msg, user_msg = router.build_messages(
|
|
text="报价上升10%",
|
|
sender_id="u1",
|
|
session_quoted=True,
|
|
fee_names=["运输费"],
|
|
work_order_no="WO202609200002",
|
|
)
|
|
self.assertIn("quote_adjust", sys_msg["content"])
|
|
self.assertIn("口令不写死", sys_msg["content"])
|
|
self.assertIn("session_quoted=true", user_msg["content"])
|
|
self.assertIn("运输费", user_msg["content"])
|
|
self.assertIn("WO202609200002", user_msg["content"])
|
|
|
|
def test_private_adjust_stamps_sales_adjust_on_session_quote(self) -> None:
|
|
"""书签和出卡必须带销售调价;不能只改主账、卡片仍用 TMS 来源。"""
|
|
from agent.policy.quote_adjust_ops import try_private_adjust
|
|
|
|
class _Sess:
|
|
status = "已报价"
|
|
phase = "wait_collab"
|
|
work_order_no = "WO202609200004"
|
|
business_line = "LAND"
|
|
collab_chat_id = ""
|
|
wait_version = 1
|
|
first_or_same = "first"
|
|
history_work_order_no = ""
|
|
quote = _quote(("运输费", "121.00", "CNY"), total="121.00")
|
|
tms_quote = {}
|
|
|
|
class _Flow:
|
|
def __init__(self) -> None:
|
|
self.emitted = None
|
|
self.saved = None
|
|
|
|
class _Ledger:
|
|
def upsert_product_quote(self, **kwargs):
|
|
_Flow.last = kwargs
|
|
return {"ok": True}
|
|
|
|
self.ledger = _Ledger()
|
|
|
|
def _save(self, sess) -> None:
|
|
self.saved = dict(sess.quote or {})
|
|
|
|
def _emit_sea_hit(self, reply, **kwargs) -> None:
|
|
self.emitted = kwargs
|
|
|
|
_Sess.quote["source"] = "TMS"
|
|
_Sess.quote["source_label"] = "TMS标准报价"
|
|
sess = _Sess()
|
|
flow = _Flow()
|
|
replies: list[str] = []
|
|
out = try_private_adjust(
|
|
flow,
|
|
sess,
|
|
"运输费提升到 150",
|
|
replies.append,
|
|
injected_adjust={
|
|
"is_adjust": True,
|
|
"items": [{"fee_name": "运输费", "kind": "target", "direction": "up", "value": 150}],
|
|
},
|
|
)
|
|
self.assertEqual(out, "wait_collab")
|
|
self.assertEqual(sess.quote.get("source_label"), "销售调价")
|
|
self.assertEqual(sess.quote.get("source"), "SALES_ADJUST")
|
|
self.assertEqual(flow.saved.get("source_label"), "销售调价")
|
|
self.assertEqual(flow.last["quote"].get("source_label"), "销售调价")
|
|
self.assertEqual(flow.last.get("kind"), "adjust")
|
|
self.assertEqual(flow.emitted["quote"].get("source_label"), "销售调价")
|
|
self.assertNotIn("TMS", str(flow.emitted["quote"].get("source_label") or ""))
|
|
self.assertEqual(str(sess.tms_quote.get("total") or ""), "121.00")
|
|
self.assertTrue(copy.quote_is_tms(sess.tms_quote))
|
|
self.assertFalse(copy.quote_is_tms(sess.quote))
|
|
|
|
def test_group_brief_keeps_tms_when_current_is_adjust(self) -> None:
|
|
"""拉群摘要:TMS 段必须是原价,调后价另写销售调价,禁止替换。"""
|
|
tms = _quote(("运输费", "110.00", "CNY"), total="110.00")
|
|
tms["source"] = "TMS"
|
|
tms["source_label"] = "TMS标准报价"
|
|
adj = _quote(("运输费", "100.00", "CNY"), total="100.00")
|
|
adj["source"] = "SALES_ADJUST"
|
|
adj["source_label"] = "销售调价"
|
|
text = copy.sea_group_brief(
|
|
work_order_no="WO202609200006",
|
|
facts={"起运地": "泉州市", "目的地": "南京市", "品名": "普货"},
|
|
quote=adj,
|
|
collab_facts={},
|
|
sales_name="销售",
|
|
product_names=["产品"],
|
|
has_price=True,
|
|
tms_quote=tms,
|
|
transport_mode="陆运",
|
|
)
|
|
tms_block = text.split("TMS报价:", 1)[1]
|
|
self.assertIn("费用合计:CNY 110.00", tms_block.split("销售调价:", 1)[0])
|
|
self.assertIn("销售调价:", text)
|
|
self.assertIn("费用合计:CNY 100.00", text.split("销售调价:", 1)[1])
|
|
self.assertNotIn("费用合计:CNY 100.00", tms_block.split("销售调价:", 1)[0])
|
|
|
|
def test_group_brief_adjust_alone_does_not_become_tms(self) -> None:
|
|
"""没单独传入 TMS 时,调后价也不能写进 TMS 段。"""
|
|
adj = _quote(("运输费", "100.00", "CNY"), total="100.00")
|
|
adj["source"] = "SALES_ADJUST"
|
|
adj["source_label"] = "销售调价"
|
|
text = copy.sea_group_brief(
|
|
work_order_no="WO202609200006",
|
|
facts={"起运地": "泉州市", "目的地": "南京市"},
|
|
quote=adj,
|
|
collab_facts={},
|
|
sales_name="销售",
|
|
product_names=[],
|
|
has_price=True,
|
|
transport_mode="陆运",
|
|
)
|
|
self.assertIn("TMS报价:\nTMS 暂无匹配报价", text)
|
|
self.assertIn("产品人员可通过“纯文字”告知新报价,或直接将新报价excel表发给智能体。", text)
|
|
self.assertIn("销售调价:", text)
|
|
self.assertIn("费用合计:CNY 100.00", text.split("销售调价:", 1)[1])
|
|
|
|
|
|
class SheetPriceStampTests(unittest.TestCase):
|
|
"""报价单读的是线路表和舱位明细,不是只读 fee_lines。"""
|
|
|
|
def test_sales_adjust_writes_land_route_copies(self) -> None:
|
|
src = {
|
|
"source": "TMS",
|
|
"source_label": "TMS标准报价",
|
|
"currency": "CNY",
|
|
"total": "CNY 16500",
|
|
"fee_lines": [{"name": "运输费", "amount": "16500.00", "currency": "CNY"}],
|
|
"feeItems": [{"feeName": "运输费", "amount": 16500.0, "currency": "CNY"}],
|
|
"landSegments": [
|
|
{"origin": "上海", "destination": "谅山", "costPrice": 16500.0, "currency": "CNY"}
|
|
],
|
|
"route_rows": [
|
|
{
|
|
"origin": "上海",
|
|
"destination": "谅山",
|
|
"costPrice": "16500.00",
|
|
"amount": "16500.00",
|
|
}
|
|
],
|
|
"selected_land_route": json.dumps(
|
|
{"legs": [{"origin": "上海", "destination": "谅山", "costPrice": 16500.0}]},
|
|
ensure_ascii=False,
|
|
),
|
|
}
|
|
got = apply_adjust(
|
|
src,
|
|
extract_adjust("运输费改为28150", fee_names=["运输费"], allow_model=False),
|
|
business_line="LAND",
|
|
tms_cost="16500",
|
|
tms_sale="16500",
|
|
)
|
|
self.assertTrue(got.ok)
|
|
self.assertIn("28150", str(got.quote["route_rows"][0]["amount"]))
|
|
self.assertIn("28150", str(got.quote["route_rows"][0]["costPrice"]))
|
|
self.assertIn("28150", str(got.quote["landSegments"][0]["costPrice"]))
|
|
selected = json.loads(got.quote["selected_land_route"])
|
|
self.assertIn("28150", str(selected["legs"][0]["costPrice"]))
|
|
self.assertIn("28150", str(got.quote["feeItems"][0]["amount"]))
|
|
|
|
def test_product_and_airline_sheet_copies_follow_new_fee(self) -> None:
|
|
from agent.policy.sea_group_ops import merge_quote_fees
|
|
from agent.schema.quote_adjust import stamp_prices_for_sheet
|
|
|
|
base = {
|
|
"source": "TMS",
|
|
"source_label": "TMS标准报价",
|
|
"currency": "CNY",
|
|
"total": "16500",
|
|
"fee_lines": [{"name": "运输费", "amount": "16500", "currency": "CNY"}],
|
|
"route_rows": [{"costPrice": "16500", "amount": "16500"}],
|
|
"oceanCostItems": [{"name": "海运费", "amount": "100", "currency": "USD"}],
|
|
}
|
|
merged = merge_quote_fees(
|
|
base,
|
|
{"fee_lines": [{"name": "运输费", "amount": "18000"}], "source_label": "产品报价"},
|
|
)
|
|
self.assertIn("18000", str(merged["route_rows"][0]["amount"]))
|
|
self.assertEqual(merged["source_label"], "产品报价")
|
|
|
|
air = stamp_prices_for_sheet(
|
|
{
|
|
"source_label": "航线报价",
|
|
"total": "900",
|
|
"currency": "CNY",
|
|
"fee_lines": [{"name": "空运费", "amount": "900", "currency": "CNY"}],
|
|
"airOptions": [
|
|
{
|
|
"salePrice": "7918.25",
|
|
"departureCharges": [
|
|
{"chargeName": "空运费", "unitPrice": "1000", "amount": "1000"}
|
|
],
|
|
}
|
|
],
|
|
}
|
|
)
|
|
self.assertEqual(air["airOptions"][0]["salePrice"], "900")
|
|
self.assertIn("900", str(air["airOptions"][0]["departureCharges"][0]["amount"]))
|
|
kept = stamp_prices_for_sheet(
|
|
{
|
|
"source_label": "航线确认TMS",
|
|
"total": "200",
|
|
"fee_lines": [{"name": "增点费", "amount": "200"}],
|
|
"airOptions": [
|
|
{
|
|
"salePrice": "7918.25",
|
|
"departureCharges": [{"chargeName": "增点费", "unitPrice": "100", "amount": "100"}],
|
|
}
|
|
],
|
|
}
|
|
)
|
|
self.assertEqual(kept["airOptions"][0]["salePrice"], "7918.25")
|
|
self.assertEqual(kept["airOptions"][0]["departureCharges"][0]["amount"], "100")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|