project.py 4.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. from datetime import datetime
  2. from sqlalchemy import JSON, Boolean, DateTime, Float, ForeignKey, Integer, String, Text, func
  3. from sqlalchemy.orm import Mapped, mapped_column, relationship
  4. from backend.app.core.database import Base
  5. # The priorities and statuses the project editor accepts.
  6. PROJECT_PRIORITIES = ("low", "normal", "high", "urgent")
  7. PROJECT_STATUSES = ("active", "completed", "archived")
  8. class Project(Base):
  9. """Project to group related prints (e.g., 'Voron Build' with multiple parts)."""
  10. __tablename__ = "projects"
  11. id: Mapped[int] = mapped_column(primary_key=True)
  12. name: Mapped[str] = mapped_column(String(255))
  13. description: Mapped[str | None] = mapped_column(Text, nullable=True)
  14. color: Mapped[str | None] = mapped_column(String(20), nullable=True) # Hex color for UI
  15. status: Mapped[str] = mapped_column(String(20), default="active") # active, completed, archived
  16. # External link rendered as a clickable icon next to the project name (#1155).
  17. url: Mapped[str | None] = mapped_column(String(2048), nullable=True)
  18. # Filename of the cover photo inside the project's attachments dir; serves as
  19. # the card's hero image when set (#1155). The file lives alongside other
  20. # attachments but is tracked here separately so users can manage one without
  21. # the other.
  22. cover_image_filename: Mapped[str | None] = mapped_column(String(255), nullable=True)
  23. target_count: Mapped[int | None] = mapped_column(
  24. Integer, nullable=True
  25. ) # Optional target number of prints (plates)
  26. target_parts_count: Mapped[int | None] = mapped_column(
  27. Integer, nullable=True
  28. ) # Optional target number of parts/objects
  29. # Optional copies-per-file target (#1897): every printable file in the
  30. # project's linked folders should be printed this many times ("sets").
  31. target_sets: Mapped[int | None] = mapped_column(Integer, nullable=True)
  32. # Phase 2: Rich text notes (HTML from WYSIWYG editor)
  33. notes: Mapped[str | None] = mapped_column(Text, nullable=True)
  34. # Phase 3: File attachments stored as JSON array
  35. # Format: [{"filename": "x.stl", "original_name": "part.stl", "size": 1234, "uploaded_at": "..."}]
  36. attachments: Mapped[list | None] = mapped_column(JSON, nullable=True)
  37. # Phase 4: Tags (comma-separated)
  38. tags: Mapped[str | None] = mapped_column(Text, nullable=True)
  39. # Phase 5: Due dates and priority
  40. due_date: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
  41. priority: Mapped[str] = mapped_column(String(20), default="normal") # low, normal, high, urgent
  42. # Phase 6: Budget tracking
  43. budget: Mapped[float | None] = mapped_column(Float, nullable=True)
  44. # Phase 8: Templates
  45. is_template: Mapped[bool] = mapped_column(Boolean, default=False)
  46. template_source_id: Mapped[int | None] = mapped_column(Integer, nullable=True)
  47. # Phase 10: Sub-projects (hierarchical)
  48. parent_id: Mapped[int | None] = mapped_column(ForeignKey("projects.id"), nullable=True)
  49. # Timestamps
  50. created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
  51. updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
  52. # Relationships
  53. archives: Mapped[list["PrintArchive"]] = relationship(back_populates="project")
  54. queue_items: Mapped[list["PrintQueueItem"]] = relationship(back_populates="project")
  55. children: Mapped[list["Project"]] = relationship(
  56. "Project",
  57. back_populates="parent",
  58. foreign_keys="Project.parent_id",
  59. )
  60. parent: Mapped["Project | None"] = relationship(
  61. "Project",
  62. back_populates="children",
  63. remote_side="Project.id",
  64. foreign_keys="Project.parent_id",
  65. )
  66. bom_items: Mapped[list["ProjectBOMItem"]] = relationship(back_populates="project", cascade="all, delete-orphan")
  67. from backend.app.models.archive import PrintArchive # noqa: E402
  68. from backend.app.models.print_queue import PrintQueueItem # noqa: E402
  69. from backend.app.models.project_bom import ProjectBOMItem # noqa: E402