HarborForge.Backend: dev-2026-03-29 -> main #13
143
app/models/calendar.py
Normal file
143
app/models/calendar.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""Calendar models — TimeSlot and related enums.
|
||||
|
||||
TimeSlot represents a single scheduled slot on a user's calendar.
|
||||
Slots can be created manually or materialized from a SchedulePlan.
|
||||
|
||||
See: NEXT_WAVE_DEV_DIRECTION.md §1.1
|
||||
"""
|
||||
|
||||
from sqlalchemy import (
|
||||
Column, Integer, String, Text, DateTime, Date, Time,
|
||||
ForeignKey, Enum, Boolean, JSON,
|
||||
)
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.config import Base
|
||||
import enum
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Enums
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class SlotType(str, enum.Enum):
|
||||
"""What kind of slot this is."""
|
||||
WORK = "work"
|
||||
ON_CALL = "on_call"
|
||||
ENTERTAINMENT = "entertainment"
|
||||
SYSTEM = "system"
|
||||
|
||||
|
||||
class SlotStatus(str, enum.Enum):
|
||||
"""Lifecycle status of a slot."""
|
||||
NOT_STARTED = "not_started"
|
||||
ONGOING = "ongoing"
|
||||
DEFERRED = "deferred"
|
||||
SKIPPED = "skipped"
|
||||
PAUSED = "paused"
|
||||
FINISHED = "finished"
|
||||
ABORTED = "aborted"
|
||||
|
||||
|
||||
class EventType(str, enum.Enum):
|
||||
"""High-level event category stored alongside the slot."""
|
||||
JOB = "job"
|
||||
ENTERTAINMENT = "entertainment"
|
||||
SYSTEM_EVENT = "system_event"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TimeSlot model
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TimeSlot(Base):
|
||||
__tablename__ = "time_slots"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
user_id = Column(
|
||||
Integer,
|
||||
ForeignKey("users.id"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="Owner of this slot",
|
||||
)
|
||||
|
||||
date = Column(
|
||||
Date,
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="Calendar date for this slot",
|
||||
)
|
||||
|
||||
slot_type = Column(
|
||||
Enum(SlotType, values_callable=lambda x: [e.value for e in x]),
|
||||
nullable=False,
|
||||
comment="work | on_call | entertainment | system",
|
||||
)
|
||||
|
||||
estimated_duration = Column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
comment="Estimated duration in minutes (1-50)",
|
||||
)
|
||||
|
||||
scheduled_at = Column(
|
||||
Time,
|
||||
nullable=False,
|
||||
comment="Planned start time (00:00-23:00)",
|
||||
)
|
||||
|
||||
started_at = Column(
|
||||
Time,
|
||||
nullable=True,
|
||||
comment="Actual start time (filled when slot begins)",
|
||||
)
|
||||
|
||||
attended = Column(
|
||||
Boolean,
|
||||
default=False,
|
||||
nullable=False,
|
||||
comment="Whether the slot has been attended",
|
||||
)
|
||||
|
||||
actual_duration = Column(
|
||||
Integer,
|
||||
nullable=True,
|
||||
comment="Actual duration in minutes (0-65535), no upper design limit",
|
||||
)
|
||||
|
||||
event_type = Column(
|
||||
Enum(EventType, values_callable=lambda x: [e.value for e in x]),
|
||||
nullable=True,
|
||||
comment="job | entertainment | system_event",
|
||||
)
|
||||
|
||||
event_data = Column(
|
||||
JSON,
|
||||
nullable=True,
|
||||
comment="Event details JSON — structure depends on event_type",
|
||||
)
|
||||
|
||||
priority = Column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="Priority 0-99, higher = more important",
|
||||
)
|
||||
|
||||
status = Column(
|
||||
Enum(SlotStatus, values_callable=lambda x: [e.value for e in x]),
|
||||
nullable=False,
|
||||
default=SlotStatus.NOT_STARTED,
|
||||
comment="Lifecycle status of this slot",
|
||||
)
|
||||
|
||||
plan_id = Column(
|
||||
Integer,
|
||||
ForeignKey("schedule_plans.id"),
|
||||
nullable=True,
|
||||
comment="Source plan if materialized from a SchedulePlan; set NULL on edit/cancel",
|
||||
)
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
Reference in New Issue
Block a user