from decimal import Decimal from sqlalchemy import JSON, Integer, Numeric, String, Text, text from sqlalchemy.orm import Mapped, mapped_column from api.v1.common.base_models import TenantScopedMixin, TimestampMixin from core.database import Base class Supplier(Base, TenantScopedMixin, TimestampMixin): """Catálogo de Proveedores (navieras, aerolíneas, transportistas, agentes, etc.). Los campos multi-valor (clasificaciones, cobertura por país/puerto/aduana) se guardan como listas JSON. Direcciones, contactos y documentos viven en tablas relacionadas (crm.addresses, crm.contacts, crm.documents). """ __tablename__ = "suppliers" __table_args__ = {"schema": "crm"} id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) # ----- Datos generales ----- name: Mapped[str] = mapped_column(String(255), nullable=False) # razón social trade_name: Mapped[str | None] = mapped_column(String(255), nullable=True) rfc: Mapped[str | None] = mapped_column(String(13), nullable=True, index=True) curp: Mapped[str | None] = mapped_column(String(18), nullable=True) person_type: Mapped[str | None] = mapped_column(String(10), nullable=True) # fisica | moral status: Mapped[str] = mapped_column(String(20), nullable=False, server_default=text("'active'")) # Clasificación (múltiple): naviera, aerolinea, transportista_terrestre, ferrocarril, # agente_aduanal, agente_carga, agente_corresponsal, almacen, aseguradora, paqueteria, otro classifications: Mapped[list | None] = mapped_column(JSON, nullable=True, default=list) # ----- Información comercial ----- services_offered: Mapped[str | None] = mapped_column(Text, nullable=True) coverage: Mapped[str | None] = mapped_column(String(20), nullable=True) # nacional | internacional | ambos countries: Mapped[list | None] = mapped_column(JSON, nullable=True, default=list) ports: Mapped[list | None] = mapped_column(JSON, nullable=True, default=list) airports: Mapped[list | None] = mapped_column(JSON, nullable=True, default=list) customs: Mapped[list | None] = mapped_column(JSON, nullable=True, default=list) # aduanas business_hours: Mapped[str | None] = mapped_column(String(255), nullable=True) quote_currency: Mapped[str | None] = mapped_column(String(3), nullable=True) avg_response_time: Mapped[str | None] = mapped_column(String(120), nullable=True) commercial_notes: Mapped[str | None] = mapped_column(Text, nullable=True) email: Mapped[str | None] = mapped_column(String(255), nullable=True) phone: Mapped[str | None] = mapped_column(String(40), nullable=True) website: Mapped[str | None] = mapped_column(String(255), nullable=True) # ----- Información fiscal ----- tax_regime: Mapped[str | None] = mapped_column(String(120), nullable=True) payment_method: Mapped[str | None] = mapped_column(String(60), nullable=True) payment_form: Mapped[str | None] = mapped_column(String(60), nullable=True) credit_limit: Mapped[Decimal | None] = mapped_column(Numeric(14, 2), nullable=True) credit_days: Mapped[int | None] = mapped_column(Integer, nullable=True) commercial_terms: Mapped[str | None] = mapped_column(Text, nullable=True) # ----- Observaciones y auditoría ----- notes: Mapped[str | None] = mapped_column(Text, nullable=True) # comentarios generales internal_notes: Mapped[str | None] = mapped_column(Text, nullable=True) owner_user_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) created_by: Mapped[str | None] = mapped_column(String(64), nullable=True) updated_by: Mapped[str | None] = mapped_column(String(64), nullable=True)