"""Company model — with soft-delete, FTS tsvector, and tenant scoping.""" from __future__ import annotations import uuid from datetime import datetime from typing import Any from sqlalchemy import String, Text, DateTime, ForeignKey, Index, Computed from sqlalchemy.dialects.postgresql import UUID as PGUUID, TSVECTOR from sqlalchemy.orm import Mapped, mapped_column from app.core.db import Base, TenantMixin class Company(Base, TenantMixin): """Company entity with full-text search support.""" __tablename__ = "companies" __table_args__ = ( Index("ix_companies_tenant_deleted", "tenant_id", "deleted_at"), Index("ix_companies_tenant_name", "tenant_id", "name"), Index("ix_companies_industry", "tenant_id", "industry"), Index("ix_companies_search_vec", "search_tsv", postgresql_using="gin"), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) name: Mapped[str] = mapped_column(String(100), nullable=False) account_number: Mapped[str | None] = mapped_column(String(40), nullable=True) industry: Mapped[str | None] = mapped_column(String(50), nullable=True) phone: Mapped[str | None] = mapped_column(String(30), nullable=True) email: Mapped[str | None] = mapped_column(String(255), nullable=True) website: Mapped[str | None] = mapped_column(String(500), nullable=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) deleted_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) # FTS vector — PostgreSQL generated column, auto-updated on insert/update search_tsv: Mapped[Any] = mapped_column( TSVECTOR, Computed( "to_tsvector('english', coalesce(name, '') || ' ' || coalesce(description, '') || ' ' || coalesce(industry, ''))", persisted=True, ), nullable=True, ) created_by: Mapped[uuid.UUID | None] = mapped_column( PGUUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True ) updated_by: Mapped[uuid.UUID | None] = mapped_column( PGUUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True )