feaature/catalogo-empresa-mejoras

This commit is contained in:
2026-04-23 14:28:56 -06:00
parent 8dce23d624
commit 29c305cd2c
8 changed files with 566 additions and 179 deletions

View File

@@ -3,10 +3,10 @@ DTOs (Data Transfer Objects) para módulo de empresa
Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS
"""
from datetime import datetime
from datetime import date, datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field
from pydantic import BaseModel, ConfigDict, Field, field_validator
class CompanyCreateDTO(BaseModel):
@@ -64,6 +64,8 @@ class CompanyCreateDTO(BaseModel):
)
previous_code: Optional[int] = Field(None, description="Previous code")
is_service_company: Optional[bool] = Field(None, description="Is service company")
fiscal_deposit: Optional[bool] = Field(None, description="Fiscal deposit")
generate_barcodes_with_fiel: Optional[bool] = Field(None, description="Generate barcodes with FIEL")
# Client and subassembly
client_name: Optional[str] = Field(None, max_length=300, description="Client name")
@@ -91,18 +93,19 @@ class CompanyCreateDTO(BaseModel):
sector3: Optional[str] = Field(None, max_length=5)
# Certification (CompanyCertification flattened)
is_certified_company: Optional[str] = Field(None, max_length=1)
is_certified_company: Optional[bool] = None
certified_company_registration: Optional[str] = Field(None, max_length=40)
certified_company_start_date: Optional[int] = None
certified_company_end_date: Optional[int] = None
annex31_certification_date: Optional[int] = None
annex31_certification_number: Optional[str] = Field(None, max_length=50)
annex31_modality: Optional[str] = Field(None, max_length=50)
annex31_company_type: Optional[str] = Field(None, max_length=50)
annex31_renewal_date: Optional[int] = None
annex31_final_certification_date: Optional[int] = None
is_oea_company: Optional[int] = None
neec_company: Optional[int] = None
certified_company_start_date: Optional[date] = None
certified_company_end_date: Optional[date] = None
annex30_certification_date: Optional[date] = None
annex30_certification_number: Optional[str] = Field(None, max_length=50)
annex30_modality: Optional[str] = Field(None, max_length=3)
annex30_company_type: Optional[str] = Field(None, max_length=50)
annex30_renewal_date: Optional[date] = None
annex30_final_certification_date: Optional[date] = None
is_oea_company: Optional[bool] = None
neec_company: Optional[bool] = None
is_seciit_company: Optional[bool] = None
# Addresses (Flattened)
# Main
@@ -223,6 +226,38 @@ class CompanyCreateDTO(BaseModel):
model_config = ConfigDict(from_attributes=True)
@field_validator(
"certified_company_start_date",
"certified_company_end_date",
"annex30_certification_date",
"annex30_renewal_date",
"annex30_final_certification_date",
mode="before",
)
@classmethod
def validate_dates(cls, value):
"""Acepta exclusivamente DD/MM/YYYY para payload string."""
if value in (None, ""):
return None
if isinstance(value, date):
return value
if isinstance(value, str):
try:
return datetime.strptime(value, "%d/%m/%Y").date()
except ValueError as exc:
raise ValueError("Date must be in DD/MM/YYYY format") from exc
raise ValueError("Date must be in DD/MM/YYYY format")
@field_validator("annex30_modality", mode="before")
@classmethod
def validate_annex30_modality(cls, value):
if value in (None, ""):
return None
normalized = str(value).strip().upper()
if normalized not in {"A", "AA", "AAA"}:
raise ValueError("Annex 30 modality must be one of: A, AA, AAA")
return normalized
class CompanyUpdateDTO(CompanyCreateDTO):
"""DTO para actualizar una empresa"""
@@ -259,6 +294,8 @@ class CompanyResponseDTO(BaseModel):
# Configuration
logo: Optional[str] = None
has_express_line: Optional[bool] = None
fiscal_deposit: Optional[bool] = None
generate_barcodes_with_fiel: Optional[bool] = None
order_format_type: Optional[str] = None
previous_code: Optional[int] = None
is_service_company: Optional[bool] = None
@@ -286,18 +323,19 @@ class CompanyResponseDTO(BaseModel):
sector3: Optional[str] = None
# Certification
is_certified_company: Optional[str] = None
is_certified_company: Optional[bool] = None
certified_company_registration: Optional[str] = None
certified_company_start_date: Optional[int] = None
certified_company_end_date: Optional[int] = None
annex31_certification_date: Optional[int] = None
annex31_certification_number: Optional[str] = None
annex31_modality: Optional[str] = None
annex31_company_type: Optional[str] = None
annex31_renewal_date: Optional[int] = None
annex31_final_certification_date: Optional[int] = None
is_oea_company: Optional[int] = None
neec_company: Optional[int] = None
certified_company_start_date: Optional[date] = None
certified_company_end_date: Optional[date] = None
annex30_certification_date: Optional[date] = None
annex30_certification_number: Optional[str] = None
annex30_modality: Optional[str] = None
annex30_company_type: Optional[str] = None
annex30_renewal_date: Optional[date] = None
annex30_final_certification_date: Optional[date] = None
is_seciit_company: Optional[bool] = None
is_oea_company: Optional[bool] = None
neec_company: Optional[bool] = None
# Addresses
# ... (Main, Ind1, Ind2 can be added here if needed for flattened response)

View File

@@ -62,6 +62,8 @@ class Company(Base, TimestampMixin):
# Configuración básica
logo: Mapped[Optional[str]] = mapped_column(String(512))
has_express_line: Mapped[Optional[bool]] = mapped_column(Boolean, default=False, server_default="false")
fiscal_deposit: Mapped[Optional[bool]] = mapped_column(Boolean, default=False, server_default="false")
generate_barcodes_with_fiel: Mapped[Optional[bool]] = mapped_column(Boolean, default=False, server_default="false")
order_format_type: Mapped[Optional[str]] = mapped_column(String(19))
is_service_company: Mapped[Optional[bool]] = mapped_column(Boolean, default=False, server_default="false")
client_name: Mapped[Optional[str]] = mapped_column(String(300))

View File

@@ -119,7 +119,8 @@ class CompanyService:
"prosec", "prosec_authorization", "sector1", "sector2", "sector3",
"manufacturer_id", "broker_company", "responsible", "responsible_name",
"responsible_last_name", "responsible_mother_last_name", "responsible_rfc",
"position", "logo", "has_express_line", "order_format_type",
"position", "logo", "has_express_line", "fiscal_deposit",
"generate_barcodes_with_fiel", "order_format_type",
"is_service_company", "client_name", "subassembly_mode", "previous_code",
"active_labels", "active_fractions", "activate_caat", "trans_interface",
"american_costs", "scaf_readonly", "parts_replacement", "activate_facmexame",
@@ -134,9 +135,9 @@ class CompanyService:
cert_fields = [
"is_certified_company", "certified_company_registration",
"certified_company_start_date", "certified_company_end_date",
"annex31_certification_date", "annex31_certification_number",
"annex31_modality", "annex31_company_type", "annex31_renewal_date",
"annex31_final_certification_date", "is_oea_company", "ctpat_svi",
"annex30_certification_date", "annex30_certification_number",
"annex30_modality", "annex30_company_type", "annex30_renewal_date",
"annex30_final_certification_date", "is_seciit_company", "is_oea_company", "ctpat_svi",
"trusted_exporter_number", "neec_company"
]
return {k: v for k, v in data.items() if k in cert_fields}
@@ -249,9 +250,9 @@ class CompanyService:
cert_fields = [
"is_certified_company", "certified_company_registration",
"certified_company_start_date", "certified_company_end_date",
"annex31_certification_date", "annex31_certification_number",
"annex31_modality", "annex31_company_type", "annex31_renewal_date",
"annex31_final_certification_date", "is_oea_company", "ctpat_svi",
"annex30_certification_date", "annex30_certification_number",
"annex30_modality", "annex30_company_type", "annex30_renewal_date",
"annex30_final_certification_date", "is_seciit_company", "is_oea_company", "ctpat_svi",
"trusted_exporter_number", "neec_company"
]
for field in cert_fields:

View File

@@ -2,7 +2,9 @@
Modelo de certificaciones de empresa
"""
from typing import Optional, TYPE_CHECKING
from sqlalchemy import Integer, String, SmallInteger, ForeignKey, Boolean
from datetime import date
from sqlalchemy import Integer, String, ForeignKey, Boolean, Date
from sqlalchemy.orm import Mapped, mapped_column, relationship
from core.database import Base
from api.v1.common.base_models import TimestampMixin
@@ -26,24 +28,25 @@ class CompanyCertification(Base, TimestampMixin):
)
# Certificación general
is_certified_company: Mapped[Optional[str]] = mapped_column(String(1))
is_certified_company: Mapped[Optional[bool]] = mapped_column(Boolean)
certified_company_registration: Mapped[Optional[str]] = mapped_column(String(40))
certified_company_start_date: Mapped[Optional[int]] = mapped_column(Integer)
certified_company_end_date: Mapped[Optional[int]] = mapped_column(Integer)
certified_company_start_date: Mapped[Optional[date]] = mapped_column(Date)
certified_company_end_date: Mapped[Optional[date]] = mapped_column(Date)
# Anexo 31
annex31_certification_date: Mapped[Optional[int]] = mapped_column(Integer)
annex31_certification_number: Mapped[Optional[str]] = mapped_column(String(50))
annex31_modality: Mapped[Optional[str]] = mapped_column(String(50))
annex31_company_type: Mapped[Optional[str]] = mapped_column(String(50))
annex31_renewal_date: Mapped[Optional[int]] = mapped_column(Integer)
annex31_final_certification_date: Mapped[Optional[int]] = mapped_column(Integer)
# Anexo 30
annex30_certification_date: Mapped[Optional[date]] = mapped_column(Date)
annex30_certification_number: Mapped[Optional[str]] = mapped_column(String(50))
annex30_modality: Mapped[Optional[str]] = mapped_column(String(3))
annex30_company_type: Mapped[Optional[str]] = mapped_column(String(50))
annex30_renewal_date: Mapped[Optional[date]] = mapped_column(Date)
annex30_final_certification_date: Mapped[Optional[date]] = mapped_column(Date)
# Otras certificaciones
is_oea_company: Mapped[Optional[int]] = mapped_column(SmallInteger)
is_seciit_company: Mapped[Optional[bool]] = mapped_column(Boolean)
is_oea_company: Mapped[Optional[bool]] = mapped_column(Boolean)
ctpat_svi: Mapped[Optional[str]] = mapped_column(String(100))
trusted_exporter_number: Mapped[Optional[str]] = mapped_column(String(50))
neec_company: Mapped[Optional[int]] = mapped_column(Integer)
neec_company: Mapped[Optional[bool]] = mapped_column(Boolean)
# Relación inversa
company: Mapped["Company"] = relationship(