"""Esquemas de los datos fiscales del emisor.""" import re from datetime import datetime from pydantic import BaseModel, ConfigDict, Field, field_validator from ..catalogs.dto import TaxRegimeResponse # RFC de persona moral (3 letras) o física (4 letras) + fecha + homoclave. RFC_PATTERN = re.compile(r"^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$") ZIP_PATTERN = re.compile(r"^\d{5}$") class IssuerSettingsInput(BaseModel): """Alta o actualización de los datos fiscales del emisor.""" legal_name: str = Field(..., min_length=1, max_length=255, description="Razón social") rfc: str = Field(..., max_length=13, description="RFC del emisor") tax_regime_id: int = Field(..., description="Régimen fiscal (c_RegimenFiscal)") zip_code: str | None = Field(None, max_length=5, description="CP del lugar de expedición") # mode="before": la normalización corre antes que el max_length del campo, para que # un RFC con espacios de sobra no se rechace por longitud antes de limpiarlo. @field_validator("rfc", mode="before") @classmethod def _validate_rfc(cls, value: str) -> str: """Normaliza a mayúsculas sin espacios y valida el formato oficial del RFC.""" if not isinstance(value, str): raise ValueError("El RFC debe ser texto") normalized = value.replace(" ", "").replace("-", "").upper() if not RFC_PATTERN.match(normalized): raise ValueError("El RFC no tiene un formato válido (ej. XAXX010101000)") return normalized @field_validator("zip_code") @classmethod def _validate_zip(cls, value: str | None) -> str | None: if value is None or value == "": return None normalized = value.strip() if not ZIP_PATTERN.match(normalized): raise ValueError("El código postal debe tener 5 dígitos") return normalized class IssuerSettingsResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int tenant_id: int company_id: int legal_name: str rfc: str tax_regime_id: int tax_regime: TaxRegimeResponse | None = None zip_code: str | None = None updated_by: str | None = None created_at: datetime updated_at: datetime