Files
shanxia2.0/backend/app/services/pl_personnel_service.py
T
2026-08-19 22:20:42 +08:00

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