Merge pull request 'catalogos-backend' (#4) from catalogos-backend into development

Reviewed-on: ADUANASOFT/anexo76#4
This commit is contained in:
2025-11-02 01:32:53 +00:00
54 changed files with 1140 additions and 185 deletions

View File

@@ -1,4 +1,5 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
from typing import Optional
class CodePedimentoRegimenDTO(BaseModel):
@@ -7,5 +8,4 @@ class CodePedimentoRegimenDTO(BaseModel):
regimen_code: Optional[str] = Field(None, min_length=1, max_length=3)
type_code: Optional[str] = Field(None, min_length=1, max_length=1)
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,19 +1,40 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import CodePedimentoRegimen
from .dto import CodePedimentoRegimenDTO
from typing import Any, Dict
router = APIRouter(prefix="/code-pedimento-regimens", tags=["Code Pedimento Regimens"])
@router.get("/", response_model=list[CodePedimentoRegimenDTO])
def list_code_pedimento_regimens(db: Session = Depends(get_core_db)):
objs = db.query(CodePedimentoRegimen).all()
return [CodePedimentoRegimenDTO.model_validate(obj) for obj in objs]
@router.get("/", response_model=Dict[str, Any])
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)
):
skip = (page - 1) * page_size
query = db.query(CodePedimentoRegimen)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [CodePedimentoRegimenDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{id}", response_model=CodePedimentoRegimenDTO)
def get_code_pedimento_regimen(id: int, db: Session = Depends(get_core_db)):
def get_code_pedimento_regimen(
id: int,
db: Session = Depends(get_core_db),
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")

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.code_pedimento_regimens.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/code-pedimento-regimens/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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"})
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"})
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

@@ -0,0 +1,15 @@
import pytest
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()
app.include_router(router)
return TestClient(app)

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
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,25 +1,46 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import Container
from .dto import ContainerDTO
from typing import Any, Dict
router = APIRouter(prefix="/containers", tags=["Containers"])
@router.get("/", response_model=list[ContainerDTO])
def list_containers(db: Session = Depends(get_core_db)):
return db.query(Container).all()
@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)
):
skip = (page - 1) * page_size
query = db.query(Container)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [ContainerDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{key}", response_model=ContainerDTO)
def get_container(key: str, db: Session = Depends(get_core_db)):
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)
def create_container(
async def create_container(
data: ContainerDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
@@ -30,8 +51,9 @@ def create_container(
db.refresh(obj)
return obj
@router.put("/{key}", response_model=ContainerDTO)
def update_container(
async def update_container(
key: str,
data: ContainerDTO,
db: Session = Depends(get_core_db),
@@ -46,8 +68,9 @@ def update_container(
db.refresh(obj)
return obj
@router.delete("/{key}", status_code=204)
def delete_container(
async def delete_container(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.containers.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/containers/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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,4 +1,5 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class CountryDTO(BaseModel):
m3_key: str = Field(..., min_length=1, max_length=3)
@@ -7,6 +8,5 @@ class CountryDTO(BaseModel):
description_es: str
description_en: str
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,25 +1,46 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import Country
from .dto import CountryDTO
from typing import Any, Dict
router = APIRouter(prefix="/countries", tags=["Countries"])
@router.get("/", response_model=list[CountryDTO])
def list_countries(db: Session = Depends(get_core_db)):
return db.query(Country).all()
@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)
):
skip = (page - 1) * page_size
query = db.query(Country)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [CountryDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{m3_key}", response_model=CountryDTO)
def get_country(m3_key: str, db: Session = Depends(get_core_db)):
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)
def create_country(
async def create_country(
data: CountryDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
@@ -30,8 +51,9 @@ def create_country(
db.refresh(obj)
return obj
@router.put("/{m3_key}", response_model=CountryDTO)
def update_country(
async def update_country(
m3_key: str,
data: CountryDTO,
db: Session = Depends(get_core_db),
@@ -46,8 +68,9 @@ def update_country(
db.refresh(obj)
return obj
@router.delete("/{m3_key}", status_code=204)
def delete_country(
async def delete_country(
m3_key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.countries.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/countries/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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"})
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
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,25 +1,46 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import CurrencyType
from .dto import CurrencyTypeDTO
from typing import Any, Dict
router = APIRouter(prefix="/currency-types", tags=["Currency Types"])
@router.get("/", response_model=list[CurrencyTypeDTO])
def list_currency_types(db: Session = Depends(get_core_db)):
return db.query(CurrencyType).all()
@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)
):
skip = (page - 1) * page_size
query = db.query(CurrencyType)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [CurrencyTypeDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{code}", response_model=CurrencyTypeDTO)
def get_currency_type(code: str, db: Session = Depends(get_core_db)):
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)
def create_currency_type(
async def create_currency_type(
data: CurrencyTypeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
@@ -30,8 +51,9 @@ def create_currency_type(
db.refresh(obj)
return obj
@router.put("/{code}", response_model=CurrencyTypeDTO)
def update_currency_type(
async def update_currency_type(
code: str,
data: CurrencyTypeDTO,
db: Session = Depends(get_core_db),
@@ -46,8 +68,9 @@ def update_currency_type(
db.refresh(obj)
return obj
@router.delete("/{code}", status_code=204)
def delete_currency_type(
async def delete_currency_type(
code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.currency_types.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/currency-types/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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"})
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"})
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
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,18 +1,36 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import CustomsSection
from .dto import CustomsSectionDTO
from typing import Any, Dict
router = APIRouter(prefix="/customs-sections", tags=["Customs Sections"])
@router.get("/", response_model=list[CustomsSectionDTO])
def list_customs_sections(db: Session = Depends(get_core_db)):
return db.query(CustomsSection).all()
@router.get("/", response_model=Dict[str, Any])
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)
):
skip = (page - 1) * page_size
query = db.query(CustomsSection)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [CustomsSectionDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{customs_code}", response_model=CustomsSectionDTO)
def get_customs_section(customs_code: str, db: Session = Depends(get_core_db)):
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")

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.customs_sections.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/customs-sections/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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"})
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"})
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
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,18 +1,36 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import CustomsWarehouse
from .dto import CustomsWarehouseDTO
from typing import Any, Dict
router = APIRouter(prefix="/customs-warehouses", tags=["Customs Warehouses"])
@router.get("/", response_model=list[CustomsWarehouseDTO])
def list_customs_warehouses(db: Session = Depends(get_core_db)):
return db.query(CustomsWarehouse).all()
@router.get("/", response_model=Dict[str, Any])
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)
):
skip = (page - 1) * page_size
query = db.query(CustomsWarehouse)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [CustomsWarehouseDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"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)):
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")

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.customs_warehouses.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/customs-warehouses/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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)
assert response.status_code == 404
def test_create_customs_warehouse_forbidden():
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"})
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,9 +1,9 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
class IncotermDTO(BaseModel):
code: str = Field(..., min_length=1, max_length=5)
description_es: str
description_en: str
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,26 +1,46 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import Incoterm
from .dto import IncotermDTO
from typing import Any, Dict
router = APIRouter(prefix="/incoterms", tags=["Incoterms"])
@router.get("/", response_model=list[IncotermDTO])
def list_incoterms(db: Session = Depends(get_core_db)):
objs = db.query(Incoterm).all()
return [IncotermDTO.model_validate(obj) for obj in objs]
@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)
):
skip = (page - 1) * page_size
query = db.query(Incoterm)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [IncotermDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{key}", response_model=IncotermDTO)
def get_incoterm(key: str, db: Session = Depends(get_core_db)):
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)
def create_incoterm(
async def create_incoterm(
data: IncotermDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
@@ -31,8 +51,9 @@ def create_incoterm(
db.refresh(obj)
return IncotermDTO.model_validate(obj)
@router.put("/{key}", response_model=IncotermDTO)
def update_incoterm(
async def update_incoterm(
key: str,
data: IncotermDTO,
db: Session = Depends(get_core_db),
@@ -47,8 +68,9 @@ def update_incoterm(
db.refresh(obj)
return IncotermDTO.model_validate(obj)
@router.delete("/{key}", status_code=204)
def delete_incoterm(
async def delete_incoterm(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.incoterms.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/incoterms/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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

@@ -1,4 +1,5 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
from typing import Optional
class InvoiceTypeDTO(BaseModel):
@@ -7,5 +8,4 @@ class InvoiceTypeDTO(BaseModel):
note: Optional[str] = None
type: Optional[str] = None
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,19 +1,36 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import InvoiceType
from .dto import InvoiceTypeDTO
from typing import Any, Dict
router = APIRouter(prefix="/invoice-types", tags=["Invoice Types"])
@router.get("/", response_model=list[InvoiceTypeDTO])
def list_invoice_types(db: Session = Depends(get_core_db)):
objs = db.query(InvoiceType).all()
return [InvoiceTypeDTO.model_validate(obj) for obj in objs]
@router.get("/", response_model=Dict[str, Any])
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)
):
skip = (page - 1) * page_size
query = db.query(InvoiceType)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [InvoiceTypeDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{key}", response_model=InvoiceTypeDTO)
def get_invoice_type(key: str, db: Session = Depends(get_core_db)):
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")

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.invoice_types.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/invoice-types/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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"})
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"})
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
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,25 +1,46 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import MaterialType
from .dto import MaterialTypeDTO
from typing import Any, Dict
router = APIRouter(prefix="/material-types", tags=["Material Types"])
@router.get("/", response_model=list[MaterialTypeDTO])
def list_material_types(db: Session = Depends(get_core_db)):
return db.query(MaterialType).all()
@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)
):
skip = (page - 1) * page_size
query = db.query(MaterialType)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [MaterialTypeDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{key}", response_model=MaterialTypeDTO)
def get_material_type(key: str, db: Session = Depends(get_core_db)):
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)
def create_material_type(
async def create_material_type(
data: MaterialTypeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
@@ -30,8 +51,9 @@ def create_material_type(
db.refresh(obj)
return obj
@router.put("/{key}", response_model=MaterialTypeDTO)
def update_material_type(
async def update_material_type(
key: str,
data: MaterialTypeDTO,
db: Session = Depends(get_core_db),
@@ -46,8 +68,9 @@ def update_material_type(
db.refresh(obj)
return obj
@router.delete("/{key}", status_code=204)
def delete_material_type(
async def delete_material_type(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.material_types.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/material-types/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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"})
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"})
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
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,18 +1,36 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import PaymentMethod
from .dto import PaymentMethodDTO
from typing import Any, Dict
router = APIRouter(prefix="/payment-methods", tags=["Payment Methods"])
@router.get("/", response_model=list[PaymentMethodDTO])
def list_payment_methods(db: Session = Depends(get_core_db)):
return db.query(PaymentMethod).all()
@router.get("/", response_model=Dict[str, Any])
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)
):
skip = (page - 1) * page_size
query = db.query(PaymentMethod)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [PaymentMethodDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{key}", response_model=PaymentMethodDTO)
def get_payment_method(key: str, db: Session = Depends(get_core_db)):
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")

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.payment_methods.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/payment-methods/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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"})
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"})
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

@@ -1,10 +1,10 @@
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
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,18 +1,36 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import PedimentoCode
from .dto import PedimentoCodeDTO
from typing import Any, Dict
router = APIRouter(prefix="/pedimento-codes", tags=["Pedimento Codes"])
@router.get("/", response_model=list[PedimentoCodeDTO])
def list_pedimento_codes(db: Session = Depends(get_core_db)):
return db.query(PedimentoCode).all()
@router.get("/", response_model=Dict[str, Any])
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)
):
skip = (page - 1) * page_size
query = db.query(PedimentoCode)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [PedimentoCodeDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{code}", response_model=PedimentoCodeDTO)
def get_pedimento_code(code: str, db: Session = Depends(get_core_db)):
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")

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.pedimento_codes.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/pedimento-codes/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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"})
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"})
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

@@ -1,9 +1,9 @@
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
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,19 +1,36 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import RegimenPedimento
from .dto import RegimenPedimentoDTO
from typing import Any, Dict
router = APIRouter(prefix="/pedimento-regimens", tags=["Pedimento Regimens"])
@router.get("/", response_model=list[RegimenPedimentoDTO])
def list_pedimento_regimens(db: Session = Depends(get_core_db)):
objs = db.query(RegimenPedimento).all()
return [RegimenPedimentoDTO.model_validate(obj) for obj in objs]
@router.get("/", response_model=Dict[str, Any])
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)
):
skip = (page - 1) * page_size
query = db.query(RegimenPedimento)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [RegimenPedimentoDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{key}", response_model=RegimenPedimentoDTO)
def get_pedimento_regimen(key: str, db: Session = Depends(get_core_db)):
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")

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.pedimento_regimens.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/pedimento-regimens/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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"})
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"})
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

@@ -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
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,18 +1,36 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import Sector
from .dto import SectorDTO
from typing import Any, Dict
router = APIRouter(prefix="/sectors", tags=["Sectors"])
@router.get("/", response_model=list[SectorDTO])
def list_sectors(db: Session = Depends(get_core_db)):
return db.query(Sector).all()
@router.get("/", response_model=Dict[str, Any])
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)
):
skip = (page - 1) * page_size
query = db.query(Sector)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [SectorDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{key}", response_model=SectorDTO)
def get_sector(key: str, db: Session = Depends(get_core_db)):
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")

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.sectors.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/sectors/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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

@@ -1,4 +1,5 @@
from pydantic import BaseModel, Field
from pydantic import ConfigDict
from typing import Optional
class StateDTO(BaseModel):
@@ -7,6 +8,5 @@ class StateDTO(BaseModel):
mex_key: Optional[str] = None
ame_key: Optional[str] = None
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,42 +1,66 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import State
from .dto import StateDTO
from typing import Any, Dict
router = APIRouter(prefix="/states", tags=["States"])
@router.get("/", response_model=list[StateDTO])
def list_states(db: Session = Depends(get_core_db)):
return db.query(State).all()
@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)
):
skip = (page - 1) * page_size
query = db.query(State)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [StateDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{m3_key}", response_model=StateDTO)
def get_state(m3_key: str, db: Session = Depends(get_core_db)):
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)
def create_state(
async def create_state(
data: StateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
):
obj = State(**data.dict())
db.add(obj)
db.commit()
db.refresh(obj)
return obj
@router.put("/{m3_key}", response_model=StateDTO)
def update_state(
async def update_state(
m3_key: str,
data: StateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
):
obj = db.query(State).filter(State.m3_key == m3_key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
@@ -46,12 +70,14 @@ def update_state(
db.refresh(obj)
return obj
@router.delete("/{m3_key}", status_code=204)
def delete_state(
async def delete_state(
m3_key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
):
obj = db.query(State).filter(State.m3_key == m3_key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.states.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/states/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,28 +1,48 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import has_role, get_current_user
from .models import TransportMode
from .dto import TransportModeDTO
from typing import Any, Dict
router = APIRouter(prefix="/transport-modes", tags=["Transport Modes"])
@router.get("/", response_model=list[TransportModeDTO])
def list_transport_modes(db: Session = Depends(get_core_db)):
return db.query(TransportMode).all()
@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)
):
skip = (page - 1) * page_size
query = db.query(TransportMode)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [TransportModeDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{key}", response_model=TransportModeDTO)
def get_transport_mode(key: str, db: Session = Depends(get_core_db)):
async def get_transport_mode(key: str, db: Session = Depends(get_core_db)):
obj = db.query(TransportMode).filter(TransportMode.key == key).first()
if not obj:
raise HTTPException(status_code=404, detail="Not found")
return obj
@router.post("/", response_model=TransportModeDTO, status_code=201)
def create_transport_mode(
async def create_transport_mode(
data: TransportModeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
user=Depends(get_current_user)
):
obj = TransportMode(**data.dict())
db.add(obj)
@@ -30,12 +50,13 @@ def create_transport_mode(
db.refresh(obj)
return obj
@router.put("/{key}", response_model=TransportModeDTO)
def update_transport_mode(
async def update_transport_mode(
key: str,
data: TransportModeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
user=Depends(get_current_user)
):
obj = db.query(TransportMode).filter(TransportMode.key == key).first()
if not obj:
@@ -46,11 +67,12 @@ def update_transport_mode(
db.refresh(obj)
return obj
@router.delete("/{key}", status_code=204)
def delete_transport_mode(
async def delete_transport_mode(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
user=Depends(get_current_user)
):
obj = db.query(TransportMode).filter(TransportMode.key == key).first()
if not obj:

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.transport_modes.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/transport-modes/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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"})
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"})
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
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,15 +1,32 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import has_role, get_current_user
from .models import TransportType
from .dto import TransportTypeDTO
from typing import Any, Dict
router = APIRouter(prefix="/transport-types", tags=["Transport Types"])
@router.get("/", response_model=list[TransportTypeDTO])
def list_transport_types(db: Session = Depends(get_core_db)):
return db.query(TransportType).all()
@router.get("/", response_model=Dict[str, Any])
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)
):
skip = (page - 1) * page_size
query = db.query(TransportType)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [TransportTypeDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{transport_code}", response_model=TransportTypeDTO)
def get_transport_type(transport_code: str, db: Session = Depends(get_core_db)):
@@ -22,7 +39,7 @@ def get_transport_type(transport_code: str, db: Session = Depends(get_core_db)):
def create_transport_type(
data: TransportTypeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
user=Depends(get_current_user)
):
obj = TransportType(**data.dict())
db.add(obj)
@@ -35,7 +52,7 @@ def update_transport_type(
transport_code: str,
data: TransportTypeDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
user=Depends(get_current_user)
):
obj = db.query(TransportType).filter(TransportType.transport_code == transport_code).first()
if not obj:
@@ -50,7 +67,7 @@ def update_transport_type(
def delete_transport_type(
transport_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
user=Depends(get_current_user)
):
obj = db.query(TransportType).filter(TransportType.transport_code == transport_code).first()
if not obj:

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.transport_types.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/transport-types/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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"})
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"})
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
class Config:
from_attributes = True
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,25 +1,46 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import has_role
from core.security import get_current_user, has_role
from .models import ValuationMethod
from .dto import ValuationMethodDTO
from typing import Any, Dict
router = APIRouter(prefix="/valuation-methods", tags=["Valuation Methods"])
@router.get("/", response_model=list[ValuationMethodDTO])
def list_valuation_methods(db: Session = Depends(get_core_db)):
return db.query(ValuationMethod).all()
@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)
):
skip = (page - 1) * page_size
query = db.query(ValuationMethod)
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
"items": [ValuationMethodDTO.model_validate(obj) for obj in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{key}", response_model=ValuationMethodDTO)
def get_valuation_method(key: str, db: Session = Depends(get_core_db)):
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)
def create_valuation_method(
async def create_valuation_method(
data: ValuationMethodDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
@@ -30,8 +51,9 @@ def create_valuation_method(
db.refresh(obj)
return obj
@router.put("/{key}", response_model=ValuationMethodDTO)
def update_valuation_method(
async def update_valuation_method(
key: str,
data: ValuationMethodDTO,
db: Session = Depends(get_core_db),
@@ -46,8 +68,9 @@ def update_valuation_method(
db.refresh(obj)
return obj
@router.delete("/{key}", status_code=204)
def delete_valuation_method(
async def delete_valuation_method(
key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))

View File

@@ -0,0 +1,35 @@
import pytest
from fastapi.testclient import TestClient
from api.v1.modules.public.reference_data.valuation_methods.routes import router
from fastapi import FastAPI
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}"}
response = client.get("/valuation-methods/", headers=headers)
assert response.status_code == 200
assert "items" in response.json()
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"})
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"})
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

@@ -4,7 +4,7 @@ Configuración de base de datos con soporte multi-tenant
- Bases de datos dedicadas para clientes enterprise
"""
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from typing import Generator, Dict, Optional, AsyncGenerator

View File

@@ -25,26 +25,24 @@ class TenantMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next: Callable):
# Rutas públicas que no requieren tenant
public_paths = [
"/api/docs",
# Permitir acceso sin autenticación a rutas de documentación y salud
doc_prefixes = [
"/api/redoc",
"/api/openapi.json",
"/api/v1/auth",
"/api/v1/auth",
"/api/v1/status",
"/api/openapi.json"
]
public_prefixes = [
"/api/v1/auth",
"/api/v1/status",
"/api/health",
"/api/"
]
# Verificar si la ruta es pública (comparación exacta o prefijo)
is_public = False
for path in public_paths:
if request.url.path == path or (path != "/" and request.url.path.startswith(path)):
is_public = True
break
if is_public:
path = request.url.path
# Permitir cualquier subruta de docs/redoc/openapi.json (por ejemplo, /api/docs, /api/docs/, /api/docs/oauth2-redirect)
if any(path == prefix or path.startswith(prefix + "/") for prefix in doc_prefixes):
return await call_next(request)
# Permitir rutas públicas exactas o con prefijo
if any(path == prefix or (prefix != "/" and path.startswith(prefix)) for prefix in public_prefixes):
return await call_next(request)
# Extraer token y obtener tenant