From cb97d390ade1a905a3e05710261282e30a9bc6f5 Mon Sep 17 00:00:00 2001 From: duxingchen Date: Tue, 4 Aug 2026 17:03:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(schemas):=20=E6=9B=B4=E6=96=B0=20Pydantic?= =?UTF-8?q?=20Schema=EF=BC=8C=E6=96=B0=E5=A2=9E=E8=AF=B7=E6=B1=82/?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task Schema 更新: - TaskResponse/TaskSummaryResponse 新增 is_rework, reject_reason, received_at, completed_at 字段 - 新增 TaskRejectRequest (品质驳回请求, reason 必填) - 新增 TaskTransferRequest (裂变转交请求, next_assignees + next_task_name 必填) - 新增 TaskTransferResponse (裂变转交响应) - TaskCreate 新增 is_rework 字段 Product Schema 更新: - ProductResponse/ProductScanResponse 新增 current_location_id 字段 - ProductScanResponse 新增 task_tree 字段 (递归任务树,供十字矩阵树状图) --- backend/app/schemas/__init__.py | 45 ++++++++++++ backend/app/schemas/product.py | 61 ++++++++++++++++ backend/app/schemas/task.py | 120 ++++++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 backend/app/schemas/product.py create mode 100644 backend/app/schemas/task.py diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py index e69de29..a01bbfa 100644 --- a/backend/app/schemas/__init__.py +++ b/backend/app/schemas/__init__.py @@ -0,0 +1,45 @@ +"""Pydantic Schemas — 请求/响应数据模型""" +from app.schemas.product import ( + ProductCreate, + ProductUpdate, + ProductResponse, + ProductScanResponse, +) +from app.schemas.task import ( + TaskCreate, + TaskUpdate, + TaskCompleteRequest, + TaskRejectRequest, + TaskTransferRequest, + SubtaskCreate, + TaskSummaryResponse, + TaskResponse, + TaskCompleteResponse, + TaskTransferResponse, + TaskListResponse, +) +from app.schemas.task_log import ( + TaskLogResponse, +) + +__all__ = [ + # Product + "ProductCreate", + "ProductUpdate", + "ProductResponse", + "ProductScanResponse", + # Task + "TaskCreate", + "TaskUpdate", + "TaskCompleteRequest", + "TaskRejectRequest", + "TaskTransferRequest", + "SubtaskCreate", + "TaskSummaryResponse", + "TaskResponse", + "TaskCompleteResponse", + "TaskTransferResponse", + "TaskListResponse", + # TaskLog + "TaskLogResponse", +] diff --git a/backend/app/schemas/product.py b/backend/app/schemas/product.py new file mode 100644 index 0000000..c312caf --- /dev/null +++ b/backend/app/schemas/product.py @@ -0,0 +1,61 @@ +"""产品 Pydantic Schemas""" +from __future__ import annotations +import uuid +from datetime import datetime +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(逻辑外键→老系统)") + parent_product_id: uuid.UUID | None = Field(None, description="父产品ID") + + model_config = {"from_attributes": True} + + +class ProductUpdate(BaseModel): + """更新产品""" + serial_number: str | None = Field(None, min_length=16, max_length=16) + material_id: str | None = Field(None, max_length=64) + status: str | None = Field(None, max_length=50, description="产品状态") + parent_product_id: uuid.UUID | None = Field(None) + + model_config = {"from_attributes": True} + + +class ProductResponse(BaseModel): + """产品响应""" + id: uuid.UUID + serial_number: str + order_id: uuid.UUID + material_id: str | None + parent_product_id: uuid.UUID | None + current_location_id: str | None = None + status: str + created_at: datetime + + model_config = {"from_attributes": True} + + +class ProductScanResponse(BaseModel): + """扫码查询响应 — 产品信息 + 完整任务树(递归嵌套)""" + id: uuid.UUID + serial_number: str + order_id: uuid.UUID + order_no: str = "" + material_id: str | None + parent_product_id: uuid.UUID | None + current_location_id: str | None = None + status: str + created_at: datetime + top_level_tasks: list[TaskSummaryResponse] = [] + task_tree: list[TaskResponse] = [] + + model_config = {"from_attributes": True} + + +# 延迟导入,避免循环引用 — TaskSummaryResponse 在 tasks.py 中定义 +from app.schemas.task import TaskSummaryResponse # noqa: E402 +ProductScanResponse.model_rebuild() diff --git a/backend/app/schemas/task.py b/backend/app/schemas/task.py new file mode 100644 index 0000000..8a9c5e6 --- /dev/null +++ b/backend/app/schemas/task.py @@ -0,0 +1,120 @@ +"""任务 Pydantic Schemas — 支持无限嵌套子任务、裂变转交、驳回返工""" +from __future__ import annotations +import uuid +from datetime import datetime +from typing import Optional +from pydantic import BaseModel, Field + + +# ============================================================ +# 请求模型 +# ============================================================ + +class TaskCreate(BaseModel): + """创建任务""" + product_id: uuid.UUID = Field(..., description="所属产品ID") + parent_task_id: uuid.UUID | None = Field(None, description="父任务ID(用于嵌套子任务/裂变分支)") + task_name: str = Field(..., max_length=200, description="任务名称") + assignee_id: str | None = Field(None, max_length=64, description="负责人ID(逻辑外键→老系统)") + notify_parent_on_complete: bool = Field(False, description="完成后是否通知父任务") + is_rework: bool = Field(False, description="是否为返工任务") + + model_config = {"from_attributes": True} + + +class TaskUpdate(BaseModel): + """更新任务""" + task_name: str | None = Field(None, max_length=200) + assignee_id: str | None = Field(None, max_length=64) + status: str | None = Field(None, max_length=50, description="任务状态") + notify_parent_on_complete: bool | None = Field(None) + + model_config = {"from_attributes": True} + + +class TaskCompleteRequest(BaseModel): + """完成任务请求 — 携带转交信息(保留兼容旧版单步转交)""" + operator_id: str | None = Field(None, max_length=64, description="操作人ID") + next_assignee_id: str | None = Field(None, max_length=64, description="下一步任务负责人ID") + next_task_name: str | None = Field(None, max_length=200, description="下一步任务名称") + remark: str | None = Field(None, description="完成备注") + + +class SubtaskCreate(BaseModel): + """创建子任务""" + task_name: str = Field(..., max_length=200, description="子任务名称") + assignee_id: str | None = Field(None, max_length=64, description="负责人ID") + notify_parent_on_complete: bool = Field(False, description="完成后是否通知父任务") + + +class TaskRejectRequest(BaseModel): + """品质驳回请求""" + reason: str = Field(..., min_length=1, max_length=500, description="驳回原因(必填)") + + +class TaskTransferRequest(BaseModel): + """完工裂变转交请求""" + next_assignees: list[str] = Field(..., min_length=1, description="下一道工序接收人列表(必填)") + next_task_name: str = Field(..., min_length=1, max_length=200, description="下一道工序名称(必填)") + note: str | None = Field(None, description="交接备注") + + +# ============================================================ +# 响应模型 +# ============================================================ + +class TaskSummaryResponse(BaseModel): + """任务摘要 — 扫码时用,不含嵌套子任务""" + id: uuid.UUID + product_id: uuid.UUID + parent_task_id: uuid.UUID | None + task_name: str + assignee_id: str | None + status: str + notify_parent_on_complete: bool + is_rework: bool = False + reject_reason: str | None = None + received_at: datetime | None = None + completed_at: datetime | None = None + created_at: datetime + + model_config = {"from_attributes": True} + + +class TaskResponse(BaseModel): + """任务详情响应 — 递归包含所有子任务""" + id: uuid.UUID + product_id: uuid.UUID + parent_task_id: uuid.UUID | None + task_name: str + assignee_id: str | None + status: str + notify_parent_on_complete: bool + is_rework: bool = False + reject_reason: str | None = None + received_at: datetime | None = None + completed_at: datetime | None = None + created_at: datetime + child_tasks: list[TaskResponse] = [] + + model_config = {"from_attributes": True} + + +class TaskCompleteResponse(BaseModel): + """任务完成响应""" + completed_task: TaskResponse + next_task: TaskResponse | None = None + message: str + + +class TaskTransferResponse(BaseModel): + """裂变转交响应""" + completed_task: TaskResponse + created_tasks: list[TaskResponse] = [] + message: str + + +class TaskListResponse(BaseModel): + """任务列表响应""" + tasks: list[TaskResponse] + total: int