""" Timer and time tracking models for integrated time tracking system """ from sqlalchemy import Column, Integer, String, DateTime, Float, Boolean, Text, ForeignKey, Enum from sqlalchemy.orm import relationship from sqlalchemy.sql import func from app.models.base import BaseModel import enum from datetime import datetime, timezone class TimerStatus(str, enum.Enum): """Timer status enumeration""" STOPPED = "stopped" RUNNING = "running" PAUSED = "paused" class TimerType(str, enum.Enum): """Timer type enumeration""" BILLABLE = "billable" NON_BILLABLE = "non_billable" ADMINISTRATIVE = "administrative" class Timer(BaseModel): """ Active timer sessions for time tracking Represents a running, paused, or stopped timer """ __tablename__ = "timers" id = Column(Integer, primary_key=True, autoincrement=True) # User and assignment user_id = Column(Integer, ForeignKey("users.id"), nullable=False) file_no = Column(String(45), ForeignKey("files.file_no")) # Optional file assignment customer_id = Column(String(45), ForeignKey("rolodex.id")) # Optional customer assignment # Timer details title = Column(String(200), nullable=False) # Brief description of task description = Column(Text) # Detailed description of work timer_type = Column(Enum(TimerType), default=TimerType.BILLABLE) # Time tracking status = Column(Enum(TimerStatus), default=TimerStatus.STOPPED) total_seconds = Column(Integer, default=0) # Total accumulated time in seconds # Session timing started_at = Column(DateTime(timezone=True)) # When timer was first started last_started_at = Column(DateTime(timezone=True)) # When current session started last_paused_at = Column(DateTime(timezone=True)) # When timer was last paused stopped_at = Column(DateTime(timezone=True)) # When timer was stopped # Billing information hourly_rate = Column(Float) # Override rate for this timer is_billable = Column(Boolean, default=True) # Metadata created_at = Column(DateTime(timezone=True), default=func.now()) updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) # Task categorization task_category = Column(String(50)) # research, drafting, client_call, court_appearance, etc. notes = Column(Text) # Additional notes # Relationships user = relationship("User", back_populates="timers") file = relationship("File", back_populates="timers") customer = relationship("Rolodex", back_populates="timers") time_entries = relationship("TimeEntry", back_populates="timer", cascade="all, delete-orphan") def __repr__(self): return f"" @property def total_hours(self) -> float: """Get total time in hours""" return self.total_seconds / 3600.0 if self.total_seconds else 0.0 @property def is_active(self) -> bool: """Check if timer is currently running or paused""" return self.status in [TimerStatus.RUNNING, TimerStatus.PAUSED] def get_current_session_seconds(self) -> int: """Get seconds for current running session""" if self.status == TimerStatus.RUNNING and self.last_started_at: now = datetime.now(timezone.utc) session_duration = (now - self.last_started_at).total_seconds() return int(session_duration) return 0 def get_total_current_seconds(self) -> int: """Get total seconds including current running session""" return self.total_seconds + self.get_current_session_seconds() class TimeEntry(BaseModel): """ Completed time entries that can be converted to billing transactions Represents finalized time that has been logged """ __tablename__ = "time_entries" id = Column(Integer, primary_key=True, autoincrement=True) # Source timer (optional - entries can be created manually) timer_id = Column(Integer, ForeignKey("timers.id")) # User and assignment user_id = Column(Integer, ForeignKey("users.id"), nullable=False) file_no = Column(String(45), ForeignKey("files.file_no")) # Optional file assignment customer_id = Column(String(45), ForeignKey("rolodex.id")) # Optional customer assignment # Time entry details title = Column(String(200), nullable=False) description = Column(Text) entry_type = Column(Enum(TimerType), default=TimerType.BILLABLE) # Time information hours = Column(Float, nullable=False) # Time in decimal hours (e.g., 1.5 = 1 hour 30 minutes) entry_date = Column(DateTime(timezone=True), nullable=False) # Date/time when work was performed # Billing information hourly_rate = Column(Float) # Rate for this entry is_billable = Column(Boolean, default=True) billed = Column(Boolean, default=False) # Whether this has been converted to a ledger entry # Related ledger entry (after billing) ledger_id = Column(Integer, ForeignKey("ledger.id")) # Link to created billing transaction # Metadata created_at = Column(DateTime(timezone=True), default=func.now()) updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) created_by = Column(String(50)) # Who created this entry # Task categorization task_category = Column(String(50)) # research, drafting, client_call, court_appearance, etc. notes = Column(Text) # Additional notes # Approval workflow approved = Column(Boolean, default=False) approved_by = Column(String(50)) approved_at = Column(DateTime(timezone=True)) # Relationships timer = relationship("Timer", back_populates="time_entries") user = relationship("User", back_populates="time_entries") file = relationship("File", back_populates="time_entries") customer = relationship("Rolodex", back_populates="time_entries") ledger_entry = relationship("Ledger") def __repr__(self): return f"" @property def calculated_amount(self) -> float: """Calculate billable amount based on hours and rate""" if not self.is_billable or not self.hourly_rate: return 0.0 return self.hours * self.hourly_rate class TimerSession(BaseModel): """ Individual timer sessions for detailed tracking Each start/stop cycle creates a session record """ __tablename__ = "timer_sessions" id = Column(Integer, primary_key=True, autoincrement=True) timer_id = Column(Integer, ForeignKey("timers.id"), nullable=False) # Session timing started_at = Column(DateTime(timezone=True), nullable=False) ended_at = Column(DateTime(timezone=True)) duration_seconds = Column(Integer, default=0) # Session notes notes = Column(Text) # Notes for this specific session # Pause tracking pause_count = Column(Integer, default=0) # Number of times paused during session total_pause_seconds = Column(Integer, default=0) # Total time spent paused # Relationships timer = relationship("Timer") def __repr__(self): return f"" @property def duration_hours(self) -> float: """Get session duration in hours""" return self.duration_seconds / 3600.0 if self.duration_seconds else 0.0 class TimerTemplate(BaseModel): """ Predefined timer templates for common tasks Allows quick creation of timers with pre-filled information """ __tablename__ = "timer_templates" id = Column(Integer, primary_key=True, autoincrement=True) # Template details name = Column(String(100), nullable=False) title_template = Column(String(200), nullable=False) # Template for timer title description_template = Column(Text) # Template for timer description # Default settings timer_type = Column(Enum(TimerType), default=TimerType.BILLABLE) task_category = Column(String(50)) default_rate = Column(Float) is_billable = Column(Boolean, default=True) # Template metadata created_by = Column(String(50)) created_at = Column(DateTime(timezone=True), default=func.now()) updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now()) is_active = Column(Boolean, default=True) usage_count = Column(Integer, default=0) # Track how often template is used def __repr__(self): return f""