feat(schemas): 扩充 Task/Product Pydantic Schema
Task Schema: - TaskResponse 新增 is_rework, reject_reason, received_at, completed_at, records, product_sn - 新增 TaskRejectRequest, TaskTransferRequest, TaskRecordCreate, TaskRecordResponse - TaskRecordResponse 含 json.loads 反序列化 images Product Schema: - ProductResponse/ProductScanResponse 新增 external_serial, current_location_id, overall_status - ProductScanResponse 新增 task_tree (递归任务树) - ProductCreate 改为 material_id/material_name/spec_model + external_serial + order_no 自由文本 - ProductUpdate 新增 order_no
This commit is contained in:
@ -6,10 +6,17 @@ from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ProductCreate(BaseModel):
|
||||
"""创建产品"""
|
||||
serial_number: str = Field(..., min_length=16, max_length=16, description="产品序列号(16位)")
|
||||
order_id: uuid.UUID = Field(..., description="所属订单ID")
|
||||
material_id: str | None = Field(None, max_length=64, description="物料ID(逻辑外键→老系统)")
|
||||
"""创建产品 — serial_number 由后端自动生成 16 位 HEX"""
|
||||
# MOM 物料挂载(必填)
|
||||
material_id: str = Field(..., max_length=64, description="MOM物料ID(必选)")
|
||||
material_name: str = Field("", max_length=255, description="物料名称")
|
||||
spec_model: str = Field("", max_length=255, description="规格型号")
|
||||
category: str = Field("", max_length=100, description="物料类别")
|
||||
material_type: str = Field("", max_length=100, description="物料类型")
|
||||
# 可选
|
||||
external_serial: str | None = Field(None, max_length=64, description="产品序列号(用户自定义,选填)")
|
||||
order_id: uuid.UUID | None = Field(None, description="所属订单ID(选填)")
|
||||
order_no: str | None = Field(None, max_length=64, description="订单号(自由键入,选填)")
|
||||
parent_product_id: uuid.UUID | None = Field(None, description="父产品ID")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
@ -18,7 +25,11 @@ class ProductCreate(BaseModel):
|
||||
class ProductUpdate(BaseModel):
|
||||
"""更新产品"""
|
||||
serial_number: str | None = Field(None, min_length=16, max_length=16)
|
||||
external_serial: str | None = Field(None, max_length=64)
|
||||
material_id: str | None = Field(None, max_length=64)
|
||||
material_name: str | None = Field(None, max_length=255)
|
||||
spec_model: str | None = Field(None, max_length=255)
|
||||
order_no: str | None = Field(None, max_length=64, description="订单号")
|
||||
status: str | None = Field(None, max_length=50, description="产品状态")
|
||||
parent_product_id: uuid.UUID | None = Field(None)
|
||||
|
||||
@ -29,10 +40,17 @@ class ProductResponse(BaseModel):
|
||||
"""产品响应"""
|
||||
id: uuid.UUID
|
||||
serial_number: str
|
||||
order_id: uuid.UUID
|
||||
external_serial: str | None = None
|
||||
order_id: uuid.UUID | None = None
|
||||
order_no: str = ""
|
||||
material_id: str | None
|
||||
material_name: str | None = None
|
||||
spec_model: str | None = None
|
||||
category: str | None = None
|
||||
material_type: str | None = None
|
||||
parent_product_id: uuid.UUID | None
|
||||
current_location_id: str | None = None
|
||||
overall_status: str | None = None
|
||||
status: str
|
||||
created_at: datetime
|
||||
|
||||
@ -43,11 +61,17 @@ class ProductScanResponse(BaseModel):
|
||||
"""扫码查询响应 — 产品信息 + 完整任务树(递归嵌套)"""
|
||||
id: uuid.UUID
|
||||
serial_number: str
|
||||
order_id: uuid.UUID
|
||||
external_serial: str | None = None
|
||||
order_id: uuid.UUID | None = None
|
||||
order_no: str = ""
|
||||
material_id: str | None
|
||||
material_name: str | None = None
|
||||
spec_model: str | None = None
|
||||
category: str | None = None
|
||||
material_type: str | None = None
|
||||
parent_product_id: uuid.UUID | None
|
||||
current_location_id: str | None = None
|
||||
overall_status: str | None = None
|
||||
status: str
|
||||
created_at: datetime
|
||||
top_level_tasks: list[TaskSummaryResponse] = []
|
||||
@ -56,6 +80,6 @@ class ProductScanResponse(BaseModel):
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
# 延迟导入,避免循环引用 — TaskSummaryResponse 在 tasks.py 中定义
|
||||
from app.schemas.task import TaskSummaryResponse # noqa: E402
|
||||
# 延迟导入,避免循环引用
|
||||
from app.schemas.task import TaskSummaryResponse, TaskResponse # noqa: E402
|
||||
ProductScanResponse.model_rebuild()
|
||||
|
||||
@ -59,6 +59,35 @@ class TaskTransferRequest(BaseModel):
|
||||
note: str | None = Field(None, description="交接备注")
|
||||
|
||||
|
||||
class TaskRecordCreate(BaseModel):
|
||||
"""任务进度记录 — 备注 + 图片"""
|
||||
remark: str = Field("", max_length=2000, description="备注文本")
|
||||
images: list[str] = Field(default_factory=list, description="图片 URL 列表")
|
||||
|
||||
|
||||
class TaskRecordResponse(BaseModel):
|
||||
id: int
|
||||
task_id: uuid.UUID
|
||||
remark: str | None = None
|
||||
images: list[str] = []
|
||||
created_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@classmethod
|
||||
def model_validate(cls, obj, **kwargs):
|
||||
"""处理 DB 中 images 的 JSON 字符串 → list 反序列化"""
|
||||
import json
|
||||
if hasattr(obj, "images") and isinstance(obj.images, str):
|
||||
try:
|
||||
obj.images = json.loads(obj.images)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
obj.images = []
|
||||
elif hasattr(obj, "images") and obj.images is None:
|
||||
obj.images = []
|
||||
return super().model_validate(obj, **kwargs)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 响应模型
|
||||
# ============================================================
|
||||
@ -85,6 +114,8 @@ class TaskResponse(BaseModel):
|
||||
"""任务详情响应 — 递归包含所有子任务"""
|
||||
id: uuid.UUID
|
||||
product_id: uuid.UUID
|
||||
product_sn: str = ""
|
||||
product_material: str = ""
|
||||
parent_task_id: uuid.UUID | None
|
||||
task_name: str
|
||||
assignee_id: str | None
|
||||
@ -96,6 +127,7 @@ class TaskResponse(BaseModel):
|
||||
completed_at: datetime | None = None
|
||||
created_at: datetime
|
||||
child_tasks: list[TaskResponse] = []
|
||||
records: list[TaskRecordResponse] = []
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user