"""运输计划列表:聚合司机/现场/出入库现场等单据附件。""" from __future__ import annotations import json from typing import Optional from sqlalchemy.orm import Session from app.models.pl_models import ( PlInboundOrder, PlOutboundOrder, PlPlan, PlPlanDelivery, PlTransport, PlTransportDriverStopFeedback, PlTransportSiteLink, PlTransportSiteStopFeedback, ) def _parse_image_urls(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(item).strip() for item in parsed if str(item).strip()] except json.JSONDecodeError: pass return [item.strip() for item in text.split(",") if item.strip()] def _short_label(text: str, max_len: int = 8) -> str: value = (text or "").strip() if not value: return "现场" if len(value) <= max_len: return value return f"{value[: max_len - 1]}…" def _stop_source_label( stop_type: str, stop_id: str, source: str, delivery_seq_by_id: dict[str, int], ) -> str: normalized = (stop_type or "").strip() if normalized == "load": return f"装货·{source}" seq = delivery_seq_by_id.get((stop_id or "").strip()) if seq: return f"送货#{seq}·{source}" return f"送货·{source}" def _stop_sort_key( stop_type: str, stop_id: str, source: str, delivery_seq_by_id: dict[str, int], ) -> tuple[int, int, int, str]: normalized = (stop_type or "").strip() stop_rank = 0 if normalized == "load" else 1 seq = delivery_seq_by_id.get((stop_id or "").strip(), 999) if normalized != "load" else 0 source_rank = 0 if source == "司机" else 1 return (stop_rank, seq, source_rank, stop_id or "") def load_plan_document_attachments_by_plan_ids( db: Session, *, tenant_no: str, plan_ids: list[str], ) -> dict[str, list[dict[str, str]]]: if not plan_ids: return {} result: dict[str, list[dict[str, str]]] = {plan_id: [] for plan_id in plan_ids} seen: dict[str, set[str]] = {plan_id: set() for plan_id in plan_ids} pending: dict[str, list[tuple[tuple[int, int, int, str], str, list[str]]]] = { plan_id: [] for plan_id in plan_ids } def add(plan_id: str, label: str, urls: list[str], sort_key: tuple[int, int, int, str]) -> None: if plan_id not in pending: return cleaned = [url for url in urls if url] if not cleaned: return pending[plan_id].append((sort_key, label, cleaned)) def flush(plan_id: str) -> None: bucket = result.get(plan_id) if bucket is None: return for _, label, urls in sorted(pending.get(plan_id, []), key=lambda item: item[0]): for url in urls: if url in seen[plan_id]: continue seen[plan_id].add(url) bucket.append({"url": url, "label": label}) delivery_seq_by_id: dict[str, int] = {} delivery_rows = ( db.query(PlPlanDelivery.id, PlPlanDelivery.plan_id, PlPlanDelivery.seq) .filter( PlPlanDelivery.plan_id.in_(plan_ids), PlPlanDelivery.tenant_no == tenant_no, PlPlanDelivery.del_flag == 0, ) .all() ) for delivery_id, _plan_id, seq in delivery_rows: delivery_seq_by_id[str(delivery_id)] = int(seq or 0) plan_rows = ( db.query(PlPlan.id, PlPlan.transport_id) .filter( PlPlan.id.in_(plan_ids), PlPlan.tenant_no == tenant_no, PlPlan.del_flag == 0, ) .all() ) transport_ids = [transport_id for _, transport_id in plan_rows if transport_id] plan_by_transport = { transport_id: plan_id for plan_id, transport_id in plan_rows if transport_id } if transport_ids: transports = ( db.query(PlTransport) .filter( PlTransport.id.in_(transport_ids), PlTransport.tenant_no == tenant_no, PlTransport.del_flag == 0, ) .all() ) for transport in transports: plan_id = plan_by_transport.get(transport.id) if not plan_id: continue add( plan_id, "司机", _parse_image_urls(transport.feedback_images), (0, 0, 0, "transport"), ) driver_rows = ( db.query(PlTransportDriverStopFeedback) .filter( PlTransportDriverStopFeedback.plan_id.in_(plan_ids), PlTransportDriverStopFeedback.tenant_no == tenant_no, PlTransportDriverStopFeedback.del_flag == 0, ) .all() ) for row in driver_rows: label = _stop_source_label(row.stop_type, row.stop_id, "司机", delivery_seq_by_id) add( row.plan_id, label, _parse_image_urls(row.attachment_urls), _stop_sort_key(row.stop_type, row.stop_id, "司机", delivery_seq_by_id), ) site_stop_rows = ( db.query(PlTransportSiteStopFeedback) .filter( PlTransportSiteStopFeedback.plan_id.in_(plan_ids), PlTransportSiteStopFeedback.tenant_no == tenant_no, PlTransportSiteStopFeedback.del_flag == 0, ) .all() ) for row in site_stop_rows: label = _stop_source_label(row.stop_type, row.stop_id, "现场", delivery_seq_by_id) add( row.plan_id, label, _parse_image_urls(row.attachment_urls), _stop_sort_key(row.stop_type, row.stop_id, "现场", delivery_seq_by_id), ) site_rows = ( db.query(PlTransportSiteLink) .filter( PlTransportSiteLink.plan_id.in_(plan_ids), PlTransportSiteLink.tenant_no == tenant_no, PlTransportSiteLink.del_flag == 0, ) .order_by(PlTransportSiteLink.order_num, PlTransportSiteLink.create_time) .all() ) for row in site_rows: product = (row.product or "").strip() label = f"现场·{_short_label(product)}" if product else "现场" add(row.plan_id, label, _parse_image_urls(row.site_feedback_images), (2, 0, 1, row.id or "")) inbound_rows = ( db.query(PlInboundOrder) .filter( PlInboundOrder.plan_id.in_(plan_ids), PlInboundOrder.tenant_no == tenant_no, PlInboundOrder.del_flag == 0, ) .all() ) for row in inbound_rows: add(row.plan_id, "入库现场", _parse_image_urls(row.site_feedback_images), (3, 0, 1, row.id or "")) outbound_rows = ( db.query(PlOutboundOrder) .filter( PlOutboundOrder.plan_id.in_(plan_ids), PlOutboundOrder.tenant_no == tenant_no, PlOutboundOrder.del_flag == 0, ) .all() ) for row in outbound_rows: add(row.plan_id, "出库现场", _parse_image_urls(row.site_feedback_images), (3, 0, 2, row.id or "")) for plan_id in plan_ids: flush(plan_id) return result