Files
shanxia2.0/backend/app/services/pl_paste_recognize_service.py
T
2026-08-21 09:35:11 +08:00

326 lines
9.7 KiB
Python

"""运输计划 - 三方粘贴识别批量提交"""
from __future__ import annotations
from typing import Optional
from fastapi import HTTPException
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.models.pl_models import PlCarrier, PlPersonnel, PlVehicle
from app.schemas.pl.base_data import VehiclePersonnelRow
from app.schemas.pl.paste_recognize import (
ConflictAction,
PasteCommitBody,
PasteCommitResult,
PastePersonnelIn,
PasteVehicleIn,
)
from app.services.pl_personnel_service import save_vehicle_personnel
def _conflict_map(conflicts: list[ConflictAction]) -> dict[str, ConflictAction]:
result: dict[str, ConflictAction] = {}
for item in conflicts:
key = (item.key or "").strip()
if not key:
continue
if key in result:
raise HTTPException(status_code=400, detail=f"冲突项「{key}」重复")
result[key] = item
return result
def _find_personnel_by_id_card(
db: Session, tenant_no: str, id_card_no: Optional[str]
) -> Optional[PlPersonnel]:
card = (id_card_no or "").strip().upper()
if not card:
return None
return (
db.query(PlPersonnel)
.filter(
PlPersonnel.tenant_no == tenant_no,
PlPersonnel.id_card_no == card,
PlPersonnel.del_flag == 0,
)
.first()
)
def _find_vehicle_by_plate(
db: Session, tenant_no: str, license_plate: str
) -> Optional[PlVehicle]:
plate = (license_plate or "").strip()
if not plate:
return None
return (
db.query(PlVehicle)
.filter(
PlVehicle.tenant_no == tenant_no,
PlVehicle.license_plate == plate,
PlVehicle.del_flag == 0,
)
.first()
)
def _get_personnel(
db: Session, tenant_no: str, personnel_id: str
) -> PlPersonnel:
row = (
db.query(PlPersonnel)
.filter(
PlPersonnel.id == personnel_id,
PlPersonnel.tenant_no == tenant_no,
PlPersonnel.del_flag == 0,
)
.first()
)
if not row:
raise HTTPException(status_code=400, detail=f"人员不存在:{personnel_id}")
return row
def _get_vehicle(db: Session, tenant_no: str, vehicle_id: str) -> PlVehicle:
row = (
db.query(PlVehicle)
.filter(
PlVehicle.id == vehicle_id,
PlVehicle.tenant_no == tenant_no,
PlVehicle.del_flag == 0,
)
.first()
)
if not row:
raise HTTPException(status_code=400, detail=f"车辆不存在:{vehicle_id}")
return row
def _resolve_personnel(
db: Session,
tenant_no: str,
*,
key: str,
draft: PastePersonnelIn,
conflict: Optional[ConflictAction],
user_id: str,
dept_id: str,
) -> str:
name = (draft.personnel_name or "").strip()
if not name:
raise HTTPException(status_code=400, detail=f"{key}姓名不能为空")
existing = _find_personnel_by_id_card(db, tenant_no, draft.id_card_no)
if existing and conflict is None:
raise HTTPException(
status_code=400,
detail=f"人员「{name}」身份证已存在,请先选择复用/覆盖/取消",
)
if conflict is not None:
if conflict.action == "cancel":
raise HTTPException(status_code=400, detail="已取消")
target = _get_personnel(db, tenant_no, conflict.existing_id)
if conflict.action == "reuse":
return target.id
# overwrite
target.personnel_name = name
target.phone_no = (draft.phone_no or "").strip() or None
card = (draft.id_card_no or "").strip().upper() or None
if card:
target.id_card_no = card
target.personnel_type = draft.personnel_type
target.personnel_affiliation = "carrier"
target.update_by = user_id
return target.id
personnel = PlPersonnel(
id=generate_uuid(),
tenant_no=tenant_no,
personnel_name=name,
phone_no=(draft.phone_no or "").strip() or None,
id_card_no=(draft.id_card_no or "").strip().upper() or None,
personnel_type=draft.personnel_type,
personnel_affiliation="carrier",
status="1",
create_by=user_id,
create_dept_id=dept_id,
)
db.add(personnel)
db.flush()
return personnel.id
def _resolve_vehicle(
db: Session,
tenant_no: str,
*,
draft: PasteVehicleIn,
conflict: Optional[ConflictAction],
user_id: str,
dept_id: str,
) -> str:
plate = (draft.license_plate or "").strip()
if not plate:
raise HTTPException(status_code=400, detail="车牌号不能为空")
carrier_id = (draft.carrier_id or "").strip() or None
carrier_name = (draft.carrier_name or "").strip() or None
if carrier_id:
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=400, detail="承运商不存在")
carrier_name = carrier_name or carrier.carrier_name
else:
carrier_name = None
existing = _find_vehicle_by_plate(db, tenant_no, plate)
if existing and conflict is None:
raise HTTPException(
status_code=400,
detail=f"车牌「{plate}」已存在,请先选择复用/覆盖/取消",
)
if conflict is not None:
if conflict.action == "cancel":
raise HTTPException(status_code=400, detail="已取消")
target = _get_vehicle(db, tenant_no, conflict.existing_id)
if conflict.action == "reuse":
return target.id
trailer_no = (draft.trailer_no or "").strip()
if not trailer_no:
raise HTTPException(status_code=400, detail="挂车号不能为空")
target.trailer_no = trailer_no
target.load_capacity = draft.load_capacity
target.carrier_id = carrier_id
target.carrier_name = carrier_name
target.vehicle_type = "carrier"
target.update_by = user_id
return target.id
trailer_no = (draft.trailer_no or "").strip()
if not trailer_no:
raise HTTPException(status_code=400, detail="挂车号不能为空")
vehicle = PlVehicle(
id=generate_uuid(),
tenant_no=tenant_no,
vehicle_type="carrier",
carrier_id=carrier_id,
carrier_name=carrier_name,
license_plate=plate,
trailer_no=trailer_no,
load_capacity=draft.load_capacity,
vehicle_status="available",
create_by=user_id,
create_dept_id=dept_id,
)
db.add(vehicle)
db.flush()
return vehicle.id
def commit_paste_recognize(
db: Session,
tenant_no: str,
body: PasteCommitBody,
*,
user_id: str,
dept_id: str,
) -> PasteCommitResult:
cmap = _conflict_map(body.conflicts or [])
for item in cmap.values():
if item.action == "cancel":
raise HTTPException(status_code=400, detail="已取消")
need_vehicle = body.mode == "vehicle_bundle" or (
body.mode == "personnel_first" and body.create_vehicle
)
if need_vehicle and body.vehicle is None:
raise HTTPException(status_code=400, detail="缺少车辆信息")
driver_id: Optional[str] = None
escort_id: Optional[str] = None
vehicle_id: Optional[str] = None
if body.driver is not None:
driver_id = _resolve_personnel(
db,
tenant_no,
key="驾驶员",
draft=body.driver,
conflict=cmap.get("driver"),
user_id=user_id,
dept_id=dept_id,
)
if body.escort is not None:
escort_id = _resolve_personnel(
db,
tenant_no,
key="押运员",
draft=body.escort,
conflict=cmap.get("escort"),
user_id=user_id,
dept_id=dept_id,
)
if need_vehicle and body.vehicle is not None:
vehicle_id = _resolve_vehicle(
db,
tenant_no,
draft=body.vehicle,
conflict=cmap.get("vehicle"),
user_id=user_id,
dept_id=dept_id,
)
rows: list[VehiclePersonnelRow] = []
if driver_id and body.driver is not None:
rows.append(
VehiclePersonnelRow(
personnelId=driver_id,
personnelName=body.driver.personnel_name.strip(),
phoneNo=(body.driver.phone_no or "").strip() or None,
role="driver",
isDefault=1,
)
)
if escort_id and body.escort is not None:
rows.append(
VehiclePersonnelRow(
personnelId=escort_id,
personnelName=body.escort.personnel_name.strip(),
phoneNo=(body.escort.phone_no or "").strip() or None,
role="escort",
isDefault=0,
)
)
if rows:
save_vehicle_personnel(
db,
vehicle_id,
rows,
tenant_no,
user_id=user_id,
dept_id=dept_id,
vehicle_type="carrier",
)
if body.mode == "personnel_first" and not need_vehicle:
if not driver_id and not escort_id:
raise HTTPException(status_code=400, detail="请至少识别一名人员")
if body.mode == "vehicle_bundle" and not vehicle_id:
raise HTTPException(status_code=400, detail="车辆创建失败")
return PasteCommitResult(
vehicleId=vehicle_id,
driverId=driver_id,
escortId=escort_id,
)