dingdanquanliucheng/backend/app/api/logistics.py
taiyi a5edb8be4d feat: 司机端拍照识别物流运单号并支持一对多录入
新增 logistics_waybill 表存储一个任务关联的多个运单号,
司机在揽货时可通过拍照调用快递100 OCR 自动识别运单号,
支持连续拍多张统一提交、手动输入兜底、编辑删除,
揽货时必填至少一个运单号并自动同步到 logistics_task。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 14:03:36 +08:00

388 lines
15 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.

"""
物流任务管理路由模块
提供物流运输任务的全生命周期管理接口,包括:
- 物流任务的创建、查询、取消(管理端)
- 司机端任务的接单、提货、送达操作
- 物流轨迹的查询与新增
URL 前缀:/api/logistics、/api/driver
权限要求manager/admin 管理端操作driver 司机端操作
"""
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from backend.app.api.deps import get_logistics_service, require_permissions, require_roles
from backend.app.db import get_db_session
from backend.app.schemas.common import success_payload
from backend.app.schemas.logistics import CreateLogisticsTaskRequest, CreateLogisticsTraceRequest, DriverTaskOperateRequest, RecognizeWaybillRequest, SubmitWaybillsRequest, UpdateTrackingRequest
from backend.app.services.logistics_service import LogisticsService
router = APIRouter(tags=["logistics"])
@router.get("/api/logistics/tasks")
def list_logistics_tasks(
order_id: int | None = Query(default=None), # 按订单ID筛选
task_no: str | None = Query(default=None), # 按任务编号精确搜索
status: str | None = Query(default=None), # 按任务状态筛选(如 pending/in_transit/completed
driver_id: int | None = Query(default=None), # 按司机ID筛选
factory_id: int | None = Query(default=None), # 按工厂ID筛选
logistics_service: LogisticsService = Depends(get_logistics_service), # 物流服务实例
session: Session = Depends(get_db_session), # 数据库会话
current_user: dict = Depends(require_roles("manager", "admin")), # 当前登录用户仅manager和admin可访问
_permission_user: dict = Depends(require_permissions("logistics:task:list")), # 权限校验:物流任务查看权限
) -> dict:
"""
获取物流任务列表(管理端)
支持按订单ID、任务编号、状态、司机ID、工厂ID进行筛选查询。
仅管理员和经理角色可访问。
请求参数:
order_id: 订单ID可选
task_no: 任务编号(可选)
status: 任务状态(可选)
driver_id: 司机ID可选
factory_id: 工厂ID可选
返回值:
包含物流任务列表的分页数据
"""
return success_payload(
logistics_service.list_tasks(
session=session,
filters={
"order_id": order_id,
"task_no": task_no,
"status": status,
"driver_id": driver_id,
"factory_id": factory_id,
},
current_user=current_user,
)
)
@router.post("/api/logistics/tasks")
def create_logistics_task(
payload: CreateLogisticsTaskRequest, # 创建物流任务请求体包含订单ID、司机ID等信息
logistics_service: LogisticsService = Depends(get_logistics_service), # 物流服务实例
session: Session = Depends(get_db_session), # 数据库会话
current_user: dict = Depends(require_roles("manager", "admin")), # 当前登录用户
_permission_user: dict = Depends(require_permissions("logistics:task:create")), # 权限校验:物流任务创建权限
) -> dict:
"""
创建物流任务
为指定订单创建新的物流运输任务,分配司机并启动物流流程。
请求参数:
payload: CreateLogisticsTaskRequest 请求体,包含订单关联信息
返回值:
新创建的物流任务详情
权限要求manager、admin 角色,需 logistics:task:create 权限
"""
return success_payload(logistics_service.create_task(payload.model_dump(), session, current_user))
@router.get("/api/logistics/tasks/{task_id}")
def get_logistics_task(
task_id: int, # 物流任务ID
logistics_service: LogisticsService = Depends(get_logistics_service), # 物流服务实例
session: Session = Depends(get_db_session), # 数据库会话
current_user: dict = Depends(require_roles("manager", "admin")), # 当前登录用户
_permission_user: dict = Depends(require_permissions("logistics:task:create")), # 权限校验
) -> dict:
"""
获取物流任务详情
根据任务ID查询物流任务的完整信息包括关联的订单和司机信息。
请求参数:
task_id: 路径参数物流任务ID
返回值:
物流任务详情信息
权限要求manager、admin 角色
"""
return success_payload(logistics_service.get_task(task_id, session, current_user))
@router.put("/api/logistics/tasks/{task_id}/tracking")
def update_tracking_number(
task_id: int, # 物流任务ID
payload: UpdateTrackingRequest, # 修改物流单号请求体
logistics_service: LogisticsService = Depends(get_logistics_service),
session: Session = Depends(get_db_session),
current_user: dict = Depends(require_roles("salesman", "manager", "admin")),
) -> dict:
"""
修改物流单号
业务员或管理员修改已有物流任务的快递单号和快递公司。
业务员仅可修改自己订单关联的物流任务。
请求参数:
task_id: 路径参数物流任务ID
payload: UpdateTrackingRequest 请求体
返回值:
更新后的物流任务信息
权限要求salesman、manager、admin 角色
"""
return success_payload(logistics_service.update_tracking(task_id, payload.model_dump(), session, current_user))
@router.get("/api/driver/tasks")
def list_driver_tasks(
logistics_service: LogisticsService = Depends(get_logistics_service), # 物流服务实例
session: Session = Depends(get_db_session), # 数据库会话
current_user: dict = Depends(require_roles("driver")), # 当前登录用户,仅司机角色
_permission_user: dict = Depends(require_permissions("driver:task:list")), # 权限校验:司机任务列表权限
) -> dict:
"""
获取司机任务列表(司机端)
返回当前登录司机的待处理任务列表。
返回值:
当前司机的物流任务列表
权限要求driver 角色,需 driver:task:list 权限
"""
return success_payload(logistics_service.list_driver_tasks(session, current_user))
@router.get("/api/driver/tasks/{task_id}")
def get_driver_task(
task_id: int, # 物流任务ID
logistics_service: LogisticsService = Depends(get_logistics_service), # 物流服务实例
session: Session = Depends(get_db_session), # 数据库会话
current_user: dict = Depends(require_roles("driver")), # 当前登录用户,仅司机角色
_permission_user: dict = Depends(require_permissions("driver:task:list")), # 权限校验
) -> dict:
"""
获取司机任务详情(司机端)
根据任务ID查询当前司机的物流任务详情。
请求参数:
task_id: 路径参数物流任务ID
返回值:
物流任务详情信息
权限要求driver 角色,需 driver:task:list 权限
"""
return success_payload(logistics_service.get_driver_task(task_id, session, current_user))
@router.post("/api/driver/tasks/{task_id}/accept")
def accept_driver_task(
task_id: int, # 物流任务ID
payload: DriverTaskOperateRequest, # 司机操作请求体,包含操作备注等信息
logistics_service: LogisticsService = Depends(get_logistics_service), # 物流服务实例
session: Session = Depends(get_db_session), # 数据库会话
current_user: dict = Depends(require_roles("driver")), # 当前登录用户,仅司机角色
_permission_user: dict = Depends(require_permissions("driver:task:accept")), # 权限校验:接单权限
) -> dict:
"""
司机接单操作
司机接受指定的物流任务,任务状态变更为已接单。
请求参数:
task_id: 路径参数物流任务ID
payload: DriverTaskOperateRequest 请求体,包含操作备注
返回值:
接单后的任务详情
权限要求driver 角色,需 driver:task:accept 权限
"""
return success_payload(logistics_service.accept_task(task_id, payload.model_dump(), session, current_user))
@router.post("/api/driver/tasks/{task_id}/pickup")
def pickup_driver_task(
task_id: int, # 物流任务ID
payload: DriverTaskOperateRequest, # 司机操作请求体,包含提货备注等信息
logistics_service: LogisticsService = Depends(get_logistics_service), # 物流服务实例
session: Session = Depends(get_db_session), # 数据库会话
current_user: dict = Depends(require_roles("driver")), # 当前登录用户,仅司机角色
_permission_user: dict = Depends(require_permissions("driver:task:pickup")), # 权限校验:提货权限
) -> dict:
"""
司机提货操作
司机确认提货,任务状态变更为运输中。
请求参数:
task_id: 路径参数物流任务ID
payload: DriverTaskOperateRequest 请求体,包含提货备注
返回值:
提货后的任务详情
权限要求driver 角色,需 driver:task:pickup 权限
"""
return success_payload(logistics_service.pickup_task(task_id, payload.model_dump(), session, current_user))
@router.post("/api/driver/tasks/{task_id}/deliver")
def deliver_driver_task(
task_id: int, # 物流任务ID
payload: DriverTaskOperateRequest, # 司机操作请求体,包含送达备注等信息
logistics_service: LogisticsService = Depends(get_logistics_service), # 物流服务实例
session: Session = Depends(get_db_session), # 数据库会话
current_user: dict = Depends(require_roles("driver")), # 当前登录用户,仅司机角色
_permission_user: dict = Depends(require_permissions("driver:task:deliver")), # 权限校验:送达权限
) -> dict:
"""
司机送达操作
司机确认货物已送达目的地,任务状态变更为已完成。
请求参数:
task_id: 路径参数物流任务ID
payload: DriverTaskOperateRequest 请求体,包含送达备注
返回值:
送达后的任务详情
权限要求driver 角色,需 driver:task:deliver 权限
"""
return success_payload(logistics_service.deliver_task(task_id, payload.model_dump(), session, current_user))
@router.post("/api/driver/recognize-waybill")
def recognize_waybill(
payload: RecognizeWaybillRequest,
logistics_service: LogisticsService = Depends(get_logistics_service),
session: Session = Depends(get_db_session),
current_user: dict = Depends(require_roles("driver")),
_permission_user: dict = Depends(require_permissions("driver:task:accept")),
) -> dict:
"""快递100面单OCR识别运单号。"""
return success_payload(logistics_service.recognize_waybill(payload.model_dump(), session, current_user))
@router.post("/api/driver/tasks/{task_id}/waybills")
def submit_waybills(
task_id: int,
payload: SubmitWaybillsRequest,
logistics_service: LogisticsService = Depends(get_logistics_service),
session: Session = Depends(get_db_session),
current_user: dict = Depends(require_roles("driver")),
_permission_user: dict = Depends(require_permissions("driver:task:accept")),
) -> dict:
"""批量提交运单号。"""
return success_payload(logistics_service.submit_waybills(task_id, payload.model_dump(), session, current_user))
@router.get("/api/driver/tasks/{task_id}/waybills")
def list_waybills(
task_id: int,
logistics_service: LogisticsService = Depends(get_logistics_service),
session: Session = Depends(get_db_session),
current_user: dict = Depends(require_roles("driver")),
_permission_user: dict = Depends(require_permissions("driver:task:list")),
) -> dict:
"""查询任务关联的运单号列表。"""
return success_payload(logistics_service.list_waybills(task_id, session, current_user))
@router.delete("/api/driver/tasks/{task_id}/waybills/{waybill_id}")
def delete_waybill(
task_id: int,
waybill_id: int,
logistics_service: LogisticsService = Depends(get_logistics_service),
session: Session = Depends(get_db_session),
current_user: dict = Depends(require_roles("driver")),
_permission_user: dict = Depends(require_permissions("driver:task:accept")),
) -> dict:
"""删除单条运单号。"""
return success_payload(logistics_service.delete_waybill(task_id, waybill_id, session, current_user))
@router.post("/api/logistics/tasks/{task_id}/cancel")
def cancel_logistics_task(
task_id: int, # 物流任务ID
payload: DriverTaskOperateRequest, # 操作请求体,包含取消原因等信息
logistics_service: LogisticsService = Depends(get_logistics_service), # 物流服务实例
session: Session = Depends(get_db_session), # 数据库会话
current_user: dict = Depends(require_roles("manager", "admin")), # 当前登录用户仅manager和admin
_permission_user: dict = Depends(require_permissions("logistics:task:cancel")), # 权限校验:任务取消权限
) -> dict:
"""
取消物流任务(管理端)
管理员取消指定的物流任务,任务状态变更为已取消。
请求参数:
task_id: 路径参数物流任务ID
payload: DriverTaskOperateRequest 请求体,包含取消原因
返回值:
取消后的任务详情
权限要求manager、admin 角色,需 logistics:task:cancel 权限
"""
return success_payload(logistics_service.cancel_task(task_id, payload.model_dump(), session, current_user))
@router.get("/api/logistics/{order_id}/trace")
def get_logistics_trace(
order_id: int, # 订单ID
logistics_service: LogisticsService = Depends(get_logistics_service), # 物流服务实例
session: Session = Depends(get_db_session), # 数据库会话
current_user: dict = Depends(require_roles("manager", "admin", "driver")), # 当前登录用户
_permission_user: dict = Depends(require_permissions("logistics:trace:list")), # 权限校验:轨迹查看权限
) -> dict:
"""
查询物流轨迹
根据订单ID查询该订单的完整物流运输轨迹记录。
请求参数:
order_id: 路径参数订单ID
返回值:
物流轨迹列表,按时间倒序排列
权限要求manager、admin、driver 角色,需 logistics:trace:list 权限
"""
return success_payload(logistics_service.get_trace(order_id, session, current_user))
@router.post("/api/logistics/{order_id}/trace")
def create_logistics_trace(
order_id: int, # 订单ID
payload: CreateLogisticsTraceRequest, # 创建轨迹请求体,包含轨迹状态、备注等信息
logistics_service: LogisticsService = Depends(get_logistics_service), # 物流服务实例
session: Session = Depends(get_db_session), # 数据库会话
current_user: dict = Depends(require_roles("manager", "admin", "driver")), # 当前登录用户
_permission_user: dict = Depends(require_permissions("logistics:trace:create")), # 权限校验:轨迹创建权限
) -> dict:
"""
新增物流轨迹记录
为指定订单添加一条新的物流轨迹记录,如更新运输状态、添加备注等。
请求参数:
order_id: 路径参数订单ID
payload: CreateLogisticsTraceRequest 请求体,包含轨迹详情
返回值:
新创建的轨迹记录信息
权限要求manager、admin、driver 角色,需 logistics:trace:create 权限
"""
return success_payload(logistics_service.create_trace(order_id, payload.model_dump(), session, current_user))