from datetime import date, datetime from sqlalchemy import ( Boolean, Column, Date, DateTime, Float, ForeignKey, Integer, String, Table, Text, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from database import Base person_tags = Table( "person_tags", Base.metadata, Column("person_id", ForeignKey("people.id", ondelete="CASCADE"), primary_key=True), Column("tag_id", ForeignKey("tags.id", ondelete="CASCADE"), primary_key=True), ) project_tags = Table( "project_tags", Base.metadata, Column("project_id", ForeignKey("projects.id", ondelete="CASCADE"), primary_key=True), Column("tag_id", ForeignKey("tags.id", ondelete="CASCADE"), primary_key=True), ) class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) username: Mapped[str] = mapped_column(String, unique=True, nullable=False, index=True) password_hash: Mapped[str] = mapped_column(String, nullable=False) role: Mapped[str] = mapped_column(String, default="viewer", nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False) class Role(Base): __tablename__ = "roles" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String, unique=True, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False) people: Mapped[list["Person"]] = relationship(back_populates="role") class Team(Base): __tablename__ = "teams" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String, unique=True, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False) people: Mapped[list["Person"]] = relationship(back_populates="team") class Tag(Base): __tablename__ = "tags" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String, unique=True, nullable=False) color: Mapped[str] = mapped_column(String, default="#7c3aed", nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False) class PersonSkill(Base): __tablename__ = "person_skills" __table_args__ = (UniqueConstraint("person_id", "skill_id", name="uq_person_skill"),) person_id: Mapped[int] = mapped_column(ForeignKey("people.id"), primary_key=True) skill_id: Mapped[int] = mapped_column(ForeignKey("skills.id"), primary_key=True) proficiency: Mapped[str] = mapped_column(String, default="intermediate", nullable=False) person: Mapped["Person"] = relationship(back_populates="person_skills") skill: Mapped["Skill"] = relationship(back_populates="person_skills") class Person(Base): __tablename__ = "people" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String, nullable=False) email: Mapped[str] = mapped_column(String, unique=True, nullable=False, index=True) role_id: Mapped[int] = mapped_column(ForeignKey("roles.id"), nullable=False, index=True) team_id: Mapped[int | None] = mapped_column(ForeignKey("teams.id"), index=True) weekly_capacity_hrs: Mapped[float] = mapped_column(Float, default=40.0, nullable=False) start_date: Mapped[date | None] = mapped_column(Date) end_date: Mapped[date | None] = mapped_column(Date) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) avatar_color: Mapped[str] = mapped_column(String, default="#2563eb", nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False) role: Mapped[Role] = relationship(back_populates="people", lazy="selectin") team: Mapped[Team | None] = relationship(back_populates="people", lazy="selectin") tags: Mapped[list[Tag]] = relationship(secondary=person_tags, lazy="selectin") person_skills: Mapped[list[PersonSkill]] = relationship(back_populates="person", cascade="all, delete-orphan") allocations: Mapped[list["Allocation"]] = relationship(back_populates="person") leaves: Mapped[list["Leave"]] = relationship(back_populates="person") owned_projects: Mapped[list["Project"]] = relationship(back_populates="owner") class Skill(Base): __tablename__ = "skills" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String, unique=True, nullable=False) category: Mapped[str | None] = mapped_column(String) person_skills: Mapped[list[PersonSkill]] = relationship(back_populates="skill") class Project(Base): __tablename__ = "projects" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String, nullable=False) description: Mapped[str | None] = mapped_column(Text) status: Mapped[str] = mapped_column(String, default="planning", nullable=False) type: Mapped[str] = mapped_column(String, default="other", nullable=False) start_date: Mapped[date] = mapped_column(Date, nullable=False) end_date: Mapped[date] = mapped_column(Date, nullable=False) is_tentative: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) color: Mapped[str] = mapped_column(String, default="#0ea5e9", nullable=False) owner_id: Mapped[int | None] = mapped_column(ForeignKey("people.id")) team_id: Mapped[int | None] = mapped_column(ForeignKey("teams.id")) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False) owner: Mapped[Person | None] = relationship(back_populates="owned_projects") team: Mapped["Team | None"] = relationship() milestones: Mapped[list["Milestone"]] = relationship(back_populates="project", cascade="all, delete-orphan") phases: Mapped[list["ProjectPhase"]] = relationship(back_populates="project", cascade="all, delete-orphan") allocations: Mapped[list["Allocation"]] = relationship(back_populates="project") tags: Mapped[list[Tag]] = relationship(secondary=project_tags, lazy="selectin") class ProjectPhase(Base): __tablename__ = "project_phases" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) project_id: Mapped[int] = mapped_column(ForeignKey("projects.id"), nullable=False) name: Mapped[str] = mapped_column(String, nullable=False) start_date: Mapped[date] = mapped_column(Date, nullable=False) end_date: Mapped[date] = mapped_column(Date, nullable=False) color: Mapped[str] = mapped_column(String, default="#14b8a6", nullable=False) project: Mapped[Project] = relationship(back_populates="phases") class Milestone(Base): __tablename__ = "milestones" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) project_id: Mapped[int] = mapped_column(ForeignKey("projects.id"), nullable=False) name: Mapped[str] = mapped_column(String, nullable=False) due_date: Mapped[date] = mapped_column(Date, nullable=False) is_completed: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) project: Mapped[Project] = relationship(back_populates="milestones") class Allocation(Base): __tablename__ = "allocations" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) person_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=False) project_id: Mapped[int] = mapped_column(ForeignKey("projects.id"), nullable=False) start_date: Mapped[date] = mapped_column(Date, nullable=False) end_date: Mapped[date] = mapped_column(Date, nullable=False) allocation_pct: Mapped[float] = mapped_column(Float, nullable=False) note: Mapped[str | None] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False) person: Mapped[Person] = relationship(back_populates="allocations") project: Mapped[Project] = relationship(back_populates="allocations") class Leave(Base): __tablename__ = "leaves" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) person_id: Mapped[int | None] = mapped_column(ForeignKey("people.id")) leave_type: Mapped[str] = mapped_column(String, nullable=False) start_date: Mapped[date] = mapped_column(Date, nullable=False) end_date: Mapped[date] = mapped_column(Date, nullable=False) note: Mapped[str | None] = mapped_column(Text) approved: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) person: Mapped[Person | None] = relationship(back_populates="leaves")