from sqlalchemy import Column, Integer, String, DateTime, Date, Enum, ForeignKey, Float, JSON from sqlalchemy.orm import relationship from sqlalchemy.sql import func from database import Base import enum class ProjectStatus(str, enum.Enum): PLANNING = "planning" IN_PROGRESS = "in_progress" ON_HOLD = "on_hold" COMPLETED = "completed" CANCELLED = "cancelled" class ProjectType(str, enum.Enum): TV = "tv" CINEMA = "cinema" GAME = "game" class Project(Base): __tablename__ = "projects" id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False, index=True) code_name = Column(String, nullable=False, unique=True, index=True) # Unique project code client_name = Column(String, nullable=False, index=True) # Client/studio name project_type = Column(Enum(ProjectType), nullable=False) # TV, Cinema, Game description = Column(String) status = Column(Enum(ProjectStatus), nullable=False, default=ProjectStatus.PLANNING) start_date = Column(Date) end_date = Column(Date) # Technical specifications frame_rate = Column(Float, nullable=True) # Frames per second (1-120 fps) data_drive_path = Column(String, nullable=True) # Physical path for project data storage publish_storage_path = Column(String, nullable=True) # Path for approved work delivery delivery_image_resolution = Column(String, nullable=True) # Required image resolution delivery_movie_specs_by_department = Column(JSON, nullable=True) # Delivery movie specs per department # Project-specific settings upload_data_location = Column(String, nullable=True) # Custom upload storage path asset_task_templates = Column(JSON, nullable=True) # Default tasks per asset category shot_task_templates = Column(JSON, nullable=True) # Default tasks for shots enabled_asset_tasks = Column(JSON, nullable=True) # Enabled/disabled asset tasks per category enabled_shot_tasks = Column(JSON, nullable=True) # Enabled/disabled shot tasks # Custom task types custom_asset_task_types = Column(JSON, nullable=True) # Custom task types for assets custom_shot_task_types = Column(JSON, nullable=True) # Custom task types for shots # Custom task statuses custom_task_statuses = Column(JSON, nullable=True) # Custom task statuses for project # Custom departments custom_departments = Column(JSON, nullable=True) # Custom departments for project (in addition to standard ones) # Submission configuration per task type submission_config_by_task_type = Column(JSON, nullable=True) # Allowed file types, naming pattern, required flag per task type # Project thumbnail thumbnail_path = Column(String, nullable=True) # Path to project thumbnail image created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) # Relationships episodes = relationship("Episode", back_populates="project", cascade="all, delete-orphan") shots = relationship("Shot", back_populates="project", cascade="all, delete-orphan") assets = relationship("Asset", back_populates="project", cascade="all, delete-orphan") project_members = relationship("ProjectMember", back_populates="project", cascade="all, delete-orphan") tasks = relationship("Task", back_populates="project", cascade="all, delete-orphan") def __repr__(self): return f"" class ProjectMember(Base): __tablename__ = "project_members" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) project_id = Column(Integer, ForeignKey("projects.id"), nullable=False) department_role = Column(String, nullable=True) # Free-form: standard department or a project's custom department joined_at = Column(DateTime(timezone=True), server_default=func.now()) # Relationships user = relationship("User", back_populates="project_memberships") project = relationship("Project", back_populates="project_members") def __repr__(self): return f""