| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- """运输计划归档 ZIP 导出(计划特有附件收集 + 公共打包)。"""
- from __future__ import annotations
- from typing import Any, Callable, Mapping, Optional, Sequence
- from fastapi.responses import StreamingResponse
- from sqlalchemy.orm import Session
- from app.services.export_attachment_zip import (
- ARCHIVE_ZIP_ATTACH_COL_MAX,
- ARCHIVE_ZIP_MAX_BYTES,
- ARCHIVE_ZIP_MAX_FILES,
- build_attachment_filename,
- build_attachment_zip_bytes,
- build_attachment_zip_streaming_response,
- build_hyperlink_formula,
- first_row_no_by_entity_id,
- parse_url_list,
- sanitize_zip_segment,
- )
- from app.utils.excel_export import ExportColumn
- __all__ = [
- "ARCHIVE_ZIP_ATTACH_COL_MAX",
- "ARCHIVE_ZIP_MAX_BYTES",
- "ARCHIVE_ZIP_MAX_FILES",
- "sanitize_zip_segment",
- "build_attachment_filename",
- "first_row_no_by_plan_id",
- "build_hyperlink_formula",
- "parse_url_list",
- "collect_hazard_cert_items",
- "merge_plan_attachment_items",
- "load_plan_products_by_plan_ids",
- "build_archive_zip_bytes",
- "build_archive_zip_streaming_response",
- ]
- def first_row_no_by_plan_id(rows: Sequence[Mapping[str, Any]]) -> dict[str, int]:
- return first_row_no_by_entity_id(rows, id_field="planId")
- 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
- def build_archive_zip_bytes(
- *,
- db: Optional[Session],
- tenant_no: str,
- columns: Sequence[ExportColumn],
- rows: Sequence[Mapping[str, Any]],
- attachments_by_plan: Mapping[str, Sequence[Mapping[str, str]]],
- stamp: str,
- download_fn: Optional[Callable[[str], bytes]] = None,
- ) -> tuple[bytes, list[str]]:
- """打包归档 ZIP:xlsx + 附件/;返回 (zip_bytes, failure_messages)。"""
- return build_attachment_zip_bytes(
- db=db,
- tenant_no=tenant_no,
- columns=columns,
- rows=rows,
- attachments_by_id=attachments_by_plan,
- stamp=stamp,
- package_label="计划归档",
- id_field="planId",
- sheet_title="计划归档",
- xlsx_basename="(总表)计划归档.xlsx",
- download_fn=download_fn,
- )
- def build_archive_zip_streaming_response(
- *,
- db: Session,
- tenant_no: str,
- columns: Sequence[ExportColumn],
- rows: Sequence[Mapping[str, Any]],
- attachments_by_plan: Mapping[str, Sequence[Mapping[str, str]]],
- stamp: str,
- download_fn: Optional[Callable[[str], bytes]] = None,
- ) -> StreamingResponse:
- return build_attachment_zip_streaming_response(
- db=db,
- tenant_no=tenant_no,
- columns=columns,
- rows=rows,
- attachments_by_id=attachments_by_plan,
- stamp=stamp,
- package_label="计划归档",
- id_field="planId",
- sheet_title="计划归档",
- xlsx_basename="(总表)计划归档.xlsx",
- download_fn=download_fn,
- )
|