落地海运协同群:测服靠会话存档听群话,报价单能带上协同字段。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-15 18:12:45 +08:00
co-authored by Cursor
parent 235ecd8c83
commit 12219cacb1
39 changed files with 4436 additions and 256 deletions
+125
View File
@@ -0,0 +1,125 @@
"""
按海运询价单标签补写协同字段。
主账填表只认英文码,货好时间 / 海关编码 / 含油电磁常空着。
本文件只改 Excel 字节,不打主账、不占回调线程。
"""
from __future__ import annotations
import logging
from pathlib import Path
from typing import Any, Mapping
logger = logging.getLogger(__name__)
# 长的「含油,电,磁」必须排在「是否含油」前面,避免只写到油。
_COLLAB_LABEL_MARKERS: tuple[tuple[str, tuple[str, ...]], ...] = (
("是否含油,电,磁", ("oil_elec_mag", "是否含油,电,磁")),
("OIL,ELEC,MAG", ("oil_elec_mag", "是否含油,电,磁")),
("贸易条款", ("贸易条款", "trade_terms")),
("货好时间", ("货好时间", "cargo_ready_date")),
("商品海关编码", ("商品海关编码", "HS编码", "海关编码", "hs_code")),
("HS Code", ("商品海关编码", "HS编码", "海关编码", "hs_code")),
("是否含油", ("是否含油", "has_oil")),
("是否含电", ("是否含电", "has_battery")),
("是否含磁", ("是否含磁", "has_magnet")),
)
def _fact_for_markers(facts: Mapping[str, Any], keys: tuple[str, ...]) -> str:
for key in keys:
val = str((facts or {}).get(key) or "").strip()
if val and val != "-":
return val
return ""
def overlay_labeled_facts(
*,
facts: Mapping[str, Any],
filepath: str = "",
file_b64: str = "",
) -> dict[str, str]:
"""
按模板标签再写协同格。失败则原样交回。
副作用:可能写临时 xlsx。
"""
import base64
import tempfile
raw = b""
if filepath:
try:
raw = Path(filepath).read_bytes()
except OSError as exc:
logger.warning("overlay 读文件失败 err=%s", exc)
if not raw and file_b64 and len(file_b64) > 64:
try:
raw = base64.b64decode(file_b64)
except Exception as exc: # noqa: BLE001
logger.warning("overlay 解码失败 err=%s", exc)
if not raw:
return {"filepath": filepath, "file_b64": file_b64}
try:
from openpyxl import load_workbook
except ImportError:
return {"filepath": filepath, "file_b64": file_b64}
tmp = Path(tempfile.mkdtemp(prefix="quote-overlay-")) / "filled.xlsx"
tmp.write_bytes(raw)
written = 0
try:
wb = load_workbook(tmp)
ws = wb.worksheets[0]
pending: list[tuple[int, int, str]] = []
used_rows: set[int] = set()
for row in ws.iter_rows(min_row=1, max_row=ws.max_row or 1, max_col=min(ws.max_column or 1, 16)):
for cell in row:
text = str(cell.value or "").strip()
if not text or cell.row in used_rows:
continue
keys = _match_collab_label(text)
if not keys:
continue
val = _fact_for_markers(facts, keys)
if not val:
continue
dest = _label_value_cell(ws, cell)
pending.append((dest.row, dest.column, val))
used_rows.add(cell.row)
break
for r, c, val in pending:
ws.cell(row=r, column=c, value=val)
written += 1
wb.save(tmp)
wb.close()
except Exception:
logger.exception("overlay 协同字段失败")
return {"filepath": filepath, "file_b64": file_b64}
out_b64 = base64.b64encode(tmp.read_bytes()).decode("ascii")
logger.info("overlay 协同字段 written=%s", written)
return {"filepath": str(tmp), "file_b64": out_b64}
def _match_collab_label(text: str) -> tuple[str, ...] | None:
raw = (text or "").replace(":", ":").strip()
for marker, keys in _COLLAB_LABEL_MARKERS:
if marker in raw:
return keys
return None
def _label_value_cell(ws, label_cell):
"""标签右侧格子;合并标签则从合并区右边开始。"""
row_idx = label_cell.row
start_col = label_cell.column + 1
merged = getattr(ws, "merged_cells", None)
if merged is not None:
for rng in merged.ranges:
if rng.min_row <= row_idx <= rng.max_row and rng.min_col <= label_cell.column <= rng.max_col:
start_col = rng.max_col + 1
break
return ws.cell(row=row_idx, column=start_col)