落地海运协同群:测服靠会话存档听群话,报价单能带上协同字段。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user