From a1561d379696eefd497a198a1605c80ec594daec Mon Sep 17 00:00:00 2001 From: hreyes Date: Mon, 4 May 2026 15:43:34 -0600 Subject: [PATCH] feature/permisos-pedimentos --- .../routes/pedimento_config_additional.py | 4 + .../routes/pedimento_config_calculations.py | 4 + .../routes/pedimento_config_parameters.py | 4 + .../routes/pedimento_config_surcharges.py | 4 + .../pedimento_config_update_rectification.py | 4 + .../routes/pedimento_config_updates.py | 4 + .../routes/pedimento_customs_offices.py | 20 +++-- .../a76/pedmientos/routes/pedimento_dates.py | 4 + .../routes/pedimento_decrementables.py | 20 +++-- .../routes/pedimento_incrementables.py | 20 +++-- .../pedmientos/routes/pedimento_indexes.py | 4 + .../pedmientos/routes/pedimento_payments.py | 20 +++-- .../pedimento_rectification_destination.py | 4 + .../routes/pedimento_rectification_origin.py | 4 + .../routes/pedimento_transport_means.py | 20 +++-- .../pedmientos/routes/pedimento_validation.py | 4 + .../a76/pedmientos/routes/pedimentos.py | 7 +- .../src/lib/components/sidebar/modules.ts | 5 ++ .../lib/permissions/pedimento-permissions.ts | 43 +++++++++ .../dashboard/pedimentos/+page.server.ts | 26 +++--- .../routes/dashboard/pedimentos/+page.svelte | 90 +++++++++++++++---- .../pedimentos/edit/[id]/+page.server.ts | 8 +- .../pedimentos/edit/[id]/+page.svelte | 15 ++++ 23 files changed, 279 insertions(+), 59 deletions(-) create mode 100644 frontend/src/lib/permissions/pedimento-permissions.ts 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 650cb92d..2c82b61f 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 @@ -23,4 +23,8 @@ router = TenantCRUDRoutes( parent_id_name="pedimento_id", enable_list=False, validate_parent_match=True, + get_permissions=["pedimentos_mgmt.view"], + create_permissions=["pedimentos_mgmt.edit"], + update_permissions=["pedimentos_mgmt.edit"], + delete_permissions=["pedimentos_mgmt.edit"], ).router 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 ceb572c4..0fce3aa3 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 @@ -23,4 +23,8 @@ router = TenantCRUDRoutes( parent_id_name="pedimento_id", enable_list=False, validate_parent_match=True, + get_permissions=["pedimentos_mgmt.view"], + create_permissions=["pedimentos_mgmt.edit"], + update_permissions=["pedimentos_mgmt.edit"], + delete_permissions=["pedimentos_mgmt.edit"], ).router 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 c182a1fc..5afa06d3 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 @@ -23,4 +23,8 @@ router = TenantCRUDRoutes( parent_id_name="pedimento_id", enable_list=False, validate_parent_match=True, + get_permissions=["pedimentos_mgmt.view"], + create_permissions=["pedimentos_mgmt.edit"], + update_permissions=["pedimentos_mgmt.edit"], + delete_permissions=["pedimentos_mgmt.edit"], ).router 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 d2575d18..508e75ea 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 @@ -23,4 +23,8 @@ router = TenantCRUDRoutes( parent_id_name="pedimento_id", enable_list=False, validate_parent_match=True, + get_permissions=["pedimentos_mgmt.view"], + create_permissions=["pedimentos_mgmt.edit"], + update_permissions=["pedimentos_mgmt.edit"], + delete_permissions=["pedimentos_mgmt.edit"], ).router 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 c16ce5a8..6d9ed316 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 @@ -25,4 +25,8 @@ router = TenantCRUDRoutes( parent_id_name="pedimento_id", enable_list=False, validate_parent_match=True, + get_permissions=["pedimentos_mgmt.view"], + create_permissions=["pedimentos_mgmt.edit"], + update_permissions=["pedimentos_mgmt.edit"], + delete_permissions=["pedimentos_mgmt.edit"], ).router 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 d026b2bf..aa763ed4 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 @@ -23,4 +23,8 @@ router = TenantCRUDRoutes( parent_id_name="pedimento_id", enable_list=False, validate_parent_match=True, + get_permissions=["pedimentos_mgmt.view"], + create_permissions=["pedimentos_mgmt.edit"], + update_permissions=["pedimentos_mgmt.edit"], + delete_permissions=["pedimentos_mgmt.edit"], ).router 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 255ffef1..5de86a85 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 @@ -27,7 +27,9 @@ async def list_customs_offices( current_user: Dict[str, Any] = Depends(get_current_user), ): """Get all customs offices for a pedimento""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.view"] + ) offices = PedimentoCustomsOfficesService.get_by_pedimento_id( db, pedimento_id, tenant_id, company_id @@ -44,7 +46,9 @@ async def get_customs_office( current_user: Dict[str, Any] = Depends(get_current_user), ): """Get a specific customs office by ID""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.view"] + ) office = PedimentoCustomsOfficesService.get_by_id( db, office_id, pedimento_id, tenant_id, company_id @@ -64,7 +68,9 @@ async def create_customs_office( current_user: Dict[str, Any] = Depends(get_current_user), ): """Create a new customs office""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: @@ -84,7 +90,9 @@ async def update_customs_office( current_user: Dict[str, Any] = Depends(get_current_user), ): """Update a customs office""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) office = PedimentoCustomsOfficesService.update( db, office_id, pedimento_id, tenant_id, company_id, data @@ -104,7 +112,9 @@ async def delete_customs_office( current_user: Dict[str, Any] = Depends(get_current_user), ): """Delete a customs office""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) success = PedimentoCustomsOfficesService.delete( db, office_id, pedimento_id, tenant_id, company_id 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 4b3788cd..6d30b124 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_dates.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_dates.py @@ -23,4 +23,8 @@ router = TenantCRUDRoutes( parent_id_name="pedimento_id", enable_list=False, validate_parent_match=True, + get_permissions=["pedimentos_mgmt.view"], + create_permissions=["pedimentos_mgmt.edit"], + update_permissions=["pedimentos_mgmt.edit"], + delete_permissions=["pedimentos_mgmt.edit"], ).router 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 3dcb2c4a..7c36fc91 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_decrementables.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_decrementables.py @@ -27,7 +27,9 @@ async def list_decrementables( current_user: Dict[str, Any] = Depends(get_current_user), ): """Get all decrementables for a pedimento""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.view"] + ) decrementables = PedimentoDecrementablesService.get_by_pedimento_id( db, pedimento_id, tenant_id, company_id @@ -44,7 +46,9 @@ async def get_decrementable( current_user: Dict[str, Any] = Depends(get_current_user), ): """Get a specific decrementable by ID""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.view"] + ) decrementable = PedimentoDecrementablesService.get_by_id( db, decrementable_id, pedimento_id, tenant_id, company_id @@ -64,7 +68,9 @@ async def create_decrementable( current_user: Dict[str, Any] = Depends(get_current_user), ): """Create a new decrementable""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: @@ -86,7 +92,9 @@ async def update_decrementable( current_user: Dict[str, Any] = Depends(get_current_user), ): """Update a decrementable""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) decrementable = PedimentoDecrementablesService.update( db, decrementable_id, pedimento_id, tenant_id, company_id, data @@ -106,7 +114,9 @@ async def delete_decrementable( current_user: Dict[str, Any] = Depends(get_current_user), ): """Delete a decrementable""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) success = PedimentoDecrementablesService.delete( db, decrementable_id, pedimento_id, tenant_id, company_id 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 fcfc2778..5912f85b 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_incrementables.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_incrementables.py @@ -27,7 +27,9 @@ async def list_incrementables( current_user: Dict[str, Any] = Depends(get_current_user), ): """Get all incrementables for a pedimento""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.view"] + ) incrementables = PedimentoIncrementablesService.get_by_pedimento_id( db, pedimento_id, tenant_id, company_id @@ -44,7 +46,9 @@ async def get_incrementable( current_user: Dict[str, Any] = Depends(get_current_user), ): """Get a specific incrementable by ID""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.view"] + ) incrementable = PedimentoIncrementablesService.get_by_id( db, incrementable_id, pedimento_id, tenant_id, company_id @@ -64,7 +68,9 @@ async def create_incrementable( current_user: Dict[str, Any] = Depends(get_current_user), ): """Create a new incrementable""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: @@ -86,7 +92,9 @@ async def update_incrementable( current_user: Dict[str, Any] = Depends(get_current_user), ): """Update an incrementable""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) incrementable = PedimentoIncrementablesService.update( db, incrementable_id, pedimento_id, tenant_id, company_id, data @@ -106,7 +114,9 @@ async def delete_incrementable( current_user: Dict[str, Any] = Depends(get_current_user), ): """Delete an incrementable""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) success = PedimentoIncrementablesService.delete( db, incrementable_id, pedimento_id, tenant_id, company_id 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 c808b559..0d6a90d4 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_indexes.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_indexes.py @@ -23,4 +23,8 @@ router = TenantCRUDRoutes( parent_id_name="pedimento_id", enable_list=False, validate_parent_match=True, + get_permissions=["pedimentos_mgmt.view"], + create_permissions=["pedimentos_mgmt.edit"], + update_permissions=["pedimentos_mgmt.edit"], + delete_permissions=["pedimentos_mgmt.edit"], ).router 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 7078e26f..958fbe36 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py @@ -27,7 +27,9 @@ async def list_payments( current_user: Dict[str, Any] = Depends(get_current_user), ): """Get all payments for a pedimento""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.view"] + ) payments = PedimentoPaymentsService.get_by_pedimento_id( db, pedimento_id, tenant_id, company_id @@ -44,7 +46,9 @@ async def get_payment( current_user: Dict[str, Any] = Depends(get_current_user), ): """Get a specific payment by ID""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.view"] + ) payment = PedimentoPaymentsService.get_by_id( db, id, pedimento_id, tenant_id, company_id @@ -64,7 +68,9 @@ async def create_payment( current_user: Dict[str, Any] = Depends(get_current_user), ): """Create a new payment""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: @@ -84,7 +90,9 @@ async def update_payment( current_user: Dict[str, Any] = Depends(get_current_user), ): """Update a payment""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) payment = PedimentoPaymentsService.update( db, id, pedimento_id, tenant_id, company_id, data @@ -104,7 +112,9 @@ async def delete_payment( current_user: Dict[str, Any] = Depends(get_current_user), ): """Delete a payment""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) success = PedimentoPaymentsService.delete( db, id, pedimento_id, tenant_id, company_id 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 59dec341..db359cb5 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 @@ -25,4 +25,8 @@ router = TenantCRUDRoutes( parent_id_name="pedimento_id", enable_list=False, validate_parent_match=True, + get_permissions=["pedimentos_mgmt.view"], + create_permissions=["pedimentos_mgmt.edit"], + update_permissions=["pedimentos_mgmt.edit"], + delete_permissions=["pedimentos_mgmt.edit"], ).router 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 0b20fce4..005a3609 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 @@ -25,4 +25,8 @@ router = TenantCRUDRoutes( parent_id_name="pedimento_id", enable_list=False, validate_parent_match=True, + get_permissions=["pedimentos_mgmt.view"], + create_permissions=["pedimentos_mgmt.edit"], + update_permissions=["pedimentos_mgmt.edit"], + delete_permissions=["pedimentos_mgmt.edit"], ).router 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 f3015a49..893a7474 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 @@ -27,7 +27,9 @@ async def list_transport_means( current_user: Dict[str, Any] = Depends(get_current_user), ): """Get all transport means for a pedimento""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.view"] + ) transport_means = PedimentoTransportMeansService.get_by_pedimento_id( db, pedimento_id, tenant_id, company_id @@ -44,7 +46,9 @@ async def get_transport_mean( current_user: Dict[str, Any] = Depends(get_current_user), ): """Get a specific transport mean by ID""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.view"] + ) transport_mean = PedimentoTransportMeansService.get_by_id( db, transport_mean_id, pedimento_id, tenant_id, company_id @@ -64,7 +68,9 @@ async def create_transport_mean( current_user: Dict[str, Any] = Depends(get_current_user), ): """Create a new transport mean""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) # Ensure pedimento_id matches if data.pedimento_id != pedimento_id: @@ -86,7 +92,9 @@ async def update_transport_mean( current_user: Dict[str, Any] = Depends(get_current_user), ): """Update a transport mean""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) transport_mean = PedimentoTransportMeansService.update( db, transport_mean_id, pedimento_id, tenant_id, company_id, data @@ -106,7 +114,9 @@ async def delete_transport_mean( current_user: Dict[str, Any] = Depends(get_current_user), ): """Delete a transport mean""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user, ["pedimentos_mgmt.edit"] + ) success = PedimentoTransportMeansService.delete( db, transport_mean_id, pedimento_id, tenant_id, company_id 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 47af93a5..7b916c50 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_validation.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_validation.py @@ -23,4 +23,8 @@ router = TenantCRUDRoutes( parent_id_name="pedimento_id", enable_list=False, # Child resource - no list endpoint validate_parent_match=True, # Validate pedimento_id matches in create + get_permissions=["pedimentos_mgmt.view"], + create_permissions=["pedimentos_mgmt.edit"], + update_permissions=["pedimentos_mgmt.edit"], + delete_permissions=["pedimentos_mgmt.edit"], ).router diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py index 060c1d69..c1cfe07f 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py @@ -30,7 +30,8 @@ async def get_creation_data( Consolidates multiple catalog calls into a single endpoint. """ from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["pedimentos_mgmt.view"]) + # Alta de pedimento: misma capacidad que editar en negocio / UI (pedimento-permissions). + validate_access_to_resource(db, company_id, current_user, ["pedimentos_mgmt.edit"]) tenant_id = current_user["tenant_id"] @@ -55,7 +56,7 @@ async def get_edition_data( Consolidates multiple catalog calls + pedimento fetch into a single endpoint. """ from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["pedimentos_mgmt.view"]) + validate_access_to_resource(db, company_id, current_user, ["pedimentos_mgmt.edit"]) tenant_id = current_user["tenant_id"] @@ -92,7 +93,7 @@ crud_router = TenantCRUDRoutes( max_page_size=1000, list_permissions=["pedimentos_mgmt.view"], get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.create"], + create_permissions=["pedimentos_mgmt.edit"], update_permissions=["pedimentos_mgmt.edit"], delete_permissions=["pedimentos_mgmt.delete"], ).router diff --git a/frontend/src/lib/components/sidebar/modules.ts b/frontend/src/lib/components/sidebar/modules.ts index 78631f18..ce6a4360 100644 --- a/frontend/src/lib/components/sidebar/modules.ts +++ b/frontend/src/lib/components/sidebar/modules.ts @@ -386,22 +386,27 @@ export function getSidebarData(): SidebarData { { title: m["sidebar.pedimentos.pedimento_management"](), url: "/dashboard/pedimentos", + permission: 'pedimentos_mgmt.view', }, { title: m["sidebar.pedimentos.pedimento_codes"](), url: "/dashboard/reference_data/pedimento_codes", + permission: 'ref_pedimento_codes.view', }, { title: m["sidebar.pedimentos.customs_regimes"](), url: "/dashboard/reference_data/pedimento_regimens", + permission: 'ref_pedimento_regimens.view', }, { title: m["sidebar.pedimentos.payment_methods"](), url: "/dashboard/reference_data/payment_methods", + permission: 'pedimentos_payments.view', }, { title: m["sidebar.pedimentos.customs_sections"](), url: "/dashboard/reference_data/customs_sections", + permission: 'ref_customs_sections.view', }, { title: m["sidebar.pedimentos.anexo_22_app_31"](), diff --git a/frontend/src/lib/permissions/pedimento-permissions.ts b/frontend/src/lib/permissions/pedimento-permissions.ts new file mode 100644 index 00000000..7e43df98 --- /dev/null +++ b/frontend/src/lib/permissions/pedimento-permissions.ts @@ -0,0 +1,43 @@ +/** + * Permisos de pedimentos — códigos `pedimentos_mgmt.*` (seed / API). + * + * - **Ver listado:** `userHasPermission` (incluye comodín de rol Keycloak `admin` como en otras pantallas). + * - **Crear / editar / borrar (mutaciones):** solo códigos en `user.permissions` (tras `syncCompanyPermissions`). + * + * **Alta de pedimento (`Nuevo` / `/edit/new`):** misma regla que editar — `pedimentos_mgmt.edit` + * (negocio: alta y edición son la misma capacidad). + */ +import type { User } from '$lib/auth'; +import { userHasPermission } from '$lib/auth'; + +function hasAssignedCode(user: User | null, code: string): boolean { + if (!user) return false; + return user.permissions.includes(code); +} + +export function canViewPedimentosList(user: User | null): boolean { + return userHasPermission(user, 'pedimentos_mgmt.view'); +} + +/** Permiso de API/rol para POST crear pedimento; el botón "Nuevo" en UI usa `canOpenNuevoPedimentoForm` (edit). */ +export function canCreatePedimento(user: User | null): boolean { + return hasAssignedCode(user, 'pedimentos_mgmt.create'); +} + +export function canEditPedimento(user: User | null): boolean { + return hasAssignedCode(user, 'pedimentos_mgmt.edit'); +} + +export function canDeletePedimento(user: User | null): boolean { + return hasAssignedCode(user, 'pedimentos_mgmt.delete'); +} + +/** Botón "Nuevo Pedimento" y navegación a `/edit/new` — requiere poder editar en RBAC de la app. */ +export function canOpenNuevoPedimentoForm(user: User | null): boolean { + return canEditPedimento(user); +} + +/** Pantalla de alta (`/edit/new`) o edición — ambas exigen `pedimentos_mgmt.edit` en cliente (alineado a API). */ +export function canAccessPedimentoEditorPage(user: User | null, _isCreate: boolean): boolean { + return canEditPedimento(user); +} diff --git a/frontend/src/routes/dashboard/pedimentos/+page.server.ts b/frontend/src/routes/dashboard/pedimentos/+page.server.ts index 19a07fcc..16be9ae1 100644 --- a/frontend/src/routes/dashboard/pedimentos/+page.server.ts +++ b/frontend/src/routes/dashboard/pedimentos/+page.server.ts @@ -1,17 +1,14 @@ import type { PageServerLoad } from './$types'; import { redirect } from '@sveltejs/kit'; -import { - getAuthTokens, - authenticatedFetch -} from '$lib/server/api'; +import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => { // Esperar a que el layout padre valide/refresque el token const parentData = await parent(); - + // Verificar autenticación const { accessToken } = getAuthTokens(cookies); - + if (!accessToken) { throw redirect(302, '/login'); } @@ -23,13 +20,13 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => { // 3. Primera compañía del usuario (fallback) const companyIdParam = url.searchParams.get('company_id'); const cookieCompanyId = cookies.get('active_company_id'); - - const companyId = companyIdParam + + const companyId = companyIdParam ? parseInt(companyIdParam) : cookieCompanyId ? parseInt(cookieCompanyId) : parentData.companies?.[0]?.id; - + // Si aún no hay companyId, mostrar error if (!companyId) { return { @@ -41,8 +38,8 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => { companies: parentData.companies || [] }; } - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens + + // Misma idea que facturas: lista desde API; permisos en cliente vía syncCompanyPermissions + userHasPermission const response = await authenticatedFetch( `v1/a76/pedimentos?company_id=${companyId}&page=1&page_size=50`, {}, @@ -52,12 +49,17 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => { ); if (!response.ok) { + const message = + response.status === 403 + ? 'No tiene permiso para ver pedimentos' + : 'Error al cargar pedimentos'; + return { items: [], total: 0, page: 1, page_size: 50, - error: 'Error al cargar pedimentos', + error: message, companies: parentData.companies || [], currentCompanyId: companyId }; diff --git a/frontend/src/routes/dashboard/pedimentos/+page.svelte b/frontend/src/routes/dashboard/pedimentos/+page.svelte index d3173a51..6f70ee4a 100644 --- a/frontend/src/routes/dashboard/pedimentos/+page.svelte +++ b/frontend/src/routes/dashboard/pedimentos/+page.svelte @@ -21,10 +21,22 @@ import { reportsWinsaaiApi } from '$lib/api/dashboard/a76/reports/reports-winsaai'; import PdfProgressDialog from '$lib/components/dashboard/invoices/pdf-progress-dialog.svelte'; import { Checkbox } from '$lib/components/ui/checkbox'; + import { get } from 'svelte/store'; + import { currentUser } from '$lib/auth'; + import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; + import { + canViewPedimentosList, + canEditPedimento, + canDeletePedimento, + canOpenNuevoPedimentoForm + } from '$lib/permissions/pedimento-permissions'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); + const canViewList = $derived(canViewPedimentosList($currentUser)); + const canNuevoPedimentoAction = $derived(canOpenNuevoPedimentoForm($currentUser)); + // Estado para filtros let filters = $state({ status: '', @@ -69,6 +81,14 @@ let filters = $state({ window.addEventListener('companyChanged', handleCompanyChange as EventListener); + void (async () => { + const cid = companyStore.activeCompany?.id; + if (cid) { + const { syncCompanyPermissions } = await import('$lib/auth'); + await syncCompanyPermissions(cid); + } + })(); + // Cleanup return () => { window.removeEventListener('companyChanged', handleCompanyChange as EventListener); @@ -95,7 +115,6 @@ let filters = $state({ // Estado para selección de filas let selectedId = $state(null); - let hasSelection = $derived(selectedId !== null); let showDeleteDialog = $state(false); let isWinsaiiConfirmOpen = $state(false); let isWinsaiiByClass = $state(false); @@ -107,6 +126,12 @@ let filters = $state({ let currentStatusFunction = $state(null); let selectedPedimento = $derived(allItems.find((p) => p.id === selectedId) || null); + const canEditSelected = $derived( + selectedPedimento ? canEditPedimento($currentUser) : false + ); + const canDeleteSelected = $derived( + selectedPedimento ? canDeletePedimento($currentUser) : false + ); function handleRowClick(pedimento: Pedimento) { // Toggle: si ya está seleccionado, deseleccionar; si no, seleccionar @@ -114,6 +139,10 @@ let filters = $state({ } function handleEditSelected() { + if (!canEditPedimento(get(currentUser))) { + toast.error('No tiene permiso para editar pedimentos'); + return; + } if (selectedId) { window.location.href = `/dashboard/pedimentos/edit/${selectedId}`; } @@ -123,6 +152,10 @@ let filters = $state({ if (!selectedId) { return; } + if (!canDeletePedimento(get(currentUser))) { + toast.error('No tiene permiso para eliminar pedimentos'); + return; + } showDeleteDialog = true; } @@ -268,18 +301,6 @@ let filters = $state({ } } - useShortcuts( - 'Pedimentos', - obtenerAtajosListaPedimento({ - manejarCrear: handleCreateClick, - manejarActualizar: reloadData, - manejarEditar: handleEditSelected, - manejarEliminar: handleDelete, - irATabla: focusFirstPedimentoTableRow, - irAAcciones: focusPedimentoListFooterActions - }) - ); - async function loadMore() { if (loading || !hasMore) return; @@ -446,7 +467,10 @@ let filters = $state({ } function handleCreateClick() { - // Redirigir a la página de creación (reusa la página de edición con ID "new") + if (!canOpenNuevoPedimentoForm(get(currentUser))) { + toast.error('No tiene permiso para editar pedimentos'); + return; + } window.location.href = '/dashboard/pedimentos/edit/new'; } @@ -455,6 +479,18 @@ let filters = $state({ reloadData(); } + useShortcuts( + 'Pedimentos', + obtenerAtajosListaPedimento({ + manejarCrear: handleCreateClick, + manejarActualizar: reloadData, + manejarEditar: handleEditSelected, + manejarEliminar: handleDelete, + irATabla: focusFirstPedimentoTableRow, + irAAcciones: focusPedimentoListFooterActions + }) + ); + // Opciones de status para el filtro const statusOptions = [ { value: '', label: 'Todos' }, @@ -479,6 +515,13 @@ let filters = $state({ const columns = createColumns(handleSuccess); +{#if !canViewList} +
+ +
+{:else}
@@ -500,7 +543,7 @@ let filters = $state({ {/each} - @@ -581,11 +624,21 @@ let filters = $state({
- - @@ -593,7 +646,7 @@ let filters = $state({ variant="outline" size="sm" onclick={handleInterfaceAgenteAduanal} - disabled={!hasSelection} + disabled={!selectedPedimento} > Interface Agente Aduanal @@ -602,6 +655,7 @@ let filters = $state({
+{/if} diff --git a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts index e8cbfed3..dd1c1cdd 100644 --- a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts +++ b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts @@ -29,7 +29,10 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => { if (!response.ok) { console.error('Error fetching creation data:', response.status); - // Graceful degradation: return empty data + if (response.status === 403) { + throw error(403, 'No tiene permiso para editar pedimentos'); + } + // Graceful degradation: return empty data (no autorización / errores distintos de 403) return { pedimento: null, pedimentoId: null, @@ -102,6 +105,9 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => { if (response.status === 401) { throw redirect(302, '/login'); } + if (response.status === 403) { + throw error(403, 'No tiene permiso para editar pedimentos'); + } throw error(response.status, 'Error al cargar el pedimento'); } diff --git a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte index 61921c6c..2f3aa89c 100644 --- a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte +++ b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte @@ -85,9 +85,16 @@ import { companyStore } from '$lib/stores/company.svelte'; import PrerequisitesModal from '$lib/components/dashboard/PrerequisitesModal.svelte'; + import { currentUser } from '$lib/auth'; + import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; + import { canAccessPedimentoEditorPage } from '$lib/permissions/pedimento-permissions'; let { data }: { data: ExtendedPageData } = $props(); + const canUsePedimentoEditor = $derived( + canAccessPedimentoEditorPage($currentUser, !!data.isCreate) + ); + let activeTab = $state('general'); // Focus first input when switching main tabs (mouse or shortcut) @@ -1069,6 +1076,13 @@ } +{#if !canUsePedimentoEditor} +
+ +
+{:else}
@@ -1297,3 +1311,4 @@ /> {/if}
+{/if}