Files
track/backend/app/models/task.py

120 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""任务模型 — 支持无限嵌套、任务裂变分支、返工闭环"""
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" # 已入库
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="是否为返工任务",
)
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",
)
records: Mapped[list["TaskRecord"]] = relationship(
"TaskRecord", back_populates="task", lazy="selectin", cascade="all, delete-orphan",
)
def __repr__(self) -> str:
return f"<Task {self.task_name}>"
# ============================================================
# 任务进度记录 — 随时备注/传图
# ============================================================
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"<TaskRecord {self.id} @ {self.created_at}>"