落地陆运私聊文字询价:选项核对后再建单查价,并按线路拉产品进群。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -44,6 +44,18 @@ SEA_REQUIRED_FIELDS = (
|
||||
)
|
||||
SEA_DISPLAY = {key: label for key, label in SEA_REQUIRED_FIELDS}
|
||||
|
||||
# 陆运销售侧名称:卡片写始发站/货物品名;内部仍用合同键起运港/品名。
|
||||
LAND_DISPLAY = {
|
||||
"起运港": "始发站",
|
||||
"目的港": "目的地",
|
||||
"品名": "货物品名",
|
||||
"毛重": "重量",
|
||||
"体积": "体积",
|
||||
"车型/数量": "车型/数量",
|
||||
"通关口岸": "通关口岸",
|
||||
"报价日期": "报价日期",
|
||||
}
|
||||
|
||||
# 协同补充字段:拉群后可静默写入,不挡首次查价。内部键仍是 HS编码。
|
||||
SEA_COLLAB_FIELDS = ("贸易条款", "货好时间", "HS编码", "是否含油", "是否含电", "是否含磁")
|
||||
# 空运群只补含电/含磁,摘要不要把海运六项搬过去。
|
||||
@@ -83,7 +95,9 @@ _ALIASES = {
|
||||
"重量(KG)": "毛重",
|
||||
"重量": "毛重",
|
||||
"体积(CBM)": "体积",
|
||||
"车型/数量": "车型数量",
|
||||
"车型/数量": "车型/数量",
|
||||
"车型数量": "车型/数量",
|
||||
"数量": "数量",
|
||||
"HS": "HS编码",
|
||||
"HS CODE": "HS编码",
|
||||
"商品海关编码": "HS编码",
|
||||
@@ -114,9 +128,33 @@ def normalize_facts(facts: dict[str, Any] | None) -> dict[str, str]:
|
||||
if not val:
|
||||
continue
|
||||
out[key] = val
|
||||
_fold_land_vehicle_qty(out)
|
||||
return out
|
||||
|
||||
|
||||
def _fold_land_vehicle_qty(merged: dict[str, str]) -> None:
|
||||
"""
|
||||
把拆开的车型、数量收成一个「车型/数量」。
|
||||
|
||||
销售侧只认这一项必填;模型若仍拆成两项,这里合并,禁止再分别补问。
|
||||
不改主账。有「车型/数量」则保留原文,缺的再用车型、数量拼上。
|
||||
"""
|
||||
combo = (merged.get("车型/数量") or "").strip()
|
||||
vehicle = (merged.get("车型") or "").strip()
|
||||
qty = (merged.get("数量") or "").strip()
|
||||
if not combo:
|
||||
if vehicle and qty:
|
||||
combo = f"{vehicle}/{qty}"
|
||||
elif vehicle:
|
||||
combo = vehicle
|
||||
elif qty:
|
||||
combo = qty
|
||||
elif qty and qty not in combo:
|
||||
combo = f"{combo}/{qty}"
|
||||
if combo:
|
||||
merged["车型/数量"] = combo
|
||||
|
||||
|
||||
def _harvest_packaging(raw: str) -> str:
|
||||
"""当前原话里最后出现的包装词;没有则空串。"""
|
||||
last = ""
|
||||
@@ -235,8 +273,13 @@ def apply_quote_date_default(facts: dict[str, str]) -> dict[str, str]:
|
||||
return merged
|
||||
|
||||
|
||||
def _pricing_required_keys(contract: dict[str, Any], business_line: str, land_subtype: str) -> list[str]:
|
||||
"""当前激活的首次查价内部键列表。"""
|
||||
def _pricing_required_keys(
|
||||
contract: dict[str, Any],
|
||||
business_line: str,
|
||||
land_subtype: str,
|
||||
facts: dict[str, str] | None = None,
|
||||
) -> list[str]:
|
||||
"""当前激活的首次查价内部键列表。中港再按装载方式收窄。"""
|
||||
line = (business_line or "").strip().upper()
|
||||
pricing = contract.get("pricing_required") or {}
|
||||
if line == "SEA":
|
||||
@@ -248,6 +291,14 @@ def _pricing_required_keys(contract: dict[str, Any], business_line: str, land_su
|
||||
block = land.get(land_subtype) or {}
|
||||
if not block:
|
||||
return []
|
||||
if land_subtype == "中港":
|
||||
from agent.schema.land_options import canonicalize_hk_load
|
||||
|
||||
load = canonicalize_hk_load(str((facts or {}).get("中港装载方式") or ""))
|
||||
by_load = block.get("by_hk_load") or {}
|
||||
picked = by_load.get(load) if load else None
|
||||
if isinstance(picked, list):
|
||||
return list(picked)
|
||||
return list(block.get("always") or [])
|
||||
return []
|
||||
|
||||
@@ -259,6 +310,8 @@ def display_name(internal_key: str, business_line: str = "AIR") -> str:
|
||||
return AIR_DISPLAY.get(internal_key, internal_key)
|
||||
if line == "SEA":
|
||||
return SEA_DISPLAY.get(internal_key, internal_key)
|
||||
if line == "LAND":
|
||||
return LAND_DISPLAY.get(internal_key, internal_key)
|
||||
return internal_key
|
||||
|
||||
|
||||
@@ -292,6 +345,10 @@ def validate_required_fields(
|
||||
"errors": [],
|
||||
"business_line": line,
|
||||
}
|
||||
if line == "LAND":
|
||||
land_subtype = (land_subtype or "").strip() or str(
|
||||
normalized.get("运输类型") or ""
|
||||
).strip()
|
||||
if line == "LAND" and not (land_subtype or "").strip():
|
||||
return {
|
||||
"ok": False,
|
||||
@@ -305,7 +362,7 @@ def validate_required_fields(
|
||||
"business_line": line,
|
||||
}
|
||||
|
||||
required = _pricing_required_keys(contract, line, land_subtype)
|
||||
required = _pricing_required_keys(contract, line, land_subtype, normalized)
|
||||
missing = [k for k in required if k != "报价日期" and not normalized.get(k)]
|
||||
missing_display = [display_name(k, line) for k in missing]
|
||||
ok = len(missing) == 0
|
||||
|
||||
Reference in New Issue
Block a user