陆运多条线路按段展示 TMS 费用名和时效,选定后再写回该条成本。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
空运 TMS 线路报价:从 airOptions 拆出可选线路。
|
||||
|
||||
本文件职责:判断一条还是多条、拼航线(含中转)、抽出该线路费用。
|
||||
选定后把起运港写回 facts。中转只用于展示,不写进报价单模板。
|
||||
选定后把起运港写回 facts。群摘要现场投影同一套分项。
|
||||
中转只用于展示,不写进报价单模板。
|
||||
禁止:猜没回的机场名;把贸易条款当线路;改六态。
|
||||
"""
|
||||
|
||||
@@ -248,3 +249,131 @@ def project_selected_quote(quote: dict[str, Any] | None, option: dict[str, Any]
|
||||
base["selected_air_option"] = src
|
||||
base["route_line"] = route_line(src, {})
|
||||
return base
|
||||
|
||||
|
||||
def _parse_option_dict(raw: Any) -> dict[str, Any]:
|
||||
"""selected_air_option:字典或 JSON 串。解不开当没有。"""
|
||||
if isinstance(raw, str) and raw.strip():
|
||||
try:
|
||||
raw = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
return dict(raw) if isinstance(raw, dict) else {}
|
||||
|
||||
|
||||
def resolve_display_air_option(
|
||||
quote: dict[str, Any] | None,
|
||||
facts: dict[str, str] | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""
|
||||
群摘要用哪一条线路:已选定 > 仅 1 条 > 用工单起运/目的对上的那条。
|
||||
|
||||
多条对不上就不猜。禁止把整单最低价的空运费套到没选的线路。
|
||||
"""
|
||||
src = dict(quote or {})
|
||||
selected = _parse_option_dict(src.get("selected_air_option"))
|
||||
if selected:
|
||||
return selected
|
||||
options = parse_air_options(src)
|
||||
if not options:
|
||||
return None
|
||||
if len(options) == 1:
|
||||
return options[0]
|
||||
facts = facts or {}
|
||||
origin = str(facts.get("originCode") or "").strip().upper()
|
||||
if not origin:
|
||||
origin = resolve_airport_code(
|
||||
str(facts.get("起运港") or facts.get("起运地") or "")
|
||||
)
|
||||
dest = str(facts.get("destinationCode") or "").strip().upper()
|
||||
if not dest:
|
||||
dest = resolve_airport_code(
|
||||
str(facts.get("目的港") or facts.get("目的地") or "")
|
||||
)
|
||||
matched: list[dict[str, Any]] = []
|
||||
for opt in options:
|
||||
oc = str(opt.get("originAirportCode") or opt.get("origin") or "").strip().upper()
|
||||
dc = str(
|
||||
opt.get("destinationAirportCode") or opt.get("destination") or ""
|
||||
).strip().upper()
|
||||
if origin and oc and oc != origin:
|
||||
continue
|
||||
if dest and dc and dc != dest:
|
||||
continue
|
||||
if origin or dest:
|
||||
matched.append(opt)
|
||||
if len(matched) == 1:
|
||||
return matched[0]
|
||||
return None
|
||||
|
||||
|
||||
def _amount_digits(raw: Any) -> str:
|
||||
"""只留数字和小数点,便于判断空运费是否就是总价。"""
|
||||
text = str(raw or "").strip().replace(",", "")
|
||||
for prefix in ("CNY", "USD", "EUR", "HKD", "RMB"):
|
||||
if text.upper().startswith(prefix):
|
||||
text = text[len(prefix):].strip()
|
||||
break
|
||||
return text
|
||||
|
||||
|
||||
def _same_amount(left: Any, right: Any) -> bool:
|
||||
"""金额是否同一笔。币种前缀、逗号、小数末零忽略。"""
|
||||
a = _amount_digits(left)
|
||||
b = _amount_digits(right)
|
||||
if not a or not b or a == "-" or b == "-":
|
||||
return False
|
||||
try:
|
||||
return float(a) == float(b)
|
||||
except ValueError:
|
||||
return a == b
|
||||
|
||||
|
||||
def drop_air_freight_total_alias(quote: dict[str, Any] | None) -> dict[str, Any]:
|
||||
"""
|
||||
整单兜底「空运费=总价」不是线路分项,群摘要禁止再写。
|
||||
|
||||
真有空运费且金额不等于总价(例如另有附加费)则保留。
|
||||
"""
|
||||
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():
|
||||
try:
|
||||
parsed = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
parsed = None
|
||||
if not isinstance(parsed, list):
|
||||
return src
|
||||
kept: list[Any] = []
|
||||
for item in parsed:
|
||||
if not isinstance(item, dict):
|
||||
kept.append(item)
|
||||
continue
|
||||
name = str(item.get("name") or item.get("label") or "").strip()
|
||||
amount = item.get("amount") or item.get("value")
|
||||
if name == "空运费" and _same_amount(amount, total):
|
||||
continue
|
||||
kept.append(item)
|
||||
src["fee_lines"] = kept
|
||||
return src
|
||||
|
||||
|
||||
def project_group_air_quote(
|
||||
quote: dict[str, Any] | None,
|
||||
facts: dict[str, str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
群摘要 TMS 段:有线路就投影该线路费用,与单聊价格明细同一套分项。
|
||||
|
||||
主账查价快照常只有「空运费=总价」;选定后的明细没写回主账,必须现场投影。
|
||||
副作用:无。不写库、不改六态。
|
||||
"""
|
||||
src = dict(quote or {})
|
||||
option = resolve_display_air_option(src, facts)
|
||||
if option:
|
||||
src = project_selected_quote(src, option)
|
||||
return drop_air_freight_total_alias(src)
|
||||
|
||||
Reference in New Issue
Block a user