- New calendar.py model file with TimeSlot table definition - SlotType enum: work, on_call, entertainment, system - SlotStatus enum: not_started, ongoing, deferred, skipped, paused, finished, aborted - EventType enum: job, entertainment, system_event - All fields per design doc: user_id, date, slot_type, estimated_duration, scheduled_at, started_at, attended, actual_duration, event_type, event_data (JSON), priority, status, plan_id (FK to schedule_plans)
144 lines
3.6 KiB
Python
144 lines
3.6 KiB
Python
"""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())
|