"""任务模型 — 支持无限嵌套、任务裂变分支、返工闭环""" import uuid from datetime import datetime from sqlalchemy import String, DateTime, Boolean, ForeignKey from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import Base from app.core.time_utils import get_beijing_time # 任务状态常量 TASK_STATUS_PENDING = "PENDING" # 待接收 TASK_STATUS_WIP = "WIP" # 进行中 TASK_STATUS_COMPLETED = "COMPLETED" # 已完成 TASK_STATUS_REJECTED = "REJECTED" # 已驳回 TASK_STATUS_ARCHIVED = "ARCHIVED" # 已入库 TASK_STATUS_CANCELED = "CANCELED" # 已撤回/已作废 class Task(Base): __tablename__ = "tasks" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, ) # ---- 物理外键(关联本库 products) ---- product_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("products.id"), nullable=False, comment="所属产品ID", ) # ---- 物理外键(自引用:无限嵌套父子任务 / 裂变分支) ---- parent_task_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("tasks.id"), nullable=True, index=True, comment="父任务ID(用于任务裂变树)", ) task_name: Mapped[str] = mapped_column( String(200), nullable=False, comment="任务名称", ) # ---- 逻辑外键(关联老系统用户表,仅存储 ID,无物理约束) ---- assignee_id: Mapped[str | None] = mapped_column( String(64), nullable=True, comment="负责人ID(逻辑外键→老系统)", ) status: Mapped[str] = mapped_column( String(50), nullable=False, default=TASK_STATUS_PENDING, comment="任务状态: PENDING(待接收) | WIP(进行中) | COMPLETED(已完成) | REJECTED(已驳回) | ARCHIVED(已入库)", ) notify_parent_on_complete: Mapped[bool] = mapped_column( Boolean, default=False, comment="完成后是否通知父任务", ) # ---- 时间追踪 ---- created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=get_beijing_time, comment="创建时间", ) received_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, comment="操作员确认接收时间", ) completed_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, comment="任务完工转交时间", ) # ---- 驳回/返工 ---- reject_reason: Mapped[str | None] = mapped_column( String(500), nullable=True, comment="驳回原因", ) is_rework: Mapped[bool] = mapped_column( Boolean, default=False, comment="是否为返工任务", ) task_type: Mapped[str | None] = mapped_column( String(20), nullable=True, comment="任务派生类型: TRANSFER/SPAWN/RECOVERY/null=历史数据", ) remark: Mapped[str | None] = mapped_column( String(2000), nullable=True, comment="任务初始描述/交接备注", ) # ---- 关系 ---- product: Mapped["Product"] = relationship("Product", lazy="selectin") parent_task: Mapped["Task | None"] = relationship( "Task", remote_side="Task.id", back_populates="child_tasks", lazy="selectin", ) child_tasks: Mapped[list["Task"]] = relationship( "Task", back_populates="parent_task", lazy="selectin", order_by="Task.created_at", ) records: Mapped[list["TaskRecord"]] = relationship( "TaskRecord", back_populates="task", lazy="selectin", cascade="all, delete-orphan", ) # 本任务挂载的 MOM 出库物料(明细级快照)。创建任务时选、之后可追加, # 见 models/task_outbound_material.py 的设计说明。 outbound_materials: Mapped[list["TaskOutboundMaterial"]] = relationship( "TaskOutboundMaterial", back_populates="task", lazy="selectin", cascade="all, delete-orphan", order_by="TaskOutboundMaterial.created_at", ) def __repr__(self) -> str: return f"" # ============================================================ # 任务进度记录 — 随时备注/传图 # ============================================================ class TaskRecord(Base): __tablename__ = "task_records" id: Mapped[int] = mapped_column( primary_key=True, autoincrement=True, ) task_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("tasks.id"), nullable=False, index=True, comment="所属任务ID", ) remark: Mapped[str | None] = mapped_column( String(2000), nullable=True, comment="备注文本", ) images: Mapped[str | None] = mapped_column( String(4000), nullable=True, comment="图片URL列表(JSON字符串)", ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=get_beijing_time, comment="记录时间", ) # ---- 关系 ---- task: Mapped["Task"] = relationship("Task", back_populates="records") def __repr__(self) -> str: return f""