Files
shanxia2.0/backend/app/services/pl_public_transport_helpers.py
T
2026-09-26 11:59:39 +08:00

110 lines
3.1 KiB
Python

"""运输计划公共页共享纯函数(司机/现场 history 等)。"""
from __future__ import annotations
import calendar
import re
from datetime import date, datetime
from typing import Any, Iterable, Optional
from app.models.pl_models import PlPlan, PlTransport
CRM_PRODUCT_UNIT = "crm_product_unit"
_MONTH_RE = re.compile(r"^(\d{4})-(\d{2})$")
def _resolve_unit_label(unit_map: dict[str, str], unit_code: Optional[str]) -> str:
code = (unit_code or "").strip()
if not code:
return "吨"
return unit_map.get(code, code)
def _normalize_main_plate(value: Optional[str]) -> str:
return (value or "").strip()
def _parse_history_date(raw: Optional[str]) -> Optional[date]:
text = (raw or "").strip()
if not text:
return None
try:
return date.fromisoformat(text)
except ValueError:
return None
def _parse_calendar_month(raw: Optional[str]) -> Optional[tuple[date, date]]:
"""解析 YYYY-MM,返回当月 [start, end];无效返回 None。"""
text = (raw or "").strip()
m = _MONTH_RE.match(text)
if not m:
return None
year, month = int(m.group(1)), int(m.group(2))
if month < 1 or month > 12:
return None
last_day = calendar.monthrange(year, month)[1]
return date(year, month, 1), date(year, month, last_day)
def _as_plain_date(value: Any) -> Optional[date]:
if value is None:
return None
if isinstance(value, datetime):
return value.date()
if isinstance(value, date):
return value
return None
def _plan_hit_dates(plan: Any, *, include_plan_date: bool) -> set[date]:
"""计划在 history 中可能命中的日期集合。"""
days: set[date] = set()
if include_plan_date:
d = _as_plain_date(getattr(plan, "plan_date", None))
if d is not None:
days.add(d)
for attr in ("pickup_time", "delivery_time"):
d = _as_plain_date(getattr(plan, attr, None))
if d is not None:
days.add(d)
return days
def _accumulate_calendar_day_counts(
plans: Iterable[Any],
*,
month_start: date,
month_end: date,
include_plan_date: bool,
) -> dict[str, int]:
"""按日累计计划条数(仅统计落在当月内的命中日)。"""
counts: dict[str, int] = {}
for plan in plans:
for day in _plan_hit_dates(plan, include_plan_date=include_plan_date):
if day < month_start or day > month_end:
continue
key = day.isoformat()
counts[key] = counts.get(key, 0) + 1
return counts
def _history_status_label(plan_voided: bool, plan_archived: bool) -> str:
if plan_voided:
return "已作废"
if plan_archived:
return "已归档"
return "进行中"
def _resolve_driver_link_flags(
plan: Optional[PlPlan], transport: PlTransport
) -> tuple[bool, bool]:
"""返回 (作废, 归档)。"""
voided = (
(plan is not None and plan.follow_status == "cancelled")
or transport.transport_status == "cancelled"
)
archived = bool(plan is not None and plan.archive_status == "archived")
return voided, archived