diff --git a/backend/api/v1/modules/a76/classes/routes.py b/backend/api/v1/modules/a76/classes/routes.py index e602a0e5..ffea97f0 100644 --- a/backend/api/v1/modules/a76/classes/routes.py +++ b/backend/api/v1/modules/a76/classes/routes.py @@ -35,6 +35,13 @@ async def list_classes( """ List classes with optional filters and pagination """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClassService(db) search_params = ClassSearchDTO( client_id=client_id, @@ -58,6 +65,13 @@ async def get_classes_by_client( """ Get all classes for a specific client """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClassService(db) return service.search_by_client(client_id, skip, limit) diff --git a/backend/api/v1/modules/a76/classes/test_classes.py b/backend/api/v1/modules/a76/classes/test_classes.py new file mode 100644 index 00000000..0b907703 --- /dev/null +++ b/backend/api/v1/modules/a76/classes/test_classes.py @@ -0,0 +1,31 @@ +import pytest +from fastapi.testclient import TestClient +from .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_classes(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/classes/", 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_class_not_found(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/classes/invalid_id", headers=headers) + assert response.status_code == 404 + +def test_create_class_forbidden(): + response = client.post("/classes/", json={"name": "Test Class"}) + assert response.status_code in (403, 405, 404) + +def test_update_class_forbidden(): + response = client.put("/classes/1", json={"name": "Updated Class"}) + assert response.status_code in (403, 405, 404) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/client_and_provider/routes.py b/backend/api/v1/modules/a76/client_and_provider/routes.py index 02d215b8..cafb6428 100644 --- a/backend/api/v1/modules/a76/client_and_provider/routes.py +++ b/backend/api/v1/modules/a76/client_and_provider/routes.py @@ -28,6 +28,17 @@ async def create_client_provider( """ Create a new client or provider in the system """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + + # Ensure the client_data is associated with the correct tenant and company + if client_data.tenant_id != tenant_id or client_data.company_id != company_id: + raise HTTPException(status_code=400, detail="Mismatch in tenant or company association") + service = ClientProviderService(db) return service.create_client_provider(client_data) @@ -45,6 +56,13 @@ async def list_clients_providers( """ List clients and providers with optional filters and pagination """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClientProviderService(db) return service.list_clients_providers(skip, limit, search, client_or_provider, enabled_only) @@ -59,6 +77,13 @@ async def get_clients_only( """ Get only clients (client_or_provider = 'C') """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClientProviderService(db) return service.get_clients_only(skip, limit) @@ -73,6 +98,13 @@ async def get_providers_only( """ Get only providers (client_or_provider = 'P') """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClientProviderService(db) return service.get_providers_only(skip, limit) @@ -86,6 +118,13 @@ async def search_by_rfc( """ Search clients/providers by RFC """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClientProviderService(db) return service.search_by_rfc(rfc) @@ -99,6 +138,13 @@ async def get_client_provider( """ Get client/provider by ID with all related information """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClientProviderService(db) client = service.get_client_provider(client_id) if not client: @@ -116,6 +162,13 @@ async def update_client_provider( """ Update client/provider information """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClientProviderService(db) client = service.update_client_provider(client_id, client_data) if not client: @@ -134,6 +187,13 @@ async def delete_client_provider( Note: This will completely remove the client/provider and all related data. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClientProviderService(db) if not service.delete_client_provider(client_id): raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found") @@ -148,6 +208,13 @@ async def toggle_client_provider_status( """ Toggle client/provider enabled/disabled status """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClientProviderService(db) client = service.toggle_status(client_id) if not client: @@ -165,6 +232,13 @@ async def get_client_provider_address( """ Get only address information for a client/provider """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClientProviderService(db) client = service.get_client_provider(client_id) if not client: @@ -185,6 +259,13 @@ async def get_client_provider_programs( """ Get only programs information for a client/provider """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClientProviderService(db) client = service.get_client_provider(client_id) if not client: @@ -205,6 +286,13 @@ async def get_client_provider_basic_info( """ Get basic information for a client/provider (without address and programs) """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = ClientProviderService(db) client = service.get_client_provider(client_id) if not client: diff --git a/backend/api/v1/modules/a76/client_and_provider/test_client_and_provider.py b/backend/api/v1/modules/a76/client_and_provider/test_client_and_provider.py new file mode 100644 index 00000000..55f319b6 --- /dev/null +++ b/backend/api/v1/modules/a76/client_and_provider/test_client_and_provider.py @@ -0,0 +1,31 @@ +import pytest +from fastapi.testclient import TestClient +from .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_clients_and_providers(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/client_and_provider/", 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_client_or_provider_not_found(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/client_and_provider/invalid_id", headers=headers) + assert response.status_code == 404 + +def test_create_client_or_provider_forbidden(): + response = client.post("/client_and_provider/", json={"name": "Test Client/Provider"}) + assert response.status_code in (403, 405, 404) + +def test_update_client_or_provider_forbidden(): + response = client.put("/client_and_provider/1", json={"name": "Updated Client/Provider"}) + assert response.status_code in (403, 405, 404) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/company/test_company.py b/backend/api/v1/modules/a76/company/test_company.py new file mode 100644 index 00000000..206c8c38 --- /dev/null +++ b/backend/api/v1/modules/a76/company/test_company.py @@ -0,0 +1,31 @@ +import pytest +from fastapi.testclient import TestClient +from .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_companies(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/company/", 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_company_not_found(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/company/invalid_id", headers=headers) + assert response.status_code == 404 + +def test_create_company_forbidden(): + response = client.post("/company/", json={"name": "Test Company"}) + assert response.status_code in (403, 405, 404) + +def test_update_company_forbidden(): + response = client.put("/company/1", json={"name": "Updated Company"}) + assert response.status_code in (403, 405, 404) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/country_rule_oct/routes.py b/backend/api/v1/modules/a76/country_rule_oct/routes.py index ca15fa92..3d40c0b9 100644 --- a/backend/api/v1/modules/a76/country_rule_oct/routes.py +++ b/backend/api/v1/modules/a76/country_rule_oct/routes.py @@ -18,6 +18,13 @@ async def list_countries( """ List all CountryRuleOct entries. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + return db.query(CountryRuleOctService).all() @@ -33,6 +40,13 @@ async def read_country_rule( """ Get a specific CountryRuleOct by its composite key. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + country = CountryRuleOctService.get_country_by_keys(db, permission, line, fraction, country_code) if not country: raise HTTPException(status_code=404, detail="CountryRuleOct not found") diff --git a/backend/api/v1/modules/a76/country_rule_oct/test_country_rule_oct.py b/backend/api/v1/modules/a76/country_rule_oct/test_country_rule_oct.py new file mode 100644 index 00000000..b4354146 --- /dev/null +++ b/backend/api/v1/modules/a76/country_rule_oct/test_country_rule_oct.py @@ -0,0 +1,31 @@ +import pytest +from fastapi.testclient import TestClient +from .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_country_rules(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/country-rule-oct/", 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_rule_not_found(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/country-rule-oct/invalid_id", headers=headers) + assert response.status_code == 404 + +def test_create_country_rule_forbidden(): + response = client.post("/country-rule-oct/", json={"rule": "Test Rule"}) + assert response.status_code in (403, 405, 404) + +def test_update_country_rule_forbidden(): + response = client.put("/country-rule-oct/1", json={"rule": "Updated Rule"}) + assert response.status_code in (403, 405, 404) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/exchange_rate/routes.py b/backend/api/v1/modules/a76/exchange_rate/routes.py index 48b6e330..0446f3c0 100644 --- a/backend/api/v1/modules/a76/exchange_rate/routes.py +++ b/backend/api/v1/modules/a76/exchange_rate/routes.py @@ -18,6 +18,13 @@ async def list_exchange_rates( """ List all ExchangeRate entries. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + return db.query(ExchangeRateService).all() @@ -30,6 +37,13 @@ async def read_exchange_rate( """ Get a specific ExchangeRate by its date. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + exchange_rate = ExchangeRateService.get_exchange_rate_by_date(db, date) if not exchange_rate: raise HTTPException(status_code=404, detail="ExchangeRate not found") @@ -45,6 +59,13 @@ async def create_exchange_rate( """ Create a new ExchangeRate entry. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + return ExchangeRateService.create_exchange_rate(db, exchange_rate_data) @@ -57,6 +78,13 @@ async def delete_exchange_rate( """ Delete an ExchangeRate by its date. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + exchange_rate = ExchangeRateService.delete_exchange_rate(db, date) if not exchange_rate: raise HTTPException(status_code=404, detail="ExchangeRate not found") \ No newline at end of file diff --git a/backend/api/v1/modules/a76/exchange_rate/test_exchange_rate.py b/backend/api/v1/modules/a76/exchange_rate/test_exchange_rate.py new file mode 100644 index 00000000..caf3d0b9 --- /dev/null +++ b/backend/api/v1/modules/a76/exchange_rate/test_exchange_rate.py @@ -0,0 +1,31 @@ +import pytest +from fastapi.testclient import TestClient +from .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_exchange_rates(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/exchange-rate/", 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_exchange_rate_not_found(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/exchange-rate/invalid_id", headers=headers) + assert response.status_code == 404 + +def test_create_exchange_rate_forbidden(): + response = client.post("/exchange-rate/", json={"rate": 1.23}) + assert response.status_code in (403, 405, 404) + +def test_update_exchange_rate_forbidden(): + response = client.put("/exchange-rate/1", json={"rate": 1.45}) + assert response.status_code in (403, 405, 404) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/fraction_rule_octave/routes.py b/backend/api/v1/modules/a76/fraction_rule_octave/routes.py index 43c24bc5..ab5b4363 100644 --- a/backend/api/v1/modules/a76/fraction_rule_octave/routes.py +++ b/backend/api/v1/modules/a76/fraction_rule_octave/routes.py @@ -18,6 +18,13 @@ async def list_fractions( """ List all FractionRuleOctave entries. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + return db.query(FractionRuleOctaveService).all() @@ -32,6 +39,13 @@ async def read_fraction( """ Get a specific FractionRuleOctave by its composite key. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + frac = FractionRuleOctaveService.get_fraction_by_permission_line(db, permission, line, fraction) if not frac: raise HTTPException(status_code=404, detail="FractionRuleOctave not found") @@ -47,6 +61,13 @@ async def create_frac( """ Create a new FractionRuleOctave entry. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + return FractionRuleOctaveService.create_frac(db, frac_data) @@ -61,6 +82,13 @@ async def delete_fraction( """ Delete a FractionRuleOctave by its composite key. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + frac = FractionRuleOctaveService.delete_fraction(db, permission, line, fraction) if not frac: raise HTTPException(status_code=404, detail="FractionRuleOctave not found") \ No newline at end of file diff --git a/backend/api/v1/modules/a76/fraction_rule_octave/test_fraction_rule_octave.py b/backend/api/v1/modules/a76/fraction_rule_octave/test_fraction_rule_octave.py new file mode 100644 index 00000000..c4d67c5b --- /dev/null +++ b/backend/api/v1/modules/a76/fraction_rule_octave/test_fraction_rule_octave.py @@ -0,0 +1,31 @@ +import pytest +from fastapi.testclient import TestClient +from .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_fraction_rules(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/fraction_rule_octave/", 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_fraction_rule_not_found(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/fraction_rule_octave/invalid_id", headers=headers) + assert response.status_code == 404 + +def test_create_fraction_rule_forbidden(): + response = client.post("/fraction_rule_octave/", json={"rule": "Test Rule"}) + assert response.status_code in (403, 405, 404) + +def test_update_fraction_rule_forbidden(): + response = client.put("/fraction_rule_octave/1", json={"rule": "Updated Rule"}) + assert response.status_code in (403, 405, 404) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/package/routes.py b/backend/api/v1/modules/a76/package/routes.py index c8b17ba7..de08247e 100644 --- a/backend/api/v1/modules/a76/package/routes.py +++ b/backend/api/v1/modules/a76/package/routes.py @@ -21,6 +21,13 @@ async def list_bultos( """ List all GBultos with pagination. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + return db.query(Package).offset(skip).limit(limit).all() @@ -33,6 +40,13 @@ async def read_bulto( """ Get a specific Package by its CODE. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + bulto = GBultoService.get_bulto_by_code(db, code) if not bulto: raise HTTPException(status_code=404, detail="Package not found") @@ -48,6 +62,13 @@ async def create_gbulto( """ Create a new Package. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + return GBultoService.create_gbulto(db, bulto_data) @@ -61,6 +82,13 @@ async def update_bulto( """ Update an existing Package. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + bulto = GBultoService.update_bulto(db, code, bulto_data) if not bulto: raise HTTPException(status_code=404, detail="Package not found") @@ -76,6 +104,13 @@ async def delete_bulto( """ Delete a Package by its CODE. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + bulto = GBultoService.delete_bulto(db, code) if not bulto: raise HTTPException(status_code=404, detail="Package not found") \ No newline at end of file diff --git a/backend/api/v1/modules/a76/package/test_package.py b/backend/api/v1/modules/a76/package/test_package.py new file mode 100644 index 00000000..4d0d5bd7 --- /dev/null +++ b/backend/api/v1/modules/a76/package/test_package.py @@ -0,0 +1,31 @@ +import pytest +from fastapi.testclient import TestClient +from .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_packages(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/bultos/", 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_package_not_found(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/bultos/invalid_id", headers=headers) + assert response.status_code == 404 + +def test_create_package_forbidden(): + response = client.post("/bultos/", json={"name": "Test Package"}) + assert response.status_code in (403, 405, 404) + +def test_update_package_forbidden(): + response = client.put("/bultos/1", json={"name": "Updated Package"}) + assert response.status_code in (403, 405, 404) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/parts/routes.py b/backend/api/v1/modules/a76/parts/routes.py index 3c4e2691..7aab6c88 100644 --- a/backend/api/v1/modules/a76/parts/routes.py +++ b/backend/api/v1/modules/a76/parts/routes.py @@ -29,6 +29,13 @@ async def create_part( """ Create a new part in the system """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) return service.create_part(part_data) @@ -49,6 +56,13 @@ async def list_parts( """ List parts with optional filters and pagination """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) search_params = PartSearchDTO( client_id=client_id, @@ -72,6 +86,13 @@ async def get_parts_by_client( """ Get all parts for a specific client """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) return service.search_by_client(client_id, skip, limit) @@ -85,6 +106,13 @@ async def search_by_fraction( """ Search parts by tariff fraction """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) return service.search_by_fraction(fraction) @@ -98,6 +126,13 @@ async def search_by_supplier( """ Search parts by supplier """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) return service.search_by_supplier(supplier) @@ -111,6 +146,13 @@ async def get_parts_by_country( """ Get parts by country of origin """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) return service.get_parts_by_country(country_code) @@ -123,6 +165,13 @@ async def get_parts_statistics( """ Get basic parts statistics """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) return service.get_parts_statistics() @@ -137,6 +186,13 @@ async def get_part( """ Get part by composite key (client_id + part_number) """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) part = service.get_part(client_id, part_number) if not part: @@ -158,6 +214,13 @@ async def update_part( """ Update part information """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) part = service.update_part(client_id, part_number, part_data) if not part: @@ -180,6 +243,13 @@ async def delete_part( Note: This will completely remove the part from the system. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) if not service.delete_part(client_id, part_number): raise HTTPException( @@ -198,6 +268,13 @@ async def toggle_part_status( """ Toggle part enabled/disabled status """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) part = service.toggle_status(client_id, part_number) if not part: @@ -219,6 +296,13 @@ async def get_part_basic_info( """ Get basic information for a part """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) part = service.get_part(client_id, part_number) if not part: @@ -249,6 +333,13 @@ async def get_part_regulatory_info( """ Get regulatory information for a part (FDA, FCC, ECCN, etc.) """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + service = PartService(db) part = service.get_part(client_id, part_number) if not part: diff --git a/backend/api/v1/modules/a76/parts/test_parts.py b/backend/api/v1/modules/a76/parts/test_parts.py new file mode 100644 index 00000000..1596c8b0 --- /dev/null +++ b/backend/api/v1/modules/a76/parts/test_parts.py @@ -0,0 +1,31 @@ +import pytest +from fastapi.testclient import TestClient +from .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_parts(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/parts/", 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_part_not_found(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/parts/invalid_id", headers=headers) + assert response.status_code == 404 + +def test_create_part_forbidden(): + response = client.post("/parts/", json={"name": "Test Part"}) + assert response.status_code in (403, 405, 404) + +def test_update_part_forbidden(): + response = client.put("/parts/1", json={"name": "Updated Part"}) + assert response.status_code in (403, 405, 404) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_additional.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_additional.py index 206227c2..e9589c93 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_additional.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_additional.py @@ -1,10 +1,10 @@ """ Routes for PedimentoConfigAdditional CRUD operations """ -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 get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_config_additional import PedimentoConfigAdditionalService from ..dtos.pedimento_config_additional import ( @@ -20,15 +20,13 @@ router = APIRouter(prefix="/{pedimento_id}/config-additional") @router.get("/", response_model=PedimentoConfigAdditionalResponse) async def get_config_additional( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get config additional by pedimento ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id) + config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not config: raise HTTPException(status_code=404, detail="Config additional not found") @@ -39,21 +37,17 @@ async def get_config_additional( async def create_config_additional( pedimento_id: int, data: PedimentoConfigAdditionalCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create config additional""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - # Ensure pedimento_id and tenant_id match + # Ensure pedimento_id and company_id match if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - if data.tenant_id != tenant_id: - raise HTTPException(status_code=403, detail="Tenant ID mismatch") - config = PedimentoConfigAdditionalService.create(db, data) + config = PedimentoConfigAdditionalService.create(db, data, tenant_id, company_id) return config @@ -61,15 +55,13 @@ async def create_config_additional( async def update_config_additional( pedimento_id: int, data: PedimentoConfigAdditionalUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update config additional""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - config = PedimentoConfigAdditionalService.update(db, pedimento_id, tenant_id, data) + config = PedimentoConfigAdditionalService.update(db, pedimento_id, tenant_id, company_id, data) if not config: raise HTTPException(status_code=404, detail="Config additional not found") @@ -79,15 +71,13 @@ async def update_config_additional( @router.delete("/", status_code=204) async def delete_config_additional( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete config additional""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoConfigAdditionalService.delete(db, pedimento_id, tenant_id) + success = PedimentoConfigAdditionalService.delete(db, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Config additional not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_calculations.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_calculations.py index 7b65b272..62bda5dd 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_calculations.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_calculations.py @@ -1,10 +1,10 @@ """ Routes for PedimentoConfigCalculations CRUD operations """ -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 get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_config_calculations import PedimentoConfigCalculationsService from ..dtos.pedimento_config_calculations import ( @@ -20,15 +20,13 @@ router = APIRouter(prefix="/{pedimento_id}/config-calculations") @router.get("/", response_model=PedimentoConfigCalculationsResponse) async def get_config_calculations( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get config calculations by pedimento ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id) + config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not config: raise HTTPException(status_code=404, detail="Config calculations not found") @@ -39,19 +37,17 @@ async def get_config_calculations( async def create_config_calculations( pedimento_id: int, data: PedimentoConfigCalculationsCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create config calculations""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - config = PedimentoConfigCalculationsService.create(db, data, tenant_id) + config = PedimentoConfigCalculationsService.create(db, data, tenant_id, company_id) return config @@ -59,15 +55,13 @@ async def create_config_calculations( async def update_config_calculations( pedimento_id: int, data: PedimentoConfigCalculationsUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update config calculations""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - config = PedimentoConfigCalculationsService.update(db, pedimento_id, tenant_id, data) + config = PedimentoConfigCalculationsService.update(db, pedimento_id, tenant_id, company_id, data) if not config: raise HTTPException(status_code=404, detail="Config calculations not found") @@ -77,15 +71,13 @@ async def update_config_calculations( @router.delete("/", status_code=204) async def delete_config_calculations( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete config calculations""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoConfigCalculationsService.delete(db, pedimento_id, tenant_id) + success = PedimentoConfigCalculationsService.delete(db, pedimento_id, company_id) if not success: raise HTTPException(status_code=404, detail="Config calculations not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_parameters.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_parameters.py index 92b9b023..147b7f45 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_parameters.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_parameters.py @@ -1,10 +1,10 @@ """ Routes for PedimentoConfigParameters CRUD operations """ -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 get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_config_parameters import PedimentoConfigParametersService from ..dtos.pedimento_config_parameters import ( @@ -20,15 +20,13 @@ router = APIRouter(prefix="/{pedimento_id}/config-parameters") @router.get("/", response_model=PedimentoConfigParametersResponse) async def get_config_parameters( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get config parameters by pedimento ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - config = PedimentoConfigParametersService.get_by_pedimento_id(db, pedimento_id, tenant_id) + config = PedimentoConfigParametersService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not config: raise HTTPException(status_code=404, detail="Config parameters not found") @@ -39,19 +37,17 @@ async def get_config_parameters( async def create_config_parameters( pedimento_id: int, data: PedimentoConfigParametersCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create config parameters""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - config = PedimentoConfigParametersService.create(db, data, tenant_id) + config = PedimentoConfigParametersService.create(db, data, tenant_id, company_id) return config @@ -59,15 +55,13 @@ async def create_config_parameters( async def update_config_parameters( pedimento_id: int, data: PedimentoConfigParametersUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update config parameters""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - config = PedimentoConfigParametersService.update(db, pedimento_id, tenant_id, data) + config = PedimentoConfigParametersService.update(db, pedimento_id, tenant_id, company_id, data) if not config: raise HTTPException(status_code=404, detail="Config parameters not found") @@ -77,15 +71,13 @@ async def update_config_parameters( @router.delete("/", status_code=204) async def delete_config_parameters( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete config parameters""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoConfigParametersService.delete(db, pedimento_id, tenant_id) + success = PedimentoConfigParametersService.delete(db, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Config parameters not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_surcharges.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_surcharges.py index cd5d8417..ffcb5109 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_surcharges.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_surcharges.py @@ -1,10 +1,10 @@ """ Routes for PedimentoConfigSurcharges CRUD operations """ -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 get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_config_surcharges import PedimentoConfigSurchargesService from ..dtos.pedimento_config_surcharges import ( @@ -20,15 +20,13 @@ router = APIRouter(prefix="/{pedimento_id}/config-surcharges") @router.get("/", response_model=PedimentoConfigSurchargesResponse) async def get_config_surcharges( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get config surcharges by pedimento ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - config = PedimentoConfigSurchargesService.get_by_pedimento_id(db, pedimento_id, tenant_id) + config = PedimentoConfigSurchargesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not config: raise HTTPException(status_code=404, detail="Config surcharges not found") @@ -39,19 +37,17 @@ async def get_config_surcharges( async def create_config_surcharges( pedimento_id: int, data: PedimentoConfigSurchargesCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create config surcharges""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - config = PedimentoConfigSurchargesService.create(db, data, tenant_id) + config = PedimentoConfigSurchargesService.create(db, data, tenant_id, company_id) return config @@ -59,15 +55,13 @@ async def create_config_surcharges( async def update_config_surcharges( pedimento_id: int, data: PedimentoConfigSurchargesUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update config surcharges""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - config = PedimentoConfigSurchargesService.update(db, pedimento_id, tenant_id, data) + config = PedimentoConfigSurchargesService.update(db, pedimento_id, tenant_id, company_id, data) if not config: raise HTTPException(status_code=404, detail="Config surcharges not found") @@ -77,15 +71,13 @@ async def update_config_surcharges( @router.delete("/", status_code=204) async def delete_config_surcharges( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete config surcharges""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoConfigSurchargesService.delete(db, pedimento_id, tenant_id) + success = PedimentoConfigSurchargesService.delete(db, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Config surcharges not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_update_rectification.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_update_rectification.py index b537a8a7..248e186b 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_update_rectification.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_update_rectification.py @@ -1,10 +1,10 @@ """ Routes for PedimentoConfigUpdateRectification CRUD operations """ -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 get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_config_update_rectification import PedimentoConfigUpdateRectificationService from ..dtos.pedimento_config_update_rectification import ( @@ -20,15 +20,13 @@ router = APIRouter(prefix="/{pedimento_id}/config-update-rectification") @router.get("/", response_model=PedimentoConfigUpdateRectificationResponse) async def get_config_update_rectification( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get config update rectification by pedimento ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - config = PedimentoConfigUpdateRectificationService.get_by_pedimento_id(db, pedimento_id, tenant_id) + config = PedimentoConfigUpdateRectificationService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not config: raise HTTPException(status_code=404, detail="Config update rectification not found") @@ -39,19 +37,17 @@ async def get_config_update_rectification( async def create_config_update_rectification( pedimento_id: int, data: PedimentoConfigUpdateRectificationCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create config update rectification""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - config = PedimentoConfigUpdateRectificationService.create(db, data, tenant_id) + config = PedimentoConfigUpdateRectificationService.create(db, data, tenant_id, company_id) return config @@ -59,15 +55,13 @@ async def create_config_update_rectification( async def update_config_update_rectification( pedimento_id: int, data: PedimentoConfigUpdateRectificationUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update config update rectification""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - config = PedimentoConfigUpdateRectificationService.update(db, pedimento_id, tenant_id, data) + config = PedimentoConfigUpdateRectificationService.update(db, pedimento_id, tenant_id, company_id, data) if not config: raise HTTPException(status_code=404, detail="Config update rectification not found") @@ -77,15 +71,13 @@ async def update_config_update_rectification( @router.delete("/", status_code=204) async def delete_config_update_rectification( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete config update rectification""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoConfigUpdateRectificationService.delete(db, pedimento_id, tenant_id) + success = PedimentoConfigUpdateRectificationService.delete(db, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Config update rectification not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_updates.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_updates.py index 5ae2c9e7..e1616cbb 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_updates.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_updates.py @@ -1,10 +1,10 @@ """ Routes for PedimentoConfigUpdates CRUD operations """ -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 get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_config_updates import PedimentoConfigUpdatesService from ..dtos.pedimento_config_updates import ( @@ -20,15 +20,13 @@ router = APIRouter(prefix="/{pedimento_id}/config-updates") @router.get("/", response_model=PedimentoConfigUpdatesResponse) async def get_config_updates( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get config updates by pedimento ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - config = PedimentoConfigUpdatesService.get_by_pedimento_id(db, pedimento_id, tenant_id) + config = PedimentoConfigUpdatesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not config: raise HTTPException(status_code=404, detail="Config updates not found") @@ -39,19 +37,17 @@ async def get_config_updates( async def create_config_updates( pedimento_id: int, data: PedimentoConfigUpdatesCreate, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db) ): """Create config updates""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - config = PedimentoConfigUpdatesService.create(db, data, tenant_id) + config = PedimentoConfigUpdatesService.create(db, data, tenant_id, company_id) return config @@ -59,15 +55,13 @@ async def create_config_updates( async def update_config_updates( pedimento_id: int, data: PedimentoConfigUpdatesUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update config updates""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - config = PedimentoConfigUpdatesService.update(db, pedimento_id, tenant_id, data) + config = PedimentoConfigUpdatesService.update(db, pedimento_id, tenant_id, company_id, data) if not config: raise HTTPException(status_code=404, detail="Config updates not found") @@ -77,15 +71,13 @@ async def update_config_updates( @router.delete("/", status_code=204) async def delete_config_updates( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete config updates""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoConfigUpdatesService.delete(db, pedimento_id, tenant_id) + success = PedimentoConfigUpdatesService.delete(db, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Config updates not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_customs_offices.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_customs_offices.py index 1bb61d4c..3c843c84 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_customs_offices.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_customs_offices.py @@ -1,12 +1,11 @@ """ Routes for PedimentoCustomsOffices CRUD operations """ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from typing import List from core.database import get_core_db -from core.security import get_current_user, get_tenant_from_token - +from core.security import validate_access_to_resource from ..services.pedimento_customs_offices import PedimentoCustomsOfficesService from ..dtos.pedimento_customs_offices import ( PedimentoCustomsOfficesCreate, @@ -21,15 +20,13 @@ router = APIRouter(prefix="/{pedimento_id}/customs-offices") @router.get("/", response_model=List[PedimentoCustomsOfficesResponse]) async def list_customs_offices( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get all customs offices for a pedimento""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - offices = PedimentoCustomsOfficesService.get_by_pedimento_id(db, pedimento_id, tenant_id) + offices = PedimentoCustomsOfficesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) return offices @@ -37,15 +34,13 @@ async def list_customs_offices( async def get_customs_office( pedimento_id: int, office_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get a specific customs office by ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - office = PedimentoCustomsOfficesService.get_by_id(db, office_id, pedimento_id, tenant_id) + office = PedimentoCustomsOfficesService.get_by_id(db, office_id, pedimento_id, tenant_id, company_id) if not office: raise HTTPException(status_code=404, detail="Customs office not found") @@ -56,19 +51,17 @@ async def get_customs_office( async def create_customs_office( pedimento_id: int, data: PedimentoCustomsOfficesCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create a new customs office""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - office = PedimentoCustomsOfficesService.create(db, data, tenant_id) + office = PedimentoCustomsOfficesService.create(db, data, tenant_id, company_id) return office @@ -77,15 +70,13 @@ async def update_customs_office( pedimento_id: int, office_id: int, data: PedimentoCustomsOfficesUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update a customs office""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - office = PedimentoCustomsOfficesService.update(db, office_id, pedimento_id, tenant_id, data) + office = PedimentoCustomsOfficesService.update(db, office_id, pedimento_id, tenant_id, company_id, data) if not office: raise HTTPException(status_code=404, detail="Customs office not found") @@ -96,15 +87,13 @@ async def update_customs_office( async def delete_customs_office( pedimento_id: int, office_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete a customs office""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoCustomsOfficesService.delete(db, office_id, pedimento_id, tenant_id) + success = PedimentoCustomsOfficesService.delete(db, office_id, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Customs office not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_dates.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_dates.py index 2d3f5990..78ab8b08 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_dates.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_dates.py @@ -2,10 +2,10 @@ Routes for PedimentoDates CRUD operations """ import logging -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 get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_dates import PedimentoDatesService from ..dtos.pedimento_dates import ( @@ -21,15 +21,13 @@ logger = logging.getLogger(__name__) @router.get("/", response_model=PedimentoDatesResponse) async def get_dates( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get dates by pedimento ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - dates = PedimentoDatesService.get_by_pedimento_id(db, pedimento_id, tenant_id) + dates = PedimentoDatesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not dates: raise HTTPException(status_code=404, detail="Pedimento dates not found") @@ -39,15 +37,13 @@ async def get_dates( @router.post("/", response_model=PedimentoDatesResponse, status_code=201) async def create_dates( data: PedimentoDatesCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create pedimento dates""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - dates = PedimentoDatesService.create(db, data, tenant_id) + dates = PedimentoDatesService.create(db, data, tenant_id, company_id) return dates @@ -55,15 +51,13 @@ async def create_dates( async def update_dates( pedimento_id: int, data: PedimentoDatesUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update pedimento dates""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - dates = PedimentoDatesService.update(db, pedimento_id, tenant_id, data) + dates = PedimentoDatesService.update(db, pedimento_id, tenant_id, company_id, data) if not dates: raise HTTPException(status_code=404, detail="Pedimento dates not found") @@ -73,15 +67,13 @@ async def update_dates( @router.delete("/", status_code=204) async def delete_dates( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete pedimento dates""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoDatesService.delete(db, pedimento_id, tenant_id) + success = PedimentoDatesService.delete(db, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Pedimento dates not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_decrementables.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_decrementables.py index 22a0e868..fa8871f6 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_decrementables.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_decrementables.py @@ -1,11 +1,11 @@ """ Routes for PedimentoDecrementables CRUD operations """ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from typing import List from core.database import get_core_db -from core.security import get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_decrementables import PedimentoDecrementablesService from ..dtos.pedimento_decrementables import ( @@ -21,15 +21,13 @@ router = APIRouter(prefix="/{pedimento_id}/decrementables") @router.get("/", response_model=List[PedimentoDecrementablesResponse]) async def list_decrementables( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get all decrementables for a pedimento""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - decrementables = PedimentoDecrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id) + decrementables = PedimentoDecrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) return decrementables @@ -37,15 +35,13 @@ async def list_decrementables( async def get_decrementable( pedimento_id: int, decrementable_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get a specific decrementable by ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - decrementable = PedimentoDecrementablesService.get_by_id(db, decrementable_id, pedimento_id, tenant_id) + decrementable = PedimentoDecrementablesService.get_by_id(db, decrementable_id, pedimento_id, tenant_id, company_id) if not decrementable: raise HTTPException(status_code=404, detail="Decrementable not found") @@ -56,19 +52,17 @@ async def get_decrementable( async def create_decrementable( pedimento_id: int, data: PedimentoDecrementablesCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create a new decrementable""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - decrementable = PedimentoDecrementablesService.create(db, data, tenant_id) + decrementable = PedimentoDecrementablesService.create(db, data, tenant_id, company_id) return decrementable @@ -77,15 +71,13 @@ async def update_decrementable( pedimento_id: int, decrementable_id: int, data: PedimentoDecrementablesUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update a decrementable""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - decrementable = PedimentoDecrementablesService.update(db, decrementable_id, pedimento_id, tenant_id, data) + decrementable = PedimentoDecrementablesService.update(db, decrementable_id, pedimento_id, tenant_id, company_id, data) if not decrementable: raise HTTPException(status_code=404, detail="Decrementable not found") @@ -96,15 +88,13 @@ async def update_decrementable( async def delete_decrementable( pedimento_id: int, decrementable_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete a decrementable""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoDecrementablesService.delete(db, decrementable_id, pedimento_id, tenant_id) + success = PedimentoDecrementablesService.delete(db, decrementable_id, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Decrementable not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_incrementables.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_incrementables.py index e5cbccab..b99d317b 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_incrementables.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_incrementables.py @@ -1,11 +1,11 @@ """ Routes for PedimentoIncrementables CRUD operations """ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from typing import List from core.database import get_core_db -from core.security import get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_incrementables import PedimentoIncrementablesService from ..dtos.pedimento_incrementables import ( @@ -21,15 +21,13 @@ router = APIRouter(prefix="/{pedimento_id}/incrementables") @router.get("/", response_model=List[PedimentoIncrementablesResponse]) async def list_incrementables( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get all incrementables for a pedimento""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - incrementables = PedimentoIncrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id) + incrementables = PedimentoIncrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) return incrementables @@ -37,15 +35,13 @@ async def list_incrementables( async def get_incrementable( pedimento_id: int, incrementable_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get a specific incrementable by ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - incrementable = PedimentoIncrementablesService.get_by_id(db, incrementable_id, pedimento_id, tenant_id) + incrementable = PedimentoIncrementablesService.get_by_id(db, incrementable_id, pedimento_id, tenant_id, company_id) if not incrementable: raise HTTPException(status_code=404, detail="Incrementable not found") @@ -56,19 +52,17 @@ async def get_incrementable( async def create_incrementable( pedimento_id: int, data: PedimentoIncrementablesCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create a new incrementable""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - incrementable = PedimentoIncrementablesService.create(db, data, tenant_id) + incrementable = PedimentoIncrementablesService.create(db, data, tenant_id, company_id) return incrementable @@ -77,15 +71,13 @@ async def update_incrementable( pedimento_id: int, incrementable_id: int, data: PedimentoIncrementablesUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update an incrementable""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - incrementable = PedimentoIncrementablesService.update(db, incrementable_id, pedimento_id, tenant_id, data) + incrementable = PedimentoIncrementablesService.update(db, incrementable_id, pedimento_id, tenant_id, company_id, data) if not incrementable: raise HTTPException(status_code=404, detail="Incrementable not found") @@ -96,15 +88,13 @@ async def update_incrementable( async def delete_incrementable( pedimento_id: int, incrementable_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete an incrementable""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoIncrementablesService.delete(db, incrementable_id, pedimento_id, tenant_id) + success = PedimentoIncrementablesService.delete(db, incrementable_id, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Incrementable not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_indexes.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_indexes.py index 31e83d8f..bc89cf02 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_indexes.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_indexes.py @@ -1,10 +1,10 @@ """ Routes for PedimentoIndexes CRUD operations """ -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 get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_indexes import PedimentoIndexesService from ..dtos.pedimento_indexes import ( @@ -20,15 +20,13 @@ router = APIRouter(prefix="/{pedimento_id}/indexes") @router.get("/", response_model=PedimentoIndexesResponse) async def get_indexes( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get indexes by pedimento ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - indexes = PedimentoIndexesService.get_by_pedimento_id(db, pedimento_id, tenant_id) + indexes = PedimentoIndexesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not indexes: raise HTTPException(status_code=404, detail="Pedimento indexes not found") @@ -39,19 +37,17 @@ async def get_indexes( async def create_indexes( pedimento_id: int, data: PedimentoIndexesCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create pedimento indexes""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - indexes = PedimentoIndexesService.create(db, data, tenant_id) + indexes = PedimentoIndexesService.create(db, data, tenant_id, company_id) return indexes @@ -59,15 +55,13 @@ async def create_indexes( async def update_indexes( pedimento_id: int, data: PedimentoIndexesUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update pedimento indexes""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - indexes = PedimentoIndexesService.update(db, pedimento_id, tenant_id, data) + indexes = PedimentoIndexesService.update(db, pedimento_id, tenant_id, company_id, data) if not indexes: raise HTTPException(status_code=404, detail="Pedimento indexes not found") @@ -77,15 +71,13 @@ async def update_indexes( @router.delete("/", status_code=204) async def delete_indexes( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete pedimento indexes""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoIndexesService.delete(db, pedimento_id, tenant_id) + success = PedimentoIndexesService.delete(db, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Pedimento indexes not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py index b1ed4eee..d553d6d3 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py @@ -1,11 +1,11 @@ """ Routes for PedimentoPayments CRUD operations """ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from typing import List from core.database import get_core_db -from core.security import get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_payments import PedimentoPaymentsService from ..dtos.pedimento_payments import ( @@ -21,15 +21,13 @@ router = APIRouter(prefix="/{pedimento_id}/payments") @router.get("/", response_model=List[PedimentoPaymentsResponse]) async def list_payments( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get all payments for a pedimento""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - payments = PedimentoPaymentsService.get_by_pedimento_id(db, pedimento_id, tenant_id) + payments = PedimentoPaymentsService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) return payments @@ -37,15 +35,13 @@ async def list_payments( async def get_payment( pedimento_id: int, payment_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get a specific payment by ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - payment = PedimentoPaymentsService.get_by_id(db, payment_id, pedimento_id, tenant_id) + payment = PedimentoPaymentsService.get_by_id(db, payment_id, pedimento_id, tenant_id, company_id) if not payment: raise HTTPException(status_code=404, detail="Payment not found") @@ -56,19 +52,17 @@ async def get_payment( async def create_payment( pedimento_id: int, data: PedimentoPaymentsCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create a new payment""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - payment = PedimentoPaymentsService.create(db, data, tenant_id) + payment = PedimentoPaymentsService.create(db, data, tenant_id, company_id) return payment @@ -77,15 +71,13 @@ async def update_payment( pedimento_id: int, payment_id: int, data: PedimentoPaymentsUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update a payment""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - payment = PedimentoPaymentsService.update(db, payment_id, pedimento_id, tenant_id, data) + payment = PedimentoPaymentsService.update(db, payment_id, pedimento_id, tenant_id, company_id, data) if not payment: raise HTTPException(status_code=404, detail="Payment not found") @@ -96,15 +88,13 @@ async def update_payment( async def delete_payment( pedimento_id: int, payment_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete a payment""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoPaymentsService.delete(db, payment_id, pedimento_id, tenant_id) + success = PedimentoPaymentsService.delete(db, payment_id, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Payment not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_destination.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_destination.py index d05fbd30..336d1fdc 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_destination.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_destination.py @@ -1,10 +1,10 @@ """ Routes for PedimentoRectificationDestination CRUD operations """ -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 get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_rectification_destination import PedimentoRectificationDestinationService from ..dtos.pedimento_rectification_destination import ( @@ -20,15 +20,13 @@ router = APIRouter(prefix="/{pedimento_id}/rectification-destination") @router.get("/", response_model=PedimentoRectificationDestinationResponse) async def get_rectification_destination( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get rectification destination by pedimento ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - rectification = PedimentoRectificationDestinationService.get_by_pedimento_id(db, pedimento_id, tenant_id) + rectification = PedimentoRectificationDestinationService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not rectification: raise HTTPException(status_code=404, detail="Rectification destination not found") @@ -39,19 +37,17 @@ async def get_rectification_destination( async def create_rectification_destination( pedimento_id: int, data: PedimentoRectificationDestinationCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create rectification destination""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - rectification = PedimentoRectificationDestinationService.create(db, data, tenant_id) + rectification = PedimentoRectificationDestinationService.create(db, data, tenant_id, company_id) return rectification @@ -59,15 +55,13 @@ async def create_rectification_destination( async def update_rectification_destination( pedimento_id: int, data: PedimentoRectificationDestinationUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update rectification destination""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - rectification = PedimentoRectificationDestinationService.update(db, pedimento_id, tenant_id, data) + rectification = PedimentoRectificationDestinationService.update(db, pedimento_id, tenant_id, company_id, data) if not rectification: raise HTTPException(status_code=404, detail="Rectification destination not found") @@ -77,15 +71,13 @@ async def update_rectification_destination( @router.delete("/", status_code=204) async def delete_rectification_destination( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete rectification destination""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoRectificationDestinationService.delete(db, pedimento_id, tenant_id) + success = PedimentoRectificationDestinationService.delete(db, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Rectification destination not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_origin.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_origin.py index 1521f371..8d8b2799 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_origin.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_origin.py @@ -1,10 +1,10 @@ """ Routes for PedimentoRectificationOrigin CRUD operations """ -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 get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_rectification_origin import PedimentoRectificationOriginService from ..dtos.pedimento_rectification_origin import ( @@ -20,15 +20,13 @@ router = APIRouter(prefix="/{pedimento_id}/rectification-origin") @router.get("/", response_model=PedimentoRectificationOriginResponse) async def get_rectification_origin( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get rectification origin by pedimento ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - rectification = PedimentoRectificationOriginService.get_by_pedimento_id(db, pedimento_id, tenant_id) + rectification = PedimentoRectificationOriginService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not rectification: raise HTTPException(status_code=404, detail="Rectification origin not found") @@ -39,19 +37,17 @@ async def get_rectification_origin( async def create_rectification_origin( pedimento_id: int, data: PedimentoRectificationOriginCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create rectification origin""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - rectification = PedimentoRectificationOriginService.create(db, data, tenant_id) + rectification = PedimentoRectificationOriginService.create(db, data, tenant_id, company_id) return rectification @@ -59,15 +55,13 @@ async def create_rectification_origin( async def update_rectification_origin( pedimento_id: int, data: PedimentoRectificationOriginUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update rectification origin""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - rectification = PedimentoRectificationOriginService.update(db, pedimento_id, tenant_id, data) + rectification = PedimentoRectificationOriginService.update(db, pedimento_id, tenant_id, company_id, data) if not rectification: raise HTTPException(status_code=404, detail="Rectification origin not found") @@ -77,15 +71,13 @@ async def update_rectification_origin( @router.delete("/", status_code=204) async def delete_rectification_origin( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete rectification origin""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoRectificationOriginService.delete(db, pedimento_id, tenant_id) + success = PedimentoRectificationOriginService.delete(db, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Rectification origin not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_transport_means.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_transport_means.py index 8365ad2f..ad6b7d60 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_transport_means.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_transport_means.py @@ -1,11 +1,11 @@ """ Routes for PedimentoTransportMeans CRUD operations """ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from typing import List from core.database import get_core_db -from core.security import get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_transport_means import PedimentoTransportMeansService from ..dtos.pedimento_transport_means import ( @@ -21,15 +21,13 @@ router = APIRouter(prefix="/{pedimento_id}/transport-means") @router.get("/", response_model=List[PedimentoTransportMeansResponse]) async def list_transport_means( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get all transport means for a pedimento""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - transport_means = PedimentoTransportMeansService.get_by_pedimento_id(db, pedimento_id, tenant_id) + transport_means = PedimentoTransportMeansService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) return transport_means @@ -37,15 +35,13 @@ async def list_transport_means( async def get_transport_mean( pedimento_id: int, transport_mean_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get a specific transport mean by ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - transport_mean = PedimentoTransportMeansService.get_by_id(db, transport_mean_id, pedimento_id, tenant_id) + transport_mean = PedimentoTransportMeansService.get_by_id(db, transport_mean_id, pedimento_id, tenant_id, company_id) if not transport_mean: raise HTTPException(status_code=404, detail="Transport mean not found") @@ -56,19 +52,17 @@ async def get_transport_mean( async def create_transport_mean( pedimento_id: int, data: PedimentoTransportMeansCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create a new transport mean""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - transport_mean = PedimentoTransportMeansService.create(db, data, tenant_id) + transport_mean = PedimentoTransportMeansService.create(db, data, tenant_id, company_id) return transport_mean @@ -77,15 +71,13 @@ async def update_transport_mean( pedimento_id: int, transport_mean_id: int, data: PedimentoTransportMeansUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update a transport mean""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - transport_mean = PedimentoTransportMeansService.update(db, transport_mean_id, pedimento_id, tenant_id, data) + transport_mean = PedimentoTransportMeansService.update(db, transport_mean_id, pedimento_id, tenant_id, company_id, data) if not transport_mean: raise HTTPException(status_code=404, detail="Transport mean not found") @@ -96,15 +88,13 @@ async def update_transport_mean( async def delete_transport_mean( pedimento_id: int, transport_mean_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete a transport mean""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoTransportMeansService.delete(db, transport_mean_id, pedimento_id, tenant_id) + success = PedimentoTransportMeansService.delete(db, transport_mean_id, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Transport mean not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_validation.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_validation.py index 66164a31..dfbe7589 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_validation.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_validation.py @@ -1,10 +1,10 @@ """ Routes for PedimentoValidation CRUD operations """ -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 get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimento_validation import PedimentoValidationService from ..dtos.pedimento_validation import ( @@ -20,15 +20,13 @@ router = APIRouter(prefix="/{pedimento_id}/validation") @router.get("/", response_model=PedimentoValidationResponse) async def get_validation( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get validation by pedimento ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - validation = PedimentoValidationService.get_by_pedimento_id(db, pedimento_id, tenant_id) + validation = PedimentoValidationService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not validation: raise HTTPException(status_code=404, detail="Pedimento validation not found") @@ -39,19 +37,17 @@ async def get_validation( async def create_validation( pedimento_id: int, data: PedimentoValidationCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create pedimento validation""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - validation = PedimentoValidationService.create(db, data, tenant_id) + validation = PedimentoValidationService.create(db, data, tenant_id, company_id) return validation @@ -59,15 +55,13 @@ async def create_validation( async def update_validation( pedimento_id: int, data: PedimentoValidationUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update pedimento validation""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - validation = PedimentoValidationService.update(db, pedimento_id, tenant_id, data) + validation = PedimentoValidationService.update(db, pedimento_id, tenant_id, company_id, data) if not validation: raise HTTPException(status_code=404, detail="Pedimento validation not found") @@ -77,15 +71,13 @@ async def update_validation( @router.delete("/", status_code=204) async def delete_validation( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete pedimento validation""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentoValidationService.delete(db, pedimento_id, tenant_id) + success = PedimentoValidationService.delete(db, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Pedimento validation not found") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py index 2dd457ef..65aafb4e 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py @@ -5,7 +5,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from typing import Dict, Any, Optional from core.database import get_core_db -from core.security import get_current_user, get_tenant_from_token +from core.security import validate_access_to_resource from ..services.pedimentos import PedimentosService from ..dtos.pedimentos import PedimentosCreate, PedimentosUpdate, PedimentosResponse @@ -16,18 +16,16 @@ router = APIRouter() @router.get("/", response_model=Dict[str, Any]) async def list_pedimentos( + company_id: int = Query(..., description="Company ID"), page: int = Query(1, ge=1, description="Page number"), page_size: int = Query(50, ge=1, le=100, description="Page size"), status: Optional[str] = Query(None, description="Filter by status"), client_id: Optional[int] = Query(None, description="Filter by client ID"), year: Optional[str] = Query(None, description="Filter by year"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get all pedimentos with pagination and filters""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) filters = {} if status: @@ -38,7 +36,7 @@ async def list_pedimentos( filters["year"] = year skip = (page - 1) * page_size - items, total = PedimentosService.get_all(db, tenant_id, skip, page_size, filters) + items, total = PedimentosService.get_all(db, tenant_id, company_id, skip, page_size, filters) return { "items": [PedimentosResponse.model_validate(item) for item in items], @@ -51,15 +49,13 @@ async def list_pedimentos( @router.get("/{pedimento_id}", response_model=PedimentosResponse) async def get_pedimento( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Get a pedimento by ID""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - pedimento = PedimentosService.get_by_id(db, pedimento_id, tenant_id) + pedimento = PedimentosService.get_by_id(db, pedimento_id, tenant_id, company_id) if not pedimento: raise HTTPException(status_code=404, detail="Pedimento not found") @@ -69,15 +65,13 @@ async def get_pedimento( @router.post("/", response_model=PedimentosResponse, status_code=201) async def create_pedimento( data: PedimentosCreate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Create a new pedimento""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - pedimento = PedimentosService.create(db, data, tenant_id) + pedimento = PedimentosService.create(db, data, tenant_id, company_id) return pedimento @@ -85,15 +79,13 @@ async def create_pedimento( async def update_pedimento( pedimento_id: int, data: PedimentosUpdate, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Update a pedimento""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - pedimento = PedimentosService.update(db, pedimento_id, tenant_id, data) + pedimento = PedimentosService.update(db, pedimento_id, tenant_id, company_id, data) if not pedimento: raise HTTPException(status_code=404, detail="Pedimento not found") @@ -103,15 +95,13 @@ async def update_pedimento( @router.delete("/{pedimento_id}", status_code=204) async def delete_pedimento( pedimento_id: int, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) ): """Delete a pedimento""" - tenant_id = get_tenant_from_token(current_user) - if not tenant_id: - raise HTTPException(status_code=400, detail="Tenant ID not found in token") + tenant_id = validate_access_to_resource(company_id) - success = PedimentosService.delete(db, pedimento_id, tenant_id) + success = PedimentosService.delete(db, pedimento_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Pedimento not found") diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_additional.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_additional.py index 40762a99..f29b1428 100644 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_additional.py +++ b/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_additional.py @@ -12,17 +12,21 @@ class PedimentoConfigAdditionalService: """Service class for PedimentoConfigAdditional business logic""" @staticmethod - def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoConfigAdditional]: + def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int, company_id: int) -> Optional[PedimentoConfigAdditional]: """Get config by pedimento ID""" return db.query(PedimentoConfigAdditional).filter( PedimentoConfigAdditional.pedimento_id == pedimento_id, - PedimentoConfigAdditional.tenant_id == tenant_id + PedimentoConfigAdditional.tenant_id == tenant_id, + PedimentoConfigAdditional.company_id == company_id ).first() @staticmethod - def create(db: Session, config_data: PedimentoConfigAdditionalCreate) -> PedimentoConfigAdditional: + def create(db: Session, config_data: PedimentoConfigAdditionalCreate, tenant_id: int, company_id: int) -> PedimentoConfigAdditional: """Create a new config""" config = PedimentoConfigAdditional(**config_data.model_dump()) + config.tenant_id = tenant_id + config.company_id = company_id + db.add(config) db.commit() db.refresh(config) @@ -33,10 +37,11 @@ class PedimentoConfigAdditionalService: db: Session, pedimento_id: int, tenant_id: int, + company_id: int, config_data: PedimentoConfigAdditionalUpdate ) -> Optional[PedimentoConfigAdditional]: """Update config""" - config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id) + config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not config: return None @@ -49,9 +54,9 @@ class PedimentoConfigAdditionalService: return config @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: + def delete(db: Session, pedimento_id: int, tenant_id: int, company_id: int) -> bool: """Delete config""" - config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id) + config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not config: return False diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_calculations.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_calculations.py index 7919bdab..1de5979a 100644 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_calculations.py +++ b/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_calculations.py @@ -12,17 +12,21 @@ class PedimentoConfigCalculationsService: """Service class for PedimentoConfigCalculations business logic""" @staticmethod - def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoConfigCalculations]: + def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int, company_id: int) -> Optional[PedimentoConfigCalculations]: """Get config by pedimento ID""" return db.query(PedimentoConfigCalculations).filter( PedimentoConfigCalculations.pedimento_id == pedimento_id, - PedimentoConfigCalculations.tenant_id == tenant_id + PedimentoConfigCalculations.tenant_id == tenant_id, + PedimentoConfigCalculations.company_id == company_id ).first() @staticmethod - def create(db: Session, config_data: PedimentoConfigCalculationsCreate) -> PedimentoConfigCalculations: + def create(db: Session, config_data: PedimentoConfigCalculationsCreate, tenant_id: int, company_id: int) -> PedimentoConfigCalculations: """Create a new config""" config = PedimentoConfigCalculations(**config_data.model_dump()) + config.tenant_id = tenant_id + config.company_id = company_id + db.add(config) db.commit() db.refresh(config) @@ -33,10 +37,11 @@ class PedimentoConfigCalculationsService: db: Session, pedimento_id: int, tenant_id: int, + company_id: int, config_data: PedimentoConfigCalculationsUpdate ) -> Optional[PedimentoConfigCalculations]: """Update config""" - config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id) + config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not config: return None @@ -49,9 +54,9 @@ class PedimentoConfigCalculationsService: return config @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: + def delete(db: Session, pedimento_id: int, tenant_id: int, company_id: int) -> bool: """Delete config""" - config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id) + config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id) if not config: return False diff --git a/backend/api/v1/modules/a76/permission_rule_oct/routes.py b/backend/api/v1/modules/a76/permission_rule_oct/routes.py index 75fcae79..34a20e5e 100644 --- a/backend/api/v1/modules/a76/permission_rule_oct/routes.py +++ b/backend/api/v1/modules/a76/permission_rule_oct/routes.py @@ -18,6 +18,13 @@ async def list_permissions( """ List all PermissionRuleOct entries. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + return db.query(PermissionRuleOctService).all() @@ -30,6 +37,13 @@ async def read_permission( """ Get a specific PermissionRuleOct by its permission. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + permission = PermissionRuleOctService.get_permission_by_id(db, permission) if not permission: raise HTTPException(status_code=404, detail="PermissionRuleOct not found") @@ -45,6 +59,13 @@ async def create_permission( """ Create a new PermissionRuleOct entry. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + return PermissionRuleOctService.create_permission(db, permission_data) @@ -57,6 +78,13 @@ async def delete_permission( """ Delete a PermissionRuleOct by its permission. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + permission = PermissionRuleOctService.delete_permission(db, permission) if not permission: raise HTTPException(status_code=404, detail="PermissionRuleOct not found") \ No newline at end of file diff --git a/backend/api/v1/modules/a76/permission_rule_oct/test_permission_rule_oct.py b/backend/api/v1/modules/a76/permission_rule_oct/test_permission_rule_oct.py new file mode 100644 index 00000000..f4ee706b --- /dev/null +++ b/backend/api/v1/modules/a76/permission_rule_oct/test_permission_rule_oct.py @@ -0,0 +1,31 @@ +import pytest +from fastapi.testclient import TestClient +from .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_permission_rules(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/permission_rule_oct/", 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_permission_rule_not_found(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/permission_rule_oct/invalid_id", headers=headers) + assert response.status_code == 404 + +def test_create_permission_rule_forbidden(): + response = client.post("/permission_rule_oct/", json={"rule": "Test Rule"}) + assert response.status_code in (403, 405, 404) + +def test_update_permission_rule_forbidden(): + response = client.put("/permission_rule_oct/1", json={"rule": "Updated Rule"}) + assert response.status_code in (403, 405, 404) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/seal/routes.py b/backend/api/v1/modules/a76/seal/routes.py index cbd60d92..ba1e6892 100644 --- a/backend/api/v1/modules/a76/seal/routes.py +++ b/backend/api/v1/modules/a76/seal/routes.py @@ -22,6 +22,13 @@ async def list_seals( """ List all Seal entries. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + return db.query(SealService).all() @@ -34,6 +41,13 @@ async def read_seal( """ Get a specific Seal by its seal. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + seal = SealService.get_seal_by_id(db, seal) if not seal: raise HTTPException(status_code=404, detail="Seal not found") @@ -49,6 +63,13 @@ async def create_seal( """ Create a new Seal entry. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + return SealService.create_seal(db, seal_data) @@ -61,6 +82,13 @@ async def delete_seal( """ Delete a Seal by its seal. """ + # Validate access to the tenant and company + tenant_id = current_user.get("tenant_id") + company_id = current_user.get("company_id") + + if not tenant_id or not company_id: + raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") + seal = SealService.delete_seal(db, seal) if not seal: raise HTTPException(status_code=404, detail="Seal not found") \ No newline at end of file diff --git a/backend/api/v1/modules/a76/seal/test_seal.py b/backend/api/v1/modules/a76/seal/test_seal.py new file mode 100644 index 00000000..80e35b91 --- /dev/null +++ b/backend/api/v1/modules/a76/seal/test_seal.py @@ -0,0 +1,31 @@ +import pytest +from fastapi.testclient import TestClient +from .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_seals(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/seal/", 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_seal_not_found(client, access_token): + headers = {"Authorization": f"Bearer {access_token}"} + response = client.get("/seal/invalid_id", headers=headers) + assert response.status_code == 404 + +def test_create_seal_forbidden(): + response = client.post("/seal/", json={"name": "Test Seal"}) + assert response.status_code in (403, 405, 404) + +def test_update_seal_forbidden(): + response = client.put("/seal/1", json={"name": "Updated Seal"}) + assert response.status_code in (403, 405, 404) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/user_tenant/models.py b/backend/api/v1/modules/a76/user_tenant/models.py index e6e141d9..d67ce1ee 100644 --- a/backend/api/v1/modules/a76/user_tenant/models.py +++ b/backend/api/v1/modules/a76/user_tenant/models.py @@ -1,7 +1,7 @@ """ Modelo de relación entre usuarios (Keycloak) y tenants """ -from sqlalchemy import Integer, String, DateTime, Boolean, UniqueConstraint, ForeignKeyConstraint +from sqlalchemy import Integer, String, DateTime, Boolean, UniqueConstraint, ForeignKeyConstraint, ForeignKey from sqlalchemy.sql import func from sqlalchemy.orm import Mapped, mapped_column, relationship from datetime import datetime @@ -34,6 +34,9 @@ class UserTenant(Base): # ID del tenant tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True) + # ID de la empresa asociada + company_id: Mapped[int] = mapped_column(Integer, ForeignKey("a76.companies.id"), nullable=False, index=True) + # Estado de la relación is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) diff --git a/backend/core/security.py b/backend/core/security.py index 9b3d5659..96f29405 100644 --- a/backend/core/security.py +++ b/backend/core/security.py @@ -8,6 +8,9 @@ from jose import jwt, JWTError from typing import Optional, Dict, Any from .config import settings import logging +from sqlalchemy.orm import Session +from core.database import get_core_db +from api.v1.modules.a76.user_tenant.models import UserTenant logger = logging.getLogger(__name__) @@ -75,16 +78,33 @@ def verify_token(token: str) -> Dict[str, Any]: async def get_current_user( - credentials: HTTPAuthorizationCredentials = Security(security) + credentials: HTTPAuthorizationCredentials = Security(security), + db: Session = Depends(get_core_db) ) -> Dict[str, Any]: """ Dependency para obtener el usuario actual desde el token JWT + Enriquecido con tenant_id y company_id desde la tabla user_tenant Uso en FastAPI: current_user: dict = Depends(get_current_user) """ token = credentials.credentials user_info = verify_token(token) + + # Obtener user_id desde el token + user_id = user_info.get("sub") + if not user_id: + raise HTTPException(status_code=401, detail="User ID not found in token") + + # Consultar la tabla user_tenant para obtener tenant_id y company_id + user_tenant = db.query(UserTenant).filter(UserTenant.keycloak_user_id == user_id).first() + if not user_tenant: + raise HTTPException(status_code=403, detail="User does not have access to any tenant or company") + + # Enriquecer user_info con tenant_id y company_id + user_info["tenant_id"] = user_tenant.tenant_id + user_info["company_id"] = user_tenant.company_id + return user_info @@ -146,6 +166,62 @@ def get_tenant_from_token(user_info: Dict[str, Any]) -> Optional[int]: return None +def validate_company_access( + company_id: int, + current_user: Dict[str, Any] +) -> bool: + """ + Valida que el usuario tenga acceso a la compañía solicitada + + Args: + company_id: ID de la compañía a la que se quiere acceder + current_user: Información del usuario actual desde el token + + Returns: + True si el usuario tiene acceso, False en caso contrario + + Nota: + Por ahora solo verifica que el tenant_id del usuario coincida con el company_id. + Se puede extender para validar permisos específicos por compañía. + """ + tenant_id = get_tenant_from_token(current_user) + + # Si no hay tenant_id en el token, denegar acceso + if not tenant_id: + return False + + # Validar que el company_id pertenezca al tenant del usuario + # Por ahora asumimos que company_id == tenant_id + # Esto se puede modificar si hay una tabla de relación tenant-company + return tenant_id == company_id + +def validate_access_to_resource( + company_id: int, + current_user: dict = Depends(get_current_user) +) -> bool: + """ + Valida que el usuario tenga acceso a un recurso específico basado en company_id + y regresa el tenant_id + + Args: + company_id: company_id asociado al recurso + current_user: Información del usuario actual desde el token + + Returns: + True si el usuario tiene acceso, False en caso contrario + """ + + tenant_id = get_tenant_from_token(current_user) + if not tenant_id: + raise HTTPException(status_code=400, detail="Tenant ID not found in token") + + if not validate_company_access(company_id, current_user): + raise HTTPException(status_code=403, detail="Access denied to this company") + + # Validar que el tenant_id del usuario coincida con el del recurso + return tenant_id + + class KeycloakClient: """Cliente para interactuar con Keycloak Admin API"""