diff --git a/backend/app/api/pl/public_transport.py b/backend/app/api/pl/public_transport.py index e6cf909a8..c00099f56 100644 --- a/backend/app/api/pl/public_transport.py +++ b/backend/app/api/pl/public_transport.py @@ -6,7 +6,8 @@ import os from datetime import date, datetime from typing import Optional -from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile +from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile +from sqlalchemy import and_, func, or_ from sqlalchemy.orm import Session from app.core.storage import storage @@ -28,6 +29,8 @@ from app.schemas.pl.io_order import BrandOption, BrandQtyItem from app.schemas.pl.resource_snapshot import parse_resource_snapshot from app.schemas.pl.transport import ( PublicTransportFeedback, + PublicTransportHistoryPageRead, + PublicTransportHistoryRecordRead, PublicTransportPageRead, PublicTransportPersonnelRead, PublicTransportPlanProductRead, @@ -123,6 +126,11 @@ def _history_status_label(plan_voided: bool, plan_archived: bool) -> str: return "进行中" +def _build_driver_url(token: str) -> str: + origin = (settings.PUBLIC_WEB_ORIGIN or "").rstrip("/") + return f"{origin}/#/public/pl/transport/{token}" + + def _resolve_driver_link_flags( plan: Optional[PlPlan], transport: PlTransport ) -> tuple[bool, bool]: @@ -327,6 +335,93 @@ def get_public_transport(token: str, db: Session = Depends(get_db)): return success_response(page.model_dump(by_alias=True)) +@router.get("/{token}/history") +def get_public_transport_history( + token: str, + date: str = Query(..., description="筛选日 YYYY-MM-DD"), + db: Session = Depends(get_db), +): + transport = _get_transport_by_token(db, token) + day = _parse_history_date(date) + if day is None: + return error_response(400, "日期格式无效,请使用 YYYY-MM-DD") + + plate = _normalize_main_plate(transport.license_plate) + if not plate: + page = PublicTransportHistoryPageRead( + license_plate=None, date=day.isoformat(), records=[] + ) + return success_response(page.model_dump(by_alias=True)) + + rows = ( + db.query(PlPlan, PlTransport) + .join( + PlPlanTransport, + and_( + PlPlanTransport.plan_id == PlPlan.id, + PlPlanTransport.del_flag == 0, + ), + ) + .join( + PlTransport, + and_( + PlTransport.id == PlPlanTransport.transport_id, + PlTransport.del_flag == 0, + ), + ) + .filter( + PlPlan.tenant_no == transport.tenant_no, + PlPlan.del_flag == 0, + PlPlan.transport_status == "arranged", + PlTransport.tenant_no == transport.tenant_no, + PlTransport.license_plate == plate, + or_( + and_(PlPlan.pickup_time.isnot(None), func.date(PlPlan.pickup_time) == day), + and_(PlPlan.delivery_time.isnot(None), func.date(PlPlan.delivery_time) == day), + ), + ) + .order_by( + PlPlan.pickup_time.desc(), + PlPlan.plan_date.desc(), + PlPlan.create_time.desc(), + ) + .all() + ) + + seen_plan: set[str] = set() + records: list[PublicTransportHistoryRecordRead] = [] + for plan, tr in rows: + if plan.id in seen_plan: + continue + seen_plan.add(plan.id) + voided, archived = _resolve_driver_link_flags(plan, tr) + tok = (tr.driver_access_token or "").strip() + records.append( + PublicTransportHistoryRecordRead( + plan_id=plan.id, + plan_no=plan.plan_no or "", + transport_id=tr.id, + driver_access_token=tok or None, + driver_url=_build_driver_url(tok) if tok else None, + license_plate=tr.license_plate or plate, + pickup_time=plan.pickup_time, + delivery_time=plan.delivery_time, + pickup_address=plan.pickup_address, + destination_address=plan.destination_address or plan.destination_name, + plan_voided=voided, + plan_archived=archived, + status_label=_history_status_label(voided, archived), + ) + ) + + page = PublicTransportHistoryPageRead( + license_plate=plate, + date=day.isoformat(), + records=records, + ) + return success_response(page.model_dump(by_alias=True)) + + @router.post("/{token}/feedback") def save_public_feedback( token: str,