feature/refactorizacion-tabla-sector
This commit is contained in:
@@ -16,6 +16,7 @@ from ...audit_log.services.service import AuditService
|
||||
from ..units_of_measure.seed import seed as units_of_measure_seed
|
||||
from ..fractions.historical_tariff_fractions.seed import seed as historical_tariff_fractions_seed
|
||||
from ..fractions.warning_fractions.seed import seed as warning_fractions_seed
|
||||
from ..sectors.seed import seed as sectors_seed
|
||||
from core.context import get_user_context
|
||||
from sqlalchemy import text
|
||||
|
||||
@@ -774,6 +775,21 @@ class CompanyService:
|
||||
"""))
|
||||
db.execute(text("ALTER TABLE public.warning_fractions ENABLE TRIGGER ALL;"))
|
||||
|
||||
# 4. Sectors
|
||||
values_sectors = ", ".join(
|
||||
[
|
||||
f"({format_value(key)}, {format_value(description)}, {str(authorized).upper()}, {tenant_id}, {company_id})"
|
||||
for key, description, authorized in sectors_seed
|
||||
]
|
||||
)
|
||||
|
||||
if values_sectors:
|
||||
db.execute(text(f"""
|
||||
INSERT INTO a76.sectors (key, description, authorized, tenant_id, company_id)
|
||||
VALUES {values_sectors}
|
||||
ON CONFLICT (key, tenant_id, company_id) DO NOTHING;
|
||||
"""))
|
||||
|
||||
def get_companies_by_tenant(self, tenant_id: int) -> List[Company]:
|
||||
"""Get all companies for a tenant"""
|
||||
return (
|
||||
|
||||
@@ -26,6 +26,7 @@ from .doda.routes import router as doda_router
|
||||
from .prevalidators.routes import router as prevalidators_router
|
||||
from .electronic_notices.routes import router as electronic_notices_router
|
||||
from .location.routes import router as location_router
|
||||
from .sectors.routes import router as sectors_router
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -56,3 +57,4 @@ router.include_router(error_catalogs_router)
|
||||
router.include_router(doda_router)
|
||||
router.include_router(prevalidators_router)
|
||||
router.include_router(electronic_notices_router)
|
||||
router.include_router(sectors_router)
|
||||
|
||||
30
backend/api/v1/modules/a76/general_catalogs/sectors/dto.py
Normal file
30
backend/api/v1/modules/a76/general_catalogs/sectors/dto.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class SectorBaseDTO(BaseModel):
|
||||
key: str = Field(..., description="Clave del sector (ej: 'XIX', 'IIa')", max_length=8)
|
||||
description: str = Field(..., description="Descripción del sector", max_length=150)
|
||||
authorized: Optional[bool] = Field(False, description="True = autorizado para PROSEC")
|
||||
|
||||
|
||||
class SectorCreateDTO(SectorBaseDTO):
|
||||
pass
|
||||
|
||||
|
||||
class SectorUpdateDTO(BaseModel):
|
||||
key: Optional[str] = Field(None, max_length=8)
|
||||
description: Optional[str] = Field(None, max_length=150)
|
||||
authorized: Optional[bool] = None
|
||||
|
||||
|
||||
class SectorResponseDTO(SectorBaseDTO):
|
||||
id: int
|
||||
company_id: int
|
||||
tenant_id: int
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -0,0 +1,23 @@
|
||||
from typing import Optional
|
||||
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
from core.database import Base
|
||||
from sqlalchemy import Boolean, Integer, PrimaryKeyConstraint, String, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
|
||||
class Sector(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "sectors" # GSectores
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("id", name="sectors_pkey"),
|
||||
UniqueConstraint("tenant_id", "company_id", "key", name="sectors_key_ukey"),
|
||||
{"schema": "a76", "extend_existing": True},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
key: Mapped[str] = mapped_column(String(8), nullable=False)
|
||||
description: Mapped[str] = mapped_column(String(150), nullable=False)
|
||||
authorized: Mapped[Optional[bool]] = mapped_column(Boolean, default=False, server_default="false")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Sector(id={self.id}, key={self.key}, description={self.description}, authorized={self.authorized})>"
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Routes for managing Sectors (GSectores) — a76 tenant-scoped catalog.
|
||||
"""
|
||||
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
|
||||
from .dto import SectorCreateDTO, SectorResponseDTO, SectorUpdateDTO
|
||||
from .service import SectorService
|
||||
|
||||
router = TenantCRUDRoutes(
|
||||
service=SectorService,
|
||||
create_schema=SectorCreateDTO,
|
||||
update_schema=SectorUpdateDTO,
|
||||
response_schema=SectorResponseDTO,
|
||||
prefix="/sectors",
|
||||
tags=["a76 / sectors"],
|
||||
resource_name="Sector",
|
||||
id_name="sector_id",
|
||||
enable_list=True,
|
||||
enable_filters=True,
|
||||
default_page_size=50,
|
||||
max_page_size=100,
|
||||
).router
|
||||
36
backend/api/v1/modules/a76/general_catalogs/sectors/seed.py
Normal file
36
backend/api/v1/modules/a76/general_catalogs/sectors/seed.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# (key, description, authorized)
|
||||
seed = [
|
||||
("I", "INDUSTRIA ELECTRICA", False),
|
||||
("II", "INDUSTRIA ELECTRONICA", False),
|
||||
("IIa", "PARA LOS BIENES A QUE SE REFIERE LA FRACCION II, INCISO a) o b), DE ARTICULO 4to DE ESTE DECRETO.", False),
|
||||
("IIb", "PARA LOS BIENES A QUE SE REFIERE LA FRACCION II, INCISO b), DE ARTICULO 4to DE ESTE DECRETO.", False),
|
||||
("III", "INDUSTRIA DEL MUEBLE", False),
|
||||
("IV", "INDUSTRIA DEL JUGUETE, JUEGOS DE RECREO Y ARTICULOS DEPORTIVOS", False),
|
||||
("IX", "INDUSTRIA DE MAQUINARIA AGRICOLA", False),
|
||||
("V", "INDUSTRIA DEL CALZADO", False),
|
||||
("VI", "INDUSTRIA MINERA Y METALURGICA", False),
|
||||
("VII", "INDUSTRIA DE BIENES DE CAPITAL", False),
|
||||
("VIII", "INDUSTRIA FOTOGRAFICA", False),
|
||||
("X", "INDUSTRIAS DIVERSAS", False),
|
||||
("XI", "INDUSTRIA QUIMICA", False),
|
||||
("XII", "INDUSTRIAS DE MANUFACTURAS DEL CAUCHO Y PLASTICOS", False),
|
||||
("XIII", "INDUSTRIA SIDERURGICA", False),
|
||||
("XIV", "INDUSTRIA DE PRODUCTOS FARMOQUIMICOS, MEDICAMENTOS Y EQUIPO MEDICO", False),
|
||||
("XIX", "INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES", False),
|
||||
("XIXa", "INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES", False),
|
||||
("XIXb", "INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES", False),
|
||||
("XV", "INDUSTRIA DEL TRANSPORTE, EXCEPTO EL SECTOR DE LA INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES", False),
|
||||
("XVa", "INDUSTRIA DEL TRANSPORTE, EXCEPTO EL SECTOR DE LA INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES.", False),
|
||||
("XVb", "INDUSTRIA DEL TRANSPORTE, EXCEPTO EL SECTOR DE LA INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES.", False),
|
||||
("XVI", "INDUSTRIA DEL PAPEL Y CARTON", False),
|
||||
("XVII", "INDUSTRIA DE LA MADERA", False),
|
||||
("XVIII", "INDUSTRIA DEL CUERO Y PIELES", False),
|
||||
("XX", "INDUSTRIA TEXTIL Y DE LA CONFECCION", False),
|
||||
("XXa", "INDUSTRIA TEXTIL Y DE LA CONFECCION", False),
|
||||
("XXb", "INDUSTRIA TEXTIL Y DE LA CONFECCION", False),
|
||||
("XXc", "INDUSTRIA TEXTIL Y DE LA CONFECCION", False),
|
||||
("XXd", "INDUSTRIA TEXTIL Y DE LA CONFECCION", False),
|
||||
("XXe", "INDUSTRIA TEXTIL Y DE LA CONFECCION", False),
|
||||
("XXI", "INDUSTRIA DE CHOCOLATES, DULCES Y SIMILARES", False),
|
||||
("XXII", "INDUSTRIA DEL CAFE", False),
|
||||
]
|
||||
143
backend/api/v1/modules/a76/general_catalogs/sectors/service.py
Normal file
143
backend/api/v1/modules/a76/general_catalogs/sectors/service.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""
|
||||
Service layer for Sectors (GSectores) — a76 tenant-scoped catalog.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from . import dto, models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SectorService:
|
||||
"""Service for Sector CRUD operations with tenant support"""
|
||||
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[models.Sector], int]:
|
||||
"""Get all sectors for a tenant/company with pagination"""
|
||||
query = db.query(models.Sector).filter(
|
||||
models.Sector.tenant_id == tenant_id,
|
||||
models.Sector.company_id == company_id,
|
||||
)
|
||||
|
||||
if filters:
|
||||
if filters.get("key"):
|
||||
query = query.filter(
|
||||
models.Sector.key.ilike(f"%{filters['key']}%")
|
||||
)
|
||||
if filters.get("description"):
|
||||
query = query.filter(
|
||||
models.Sector.description.ilike(f"%{filters['description']}%")
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
sectors = query.order_by(models.Sector.key).offset(skip).limit(limit).all()
|
||||
|
||||
return sectors, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session, sector_id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[models.Sector]:
|
||||
"""Get sector by ID"""
|
||||
return (
|
||||
db.query(models.Sector)
|
||||
.filter(
|
||||
models.Sector.id == sector_id,
|
||||
models.Sector.tenant_id == tenant_id,
|
||||
models.Sector.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
sector_data: dto.SectorCreateDTO,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> models.Sector:
|
||||
"""Create a new sector"""
|
||||
new_sector = models.Sector(
|
||||
**sector_data.model_dump(), tenant_id=tenant_id, company_id=company_id
|
||||
)
|
||||
db.add(new_sector)
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(new_sector)
|
||||
return new_sector
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"IntegrityError creating sector: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Ya existe un sector con esa clave para esta empresa.",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
sector_id: int,
|
||||
tenant_id: int,
|
||||
sector_data: dto.SectorUpdateDTO,
|
||||
company_id: int,
|
||||
) -> Optional[models.Sector]:
|
||||
"""Update a sector"""
|
||||
sector = SectorService.get_by_id(db, sector_id, tenant_id, company_id)
|
||||
if not sector:
|
||||
return None
|
||||
|
||||
update_data = sector_data.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(sector, field, value)
|
||||
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(sector)
|
||||
return sector
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"IntegrityError updating sector {sector_id}: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Ya existe un sector con esa clave para esta empresa.",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session, sector_id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
"""Delete a sector"""
|
||||
sector = SectorService.get_by_id(db, sector_id, tenant_id, company_id)
|
||||
if not sector:
|
||||
return False
|
||||
|
||||
try:
|
||||
db.delete(sector)
|
||||
db.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"IntegrityError deleting sector {sector_id}: {str(e)}")
|
||||
if "foreign key constraint" in str(e).lower():
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No se puede eliminar el sector porque tiene registros relacionados.",
|
||||
)
|
||||
raise HTTPException(status_code=400, detail="Error al eliminar el sector.")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error deleting sector {sector_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error al eliminar el sector.")
|
||||
@@ -16,7 +16,7 @@ from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMe
|
||||
from api.v1.modules.a76.general_catalogs.packages.models import Package
|
||||
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
|
||||
from api.v1.modules.public.reference_data.countries.models import Country
|
||||
from api.v1.modules.public.reference_data.sectors.models import Sector
|
||||
from api.v1.modules.a76.general_catalogs.sectors.models import Sector
|
||||
from api.v1.modules.public.reference_data.valuation_methods.models import (
|
||||
ValuationMethod,
|
||||
)
|
||||
@@ -314,7 +314,11 @@ def validate_common(
|
||||
)
|
||||
elif fraction_type.strip().upper() == FractionType.PROSEC and sector:
|
||||
sector_db: Sector = (
|
||||
db.query(Sector).filter(Sector.key == sector).scalar()
|
||||
db.query(Sector).filter(
|
||||
Sector.key == sector,
|
||||
Sector.tenant_id == tenant_id,
|
||||
Sector.company_id == company_id,
|
||||
).scalar()
|
||||
)
|
||||
if sector_db:
|
||||
errors.add_error(
|
||||
|
||||
@@ -15,7 +15,7 @@ from api.v1.modules.a76.classes.models import Class
|
||||
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
|
||||
from api.v1.modules.a76.general_catalogs.packages.models import Package
|
||||
from api.v1.modules.public.reference_data.countries.models import Country
|
||||
from api.v1.modules.public.reference_data.sectors.models import Sector
|
||||
from api.v1.modules.a76.general_catalogs.sectors.models import Sector
|
||||
from api.v1.modules.public.reference_data.valuation_methods.models import (
|
||||
ValuationMethod,
|
||||
)
|
||||
@@ -239,7 +239,11 @@ def validate_common(
|
||||
)
|
||||
elif fraction_type.strip().upper() == FractionType.PROSEC and sector:
|
||||
sector_db: Sector = (
|
||||
db.query(Sector).filter(Sector.key == sector).scalar()
|
||||
db.query(Sector).filter(
|
||||
Sector.key == sector,
|
||||
Sector.tenant_id == tenant_id,
|
||||
Sector.company_id == company_id,
|
||||
).scalar()
|
||||
)
|
||||
if sector_db:
|
||||
errors.add_error(
|
||||
|
||||
@@ -895,7 +895,7 @@ def _do_scan_file(job_id: str, model_target: str, config: Optional[str] = None,
|
||||
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
|
||||
from api.v1.modules.a76.general_catalogs.packages.models import Package
|
||||
from api.v1.modules.public.reference_data.countries.models import Country
|
||||
from api.v1.modules.public.reference_data.sectors.models import Sector
|
||||
from api.v1.modules.a76.general_catalogs.sectors.models import Sector
|
||||
from api.v1.modules.public.reference_data.valuation_methods.models import ValuationMethod
|
||||
from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod
|
||||
from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import USTariffFraction
|
||||
@@ -1018,7 +1018,11 @@ def _do_scan_file(job_id: str, model_target: str, config: Optional[str] = None,
|
||||
valid_fraction_ame.add((row[0] or "").strip())
|
||||
|
||||
authorized_sectors: Set[str] = set()
|
||||
for row in session.query(Sector.key).filter(Sector.authorized == True).all():
|
||||
for row in session.query(Sector.key).filter(
|
||||
Sector.authorized == True,
|
||||
Sector.tenant_id == tenant_id,
|
||||
Sector.company_id == company_id,
|
||||
).all():
|
||||
if row[0]:
|
||||
authorized_sectors.add((row[0] or "").strip().upper())
|
||||
|
||||
@@ -1151,7 +1155,7 @@ def _do_scan_file(job_id: str, model_target: str, config: Optional[str] = None,
|
||||
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
|
||||
from api.v1.modules.a76.general_catalogs.packages.models import Package
|
||||
from api.v1.modules.public.reference_data.countries.models import Country
|
||||
from api.v1.modules.public.reference_data.sectors.models import Sector
|
||||
from api.v1.modules.a76.general_catalogs.sectors.models import Sector
|
||||
from api.v1.modules.public.reference_data.valuation_methods.models import ValuationMethod
|
||||
from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod
|
||||
from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import USTariffFraction
|
||||
@@ -1277,7 +1281,11 @@ def _do_scan_file(job_id: str, model_target: str, config: Optional[str] = None,
|
||||
valid_fraction_ame.add((row[0] or "").strip())
|
||||
|
||||
authorized_sectors: Set[str] = set()
|
||||
for row in session.query(Sector.key).filter(Sector.authorized == True).all():
|
||||
for row in session.query(Sector.key).filter(
|
||||
Sector.authorized == True,
|
||||
Sector.tenant_id == tenant_id,
|
||||
Sector.company_id == company_id,
|
||||
).all():
|
||||
if row[0]:
|
||||
authorized_sectors.add((row[0] or "").strip().upper())
|
||||
|
||||
@@ -1679,7 +1687,7 @@ def _do_scan_file(job_id: str, model_target: str, config: Optional[str] = None,
|
||||
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
|
||||
from api.v1.modules.a76.general_catalogs.packages.models import Package
|
||||
from api.v1.modules.public.reference_data.countries.models import Country
|
||||
from api.v1.modules.public.reference_data.sectors.models import Sector
|
||||
from api.v1.modules.a76.general_catalogs.sectors.models import Sector
|
||||
from api.v1.modules.public.reference_data.valuation_methods.models import ValuationMethod
|
||||
from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod
|
||||
from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import USTariffFraction
|
||||
@@ -1804,7 +1812,11 @@ def _do_scan_file(job_id: str, model_target: str, config: Optional[str] = None,
|
||||
valid_fraction_ame.add((row[0] or "").strip())
|
||||
|
||||
authorized_sectors: Set[str] = set()
|
||||
for row in session.query(Sector.key).filter(Sector.authorized == True).all():
|
||||
for row in session.query(Sector.key).filter(
|
||||
Sector.authorized == True,
|
||||
Sector.tenant_id == tenant_id,
|
||||
Sector.company_id == company_id,
|
||||
).all():
|
||||
if row[0]:
|
||||
authorized_sectors.add((row[0] or "").strip().upper())
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ def load_parts_fk_sets(
|
||||
from api.v1.modules.public.reference_data.currency_types.models import CurrencyType
|
||||
from api.v1.modules.a76.general_catalogs.company.models import Company
|
||||
from api.v1.modules.public.reference_data.countries.models import Country
|
||||
from api.v1.modules.public.reference_data.sectors.models import Sector
|
||||
from api.v1.modules.a76.general_catalogs.sectors.models import Sector
|
||||
from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction
|
||||
from api.v1.modules.a76.general_catalogs.fractions.historical_tariff_fractions.models import (
|
||||
HistoricalTariffFraction,
|
||||
@@ -102,7 +102,11 @@ def load_parts_fk_sets(
|
||||
|
||||
for row in (
|
||||
session.query(Sector.key)
|
||||
.filter(Sector.authorized == True)
|
||||
.filter(
|
||||
Sector.authorized == True,
|
||||
Sector.tenant_id == tenant_id,
|
||||
Sector.company_id == company_id,
|
||||
)
|
||||
.all()
|
||||
):
|
||||
if row[0]:
|
||||
|
||||
@@ -320,12 +320,11 @@ def validate_row_sector(
|
||||
# Formato: solo A-Z/0-9 (sin espacios ni especiales), hasta 8
|
||||
if sector:
|
||||
s = sector.strip().upper()
|
||||
# Alinear con modelo A76: solo dígitos, máx 8
|
||||
if not (1 <= len(s) <= 8) or not s.isdigit():
|
||||
if not (1 <= len(s) <= 8) or not s.isalnum():
|
||||
return {
|
||||
"line": line_num,
|
||||
"col": "SECTOR",
|
||||
"msg": "Error: (Col. P) El Sector contiene caracteres no permitidos. Use solo números (máx. 8 dígitos).",
|
||||
"msg": "Error: (Col. P) El Sector contiene caracteres no permitidos. Use solo letras y números sin espacios (máx. 8 caracteres).",
|
||||
}
|
||||
if pref == "PROSEC":
|
||||
if not sector:
|
||||
|
||||
@@ -6,7 +6,7 @@ from pydantic import BaseModel, Field, ConfigDict
|
||||
# --- SUB-DTO: DATOS ADUANALES (FaData) ---
|
||||
class FaDataDTO(BaseModel):
|
||||
origin_country: Optional[str] = Field(default=None, pattern=r"^[A-Z]{3}$")
|
||||
sector: Optional[str] = Field(default=None, pattern=r"^[0-9]{1,8}$")
|
||||
sector: Optional[str] = Field(default=None, pattern=r"^[A-Za-z0-9]{1,8}$")
|
||||
fraction_type: Optional[Literal["GENERAL", "PROSEC", "ALADI", "TLCS"]] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
Reference in New Issue
Block a user