Files
shanxia2.0/backend/app/api/pl/base_data.py
T
2026-06-21 09:19:07 +08:00

769 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""运输计划管理 - 基础数据 API(承运商、车辆、人员)"""
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import case, func, or_
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.db_auth import DbUserContext, get_db_current_user
from app.core.tenant_context import get_tenant_no
from app.core.utils import generate_uuid
from app.models.pl_models import (
PlCarrier,
PlPersonnel,
PlVehicle,
PlVehicleDriver,
PlVehiclePersonnel,
)
from app.schemas.pl import (
CarrierCreate,
CarrierRead,
CarrierUpdate,
DriverCreate,
DriverRead,
DriverUpdate,
PersonnelCreate,
PersonnelDetailRead,
PersonnelRead,
PersonnelUpdate,
VehicleCreate,
VehicleDetailRead,
VehicleDriverCreate,
VehicleRead,
VehicleUpdate,
)
from app.services.pl_cert_service import (
certs_to_read,
list_owner_certs,
nearest_expire_date,
save_owner_certs,
)
from app.services.pl_personnel_service import (
load_vehicle_personnel_rows,
load_vehicle_personnel_summaries,
save_vehicle_personnel,
)
router = APIRouter(prefix="", tags=["运输计划-基础数据"])
# =============================================================================
# 承运商 API
# =============================================================================
@router.get("/carrier", response_model=list[CarrierRead])
def list_carriers(
q: Optional[str] = Query(default=None, description="模糊搜索"),
status: Optional[str] = Query(default=None, description="状态"),
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> list[CarrierRead]:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
query = db.query(PlCarrier).filter(
PlCarrier.tenant_no == tenant_no,
PlCarrier.del_flag == 0,
)
if status:
query = query.filter(PlCarrier.status == status)
if q:
query = query.filter(
or_(
func.lower(PlCarrier.carrier_name).contains(q.lower()),
func.lower(PlCarrier.contact_name).contains(q.lower()),
)
)
query = query.order_by(
case((PlCarrier.order_num.is_(None), 1), else_=0).asc(),
PlCarrier.create_time.desc(),
)
return query.all()
@router.post("/carrier", response_model=CarrierRead, status_code=status.HTTP_201_CREATED)
def create_carrier(
payload: CarrierCreate,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> CarrierRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
carrier = PlCarrier(
id=generate_uuid(),
tenant_no=tenant_no,
carrier_name=payload.carrier_name,
contact_name=payload.contact_name,
contact_phone=payload.contact_phone,
status=payload.status,
remark=payload.remark,
create_by=user.id,
create_dept_id=user.dept_id,
)
db.add(carrier)
db.commit()
db.refresh(carrier)
return carrier
@router.get("/carrier/{id}", response_model=CarrierRead)
def get_carrier(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> CarrierRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
carrier = _get_carrier(db, tenant_no, id)
return carrier
@router.put("/carrier/{id}", response_model=CarrierRead)
def update_carrier(
id: str,
payload: CarrierUpdate,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> CarrierRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
carrier = _get_carrier(db, tenant_no, id)
for key, value in payload.model_dump(exclude_unset=True).items():
setattr(carrier, key, value)
carrier.update_by = user.id
db.commit()
db.refresh(carrier)
return carrier
@router.delete("/carrier/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_carrier(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> None:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
carrier = _get_carrier(db, tenant_no, id)
carrier.del_flag = 1
carrier.update_by = user.id
db.commit()
def _get_carrier(db: Session, tenant_no: str, carrier_id: str) -> PlCarrier:
carrier = (
db.query(PlCarrier)
.filter(
PlCarrier.id == carrier_id,
PlCarrier.tenant_no == tenant_no,
PlCarrier.del_flag == 0,
)
.first()
)
if not carrier:
raise HTTPException(status_code=404, detail="承运商不存在")
return carrier
# =============================================================================
# 车辆 API
# =============================================================================
def _vehicle_to_read(
vehicle: PlVehicle,
summary: Optional[str] = None,
default_driver_name: Optional[str] = None,
) -> VehicleRead:
data = VehicleRead.model_validate(vehicle)
if summary is not None:
data.personnel_summary = summary
if default_driver_name is not None:
data.default_driver_name = default_driver_name
return data
def _vehicle_to_detail(
db: Session, tenant_no: str, vehicle: PlVehicle
) -> VehicleDetailRead:
summaries = load_vehicle_personnel_summaries(db, tenant_no, [vehicle.id])
summary, default_name = summaries.get(vehicle.id, ("", None))
personnel_list = load_vehicle_personnel_rows(db, tenant_no, vehicle.id)
cert_list = certs_to_read(
list_owner_certs(db, tenant_no, "vehicle", vehicle.id)
)
base = _vehicle_to_read(vehicle, summary, default_name)
return VehicleDetailRead(
**base.model_dump(by_alias=False),
personnel_list=personnel_list,
cert_list=cert_list,
)
@router.get("/vehicle", response_model=list[VehicleRead])
def list_vehicles(
q: Optional[str] = Query(default=None, description="模糊搜索车牌号"),
vehicle_type: Optional[str] = Query(default=None, alias="vehicleType", description="车辆类型"),
carrier_id: Optional[str] = Query(default=None, alias="carrierId", description="承运商ID"),
vehicle_status: Optional[str] = Query(default=None, alias="vehicleStatus", description="车辆状态"),
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> list[VehicleRead]:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
query = db.query(PlVehicle).filter(
PlVehicle.tenant_no == tenant_no,
PlVehicle.del_flag == 0,
)
if vehicle_type:
query = query.filter(PlVehicle.vehicle_type == vehicle_type)
if carrier_id:
query = query.filter(PlVehicle.carrier_id == carrier_id)
if vehicle_status:
query = query.filter(PlVehicle.vehicle_status == vehicle_status)
if q:
query = query.filter(PlVehicle.license_plate.contains(q))
query = query.order_by(
case((PlVehicle.order_num.is_(None), 1), else_=0).asc(),
PlVehicle.create_time.desc(),
)
vehicles = query.all()
summaries = load_vehicle_personnel_summaries(
db, tenant_no, [v.id for v in vehicles]
)
return [
_vehicle_to_read(v, *summaries.get(v.id, ("", None)))
for v in vehicles
]
@router.post("/vehicle", response_model=VehicleDetailRead, status_code=status.HTTP_201_CREATED)
def create_vehicle(
payload: VehicleCreate,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> VehicleDetailRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
existing = (
db.query(PlVehicle)
.filter(
PlVehicle.license_plate == payload.license_plate,
PlVehicle.del_flag == 0,
)
.first()
)
if existing:
raise HTTPException(status_code=400, detail="车牌号已存在")
vehicle = PlVehicle(
id=generate_uuid(),
tenant_no=tenant_no,
vehicle_type=payload.vehicle_type,
carrier_id=payload.carrier_id,
carrier_name=payload.carrier_name,
license_plate=payload.license_plate,
load_capacity=payload.load_capacity,
vehicle_status=payload.vehicle_status,
remark=payload.remark,
create_by=user.id,
create_dept_id=user.dept_id,
)
db.add(vehicle)
db.flush()
try:
save_vehicle_personnel(
db,
vehicle.id,
payload.personnel_list,
tenant_no,
user_id=user.id,
dept_id=user.dept_id,
)
save_owner_certs(
db,
"vehicle",
vehicle.id,
payload.cert_list,
tenant_no,
user_id=user.id,
dept_id=user.dept_id,
)
db.commit()
except HTTPException:
db.rollback()
raise
except Exception:
db.rollback()
raise
db.refresh(vehicle)
return _vehicle_to_detail(db, tenant_no, vehicle)
@router.get("/vehicle/{id}", response_model=VehicleDetailRead)
def get_vehicle(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> VehicleDetailRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
vehicle = _get_vehicle(db, tenant_no, id)
return _vehicle_to_detail(db, tenant_no, vehicle)
@router.put("/vehicle/{id}", response_model=VehicleDetailRead)
def update_vehicle(
id: str,
payload: VehicleUpdate,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> VehicleDetailRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
vehicle = _get_vehicle(db, tenant_no, id)
personnel_list = payload.personnel_list
cert_list = payload.cert_list
data = payload.model_dump(
exclude_unset=True, exclude={"personnel_list", "cert_list"}
)
for key, value in data.items():
setattr(vehicle, key, value)
vehicle.update_by = user.id
try:
if personnel_list is not None:
save_vehicle_personnel(
db,
vehicle.id,
personnel_list,
tenant_no,
user_id=user.id,
dept_id=user.dept_id,
)
if cert_list is not None:
save_owner_certs(
db,
"vehicle",
vehicle.id,
cert_list,
tenant_no,
user_id=user.id,
dept_id=user.dept_id,
)
db.commit()
except HTTPException:
db.rollback()
raise
except Exception:
db.rollback()
raise
db.refresh(vehicle)
return _vehicle_to_detail(db, tenant_no, vehicle)
@router.delete("/vehicle/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_vehicle(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> None:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
vehicle = _get_vehicle(db, tenant_no, id)
vehicle.del_flag = 1
vehicle.update_by = user.id
db.commit()
def _get_vehicle(db: Session, tenant_no: str, vehicle_id: str) -> PlVehicle:
vehicle = (
db.query(PlVehicle)
.filter(
PlVehicle.id == vehicle_id,
PlVehicle.tenant_no == tenant_no,
PlVehicle.del_flag == 0,
)
.first()
)
if not vehicle:
raise HTTPException(status_code=404, detail="车辆不存在")
return vehicle
# =============================================================================
# 人员 API
# =============================================================================
def _personnel_to_read(
db: Session, tenant_no: str, personnel: PlPersonnel
) -> PersonnelRead:
nearest = nearest_expire_date(db, tenant_no, "personnel", personnel.id)
data = PersonnelRead.model_validate(personnel)
data.nearest_expire_date = nearest
return data
def _personnel_to_detail(
db: Session, tenant_no: str, personnel: PlPersonnel
) -> PersonnelDetailRead:
base = _personnel_to_read(db, tenant_no, personnel)
cert_list = certs_to_read(
list_owner_certs(db, tenant_no, "personnel", personnel.id)
)
return PersonnelDetailRead(
**base.model_dump(by_alias=False),
cert_list=cert_list,
)
@router.get("/personnel", response_model=list[PersonnelRead])
def list_personnel(
q: Optional[str] = Query(default=None, description="模糊搜索姓名/手机号"),
status: Optional[str] = Query(default=None, description="状态"),
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> list[PersonnelRead]:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
query = db.query(PlPersonnel).filter(
PlPersonnel.tenant_no == tenant_no,
PlPersonnel.del_flag == 0,
)
if status:
query = query.filter(PlPersonnel.status == status)
if q:
query = query.filter(
or_(
func.lower(PlPersonnel.personnel_name).contains(q.lower()),
PlPersonnel.phone_no.contains(q),
)
)
query = query.order_by(
case((PlPersonnel.order_num.is_(None), 1), else_=0).asc(),
PlPersonnel.create_time.desc(),
)
rows = query.all()
return [_personnel_to_read(db, tenant_no, p) for p in rows]
@router.post("/personnel", response_model=PersonnelDetailRead, status_code=status.HTTP_201_CREATED)
def create_personnel(
payload: PersonnelCreate,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> PersonnelDetailRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
personnel = PlPersonnel(
id=generate_uuid(),
tenant_no=tenant_no,
personnel_name=payload.personnel_name,
phone_no=payload.phone_no,
age=payload.age,
id_card_no=payload.id_card_no,
status=payload.status,
remark=payload.remark,
create_by=user.id,
create_dept_id=user.dept_id,
)
db.add(personnel)
db.flush()
try:
save_owner_certs(
db,
"personnel",
personnel.id,
payload.cert_list,
tenant_no,
user_id=user.id,
dept_id=user.dept_id,
)
db.commit()
except Exception:
db.rollback()
raise
db.refresh(personnel)
return _personnel_to_detail(db, tenant_no, personnel)
@router.get("/personnel/{id}", response_model=PersonnelDetailRead)
def get_personnel(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> PersonnelDetailRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
personnel = _get_personnel(db, tenant_no, id)
return _personnel_to_detail(db, tenant_no, personnel)
@router.put("/personnel/{id}", response_model=PersonnelDetailRead)
def update_personnel(
id: str,
payload: PersonnelUpdate,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> PersonnelDetailRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
personnel = _get_personnel(db, tenant_no, id)
cert_list = payload.cert_list
data = payload.model_dump(exclude_unset=True, exclude={"cert_list"})
for key, value in data.items():
setattr(personnel, key, value)
personnel.update_by = user.id
try:
if cert_list is not None:
save_owner_certs(
db,
"personnel",
personnel.id,
cert_list,
tenant_no,
user_id=user.id,
dept_id=user.dept_id,
)
db.commit()
except Exception:
db.rollback()
raise
db.refresh(personnel)
return _personnel_to_detail(db, tenant_no, personnel)
@router.delete("/personnel/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_personnel(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> None:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
personnel = _get_personnel(db, tenant_no, id)
personnel.del_flag = 1
personnel.update_by = user.id
db.commit()
def _get_personnel(db: Session, tenant_no: str, personnel_id: str) -> PlPersonnel:
personnel = (
db.query(PlPersonnel)
.filter(
PlPersonnel.id == personnel_id,
PlPersonnel.tenant_no == tenant_no,
PlPersonnel.del_flag == 0,
)
.first()
)
if not personnel:
raise HTTPException(status_code=404, detail="人员不存在")
return personnel
# =============================================================================
# 司机 API(兼容别名,内部使用 pl_personnel)
# =============================================================================
def _personnel_to_driver_read(personnel: PlPersonnel) -> DriverRead:
return DriverRead(
id=personnel.id,
driver_name=personnel.personnel_name,
phone_no=personnel.phone_no,
id_card_no=personnel.id_card_no,
tenant_no=personnel.tenant_no,
order_num=personnel.order_num,
remark=personnel.remark,
create_by=personnel.create_by,
create_dept_id=personnel.create_dept_id,
create_time=personnel.create_time,
update_by=personnel.update_by,
update_time=personnel.update_time,
del_flag=personnel.del_flag,
)
@router.get("/driver", response_model=list[DriverRead])
def list_drivers(
q: Optional[str] = Query(default=None, description="模糊搜索姓名/手机号"),
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> list[DriverRead]:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
query = db.query(PlPersonnel).filter(
PlPersonnel.tenant_no == tenant_no,
PlPersonnel.del_flag == 0,
)
if q:
query = query.filter(
or_(
func.lower(PlPersonnel.personnel_name).contains(q.lower()),
PlPersonnel.phone_no.contains(q),
)
)
query = query.order_by(
case((PlPersonnel.order_num.is_(None), 1), else_=0).asc(),
PlPersonnel.create_time.desc(),
)
return [_personnel_to_driver_read(p) for p in query.all()]
@router.post("/driver", response_model=DriverRead, status_code=status.HTTP_201_CREATED)
def create_driver(
payload: DriverCreate,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> DriverRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
personnel = PlPersonnel(
id=generate_uuid(),
tenant_no=tenant_no,
personnel_name=payload.driver_name,
phone_no=payload.phone_no,
id_card_no=payload.id_card_no,
status="1",
remark=payload.remark,
create_by=user.id,
create_dept_id=user.dept_id,
)
db.add(personnel)
db.commit()
db.refresh(personnel)
return _personnel_to_driver_read(personnel)
@router.get("/driver/{id}", response_model=DriverRead)
def get_driver(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> DriverRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
personnel = _get_personnel(db, tenant_no, id)
return _personnel_to_driver_read(personnel)
@router.put("/driver/{id}", response_model=DriverRead)
def update_driver(
id: str,
payload: DriverUpdate,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> DriverRead:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
personnel = _get_personnel(db, tenant_no, id)
if payload.driver_name is not None:
personnel.personnel_name = payload.driver_name
if payload.phone_no is not None:
personnel.phone_no = payload.phone_no
if payload.id_card_no is not None:
personnel.id_card_no = payload.id_card_no
if payload.remark is not None:
personnel.remark = payload.remark
personnel.update_by = user.id
db.commit()
db.refresh(personnel)
return _personnel_to_driver_read(personnel)
@router.delete("/driver/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_driver(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> None:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
personnel = _get_personnel(db, tenant_no, id)
personnel.del_flag = 1
personnel.update_by = user.id
db.commit()
# =============================================================================
# 车辆-司机绑定 API(兼容,写入 pl_vehicle_personnel)
# =============================================================================
@router.post("/vehicle-driver", status_code=status.HTTP_201_CREATED)
def bind_vehicle_driver(
payload: VehicleDriverCreate,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> dict:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
if payload.is_default == 1:
db.query(PlVehiclePersonnel).filter(
PlVehiclePersonnel.vehicle_id == payload.vehicle_id,
PlVehiclePersonnel.role == "driver",
PlVehiclePersonnel.del_flag == 0,
).update({"is_default": 0})
vd = PlVehiclePersonnel(
id=generate_uuid(),
tenant_no=tenant_no,
vehicle_id=payload.vehicle_id,
personnel_id=payload.driver_id,
role="driver",
is_default=payload.is_default,
create_by=user.id,
create_dept_id=user.dept_id,
)
db.add(vd)
db.commit()
return {"id": vd.id}
@router.delete("/vehicle-driver/{id}", status_code=status.HTTP_204_NO_CONTENT)
def unbind_vehicle_driver(
id: str,
db: Session = Depends(get_db),
user: DbUserContext = Depends(get_db_current_user),
) -> None:
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
vd = (
db.query(PlVehiclePersonnel)
.filter(
PlVehiclePersonnel.id == id,
PlVehiclePersonnel.tenant_no == tenant_no,
PlVehiclePersonnel.del_flag == 0,
)
.first()
)
if not vd:
vd = (
db.query(PlVehicleDriver)
.filter(
PlVehicleDriver.id == id,
PlVehicleDriver.tenant_no == tenant_no,
PlVehicleDriver.del_flag == 0,
)
.first()
)
if vd:
vd.del_flag = 1
db.commit()