diff --git a/backend/app/schemas/product.py b/backend/app/schemas/product.py index c312caf..cff00f3 100644 --- a/backend/app/schemas/product.py +++ b/backend/app/schemas/product.py @@ -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() diff --git a/backend/app/schemas/task.py b/backend/app/schemas/task.py index 8a9c5e6..e94318e 100644 --- a/backend/app/schemas/task.py +++ b/backend/app/schemas/task.py @@ -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}