import uuid import enum from datetime import datetime, timezone, date from sqlalchemy import String, Boolean, DateTime, Date, Enum as SAEnum, ForeignKey, Text, Numeric, Integer from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base class InvoiceStatus(str, enum.Enum): draft = "draft" sent = "sent" paid = "paid" overdue = "overdue" cancelled = "cancelled" class BillingCycle(str, enum.Enum): monthly = "monthly" annual = "annual" class Invoice(Base): __tablename__ = "invoices" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) school_id: Mapped[str] = mapped_column(String(36), ForeignKey("schools.id", ondelete="CASCADE"), nullable=False, index=True) invoice_number: Mapped[str] = mapped_column(String(30), unique=True, nullable=False) status: Mapped[InvoiceStatus] = mapped_column(SAEnum(InvoiceStatus), default=InvoiceStatus.draft, nullable=False) billing_period_start: Mapped[date] = mapped_column(Date, nullable=False) billing_period_end: Mapped[date] = mapped_column(Date, nullable=False) subscription_amount: Mapped[float] = mapped_column(Numeric(12, 2), default=0.0, nullable=False) sms_credit_amount: Mapped[float] = mapped_column(Numeric(12, 2), default=0.0, nullable=False) other_amount: Mapped[float] = mapped_column(Numeric(12, 2), default=0.0, nullable=False) total_amount: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False) currency: Mapped[str] = mapped_column(String(3), default="PHP", nullable=False) due_date: Mapped[date | None] = mapped_column(Date, nullable=True) paid_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) payment_method: Mapped[str | None] = mapped_column(String(50), nullable=True) payment_reference: Mapped[str | None] = mapped_column(String(100), nullable=True) notes: Mapped[str | None] = mapped_column(Text, nullable=True) pdf_path: Mapped[str | None] = mapped_column(String(255), nullable=True) email_sent_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) school: Mapped["School"] = relationship("School", back_populates="invoices") line_items: Mapped[list["InvoiceLineItem"]] = relationship("InvoiceLineItem", back_populates="invoice", cascade="all, delete-orphan") class InvoiceLineItem(Base): __tablename__ = "invoice_line_items" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) invoice_id: Mapped[str] = mapped_column(String(36), ForeignKey("invoices.id", ondelete="CASCADE"), nullable=False) description: Mapped[str] = mapped_column(String(255), nullable=False) quantity: Mapped[float] = mapped_column(Numeric(10, 2), default=1.0, nullable=False) unit_price: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False) amount: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False) invoice: Mapped[Invoice] = relationship("Invoice", back_populates="line_items") class SchoolSubscription(Base): """Stores billing plan per school.""" __tablename__ = "school_subscriptions" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) school_id: Mapped[str] = mapped_column(String(36), ForeignKey("schools.id", ondelete="CASCADE"), unique=True, nullable=False) cycle: Mapped[BillingCycle] = mapped_column(SAEnum(BillingCycle), default=BillingCycle.monthly, nullable=False) monthly_fee: Mapped[float] = mapped_column(Numeric(12, 2), default=0.0, nullable=False) sms_cost_per_message: Mapped[float] = mapped_column(Numeric(8, 4), default=1.0, nullable=False) next_billing_date: Mapped[date | None] = mapped_column(Date, nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))