VWED_server/routes/operate_point_api.py

725 lines
26 KiB
Python
Raw Normal View History

2025-07-14 10:29:37 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
动作点管理API路由
实现动作点和库位的管理功能
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import Any, Dict, Optional
from data.session import get_db
from services.operate_point_service import OperatePointService
from routes.model.base import ApiResponse
from routes.model.operate_point_model import (
# OperatePointListRequest, OperatePointListResponse,
StorageLocationListRequest, StorageLocationListResponse,
StorageLocationStatusUpdateRequest, StorageLocationStatusUpdateResponse,
BatchStorageLocationStatusUpdateRequest, BatchStorageLocationStatusUpdateResponse,
StorageLocationActionEnum,
# StorageAreaTypeEnum
ExtendedPropertyCreateRequest, ExtendedPropertyCreateResponse,
ExtendedPropertyListRequest, ExtendedPropertyListResponse,
ExtendedPropertyDeleteResponse,
StorageLocationDetailResponse, StorageLocationEditRequest, StorageLocationEditResponse,
StorageLocationLogListRequest, StorageLocationLogListResponse,
)
from routes.common_api import format_response, error_response
from utils.logger import get_logger
from data.models import OperatePointLayer
from data.models import OperatePoint # Added missing import
# 创建路由
router = APIRouter(prefix="/api/vwed-operate-point", tags=["动作点管理"])
# 设置日志
logger = get_logger("app.operate_point_api")
def get_action_descriptions():
"""获取操作类型的说明文档"""
descriptions = {
StorageLocationActionEnum.OCCUPY: "占用库位",
StorageLocationActionEnum.RELEASE: "释放库位",
StorageLocationActionEnum.LOCK: "锁定库位(需要提供锁定者)",
StorageLocationActionEnum.UNLOCK: "解锁库位",
StorageLocationActionEnum.ENABLE: "启用库位",
StorageLocationActionEnum.DISABLE: "禁用库位",
StorageLocationActionEnum.SET_EMPTY_TRAY: "设置为空托盘",
StorageLocationActionEnum.CLEAR_EMPTY_TRAY: "清除空托盘状态"
}
return descriptions
# 标准API响应格式
def api_response(code: int = 200, message: str = "操作成功", data: Any = None) -> Dict[str, Any]:
"""
标准API响应格式
Args:
code: 状态码
message: 响应消息
data: 响应数据
Returns:
Dict[str, Any]: 格式化的响应数据
"""
return {
"code": code,
"message": message,
"data": data
}
@router.get("/list", response_model=ApiResponse[StorageLocationListResponse])
async def get_storage_location_list(
scene_id: Optional[str] = Query(None, description="场景ID"),
storage_area_id: Optional[str] = Query(None, description="库区ID"),
station_name: Optional[str] = Query(None, description="站点名称(支持模糊搜索)"),
storage_location_name: Optional[str] = Query(None, description="库位名称(支持模糊搜索)"),
layer_name: Optional[str] = Query(None, description="层名称(支持模糊搜索)"),
is_disabled: Optional[bool] = Query(None, description="是否禁用"),
is_occupied: Optional[bool] = Query(None, description="是否占用"),
is_locked: Optional[bool] = Query(None, description="是否锁定"),
is_empty_tray: Optional[bool] = Query(None, description="是否空托盘"),
include_operate_point_info: bool = Query(True, description="是否包含动作点信息"),
include_extended_fields: bool = Query(True, description="是否包含扩展字段"),
page: int = Query(1, ge=1, description="页码"),
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
db: Session = Depends(get_db)
):
"""
获取库位列表
库位基于动作点分层OperatePointLayer每一层对应一个库位
支持多种筛选条件
- 场景ID根据场景筛选库位
- 库区ID根据库区筛选库位
- 站点名称支持模糊搜索
- 库位名称支持模糊搜索
- 层名称支持模糊搜索
- 是否禁用筛选禁用/启用的库位
- 是否占用筛选已占用/空闲的库位
- 是否锁定筛选锁定/解锁的库位
- 是否空托盘筛选空托盘/非空托盘的库位
- 是否包含动作点信息控制返回数据是否包含所属动作点的详细信息
- 是否包含扩展字段控制返回数据是否包含自定义扩展字段
返回数据包含
- 库位基本信息ID层索引层名称等
- 库位状态是否占用锁定禁用空托盘等
- 货物信息货物内容重量体积等
- 库位规格最大承重最大体积层高等
- 动作点信息如果启用include_operate_point_info
- 扩展字段如果启用include_extended_fields
- 统计信息总数各种状态的数量使用率等
Args:
scene_id: 场景ID
storage_area_id: 库区ID
station_name: 站点名称
storage_location_name: 库位名称
layer_name: 层名称
is_disabled: 是否禁用
is_occupied: 是否占用
is_locked: 是否锁定
is_empty_tray: 是否空托盘
include_operate_point_info: 是否包含动作点信息
include_extended_fields: 是否包含扩展字段
page: 页码
page_size: 每页数量
db: 数据库会话
Returns:
ApiResponse[StorageLocationListResponse]: 库位列表响应
"""
try:
# 构建请求对象
request = StorageLocationListRequest(
scene_id=scene_id,
storage_area_id=storage_area_id,
station_name=station_name,
storage_location_name=storage_location_name,
layer_name=layer_name,
is_disabled=is_disabled,
is_occupied=is_occupied,
is_locked=is_locked,
is_empty_tray=is_empty_tray,
include_operate_point_info=include_operate_point_info,
include_extended_fields=include_extended_fields,
page=page,
page_size=page_size
)
# 调用服务层方法获取库位列表
result = OperatePointService.get_storage_location_list(db=db, request=request)
return api_response(message="查询成功", data=result)
except ValueError as e:
# 数据验证错误
return error_response(str(e), 400)
except Exception as e:
logger.error(f"获取库位列表失败: {str(e)}")
return error_response(f"获取库位列表失败: {str(e)}", 500)
@router.put("/status", response_model=ApiResponse[StorageLocationStatusUpdateResponse])
async def update_storage_location_status(
request: StorageLocationStatusUpdateRequest,
db: Session = Depends(get_db)
):
"""
更新库位状态
支持的操作类型
- occupy: 占用库位
- release: 释放库位
- lock: 锁定库位需要提供锁定者
- unlock: 解锁库位
- enable: 启用库位
- disable: 禁用库位
- set_empty_tray: 设置为空托盘
- clear_empty_tray: 清除空托盘状态
Args:
request: 库位状态更新请求
db: 数据库会话
Returns:
ApiResponse[StorageLocationStatusUpdateResponse]: 状态更新响应
"""
try:
# 验证操作类型 - 使用枚举类型
valid_actions = [action.value for action in StorageLocationActionEnum]
if request.action not in valid_actions:
return error_response(f"不支持的操作类型: {request.action},支持的操作:{', '.join(valid_actions)}", 400)
# 锁定操作必须提供锁定者
if request.action == StorageLocationActionEnum.LOCK and not request.locked_by:
return error_response("锁定操作必须提供锁定者", 400)
# 调用服务层方法
result = OperatePointService.update_storage_location_status(db=db, request=request)
return api_response(message="状态更新完成", data=result)
except ValueError as e:
return error_response(str(e), 400)
except Exception as e:
logger.error(f"更新库位状态失败: {str(e)}")
return error_response(f"更新库位状态失败: {str(e)}", 500)
@router.put("/batch-status", response_model=ApiResponse[BatchStorageLocationStatusUpdateResponse])
async def batch_update_storage_location_status(
request: BatchStorageLocationStatusUpdateRequest,
db: Session = Depends(get_db)
):
"""
批量更新库位状态
支持的操作类型
- occupy: 占用库位
- release: 释放库位
- lock: 锁定库位需要提供锁定者
- unlock: 解锁库位
- enable: 启用库位
- disable: 禁用库位
- set_empty_tray: 设置为空托盘
- clear_empty_tray: 清除空托盘状态
Args:
request: 批量库位状态更新请求
db: 数据库会话
Returns:
ApiResponse[BatchStorageLocationStatusUpdateResponse]: 批量状态更新响应
"""
try:
# 验证操作类型 - 使用枚举类型
valid_actions = [action.value for action in StorageLocationActionEnum]
if request.action not in valid_actions:
return error_response(f"不支持的操作类型: {request.action},支持的操作:{', '.join(valid_actions)}", 400)
# 锁定操作必须提供锁定者
if request.action == StorageLocationActionEnum.LOCK and not request.locked_by:
return error_response("锁定操作必须提供锁定者", 400)
# 验证库位ID列表
if not request.storage_location_ids:
return error_response("库位ID列表不能为空", 400)
if len(request.storage_location_ids) > 100:
return error_response("批量操作的库位数量不能超过100个", 400)
# 调用服务层方法
result = OperatePointService.batch_update_storage_location_status(db=db, request=request)
# 构建更详细的响应消息
success_details = []
if result.success_count > 0:
success_details.append(f"成功操作 {result.success_count} 个库位")
if result.failed_count > 0:
success_details.append(f"失败操作 {result.failed_count} 个库位")
# 统计无需更改的操作
no_change_count = sum(1 for r in result.results
if r.success and "无需重复操作" in r.message)
actual_update_count = result.success_count - no_change_count
if no_change_count > 0:
success_details.append(f"其中 {no_change_count} 个库位已是目标状态")
if actual_update_count > 0:
success_details.append(f"实际更新 {actual_update_count} 个库位")
detailed_message = f"批量状态更新完成:{', '.join(success_details)}"
return api_response(message=detailed_message, data=result)
except ValueError as e:
return error_response(str(e), 400)
except Exception as e:
logger.error(f"批量更新库位状态失败: {str(e)}")
return error_response(f"批量更新库位状态失败: {str(e)}", 500)
@router.get("/actions")
async def get_supported_actions():
"""
获取支持的操作类型列表
Returns:
ApiResponse: 支持的操作类型列表及其说明
"""
try:
descriptions = get_action_descriptions()
actions = []
for action_enum in StorageLocationActionEnum:
actions.append({
"value": action_enum.value,
"description": descriptions.get(action_enum, "")
})
return api_response(
message="获取支持的操作类型成功",
data={
"actions": actions,
"count": len(actions)
}
)
except Exception as e:
logger.error(f"获取支持的操作类型失败: {str(e)}")
return error_response(f"获取支持的操作类型失败: {str(e)}", 500)
@router.get("/{storage_location_id}/status")
async def get_storage_location_status(
storage_location_id: str,
db: Session = Depends(get_db)
):
"""
获取单个库位状态信息
Args:
storage_location_id: 库位ID
db: 数据库会话
Returns:
ApiResponse: 库位状态信息
"""
try:
# 查询库位
storage_location = db.query(OperatePointLayer).filter(
OperatePointLayer.id == storage_location_id,
OperatePointLayer.is_deleted == False
).first()
if not storage_location:
return error_response("库位不存在", 404)
# 获取状态信息
status = OperatePointService._get_storage_location_status(storage_location)
return api_response(message="查询成功", data=status)
except Exception as e:
logger.error(f"获取库位状态失败: {str(e)}")
return error_response(f"获取库位状态失败: {str(e)}", 500)
# 扩展属性管理接口
@router.post("/extended-properties", response_model=ApiResponse[ExtendedPropertyCreateResponse])
async def create_extended_property(
request: ExtendedPropertyCreateRequest,
db: Session = Depends(get_db)
):
"""
创建扩展属性
用于创建新的扩展属性定义这些属性可以在库位管理中使用
重要提示
- 创建扩展属性后会自动将该属性添加到所有现有的库位层中
- 每个库位层的config_json会自动更新包含新的扩展属性配置
- 新属性会使用指定的默认值进行初始化
支持的属性类型
- string: 字符串
- integer: 整数
- float: 浮点数
- boolean: 布尔值
- date: 日期
- datetime: 日期时间
- text: 长文本
- select: 下拉选择
- multiselect: 多选
Args:
request: 扩展属性创建请求
db: 数据库会话
Returns:
ApiResponse[ExtendedPropertyCreateResponse]: 创建响应
"""
try:
# 调用服务层方法
result = OperatePointService.create_extended_property(db=db, request=request)
return api_response(message="扩展属性创建成功", data=result)
except ValueError as e:
return error_response(str(e), 400)
except Exception as e:
logger.error(f"创建扩展属性失败: {str(e)}")
return error_response(f"创建扩展属性失败: {str(e)}", 500)
@router.get("/extended-properties", response_model=ApiResponse[ExtendedPropertyListResponse])
async def get_extended_property_list(
property_name: Optional[str] = Query(None, description="属性名称(支持模糊搜索)"),
property_type: Optional[str] = Query(None, description="属性类型"),
category: Optional[str] = Query(None, description="属性分类"),
is_enabled: Optional[bool] = Query(None, description="是否启用"),
page: int = Query(1, ge=1, description="页码"),
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
db: Session = Depends(get_db)
):
"""
获取扩展属性列表
支持多种筛选条件
- 属性名称支持模糊搜索
- 属性类型精确匹配
- 属性分类精确匹配
- 是否启用筛选启用/禁用的属性
返回数据包含
- 属性基本信息ID名称类型等
- 属性设置是否必填是否启用等
- 验证规则和选项配置
- 显示设置宽度格式等
- 创建和更新时间
Args:
property_name: 属性名称
property_type: 属性类型
category: 属性分类
is_enabled: 是否启用
page: 页码
page_size: 每页数量
db: 数据库会话
Returns:
ApiResponse[ExtendedPropertyListResponse]: 属性列表响应
"""
try:
# 构建请求对象
request = ExtendedPropertyListRequest(
property_name=property_name,
property_type=property_type,
category=category,
is_enabled=is_enabled,
page=page,
page_size=page_size
)
# 调用服务层方法
result = OperatePointService.get_extended_property_list(db=db, request=request)
return api_response(message="查询成功", data=result)
except ValueError as e:
return error_response(str(e), 400)
except Exception as e:
logger.error(f"获取扩展属性列表失败: {str(e)}")
return error_response(f"获取扩展属性列表失败: {str(e)}", 500)
@router.delete("/extended-properties/{property_id}", response_model=ApiResponse[ExtendedPropertyDeleteResponse])
async def delete_extended_property(
property_id: str,
db: Session = Depends(get_db)
):
"""
删除扩展属性
删除指定的扩展属性软删除
重要提示
- 删除扩展属性后会自动从所有现有的库位层中移除该属性
- 每个库位层的config_json会自动更新清除已删除的扩展属性配置
- 此操作会影响所有库位的扩展属性数据
注意此操作不可逆请谨慎使用
Args:
property_id: 属性ID
db: 数据库会话
Returns:
ApiResponse[ExtendedPropertyDeleteResponse]: 删除响应
"""
try:
# 调用服务层方法
result = OperatePointService.delete_extended_property(db=db, property_id=property_id)
return api_response(message="扩展属性删除成功", data=result)
except ValueError as e:
return error_response(str(e), 400)
except Exception as e:
logger.error(f"删除扩展属性失败: {str(e)}")
return error_response(f"删除扩展属性失败: {str(e)}", 500)
@router.get("/extended-properties/types")
async def get_extended_property_types():
"""
获取支持的扩展属性类型列表
返回系统支持的所有扩展属性类型及其说明
Returns:
ApiResponse: 支持的属性类型列表
"""
try:
from data.models.extended_property import ExtendedPropertyTypeEnum
# 属性类型说明
type_descriptions = {
ExtendedPropertyTypeEnum.STRING: "字符串 - 适用于短文本输入",
ExtendedPropertyTypeEnum.INTEGER: "整数 - 适用于整数值",
ExtendedPropertyTypeEnum.FLOAT: "浮点数 - 适用于小数值",
ExtendedPropertyTypeEnum.BOOLEAN: "布尔值 - 适用于是/否选择",
ExtendedPropertyTypeEnum.DATE: "日期 - 适用于日期选择",
ExtendedPropertyTypeEnum.DATETIME: "日期时间 - 适用于日期和时间选择",
ExtendedPropertyTypeEnum.TEXT: "长文本 - 适用于多行文本输入",
ExtendedPropertyTypeEnum.SELECT: "下拉选择 - 适用于单选择",
ExtendedPropertyTypeEnum.MULTISELECT: "多选 - 适用于多选择"
}
types = []
for type_enum in ExtendedPropertyTypeEnum:
types.append({
"value": type_enum.value,
"description": type_descriptions.get(type_enum, "")
})
return api_response(
message="获取扩展属性类型成功",
data={
"types": types,
"count": len(types)
}
)
except Exception as e:
logger.error(f"获取扩展属性类型失败: {str(e)}")
return error_response(f"获取扩展属性类型失败: {str(e)}", 500)
@router.get("/operation-logs", response_model=ApiResponse[StorageLocationLogListResponse])
async def get_storage_location_operation_logs(
storage_location_id: Optional[str] = Query(None, description="库位ID"),
operator: Optional[str] = Query(None, description="操作人(支持模糊搜索)"),
operation_type: Optional[str] = Query(None, description="操作类型"),
start_time: Optional[str] = Query(None, description="开始时间 (格式: YYYY-MM-DD HH:MM:SS)"),
end_time: Optional[str] = Query(None, description="结束时间 (格式: YYYY-MM-DD HH:MM:SS)"),
page: int = Query(1, ge=1, description="页码"),
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
db: Session = Depends(get_db)
):
"""
获取库位操作记录列表
获取库位相关的操作记录包括
- 状态更新操作占用释放锁定解锁启用禁用等
- 库位信息编辑操作
- 其他库位相关的操作
支持多种筛选条件
- 库位ID查询特定库位的操作记录
- 操作人支持模糊搜索操作人姓名
- 操作类型筛选特定类型的操作
- 时间范围指定操作时间的开始和结束时间
操作记录按时间倒序排列最新的操作在前
Args:
storage_location_id: 库位ID
operator: 操作人
operation_type: 操作类型
start_time: 开始时间
end_time: 结束时间
page: 页码
page_size: 每页数量
db: 数据库会话
Returns:
ApiResponse[StorageLocationLogListResponse]: 操作记录列表响应
"""
try:
# 时间格式转换
start_time_dt = None
end_time_dt = None
if start_time:
try:
from datetime import datetime
start_time_dt = datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S")
except ValueError:
return error_response("开始时间格式错误请使用格式YYYY-MM-DD HH:MM:SS", 400)
if end_time:
try:
from datetime import datetime
end_time_dt = datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S")
except ValueError:
return error_response("结束时间格式错误请使用格式YYYY-MM-DD HH:MM:SS", 400)
# 验证时间范围
if start_time_dt and end_time_dt and start_time_dt > end_time_dt:
return error_response("开始时间不能大于结束时间", 400)
# 构建请求对象
request = StorageLocationLogListRequest(
storage_location_id=storage_location_id,
operator=operator,
operation_type=operation_type,
start_time=start_time_dt,
end_time=end_time_dt,
page=page,
page_size=page_size
)
# 调用服务层方法
result = OperatePointService.get_storage_location_logs(db=db, request=request)
return api_response(message="查询操作记录成功", data=result)
except ValueError as e:
return error_response(str(e), 400)
except Exception as e:
logger.error(f"获取库位操作记录失败: {str(e)}")
return error_response(f"获取库位操作记录失败: {str(e)}", 500)
@router.get("/{storage_location_id}", response_model=ApiResponse[StorageLocationDetailResponse])
async def get_storage_location_detail(
storage_location_id: str,
db: Session = Depends(get_db)
):
"""
获取库位详情
获取指定库位的详细信息包括
- 库位基本信息和当前状态
- 动作点详细信息
- 扩展字段定义和值
- 状态变更历史记录
Args:
storage_location_id: 库位ID
db: 数据库会话
Returns:
ApiResponse[StorageLocationDetailResponse]: 库位详情响应
"""
try:
# 调用服务层方法
result = OperatePointService.get_storage_location_detail(db=db, storage_location_id=storage_location_id)
return api_response(message="获取库位详情成功", data=result)
except ValueError as e:
return error_response(str(e), 404)
except Exception as e:
logger.error(f"获取库位详情失败: {str(e)}")
return error_response(f"获取库位详情失败: {str(e)}", 500)
@router.put("/{storage_location_id}", response_model=ApiResponse[StorageLocationEditResponse])
async def edit_storage_location(
storage_location_id: str,
request: StorageLocationEditRequest,
db: Session = Depends(get_db)
):
"""
编辑库位信息
允许修改库位的各种属性包括
- 货物信息货物内容重量体积等
- 库位规格最大承重最大体积层高等
- 状态字段是否锁定是否禁用是否空托盘
- 扩展字段自定义的扩展属性值
- 其他属性标签描述等
注意
- 只有传入的字段且值发生变化时才会被更新
- 层名称layer_name不能通过此接口修改
- 扩展字段必须在系统中已定义且已启用
- 如果所有字段都没有发生变化会返回相应提示信息
Args:
storage_location_id: 库位ID
request: 库位编辑请求
db: 数据库会话
Returns:
ApiResponse[StorageLocationEditResponse]: 编辑响应
"""
try:
# 调用服务层方法
result = OperatePointService.edit_storage_location(
db=db,
storage_location_id=storage_location_id,
request=request
)
return api_response(message="库位信息编辑成功", data=result)
except ValueError as e:
return error_response(str(e), 400)
except Exception as e:
logger.error(f"编辑库位信息失败: {str(e)}")
return error_response(f"编辑库位信息失败: {str(e)}", 500)