"""Calendar plugin models — calendars, entries, links, shares, visibility, subtasks, resources, bookings.""" from __future__ import annotations import uuid from datetime import datetime from typing import Any from sqlalchemy import ( Boolean, DateTime, ForeignKey, Index, String, ) from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.dialects.postgresql import UUID as PGUUID from sqlalchemy.orm import Mapped, mapped_column from app.core.db import Base, TenantMixin class Calendar(Base, TenantMixin): """Calendar entity — tenant-scoped, soft-deletable, owned by a user.""" __tablename__ = "calendars" __table_args__ = (Index("ix_calendars_tenant", "tenant_id"),) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) name: Mapped[str] = mapped_column(String(200), nullable=False) color: Mapped[str] = mapped_column(String(20), nullable=False, default="#3B82F6") type: Mapped[str] = mapped_column(String(20), nullable=False, default="personal") owner_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False) ics_token: Mapped[str | None] = mapped_column(String(64), nullable=True) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class CalendarEntry(Base, TenantMixin): """Calendar entry — appointment or task, tenant-scoped, soft-deletable.""" __tablename__ = "calendar_entries" __table_args__ = ( Index("ix_entries_tenant_cal", "tenant_id", "calendar_id"), Index("ix_entries_tenant_start", "tenant_id", "start_at"), Index("ix_entries_tenant_due", "tenant_id", "due_date"), Index("ix_entries_assigned", "tenant_id", "assigned_to", "status"), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) calendar_id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), ForeignKey("calendars.id", ondelete="CASCADE"), nullable=False, ) entry_type: Mapped[str] = mapped_column(String(15), nullable=False) subtype: Mapped[str] = mapped_column(String(20), nullable=False, default="normal") title: Mapped[str] = mapped_column(String(500), nullable=False) description: Mapped[str | None] = mapped_column(String, nullable=True) start_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) end_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) all_day: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) location: Mapped[str | None] = mapped_column(String(500), nullable=True) due_date: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) priority: Mapped[str] = mapped_column(String(10), nullable=False, default="medium") status: Mapped[str] = mapped_column(String(15), nullable=False, default="open") assigned_to: Mapped[uuid.UUID | None] = mapped_column(PGUUID(as_uuid=True), nullable=True) reminder: Mapped[dict[str, Any] | None] = mapped_column(JSONB, nullable=True) recurrence: Mapped[dict[str, Any] | None] = mapped_column(JSONB, nullable=True) source_mail_id: Mapped[uuid.UUID | None] = mapped_column(PGUUID(as_uuid=True), nullable=True) created_by: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class CalendarEntryLink(Base, TenantMixin): """Link between a calendar entry and an entity (company/contact).""" __tablename__ = "calendar_entry_links" __table_args__ = (Index("ix_entry_links_entry", "entry_id"),) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) entry_id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), ForeignKey("calendar_entries.id", ondelete="CASCADE"), nullable=False, ) entity_type: Mapped[str] = mapped_column(String(50), nullable=False) entity_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False) class CalendarShare(Base, TenantMixin): """Calendar sharing — user or group with read/write permission.""" __tablename__ = "calendar_shares" __table_args__ = (Index("ix_calendar_shares_cal", "calendar_id"),) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) calendar_id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), ForeignKey("calendars.id", ondelete="CASCADE"), nullable=False, ) user_id: Mapped[uuid.UUID | None] = mapped_column(PGUUID(as_uuid=True), nullable=True) group_id: Mapped[uuid.UUID | None] = mapped_column(PGUUID(as_uuid=True), nullable=True) permission: Mapped[str] = mapped_column(String(10), nullable=False) class UserCalendarVisibility(Base): """Per-user calendar visibility toggle.""" __tablename__ = "user_calendar_visibility" user_id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), primary_key=True, ) calendar_id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), ForeignKey("calendars.id", ondelete="CASCADE"), primary_key=True, ) tenant_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False) visible: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) class Subtask(Base, TenantMixin): """Subtask belonging to a calendar entry.""" __tablename__ = "subtasks" __table_args__ = (Index("ix_subtasks_entry", "entry_id"),) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) entry_id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), ForeignKey("calendar_entries.id", ondelete="CASCADE"), nullable=False, ) title: Mapped[str] = mapped_column(String(500), nullable=False) completed: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) class Resource(Base, TenantMixin): """Bookable resource — room or equipment.""" __tablename__ = "resources" __table_args__ = (Index("ix_resources_tenant", "tenant_id"),) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) name: Mapped[str] = mapped_column(String(200), nullable=False) type: Mapped[str] = mapped_column(String(50), nullable=False) class ResourceBooking(Base, TenantMixin): """Booking of a resource for a calendar entry.""" __tablename__ = "resource_bookings" __table_args__ = ( Index("ix_bookings_resource", "resource_id"), Index("ix_bookings_entry", "entry_id"), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) resource_id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), ForeignKey("resources.id", ondelete="CASCADE"), nullable=False, ) entry_id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), ForeignKey("calendar_entries.id", ondelete="CASCADE"), nullable=False, ) start_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) end_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)