from decimal import Decimal from sqlalchemy import 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 Account(Base, TenantScopedMixin, TimestampMixin): """Catálogo de Clientes / Prospectos. Un mismo registro puede ser Cliente o Prospecto (``record_type``). Concentra datos generales, comerciales y fiscales; las direcciones, contactos y documentos viven en tablas relacionadas (crm.addresses, crm.contacts, crm.documents). """ __tablename__ = "accounts" __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) # nombre comercial rfc: Mapped[str | None] = mapped_column(String(13), nullable=True, index=True) curp: Mapped[str | None] = mapped_column(String(18), nullable=True) # persona física # Tipo de registro: cliente | prospecto record_type: Mapped[str] = mapped_column(String(20), nullable=False, server_default=text("'cliente'"), index=True) # Tipo de persona: fisica | moral person_type: Mapped[str | None] = mapped_column(String(10), nullable=True) industry: Mapped[str | None] = mapped_column(String(120), nullable=True) # giro / industria # Tipo operativo (immex | agencia_aduanal | importador | exportador | transportista | otro) account_type: Mapped[str | None] = mapped_column(String(40), nullable=True) # Estatus: active | inactive status: Mapped[str] = mapped_column(String(20), nullable=False, server_default=text("'active'")) # ----- Información comercial ----- # Clasificación: importador | exportador | ambos commercial_classification: Mapped[str | None] = mapped_column(String(20), nullable=True) # Medio de contacto preferido: llamada | correo | videoconferencia | whatsapp | otro preferred_contact_method: Mapped[str | None] = mapped_column(String(20), nullable=True) # Texto libre cuando el medio de contacto es "otro" preferred_contact_other: Mapped[str | None] = mapped_column(String(120), nullable=True) language: Mapped[str | None] = mapped_column(String(40), 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) commercial_observations: Mapped[str | None] = mapped_column(Text, nullable=True) # observaciones generales # ----- Información fiscal ----- tax_regime: Mapped[str | None] = mapped_column(String(120), nullable=True) # régimen fiscal cfdi_use: Mapped[str | None] = mapped_column(String(60), nullable=True) # uso de CFDI payment_method: Mapped[str | None] = mapped_column(String(60), nullable=True) # método de pago payment_form: Mapped[str | None] = mapped_column(String(60), nullable=True) # forma de pago currency: Mapped[str | None] = mapped_column(String(3), nullable=True) # moneda 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) # condiciones comerciales # ----- Datos aduaneros / ubicación rápida ----- patente_aduanal: Mapped[str | None] = mapped_column(String(20), nullable=True) # Ubicación de referencia (el detalle vive en crm.addresses) address: Mapped[str | None] = mapped_column(Text, nullable=True) city: Mapped[str | None] = mapped_column(String(120), nullable=True) state: Mapped[str | None] = mapped_column(String(120), nullable=True) country: Mapped[str | None] = mapped_column(String(2), nullable=True, server_default=text("'MX'")) # ----- 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) # notas internas owner_user_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) # vendedor created_by: Mapped[str | None] = mapped_column(String(64), nullable=True) updated_by: Mapped[str | None] = mapped_column(String(64), nullable=True)