247 lines
7.3 KiB
Python
247 lines
7.3 KiB
Python
"""运输计划 - 人员 upsert 与车辆-人员关联保存"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from fastapi import HTTPException
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.id_card import calc_age_from_chinese_id_card
|
|
from app.core.utils import generate_uuid
|
|
from app.models.pl_models import PlPersonnel, PlVehiclePersonnel
|
|
from app.schemas.pl.base_data import VehiclePersonnelRow
|
|
|
|
|
|
def upsert_personnel(
|
|
db: Session,
|
|
tenant_no: str,
|
|
row: VehiclePersonnelRow,
|
|
*,
|
|
user_id: str,
|
|
dept_id: str,
|
|
vehicle_type: str = "self",
|
|
) -> str:
|
|
"""按 personnelId → phone → name 弱匹配 upsert,返回 personnel_id。"""
|
|
personnel: Optional[PlPersonnel] = None
|
|
|
|
if row.personnel_id:
|
|
personnel = (
|
|
db.query(PlPersonnel)
|
|
.filter(
|
|
PlPersonnel.id == row.personnel_id,
|
|
PlPersonnel.tenant_no == tenant_no,
|
|
PlPersonnel.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
phone = (row.phone_no or "").strip() or None
|
|
if personnel is None and phone:
|
|
personnel = (
|
|
db.query(PlPersonnel)
|
|
.filter(
|
|
PlPersonnel.tenant_no == tenant_no,
|
|
PlPersonnel.phone_no == phone,
|
|
PlPersonnel.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
name = (row.personnel_name or "").strip()
|
|
if personnel is None and name:
|
|
personnel = (
|
|
db.query(PlPersonnel)
|
|
.filter(
|
|
PlPersonnel.tenant_no == tenant_no,
|
|
PlPersonnel.personnel_name == name,
|
|
PlPersonnel.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
if personnel:
|
|
personnel.personnel_name = name or personnel.personnel_name
|
|
personnel.phone_no = phone
|
|
if row.age is not None:
|
|
personnel.age = row.age
|
|
personnel.update_by = user_id
|
|
return personnel.id
|
|
|
|
affiliation = (vehicle_type or "self").strip()
|
|
if affiliation not in ("self", "carrier"):
|
|
affiliation = "self"
|
|
|
|
personnel = PlPersonnel(
|
|
id=generate_uuid(),
|
|
tenant_no=tenant_no,
|
|
personnel_name=name,
|
|
phone_no=phone,
|
|
age=row.age,
|
|
personnel_type="both",
|
|
personnel_affiliation=affiliation,
|
|
status="1",
|
|
create_by=user_id,
|
|
create_dept_id=dept_id,
|
|
)
|
|
db.add(personnel)
|
|
db.flush()
|
|
return personnel.id
|
|
|
|
|
|
def save_vehicle_personnel(
|
|
db: Session,
|
|
vehicle_id: str,
|
|
rows: list[VehiclePersonnelRow],
|
|
tenant_no: str,
|
|
*,
|
|
user_id: str,
|
|
dept_id: str,
|
|
vehicle_type: str = "self",
|
|
) -> None:
|
|
"""软删旧关联后批量插入;校验默认司机唯一。"""
|
|
default_count = sum(
|
|
1 for r in rows if r.role == "driver" and r.is_default == 1
|
|
)
|
|
if default_count > 1:
|
|
raise HTTPException(status_code=400, detail="每辆车最多设置一名默认司机")
|
|
|
|
db.query(PlVehiclePersonnel).filter(
|
|
PlVehiclePersonnel.vehicle_id == vehicle_id,
|
|
PlVehiclePersonnel.tenant_no == tenant_no,
|
|
PlVehiclePersonnel.del_flag == 0,
|
|
).update({"del_flag": 1, "update_by": user_id}, synchronize_session=False)
|
|
|
|
for idx, row in enumerate(rows):
|
|
if row.role == "escort" and row.is_default == 1:
|
|
raise HTTPException(status_code=400, detail="仅司机角色可设为默认")
|
|
|
|
personnel_id = upsert_personnel(
|
|
db,
|
|
tenant_no,
|
|
row,
|
|
user_id=user_id,
|
|
dept_id=dept_id,
|
|
vehicle_type=vehicle_type,
|
|
)
|
|
link = PlVehiclePersonnel(
|
|
id=generate_uuid(),
|
|
tenant_no=tenant_no,
|
|
vehicle_id=vehicle_id,
|
|
personnel_id=personnel_id,
|
|
role=row.role,
|
|
is_default=row.is_default if row.role == "driver" else 0,
|
|
remark=row.remark,
|
|
order_num=idx,
|
|
create_by=user_id,
|
|
create_dept_id=dept_id,
|
|
)
|
|
db.add(link)
|
|
|
|
|
|
def build_personnel_summary(
|
|
links: list[tuple[str, str, int]],
|
|
) -> tuple[str, Optional[str]]:
|
|
"""links: [(personnel_name, role, is_default)] → (summary, default_driver_name)."""
|
|
role_labels = {"driver": "驾", "escort": "押"}
|
|
parts: list[str] = []
|
|
default_name: Optional[str] = None
|
|
|
|
for name, role, is_default in links:
|
|
label = role_labels.get(role, role)
|
|
parts.append(f"{name}({label})")
|
|
if role == "driver" and int(is_default) == 1:
|
|
default_name = name
|
|
|
|
return "、".join(parts), default_name
|
|
|
|
|
|
def load_vehicle_personnel_rows(
|
|
db: Session, tenant_no: str, vehicle_id: str
|
|
) -> list[VehiclePersonnelRow]:
|
|
"""查询车辆关联人员子表行。"""
|
|
rows = (
|
|
db.query(PlVehiclePersonnel, PlPersonnel)
|
|
.join(
|
|
PlPersonnel,
|
|
PlPersonnel.id == PlVehiclePersonnel.personnel_id,
|
|
)
|
|
.filter(
|
|
PlVehiclePersonnel.vehicle_id == vehicle_id,
|
|
PlVehiclePersonnel.tenant_no == tenant_no,
|
|
PlVehiclePersonnel.del_flag == 0,
|
|
PlPersonnel.del_flag == 0,
|
|
)
|
|
.order_by(PlVehiclePersonnel.order_num.asc(), PlVehiclePersonnel.create_time.asc())
|
|
.all()
|
|
)
|
|
return [
|
|
VehiclePersonnelRow(
|
|
personnel_id=p.personnel_id,
|
|
personnel_name=person.personnel_name,
|
|
phone_no=person.phone_no,
|
|
age=person.age,
|
|
role=p.role,
|
|
is_default=p.is_default,
|
|
remark=p.remark,
|
|
)
|
|
for p, person in rows
|
|
]
|
|
|
|
|
|
def load_vehicle_personnel_summaries(
|
|
db: Session, tenant_no: str, vehicle_ids: list[str]
|
|
) -> dict[str, tuple[str, Optional[str]]]:
|
|
"""批量加载 personnelSummary / defaultDriverName。"""
|
|
if not vehicle_ids:
|
|
return {}
|
|
|
|
rows = (
|
|
db.query(
|
|
PlVehiclePersonnel.vehicle_id,
|
|
PlPersonnel.personnel_name,
|
|
PlVehiclePersonnel.role,
|
|
PlVehiclePersonnel.is_default,
|
|
)
|
|
.join(PlPersonnel, PlPersonnel.id == PlVehiclePersonnel.personnel_id)
|
|
.filter(
|
|
PlVehiclePersonnel.vehicle_id.in_(vehicle_ids),
|
|
PlVehiclePersonnel.tenant_no == tenant_no,
|
|
PlVehiclePersonnel.del_flag == 0,
|
|
PlPersonnel.del_flag == 0,
|
|
)
|
|
.order_by(PlVehiclePersonnel.order_num.asc())
|
|
.all()
|
|
)
|
|
|
|
grouped: dict[str, list[tuple[str, str, str]]] = {}
|
|
for vehicle_id, name, role, is_default in rows:
|
|
grouped.setdefault(vehicle_id, []).append((name, role, is_default))
|
|
|
|
return {
|
|
vid: build_personnel_summary(items) for vid, items in grouped.items()
|
|
}
|
|
|
|
|
|
def refresh_personnel_ages_from_id_card(db: Session) -> int:
|
|
"""按 18 位身份证号重算 pl_personnel.age,仅更新算出值与库中不一致的记录。返回更新条数。"""
|
|
rows = (
|
|
db.query(PlPersonnel)
|
|
.filter(
|
|
PlPersonnel.del_flag == 0,
|
|
PlPersonnel.id_card_no.isnot(None),
|
|
PlPersonnel.id_card_no != "",
|
|
)
|
|
.all()
|
|
)
|
|
now = datetime.now()
|
|
updated = 0
|
|
for row in rows:
|
|
age = calc_age_from_chinese_id_card(row.id_card_no)
|
|
if age is None or row.age == age:
|
|
continue
|
|
row.age = age
|
|
row.update_time = now
|
|
updated += 1
|
|
return updated
|