feat: Implement multi-tenancy support in middleware and security layers

- Enhanced TenantMiddleware to validate tenant information from JWT tokens.
- Added LicenseValidationMiddleware to check tenant licenses before processing requests.
- Updated security utilities to extract tenant information from tokens and validate company access.
- Introduced CompanyStore to manage active company state and handle company switching in the frontend.
- Modified API routes to include company_id in requests for better resource management.
- Improved logging and error handling throughout the middleware and API layers.
- Updated frontend components to reflect changes in company management and selection.
- Added new API route for fetching user's companies with proper authentication handling.
This commit is contained in:
2025-11-11 14:00:56 -06:00
parent e1eb6bbd01
commit 52b8fcd434
242 changed files with 7067 additions and 3274 deletions

View File

@@ -2,6 +2,7 @@ from pydantic import BaseModel, Field
from pydantic import ConfigDict
from typing import Optional
class CodePedimentoRegimenDTO(BaseModel):
id: Optional[int] = None
pedimento_code: str = Field(..., min_length=1, max_length=3)

View File

@@ -1,5 +1,11 @@
from typing import TYPE_CHECKING, Optional
from sqlalchemy import String, Integer, ForeignKey, ForeignKeyConstraint, PrimaryKeyConstraint
from sqlalchemy import (
String,
Integer,
ForeignKey,
ForeignKeyConstraint,
PrimaryKeyConstraint,
)
from sqlalchemy.orm import mapped_column, Mapped, relationship
from core.database import Base
@@ -7,29 +13,36 @@ if TYPE_CHECKING:
from ..pedimento_codes.models import PedimentoCode
from ..pedimento_regimens.models import RegimenPedimento
class CodePedimentoRegimen(Base):
__tablename__ = "code_pedimento_regimens" #GClavePedRegimen
__tablename__ = "code_pedimento_regimens" # GClavePedRegimen
__table_args__ = (
ForeignKeyConstraint(['pedimento_code'], ['public.pedimento_codes.code'], name='fk_codeped'),
ForeignKeyConstraint(['regimen_code'], ['public.pedimento_regimens.code'], name='fk_regimenped'),
PrimaryKeyConstraint('id', name='clave_pedimento_regimens_pkey'),
{"schema": "public"}
ForeignKeyConstraint(
["pedimento_code"], ["public.pedimento_codes.code"], name="fk_codeped"
),
ForeignKeyConstraint(
["regimen_code"], ["public.pedimento_regimens.code"], name="fk_regimenped"
),
PrimaryKeyConstraint("id", name="clave_pedimento_regimens_pkey"),
{"schema": "public"},
)
id: Mapped[int] = mapped_column(Integer)
pedimento_code: Mapped[str] = mapped_column(String(3), nullable=False)
regimen_code: Mapped[Optional[str]] = mapped_column(String(3), nullable=False)
type_code: Mapped[Optional[str]] = mapped_column(String(1)) # si aplica un tipo de relación
type_code: Mapped[Optional[str]] = mapped_column(
String(1)
) # si aplica un tipo de relación
# Relaciones ORM
#GClavePed
pedimento: Mapped['PedimentoCode'] = relationship(
'PedimentoCode', back_populates='regimens'
# GClavePed
pedimento: Mapped["PedimentoCode"] = relationship(
"PedimentoCode", back_populates="regimens"
)
#GRegimenPed
regimen: Mapped[Optional['RegimenPedimento']] = relationship(
'RegimenPedimento', back_populates='claves_pedimento'
# GRegimenPed
regimen: Mapped[Optional["RegimenPedimento"]] = relationship(
"RegimenPedimento", back_populates="claves_pedimento"
)
def __repr__(self):
return f"<ClavePedimentoRegimen(pedimento={self.pedimento_code}, regimen={self.regimen_code}, type={self.type_code})>"
return f"<ClavePedimentoRegimen(pedimento={self.pedimento_code}, regimen={self.regimen_code}, type={self.type_code})>"

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -16,7 +15,7 @@ def list_code_pedimento_regimens(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(CodePedimentoRegimen)
@@ -26,25 +25,27 @@ def list_code_pedimento_regimens(
"items": [CodePedimentoRegimenDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{id}", response_model=CodePedimentoRegimenDTO)
def get_code_pedimento_regimen(
id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
obj = db.query(CodePedimentoRegimen).filter(CodePedimentoRegimen.id == id).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return CodePedimentoRegimenDTO.model_validate(obj)
@router.post("/", response_model=CodePedimentoRegimenDTO, status_code=201)
def create_code_pedimento_regimen(
data: CodePedimentoRegimenDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = CodePedimentoRegimen(**data.model_dump())
db.add(obj)
@@ -52,12 +53,13 @@ def create_code_pedimento_regimen(
db.refresh(obj)
return CodePedimentoRegimenDTO.model_validate(obj)
@router.put("/{id}", response_model=CodePedimentoRegimenDTO)
def update_code_pedimento_regimen(
id: int,
data: CodePedimentoRegimenDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(CodePedimentoRegimen).filter(CodePedimentoRegimen.id == id).first()
if not obj:
@@ -68,11 +70,12 @@ def update_code_pedimento_regimen(
db.refresh(obj)
return CodePedimentoRegimenDTO.model_validate(obj)
@router.delete("/{id}", status_code=204)
def delete_code_pedimento_regimen(
id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(CodePedimentoRegimen).filter(CodePedimentoRegimen.id == id).first()
if not obj:

View File

@@ -33,8 +33,8 @@ seed = [
("H1", "EXD", "E"),
("H8", "EXD", "E"),
("I1", "EXD", "E"),
#("J1", "EXD", "E"),
#("J2", "EXD", "E"),
# ("J1", "EXD", "E"),
# ("J2", "EXD", "E"),
("K1", "EXD", "E"),
("K2", "EXD", "E"),
("K3", "EXD", "E"),
@@ -53,7 +53,7 @@ seed = [
("A1", "IMD", "I"),
("A3", "IMD", "I"),
("C1", "IMD", "I"),
#("C2", "IMD", "I"),
# ("C2", "IMD", "I"),
("C3", "IMD", "I"),
("D1", "IMD", "I"),
("F3", "IMD", "I"),
@@ -78,19 +78,19 @@ seed = [
("V9", "IMD", "I"),
("VF", "IMD", "I"),
("VU", "IMD", "I"),
#("A2", "ITE", "I"),
#("A8", "ITE", "I"),
#("AA", "ITE", "I"),
# ("A2", "ITE", "I"),
# ("A8", "ITE", "I"),
# ("AA", "ITE", "I"),
("AF", "ITE", "I"),
("E1", "ITE", "I"),
("E3", "ITE", "I"),
#("H3", "ITE", "I"),
# ("H3", "ITE", "I"),
("IN", "ITE", "I"),
("R1", "ITE", "I"),
("V1", "ITE", "I"),
("A6", "ITR", "I"),
#("A7", "ITR", "I"),
#("A9", "ITR", "I"),
# ("A7", "ITR", "I"),
# ("A9", "ITR", "I"),
("AD", "ITR", "I"),
("AF", "ITR", "I"),
("AJ", "ITR", "I"),
@@ -104,7 +104,7 @@ seed = [
("BP", "ITR", "I"),
("E2", "ITR", "I"),
("E4", "ITR", "I"),
#("H3", "ITR", "I"),
# ("H3", "ITR", "I"),
("R1", "ITR", "I"),
("V1", "ITR", "I"),
("V4", "ITR", "I"),
@@ -120,5 +120,5 @@ seed = [
("T3", "TRA", "I"),
("T6", "TRA", "E"),
("T7", "TRA", "I"),
("T9", "TRA", "I")
]
("T9", "TRA", "I"),
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_code_pedimento_regimens(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,28 @@ def test_list_code_pedimento_regimens(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_code_pedimento_regimen_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/code-pedimento-regimens/999999", headers=headers)
assert response.status_code == 404
def test_create_code_pedimento_regimen_forbidden():
response = client.post("/code-pedimento-regimens/", json={"id": 999999, "description": "Test"})
response = client.post(
"/code-pedimento-regimens/", json={"id": 999999, "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_update_code_pedimento_regimen_forbidden():
response = client.put("/code-pedimento-regimens/999999", json={"id": 999999, "description": "Test"})
response = client.put(
"/code-pedimento-regimens/999999", json={"id": 999999, "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_delete_code_pedimento_regimen_forbidden():
response = client.delete("/code-pedimento-regimens/999999")
assert response.status_code in (403, 405, 404)

View File

@@ -3,11 +3,13 @@ from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.transport_types.routes import router
from fastapi import FastAPI
@pytest.fixture(scope="session")
def access_token():
# Reemplaza este token por uno válido generado por Keycloak
return "aqui-va-tu-token-valido"
@pytest.fixture(scope="session")
def client():
app = FastAPI()

View File

@@ -1,9 +1,9 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class ContainerDTO(BaseModel):
key: str = Field(..., min_length=1, max_length=3)
description: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -2,15 +2,20 @@ from sqlalchemy import String, PrimaryKeyConstraint
from sqlalchemy.orm import mapped_column, Mapped
from core.database import Base
class Container(Base):
__tablename__ = "containers" #GContenedores
__tablename__ = "containers" # GContenedores
__table_args__ = (
PrimaryKeyConstraint("key", name="containers_pkey"),
{"schema": "public"} # opcional
{"schema": "public"}, # opcional
)
key: Mapped[str] = mapped_column(String(3), nullable=False) # mantiene ceros iniciales
description: Mapped[str] = mapped_column(String(500), nullable=False) # descripción legal en español
key: Mapped[str] = mapped_column(
String(3), nullable=False
) # mantiene ceros iniciales
description: Mapped[str] = mapped_column(
String(500), nullable=False
) # descripción legal en español
def __repr__(self):
return f"<Container(key={self.key}, description={self.description})>"
return f"<Container(key={self.key}, description={self.description})>"

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -11,13 +10,12 @@ from typing import Any, Dict
router = APIRouter(prefix="/containers")
@router.get("/", response_model=Dict[str, Any])
async def list_containers(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(Container)
@@ -27,23 +25,27 @@ async def list_containers(
"items": [ContainerDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{key}", response_model=ContainerDTO)
async def get_container(key: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
async def get_container(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = db.query(Container).filter(Container.key == key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=ContainerDTO, status_code=201)
async def create_container(
data: ContainerDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = Container(**data.dict())
db.add(obj)
@@ -51,13 +53,13 @@ async def create_container(
db.refresh(obj)
return obj
@router.put("/{key}", response_model=ContainerDTO)
async def update_container(
key: str,
data: ContainerDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(Container).filter(Container.key == key).first()
if not obj:
@@ -68,12 +70,12 @@ async def update_container(
db.refresh(obj)
return obj
@router.delete("/{key}", status_code=204)
async def delete_container(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(Container).filter(Container.key == key).first()
if not obj:

View File

@@ -1,13 +1,13 @@
seed = [
("1", "CONTENEDOR ESTANDAR 20' (STANDARD CONTAINER 20')."),
("2", "CONTENEDOR ESTANDAR 40' (STANDARD CONTAINER 40')."),
("3", "CONTENEDOR ESTANDAR DE CUBO ALTO 40' (HIGH CUBE STANDARD CONTAINER 40')."),
("4", "CONTENEDOR TAPA DURA 20 (HARDTOP CONTAINER 20')."),
("5", "CONTENEDOR TAPA DURA 40 (HARDTOP CONTAINER 40')."),
("6", "CONTENEDOR TAPA ABIERTA 20 (OPEN TOP CONTAINER 20')."),
("7", "CONTENEDOR TAPA ABIERTA 40' (OPEN TOP CONTAINER 40')."),
("8", "FLAT 20' (FLAT 20')."),
("9", "FLAT 40' (FLAT 40')."),
("1", "CONTENEDOR ESTANDAR 20' (STANDARD CONTAINER 20')."),
("2", "CONTENEDOR ESTANDAR 40' (STANDARD CONTAINER 40')."),
("3", "CONTENEDOR ESTANDAR DE CUBO ALTO 40' (HIGH CUBE STANDARD CONTAINER 40')."),
("4", "CONTENEDOR TAPA DURA 20 (HARDTOP CONTAINER 20')."),
("5", "CONTENEDOR TAPA DURA 40 (HARDTOP CONTAINER 40')."),
("6", "CONTENEDOR TAPA ABIERTA 20 (OPEN TOP CONTAINER 20')."),
("7", "CONTENEDOR TAPA ABIERTA 40' (OPEN TOP CONTAINER 40')."),
("8", "FLAT 20' (FLAT 20')."),
("9", "FLAT 40' (FLAT 40')."),
("10", "PLATAFORMA 20' (PLATFORM 20')."),
("11", "PLATAFORMA 40' (PLATFORM 40')."),
("12", "CONTENEDOR VENTILADO 20 (VENTILATED CONTAINER 20')."),
@@ -15,9 +15,12 @@ seed = [
("14", "CONTENEDOR TERMICO 40' (INSULATED CONTAINER 40')."),
("15", "CONTENEDOR REFRIGERANTE 20 (REFRIGERATED CONTAINER 20')."),
("16", "CONTENEDOR REFRIGERANTE 40 (REFRIGERATED CONTAINER 40')."),
("17", "CONTENEDOR REFRIGERANTE CUBO ALTO 40 (HIGH CUBE REFRIGERATED CONTAINER 40')."),
(
"17",
"CONTENEDOR REFRIGERANTE CUBO ALTO 40 (HIGH CUBE REFRIGERATED CONTAINER 40').",
),
("18", "CONTENEDOR CARGA A GRANEL 20 (BULK CONTAINER 20')."),
("19", "CONTENEDOR TIPO TANQUE 20 (TANK CONTAINER 20')."),
("19", "CONTENEDOR TIPO TANQUE 20 (TANK CONTAINER 20')."),
("20", "CONTENEDOR ESTANDAR 45' (STANDARD CONTAINER 45')."),
("21", "CONTENEDOR ESTANDAR 48' (STANDARD CONTAINER 48')."),
("22", "CONTENEDOR ESTANDAR 53' (STANDARD CONTAINER 53')."),
@@ -27,7 +30,7 @@ seed = [
("26", "SEMIRREMOLQUE CON RACKS PARA ENVASES DE BEBIDAS."),
("27", "SEMIRREMOLQUE CUELLO DE GANZO."),
("28", "SEMIRREMOLQUE TOLVA CUBIERTO."),
("29", "SEMIRREMOLQUE TOLVA (ABIERTO)."),
("29", "SEMIRREMOLQUE TOLVA (ABIERTO)."),
("30", "AUTO-TOLVA CUBIERTO/DESCARGA NEUMATICA."),
("31", "SEMIRREMOLQUE CHASIS."),
("32", "SEMIRREMOLQUE AUTOCARGABLE (CON SISTEMA DE ELEVACION)."),
@@ -37,7 +40,7 @@ seed = [
("36", "PLATAFORMA DE 28."),
("37", "PLATAFORMA DE 45."),
("38", "PLATAFORMA DE 48."),
("39", "SEMIRREMOLQUE PARA TRANSPORTE DE CABALLOS."),
("39", "SEMIRREMOLQUE PARA TRANSPORTE DE CABALLOS."),
("40", "SEMIRREMOLQUE PARA TRANSPORTE DE GANADO."),
("41", "SEMIRREMOLQUE TANQUE (LIQUIDOS)/SIN CALEFACCION/SIN AISLAR."),
("42", "SEMIRREOLQUE TANQUE (LIQUIDOS)/CON CALEFACCION/SIN AISLAR."),
@@ -47,7 +50,7 @@ seed = [
("46", "SEMIRREMOLQUE TANQUE (GAS)/CON CALEFACCION/SIN AISLAR."),
("47", "SEMIRREMOLQUE TANQUE (GAS)/SIN CALEFACCION/AISLADO."),
("48", "SEMIRREMOLQUE TANQUE (GAS)/CON CALEFACCION/AISLADO."),
("49", "SEMIRREMOLQUE TANQUE (QUIMICOS)/SIN CALEFACCION/SIN AISLAR."),
("49", "SEMIRREMOLQUE TANQUE (QUIMICOS)/SIN CALEFACCION/SIN AISLAR."),
("50", "SEMIRREMOLQUE TANQUE (QUIMICOS)/CON CALEFACCION/SIN AISLAR."),
("51", "SEMIRREMOLQUE TANQUE (QUIMICOS)/SIN CALEFACCION/AISLADO."),
("52", "SEMIRREMOLQUE TANQUE (QUIMICOS)/CON CALEFACCION/AISLADO."),
@@ -68,4 +71,4 @@ seed = [
("67", "CAMIÓN UNITARIO DE TRES EJES"),
("68", "VEHÍCULOS CON CAPACIDAD DE CARGA DE HASTA 3.5. TONELADAS"),
("69", "TRACTOCAMIÓN"),
]
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_containers(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,24 @@ def test_list_containers(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_container_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/containers/invalid_key", headers=headers)
assert response.status_code == 404
def test_create_container_forbidden():
response = client.post("/containers/", json={"key": "TST", "description": "Test"})
assert response.status_code in (403, 405, 404)
def test_update_container_forbidden():
response = client.put("/containers/TST", json={"key": "TST", "description": "Test"})
assert response.status_code in (403, 405, 404)
def test_delete_container_forbidden():
response = client.delete("/containers/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -1,6 +1,7 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class CountryDTO(BaseModel):
m3_key: str = Field(..., min_length=1, max_length=3)
mex_key: str = Field(..., min_length=1, max_length=2)
@@ -9,4 +10,3 @@ class CountryDTO(BaseModel):
description_en: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -2,19 +2,26 @@ from sqlalchemy import String, PrimaryKeyConstraint, Index
from sqlalchemy.orm import mapped_column, Mapped
from core.database import Base
class Country(Base):
__tablename__ = "countries" #GPaises
__tablename__ = "countries" # GPaises
__table_args__ = (
PrimaryKeyConstraint("m3_key", name="countries_pkey"),
Index("ak_country_ame", "ame_key", unique=True),
{"schema": "public"} # opcional
{"schema": "public"}, # opcional
)
m3_key: Mapped[str] = mapped_column(String(3), nullable=False) # clave M3
mex_key: Mapped[str] = mapped_column(String(2), nullable=False) # clave país México
ame_key: Mapped[str] = mapped_column(String(2), nullable=False) # clave país América / regional
description_es: Mapped[str] = mapped_column(String(50), nullable=False) # nombre oficial en español
description_en: Mapped[str] = mapped_column(String(50), nullable=False) # nombre en inglés para UI
m3_key: Mapped[str] = mapped_column(String(3), nullable=False) # clave M3
mex_key: Mapped[str] = mapped_column(String(2), nullable=False) # clave país México
ame_key: Mapped[str] = mapped_column(
String(2), nullable=False
) # clave país América / regional
description_es: Mapped[str] = mapped_column(
String(50), nullable=False
) # nombre oficial en español
description_en: Mapped[str] = mapped_column(
String(50), nullable=False
) # nombre en inglés para UI
def __repr__(self):
return f"<Country(m3_key={self.m3_key}, mex_key={self.mex_key}, ame_key={self.ame_key}, description_es={self.description_es}, description_en={self.description_en})>"

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -11,13 +10,12 @@ from typing import Any, Dict
router = APIRouter(prefix="/countries")
@router.get("/", response_model=Dict[str, Any])
async def list_countries(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(Country)
@@ -27,23 +25,27 @@ async def list_countries(
"items": [CountryDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{m3_key}", response_model=CountryDTO)
async def get_country(m3_key: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
async def get_country(
m3_key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = db.query(Country).filter(Country.m3_key == m3_key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=CountryDTO, status_code=201)
async def create_country(
data: CountryDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = Country(**data.dict())
db.add(obj)
@@ -51,13 +53,13 @@ async def create_country(
db.refresh(obj)
return obj
@router.put("/{m3_key}", response_model=CountryDTO)
async def update_country(
m3_key: str,
data: CountryDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(Country).filter(Country.m3_key == m3_key).first()
if not obj:
@@ -68,12 +70,12 @@ async def update_country(
db.refresh(obj)
return obj
@router.delete("/{m3_key}", status_code=204)
async def delete_country(
m3_key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(Country).filter(Country.m3_key == m3_key).first()
if not obj:

View File

@@ -1,15 +1,45 @@
seed = [
("ABW", "A0", "AW", "Aruba (Territorio Holandes de Ultramar)", "Aruba (Netherlands Territory)"),
("AFG", "A1", "AF", "Afganistan (Emirato Islamico De)", "Afghanistan (Islamic Emirate of)"),
(
"ABW",
"A0",
"AW",
"Aruba (Territorio Holandes de Ultramar)",
"Aruba (Netherlands Territory)",
),
(
"AFG",
"A1",
"AF",
"Afganistan (Emirato Islamico De)",
"Afghanistan (Islamic Emirate of)",
),
("AGO", "A8", "AO", "Angola ( Republica De )", "Angola (People's Republic of )"),
("AIA", "AI", "AI", "Anguila", "Anguilla"),
("ALB", "A2", "AL", "Albania ( Republica De)", "Albania (People's Socialist Republic)"),
(
"ALB",
"A2",
"AL",
"Albania ( Republica De)",
"Albania (People's Socialist Republic)",
),
("AND", "A7", "AD", "Andorra (Principado De)", "Andorra (Principated of)"),
("ANT", "B1", "AN", "Antillas Neerlandesas (Terr. Holandes de Ultramar)", "Antilles Netherlands"),
(
"ANT",
"B1",
"AN",
"Antillas Neerlandesas (Terr. Holandes de Ultramar)",
"Antilles Netherlands",
),
("ARE", "G6", "AE", "Emiratos Arabes Unidos", "United Arab Emirates"),
("ARG", "B4", "AR", "Argentina ( Republica )", "Argentina (Republic of)"),
("ARM", "AM", "AM", "Armenia (Republica De)", "Armenia (Republic of)"),
("ATG", "A9", "AG", "Antigua Y Barbuda (Com. Britanica de Naciones)", "Antigua & Barbuda (Brithish Community)"),
(
"ATG",
"A9",
"AG",
"Antigua Y Barbuda (Com. Britanica de Naciones)",
"Antigua & Barbuda (Brithish Community)",
),
("AUS", "B5", "AU", "Australia ( Comunidad De )", "Australia (Community of)"),
("AUT", "B6", "AT", "Austria ( Republica De )", "Austria (Republic of)"),
("AZE", "AZ", "AZ", "Azerbaijan (Republica Azerbaijani)", "Azerbaijan"),
@@ -17,7 +47,13 @@ seed = [
("BEL", "C2", "BE", "Belgica ( Reino De )", "Belgium (Kingdom of)"),
("BEN", "F9", "BJ", "Benin ( Republica De)", "Benin (People's Republic of)"),
("BFA", "A6", "BF", "Burkina Faso", "Burkina Faso"),
("BGD", "B9", "BD", "Bangladesh ( Republica Popular De )", "Bangladesh (People's Republic of)"),
(
"BGD",
"B9",
"BD",
"Bangladesh ( Republica Popular De )",
"Bangladesh (People's Republic of)",
),
("BGR", "D1", "BG", "Bulgaria ( Republica De )", "Bulgaria (Republic of)"),
("BHR", "B8", "BH", "Bahrein ( Estado De )", "Bahrain (State of)"),
("BHS", "B7", "BS", "Bahamas( Comunidad De Las )", "Bahamas (Community of the )"),
@@ -26,20 +62,50 @@ seed = [
("BLZ", "C3", "BZ", "Belice", "Belize"),
("BMU", "C4", "BM", "Bermudas", "Bermuda"),
("BOL", "C6", "BO", "Bolivia ( Republica De )", "Bolivia (Republic of)"),
("BRA", "C8", "BR", "Brasil (Republica Federativa De)", "Brazil (Federative Republic of)"),
("BRB", "C1", "BB", "Barbados (Comunidad Britanica de Naciones)", "Barbados (Brithish Community of Nations)"),
(
"BRA",
"C8",
"BR",
"Brasil (Republica Federativa De)",
"Brazil (Federative Republic of)",
),
(
"BRB",
"C1",
"BB",
"Barbados (Comunidad Britanica de Naciones)",
"Barbados (Brithish Community of Nations)",
),
("BRN", "C9", "BN", "Brunei (Estado De)(Residencia de Paz)", "Brunei (State of)"),
("BTN", "D3", "BT", "Butan (Reino De )", "Bhutan (Royal Goverment of)"),
("BUR", "BU", "BU", "Burma ( Birmania )", "Burma (Birmany)"),
("BWA", "C7", "BW", "Bostwana ( Republica De )", "Botswana (Republic of)"),
("CAF", "CF", "RB", "Republica Centro Africana", "Central African Republic"),
("CAN", "D9", "CA", "Canada", "Canada"),
("CCK", "E3", "CC", "Cocos ( Keeling, Islas Australianas)", "Cocos Keeling Islands (Australian Island"),
(
"CCK",
"E3",
"CC",
"Cocos ( Keeling, Islas Australianas)",
"Cocos Keeling Islands (Australian Island",
),
("CHE", "U8", "CH", "Suiza (Confederacion)", "Switzerland (Confederation)"),
("CHL", "F6", "CL", "Chile ( Republica De )", "Chile (Republic of)"),
("CHN", "Z3", "CN", "China ( Republica Popular) Derogado", "China (People's Republic of)"),
(
"CHN",
"Z3",
"CN",
"China ( Republica Popular) Derogado",
"China (People's Republic of)",
),
("CIA", "E2", "VA", "Ciudad Del Vaticano ( Estado De La )", "Vatican City State"),
("CIV", "F1", "CT", "Costa de Marfil (Republica De La)", "Ivory Coast (Republic of)"),
(
"CIV",
"F1",
"CT",
"Costa de Marfil (Republica De La)",
"Ivory Coast (Republic of)",
),
("CMR", "D8", "CM", "Camerun ( Republica Del )", "Cameroon (Republic of the)"),
("COG", "E6", "CG", "Congo ( Republica Del )", " Congo (Republic of the)"),
("COK", "E7", "CK", "Cook ( Islas )", "Cook Islands"),
@@ -48,50 +114,134 @@ seed = [
("CPV", "D4", "CV", "Cabo Verde ( Republica De )", "Cape Verde (Republic of)"),
("CRI", "F2", "CR", "Costa Rica ( Republica De )", "Costa Rica (Republic of)"),
("CUB", "F3", "CU", "Cuba ( Republica De )", "Cuba (Republic of)"),
("CUR", "D0", "UR", "Curazao (Terr. Holandes De Ultramar)", "Curazao (Netherlands Territory)"),
(
"CUR",
"D0",
"UR",
"Curazao (Terr. Holandes De Ultramar)",
"Curazao (Netherlands Territory)",
),
("CXI", "N8", "CX", "Navidad ( Christmas ) ( Islas )", "Christmas Islands"),
("CYM", "D6", "KY", "Caiman ( Islas )", "Cayman Islands"),
("CYP", "F8", "CY", "Chipre ( Republica De )", "Cyprus (Island of)"),
("CZE", "CZ", "CZ", "Republica Checa", "Czech Federative Republic"),
("DEU", "A4", "DE", "Alemania ( Republica Federal De )", "Germany (Federal Republic of)"),
(
"DEU",
"A4",
"DE",
"Alemania ( Republica Federal De )",
"Germany (Federal Republic of)",
),
("DJI", "V4", "DJ", "Djibouti ( Republica De )", "Djibouti (Republic of)"),
("DMA", "G2", "DM", "Dominica ( Comunidad De )", "Dominica (Community of)"),
("DNK", "G1", "DK", "Dinamarca ( Reino De )", "Denmark (Kingdom of)"),
("DOM", "S2", "DO", "Republica Dominicana", "Dominican Republic"),
("DSM", "FM", "FM", "Estado Federado De Micronesia", "Micronesia Federated State of"),
("DZA", "B3", "DZ", "Argelia ( Republica Democratica y Popular de) Dero", "Argelia (People's Democratic Republic)"),
(
"DSM",
"FM",
"FM",
"Estado Federado De Micronesia",
"Micronesia Federated State of",
),
(
"DZA",
"B3",
"DZ",
"Argelia ( Republica Democratica y Popular de) Dero",
"Argelia (People's Democratic Republic)",
),
("ECU", "G3", "EC", "Ecuador ( Republica Del)", "Ecuador (Republic of the)"),
("EGY", "G4", "EG", "Egipto ( Republica Arabe De )", "Egypt (Arab Republic of)"),
("EMU", "EU", "EU", "Comunidad Europea", "European Economic Community"),
("ERI", "ER", "ER", "Eritrea (Estado De)", "Eritrea (State of)"),
("ESH", "EH", "EH", "Sahara Occidental (Rep. Arabe Saharavi Dem.)", "Western Sahara (Arab Democratic Rep.)"),
(
"ESH",
"EH",
"EH",
"Sahara Occidental (Rep. Arabe Saharavi Dem.)",
"Western Sahara (Arab Democratic Rep.)",
),
("ESP", "G7", "ES", "España ( Reino De )", "Spain (Kingdom of)"),
("EST", "G0", "EE", "Estonia (Republica De)", "Estonia (Republic of)"),
("ETH", "G9", "ET", "Etiopia ( Republica Democratica Federal)", "Ethiopia (Federal Democratic Republic)"),
(
"ETH",
"G9",
"ET",
"Etiopia ( Republica Democratica Federal)",
"Ethiopia (Federal Democratic Republic)",
),
("FIN", "H4", "FI", "Finlandia ( Republica De )", "Finland (Republic of)"),
("FJI", "H1", "FJ", "Fidji (Republica De )", "Fiji Islands"),
("FLK", "FK", "IV", "Islas Malvinas (R.U.)", "Malvine Islands"),
("FRA", "H5", "FR", "Francia (Republica Francesa)", "France (Republic)"),
("FXA", "TF", "TF", "Territorios Franceses Austriales y Antarticos", "French Territory of Antartic Austral"),
(
"FXA",
"TF",
"TF",
"Territorios Franceses Austriales y Antarticos",
"French Territory of Antartic Austral",
),
("GAB", "H6", "GA", "Gabonesa ( Republica )", "Gabonese Republic"),
("GBR", "R9", "GB", "Reino Unido de la Gran Bretaña e Irlanda del Norte", "United Kingdom (Great Britain, Ireland N"),
(
"GBR",
"R9",
"GB",
"Reino Unido de la Gran Bretaña e Irlanda del Norte",
"United Kingdom (Great Britain, Ireland N",
),
("GEO", "GE", "GE", "Georgia (Republica De)", "Georgia (Republic of)"),
("GHA", "H8", "GH", "Ghana ( Republica De )", "Ghana (Republic of)"),
("GIB", "GI", "GI", "Gibraltar (R.U.)", "Gibraltar (U. K.)"),
("GIN", "I8", "GN", "Guinea ( Republica De )", "Guinea (Republic of)"),
("GLP", "I4", "GP", "Guadalupe (Departamento De)", "Guadeloupe (French Caribean Dependences)"),
(
"GLP",
"I4",
"GP",
"Guadalupe (Departamento De)",
"Guadeloupe (French Caribean Dependences)",
),
("GMB", "H7", "GM", "Gambia ( Republica De La)", "Gambia (Republic of)"),
("GNB", "J1", "GW", "Guinea-Bissau ( Republica De )", "Guinea-Bissau (Republic of)"),
("GNQ", "I9", "GQ", "Guinea Ecuatorial ( Republica De )", "Equatorial Guinea (Republic of)"),
("GRC", "I2", "GR", "Grecia (Republica Helenica)", "Greece (Helenical Republic of)"),
(
"GNB",
"J1",
"GW",
"Guinea-Bissau ( Republica De )",
"Guinea-Bissau (Republic of)",
),
(
"GNQ",
"I9",
"GQ",
"Guinea Ecuatorial ( Republica De )",
"Equatorial Guinea (Republic of)",
),
(
"GRC",
"I2",
"GR",
"Grecia (Republica Helenica)",
"Greece (Helenical Republic of)",
),
("GRD", "I1", "GD", "Granada", "Grenada"),
("GRL", "GL", "GL", "Groenlandia (Dinamarca)", "Greenland (Denmark)"),
("GTM", "I6", "GT", "Guatemala ( Republica De )", "Guatemala (Republic of)"),
("GUF", "I7", "GF", "Guyana Francesa", "French Guyana"),
("GUM", "I5", "GU", "Guam ( E.U.A )", "Guam (U.S.A.)"),
("GUY", "J2", "GY", "Guyana ( Republica Cooperativa De )", "Guyana (Cooperative Republic of)"),
(
"GUY",
"J2",
"GY",
"Guyana ( Republica Cooperativa De )",
"Guyana (Cooperative Republic of)",
),
("GZA", "GZ", "GZ", "Franja De Gaza", "Gaza Strip"),
("HKG", "J6", "HK", "Hong Kong (Region Admiva. Especial de la Rep. )", "Hong Kong (Territory of)"),
(
"HKG",
"J6",
"HK",
"Hong Kong (Region Admiva. Especial de la Rep. )",
"Hong Kong (Territory of)",
),
("HND", "J5", "HN", "Honduras ( Republica De )", "Honduras (Republic of)"),
("HRV", "HR", "HR", "Croacia (Republica De)", "Croatia (Republic of)"),
("HTI", "J3", "HT", "Haiti ( Republica De )", "Haiti (Republic of)"),
@@ -99,13 +249,25 @@ seed = [
("IDN", "J9", "ID", "Indonesia ( Republica De )", "Indonesia (Republic of)"),
("IND", "J8", "IN", "India ( Republica De)", "India (Republic of the)"),
("IRL", "K3", "IE", "Irlanda ( Republica De )", "Ireland (Republic of)"),
("IRN", "K2", "IR", "Iran ( Republica Islamica Del )", "Iran (Islamic Republic of)"),
(
"IRN",
"K2",
"IR",
"Iran ( Republica Islamica Del )",
"Iran (Islamic Republic of)",
),
("IRQ", "K1", "IQ", "Irak ( Republica De )", "Iraq (Republic of)"),
("ISL", "K4", "IS", "Islandia ( Republica De )", "Iceland (Republic of)"),
("ISR", "K5", "IL", "Israel ( Estado De )", "Israel (State of)"),
("ITA", "K6", "IT", "Italia (Republica Italiana)", "Italy (Republic)"),
("JAM", "K7", "JM", "Jamaica", "Jamaica"),
("JOR", "L1", "JO", "Jordania ( Reino Hachemita De )", "Jordan (Hachemite Kingdom of)"),
(
"JOR",
"L1",
"JO",
"Jordania ( Reino Hachemita De )",
"Jordan (Hachemite Kingdom of)",
),
("JPN", "K9", "JP", "Japon", "Japan"),
("KAZ", "KZ", "KZ", "Kazakhstan (Republica de)", "Kazakhstan"),
("KCD", "Z9", "PD", "Paises No Declarados", "Not Declared Countries"),
@@ -113,49 +275,133 @@ seed = [
("KGZ", "KG", "KG", "Kyrgyzstan (Republica Kirgyzia)", "Kyrgyzstan"),
("KHM", "D7", "KH", "Camboya (Reino de)", "Cambodia"),
("KIR", "L0", "KI", "Kiribati (Republica de)", "Kiribati"),
("KNA", "S9", "KN", "San Cristobal Y Nieves (Fed. de)(San Kitts-Nevis)", "St. Christopher - Nevis"),
(
"KNA",
"S9",
"KN",
"San Cristobal Y Nieves (Fed. de)(San Kitts-Nevis)",
"St. Christopher - Nevis",
),
("KOR", "E8", "KR", "Corea (Republica De)(Corea del Sur)", "Korea Republic of"),
("KWT", "L3", "KW", "Kuwait (Estado de)", "kuwait"),
("LAO", "L4", "LA", "Republica Democratica Popular Laos", "Laos (People's Democratic Republic of)"),
(
"LAO",
"L4",
"LA",
"Republica Democratica Popular Laos",
"Laos (People's Democratic Republic of)",
),
("LBN", "L7", "LB", "Libano (Republica de)", "Lebanon"),
("LBR", "L8", "LR", "Liberia ( Republica De )", "Liberia (Republic of)"),
("LBY", "L9", "LY", "Libia (Jamahiriya Libia Araba Pop. Soc.)", "Lybia (Arab Jamahiriya)"),
(
"LBY",
"L9",
"LY",
"Libia (Jamahiriya Libia Araba Pop. Soc.)",
"Lybia (Arab Jamahiriya)",
),
("LCA", "T4", "LC", "Santa Lucia", "Saint Lucia"),
("LHM", "HM", "HM", "Islas Heard Y Mcdonald", "Heard & MacDonald Islands"),
("LIE", "L5", "LI", "Liechtenstein (Principado de)", "Liechtenstein (Principated of)"),
("LKA", "U4", "LK", "Sri Lanka ( Republica Democratica Soc.)", "Sri Lanka (Socialist Democratic Republic"),
(
"LIE",
"L5",
"LI",
"Liechtenstein (Principado de)",
"Liechtenstein (Principated of)",
),
(
"LKA",
"U4",
"LK",
"Sri Lanka ( Republica Democratica Soc.)",
"Sri Lanka (Socialist Democratic Republic",
),
("LSO", "L6", "LS", "Lesotho ( Reino De )", "Lesotho (Kingdom of)"),
("LTU", "Y2", "LT", "Lituania (Republica de)", "Lithuania"),
("LUX", "M0", "LU", "Luxemburgo ( Gran Ducado De)", "Luxembourg (Great Ducated of)"),
(
"LUX",
"M0",
"LU",
"Luxemburgo ( Gran Ducado De)",
"Luxembourg (Great Ducated of)",
),
("LVA", "Y1", "LV", "Letonia (Republica de)", "Latvia"),
("MAC", "M1", "MO", "Macao", "Macau"),
("MAR", "M8", "MR", "Marruecos ( Reino De )", "Morocco (Kingdom of)"),
("MCO", "N0", "MC", "Monaco (Principado De)", "Monaco (Principated of)"),
("MDA", "MD", "MD", "Moldavia (Republica de)", "Moldova"),
("MDG", "M2", "MG", "Madagascar ( Republica De)", "Madagascar (Democratic Republic of)"),
(
"MDG",
"M2",
"MG",
"Madagascar ( Republica De)",
"Madagascar (Democratic Republic of)",
),
("MDV", "M5", "MV", "Maldivas ( Republica De )", "Maldives (Republic of the)"),
("MEX", "N3", "MX", "Mexico (Estados Unidos Mexicanos)", "Mexico"),
("MHL", "MH", "MH", "Islas Marshall", "Marshall Islands"),
("MKD", "MK", "MK", "Macedonia (Antigua Rep. Yugoslava De)", "Macedonia (Old Yugoslavian Republic)"),
(
"MKD",
"MK",
"MK",
"Macedonia (Antigua Rep. Yugoslava De)",
"Macedonia (Old Yugoslavian Republic)",
),
("MLI", "M6", "ML", "Mali ( Republica De )", "Mali (Republic of)"),
("MLT", "M7", "MT", "Malta ( Republica De )", "Malta and Gozo (Republic of)"),
("MMR", "C5", "MM", "Myanmar ( Union De )", "Myammar (Union of)"),
("MNE", "ME", "ME", "Montenegro", ""),
("MNG", "N4", "MN", "Mongolia", "Mongolia (People's Republic of)"),
("MNP", "MP", "IM", "Islas Marianas Septentrionales", "Marianes Septentrional Islands"),
("MOZ", "N6", "MZ", "Mozambique ( Republica De)", "Mozambique (People's Republic of)"),
("MRT", "N2", "RT", "Mauritania ( Republica Islamica De )", "Mauritania (Islamic Republic of)"),
(
"MNP",
"MP",
"IM",
"Islas Marianas Septentrionales",
"Marianes Septentrional Islands",
),
(
"MOZ",
"N6",
"MZ",
"Mozambique ( Republica De)",
"Mozambique (People's Republic of)",
),
(
"MRT",
"N2",
"RT",
"Mauritania ( Republica Islamica De )",
"Mauritania (Islamic Republic of)",
),
("MSR", "N5", "MS", "Monserrat ( Isla )", "Montserrat Island"),
("MTQ", "M9", "MQ", "Martinica (Departamento de) (Francia)", "Martinique (Department of)"),
(
"MTQ",
"M9",
"MQ",
"Martinica (Departamento de) (Francia)",
"Martinique (Department of)",
),
("MUS", "N1", "MU", "Mauricio ( Republica De )", "Mauritius (State of)"),
("MWI", "M4", "MW", "Malawi ( Republica De )", "Malawi (Republic of)"),
("MYS", "M3", "MY", "Malasia", "Malaysia (Federation of)"),
("NAM", "P0", "NA", "Namibia ( Republica De )", "Namibia (Republic of)"),
("NCA", "P7", "TE", "Terr. Frances Ultramar Nueva Caledonia", "French Territory of New Caledonia"),
(
"NCA",
"P7",
"TE",
"Terr. Frances Ultramar Nueva Caledonia",
"French Territory of New Caledonia",
),
("NCL", "NC", "NC", "Nueva Caledonia (Terr.Frances de Ultramar)", "New Caledonia"),
("NER", "P2", "NE", "Niger ( Republica De)", "Niger (Federal Republic of)"),
("NFK", "P5", "NF", "Norfolk ( Isla )", "Norfolk Island"),
("NGA", "P3", "NG", "Nigeria ( Republica Federal De)", "Nigeria (Federal Republic of)"),
(
"NGA",
"P3",
"NG",
"Nigeria ( Republica Federal De)",
"Nigeria (Federal Republic of)",
),
("NIC", "P1", "NI", "Nicaragua ( Republica De )", "Nicaragua (Republic of)"),
("NIU", "P4", "NU", "Nive ( Isla )", "Nive Island"),
("NOR", "P6", "NO", "Noruega ( Reino De )", "Norway (Kingdom of)"),
@@ -163,27 +409,81 @@ seed = [
("NRU", "N7", "NR", "Nauru", "Nauru"),
("NZL", "P9", "NZ", "Nueva Zelandia", "New Zealand"),
("OMN", "Q2", "OM", "Oman (Sultanato De )", "Oman (Sultanate of)"),
("PAK", "Q7", "PK", "Pakistan ( Republica Islamica De )", "Pakistan (Islamic Republic of)"),
(
"PAK",
"Q7",
"PK",
"Pakistan ( Republica Islamica De )",
"Pakistan (Islamic Republic of)",
),
("PAN", "Q8", "PA", "Panama ( Republica De )", "Panama (Republic of)"),
("PCN", "R3", "PN", "Pitcairns ( Islas Dependencia Britanica )", "Pitcairn Island (Brithish Dependence)"),
(
"PCN",
"R3",
"PN",
"Pitcairns ( Islas Dependencia Britanica )",
"Pitcairn Island (Brithish Dependence)",
),
("PER", "R2", "PE", "Peru ( Republica Del )", "Peru (Republic of )"),
("PHL", "H3", "PH", "Filipinas ( Republica De Las )", "Philippines (Republic of the)"),
("PIK", "Q3", "PI", "Pacifico Islas Del ( Admon. E.U.A. )", "Pacific Islands (U.S.A. Administration)"),
(
"PHL",
"H3",
"PH",
"Filipinas ( Republica De Las )",
"Philippines (Republic of the)",
),
(
"PIK",
"Q3",
"PI",
"Pacifico Islas Del ( Admon. E.U.A. )",
"Pacific Islands (U.S.A. Administration)",
),
("PLW", "PW", "PW", "Palau (Republica De)", "Palau (Republic of)"),
("PNG", "P8", "PP", "Papua Nueva Guinea (Edo. Independiente de)", "Papua New Guinea (Independent State of)"),
(
"PNG",
"P8",
"PP",
"Papua Nueva Guinea (Edo. Independiente de)",
"Papua New Guinea (Independent State of)",
),
("POL", "R5", "PL", "Polonia ( Republica De )", "Poland (Republic of)"),
("PRI", "R7", "PR", "Puerto Rico (Edo.Libre Asociado de la Com. de) Der", "Puerto Rico (Free Asociated State of)"),
("PRK", "E9", "KP", "Corea ( Rep. Pop. Dem.de)(Corea del Norte)", "Korea (North)(People's Democratic Rep.of"),
(
"PRI",
"R7",
"PR",
"Puerto Rico (Edo.Libre Asociado de la Com. de) Der",
"Puerto Rico (Free Asociated State of)",
),
(
"PRK",
"E9",
"KP",
"Corea ( Rep. Pop. Dem.de)(Corea del Norte)",
"Korea (North)(People's Democratic Rep.of",
),
("PRT", "R6", "PT", "Portugal (Republica Portuguesa)", "Portugal (Republic of)"),
("PRY", "R1", "PY", "Paraguay ( Republica Del )", "Paraguay (Republic of)"),
("PSE", "PS", "PS", "Palestina", ""),
("PTY", "Z2", "ZO", "Zona Del Canal De Panama", "Zone of the Panama's Channel"),
("PYF", "R4", "PF", "Polinesia Francesa", "French Polynesia"),
("QAT", "R8", "QA", "Qatar ( Estado De )", "Qatar (State of)"),
("REU", "S3", "RE", "Reunion (Departamento de la) ( Francia)", "Reunion Islands (French Department)"),
(
"REU",
"S3",
"RE",
"Reunion (Departamento de la) ( Francia)",
"Reunion Islands (French Department)",
),
("RKE", "E1", "RK", "Canal Islas del ( Islas Normandas )", "Channel Islands"),
("ROM", "S5", "RO", "Rumania", "Romania (Republic of)"),
("RUH", "NT", "NT", "Zona Neutral Iraq-Arabia Saudita", "Neutral Zone of Iraq - Saudi Arabia"),
(
"RUH",
"NT",
"NT",
"Zona Neutral Iraq-Arabia Saudita",
"Neutral Zone of Iraq - Saudi Arabia",
),
("RUS", "RU", "RU", "Rusia (Federacion Rusa)", "Russia (Federation)"),
("RWA", "S6", "RW", "Republica Ruandesa", "Rwanda"),
("SAU", "B2", "SA", "Arabia Saudita ( Reino De )", "Saudi Arabia (Kingdom of)"),
@@ -191,21 +491,51 @@ seed = [
("SEN", "T6", "SN", "Senegal ( Republica Del )", "Senegal (Republic of the)"),
("SGP", "U1", "SG", "Singapur ( Republica De )", "Singapore (Republic of)"),
("SHN", "T3", "SH", "Santa Elena", "St. Helena"),
("SJM", "SJ", "SJ", "Islas Svalbard Y Jan Mayen (Noruega)", "Svalbard & Jan Mayen Islands"),
("SLB", "SB", "SB", "Islas Salomon (Com. Britanica de Naciones)", "Solomon Islands (Brithish Community)"),
(
"SJM",
"SJ",
"SJ",
"Islas Svalbard Y Jan Mayen (Noruega)",
"Svalbard & Jan Mayen Islands",
),
(
"SLB",
"SB",
"SB",
"Islas Salomon (Com. Britanica de Naciones)",
"Solomon Islands (Brithish Community)",
),
("SLE", "T8", "SL", "Sierra Leona ( Republica De )", "Sierra Leone (Republic of)"),
("SLV", "G5", "SV", "El Salvador ( Republica De )", "El Salvador (Republic of)"),
("SMR", "T0", "SM", "San Marino (Serenisima Republica De)", "San Marino (Republic of"),
(
"SMR",
"T0",
"SM",
"San Marino (Serenisima Republica De)",
"San Marino (Republic of",
),
("SOM", "U3", "SO", "Somalia", "Somalia (Democratic Republic of)"),
("SPM", "T1", "PM", "San Pedro Y Miquelon", "St. Pierre and Miquelon"),
("SRB", "RS", "RS", "Republica de Serbia", ""),
("STP", "T5", "ST", "Santo Tome Y Principe (Rep. Democratica de)", "Sao Tome and Principe (Dem. Rep.)"),
(
"STP",
"T5",
"ST",
"Santo Tome Y Principe (Rep. Democratica de)",
"Sao Tome and Principe (Dem. Rep.)",
),
("SUR", "U9", "SR", "Suriname ( Republica De )", "Surinam (Republic of)"),
("SVK", "SK", "SK", "Republica Eslovaca", "Slovakia (Republic)"),
("SVN", "SI", "SI", "Eslovenia (Republica De)", "Slovenia (Republic of)"),
("SWE", "U7", "SE", "Suecia ( Reino De )", "Sweden (Kingdom of)"),
("SWZ", "V0", "SZ", "Swazilandia ( Reino De )", "Swaziland (Kingdom of)"),
("SYC", "T7", "SC", "Seychelles (Republica De Las)", "Seychelles (Republic of the)"),
(
"SYC",
"T7",
"SC",
"Seychelles (Republica De Las)",
"Seychelles (Republic of the)",
),
("SYR", "U2", "SY", "Siria ( Republica Arabe )", "Syrian Arab Republic"),
("TCA", "W3", "TC", "Turcas Y Caicos ( Islas )", "Turks and Caicos Islands"),
("TCD", "F4", "TD", "Chad ( Republica De )", "Chad (Republic of)"),
@@ -216,30 +546,102 @@ seed = [
("TKM", "TM", "TM", "Turkmenistan (Republica De)", "Turkmenistan (Republic of)"),
("TMP", "TP", "TP", "Timor Oriental", "East Timor"),
("TON", "TO", "TO", "Tonga (Reino De)", "Tonga (Kingdom of)"),
("TTO", "W1", "TT", "Trinidad Y Tobago ( Republica De )", "Trinidad and Tobago (Republic of)"),
(
"TTO",
"W1",
"TT",
"Trinidad Y Tobago ( Republica De )",
"Trinidad and Tobago (Republic of)",
),
("TUN", "W2", "TN", "Tunez ( Republica De )", "Tunisia (Republic of)"),
("TUR", "W4", "TR", "Turquia ( Republica De )", "Turkey (Republic of)"),
("TUV", "TV", "TV", "Tuvalu (Comunidad Britanica de Naciones)", "Tuvalu (Brithish Community of Nations)"),
(
"TUV",
"TV",
"TV",
"Tuvalu (Comunidad Britanica de Naciones)",
"Tuvalu (Brithish Community of Nations)",
),
("TWN", "F7", "TW", "Taiwan (Republica de China)", "Taiwan"),
("TZA", "V2", "TZ", "Tanzania ( Republica Unida De )", "Tanzania United Republic of"),
(
"TZA",
"V2",
"TZ",
"Tanzania ( Republica Unida De )",
"Tanzania United Republic of",
),
("UGA", "W5", "UG", "Uganda ( Republica De )", "Uganda (Republic of)"),
("UKR", "UA", "UA", "Ucrania", "Ukraine"),
("URY", "W7", "UY", "Uruguay ( Republica Oriental Del )", "Uruguay (Eastern Republic of the)"),
(
"URY",
"W7",
"UY",
"Uruguay ( Republica Oriental Del )",
"Uruguay (Eastern Republic of the)",
),
("USA", "G8", "US", "Estados Unidos de America", "United States of America"),
("UZB", "Y4", "UZ", "Uzbejistan (Republica de)", "Uzbekistan (Republic)"),
("VCT", "T2", "VC", "San Vicente Y Las Granadinas", "St. Vincent and the Grenadines"),
(
"VCT",
"T2",
"VC",
"San Vicente Y Las Granadinas",
"St. Vincent and the Grenadines",
),
("VEN", "W8", "VE", "Venezuela ( Republica De )", "Venezuela (Republic of)"),
("VGB", "X2", "VG", "Virgenes Islas ( Britanicas )", "Virgin Islands (British)"),
("VIR", "X3", "VI", "Virgenes Islas ( Norteamericanas )", "Virgin Islands (American)"),
("VNM", "W9", "VN", "Vietnam ( Republica Socialista De )", "Vietnam (Socialist Republic of)"),
(
"VIR",
"X3",
"VI",
"Virgenes Islas ( Norteamericanas )",
"Virgin Islands (American)",
),
(
"VNM",
"W9",
"VN",
"Vietnam ( Republica Socialista De )",
"Vietnam (Socialist Republic of)",
),
("VUT", "Q1", "VU", "Vanuatu", "Vanuatu"),
("WLF", "WF", "WF", "Islas Wallis Y Futuna", "Wallis & Futuna Islands"),
("WSM", "S8", "WS", "Samoa (Estado Independiente de)", "Western Samoa (Independent State)"),
("XCH", "V3", "IO", "Territorios Britanicos Del Oceano Indico", "Brithish Territory of the Indic Ocean"),
(
"WSM",
"S8",
"WS",
"Samoa (Estado Independiente de)",
"Western Samoa (Independent State)",
),
(
"XCH",
"V3",
"IO",
"Territorios Britanicos Del Oceano Indico",
"Brithish Territory of the Indic Ocean",
),
("YEM", "YE", "YE", "Yemen (Republica De)", "Yemen (Republic of)"),
("YUG", "X8", "YU", "Yugoslavia (Republica Federal de)", "Yugoslavia (Federal Republic of)"),
("ZAF", "U5", "ZA", "Sudafrica ( Republica De ) Derogado", "South Africa (Republic of)"),
(
"YUG",
"X8",
"YU",
"Yugoslavia (Republica Federal de)",
"Yugoslavia (Federal Republic of)",
),
(
"ZAF",
"U5",
"ZA",
"Sudafrica ( Republica De ) Derogado",
"South Africa (Republic of)",
),
("ZMB", "Z1", "ZM", "Zambia ( Republica De )", "Zambia (Republic of)"),
("ZWE", "S4", "ZW", "Zimbabwe ( Republica De )", "Zimbabwe (Republic of)"),
("ZYA", "J4", "NL", "Paises Bajos ( Reino De Los )(Holanda)", "Netherlands (Kingdom of)(Holand)"),
]
(
"ZYA",
"J4",
"NL",
"Paises Bajos ( Reino De Los )(Holanda)",
"Netherlands (Kingdom of)(Holand)",
),
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_countries(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,26 @@ def test_list_countries(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_country_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/countries/invalid_key", headers=headers)
assert response.status_code == 404
def test_create_country_forbidden():
response = client.post("/countries/", json={"m3_key": "TST", "description": "Test"})
assert response.status_code in (403, 405, 404)
def test_update_country_forbidden():
response = client.put("/countries/TST", json={"m3_key": "TST", "description": "Test"})
response = client.put(
"/countries/TST", json={"m3_key": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_delete_country_forbidden():
response = client.delete("/countries/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -1,10 +1,10 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class CurrencyTypeDTO(BaseModel):
code: str = Field(..., min_length=1, max_length=3)
currency_name: str
country_description: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -4,15 +4,21 @@ from core.database import Base
class CurrencyType(Base):
__tablename__ = "currency_types" #GTiposMoneda
__tablename__ = "currency_types" # GTiposMoneda
__table_args__ = (
PrimaryKeyConstraint("code", name="currency_types_pkey"),
{"schema": "public"} # opcional
{"schema": "public"}, # opcional
)
code: Mapped[str] = mapped_column(String(3), nullable=False) # código ISO o clave de moneda
currency_name: Mapped[str] = mapped_column(String(15), nullable=False) # nombre de la moneda (por ejemplo: Peso, Dollar)
country_description: Mapped[str] = mapped_column(String(50)) # país asociado o descripción del país
code: Mapped[str] = mapped_column(
String(3), nullable=False
) # código ISO o clave de moneda
currency_name: Mapped[str] = mapped_column(
String(15), nullable=False
) # nombre de la moneda (por ejemplo: Peso, Dollar)
country_description: Mapped[str] = mapped_column(
String(50)
) # país asociado o descripción del país
def __repr__(self):
return f"<CurrencyType(code={self.code}, currency_name={self.currency_name}, country_description={self.country_description})>"

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -11,13 +10,12 @@ from typing import Any, Dict
router = APIRouter(prefix="/currency-types")
@router.get("/", response_model=Dict[str, Any])
async def list_currency_types(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(CurrencyType)
@@ -27,23 +25,27 @@ async def list_currency_types(
"items": [CurrencyTypeDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{code}", response_model=CurrencyTypeDTO)
async def get_currency_type(code: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
async def get_currency_type(
code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = db.query(CurrencyType).filter(CurrencyType.code == code).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=CurrencyTypeDTO, status_code=201)
async def create_currency_type(
data: CurrencyTypeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = CurrencyType(**data.dict())
db.add(obj)
@@ -51,13 +53,13 @@ async def create_currency_type(
db.refresh(obj)
return obj
@router.put("/{code}", response_model=CurrencyTypeDTO)
async def update_currency_type(
code: str,
data: CurrencyTypeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(CurrencyType).filter(CurrencyType.code == code).first()
if not obj:
@@ -68,12 +70,12 @@ async def update_currency_type(
db.refresh(obj)
return obj
@router.delete("/{code}", status_code=204)
async def delete_currency_type(
code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(CurrencyType).filter(CurrencyType.code == code).first()
if not obj:

View File

@@ -93,4 +93,4 @@ seed = [
("YUD", "DINAR", "YUGOSLAVIA"),
("ZAR", "RAND", "UNION SUDAFRICANA"),
("ZRZ", "FRANCO", "REPUBLICA DEMOCRATICA DEL CONGO"),
]
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_currency_types(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,28 @@ def test_list_currency_types(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_currency_type_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/currency-types/invalid_code", headers=headers)
assert response.status_code == 404
def test_create_currency_type_forbidden():
response = client.post("/currency-types/", json={"code": "TST", "description": "Test"})
response = client.post(
"/currency-types/", json={"code": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_update_currency_type_forbidden():
response = client.put("/currency-types/TST", json={"code": "TST", "description": "Test"})
response = client.put(
"/currency-types/TST", json={"code": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_delete_currency_type_forbidden():
response = client.delete("/currency-types/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -1,9 +1,9 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class CustomsSectionDTO(BaseModel):
customs_code: str = Field(..., min_length=1, max_length=3)
section_name: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -2,15 +2,16 @@ from sqlalchemy import String, PrimaryKeyConstraint
from sqlalchemy.orm import mapped_column
from core.database import Base
class CustomsSection(Base):
__tablename__ = "customs_sections" #GAduanaSec
__tablename__ = "customs_sections" # GAduanaSec
__table_args__ = (
PrimaryKeyConstraint("customs_code", name="customs_code_pkey"),
{"schema": "public"}
{"schema": "public"},
)
customs_code = mapped_column(String(3), nullable=False)
section_name = mapped_column(String(255), nullable=False)
section_name = mapped_column(String(255), nullable=False)
def __repr__(self):
return f"<CustomsSection(code={self.customs_code}, name={self.section_name })>"
return f"<CustomsSection(code={self.customs_code}, name={self.section_name })>"

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -16,7 +15,7 @@ def list_customs_sections(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(CustomsSection)
@@ -26,21 +25,31 @@ def list_customs_sections(
"items": [CustomsSectionDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{customs_code}", response_model=CustomsSectionDTO)
def get_customs_section(customs_code: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
obj = db.query(CustomsSection).filter(CustomsSection.customs_code == customs_code).first()
def get_customs_section(
customs_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = (
db.query(CustomsSection)
.filter(CustomsSection.customs_code == customs_code)
.first()
)
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=CustomsSectionDTO, status_code=201)
def create_customs_section(
data: CustomsSectionDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = CustomsSection(**data.dict())
db.add(obj)
@@ -48,14 +57,19 @@ def create_customs_section(
db.refresh(obj)
return obj
@router.put("/{customs_code}", response_model=CustomsSectionDTO)
def update_customs_section(
customs_code: str,
data: CustomsSectionDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(CustomsSection).filter(CustomsSection.customs_code == customs_code).first()
obj = (
db.query(CustomsSection)
.filter(CustomsSection.customs_code == customs_code)
.first()
)
if not obj:
raise HTTPException(status_code=404, detail="Not found")
for field, value in data.dict().items():
@@ -64,13 +78,18 @@ def update_customs_section(
db.refresh(obj)
return obj
@router.delete("/{customs_code}", status_code=204)
def delete_customs_section(
customs_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(CustomsSection).filter(CustomsSection.customs_code == customs_code).first()
obj = (
db.query(CustomsSection)
.filter(CustomsSection.customs_code == customs_code)
.first()
)
if not obj:
raise HTTPException(status_code=404, detail="Not found")
db.delete(obj)

View File

@@ -1,10 +1,13 @@
seed = [
("01", "AEROPUERTO INTERNACIONAL GENERAL JUAN N. ALVAREZ, ACAPULCO, GUERRERO."),
("01", "AEROPUERTO INTERNACIONAL GENERAL JUAN N. ALVAREZ, ACAPULCO, GUERRERO."),
("010", "ACAPULCO, ACAPULCO DE JUAREZ, GUERRERO."),
("012", "AEROPUERTO INTERNACIONAL GENERAL JUAN N. ALVAREZ, ACAPULCO, GUERRERO."),
("020", "AGUA PRIETA, AGUA PRIETA, SONORA."),
("050", "SUBTENIENTE LOPEZ, SUBTENIENTE LOPEZ, QUINTANA ROO."),
("051", "SUBTENIENTE LOPEZ II „CHACTEMAL“, OTHÓN P. BLANCO, CHETUMAL, QUINTANA ROO."),
(
"051",
"SUBTENIENTE LOPEZ II „CHACTEMAL“, OTHÓN P. BLANCO, CHETUMAL, QUINTANA ROO.",
),
("060", "CIUDAD DEL CARMEN, CIUDAD DEL CARMEN, CAMPECHE."),
("063", "SEYBAPLAYA, CHAMPOTON, CAMPECHE."),
("070", "CIUDAD JUAREZ, CIUDAD JUAREZ, CHIHUAHUA."),
@@ -15,8 +18,14 @@ seed = [
("080", "COATZACOALCOS, COATZACOALCOS, VERACRUZ."),
("110", "ENSENADA, ENSENADA, BAJA CALIFORNIA."),
("120", "GUAYMAS, GUAYMAS, SONORA."),
("121", "AEROPUERTO INTERNACIONAL GENERAL IGNACIO PESQUEIRA GARCIA, HERMOSILLO, SONORA."),
("123", "CIUDAD OBREGON ADYACENTE AL AEROPUERTO DE CIUDAD OBREGON, CAJEME, SONORA."),
(
"121",
"AEROPUERTO INTERNACIONAL GENERAL IGNACIO PESQUEIRA GARCIA, HERMOSILLO, SONORA.",
),
(
"123",
"CIUDAD OBREGON ADYACENTE AL AEROPUERTO DE CIUDAD OBREGON, CAJEME, SONORA.",
),
("140", "LA PAZ, LA PAZ, BAJA CALIFORNIA SUR."),
("142", "SAN JOSE DEL CABO, LOS CABOS, BAJA CALIFORNIA SUR."),
("143", "CABO SAN LUCAS, LOS CABOS, BAJA CALIFORNIA SUR."),
@@ -25,7 +34,7 @@ seed = [
("147", "PICHILINGÜE, LA PAZ, BAJA CALIFORNIA SUR."),
("160", "MANZANILLO, MANZANILLO, COLIMA."),
("161", "ARMERÍA, ARMERÍA, COLIMA."),
("17", "AEROPUERTO INTERNACIONAL GENERAL SERVANDO CANALES, MATAMOROS, TAMAULIPAS."),
("17", "AEROPUERTO INTERNACIONAL GENERAL SERVANDO CANALES, MATAMOROS, TAMAULIPAS."),
("170", "MATAMOROS, MATAMOROS, TAMAULIPAS."),
("171", "LUCIO BLANCO-LOS INDIOS, MATAMOROS, TAMAULIPAS."),
("172", "SECCION ADUANERA FERROVIARIA DE MATAMOROS."),
@@ -36,22 +45,31 @@ seed = [
("192", "LOS ALGODONES, MEXICALI, BAJA CALIFORNIA."),
("193", "SAN FELIPE, MEXICALI, BAJA CALIFORNIA."),
("200", "MÉXICO, CIUDAD DE MÉXICO."),
("202", "IMPORTACION Y EXPORTACION DE CONTENEDORES, DELEGACION AZCAPOTZALCO, CIUDAD DE MÉXICO."),
(
"202",
"IMPORTACION Y EXPORTACION DE CONTENEDORES, DELEGACION AZCAPOTZALCO, CIUDAD DE MÉXICO.",
),
("220", "NACO, NACO, SONORA."),
("230", "NOGALES, NOGALES, SONORA."),
("231", "SASABE, SARIC, SONORA."),
("24", "AEROPUERTO INTERNACIONAL DE NUEVO LAREDO „QUETZALCOATL“, NUEVO LAREDO, TAMAULIPAS."),
(
"24",
"AEROPUERTO INTERNACIONAL DE NUEVO LAREDO „QUETZALCOATL“, NUEVO LAREDO, TAMAULIPAS.",
),
("240", "NUEVO LAREDO, NUEVO LAREDO, TAMAULIPAS."),
("250", "OJINAGA, OJINAGA, CHIHUAHUA."),
("260", "PUERTO PALOMAS, PUERTO PALOMAS, CHIHUAHUA."),
("27", "RIO ESCONDIDO, NAVA, COAHUILA."),
("27", "RIO ESCONDIDO, NAVA, COAHUILA."),
("270", "PIEDRAS NEGRAS, PIEDRAS NEGRAS, COAHUILA."),
("271", "AEROPUERTO INTERNACIONAL PLAN DE GUADALUPE, RAMOS ARIZPE, COAHUILA."),
("280", "PROGRESO, PROGRESO, YUCATAN."),
("282", "AEROPUERTO INTERNACIONAL LIC. MANUEL CRESCENCIO REJON, MERIDA, YUCATAN."),
("300", "CIUDAD REYNOSA, CIUDAD REYNOSA, TAMAULIPAS."),
("302", "LAS FLORES, RIO BRAVO, TAMAULIPAS."),
("304", "AEROPUERTO INTERNACIONAL GENERAL. LUCIO BLANCO, CIUDAD REYNOSA, TAMAULIPAS."),
(
"304",
"AEROPUERTO INTERNACIONAL GENERAL. LUCIO BLANCO, CIUDAD REYNOSA, TAMAULIPAS.",
),
("305", "RIO BRAVO-DONNA, RIO BRAVO, TAMAULIPAS."),
("306", "ANZALDUAS, CIUDAD REYNOSA, TAMAULIPAS."),
("310", "SALINA CRUZ, SALINA CRUZ, OAXACA."),
@@ -59,7 +77,7 @@ seed = [
("330", "SAN LUIS RIO COLORADO, SAN LUIS RIO COLORADO, SONORA."),
("340", "CIUDAD MIGUEL ALEMAN, CIUDAD MIGUEL ALEMAN, TAMAULIPAS."),
("342", "GUERRERO, GUERRERO, TAMAULIPAS."),
("37", "AEROPUERTO INTERNACIONAL DE TAPACHULA, TAPACHULA, CHIAPAS."),
("37", "AEROPUERTO INTERNACIONAL DE TAPACHULA, TAPACHULA, CHIAPAS."),
("370", "CIUDAD HIDALGO, CIUDAD HIDALGO, CHIAPAS."),
("372", "CIUDAD TALISMAN, TUXTLA CHICO, CHIAPAS."),
("375", "PUERTO CHIAPAS, TAPACHULA, CHIAPAS."),
@@ -67,27 +85,42 @@ seed = [
("380", "TAMPICO, TAMPICO, TAMAULIPAS."),
("390", "TECATE, TECATE, BAJA CALIFORNIA."),
("400", "TIJUANA, TIJUANA, BAJA CALIFORNIA."),
("402", "AEROPUERTO INTERNACIONAL GENERAL ABELARDO L. RODRIGUEZ, TIJUANA, BAJA CALIFORNIA."),
(
"402",
"AEROPUERTO INTERNACIONAL GENERAL ABELARDO L. RODRIGUEZ, TIJUANA, BAJA CALIFORNIA.",
),
("420", "TUXPAN, TUXPAN DE RODRIGUEZ CANO, VERACRUZ."),
("421", "TUXPAN, TUXPAN, VERACRUZ."),
("430", "VERACRUZ, VERACRUZ, VERACRUZ."),
("432", "AEROPUERTO INTERNACIONAL GENERAL HERIBERTO JARA CORONA, VERACRUZ, VERACRUZ."),
(
"432",
"AEROPUERTO INTERNACIONAL GENERAL HERIBERTO JARA CORONA, VERACRUZ, VERACRUZ.",
),
("440", "CIUDAD ACUÑA, CIUDAD ACUÑA, COAHUILA."),
("460", "TORREON, TORREON, COAHUILA."),
("461", "AEROPUERTO DE TORREÓN, COAHUILA DE ZARAGOZA."),
("462", "GOMEZ PALACIO, GOMEZ PALACIO, DURANGO."),
("463", "AEROPUERTO INTERNACIONAL GENERAL GUADALUPE VICTORIA, DURANGO, DURANGO."),
("470", "AEROPUERTO INTERNACIONAL DE LA CIUDAD DE MEXICO."),
("471", "SATELITE, PARA IMPORTACION Y EXPORTACION POR VIA AEREA, AEROPUERTO INTERNACIONAL BENITO JUAREZ DE LA CIUDAD DE MEXICO."),
("472", "CENTRO POSTAL MECANIZADO, POR VIA POSTAL Y POR TRAFICO AEREO, AEROPUERTO INTERNACIONAL BENITO JUAREZ DE LA CIUDAD DE MEXICO."),
(
"471",
"SATELITE, PARA IMPORTACION Y EXPORTACION POR VIA AEREA, AEROPUERTO INTERNACIONAL BENITO JUAREZ DE LA CIUDAD DE MEXICO.",
),
(
"472",
"CENTRO POSTAL MECANIZADO, POR VIA POSTAL Y POR TRAFICO AEREO, AEROPUERTO INTERNACIONAL BENITO JUAREZ DE LA CIUDAD DE MEXICO.",
),
("480", "GUADALAJARA, TLACOMULCO DE ZUÑIGA, JALISCO."),
("481", "PUERTO VALLARTA, PUERTO VALLARTA, JALISCO."),
("484", "TERMINAL INTERMODAL FERROVIARIA, GUADALAJARA, JALISCO."),
("50", "SONORA, PITIQUITO, SONORA."),
("50", "SONORA, PITIQUITO, SONORA."),
("500", "SONOYTA, SONOYTA, SONORA."),
("501", "SAN EMETERIO, GENERAL PLUTARCO ELIAS CALLES, SONORA."),
("510", "LAZARO CARDENAS, LAZARO CARDENAS, MICHOACAN."),
("511", "AEROPUERTO INTERNACIONAL IXTAPA-ZIHUATANEJO, ZIHUATANEJO DE AZUETA, GUERRERO."),
(
"511",
"AEROPUERTO INTERNACIONAL IXTAPA-ZIHUATANEJO, ZIHUATANEJO DE AZUETA, GUERRERO.",
),
("520", "MONTERREY, GENERAL MARIANO ESCOBEDO, NUEVO LEON."),
("521", "AEROPUERTO INTERNACIONAL GENERAL MARIANO ESCOBEDO, APODACA, NUEVO LEON."),
("523", "SALINAS VICTORIA A (TERMINAL FERROVIARIA), SALINAS VICTORIA, NUEVO LEON."),
@@ -102,14 +135,23 @@ seed = [
("651", "SAN CAYETANO MORELOS, TOLUCA, ESTADO DE MÉXICO"),
("670", "CHIHUAHUA, CHIHUAHUA, CHIHUAHUA."),
("671", "PARQUE INDUSTRIAL LAS AMERICAS, CHIHUAHUA, CHIHUAHUA."),
("672", "AEROPUERTO INTERNACIONAL GENERAL ROBERTO FIERRO VILLALOBOS, CHIHUAHUA, CHIHUAHUA."),
("73", "CHICALOTE, SAN FRANCISCO DE LOS ROMO, AGUASCALIENTES."),
(
"672",
"AEROPUERTO INTERNACIONAL GENERAL ROBERTO FIERRO VILLALOBOS, CHIHUAHUA, CHIHUAHUA.",
),
("73", "CHICALOTE, SAN FRANCISCO DE LOS ROMO, AGUASCALIENTES."),
("730", "AGUASCALIENTES, AGUASCALIENTES, AGUASCALIENTES."),
("731", "PARQUE MULTIMODAL INTERPUERTO, SAN LUIS POTOSI, SAN LUIS POTOSI."),
("732", "AEROPUERTO INTERNACIONAL GENERAL LEOBARDO C. RUIZ, EN CALERA ZACATECAS."),
("733", "AEROPUERTO INTERNACIONAL PONCIANO ARRIAGA, SOLEDAD DE GRACIANO SANCHEZ, SAN LUIS POTOSI."),
(
"733",
"AEROPUERTO INTERNACIONAL PONCIANO ARRIAGA, SOLEDAD DE GRACIANO SANCHEZ, SAN LUIS POTOSI.",
),
("734", "LA PILA-VILLA, VILLA DE REYES, SAN LUIS POTOSI."),
("735", "AEROPUERTO INTERNACIONAL LIC. JESUS TERAN PEREDO, AGUASCALIENTES, AGUASCALIENTES."),
(
"735",
"AEROPUERTO INTERNACIONAL LIC. JESUS TERAN PEREDO, AGUASCALIENTES, AGUASCALIENTES.",
),
("750", "PUEBLA, HEROICA PUEBLA DE ZARAGOZA, PUEBLA."),
("751", "CUERNAVACA, JIUTEPEC, MORELOS."),
("754", "AEROPUERTO INTERNACIONAL HERMANOS SERDAN, HUEJOTZINGO, PUEBLA."),
@@ -117,9 +159,12 @@ seed = [
("810", "ALTAMIRA, ALTAMIRA, TAMAULIPAS."),
("820", "CIUDAD CAMARGO, CIUDAD CAMARGO, TAMAULIPAS."),
("830", "DOS BOCAS, PARAISO, TABASCO."),
("831", "AEROPUERTO INTERNACIONAL C.P.A. CARLOS ROVIROSA PEREZ, CIUDAD DE VILLAHERMOSA, CENTRO, TABASCO."),
(
"831",
"AEROPUERTO INTERNACIONAL C.P.A. CARLOS ROVIROSA PEREZ, CIUDAD DE VILLAHERMOSA, CENTRO, TABASCO.",
),
("834", "EL CEIBO, TENOSIQUE, TABASCO."),
("840", "GUANAJUATO, SILAO, GUANAJUATO."),
("841", "CELAYA, CELAYA, GUANAJUATO."),
("842", "AEROPUERTO INTERNACIONAL DE GUANAJUATO, SILAO, GUANAJUATO."),
]
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_customs_sections(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,28 @@ def test_list_customs_sections(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_customs_section_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/customs-sections/invalid_code", headers=headers)
assert response.status_code == 404
def test_create_customs_section_forbidden():
response = client.post("/customs-sections/", json={"customs_code": "TST", "description": "Test"})
response = client.post(
"/customs-sections/", json={"customs_code": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_update_customs_section_forbidden():
response = client.put("/customs-sections/TST", json={"customs_code": "TST", "description": "Test"})
response = client.put(
"/customs-sections/TST", json={"customs_code": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_delete_customs_section_forbidden():
response = client.delete("/customs-sections/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -1,10 +1,10 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class CustomsWarehouseDTO(BaseModel):
key: str = Field(..., min_length=1, max_length=3)
customs: str
fiscalized_warehouse: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -2,16 +2,19 @@ from sqlalchemy import String, PrimaryKeyConstraint
from sqlalchemy.orm import mapped_column, Mapped
from core.database import Base
class CustomsWarehouse(Base):
__tablename__ = "customs_warehouses" #GRecintos
__tablename__ = "customs_warehouses" # GRecintos
__table_args__ = (
PrimaryKeyConstraint("key", "customs", name="pk_customs_warehouse"),
{"schema": "public"} # opcional
{"schema": "public"}, # opcional
)
key: Mapped[str] = mapped_column(String(3), nullable=False) # clave del recinto
customs: Mapped[str] = mapped_column(String(100), nullable=False) # aduana asociada
fiscalized_warehouse: Mapped[str] = mapped_column(String(1000)) # recintos fiscalizados (valor legal)
key: Mapped[str] = mapped_column(String(3), nullable=False) # clave del recinto
customs: Mapped[str] = mapped_column(String(100), nullable=False) # aduana asociada
fiscalized_warehouse: Mapped[str] = mapped_column(
String(1000)
) # recintos fiscalizados (valor legal)
def __repr__(self):
return f"<CustomsWarehouse(key={self.key}, customs={self.customs}, fiscalized_warehouse={self.fiscalized_warehouse})>"

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -16,7 +15,7 @@ def list_customs_warehouses(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(CustomsWarehouse)
@@ -26,21 +25,32 @@ def list_customs_warehouses(
"items": [CustomsWarehouseDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{key}/{customs}", response_model=CustomsWarehouseDTO)
def get_customs_warehouse(key: str, customs: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
obj = db.query(CustomsWarehouse).filter(CustomsWarehouse.key == key, CustomsWarehouse.customs == customs).first()
def get_customs_warehouse(
key: str,
customs: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = (
db.query(CustomsWarehouse)
.filter(CustomsWarehouse.key == key, CustomsWarehouse.customs == customs)
.first()
)
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=CustomsWarehouseDTO, status_code=201)
def create_customs_warehouse(
data: CustomsWarehouseDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = CustomsWarehouse(**data.dict())
db.add(obj)
@@ -48,15 +58,20 @@ def create_customs_warehouse(
db.refresh(obj)
return obj
@router.put("/{key}/{customs}", response_model=CustomsWarehouseDTO)
def update_customs_warehouse(
key: str,
customs: str,
data: CustomsWarehouseDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(CustomsWarehouse).filter(CustomsWarehouse.key == key, CustomsWarehouse.customs == customs).first()
obj = (
db.query(CustomsWarehouse)
.filter(CustomsWarehouse.key == key, CustomsWarehouse.customs == customs)
.first()
)
if not obj:
raise HTTPException(status_code=404, detail="Not found")
for field, value in data.dict().items():
@@ -65,14 +80,19 @@ def update_customs_warehouse(
db.refresh(obj)
return obj
@router.delete("/{key}/{customs}", status_code=204)
def delete_customs_warehouse(
key: str,
customs: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(CustomsWarehouse).filter(CustomsWarehouse.key == key, CustomsWarehouse.customs == customs).first()
obj = (
db.query(CustomsWarehouse)
.filter(CustomsWarehouse.key == key, CustomsWarehouse.customs == customs)
.first()
)
if not obj:
raise HTTPException(status_code=404, detail="Not found")
db.delete(obj)

View File

@@ -1,28 +1,68 @@
seed = [
("1 ", "Acapulco", "Administración Portuaria Integral de Acapulco, S.A. de C.V."),
("10 ", "Aeropuerto Internacional de la Ciudad de México", "Cargo Service Center de México, S.A. de C.V."),
("12 ", "Aeropuerto Internacional de la Ciudad de México", "DHL Express México, S.A. de C.V."),
("14 ", "Aeropuerto Internacional de la Ciudad de México", "Lufthansa Cargo Servicios Logísticos de México, S.A. de C.V."),
(
"10 ",
"Aeropuerto Internacional de la Ciudad de México",
"Cargo Service Center de México, S.A. de C.V.",
),
(
"12 ",
"Aeropuerto Internacional de la Ciudad de México",
"DHL Express México, S.A. de C.V.",
),
(
"14 ",
"Aeropuerto Internacional de la Ciudad de México",
"Lufthansa Cargo Servicios Logísticos de México, S.A. de C.V.",
),
("145", "México", "Ferrocarril y Terminal de Valle de México, S.A. de C.V."),
("146", "Veracruz", "Cargill de México, S.A. de C.V."),
("147", "Aeropuerto Internacional de la Ciudad de México", "Braniff Transport Carga, S.A. de C.V."),
("148", "Nuevo Laredo", "Inspecciones Fitosanitarias y Aduaneras de Nuevo Laredo, S.A. de C.V."),
(
"147",
"Aeropuerto Internacional de la Ciudad de México",
"Braniff Transport Carga, S.A. de C.V.",
),
(
"148",
"Nuevo Laredo",
"Inspecciones Fitosanitarias y Aduaneras de Nuevo Laredo, S.A. de C.V.",
),
("149", "Nuevo Laredo", "PG Servicios de Logística, S.C."),
("15 ", "Aeropuerto Internacional de la Ciudad de México", "Tramitadores Asociados de Aerocarga, S.A. de C.V."),
(
"15 ",
"Aeropuerto Internacional de la Ciudad de México",
"Tramitadores Asociados de Aerocarga, S.A. de C.V.",
),
("150", "Piedras Negras", "Mercurio Cargo, S.A. de C.V."),
("151", "Colombia", "S.R. Asesores Aduanales de Nuevo Laredo, S.C."),
("154", "Monterrey", "Federal Express Holdings (México) y Compañía, S.N.C. de C.V."),
(
"154",
"Monterrey",
"Federal Express Holdings (México) y Compañía, S.N.C. de C.V.",
),
("155", "Ciudad Hidalgo", "Corporativo de Servicios del Sureste, S.A. de C.V."),
("158", "Monterrey", "Aeropuerto de Monterrey, S.A. de C.V."),
("16 ", "Aeropuerto Internacional de la Ciudad de México", "Transportación México Express, S.A. de C.V."),
(
"16 ",
"Aeropuerto Internacional de la Ciudad de México",
"Transportación México Express, S.A. de C.V.",
),
("160", "Manzanillo", "Frigorífico de Manzanillo, S.A. de C.V."),
("161", "Colombia", "Santos Esquivel y Compañía, S.C."),
("162", "Guadalajara", "Ferrocarril Mexicano, S.A. de C.V."),
("164", "Monterrey", "United Parcel Service de México, S.A. de C.V."),
("165", "Aguascalientes", "Centros de Intercambio de Carga Express Estafeta, S.A. de C.V."),
(
"165",
"Aguascalientes",
"Centros de Intercambio de Carga Express Estafeta, S.A. de C.V.",
),
("166", "Altamira", "Administración Portuaria Integral de Altamira, S.A. de C.V."),
("167", "Ciudad Juárez", "Accel, Recinto Fiscalizado, S.A. de C.V."),
("17 ", "Aeropuerto Internacional de la Ciudad de México", "United Parcel Service de México, S.A. de C.V."),
(
"17 ",
"Aeropuerto Internacional de la Ciudad de México",
"United Parcel Service de México, S.A. de C.V.",
),
("171", "Chihuahua", "Aeropuerto de Chihuahua, S.A. de C.V."),
("172", "Veracruz", "Servicios Especiales Portuarios, S.A. de C.V."),
("173", "Lázaro Cárdenas", "UTTSA, S.A. de C.V."),
@@ -34,7 +74,11 @@ seed = [
("179", "Altamira", "D.A. Hinojosa Terminal Multiusos, S.A. de C.V."),
("18 ", "Aeropuerto Internacional de la Ciudad de México", "Varig de México, S.A."),
("180", "Altamira", "Inmobiliaria Portuaria de Altamira, S.A. de C.V."),
("182", "Veracruz", "Servicios, Maniobras y Almacenamientos de Veracruz, S.A. de C.V."),
(
"182",
"Veracruz",
"Servicios, Maniobras y Almacenamientos de Veracruz, S.A. de C.V.",
),
("184", "Progreso", "Terminal de Contenedores de Yucatán, S.A. de C.V."),
("186", "Nuevo Laredo", "Logis Servicios Comerciales, S.A. de C.V."),
("187", "Manzanillo", "Tecnoadministración del Pacífico, S.A. de C.V."),
@@ -50,8 +94,16 @@ seed = [
("203", "Altamira", "Grupo Castañeda, S.A. de C.V."),
("204", "Monterrey", "Ferrocarril Mexicano, S.A. de C.V."),
("210", "Querétaro", "Terminal Logistics, S.A. de C.V."),
("211", "Aeropuerto Internacional de la Ciudad de México", "World Express Cargo de México, S.A. de C.V."),
("212", "Piedras Negras", "Consultores de Logística en Comercio Exterior, S.A. de C.V."),
(
"211",
"Aeropuerto Internacional de la Ciudad de México",
"World Express Cargo de México, S.A. de C.V.",
),
(
"212",
"Piedras Negras",
"Consultores de Logística en Comercio Exterior, S.A. de C.V.",
),
("214", "Altamira", "Possehl México, S.A. de C.V."),
("215", "Tampico", "Refitam, S.A. de C.V."),
("217", "Veracruz", "SSA México, S.A. de C.V."),
@@ -62,12 +114,20 @@ seed = [
("222", "Matamoros", "Puerto Los Indios, S.A. de C.V."),
("223", "Monterrey", "DHL Express México, S.A. de C.V."),
("224", "Aguascalientes", "Nafta Rail, S.A. de C.V."),
("225", "Altamira", "Integradora de Servicios, Transporte y Almacenaje, S.A. de C.V."),
(
"225",
"Altamira",
"Integradora de Servicios, Transporte y Almacenaje, S.A. de C.V.",
),
("226", "Nuevo Laredo", "DAF, Delivery After Frontier, S.A. de C.V."),
("227", "Matamoros", "Profesionales Mexicanos del Comercio Exterior, S.C."),
("228", "Guadalajara", "CLA Guadalajara, S.A. de C.V."),
("229", "Manzanillo", "Maniobras Integradas del Puerto, S.A. de C.V."),
("23 ", "Coatzacoalcos", "Administración Portuaria Integral de Coatzacoalcos, S.A. de C.V."),
(
"23 ",
"Coatzacoalcos",
"Administración Portuaria Integral de Coatzacoalcos, S.A. de C.V.",
),
("230", "Querétaro", "Terminal Intermodal Logística de Hidalgo, S.A.P.I. de C.V."),
("231", "Lázaro Cárdenas", "Terminales Portuarias del Pacífico, S.A.P.I. de C.V."),
("232", "Lázaro Cárdenas", "Arcelormittal Portuarios, S.A. de C.V."),
@@ -83,34 +143,74 @@ seed = [
("26 ", "Colombia", "Mex Securit, S.A. de C.V."),
("27 ", "Ensenada", "Ensenada International Terminal, S.A. de C.V."),
("28 ", "Guadalajara", "Almacenadora GWTC, S.A. de C.V."),
("29 ", "Guadalajara", "Federal Express Holdings (México) y Compañía, S.N.C. de C.V."),
("3 ", "Aeropuerto Internacional de la Ciudad de México", "Aerovías de México, S.A. de C.V."),
(
"29 ",
"Guadalajara",
"Federal Express Holdings (México) y Compañía, S.N.C. de C.V.",
),
(
"3 ",
"Aeropuerto Internacional de la Ciudad de México",
"Aerovías de México, S.A. de C.V.",
),
("30 ", "Guaymas", "Administración Portuaria Integral de Guaymas, S.A. de C.V."),
("31 ", "Lázaro Cárdenas", "Administración Portuaria Integral de Lázaro Cárdenas, S.A. de C.V."),
(
"31 ",
"Lázaro Cárdenas",
"Administración Portuaria Integral de Lázaro Cárdenas, S.A. de C.V.",
),
("33 ", "Lázaro Cárdenas", "Aarhuskarlshamn México, S.A. de C.V."),
("35 ", "Manzanillo", "Administración Portuaria Integral de Manzanillo, S.A. de C.V."),
(
"35 ",
"Manzanillo",
"Administración Portuaria Integral de Manzanillo, S.A. de C.V.",
),
("36 ", "Manzanillo", "Comercializadora La Junta, S.A. de C.V."),
("38 ", "Manzanillo", "Operadora de la Cuenca del Pacífico, S.A. de C.V."),
("39 ", "Manzanillo", "SSA México, S.A. de C.V."),
("4 ", "Aeropuerto Internacional de la Ciudad de México", "AAACESA Almacenes Fiscalizados, S.A. de C.V."),
(
"4 ",
"Aeropuerto Internacional de la Ciudad de México",
"AAACESA Almacenes Fiscalizados, S.A. de C.V.",
),
("40 ", "Manzanillo", "Terminal Internacional de Manzanillo, S.A. de C.V."),
("42 ", "Mazatlán", "Administración Portuaria Integral de Mazatlán, S.A. de C.V."),
("43 ", "Mazatlán", "Administración Portuaria Integral de Topolobampo, S.A. de C.V."),
(
"43 ",
"Mazatlán",
"Administración Portuaria Integral de Topolobampo, S.A. de C.V.",
),
("44 ", "Monterrey", "Braniff Air Freight and Company, S.A. de C.V."),
("45 ", "Monterrey", "Kansas City Southern de México, S.A. de C.V."),
("46 ", "Nogales", "Servicios de Almacén Fiscalizado de Nogales, S.A. de C.V."),
("47 ", "Progreso", "Administración Portuaria Integral de Progreso, S.A. de C.V."),
("49 ", "Progreso", "Grupo de Desarrollo del Sureste, S.A. de C.V."),
("5 ", "Aeropuerto Internacional de la Ciudad de México", "México Cargo Handling, S.A. de C.V."),
(
"5 ",
"Aeropuerto Internacional de la Ciudad de México",
"México Cargo Handling, S.A. de C.V.",
),
("50 ", "Progreso", "Multisur, S.A. de C.V."),
("51 ", "Querétaro", "Servicios Integrales y Desarrollo GMG, S.A. de C.V."),
("52 ", "Reynosa", "Recintos Fiscalizados de Noreste, S.A. de C.V."),
("53 ", "Salina Cruz", "Administración Portuaria Integral de Salina Cruz, S.A. de C.V."),
("54 ", "Cancún", "Administración Portuaria Integral de Quintana Roo, S.A. de C.V."),
(
"53 ",
"Salina Cruz",
"Administración Portuaria Integral de Salina Cruz, S.A. de C.V.",
),
(
"54 ",
"Cancún",
"Administración Portuaria Integral de Quintana Roo, S.A. de C.V.",
),
("56 ", "Toluca", "Braniff Air Freight and Company, S.A. de C.V."),
("57 ", "Toluca", "Federal Express Holdings (México) y Compañía, S.N.C. de C.V."),
("59 ", "Tuxpan", "Administración Portuaria Integral de Tuxpan, S.A. de C.V."),
("6 ", "Aeropuerto Internacional de la Ciudad de México", "American Airlines de México, S.A. de C.V."),
(
"6 ",
"Aeropuerto Internacional de la Ciudad de México",
"American Airlines de México, S.A. de C.V.",
),
("60 ", "Tuxpan", "Fenoresinas, S.A. de C.V."),
("61 ", "Tuxpan", "Terminal Marítima de Tuxpan, S.A. de C.V."),
("62 ", "Tuxpan", "Terminales Marítimas Transunisa, S.A. de C.V."),
@@ -118,8 +218,16 @@ seed = [
("64 ", "Veracruz", "Almacenadora Golmex, S.A. de C.V."),
("66 ", "Veracruz", "CIF Almacenajes y Servicios, S.A. de C.V."),
("67 ", "Veracruz", "Corporación Integral de Comercio Exterior, S.A. de C.V."),
("69 ", "Veracruz", "Internacional de Contenedores Asociados de Veracruz, S.A. de C.V."),
("7 ", "Aeropuerto Internacional de la Ciudad de México", "Braniff Air Freight and Company, S.A. de C.V."),
(
"69 ",
"Veracruz",
"Internacional de Contenedores Asociados de Veracruz, S.A. de C.V.",
),
(
"7 ",
"Aeropuerto Internacional de la Ciudad de México",
"Braniff Air Freight and Company, S.A. de C.V.",
),
("71 ", "Veracruz", "Reparación Integral de Contenedores, S.A. de C.V."),
("73 ", "Veracruz", "Terminales de Cargas Especializadas, S.A. de C.V."),
("74 ", "Veracruz", "Vopak Terminals México, S.A. de C.V."),
@@ -127,9 +235,17 @@ seed = [
("76 ", "Manzanillo", "Cemex México, S.A. de C.V."),
("77 ", "Manzanillo", "Corporación Multimodal, S.A. de C.V."),
("78 ", "Ensenada", "Administración Portuaria Integral de Ensenada, S.A. de C.V."),
("8 ", "Aeropuerto Internacional de la Ciudad de México", "Iberia de México, S.A."),
(
"8 ",
"Aeropuerto Internacional de la Ciudad de México",
"Iberia de México, S.A.",
),
("81 ", "Tuxpan", "Frigoríficos Especializados de Tuxpan, S.A. de C.V."),
("82 ", "Veracruz", "SSA México, S.A. de C.V."),
("9 ", "Aeropuerto Internacional de la Ciudad de México", "Compañía Mexicana de Aviación, S.A. de C.V."),
(
"9 ",
"Aeropuerto Internacional de la Ciudad de México",
"Compañía Mexicana de Aviación, S.A. de C.V.",
),
("98 ", "Veracruz", "Corporación Portuaria de Veracruz, S.A. de C.V."),
]
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_customs_warehouses(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,32 @@ def test_list_customs_warehouses(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_customs_warehouse_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/customs-warehouses/invalid_key/invalid_customs", headers=headers)
response = client.get(
"/customs-warehouses/invalid_key/invalid_customs", headers=headers
)
assert response.status_code == 404
def test_create_customs_warehouse_forbidden():
response = client.post("/customs-warehouses/", json={"key": "TST", "customs": "TST", "description": "Test"})
response = client.post(
"/customs-warehouses/",
json={"key": "TST", "customs": "TST", "description": "Test"},
)
assert response.status_code in (403, 405, 404)
def test_update_customs_warehouse_forbidden():
response = client.put("/customs-warehouses/TST/TST", json={"key": "TST", "customs": "TST", "description": "Test"})
response = client.put(
"/customs-warehouses/TST/TST",
json={"key": "TST", "customs": "TST", "description": "Test"},
)
assert response.status_code in (403, 405, 404)
def test_delete_customs_warehouse_forbidden():
response = client.delete("/customs-warehouses/TST/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -1,6 +1,7 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class IncotermDTO(BaseModel):
code: str = Field(..., min_length=1, max_length=5)
description_es: str

View File

@@ -2,11 +2,12 @@ from sqlalchemy import String, PrimaryKeyConstraint
from sqlalchemy.orm import mapped_column, Mapped
from core.database import Base
class Incoterm(Base):
__tablename__ = "incoterms" #GIncoterm
__tablename__ = "incoterms" # GIncoterm
__table_args__ = (
PrimaryKeyConstraint("code", name="incoterms_pkey"),
{"schema": "public"}
{"schema": "public"},
)
code: Mapped[str] = mapped_column(String(5), nullable=False)

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -11,13 +10,12 @@ from typing import Any, Dict
router = APIRouter(prefix="/incoterms")
@router.get("/", response_model=Dict[str, Any])
async def list_incoterms(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(Incoterm)
@@ -27,23 +25,27 @@ async def list_incoterms(
"items": [IncotermDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{key}", response_model=IncotermDTO)
async def get_incoterm(key: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
async def get_incoterm(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = db.query(Incoterm).filter(Incoterm.code == key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return IncotermDTO.model_validate(obj)
@router.post("/", response_model=IncotermDTO, status_code=201)
async def create_incoterm(
data: IncotermDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = Incoterm(**data.model_dump())
db.add(obj)
@@ -51,13 +53,13 @@ async def create_incoterm(
db.refresh(obj)
return IncotermDTO.model_validate(obj)
@router.put("/{key}", response_model=IncotermDTO)
async def update_incoterm(
key: str,
data: IncotermDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(Incoterm).filter(Incoterm.code == key).first()
if not obj:
@@ -68,12 +70,12 @@ async def update_incoterm(
db.refresh(obj)
return IncotermDTO.model_validate(obj)
@router.delete("/{key}", status_code=204)
async def delete_incoterm(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(Incoterm).filter(Incoterm.key == key).first()
if not obj:

View File

@@ -10,4 +10,4 @@ seed = [
("FOB", "PUERTO DE EMBARQUE CONVENIDO", "FREE ON BOARD"),
("CFR", "COSTO Y FLETE", "COST AND FREIGHT"),
("CIF", "COSTO, SEGURO Y FLETE", "COST, INSURANCE AND FREIGHT"),
]
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_incoterms(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,24 @@ def test_list_incoterms(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_incoterm_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/incoterms/invalid_key", headers=headers)
assert response.status_code == 404
def test_create_incoterm_forbidden():
response = client.post("/incoterms/", json={"key": "TST", "description": "Test"})
assert response.status_code in (403, 405, 404)
def test_update_incoterm_forbidden():
response = client.put("/incoterms/TST", json={"key": "TST", "description": "Test"})
assert response.status_code in (403, 405, 404)
def test_delete_incoterm_forbidden():
response = client.delete("/incoterms/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -2,6 +2,7 @@ from pydantic import BaseModel, Field
from pydantic import ConfigDict
from typing import Optional
class InvoiceTypeDTO(BaseModel):
key: str = Field(..., min_length=1, max_length=5)
description: str

View File

@@ -4,16 +4,20 @@ from core.database import Base
class InvoiceType(Base):
__tablename__ = "invoice_types" #GTiposFactura
__tablename__ = "invoice_types" # GTiposFactura
__table_args__ = (
PrimaryKeyConstraint("key", name="invoice_types_pkey"),
{"schema": "public"} # opcional
{"schema": "public"}, # opcional
)
key: Mapped[str] = mapped_column(String(5), nullable=False) # clave del tipo de factura
description: Mapped[str] = mapped_column(String(50), nullable=False) # descripción oficial (en español)
note: Mapped[str] = mapped_column(String(500)) # observación o comentario adicional
type: Mapped[str] = mapped_column(String(15)) # tipo
key: Mapped[str] = mapped_column(
String(5), nullable=False
) # clave del tipo de factura
description: Mapped[str] = mapped_column(
String(50), nullable=False
) # descripción oficial (en español)
note: Mapped[str] = mapped_column(String(500)) # observación o comentario adicional
type: Mapped[str] = mapped_column(String(15)) # tipo
def __repr__(self):
return f"<InvoiceType(key={self.key}, description={self.description}, origin_type={self.origin_type})>"

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -16,7 +15,7 @@ def list_invoice_types(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(InvoiceType)
@@ -26,21 +25,27 @@ def list_invoice_types(
"items": [InvoiceTypeDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{key}", response_model=InvoiceTypeDTO)
def get_invoice_type(key: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
def get_invoice_type(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = db.query(InvoiceType).filter(InvoiceType.key == key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return InvoiceTypeDTO.model_validate(obj)
@router.post("/", response_model=InvoiceTypeDTO, status_code=201)
def create_invoice_type(
data: InvoiceTypeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = InvoiceType(**data.model_dump())
db.add(obj)
@@ -48,12 +53,13 @@ def create_invoice_type(
db.refresh(obj)
return InvoiceTypeDTO.model_validate(obj)
@router.put("/{key}", response_model=InvoiceTypeDTO)
def update_invoice_type(
key: str,
data: InvoiceTypeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(InvoiceType).filter(InvoiceType.key == key).first()
if not obj:
@@ -64,11 +70,12 @@ def update_invoice_type(
db.refresh(obj)
return InvoiceTypeDTO.model_validate(obj)
@router.delete("/{key}", status_code=204)
def delete_invoice_type(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(InvoiceType).filter(InvoiceType.key == key).first()
if not obj:

View File

@@ -1,13 +1,38 @@
seed = [
("DONAC", "DONACION", "", "AMBOS"),
("EXDEF", "EXPORTACION DEFINITIVA", "", "MATERIAL"),
("MATDE", "MATERIA PRIMA O MATERIAL DEVUELTO", "ESTE PROCESO CONSISTE EN SOLO DESCARGAR LAS PARTES DADAS DE ALTA EN MATERIALES QUE SON RETORNADAS SIN NINGUNA MODIFICACION (A1)", "MATERIAL"),
("NODES", "NO HACE DESCARGA", "ESTE PROCESO DE ACTUALIZACION CONSISTE EN EXPORTAR UNA MERCANCIA Y NO DESCARGAR, POR LO TANTO NO EXISTE REPORTE DE DESCARGAS Y NO AFECTA SALDOS.", "AMBOS"),
("PTERM", "PRODUCTO TERMINADO Y VIRTUALES", "EL PRODUCTO TERMINADO Y VIRTUALES DESCARGARAN: 1) APARTIR DE LOS COMPONENTES DE CADA PRODUCTO TERMINADO REGISTRADO EN LAS PARTIDAS DE EXPORTACION. 2) POR PARTE, CON LAS OPCIONES DE PODER DESCARGAR POR SUSTITUTO Y POR CLASE EN CASO DE INSUFICIENCIAS DEL COMPONENTE.", "MATERIAL"),
("REPAR", "REPARACION", "PROCESO QUE CONSISTE EN DOS ETAPAS: 1) DESCARGA EL PRODUCTO DE REPARACION QUE SE IMPORTO PARA REPARA, 2) DESCARGA EL LISTADO DE COMPONENTES QUE SE AGREGO AL PRODUCTO DE REPARACION", "MATERIAL"),
(
"MATDE",
"MATERIA PRIMA O MATERIAL DEVUELTO",
"ESTE PROCESO CONSISTE EN SOLO DESCARGAR LAS PARTES DADAS DE ALTA EN MATERIALES QUE SON RETORNADAS SIN NINGUNA MODIFICACION (A1)",
"MATERIAL",
),
(
"NODES",
"NO HACE DESCARGA",
"ESTE PROCESO DE ACTUALIZACION CONSISTE EN EXPORTAR UNA MERCANCIA Y NO DESCARGAR, POR LO TANTO NO EXISTE REPORTE DE DESCARGAS Y NO AFECTA SALDOS.",
"AMBOS",
),
(
"PTERM",
"PRODUCTO TERMINADO Y VIRTUALES",
"EL PRODUCTO TERMINADO Y VIRTUALES DESCARGARAN: 1) APARTIR DE LOS COMPONENTES DE CADA PRODUCTO TERMINADO REGISTRADO EN LAS PARTIDAS DE EXPORTACION. 2) POR PARTE, CON LAS OPCIONES DE PODER DESCARGAR POR SUSTITUTO Y POR CLASE EN CASO DE INSUFICIENCIAS DEL COMPONENTE.",
"MATERIAL",
),
(
"REPAR",
"REPARACION",
"PROCESO QUE CONSISTE EN DOS ETAPAS: 1) DESCARGA EL PRODUCTO DE REPARACION QUE SE IMPORTO PARA REPARA, 2) DESCARGA EL LISTADO DE COMPONENTES QUE SE AGREGO AL PRODUCTO DE REPARACION",
"MATERIAL",
),
("SCRAP", "SCRAP", "", "AMBOS"),
("VEMEX", "VENTAS EN MEXICO", "ESTE PROCESO CONSISTE EN LA VENTA EN EL MERCADO NACIONAL DE LOS PRODUCTOS.", "AMBOS"),
("VIRTU", "VIRTUALES", "", "MATERIAL"),
(
"VEMEX",
"VENTAS EN MEXICO",
"ESTE PROCESO CONSISTE EN LA VENTA EN EL MERCADO NACIONAL DE LOS PRODUCTOS.",
"AMBOS",
),
("VIRTU", "VIRTUALES", "", "MATERIAL"),
("AFIJO", "ACTIVO FIJO", "", "ACTIVO FIJO"),
("REEXP", "REEXPEDICION", "", "ACTIVO FIJO"),
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_invoice_types(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,28 @@ def test_list_invoice_types(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_invoice_type_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/invoice-types/invalid_key", headers=headers)
assert response.status_code == 404
def test_create_invoice_type_forbidden():
response = client.post("/invoice-types/", json={"key": "TST", "description": "Test"})
response = client.post(
"/invoice-types/", json={"key": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_update_invoice_type_forbidden():
response = client.put("/invoice-types/TST", json={"key": "TST", "description": "Test"})
response = client.put(
"/invoice-types/TST", json={"key": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_delete_invoice_type_forbidden():
response = client.delete("/invoice-types/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -1,10 +1,10 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class MaterialTypeDTO(BaseModel):
key: str = Field(..., min_length=1, max_length=10)
type: str = Field(..., min_length=1, max_length=15)
description: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -2,16 +2,19 @@ from sqlalchemy import String, PrimaryKeyConstraint
from sqlalchemy.orm import mapped_column, Mapped
from core.database import Base
class MaterialType(Base):
__tablename__ = "material_types" #STipoMat QTipoActFijo
__tablename__ = "material_types" # STipoMat QTipoActFijo
__table_args__ = (
PrimaryKeyConstraint("key", name="material_types_pkey"),
{"schema": "public"}
{"schema": "public"},
)
key: Mapped[str] = mapped_column(String(10), nullable=False) # clave del material
type: Mapped[str] = mapped_column(String(15), nullable=False) # tipo
description: Mapped[str] = mapped_column(String(256), nullable=False) # descripción oficial (en español)
key: Mapped[str] = mapped_column(String(10), nullable=False) # clave del material
type: Mapped[str] = mapped_column(String(15), nullable=False) # tipo
description: Mapped[str] = mapped_column(
String(256), nullable=False
) # descripción oficial (en español)
def __repr__(self):
return f"<MaterialType(key={self.key}, type={self.type}, description={self.description})>"

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -11,13 +10,12 @@ from typing import Any, Dict
router = APIRouter(prefix="/material-types")
@router.get("/", response_model=Dict[str, Any])
async def list_material_types(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(MaterialType)
@@ -27,23 +25,27 @@ async def list_material_types(
"items": [MaterialTypeDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{key}", response_model=MaterialTypeDTO)
async def get_material_type(key: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
async def get_material_type(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = db.query(MaterialType).filter(MaterialType.key == key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=MaterialTypeDTO, status_code=201)
async def create_material_type(
data: MaterialTypeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = MaterialType(**data.dict())
db.add(obj)
@@ -51,13 +53,13 @@ async def create_material_type(
db.refresh(obj)
return obj
@router.put("/{key}", response_model=MaterialTypeDTO)
async def update_material_type(
key: str,
data: MaterialTypeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(MaterialType).filter(MaterialType.key == key).first()
if not obj:
@@ -68,12 +70,12 @@ async def update_material_type(
db.refresh(obj)
return obj
@router.delete("/{key}", status_code=204)
async def delete_material_type(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(MaterialType).filter(MaterialType.key == key).first()
if not obj:

View File

@@ -32,5 +32,5 @@ seed = [
("MAQEQ", "MAQUINARIA Y EQUIPO", "ACTIVO FIJO"),
("MAQUI", "MAQUINARIA", "ACTIVO FIJO"),
("REFAC", "REFACCIONES", "ACTIVO FIJO"),
("TERR", "TERRRENOS" , "ACTIVO FIJO"),
]
("TERR", "TERRRENOS", "ACTIVO FIJO"),
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_material_types(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,28 @@ def test_list_material_types(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_material_type_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/material-types/invalid_key", headers=headers)
assert response.status_code == 404
def test_create_material_type_forbidden():
response = client.post("/material-types/", json={"key": "TST", "description": "Test"})
response = client.post(
"/material-types/", json={"key": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_update_material_type_forbidden():
response = client.put("/material-types/TST", json={"key": "TST", "description": "Test"})
response = client.put(
"/material-types/TST", json={"key": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_delete_material_type_forbidden():
response = client.delete("/material-types/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -1,9 +1,9 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class PaymentMethodDTO(BaseModel):
key: str = Field(..., min_length=1, max_length=2)
description: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -2,11 +2,12 @@ from sqlalchemy import String, PrimaryKeyConstraint
from sqlalchemy.orm import mapped_column, Mapped
from core.database import Base
class PaymentMethod(Base):
__tablename__ = "payment_methods" #GFormaPago
__tablename__ = "payment_methods" # GFormaPago
__table_args__ = (
PrimaryKeyConstraint("key", name="payment_methods_pkey"),
{"schema": "public"} # opcional
{"schema": "public"}, # opcional
)
key: Mapped[str] = mapped_column(String(2), nullable=False)

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -16,7 +15,7 @@ def list_payment_methods(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(PaymentMethod)
@@ -26,21 +25,27 @@ def list_payment_methods(
"items": [PaymentMethodDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{key}", response_model=PaymentMethodDTO)
def get_payment_method(key: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
def get_payment_method(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = db.query(PaymentMethod).filter(PaymentMethod.key == key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=PaymentMethodDTO, status_code=201)
def create_payment_method(
data: PaymentMethodDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = PaymentMethod(**data.dict())
db.add(obj)
@@ -48,12 +53,13 @@ def create_payment_method(
db.refresh(obj)
return obj
@router.put("/{key}", response_model=PaymentMethodDTO)
def update_payment_method(
key: str,
data: PaymentMethodDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(PaymentMethod).filter(PaymentMethod.key == key).first()
if not obj:
@@ -64,11 +70,12 @@ def update_payment_method(
db.refresh(obj)
return obj
@router.delete("/{key}", status_code=204)
def delete_payment_method(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(PaymentMethod).filter(PaymentMethod.key == key).first()
if not obj:

View File

@@ -10,7 +10,10 @@ seed = [
("18", "ESTIMULO FISCAL."),
("19", "OTROS MEDIOS DE GARANTIA."),
("2", "FIANZA."),
("20", "DEROGADA. --- (PAGO CONFORME AL ARTICULO 7 DE LA LEY DE INGRESOS DE LA FEDERACION, VIGENTE)"),
(
"20",
"DEROGADA. --- (PAGO CONFORME AL ARTICULO 7 DE LA LEY DE INGRESOS DE LA FEDERACION, VIGENTE)",
),
("21", "CRÉDITO EN IVA E IEPS."),
("22", "GARANTÍA EN IVA E IEPS."),
("4", "DEPOSITO EN CUENTA ADUANERA."),
@@ -19,4 +22,4 @@ seed = [
("7", "CARGO A PARTIDA PRESUPUESTAL GOBIERNO FEDERAL."),
("8", "FRANQUICIA."),
("9", "EXENTO DE PAGO."),
]
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_payment_methods(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,28 @@ def test_list_payment_methods(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_payment_method_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/payment-methods/invalid_key", headers=headers)
assert response.status_code == 404
def test_create_payment_method_forbidden():
response = client.post("/payment-methods/", json={"key": "TST", "description": "Test"})
response = client.post(
"/payment-methods/", json={"key": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_update_payment_method_forbidden():
response = client.put("/payment-methods/TST", json={"key": "TST", "description": "Test"})
response = client.put(
"/payment-methods/TST", json={"key": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_delete_payment_method_forbidden():
response = client.delete("/payment-methods/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -2,9 +2,9 @@ from pydantic import BaseModel, Field
from pydantic import ConfigDict
from typing import Optional
class PedimentoCodeDTO(BaseModel):
code: str = Field(..., min_length=1, max_length=3)
description: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -5,25 +5,23 @@ from core.database import Base
if TYPE_CHECKING:
from ..code_pedimento_regimens.models import CodePedimentoRegimen
class PedimentoCode(Base):
__tablename__ = "pedimento_codes" # GClavePed
__table_args__ = (
PrimaryKeyConstraint("code", name="pedimento_codes_pkey"),
{"schema": "public"} # esquema del anexo 22
{"schema": "public"}, # esquema del anexo 22
)
code: Mapped[str] = mapped_column(String(3), nullable=False)
code: Mapped[str] = mapped_column(String(3), nullable=False)
description: Mapped[str] = mapped_column(String(250), nullable=False)
# Relación con los regímenes asociados
#GClavePedRegimen
regimens: Mapped[List['CodePedimentoRegimen']] = relationship(
"CodePedimentoRegimen",
uselist=True,
back_populates="pedimento"
# GClavePedRegimen
regimens: Mapped[List["CodePedimentoRegimen"]] = relationship(
"CodePedimentoRegimen", uselist=True, back_populates="pedimento"
)
def __repr__(self):
return f"<PedimentoCode(code={self.code}, description={self.description})>"
return f"<PedimentoCode(code={self.code}, description={self.description})>"

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -16,7 +15,7 @@ def list_pedimento_codes(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(PedimentoCode)
@@ -26,21 +25,27 @@ def list_pedimento_codes(
"items": [PedimentoCodeDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{code}", response_model=PedimentoCodeDTO)
def get_pedimento_code(code: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
def get_pedimento_code(
code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = db.query(PedimentoCode).filter(PedimentoCode.code == code).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=PedimentoCodeDTO, status_code=201)
def create_pedimento_code(
data: PedimentoCodeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = PedimentoCode(**data.dict())
db.add(obj)
@@ -48,12 +53,13 @@ def create_pedimento_code(
db.refresh(obj)
return obj
@router.put("/{code}", response_model=PedimentoCodeDTO)
def update_pedimento_code(
code: str,
data: PedimentoCodeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(PedimentoCode).filter(PedimentoCode.code == code).first()
if not obj:
@@ -64,11 +70,12 @@ def update_pedimento_code(
db.refresh(obj)
return obj
@router.delete("/{code}", status_code=204)
def delete_pedimento_code(
code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(PedimentoCode).filter(PedimentoCode.code == code).first()
if not obj:

View File

@@ -3,27 +3,69 @@ seed = [
("A3", "REGULARIZACION DE MERCANCIAS (IMPORTACION DEFINITIVA)."),
("A4", "INTRODUCCION PARA DEPOSITO FISCAL (AGD)."),
("A5", "INTRODUCCION A DEPOSITO FISCAL EN LOCAL AUTORIZADO."),
("A6", "IMPORTACIÓN TEMPORAL DE BIENES DE ACTIVO FIJO POR PARTE DE EMPRESAS CON PITEX."),
("AD", "IMPORTACIÓN TEMPORAL DE MERCANCIAS DESTINADAS A CONVENCIONES Y CONGRESOS INTERNACIONALES (ARTICULO 106, FRACCION III, INCISO A) DE LA LEY)."),
(
"A6",
"IMPORTACIÓN TEMPORAL DE BIENES DE ACTIVO FIJO POR PARTE DE EMPRESAS CON PITEX.",
),
(
"AD",
"IMPORTACIÓN TEMPORAL DE MERCANCIAS DESTINADAS A CONVENCIONES Y CONGRESOS INTERNACIONALES (ARTICULO 106, FRACCION III, INCISO A) DE LA LEY).",
),
("AF", "IMPORTACION TEMPORAL DE BIENES DE ACTIVO FIJO (IMMEX)."),
("AJ", "IMPORTACION Y EXPORTACION TEMPORAL DE ENVASES DE MERCANCIAS (ARTICULOS 106, FRACCION II, INCISO B) Y 116, FRACCION II, INCISO A) DE LA LEY)."),
("BA", "IMPORTACION Y EXPORTACION TEMPORAL DE BIENES PARA SER RETORNADOS EN SU MISMO ESTADO. (ARTICULO 106, FRACCIONES II, INCISOS A) Y C), Y IV, INCISO B) DE LA LEY)."),
(
"AJ",
"IMPORTACION Y EXPORTACION TEMPORAL DE ENVASES DE MERCANCIAS (ARTICULOS 106, FRACCION II, INCISO B) Y 116, FRACCION II, INCISO A) DE LA LEY).",
),
(
"BA",
"IMPORTACION Y EXPORTACION TEMPORAL DE BIENES PARA SER RETORNADOS EN SU MISMO ESTADO. (ARTICULO 106, FRACCIONES II, INCISOS A) Y C), Y IV, INCISO B) DE LA LEY).",
),
("BB", "EXPORTACION, IMPORTACION Y RETORNOS VIRTUALES."),
("BC", "IMPORTACION Y EXPORTACION TEMPORAL DE MERCANCIAS DESTINADAS A EVENTOS CULTURALES O DEPORTIVOS (ARTICULO 106, FRACCION III, INCISO B DE LA LEY)."),
("BD", "IMPORTACION Y EXPORTACION TEMPORAL DE EQUIPO PARA FILMACION (ARTICULOS 106, FRACCION III, INCISO C) Y 116, FRACCION II INCISO D) DE LA LEY)."),
("BE", "IMPORTACION Y EXPORTACION TEMPORAL DE VEHICULOS DE PRUEBA (ARTICULO 106, FRACCION III, INCISO D) DE LA LEY)."),
("BF", "EXPORTACION TEMPORAL DE MERCANCIAS DESTINADAS A EXPOSICIONES, CONVENCIONES O EVENTOS CULTURALES O DEPORTIVOS (ARTICULO 116, FRACCION III DE LA LEY)."),
("BH", "IMPORTACION TEMPORAL DE CONTENEDORES, AVIONES, HELICOPTEROS, EMBARCACIONES Y CARROS DE FERROCARRIL (ARTICULO 106, FRACCION V, INCISOS A), B) Y E) DE LA LEY)."),
(
"BC",
"IMPORTACION Y EXPORTACION TEMPORAL DE MERCANCIAS DESTINADAS A EVENTOS CULTURALES O DEPORTIVOS (ARTICULO 106, FRACCION III, INCISO B DE LA LEY).",
),
(
"BD",
"IMPORTACION Y EXPORTACION TEMPORAL DE EQUIPO PARA FILMACION (ARTICULOS 106, FRACCION III, INCISO C) Y 116, FRACCION II INCISO D) DE LA LEY).",
),
(
"BE",
"IMPORTACION Y EXPORTACION TEMPORAL DE VEHICULOS DE PRUEBA (ARTICULO 106, FRACCION III, INCISO D) DE LA LEY).",
),
(
"BF",
"EXPORTACION TEMPORAL DE MERCANCIAS DESTINADAS A EXPOSICIONES, CONVENCIONES O EVENTOS CULTURALES O DEPORTIVOS (ARTICULO 116, FRACCION III DE LA LEY).",
),
(
"BH",
"IMPORTACION TEMPORAL DE CONTENEDORES, AVIONES, HELICOPTEROS, EMBARCACIONES Y CARROS DE FERROCARRIL (ARTICULO 106, FRACCION V, INCISOS A), B) Y E) DE LA LEY).",
),
("BI", "IMPORTACION TEMPORAL (ARTICULO 106, FRACCION III, INCISO E) DE LA LEY)."),
("BM", "EXPORTACION TEMPORAL DE MERCANCIAS PARA SU TRANSFORMACION, ELABORACION O REPARACION (ARTICULO 117 DE LA LEY)."),
("BO", "EXPORTACION TEMPORAL PARA REPARACION O SUSTITUCION Y RETORNO AL PAIS (IMMEX, RFE U OPERADOR ECONOMICO AUTORIZADO."),
("BP", "IMPORTACION Y EXPORTACION TEMPORAL DE MUESTRAS O MUESTRARIOS (ARTICULOS 106, FRACCION II, INCISO D) Y 116, FRACCION II, INCISO C) DE LA LEY)."),
(
"BM",
"EXPORTACION TEMPORAL DE MERCANCIAS PARA SU TRANSFORMACION, ELABORACION O REPARACION (ARTICULO 117 DE LA LEY).",
),
(
"BO",
"EXPORTACION TEMPORAL PARA REPARACION O SUSTITUCION Y RETORNO AL PAIS (IMMEX, RFE U OPERADOR ECONOMICO AUTORIZADO.",
),
(
"BP",
"IMPORTACION Y EXPORTACION TEMPORAL DE MUESTRAS O MUESTRARIOS (ARTICULOS 106, FRACCION II, INCISO D) Y 116, FRACCION II, INCISO C) DE LA LEY).",
),
("BR", "EXPORTACION TEMPORAL Y RETORNO DE MERCANCIAS FUNGIBLES."),
("C1", "IMPORTACION DEFINITIVA A LA FRANJA FRONTERIZA NORTE Y REGION FRONTERIZA AL AMPARO DEL „DECRETO DE LA FRANJA O REGION FRONTERIZA“ (DOF 24/12/2008 Y SUS POSTERIORES MODIFICACIONES)."),
(
"C1",
"IMPORTACION DEFINITIVA A LA FRANJA FRONTERIZA NORTE Y REGION FRONTERIZA AL AMPARO DEL „DECRETO DE LA FRANJA O REGION FRONTERIZA“ (DOF 24/12/2008 Y SUS POSTERIORES MODIFICACIONES).",
),
("C3", "EXTRACCION DE DEPOSITO FISCAL DE FRANJA O REGION FRONTERIZA (AGD)."),
("CT", "PEDIMENTO COMPLEMENTARIO."),
("D1", "RETORNO POR SUSTITUCION."),
("E1", "EXTRACCION DE DEPOSITO FISCAL DE BIENES QUE SERAN SUJETOS A TRANSFORMACION, ELABORACION O REPARACION (AGD)."),
(
"E1",
"EXTRACCION DE DEPOSITO FISCAL DE BIENES QUE SERAN SUJETOS A TRANSFORMACION, ELABORACION O REPARACION (AGD).",
),
("E2", "EXTRACCION DE DEPOSITO FISCAL DE BIENES DE ACTIVO FIJO (AGD)."),
("E3", "EXTRACCION DE DEPOSITO FISCAL EN LOCAL AUTORIZADO (INSUMOS)."),
("E4", "EXTRACCION DE DEPOSITO FISCAL EN LOCAL AUTORIZADO (ACTIVO FIJO)."),
@@ -31,49 +73,112 @@ seed = [
("F3", "EXTRACCION DE DEPOSITO FISCAL (IA)."),
("F4", "CAMBIO DE REGIMEN DE INSUMOS O DE MERCANCIA EXPORTADA TEMPORALMENTE."),
("F5", "CAMBIO DE REGIMEN DE MERCANCÍAS DE IMPORTACIÓN TEMPORAL A DEFINITIVA."),
("F8", "INTRODUCCION Y EXTRACCION DE DEPOSITO FISCAL DE MERCANCIAS NACIONALES O NACIONALIZADAS EN TIENDAS LIBRES DE IMPUESTOS (DUTY FREE)."),
("F9", "INTRODUCCION Y EXTRACCION DE DEPOSITO FISCAL DE MERCANCIAS EXTRANJERAS PARA EXPOSICION Y VENTA DE MERCANCIAS EN TIENDAS LIBRES DE IMPUESTOS (DUTY FREE)."),
(
"F8",
"INTRODUCCION Y EXTRACCION DE DEPOSITO FISCAL DE MERCANCIAS NACIONALES O NACIONALIZADAS EN TIENDAS LIBRES DE IMPUESTOS (DUTY FREE).",
),
(
"F9",
"INTRODUCCION Y EXTRACCION DE DEPOSITO FISCAL DE MERCANCIAS EXTRANJERAS PARA EXPOSICION Y VENTA DE MERCANCIAS EN TIENDAS LIBRES DE IMPUESTOS (DUTY FREE).",
),
("G1", "EXTRACCION DE DEPOSITO FISCAL (AGD)."),
("G2", "EXTRACCION DE DEPOSITO FISCAL EN LOCAL AUTORIZADO PARA SU IMPORTACION DEFINITIVA."),
("G6", "INFORME DE EXTRACCION DE DEPOSITO FISCAL DE MERCANCIAS NACIONALES O NACIONALIZADAS VENDIDAS EN TIENDAS LIBRES DE IMPUESTOS (DUTY FREE)."),
("G7", "INFORME DE EXTRACCION DE DEPOSITO FISCAL DE MERCANCIAS EXTRANJERAS VENDIDAS EN TIENDAS LIBRES DE IMPUESTOS (DUTY FREE)."),
(
"G2",
"EXTRACCION DE DEPOSITO FISCAL EN LOCAL AUTORIZADO PARA SU IMPORTACION DEFINITIVA.",
),
(
"G6",
"INFORME DE EXTRACCION DE DEPOSITO FISCAL DE MERCANCIAS NACIONALES O NACIONALIZADAS VENDIDAS EN TIENDAS LIBRES DE IMPUESTOS (DUTY FREE).",
),
(
"G7",
"INFORME DE EXTRACCION DE DEPOSITO FISCAL DE MERCANCIAS EXTRANJERAS VENDIDAS EN TIENDAS LIBRES DE IMPUESTOS (DUTY FREE).",
),
("G8", "REINCORPORAR AL MERCADO NACIONAL (RFE)."),
("G9", "TRANSFERENCIA DE MERCANCÍAS DE RECINTO FISCALIZADO ESTRATEGICO NO COLINDANTE CON LA ADUANA (RETIRO VIRTUAL PARA IMPORTACIÓN DEFINTIVA POR RESIDENTES EN TERRITORIO NACIONAL)."),
(
"G9",
"TRANSFERENCIA DE MERCANCÍAS DE RECINTO FISCALIZADO ESTRATEGICO NO COLINDANTE CON LA ADUANA (RETIRO VIRTUAL PARA IMPORTACIÓN DEFINTIVA POR RESIDENTES EN TERRITORIO NACIONAL).",
),
("GC", "GLOBAL COMPLEMENTARIO."),
("H1", "RETORNO DE MERCANCIAS EN SU MISMO ESTADO."),
("H8", "RETORNO DE ENVASES."),
("I1", "IMPORTACION, EXPORTACION Y RETORNO DE MERCANCIAS ELABORADAS, TRANSFORMADAS O REPARADAS."),
("IN", "IMPORTACION TEMPORAL DE BIENES QUE SERAN SUJETOS A TRANSFORMACION, ELABORACION O REPARACION (IMMEX)."),
("J3", "RETORNO Y EXPORTACION DE INSUMOS ELABORADOS O TRANSFORMADOS EN RECINTO FISCALIZADO."),
(
"I1",
"IMPORTACION, EXPORTACION Y RETORNO DE MERCANCIAS ELABORADAS, TRANSFORMADAS O REPARADAS.",
),
(
"IN",
"IMPORTACION TEMPORAL DE BIENES QUE SERAN SUJETOS A TRANSFORMACION, ELABORACION O REPARACION (IMMEX).",
),
(
"J3",
"RETORNO Y EXPORTACION DE INSUMOS ELABORADOS O TRANSFORMADOS EN RECINTO FISCALIZADO.",
),
("J4", "RETORNO DE MERCANCIAS EXTRANJERAS (RFE)."),
("K1", "DESISTIMIENTO DE REGIMEN Y RETORNO DE MERCANCIAS POR DEVOLUCION."),
("K2", "EXTRACCION DE DEPOSITO FISCAL POR DESISTIMIENTO O TRANSFERENCIAS (AGD)."),
("K3", "EXTRACCION DE DEPOSITO FISCAL EN LOCAL AUTORIZADO PARA RETORNO O TRANSFERENCIA."),
(
"K3",
"EXTRACCION DE DEPOSITO FISCAL EN LOCAL AUTORIZADO PARA RETORNO O TRANSFERENCIA.",
),
("L1", "PEQUEÑA IMPORTACION DEFINITIVA."),
("M1", "INTRODUCCION Y EXPORTACION DE INSUMOS."),
("M2", "INTRODUCCION Y EXPORTACION DE MAQUINARIA Y EQUIPO."),
("M3", "INTRODUCCION DE MERCANCIAS (RFE)."),
("M4", "INTRODUCCION DE ACTIVO FIJO (RFE)."),
("M5", "INTRODUCCION DE MERCANCIA NACIONAL O NACIONALIZADA (RFE)."),
("P1", "REEXPEDICION DE MERCANCIAS DE FRANJA FRONTERIZA O REGION FRONTERIZA AL INTERIOR DEL PAIS."),
(
"P1",
"REEXPEDICION DE MERCANCIAS DE FRANJA FRONTERIZA O REGION FRONTERIZA AL INTERIOR DEL PAIS.",
),
("R1", "RECTIFICACION DE PEDIMENTOS."),
("RT", "RETORNO DE MERCANCIAS (IMMEX)."),
("S2", "IMPORTACION Y EXPORTACION DE MERCANCIAS PARA RETORNAR EN SU MISMO ESTADO (ARTICULO 86 DE LA LEY)."),
(
"S2",
"IMPORTACION Y EXPORTACION DE MERCANCIAS PARA RETORNAR EN SU MISMO ESTADO (ARTICULO 86 DE LA LEY).",
),
("T1", "IMPORTACION Y EXPORTACION POR EMPRESAS DE MENSAJERIA."),
("T3", "TRANSITO INTERNO."),
("T6", "TRANSITO INTERNACIONAL POR TERRITORIO EXTRANJERO."),
("T7", "TRANSITO INTERNACIONAL POR TERRITORIO NACIONAL."),
("T9", "TRANSITO INTERNACIONAL DE TRANSMIGRANTES."),
("V1", "TRANSFERENCIAS DE MERCANCIAS (IMPORTACION TEMPORAL VIRTUAL; INTRODUCCION VIRTUAL A DEPOSITO FISCAL O A RECINTO FISCALIZADO ESTRATEGICO; RETORNO VIRTUAL; EXPORTACION VIRTUAL DE PROVEEDORES NACIONALES)."),
("V2", "TRANSFERENCIAS DE MERCANCIAS IMPORTADAS CON CUENTA ADUANERA (EXPORTACION E IMPORTACION VIRTUAL)."),
("V3", "EXTRACCION DE DEPOSITO FISCAL DE BIENES PARA SU RETORNO O EXPORTACION VIRTUAL (IA)."),
("V4", "RETORNO VIRTUAL DERIVADO DE LA CONSTANCIA DE TRANSFERENCIA DE MERCANCIAS (IA)."),
("V5", "TRANSFERENCIAS DE MERCANCIAS DE EMPRESAS CERTIFICADAS (RETORNO VIRTUAL PARA IMPORTACION DEFINITIVA)."),
("V6", "TRANSFERENCIAS DE MERCANCIAS SUJETAS A CUPO (IMPORTACION DEFINITIVA Y RETORNO VIRTUAL)."),
("V7", "TRANSFERENCIAS DEL SECTOR AZUCARERO (EXPORTACION VIRTUAL E IMPORTACION TEMPORAL VIRTUAL)."),
("V8", "TRANSFERENCIA DE MERCANCIAS EN DEPOSITO FISCAL PARA LA EXPOSICION Y VENTA DE MERCANCIAS EXTRANJERAS, NACIONALES Y NACIONALIZADAS DE TIENDAS LIBRES DE IMPUESTOS (DUTY FREE)."),
(
"V1",
"TRANSFERENCIAS DE MERCANCIAS (IMPORTACION TEMPORAL VIRTUAL; INTRODUCCION VIRTUAL A DEPOSITO FISCAL O A RECINTO FISCALIZADO ESTRATEGICO; RETORNO VIRTUAL; EXPORTACION VIRTUAL DE PROVEEDORES NACIONALES).",
),
(
"V2",
"TRANSFERENCIAS DE MERCANCIAS IMPORTADAS CON CUENTA ADUANERA (EXPORTACION E IMPORTACION VIRTUAL).",
),
(
"V3",
"EXTRACCION DE DEPOSITO FISCAL DE BIENES PARA SU RETORNO O EXPORTACION VIRTUAL (IA).",
),
(
"V4",
"RETORNO VIRTUAL DERIVADO DE LA CONSTANCIA DE TRANSFERENCIA DE MERCANCIAS (IA).",
),
(
"V5",
"TRANSFERENCIAS DE MERCANCIAS DE EMPRESAS CERTIFICADAS (RETORNO VIRTUAL PARA IMPORTACION DEFINITIVA).",
),
(
"V6",
"TRANSFERENCIAS DE MERCANCIAS SUJETAS A CUPO (IMPORTACION DEFINITIVA Y RETORNO VIRTUAL).",
),
(
"V7",
"TRANSFERENCIAS DEL SECTOR AZUCARERO (EXPORTACION VIRTUAL E IMPORTACION TEMPORAL VIRTUAL).",
),
(
"V8",
"TRANSFERENCIA DE MERCANCIAS EN DEPOSITO FISCAL PARA LA EXPOSICION Y VENTA DE MERCANCIAS EXTRANJERAS, NACIONALES Y NACIONALIZADAS DE TIENDAS LIBRES DE IMPUESTOS (DUTY FREE).",
),
("V9", "TRANSFERENCIAS DE MERCANCIAS POR DONACION"),
("VD", "VIRTUALES DIVERSOS."),
("VF", "IMPORTACION DEFINITIVA DE VEHICULOS USADOS A LA FRANJA O REGION FRONTERIZA NORTE."),
("VU", "IMPORTACION DEFINITIVA DE VEHICULOS USADOS.")
]
(
"VF",
"IMPORTACION DEFINITIVA DE VEHICULOS USADOS A LA FRANJA O REGION FRONTERIZA NORTE.",
),
("VU", "IMPORTACION DEFINITIVA DE VEHICULOS USADOS."),
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_pedimento_codes(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,28 @@ def test_list_pedimento_codes(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_pedimento_code_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/pedimento-codes/invalid_code", headers=headers)
assert response.status_code == 404
def test_create_pedimento_code_forbidden():
response = client.post("/pedimento-codes/", json={"code": "TST", "description": "Test"})
response = client.post(
"/pedimento-codes/", json={"code": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_update_pedimento_code_forbidden():
response = client.put("/pedimento-codes/TST", json={"code": "TST", "description": "Test"})
response = client.put(
"/pedimento-codes/TST", json={"code": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_delete_pedimento_code_forbidden():
response = client.delete("/pedimento-codes/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -2,6 +2,7 @@ from pydantic import BaseModel, Field
from pydantic import ConfigDict
from typing import List
class RegimenPedimentoDTO(BaseModel):
code: str = Field(..., min_length=1, max_length=3)
description: str

View File

@@ -6,22 +6,25 @@ from core.database import Base
if TYPE_CHECKING:
from ..code_pedimento_regimens.models import CodePedimentoRegimen
class RegimenPedimento(Base):
__tablename__ = "pedimento_regimens" #GRegimenPed
__tablename__ = "pedimento_regimens" # GRegimenPed
__table_args__ = (
PrimaryKeyConstraint("code", name="pedimento_regimens_pkey"),
{"schema": "public"}
{"schema": "public"},
)
code: Mapped[str] = mapped_column(String(3), nullable=False) # código tipo "01", "31"
description: Mapped[str] = mapped_column(String(100), nullable=False) # nombre legal en español
code: Mapped[str] = mapped_column(
String(3), nullable=False
) # código tipo "01", "31"
description: Mapped[str] = mapped_column(
String(100), nullable=False
) # nombre legal en español
# Relación con Claves de Pedimento
#GClavePedRegimen
claves_pedimento: Mapped[List['CodePedimentoRegimen']] = relationship(
"CodePedimentoRegimen",
uselist=True,
back_populates="regimen"
# GClavePedRegimen
claves_pedimento: Mapped[List["CodePedimentoRegimen"]] = relationship(
"CodePedimentoRegimen", uselist=True, back_populates="regimen"
)
def __repr__(self):

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -16,7 +15,7 @@ def list_pedimento_regimens(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(RegimenPedimento)
@@ -26,21 +25,27 @@ def list_pedimento_regimens(
"items": [RegimenPedimentoDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{key}", response_model=RegimenPedimentoDTO)
def get_pedimento_regimen(key: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
def get_pedimento_regimen(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = db.query(RegimenPedimento).filter(RegimenPedimento.code == key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return RegimenPedimentoDTO.model_validate(obj)
@router.post("/", response_model=RegimenPedimentoDTO, status_code=201)
def create_pedimento_regimen(
data: RegimenPedimentoDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = RegimenPedimento(**data.model_dump())
db.add(obj)
@@ -48,12 +53,13 @@ def create_pedimento_regimen(
db.refresh(obj)
return RegimenPedimentoDTO.model_validate(obj)
@router.put("/{key}", response_model=RegimenPedimentoDTO)
def update_pedimento_regimen(
key: str,
data: RegimenPedimentoDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(RegimenPedimento).filter(RegimenPedimento.code == key).first()
if not obj:
@@ -64,11 +70,12 @@ def update_pedimento_regimen(
db.refresh(obj)
return RegimenPedimentoDTO.model_validate(obj)
@router.delete("/{key}", status_code=204)
def delete_pedimento_regimen(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(RegimenPedimento).filter(RegimenPedimento.code == key).first()
if not obj:

View File

@@ -1,12 +1,18 @@
seed = [
("DFI", "DEPOSITO FISCAL."),
("ETE", "TEMPORALES DE EXPORTACION PARA ELABORACION, TRANSFORMACION O REPARACION."),
("ETR", "TEMPORALES DE EXPORTACION PARA RETORNAR AL PAIS EN EL MISMO ESTADO."),
("EXD", "DEFINITIVO DE EXPORTACIÓN."),
("IMD", "DEFINITIVO DE IMPORTACIÓN."),
("ITE", "TEMPORALES DE IMPORTACION PARA ELABORACION, TRANSFORMACION O REPARACION PARA EMPRESAS CON PROGRAMA I"),
("ITR", "TEMPORALES DE IMPORTACION PARA RETORNAR AL EXTRANJERO EN EL MISMO ESTADO."),
("RFE", "ELABORACION, TRANSFORMACION O REPARACION EN RECINTO FISCALIZADO."),
("RFS", "RECINTO FISCALIZADO ESTRATEGICO."),
("TRA", "TRANSITOS.")
]
("DFI", "DEPOSITO FISCAL."),
("ETE", "TEMPORALES DE EXPORTACION PARA ELABORACION, TRANSFORMACION O REPARACION."),
("ETR", "TEMPORALES DE EXPORTACION PARA RETORNAR AL PAIS EN EL MISMO ESTADO."),
("EXD", "DEFINITIVO DE EXPORTACIÓN."),
("IMD", "DEFINITIVO DE IMPORTACIÓN."),
(
"ITE",
"TEMPORALES DE IMPORTACION PARA ELABORACION, TRANSFORMACION O REPARACION PARA EMPRESAS CON PROGRAMA I",
),
(
"ITR",
"TEMPORALES DE IMPORTACION PARA RETORNAR AL EXTRANJERO EN EL MISMO ESTADO.",
),
("RFE", "ELABORACION, TRANSFORMACION O REPARACION EN RECINTO FISCALIZADO."),
("RFS", "RECINTO FISCALIZADO ESTRATEGICO."),
("TRA", "TRANSITOS."),
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_pedimento_regimens(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,28 @@ def test_list_pedimento_regimens(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_pedimento_regimen_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/pedimento-regimens/invalid_key", headers=headers)
assert response.status_code == 404
def test_create_pedimento_regimen_forbidden():
response = client.post("/pedimento-regimens/", json={"code": "TST", "description": "Test"})
response = client.post(
"/pedimento-regimens/", json={"code": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_update_pedimento_regimen_forbidden():
response = client.put("/pedimento-regimens/TST", json={"code": "TST", "description": "Test"})
response = client.put(
"/pedimento-regimens/TST", json={"code": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_delete_pedimento_regimen_forbidden():
response = client.delete("/pedimento-regimens/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -2,6 +2,7 @@
Router principal de API v1
Agrega todos los módulos de la aplicación
"""
from fastapi import APIRouter
from .pedimento_codes.routes import router as pedimento_codes_router
@@ -26,20 +27,86 @@ from .incoterms.routes import router as incoterms_router
router = APIRouter()
# Registrar módulos
router.include_router(pedimento_codes_router, prefix="/refrence_data", tags=["public / refrence_data / pedimento_codes"])
router.include_router(payment_methods_router, prefix="/refrence_data", tags=["public / refrence_data / payment_methods"])
router.include_router(containers_router, prefix="/refrence_data", tags=["public / refrence_data / containers"])
router.include_router(countries_router, prefix="/refrence_data", tags=["public / refrence_data / countries"])
router.include_router(material_types_router, prefix="/refrence_data", tags=["public / refrence_data / material_types"])
router.include_router(currency_types_router, prefix="/refrence_data", tags=["public / refrence_data / currency_types"])
router.include_router(states_router, prefix="/refrence_data", tags=["public / refrence_data / states"])
router.include_router(transport_types_router, prefix="/refrence_data", tags=["public / refrence_data / transport_types"])
router.include_router(customs_warehouses_router, prefix="/refrence_data", tags=["public / refrence_data / customs_warehouses"])
router.include_router(valuation_methods_router, prefix="/refrence_data", tags=["public / refrence_data / valuation_methods"])
router.include_router(sectors_router, prefix="/refrence_data", tags=["public / public / refrence_data / sectors"])
router.include_router(transport_modes_router, prefix="/refrence_data", tags=["public / refrence_data / transport_modes"])
router.include_router(customs_sections_router, prefix="/refrence_data", tags=["public / refrence_data / customs_sections"])
router.include_router(invoice_types_router, prefix="/refrence_data", tags=["public / refrence_data / invoice_types"])
router.include_router(code_pedimento_regimens_router, prefix="/refrence_data", tags=["public / refrence_data / code_pedimento_regimens"])
router.include_router(pedimento_regimens_router, prefix="/refrence_data", tags=["public / refrence_data / pedimento_regimens"])
router.include_router(incoterms_router, prefix="/refrence_data", tags=["public / refrence_data / incoterms"])
router.include_router(
pedimento_codes_router,
prefix="/refrence_data",
tags=["public / refrence_data / pedimento_codes"],
)
router.include_router(
payment_methods_router,
prefix="/refrence_data",
tags=["public / refrence_data / payment_methods"],
)
router.include_router(
containers_router,
prefix="/refrence_data",
tags=["public / refrence_data / containers"],
)
router.include_router(
countries_router,
prefix="/refrence_data",
tags=["public / refrence_data / countries"],
)
router.include_router(
material_types_router,
prefix="/refrence_data",
tags=["public / refrence_data / material_types"],
)
router.include_router(
currency_types_router,
prefix="/refrence_data",
tags=["public / refrence_data / currency_types"],
)
router.include_router(
states_router, prefix="/refrence_data", tags=["public / refrence_data / states"]
)
router.include_router(
transport_types_router,
prefix="/refrence_data",
tags=["public / refrence_data / transport_types"],
)
router.include_router(
customs_warehouses_router,
prefix="/refrence_data",
tags=["public / refrence_data / customs_warehouses"],
)
router.include_router(
valuation_methods_router,
prefix="/refrence_data",
tags=["public / refrence_data / valuation_methods"],
)
router.include_router(
sectors_router,
prefix="/refrence_data",
tags=["public / public / refrence_data / sectors"],
)
router.include_router(
transport_modes_router,
prefix="/refrence_data",
tags=["public / refrence_data / transport_modes"],
)
router.include_router(
customs_sections_router,
prefix="/refrence_data",
tags=["public / refrence_data / customs_sections"],
)
router.include_router(
invoice_types_router,
prefix="/refrence_data",
tags=["public / refrence_data / invoice_types"],
)
router.include_router(
code_pedimento_regimens_router,
prefix="/refrence_data",
tags=["public / refrence_data / code_pedimento_regimens"],
)
router.include_router(
pedimento_regimens_router,
prefix="/refrence_data",
tags=["public / refrence_data / pedimento_regimens"],
)
router.include_router(
incoterms_router,
prefix="/refrence_data",
tags=["public / refrence_data / incoterms"],
)

View File

@@ -1,10 +1,10 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class SectorDTO(BaseModel):
key: str = Field(..., min_length=1, max_length=8)
description: str
authorized: int
model_config = ConfigDict(from_attributes=True)

View File

@@ -2,16 +2,21 @@ from sqlalchemy import String, SmallInteger, PrimaryKeyConstraint
from sqlalchemy.orm import mapped_column, Mapped
from core.database import Base
class Sector(Base):
__tablename__ = "sectors" #GSectores
__tablename__ = "sectors" # GSectores
__table_args__ = (
PrimaryKeyConstraint("key", name="sectors_pkey"),
{"schema": "public"} # opcional
{"schema": "public"}, # opcional
)
key: Mapped[str] = mapped_column(String(8), nullable=False) # clave del sector
description: Mapped[str] = mapped_column(String(150), nullable=False) # descripción oficial (en español)
authorized: Mapped[SmallInteger] = mapped_column(SmallInteger) # 1 = autorizado, 0 = no autorizado
key: Mapped[str] = mapped_column(String(8), nullable=False) # clave del sector
description: Mapped[str] = mapped_column(
String(150), nullable=False
) # descripción oficial (en español)
authorized: Mapped[SmallInteger] = mapped_column(
SmallInteger
) # 1 = autorizado, 0 = no autorizado
def __repr__(self):
return f"<Sector(key={self.key}, description={self.description}, authorized={self.authorized})>"

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -16,7 +15,7 @@ def list_sectors(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(Sector)
@@ -26,21 +25,27 @@ def list_sectors(
"items": [SectorDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{key}", response_model=SectorDTO)
def get_sector(key: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
def get_sector(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = db.query(Sector).filter(Sector.key == key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=SectorDTO, status_code=201)
def create_sector(
data: SectorDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = Sector(**data.dict())
db.add(obj)
@@ -48,12 +53,13 @@ def create_sector(
db.refresh(obj)
return obj
@router.put("/{key}", response_model=SectorDTO)
def update_sector(
key: str,
data: SectorDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(Sector).filter(Sector.key == key).first()
if not obj:
@@ -64,11 +70,12 @@ def update_sector(
db.refresh(obj)
return obj
@router.delete("/{key}", status_code=204)
def delete_sector(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(Sector).filter(Sector.key == key).first()
if not obj:

View File

@@ -1,8 +1,16 @@
seed = [
("I", "INDUSTRIA ELECTRICA", "0"),
("II", "INDUSTRIA ELECTRONICA", "0"),
("IIa", "PARA LOS BIENES A QUE SE REFIERE LA FRACCION II, INCISO a) o b), DE ARTICULO 4to DE ESTE DECRETO.", "0"),
("IIb", "PARA LOS BIENES A QUE SE REFIERE LA FRACCION II, INCISO b), DE ARTICULO 4to DE ESTE DECRETO.", "0"),
(
"IIa",
"PARA LOS BIENES A QUE SE REFIERE LA FRACCION II, INCISO a) o b), DE ARTICULO 4to DE ESTE DECRETO.",
"0",
),
(
"IIb",
"PARA LOS BIENES A QUE SE REFIERE LA FRACCION II, INCISO b), DE ARTICULO 4to DE ESTE DECRETO.",
"0",
),
("III", "INDUSTRIA DEL MUEBLE", "0"),
("IV", "INDUSTRIA DEL JUGUETE, JUEGOS DE RECREO Y ARTICULOS DEPORTIVOS", "0"),
("IX", "INDUSTRIA DE MAQUINARIA AGRICOLA", "0"),
@@ -18,9 +26,21 @@ seed = [
("XIX", "INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES", "0"),
("XIXa", "INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES", "0"),
("XIXb", "INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES", "0"),
("XV", "INDUSTRIA DEL TRANSPORTE, EXCEPTO EL SECTOR DE LA INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES", "0"),
("XVa", "INDUSTRIA DEL TRANSPORTE, EXCEPTO EL SECTOR DE LA INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES.", "0"),
("XVb", "INDUSTRIA DEL TRANSPORTE, EXCEPTO EL SECTOR DE LA INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES.", "0"),
(
"XV",
"INDUSTRIA DEL TRANSPORTE, EXCEPTO EL SECTOR DE LA INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES",
"0",
),
(
"XVa",
"INDUSTRIA DEL TRANSPORTE, EXCEPTO EL SECTOR DE LA INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES.",
"0",
),
(
"XVb",
"INDUSTRIA DEL TRANSPORTE, EXCEPTO EL SECTOR DE LA INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES.",
"0",
),
("XVI", "INDUSTRIA DEL PAPEL Y CARTON", "0"),
("XVII", "INDUSTRIA DE LA MADERA", "0"),
("XVIII", "INDUSTRIA DEL CUERO Y PIELES", "0"),
@@ -32,4 +52,4 @@ seed = [
("XXe", "INDUSTRIA TEXTIL Y DE LA CONFECCION", "0"),
("XXI", "INDUSTRIA DE CHOCOLATES, DULCES Y SIMILARES", "0"),
("XXII", "INDUSTRIA DEL CAFE", "0"),
]
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_sectors(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,24 @@ def test_list_sectors(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_sector_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/sectors/invalid_key", headers=headers)
assert response.status_code == 404
def test_create_sector_forbidden():
response = client.post("/sectors/", json={"key": "TST", "description": "Test"})
assert response.status_code in (403, 405, 404)
def test_update_sector_forbidden():
response = client.put("/sectors/TST", json={"key": "TST", "description": "Test"})
assert response.status_code in (403, 405, 404)
def test_delete_sector_forbidden():
response = client.delete("/sectors/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -2,6 +2,7 @@ from pydantic import BaseModel, Field
from pydantic import ConfigDict
from typing import Optional
class StateDTO(BaseModel):
m3_key: str = Field(..., min_length=1, max_length=3)
description: str
@@ -9,4 +10,3 @@ class StateDTO(BaseModel):
ame_key: Optional[str] = None
model_config = ConfigDict(from_attributes=True)

View File

@@ -3,15 +3,18 @@ from sqlalchemy import String, PrimaryKeyConstraint
from sqlalchemy.orm import mapped_column, Mapped
from core.database import Base
class State(Base):
__tablename__ = "states" #GEstados
__tablename__ = "states" # GEstados
__table_args__ = (
PrimaryKeyConstraint('m3_key', 'description', name='states_pkey'),
{"schema": "public"}
PrimaryKeyConstraint("m3_key", "description", name="states_pkey"),
{"schema": "public"},
)
m3_key: Mapped[str] = mapped_column(String(3), nullable=False)
description: Mapped[str] = mapped_column(String(50), nullable=False) # valor legal en español
description: Mapped[str] = mapped_column(
String(50), nullable=False
) # valor legal en español
mex_key: Mapped[Optional[str]] = mapped_column(String(3))
ame_key: Mapped[Optional[str]] = mapped_column(String(2))

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -11,13 +10,12 @@ from typing import Any, Dict
router = APIRouter(prefix="/states")
@router.get("/", response_model=Dict[str, Any])
async def list_states(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(State)
@@ -27,23 +25,27 @@ async def list_states(
"items": [StateDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{m3_key}", response_model=StateDTO)
async def get_state(m3_key: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
async def get_state(
m3_key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = db.query(State).filter(State.m3_key == m3_key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=StateDTO, status_code=201)
async def create_state(
data: StateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = State(**data.dict())
@@ -52,13 +54,13 @@ async def create_state(
db.refresh(obj)
return obj
@router.put("/{m3_key}", response_model=StateDTO)
async def update_state(
m3_key: str,
data: StateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(State).filter(State.m3_key == m3_key).first()
@@ -70,12 +72,12 @@ async def update_state(
db.refresh(obj)
return obj
@router.delete("/{m3_key}", status_code=204)
async def delete_state(
m3_key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(State).filter(State.m3_key == m3_key).first()

View File

@@ -1,3 +1 @@
seed = [
]
seed = []

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_states(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,24 @@ def test_list_states(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_state_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/states/invalid_key", headers=headers)
assert response.status_code == 404
def test_create_state_forbidden():
response = client.post("/states/", json={"key": "TST", "description": "Test"})
assert response.status_code in (403, 405, 404)
def test_update_state_forbidden():
response = client.put("/states/TST", json={"key": "TST", "description": "Test"})
assert response.status_code in (403, 405, 404)
def test_delete_state_forbidden():
response = client.delete("/states/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -1,9 +1,9 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class TransportModeDTO(BaseModel):
key: str = Field(..., min_length=1, max_length=3)
name: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -2,14 +2,15 @@ from sqlalchemy import String, PrimaryKeyConstraint
from sqlalchemy.orm import mapped_column, Mapped
from core.database import Base
class TransportMode(Base):
__tablename__ = "transport_modes" #GModTransporte
__tablename__ = "transport_modes" # GModTransporte
__table_args__ = (
PrimaryKeyConstraint("key", name="transport_modes_pkey"),
{"schema": "public"} # opcional
{"schema": "public"}, # opcional
)
key: Mapped[str] = mapped_column(String(3), nullable=False)
key: Mapped[str] = mapped_column(String(3), nullable=False)
name: Mapped[str] = mapped_column(String(30), nullable=False)
def __repr__(self):

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -11,12 +10,11 @@ from typing import Any, Dict
router = APIRouter(prefix="/transport-modes")
@router.get("/", response_model=Dict[str, Any])
async def list_transport_modes(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db)
db: Session = Depends(get_core_db),
):
skip = (page - 1) * page_size
query = db.query(TransportMode)
@@ -26,10 +24,10 @@ async def list_transport_modes(
"items": [TransportModeDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{key}", response_model=TransportModeDTO)
async def get_transport_mode(key: str, db: Session = Depends(get_core_db)):
obj = db.query(TransportMode).filter(TransportMode.key == key).first()
@@ -37,12 +35,12 @@ async def get_transport_mode(key: str, db: Session = Depends(get_core_db)):
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=TransportModeDTO, status_code=201)
async def create_transport_mode(
data: TransportModeDTO,
db: Session = Depends(get_core_db),
user=Depends(get_current_user)
user=Depends(get_current_user),
):
obj = TransportMode(**data.dict())
db.add(obj)
@@ -50,13 +48,13 @@ async def create_transport_mode(
db.refresh(obj)
return obj
@router.put("/{key}", response_model=TransportModeDTO)
async def update_transport_mode(
key: str,
data: TransportModeDTO,
db: Session = Depends(get_core_db),
user=Depends(get_current_user)
user=Depends(get_current_user),
):
obj = db.query(TransportMode).filter(TransportMode.key == key).first()
if not obj:
@@ -67,12 +65,10 @@ async def update_transport_mode(
db.refresh(obj)
return obj
@router.delete("/{key}", status_code=204)
async def delete_transport_mode(
key: str,
db: Session = Depends(get_core_db),
user=Depends(get_current_user)
key: str, db: Session = Depends(get_core_db), user=Depends(get_current_user)
):
obj = db.query(TransportMode).filter(TransportMode.key == key).first()
if not obj:

View File

@@ -9,4 +9,4 @@ seed = [
("40", "AIR"),
("41", "AIR CONTAINER"),
("50", "MAIL"),
]
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_transport_modes(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,28 @@ def test_list_transport_modes(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_transport_mode_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/transport-modes/invalid_key", headers=headers)
assert response.status_code == 404
def test_create_transport_mode_forbidden():
response = client.post("/transport-modes/", json={"key": "TST", "description": "Test"})
response = client.post(
"/transport-modes/", json={"key": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_update_transport_mode_forbidden():
response = client.put("/transport-modes/TST", json={"key": "TST", "description": "Test"})
response = client.put(
"/transport-modes/TST", json={"key": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_delete_transport_mode_forbidden():
response = client.delete("/transport-modes/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -1,9 +1,9 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class TransportTypeDTO(BaseModel):
transport_code: str = Field(..., min_length=1, max_length=2)
description: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -4,14 +4,18 @@ from core.database import Base
class TransportType(Base):
__tablename__ = "transport_types" #GTiposTransporte
__tablename__ = "transport_types" # GTiposTransporte
__table_args__ = (
PrimaryKeyConstraint("transport_code", name="transport_types_pkey"),
{"schema": "public"}
{"schema": "public"},
)
transport_code: Mapped[str] = mapped_column(String(2), nullable=False) # código SAT o interno
description: Mapped[str] = mapped_column(String(100), nullable=False) # descripción del medio de transporte
transport_code: Mapped[str] = mapped_column(
String(2), nullable=False
) # código SAT o interno
description: Mapped[str] = mapped_column(
String(100), nullable=False
) # descripción del medio de transporte
def __repr__(self):
return f"<TransportType(code={self.transport_code}, description={self.description})>"

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -15,7 +14,7 @@ router = APIRouter(prefix="/transport-types")
def list_transport_types(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db)
db: Session = Depends(get_core_db),
):
skip = (page - 1) * page_size
query = db.query(TransportType)
@@ -25,21 +24,27 @@ def list_transport_types(
"items": [TransportTypeDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{transport_code}", response_model=TransportTypeDTO)
def get_transport_type(transport_code: str, db: Session = Depends(get_core_db)):
obj = db.query(TransportType).filter(TransportType.transport_code == transport_code).first()
obj = (
db.query(TransportType)
.filter(TransportType.transport_code == transport_code)
.first()
)
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=TransportTypeDTO, status_code=201)
def create_transport_type(
data: TransportTypeDTO,
db: Session = Depends(get_core_db),
user=Depends(get_current_user)
user=Depends(get_current_user),
):
obj = TransportType(**data.dict())
db.add(obj)
@@ -47,14 +52,19 @@ def create_transport_type(
db.refresh(obj)
return obj
@router.put("/{transport_code}", response_model=TransportTypeDTO)
def update_transport_type(
transport_code: str,
data: TransportTypeDTO,
db: Session = Depends(get_core_db),
user=Depends(get_current_user)
user=Depends(get_current_user),
):
obj = db.query(TransportType).filter(TransportType.transport_code == transport_code).first()
obj = (
db.query(TransportType)
.filter(TransportType.transport_code == transport_code)
.first()
)
if not obj:
raise HTTPException(status_code=404, detail="Not found")
for field, value in data.dict().items():
@@ -63,13 +73,18 @@ def update_transport_type(
db.refresh(obj)
return obj
@router.delete("/{transport_code}", status_code=204)
def delete_transport_type(
transport_code: str,
db: Session = Depends(get_core_db),
user=Depends(get_current_user)
user=Depends(get_current_user),
):
obj = db.query(TransportType).filter(TransportType.transport_code == transport_code).first()
obj = (
db.query(TransportType)
.filter(TransportType.transport_code == transport_code)
.first()
)
if not obj:
raise HTTPException(status_code=404, detail="Not found")
db.delete(obj)

View File

@@ -19,4 +19,4 @@ seed = [
("RV", "Recreation Vehicle (RV)"),
("TR", "Semi Tracker"),
("TV", "Van"),
]
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_transport_types(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,28 @@ def test_list_transport_types(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_transport_type_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/transport-types/invalid_code", headers=headers)
assert response.status_code == 404
def test_create_transport_type_forbidden():
response = client.post("/transport-types/", json={"transport_code": "TST", "description": "Test"})
response = client.post(
"/transport-types/", json={"transport_code": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_update_transport_type_forbidden():
response = client.put("/transport-types/TST", json={"transport_code": "TST", "description": "Test"})
response = client.put(
"/transport-types/TST", json={"transport_code": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_delete_transport_type_forbidden():
response = client.delete("/transport-types/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -1,9 +1,9 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class ValuationMethodDTO(BaseModel):
key: str = Field(..., min_length=1, max_length=2)
description: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -2,14 +2,15 @@ from sqlalchemy import String, PrimaryKeyConstraint
from sqlalchemy.orm import mapped_column, Mapped
from core.database import Base
class ValuationMethod(Base):
__tablename__ = "valuation_methods" #GMetValor
__tablename__ = "valuation_methods" # GMetValor
__table_args__ = (
PrimaryKeyConstraint("key", name="valuation_methods_pkey"),
{"schema": "public"}
{"schema": "public"},
)
key: Mapped[str] = mapped_column(String(2), nullable=False)
key: Mapped[str] = mapped_column(String(2), nullable=False)
description: Mapped[str] = mapped_column(String(200), nullable=False)
def __repr__(self):

View File

@@ -1,4 +1,3 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -11,13 +10,12 @@ from typing import Any, Dict
router = APIRouter(prefix="/valuation-methods")
@router.get("/", response_model=Dict[str, Any])
async def list_valuation_methods(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(ValuationMethod)
@@ -27,23 +25,27 @@ async def list_valuation_methods(
"items": [ValuationMethodDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
"page_size": page_size,
}
@router.get("/{key}", response_model=ValuationMethodDTO)
async def get_valuation_method(key: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)):
async def get_valuation_method(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
obj = db.query(ValuationMethod).filter(ValuationMethod.key == key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=ValuationMethodDTO, status_code=201)
async def create_valuation_method(
data: ValuationMethodDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = ValuationMethod(**data.dict())
db.add(obj)
@@ -51,13 +53,13 @@ async def create_valuation_method(
db.refresh(obj)
return obj
@router.put("/{key}", response_model=ValuationMethodDTO)
async def update_valuation_method(
key: str,
data: ValuationMethodDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(ValuationMethod).filter(ValuationMethod.key == key).first()
if not obj:
@@ -68,12 +70,12 @@ async def update_valuation_method(
db.refresh(obj)
return obj
@router.delete("/{key}", status_code=204)
async def delete_valuation_method(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
obj = db.query(ValuationMethod).filter(ValuationMethod.key == key).first()
if not obj:

View File

@@ -6,4 +6,4 @@ seed = [
("4", "VALOR DE PRECIO UNITARIO DE VENTA."),
("5", "VALOR RECONSTRUIDO."),
("6", "ULTIMO RECURSO"),
]
]

View File

@@ -7,6 +7,7 @@ app = FastAPI()
app.include_router(router)
client = TestClient(app)
@pytest.mark.usefixtures("client", "access_token")
def test_list_valuation_methods(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
@@ -16,20 +17,28 @@ def test_list_valuation_methods(client, access_token):
assert "page" in response.json()
assert "page_size" in response.json()
@pytest.mark.usefixtures("client", "access_token")
def test_get_valuation_method_not_found(client, access_token):
headers = {"Authorization": f"Bearer {access_token}"}
response = client.get("/valuation-methods/invalid_key", headers=headers)
assert response.status_code == 404
def test_create_valuation_method_forbidden():
response = client.post("/valuation-methods/", json={"key": "TST", "description": "Test"})
response = client.post(
"/valuation-methods/", json={"key": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_update_valuation_method_forbidden():
response = client.put("/valuation-methods/TST", json={"key": "TST", "description": "Test"})
response = client.put(
"/valuation-methods/TST", json={"key": "TST", "description": "Test"}
)
assert response.status_code in (403, 405, 404)
def test_delete_valuation_method_forbidden():
response = client.delete("/valuation-methods/TST")
assert response.status_code in (403, 405, 404)

View File

@@ -2,6 +2,7 @@
Router principal de API v1
Agrega todos los módulos de la aplicación
"""
from fastapi import APIRouter
from .reference_data.router import router as reference_data_router