空运协同里手工改费用、有起运地、锁舱失败或缺参会被误判,报价单已出后再激活又重发模板。按销售侧口径收紧这些分支,并让价格卡和未成交回执带上真实工单号。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-08 18:05:29 +08:00
co-authored by Cursor
parent 43e633f359
commit 6141978a9e
25 changed files with 1347 additions and 98 deletions
+64 -10
View File
@@ -45,10 +45,55 @@ def parse_air_options(quote: dict[str, Any] | None) -> list[dict[str, Any]]:
def needs_route_pick(quote: dict[str, Any] | None) -> bool:
"""多于一条线路才进 H5 选。"""
"""没写起运地、且多于一条线路,才进 H5 选。有起运地时由调用方先按起运地收口。"""
return len(parse_air_options(quote)) > 1
def options_matching_origin(
options: list[dict[str, Any]],
origin_text: str,
) -> list[dict[str, Any]]:
"""
销售已经写了起运地时,只留起运机场对得上的线路。
杭州对 HGH,珠海对 ZUH。回包里的南京/深圳/郑州不能拿来给这张单选。
对不上返回空列表。不改入参。
"""
text = (origin_text or "").strip()
if not text:
return list(options)
code = resolve_airport_code(text).upper()
raw = text.upper()
matched: list[dict[str, Any]] = []
for opt in options:
origin = str(opt.get("originAirportCode") or opt.get("origin") or "").strip().upper()
if not origin:
continue
if code and origin == code:
matched.append(opt)
elif origin == raw:
matched.append(opt)
return matched
def choose_stated_origin_option(
options: list[dict[str, Any]],
quote: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""
起运地已指定时直接用一条,不再让销售选线。
只有一条就用它。多条时优先和报价总价相同的那条,否则用第一条。
"""
if len(options) == 1:
return options[0]
total = (quote or {}).get("total")
for opt in options:
if _same_amount(opt.get("salePrice"), total):
return opt
return options[0]
def _place_name(code: str, fallback: str = "") -> str:
"""三字码能对上就用中文,否则用原文或 fallback。"""
raw = (code or "").strip()
@@ -331,14 +376,14 @@ def _same_amount(left: Any, right: Any) -> bool:
def drop_air_freight_total_alias(quote: dict[str, Any] | None) -> dict[str, Any]:
"""
整单兜底「空运费=总价」不是线路分项,群摘要禁止再写。
已经有其它费用分项时,去掉「空运费=总价」这一行兜底。
真有空运费且金额不等于总价(例如另有附加费)则保留。
舱位上有运输费、提送货费时,主账快照常再塞一行空运费,金额就是总价,不能和分项叠在一起。
只有空运费这一项时必须留下:TMS 经常只回这一项,金额自然等于总价,群里仍要写出费用名。
不写库。群摘要投影之后调用。
"""
src = dict(quote or {})
total = src.get("total")
if _same_amount(src.get("freight"), total):
src["freight"] = "-"
raw = src.get("fee_lines") or src.get("fee_rows")
parsed: Any = raw
if isinstance(raw, str) and raw.strip():
@@ -348,17 +393,26 @@ def drop_air_freight_total_alias(quote: dict[str, Any] | None) -> dict[str, Any]
parsed = None
if not isinstance(parsed, list):
return src
kept: list[Any] = []
others: list[Any] = []
alias: list[Any] = []
for item in parsed:
if not isinstance(item, dict):
kept.append(item)
others.append(item)
continue
name = str(item.get("name") or item.get("label") or "").strip()
name = str(item.get("name") or item.get("label") or item.get("feeName") or "").strip()
amount = item.get("amount") or item.get("value")
if name == "空运费" and _same_amount(amount, total):
alias.append(item)
continue
kept.append(item)
src["fee_lines"] = kept
others.append(item)
if others:
# 分项已经在,空运费=总价是重复兜底。运费字段同值也不再补一行。
src["fee_lines"] = others
if _same_amount(src.get("freight"), total):
src["freight"] = "-"
return src
# 明细就是这一条空运费,群摘要要显示费用名,不能只剩费用合计。
src["fee_lines"] = alias
return src
@@ -493,6 +493,40 @@ def effective_quote(tms: dict[str, Any] | None, overlay: dict[str, Any] | None)
return out
def text_has_relative_adjust(text: str) -> bool:
"""
这句话是在按比例改价:加10%、减5%、上调、上浮、加一成。
这种是销售调价,不是航线/产品报一个新金额。不打模型。
"""
raw = text or ""
if _PERCENT_UP.search(raw) or _PERCENT_DOWN.search(raw):
return True
blob = raw.replace(" ", "").replace(" ", "")
return any(phrase in blob for phrase in ("加一成", "减一成", "往上抬", "往下压", "上浮", "下浮"))
def text_has_total_only_adjust(text: str) -> bool:
"""
只动总价/总销售价/费用合计,没有点名某一项费用。
「总销售价改为180」仍是销售调价。点了「空运费」「报关费」则不是。不打模型。
"""
raw = text or ""
if not (_TARGET.search(raw) or _PERCENT_UP.search(raw) or _PERCENT_DOWN.search(raw)):
return False
if not any(word in raw for word in _TOTAL_WORDS) and "报价" not in raw:
return False
real: list[str] = []
for name in _FEE_TOKEN.findall(raw):
if name in {"费用", "费用合计"}:
continue
if any(word in name for word in ("合计", "总价", "汇总")):
continue
real.append(name)
return not real
def extract_adjust(
text: str,
*,
+4 -1
View File
@@ -35,6 +35,9 @@ _AIRPORTS = {
"南京": "NKG",
"吉隆坡": "KUL",
"北京": "PEK",
"杭州": "HGH",
"萧山": "HGH",
"杭州萧山": "HGH",
}
# 历史字典:只给对照,空运出站不再编成 1/2/3。
@@ -199,7 +202,7 @@ def assemble_air_query(facts: dict[str, Any] | None) -> dict[str, Any]:
包装只看销售是否说了,散货等原文原样保留。
"""
src = dict(facts or {})
origin = str(src.get("起运港") or "").strip()
origin = str(src.get("起运港") or src.get("起运地") or "").strip()
dest = str(src.get("目的港") or "").strip()
cargo = str(src.get("品名") or "").strip()
pieces = _first_number(str(src.get("件数") or ""))