"""Subscription model for organization billing.""" import logging from datetime import datetime, timezone from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, Enum from gatehouse_app.models.base import BaseModel import enum logger = logging.getLogger(__name__) class SubscriptionStatus(enum.Enum): """Subscription status values.""" TRIAL = "trial" ACTIVE = "active" PAST_DUE = "past_due" CANCELLED = "cancelled" SUSPENDED = "suspended" class BillingCycle(enum.Enum): """Billing cycle values.""" MONTHLY = "monthly" YEARLY = "yearly" class Subscription(BaseModel): """Organization subscription record. Links an organization to a plan and tracks billing state. """ __tablename__ = "subscriptions" # Organization relation organization_id = Column( String(36), ForeignKey("organizations.id", ondelete="CASCADE"), unique=True, nullable=False ) # Plan relation plan_id = Column( String(36), ForeignKey("plans.id", ondelete="SET NULL"), nullable=True ) # Status status = Column( Enum(SubscriptionStatus, name="subscription_status"), nullable=False, default=SubscriptionStatus.TRIAL ) # Billing billing_cycle = Column( Enum(BillingCycle, name="billing_cycle"), nullable=False, default=BillingCycle.MONTHLY ) # Period dates current_period_start = Column(DateTime, nullable=True) current_period_end = Column(DateTime, nullable=True) # Trial trial_ends_at = Column(DateTime, nullable=True) # Stripe stripe_subscription_id = Column(String(100), nullable=True) # Overage overage_enabled = Column(Boolean, nullable=False, default=True) # Cancellation cancelled_at = Column(DateTime, nullable=True) cancel_at_period_end = Column(Boolean, nullable=False, default=False) def __repr__(self): return f"" def to_dict(self): """Convert subscription to dictionary.""" return { "id": self.id, "organization_id": self.organization_id, "plan_id": self.plan_id, "status": self.status.value if self.status else None, "billing_cycle": self.billing_cycle.value if self.billing_cycle else None, "current_period_start": self.current_period_start.isoformat() + "Z" if self.current_period_start else None, "current_period_end": self.current_period_end.isoformat() + "Z" if self.current_period_end else None, "trial_ends_at": self.trial_ends_at.isoformat() + "Z" if self.trial_ends_at else None, "stripe_subscription_id": self.stripe_subscription_id, "overage_enabled": self.overage_enabled, "cancelled_at": self.cancelled_at.isoformat() + "Z" if self.cancelled_at else None, "cancel_at_period_end": self.cancel_at_period_end, "created_at": self.created_at.isoformat() + "Z" if self.created_at else None, "updated_at": self.updated_at.isoformat() + "Z" if self.updated_at else None, }