空运成交后可在本群继续锁舱;调价同步舱位费用桶;出单按空运段骨架落费并保留陆运车型尺寸;导出文件名乱码修复。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -903,10 +903,12 @@ _SKIP_FEE_NAMES = frozenset(
|
||||
)
|
||||
# 航线常写「空运费 单价CNY99,数量100,金额CNY199」。单价和金额不是同一个数,必须认金额。
|
||||
# WO202610130003:协同句常见「空运费, KG, 单价CNY99.00, 数量100, 金额CNY9900」——费名与单价之间夹单位;
|
||||
# WO202610140008:航线写「增值费_板,单价CNY10…」——下划线单位;漏认会落到金额误抽,出单默认单位「票」。
|
||||
# 数量/金额前也可能只有空格无逗号。漏匹配会落到 _RE_FEE_LINE,把单价 99 当成行金额、数量被补成 1。
|
||||
_FEE_UOM = r"KG|KGS|公斤|千克|TON|CBM|柜|票|板数|板|点|[A-Za-z]{1,8}"
|
||||
_RE_FEE_PRICED = re.compile(
|
||||
r"(?P<name>[\u4e00-\u9fffA-Za-z][\u4e00-\u9fffA-Za-z0-9/()()++\--]{0,24}费)"
|
||||
r"(?:\s*[,,、]\s*(?:KG|KGS|公斤|千克|TON|CBM|柜|票|板|点|[A-Za-z]{1,8}))?"
|
||||
rf"(?:(?:\s*[,,、/]\s*|\s+|_)(?P<uom>{_FEE_UOM}))?"
|
||||
r"\s*[,,、]?\s*单价\s*"
|
||||
rf"(?:(?P<unit_ccy>{_CCY_TOKEN})\s*)?"
|
||||
r"(?P<unit>\d+(?:\.\d+)?)"
|
||||
@@ -916,6 +918,11 @@ _RE_FEE_PRICED = re.compile(
|
||||
r"(?P<amount>\d+(?:\.\d+)?))?",
|
||||
re.I,
|
||||
)
|
||||
# 句首分段:起运港、增值费_板… → project=起运港(出单落到对应项目区)
|
||||
_RE_FEE_PROJECT_PREFIX = re.compile(
|
||||
r"^\s*(?P<project>空运段|空运|起运港|起运地|目的地|目的港|其它|其他)\s*[,,、::]\s*",
|
||||
re.I,
|
||||
)
|
||||
_FULLWIDTH_NUM = str.maketrans("0123456789.", "0123456789.")
|
||||
|
||||
|
||||
@@ -950,14 +957,56 @@ def _currency_of(text: str, hint: str = "") -> str:
|
||||
return str(hint or "").strip().upper()
|
||||
|
||||
|
||||
def _normalize_fee_project(raw: str) -> str:
|
||||
"""空运报价单项目列:空运段/起运港/目的地/其它。"""
|
||||
t = (raw or "").strip()
|
||||
if t in {"空运", "空运段"}:
|
||||
return "空运段"
|
||||
if t in {"起运港", "起运地"}:
|
||||
return "起运港"
|
||||
if t in {"目的地", "目的港"}:
|
||||
return "目的地"
|
||||
if t in {"其它", "其他"}:
|
||||
return "其它"
|
||||
return ""
|
||||
|
||||
|
||||
def _strip_group_bot_mentions(text: str) -> str:
|
||||
"""
|
||||
去掉群里 @询价小助手 前缀,避免挡住句首「起运港、」分段标签。
|
||||
|
||||
企微协同句常以 @ 开头;不剥的话 project 抽不到,出单只能靠附加费默认「起运港」,
|
||||
且二次盖价时更容易丢掉分段。
|
||||
副作用:无。
|
||||
"""
|
||||
cleaned = (text or "").replace("@询价小助手", "").replace("询价小助手", "")
|
||||
return cleaned.strip()
|
||||
|
||||
|
||||
def _split_fee_project_prefix(text: str) -> tuple[str, str]:
|
||||
"""
|
||||
拆句首分段标签。例:起运港、增值费_板,单价… → (起运港, 增值费_板,单价…)
|
||||
副作用:无。
|
||||
"""
|
||||
raw = _strip_group_bot_mentions(text or "")
|
||||
hit = _RE_FEE_PROJECT_PREFIX.match(raw)
|
||||
if not hit:
|
||||
return "", raw
|
||||
project = _normalize_fee_project(hit.group("project") or "")
|
||||
rest = raw[hit.end() :]
|
||||
return project, rest
|
||||
|
||||
|
||||
def _parse_fee_rows(text: str, baseline_currency: str = "") -> list[dict[str, str]]:
|
||||
"""
|
||||
按行抽费用:码头操作费100CNY、报关费改为300CNY。
|
||||
|
||||
只认带「费」的中文名或短英文费用码,避免把工单号当金额。
|
||||
句里无币种时继承 baseline_currency(通常是当前 TMS/主账币种),禁止默认 USD。
|
||||
认「费名, KG, 单价…」与「费名_板,单价…」里的计量单位,写入 unit,禁止出单再默认「票」。
|
||||
"""
|
||||
raw = _ascii_amount(text or "")
|
||||
project, body = _split_fee_project_prefix(text or "")
|
||||
raw = _ascii_amount(body)
|
||||
base_ccy = str(baseline_currency or "").strip().upper()
|
||||
rows: list[dict[str, str]] = []
|
||||
seen: set[str] = set()
|
||||
@@ -977,6 +1026,8 @@ def _parse_fee_rows(text: str, baseline_currency: str = "") -> list[dict[str, st
|
||||
"amount": amount,
|
||||
"currency": line_ccy,
|
||||
}
|
||||
if project:
|
||||
row["project"] = project
|
||||
for key, val in extra.items():
|
||||
if val:
|
||||
row[key] = val
|
||||
@@ -986,10 +1037,12 @@ def _parse_fee_rows(text: str, baseline_currency: str = "") -> list[dict[str, st
|
||||
# 有「金额」用金额;只有单价时才用单价。禁止把单价误当成这一行的总金额。
|
||||
amount = (hit.group("amount") or "").strip() or (hit.group("unit") or "").strip()
|
||||
ccy = hit.group("amt_ccy") or hit.group("unit_ccy") or ""
|
||||
uom = (hit.groupdict().get("uom") or "").strip()
|
||||
_add(
|
||||
(hit.group("name") or "").strip(),
|
||||
_ascii_amount(amount),
|
||||
ccy,
|
||||
unit=uom,
|
||||
unit_price=_ascii_amount(hit.group("unit") or ""),
|
||||
quantity=_ascii_amount(hit.group("qty") or ""),
|
||||
)
|
||||
@@ -1054,15 +1107,16 @@ def extract_product_quote(
|
||||
raw = (text or "").strip()
|
||||
if not raw:
|
||||
return {}
|
||||
stripped = raw.replace("@询价小助手", "").replace("询价小助手", "")
|
||||
stripped = _strip_group_bot_mentions(raw)
|
||||
compact = stripped.replace(" ", "").replace(" ", "")
|
||||
if re.fullmatch(r"WO\d{12}", compact, re.I):
|
||||
return {}
|
||||
band = _parse_weight_band_quote(raw, baseline_currency=base_ccy)
|
||||
band = _parse_weight_band_quote(stripped or raw, baseline_currency=base_ccy)
|
||||
if band:
|
||||
return band
|
||||
# 费率句若仍落到普通费用行,会把「燃油附加费」配上句首公斤数。先剥掉再抽。
|
||||
scrubbed = _strip_rate_and_band_noise(raw)
|
||||
# 必须用剥掉 @询价小助手 后的正文,否则句首「起运港、」分段标签匹配不到。
|
||||
scrubbed = _strip_rate_and_band_noise(stripped or raw)
|
||||
rows = _parse_fee_rows(scrubbed, baseline_currency=base_ccy) if scrubbed.strip() else []
|
||||
if rows:
|
||||
ccy = rows[0].get("currency") or _currency_of(raw, base_ccy) or base_ccy
|
||||
|
||||
Reference in New Issue
Block a user