|
|
@@ -1,8 +1,9 @@
|
|
|
from __future__ import annotations
|
|
|
|
|
|
import hashlib
|
|
|
+import json
|
|
|
import re
|
|
|
-from typing import Any, Mapping, Sequence
|
|
|
+from typing import Any, Mapping, Optional, Sequence
|
|
|
from urllib.parse import unquote, urlparse
|
|
|
|
|
|
ARCHIVE_ZIP_ATTACH_COL_MAX = 20
|
|
|
@@ -55,3 +56,70 @@ def build_hyperlink_formula(rel_path: str, display: str) -> str:
|
|
|
path = str(rel_path).replace('"', '""')
|
|
|
text = sanitize_zip_segment(display).replace('"', '""')
|
|
|
return f'=HYPERLINK("{path}","{text}")'
|
|
|
+
|
|
|
+
|
|
|
+def parse_url_list(raw: Optional[str]) -> list[str]:
|
|
|
+ if not raw:
|
|
|
+ return []
|
|
|
+ text = str(raw).strip()
|
|
|
+ if not text:
|
|
|
+ return []
|
|
|
+ try:
|
|
|
+ parsed = json.loads(text)
|
|
|
+ if isinstance(parsed, list):
|
|
|
+ return [str(x).strip() for x in parsed if str(x).strip()]
|
|
|
+ except json.JSONDecodeError:
|
|
|
+ pass
|
|
|
+ return [p.strip() for p in text.split(",") if p.strip()]
|
|
|
+
|
|
|
+
|
|
|
+def collect_hazard_cert_items(products: Sequence[Any]) -> list[dict[str, str]]:
|
|
|
+ out: list[dict[str, str]] = []
|
|
|
+ for product in products or []:
|
|
|
+ pname = sanitize_zip_segment(getattr(product, "product_name", None) or "")
|
|
|
+ specs = (
|
|
|
+ ("购买证", getattr(product, "purchase_cert_url", None)),
|
|
|
+ ("运输证", getattr(product, "transport_cert_url", None)),
|
|
|
+ ("合法使用证明", getattr(product, "legal_use_cert_url", None)),
|
|
|
+ )
|
|
|
+ for base_label, raw in specs:
|
|
|
+ for url in parse_url_list(raw):
|
|
|
+ label = base_label if not pname or pname == "未命名" else f"{base_label}·{pname}"
|
|
|
+ out.append({"url": url, "label": label})
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
+def merge_plan_attachment_items(
|
|
|
+ document_items: Sequence[Mapping[str, str]],
|
|
|
+ hazard_items: Sequence[Mapping[str, str]],
|
|
|
+) -> list[dict[str, str]]:
|
|
|
+ seen: set[str] = set()
|
|
|
+ out: list[dict[str, str]] = []
|
|
|
+ for item in list(document_items or []) + list(hazard_items or []):
|
|
|
+ url = str(item.get("url") or "").strip()
|
|
|
+ if not url or url in seen:
|
|
|
+ continue
|
|
|
+ seen.add(url)
|
|
|
+ out.append({"url": url, "label": str(item.get("label") or "附件")})
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
+def load_plan_products_by_plan_ids(db, *, tenant_no: str, plan_ids: list[str]):
|
|
|
+ from app.models.pl_models import PlPlanProduct
|
|
|
+
|
|
|
+ if not plan_ids:
|
|
|
+ return {}
|
|
|
+ rows = (
|
|
|
+ db.query(PlPlanProduct)
|
|
|
+ .filter(
|
|
|
+ PlPlanProduct.plan_id.in_(plan_ids),
|
|
|
+ PlPlanProduct.tenant_no == tenant_no,
|
|
|
+ PlPlanProduct.del_flag == 0,
|
|
|
+ )
|
|
|
+ .order_by(PlPlanProduct.order_num, PlPlanProduct.create_time)
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+ grouped: dict[str, list] = {pid: [] for pid in plan_ids}
|
|
|
+ for row in rows:
|
|
|
+ grouped.setdefault(row.plan_id, []).append(row)
|
|
|
+ return grouped
|