feaature/catalogo-empresa-mejoras
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
"""company certification refactor + legacy ui fields
|
||||
|
||||
Revision ID: 6a7b8c9d0e1f
|
||||
Revises: 1f4ba75eaa35
|
||||
Create Date: 2026-04-23 11:10:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "6a7b8c9d0e1f"
|
||||
down_revision: Union[str, Sequence[str], None] = "1f4ba75eaa35"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
SCHEMA = "a76"
|
||||
TABLE = "company_certification"
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Rename Annex 31 columns to Annex 30 names.
|
||||
op.alter_column(TABLE, "annex31_certification_date", new_column_name="annex30_certification_date", schema=SCHEMA)
|
||||
op.alter_column(TABLE, "annex31_certification_number", new_column_name="annex30_certification_number", schema=SCHEMA)
|
||||
op.alter_column(TABLE, "annex31_modality", new_column_name="annex30_modality", schema=SCHEMA)
|
||||
op.alter_column(TABLE, "annex31_company_type", new_column_name="annex30_company_type", schema=SCHEMA)
|
||||
op.alter_column(TABLE, "annex31_renewal_date", new_column_name="annex30_renewal_date", schema=SCHEMA)
|
||||
op.alter_column(TABLE, "annex31_final_certification_date", new_column_name="annex30_final_certification_date", schema=SCHEMA)
|
||||
|
||||
op.alter_column(
|
||||
TABLE,
|
||||
"annex30_modality",
|
||||
existing_type=sa.String(length=50),
|
||||
type_=sa.String(length=3),
|
||||
schema=SCHEMA,
|
||||
)
|
||||
|
||||
# Convert legacy integer dates (YYYYMMDD or 0) to DATE.
|
||||
date_columns = [
|
||||
"certified_company_start_date",
|
||||
"certified_company_end_date",
|
||||
"annex30_certification_date",
|
||||
"annex30_renewal_date",
|
||||
"annex30_final_certification_date",
|
||||
]
|
||||
for col in date_columns:
|
||||
op.alter_column(
|
||||
TABLE,
|
||||
col,
|
||||
existing_type=sa.Integer(),
|
||||
type_=sa.Date(),
|
||||
schema=SCHEMA,
|
||||
postgresql_using=(
|
||||
f"CASE "
|
||||
f"WHEN {col} IS NULL OR {col} = 0 THEN NULL "
|
||||
f"WHEN length({col}::text) = 8 THEN to_date({col}::text, 'YYYYMMDD') "
|
||||
f"ELSE NULL END"
|
||||
),
|
||||
)
|
||||
|
||||
# Convert legacy S/N and 0/1 flags to native booleans.
|
||||
op.alter_column(
|
||||
TABLE,
|
||||
"is_certified_company",
|
||||
existing_type=sa.String(length=1),
|
||||
type_=sa.Boolean(),
|
||||
schema=SCHEMA,
|
||||
postgresql_using=(
|
||||
"CASE "
|
||||
"WHEN is_certified_company IS NULL THEN NULL "
|
||||
"WHEN upper(trim(is_certified_company)) IN ('S','SI','1','T','TRUE','Y','YES') THEN true "
|
||||
"WHEN upper(trim(is_certified_company)) IN ('N','NO','0','F','FALSE') THEN false "
|
||||
"ELSE NULL END"
|
||||
),
|
||||
)
|
||||
|
||||
op.alter_column(
|
||||
TABLE,
|
||||
"is_oea_company",
|
||||
existing_type=sa.SmallInteger(),
|
||||
type_=sa.Boolean(),
|
||||
schema=SCHEMA,
|
||||
postgresql_using="CASE WHEN is_oea_company IS NULL THEN NULL WHEN is_oea_company = 1 THEN true ELSE false END",
|
||||
)
|
||||
|
||||
op.alter_column(
|
||||
TABLE,
|
||||
"neec_company",
|
||||
existing_type=sa.Integer(),
|
||||
type_=sa.Boolean(),
|
||||
schema=SCHEMA,
|
||||
postgresql_using="CASE WHEN neec_company IS NULL THEN NULL WHEN neec_company = 1 THEN true ELSE false END",
|
||||
)
|
||||
|
||||
# Additional legacy UI fields.
|
||||
op.add_column(
|
||||
"company",
|
||||
sa.Column("fiscal_deposit", sa.Boolean(), nullable=True, server_default=sa.text("false")),
|
||||
schema=SCHEMA,
|
||||
)
|
||||
op.add_column(
|
||||
"company",
|
||||
sa.Column(
|
||||
"generate_barcodes_with_fiel",
|
||||
sa.Boolean(),
|
||||
nullable=True,
|
||||
server_default=sa.text("false"),
|
||||
),
|
||||
schema=SCHEMA,
|
||||
)
|
||||
|
||||
op.add_column(
|
||||
"company_certification",
|
||||
sa.Column("is_seciit_company", sa.Boolean(), nullable=True, server_default=sa.text("false")),
|
||||
schema=SCHEMA,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Remove additional legacy UI fields.
|
||||
op.drop_column("company_certification", "is_seciit_company", schema=SCHEMA)
|
||||
op.drop_column("company", "generate_barcodes_with_fiel", schema=SCHEMA)
|
||||
op.drop_column("company", "fiscal_deposit", schema=SCHEMA)
|
||||
|
||||
# Restore booleans to legacy flag formats.
|
||||
op.alter_column(
|
||||
TABLE,
|
||||
"is_certified_company",
|
||||
existing_type=sa.Boolean(),
|
||||
type_=sa.String(length=1),
|
||||
schema=SCHEMA,
|
||||
postgresql_using="CASE WHEN is_certified_company IS NULL THEN NULL WHEN is_certified_company THEN 'S' ELSE 'N' END",
|
||||
)
|
||||
|
||||
op.alter_column(
|
||||
TABLE,
|
||||
"is_oea_company",
|
||||
existing_type=sa.Boolean(),
|
||||
type_=sa.SmallInteger(),
|
||||
schema=SCHEMA,
|
||||
postgresql_using="CASE WHEN is_oea_company IS NULL THEN NULL WHEN is_oea_company THEN 1 ELSE 0 END",
|
||||
)
|
||||
|
||||
op.alter_column(
|
||||
TABLE,
|
||||
"neec_company",
|
||||
existing_type=sa.Boolean(),
|
||||
type_=sa.Integer(),
|
||||
schema=SCHEMA,
|
||||
postgresql_using="CASE WHEN neec_company IS NULL THEN NULL WHEN neec_company THEN 1 ELSE 0 END",
|
||||
)
|
||||
|
||||
# Restore DATE columns to integer YYYYMMDD format.
|
||||
date_columns = [
|
||||
"certified_company_start_date",
|
||||
"certified_company_end_date",
|
||||
"annex30_certification_date",
|
||||
"annex30_renewal_date",
|
||||
"annex30_final_certification_date",
|
||||
]
|
||||
for col in date_columns:
|
||||
op.alter_column(
|
||||
TABLE,
|
||||
col,
|
||||
existing_type=sa.Date(),
|
||||
type_=sa.Integer(),
|
||||
schema=SCHEMA,
|
||||
postgresql_using=f"CASE WHEN {col} IS NULL THEN NULL ELSE to_char({col}, 'YYYYMMDD')::integer END",
|
||||
)
|
||||
|
||||
op.alter_column(
|
||||
TABLE,
|
||||
"annex30_modality",
|
||||
existing_type=sa.String(length=3),
|
||||
type_=sa.String(length=50),
|
||||
schema=SCHEMA,
|
||||
)
|
||||
|
||||
# Rename Annex 30 columns back to Annex 31.
|
||||
op.alter_column(TABLE, "annex30_certification_date", new_column_name="annex31_certification_date", schema=SCHEMA)
|
||||
op.alter_column(TABLE, "annex30_certification_number", new_column_name="annex31_certification_number", schema=SCHEMA)
|
||||
op.alter_column(TABLE, "annex30_modality", new_column_name="annex31_modality", schema=SCHEMA)
|
||||
op.alter_column(TABLE, "annex30_company_type", new_column_name="annex31_company_type", schema=SCHEMA)
|
||||
op.alter_column(TABLE, "annex30_renewal_date", new_column_name="annex31_renewal_date", schema=SCHEMA)
|
||||
op.alter_column(TABLE, "annex30_final_certification_date", new_column_name="annex31_final_certification_date", schema=SCHEMA)
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user