From efe8aed4098d3787dca6d4dd3e80aa3b4ffc3b0c Mon Sep 17 00:00:00 2001 From: wuzilong <704541015@qq.com> Date: Sat, 12 Sep 2026 21:07:23 +0800 Subject: [PATCH] feat(pl): filter plans by delivery-stop date range Add EXISTS on pl_plan_delivery.delivery_time for list column filters. Co-authored-by: Cursor --- backend/app/services/pl/plan_list_filters.py | 34 +++++++++++++++++++- backend/tests/test_pl_plan_list_filters.py | 30 +++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/backend/app/services/pl/plan_list_filters.py b/backend/app/services/pl/plan_list_filters.py index 1854decf6..dcff61354 100644 --- a/backend/app/services/pl/plan_list_filters.py +++ b/backend/app/services/pl/plan_list_filters.py @@ -11,7 +11,7 @@ from typing import List, Optional, Union from sqlalchemy import func, or_ from sqlalchemy.orm import Session -from app.models.pl_models import PlPlan, PlPlanProduct +from app.models.pl_models import PlPlan, PlPlanDelivery, PlPlanProduct from app.services.pl_plan_service import plan_has_incomplete_hazard_cert_exists DateLike = Union[date, datetime, str, None] @@ -45,6 +45,27 @@ def plan_package_spec_exists(db: Session, tenant_no: str, keyword: str): ) +def plan_delivery_time_exists( + db: Session, + tenant_no: str, + *, + d_from: Optional[date] = None, + d_to: Optional[date] = None, +): + """任一未删除送货段的送货日历日落在 [from, to] 内。""" + q = db.query(PlPlanDelivery.id).filter( + PlPlanDelivery.plan_id == PlPlan.id, + PlPlanDelivery.tenant_no == tenant_no, + PlPlanDelivery.del_flag == 0, + PlPlanDelivery.delivery_time.isnot(None), + ) + if d_from is not None: + q = q.filter(func.date(PlPlanDelivery.delivery_time) >= d_from) + if d_to is not None: + q = q.filter(func.date(PlPlanDelivery.delivery_time) <= d_to) + return q.exists() + + def plan_plate_match(keyword: str): """车牌号 / 挂车号模糊匹配。""" kw = keyword.lower() @@ -109,6 +130,8 @@ def build_plan_column_filters( license_plate: Optional[str] = None, plan_date_from: DateLike = None, plan_date_to: DateLike = None, + delivery_time_from: DateLike = None, + delivery_time_to: DateLike = None, cert_upload_status: Optional[str] = None, hazard_cert_ready: Optional[Union[int, str]] = None, hazard_cert_ready_in: Optional[str] = None, @@ -169,6 +192,15 @@ def build_plan_column_filters( if d_to is not None: filters.append(PlPlan.plan_date <= d_to) + dt_from = _parse_date(delivery_time_from) + dt_to = _parse_date(delivery_time_to) + if (dt_from is not None or dt_to is not None) and db is not None: + filters.append( + plan_delivery_time_exists( + db, tenant_no, d_from=dt_from, d_to=dt_to + ) + ) + if (cert_upload_status or "").strip().lower() == "incomplete": filters.append(plan_has_incomplete_hazard_cert_exists(tenant_no)) diff --git a/backend/tests/test_pl_plan_list_filters.py b/backend/tests/test_pl_plan_list_filters.py index f8bd0d909..1a88ef9db 100644 --- a/backend/tests/test_pl_plan_list_filters.py +++ b/backend/tests/test_pl_plan_list_filters.py @@ -120,3 +120,33 @@ def test_hazard_cert_ready_single(): ) assert len(filters) == 1 assert "hazard_cert_ready" in _compile(filters[0]) + + +def test_delivery_time_range_exists_requires_db(): + filters = build_plan_column_filters( + db=None, + tenant_no="shanxia2.0", + delivery_time_from=date(2026, 9, 12), + delivery_time_to=date(2026, 9, 14), + ) + assert filters == [] + + +def test_delivery_time_range_exists_sql(): + from unittest.mock import MagicMock + + db = MagicMock() + q = MagicMock() + db.query.return_value = q + q.filter.return_value = q + q.exists.return_value = "DELIVERY_TIME_EXISTS" + + filters = build_plan_column_filters( + db=db, + tenant_no="shanxia2.0", + delivery_time_from=date(2026, 9, 12), + delivery_time_to="2026-09-14", + ) + assert filters == ["DELIVERY_TIME_EXISTS"] + assert db.query.called + assert q.exists.called