diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/routes.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/routes.py index 098500eb..50b9586d 100644 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/routes.py +++ b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/routes.py @@ -9,7 +9,7 @@ from uuid import uuid4 from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends from sqlalchemy.orm import Session -from typing import Dict, Any +from typing import Dict, Any, Optional from core.celery_app import celery_app from core.database import get_core_db @@ -39,6 +39,50 @@ def _get_redis(): return redis.Redis.from_url(url, decode_responses=False) +def _assert_us_tariff_csv_job_access( + db: Session, + job_id: str, + current_user: Dict[str, Any], +) -> None: + from api.v1.modules.core.tasks_tracking.models import TaskRun + + company_id: Optional[int] = None + row = db.query(TaskRun).filter(TaskRun.task_id == job_id).first() + if row is not None and row.company_id is not None: + company_id = int(row.company_id) + + r = _get_redis() + if company_id is None: + raw = r.get(f"{FA_IMPORT_META_PREFIX}{job_id}") + if raw: + meta = json.loads(raw.decode("utf-8")) + cid = meta.get("company_id") + if cid is not None: + company_id = int(cid) + + if company_id is None and row is not None and row.meta_payload: + layout_jid = row.meta_payload.get("layout_import_job_id") + if layout_jid: + raw2 = r.get(f"{FA_IMPORT_META_PREFIX}{layout_jid}") + if raw2: + meta2 = json.loads(raw2.decode("utf-8")) + cid2 = meta2.get("company_id") + if cid2 is not None: + company_id = int(cid2) + + if company_id is None: + raise HTTPException(status_code=403, detail="Sin acceso a este job") + try: + validate_access_to_resource( + db, company_id, current_user, ["csv_upload.process"] + ) + except HTTPException: + raise + except Exception as e: + logger.error("US tariff fractions import: job access validation failed: %s", e) + raise HTTPException(status_code=403, detail="Sin acceso a este job") from None + + @router.post("/upload", response_model=ImportJobResponse) async def upload_import_file( file: UploadFile = File(...), @@ -112,10 +156,15 @@ async def upload_import_file( @router.get("/{job_id}/status") -async def get_import_status(job_id: str): +async def get_import_status( + job_id: str, + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): """ Polling: estado del escaneo o del commit. """ + _assert_us_tariff_csv_job_access(db, job_id, current_user) task_result = celery_app.AsyncResult(job_id) if task_result.state == "PENDING": @@ -179,6 +228,7 @@ async def commit_import_job( task_name="us_tariff_fractions_insert_valid_rows", task_origin="a76/layouts_csv/us_tariff_fractions/commit", args=[job_id], + required_permissions=["csv_upload.process"], ) return { "status": "committing", @@ -188,5 +238,10 @@ async def commit_import_job( @router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv(job_id: str): +async def download_scan_errors_csv( + job_id: str, + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + _assert_us_tariff_csv_job_access(db, job_id, current_user) return download_scan_errors_csv_stream("fa", job_id) diff --git a/frontend/src/lib/components/dashboard/goods/fractions/CanadianFractionList.svelte b/frontend/src/lib/components/dashboard/goods/fractions/CanadianFractionList.svelte index 59ec6744..d6053fa8 100644 --- a/frontend/src/lib/components/dashboard/goods/fractions/CanadianFractionList.svelte +++ b/frontend/src/lib/components/dashboard/goods/fractions/CanadianFractionList.svelte @@ -20,8 +20,14 @@ import { toast } from 'svelte-sonner'; import CanadianFractionDialog from './CanadianFractionDialog.svelte'; import { companyStore } from '$lib/stores/company.svelte'; - import { currentUser, userHasPermission } from '$lib/auth'; + import { currentUser } from '$lib/auth'; import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; + import { + canCreateTariffFractionCatalog, + canDeleteTariffFractionCatalog, + canEditTariffFractionCatalog, + canViewTariffFractionCatalog + } from '$lib/permissions/tariff-fraction-permissions'; import * as AlertDialog from '$lib/components/ui/alert-dialog'; let fractions = $state([]); @@ -49,11 +55,13 @@ let showDeleteConfirm = $state(false); let fractionToDelete = $state(null); + const FRAC_CA = 'frac_canadian'; + // Permissions - const canView = $derived(userHasPermission($currentUser, 'frac_canadian.view')); - const canCreate = $derived(userHasPermission($currentUser, 'frac_canadian.create')); - const canEdit = $derived(userHasPermission($currentUser, 'frac_canadian.edit')); - const canDelete = $derived(userHasPermission($currentUser, 'frac_canadian.delete')); + const canView = $derived(canViewTariffFractionCatalog($currentUser, FRAC_CA)); + const canCreate = $derived(canCreateTariffFractionCatalog($currentUser, FRAC_CA)); + const canEdit = $derived(canEditTariffFractionCatalog($currentUser, FRAC_CA)); + const canDelete = $derived(canDeleteTariffFractionCatalog($currentUser, FRAC_CA)); const isError = $derived(!canView || status >= 400 || error); @@ -118,22 +126,34 @@ } function handleCreate() { + if (!canCreate) { + toast.error('No tienes permiso para crear fracciones canadienses'); + return; + } editingFraction = null; dialogOpen = true; } function handleEdit(fraction: CanadianFraction) { + if (!canEdit) { + toast.error('No tienes permiso para editar fracciones canadienses'); + return; + } editingFraction = fraction; dialogOpen = true; } function confirmDelete(fraction: CanadianFraction) { + if (!canDelete) { + toast.error('No tienes permiso para eliminar fracciones canadienses'); + return; + } fractionToDelete = fraction; showDeleteConfirm = true; } async function handleDelete() { - if (!fractionToDelete || !companyStore.activeCompany?.id) return; + if (!canDelete || !fractionToDelete || !companyStore.activeCompany?.id) return; try { deletingFractionId = fractionToDelete.id; diff --git a/frontend/src/lib/components/dashboard/goods/fractions/HistoricalFractionList.svelte b/frontend/src/lib/components/dashboard/goods/fractions/HistoricalFractionList.svelte index ff457337..7841137c 100644 --- a/frontend/src/lib/components/dashboard/goods/fractions/HistoricalFractionList.svelte +++ b/frontend/src/lib/components/dashboard/goods/fractions/HistoricalFractionList.svelte @@ -12,18 +12,26 @@ import { Search, Loader2, Plus, Pencil, Trash2 } from 'lucide-svelte'; import { toast } from 'svelte-sonner'; import HistoricalFractionDialog from './HistoricalFractionDialog.svelte'; - import { currentUser, userHasPermission } from '$lib/auth'; + import { currentUser } from '$lib/auth'; import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; + import { + canCreateTariffFractionCatalog, + canDeleteTariffFractionCatalog, + canEditTariffFractionCatalog, + canViewTariffFractionCatalog + } from '$lib/permissions/tariff-fraction-permissions'; import { companyStore } from '$lib/stores/company.svelte'; let { title = 'Fracciones históricas' }: { title?: string } = $props(); + const FRAC_HIST = 'frac_historical'; + // Permisos - const canView = $derived(userHasPermission($currentUser, 'frac_historical.view')); - const canCreate = $derived(userHasPermission($currentUser, 'frac_historical.create')); - const canEdit = $derived(userHasPermission($currentUser, 'frac_historical.edit')); - const canDelete = $derived(userHasPermission($currentUser, 'frac_historical.delete')); + const canView = $derived(canViewTariffFractionCatalog($currentUser, FRAC_HIST)); + const canCreate = $derived(canCreateTariffFractionCatalog($currentUser, FRAC_HIST)); + const canEdit = $derived(canEditTariffFractionCatalog($currentUser, FRAC_HIST)); + const canDelete = $derived(canDeleteTariffFractionCatalog($currentUser, FRAC_HIST)); let fractions = $state([]); let loading = $state(false); @@ -111,16 +119,28 @@ let scrollContainer = $state(null); } function handleCreate() { + if (!canCreate) { + toast.error('No tienes permiso para crear fracciones históricas'); + return; + } editingFraction = null; dialogOpen = true; } function handleEdit(fraction: HistoricalFraction) { + if (!canEdit) { + toast.error('No tienes permiso para editar fracciones históricas'); + return; + } editingFraction = fraction; dialogOpen = true; } async function handleDelete(fraction: HistoricalFraction) { + if (!canDelete) { + toast.error('No tienes permiso para eliminar fracciones históricas'); + return; + } const companyId = companyStore.activeCompany?.id; if (!companyId) return; diff --git a/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionFormDialog.svelte b/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionFormDialog.svelte index 9e0a6b62..40508a85 100644 --- a/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionFormDialog.svelte +++ b/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionFormDialog.svelte @@ -14,19 +14,30 @@ import { companyStore } from '$lib/stores/company.svelte'; import { toast } from 'svelte-sonner'; import { Loader2 } from 'lucide-svelte'; + import { currentUser } from '$lib/auth'; + import { + canCreateTariffFractionCatalog, + canEditTariffFractionCatalog, + resolveTariffFractionBasePrefix + } from '$lib/permissions/tariff-fraction-permissions'; let { open = $bindable(false), fraction = null, // If null, create mode. If set, edit mode. catalog = 'mex', + catalogBasePrefix = null, onSuccess }: { open: boolean; fraction?: TariffFraction | null; catalog?: string; + /** Prefijo RBAC explícito (p. ej. frac_sitar_7); si null se deduce del catalog */ + catalogBasePrefix?: string | null; onSuccess: () => void; } = $props(); + const basePrefix = $derived(resolveTariffFractionBasePrefix(catalog, catalogBasePrefix)); + let isLoading = $state(false); // Form fields @@ -77,6 +88,18 @@ const companyId = companyStore.activeCompany?.id; if (!companyId) return; + if (fraction) { + if (!canEditTariffFractionCatalog($currentUser, basePrefix)) { + toast.error('No tienes permiso para editar fracciones en este catálogo'); + return; + } + } else { + if (!canCreateTariffFractionCatalog($currentUser, basePrefix)) { + toast.error('No tienes permiso para crear fracciones en este catálogo'); + return; + } + } + isLoading = true; try { if (fraction) { diff --git a/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionList.svelte b/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionList.svelte index cfb49d74..2ece79fa 100644 --- a/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionList.svelte +++ b/frontend/src/lib/components/dashboard/goods/fractions/TariffFractionList.svelte @@ -22,8 +22,15 @@ import TariffFractionFormDialog from './TariffFractionFormDialog.svelte'; import SitarFractionTabs from './SitarFractionTabs.svelte'; import { toast } from 'svelte-sonner'; - import { currentUser, userHasPermission } from '$lib/auth'; + import { currentUser } from '$lib/auth'; import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; + import { + canCreateTariffFractionCatalog, + canDeleteTariffFractionCatalog, + canEditTariffFractionCatalog, + canViewTariffFractionCatalog, + resolveTariffFractionBasePrefix + } from '$lib/permissions/tariff-fraction-permissions'; import { getTariffFractionDisplayFraction, getTariffFractionDisplayKey @@ -53,20 +60,13 @@ let error = $state(null); let status = $state(200); - // Permisos - const permMap: Record = { - 'mex': 'frac_sitar', - 'usa': 'frac_sitar_us', - 'american': 'frac_american', - 'canadian': 'frac_canadian', - 'historical': 'frac_historical' - }; - const basePerm = $derived(customBasePerm || permMap[catalog] || 'frac_sitar'); + // Permisos (prefijo RBAC por catálogo o basePerm override, p. ej. frac_sitar_7) + const basePrefix = $derived(resolveTariffFractionBasePrefix(catalog, customBasePerm)); - const canView = $derived(userHasPermission($currentUser, `${basePerm}.view`)); - const canCreate = $derived(userHasPermission($currentUser, `${basePerm}.create`)); - const canEdit = $derived(userHasPermission($currentUser, `${basePerm}.edit`)); - const canDelete = $derived(userHasPermission($currentUser, `${basePerm}.delete`)); + const canView = $derived(canViewTariffFractionCatalog($currentUser, basePrefix)); + const canCreate = $derived(canCreateTariffFractionCatalog($currentUser, basePrefix)); + const canEdit = $derived(canEditTariffFractionCatalog($currentUser, basePrefix)); + const canDelete = $derived(canDeleteTariffFractionCatalog($currentUser, basePrefix)); const isError = $derived(!canView || status >= 400 || error); @@ -173,12 +173,20 @@ } function openCreateDialog() { + if (!canCreate) { + toast.error('No tienes permiso para crear fracciones en este catálogo'); + return; + } selectedFraction = null; isManageMode = false; // Create mode isFormDialogOpen = true; } function openEditDialog(fraction: TariffFraction) { + if (!canEdit) { + toast.error('No tienes permiso para editar fracciones en este catálogo'); + return; + } selectedFraction = fraction; isManageMode = true; // Edit mode isFormDialogOpen = true; @@ -190,12 +198,16 @@ } function confirmDelete(fraction: TariffFraction) { + if (!canDelete) { + toast.error('No tienes permiso para eliminar fracciones en este catálogo'); + return; + } fractionToDelete = fraction; showDeleteConfirm = true; } async function handleDelete() { - if (!fractionToDelete || !companyStore.activeCompany?.id) return; + if (!canDelete || !fractionToDelete || !companyStore.activeCompany?.id) return; try { await deleteTariffFraction(fractionToDelete.id, companyStore.activeCompany.id, catalog); @@ -404,6 +416,7 @@ bind:open={isFormDialogOpen} fraction={selectedFraction} {catalog} + catalogBasePrefix={basePrefix} onSuccess={() => { loadFractions(true); isFormDialogOpen = false; diff --git a/frontend/src/lib/components/sidebar/modules.ts b/frontend/src/lib/components/sidebar/modules.ts index ed4f7791..c050ff45 100644 --- a/frontend/src/lib/components/sidebar/modules.ts +++ b/frontend/src/lib/components/sidebar/modules.ts @@ -313,26 +313,32 @@ export function getSidebarData(): SidebarData { { title: m["sidebar.fractions.sitar"](), url: "/dashboard/general_catalogs/tariff-fractions/sitar", + permission: 'frac_sitar.view', }, { title: m["sidebar.fractions.sitar_seventh_amendment"](), url: "/dashboard/general_catalogs/tariff-fractions/seventh-amendment", + permission: 'frac_sitar_7.view', }, { title: m["sidebar.fractions.sitar_us"](), url: "/dashboard/general_catalogs/tariff-fractions/us", + permission: 'frac_sitar_us.view', }, { title: m["sidebar.fractions.canadian"](), url: "/dashboard/general_catalogs/tariff-fractions/canadian", + permission: 'frac_canadian.view', }, { title: m["sidebar.fractions.historical"](), url: "/dashboard/general_catalogs/tariff-fractions/historical", + permission: 'frac_historical.view', }, { title: m["sidebar.fractions.sectors"](), url: "/dashboard/general_catalogs/sectors", + permission: 'frac_sectors.view', }, ], }, diff --git a/frontend/src/lib/permissions/clients-providers-permissions.ts b/frontend/src/lib/permissions/clients-providers-permissions.ts index 52eab8dd..73ed4f2c 100644 --- a/frontend/src/lib/permissions/clients-providers-permissions.ts +++ b/frontend/src/lib/permissions/clients-providers-permissions.ts @@ -1,23 +1,18 @@ 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 canViewClientsAndProviders(user: User | null): boolean { return userHasPermission(user, 'partners_mgmt.view'); } export function canCreateClientsAndProviders(user: User | null): boolean { - return hasAssignedCode(user, 'partners_mgmt.create'); + return userHasPermission(user, 'partners_mgmt.create'); } export function canEditClientsAndProviders(user: User | null): boolean { - return hasAssignedCode(user, 'partners_mgmt.edit'); + return userHasPermission(user, 'partners_mgmt.edit'); } export function canDeleteClientsAndProviders(user: User | null): boolean { - return hasAssignedCode(user, 'partners_mgmt.delete'); + return userHasPermission(user, 'partners_mgmt.delete'); } diff --git a/frontend/src/lib/permissions/goods-classes-permissions.ts b/frontend/src/lib/permissions/goods-classes-permissions.ts index 85c0e232..cc204b03 100644 --- a/frontend/src/lib/permissions/goods-classes-permissions.ts +++ b/frontend/src/lib/permissions/goods-classes-permissions.ts @@ -1,23 +1,18 @@ 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 canViewGoodsClasses(user: User | null): boolean { return userHasPermission(user, 'goods_classes.view'); } export function canCreateGoodsClasses(user: User | null): boolean { - return hasAssignedCode(user, 'goods_classes.create'); + return userHasPermission(user, 'goods_classes.create'); } export function canEditGoodsClasses(user: User | null): boolean { - return hasAssignedCode(user, 'goods_classes.edit'); + return userHasPermission(user, 'goods_classes.edit'); } export function canDeleteGoodsClasses(user: User | null): boolean { - return hasAssignedCode(user, 'goods_classes.delete'); + return userHasPermission(user, 'goods_classes.delete'); } diff --git a/frontend/src/lib/permissions/goods-parts-permissions.ts b/frontend/src/lib/permissions/goods-parts-permissions.ts index c6515018..39ed9b93 100644 --- a/frontend/src/lib/permissions/goods-parts-permissions.ts +++ b/frontend/src/lib/permissions/goods-parts-permissions.ts @@ -1,23 +1,18 @@ 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 canViewGoodsParts(user: User | null): boolean { return userHasPermission(user, 'goods_parts.view'); } export function canCreateGoodsParts(user: User | null): boolean { - return hasAssignedCode(user, 'goods_parts.create'); + return userHasPermission(user, 'goods_parts.create'); } export function canEditGoodsParts(user: User | null): boolean { - return hasAssignedCode(user, 'goods_parts.edit'); + return userHasPermission(user, 'goods_parts.edit'); } export function canDeleteGoodsParts(user: User | null): boolean { - return hasAssignedCode(user, 'goods_parts.delete'); + return userHasPermission(user, 'goods_parts.delete'); } diff --git a/frontend/src/lib/permissions/tariff-fraction-permissions.ts b/frontend/src/lib/permissions/tariff-fraction-permissions.ts new file mode 100644 index 00000000..0a403faa --- /dev/null +++ b/frontend/src/lib/permissions/tariff-fraction-permissions.ts @@ -0,0 +1,39 @@ +import type { User } from '$lib/auth'; +import { userHasPermission } from '$lib/auth'; + +/** Prefijos RBAC por valor de `catalog` en TariffFractionList (sin sufijos .view/.create). */ +export const CATALOG_TO_BASE_PREFIX: Record = { + mex: 'frac_sitar', + usa: 'frac_sitar_us', + american: 'frac_american', + canadian: 'frac_canadian', + historical: 'frac_historical' +}; + +/** + * Resuelve el prefijo RBAC (p. ej. `frac_sitar`, `frac_sitar_7`) a partir de catálogo + * y override opcional (Séptima enmienda, etc.). + */ +export function resolveTariffFractionBasePrefix( + catalog: string, + override?: string | null +): string { + if (override) return override; + return CATALOG_TO_BASE_PREFIX[catalog] ?? 'frac_sitar'; +} + +export function canViewTariffFractionCatalog(user: User | null, basePrefix: string): boolean { + return userHasPermission(user, `${basePrefix}.view`); +} + +export function canCreateTariffFractionCatalog(user: User | null, basePrefix: string): boolean { + return userHasPermission(user, `${basePrefix}.create`); +} + +export function canEditTariffFractionCatalog(user: User | null, basePrefix: string): boolean { + return userHasPermission(user, `${basePrefix}.edit`); +} + +export function canDeleteTariffFractionCatalog(user: User | null, basePrefix: string): boolean { + return userHasPermission(user, `${basePrefix}.delete`); +} diff --git a/frontend/src/routes/dashboard/users/+page.svelte b/frontend/src/routes/dashboard/users/+page.svelte index 06f6fa22..d6f38b14 100644 --- a/frontend/src/routes/dashboard/users/+page.svelte +++ b/frontend/src/routes/dashboard/users/+page.svelte @@ -82,6 +82,13 @@ userHasPermission($currentUser, 'roles.view') ); + /** Prefijos de código ocultos en UI (pickers, heredados del rol, etc.; siguen en BD). */ + const HIDDEN_ASSIGNMENT_PERMISSION_PREFIXES = ['frac_american.'] as const; + + function isHiddenAssignmentPermission(p: { code: string }): boolean { + return HIDDEN_ASSIGNMENT_PERMISSION_PREFIXES.some((prefix) => p.code.startsWith(prefix)); + } + // ============================================================================ // HELPERS PARA FORMATEO DE PERMISOS // ============================================================================ @@ -178,7 +185,6 @@ frac_sitar: 'Fracciones SITAR', frac_sitar_7: 'FRacciones Sitar - 7ma', frac_sitar_us: 'Fracciones sitar US', - frac_american: 'Fracciones Americana', frac_canadian: 'Fracciones Canadienses', frac_historical: 'Fracciones historicas', frac_sectors: 'Sectores', @@ -390,7 +396,6 @@ 'frac_sitar', 'frac_sitar_7', 'frac_sitar_us', - 'frac_american', 'frac_canadian', 'frac_historical', 'frac_sectors' @@ -477,25 +482,43 @@ } const filteredRolePermissions = $derived( - rolePermissionsState.filter((rp) => matchesPermissionQuery(rp.permission, rolePermissionsSearchQuery)) + rolePermissionsState + .filter((rp) => rp.permission && !isHiddenAssignmentPermission(rp.permission)) + .filter((rp) => matchesPermissionQuery(rp.permission, rolePermissionsSearchQuery)) + ); + + const rolePermissionsVisibleCount = $derived( + rolePermissionsState.filter( + (rp) => rp.permission && !isHiddenAssignmentPermission(rp.permission) + ).length ); const filteredGrantedPermissions = $derived( - effectivePermissions?.granted_permissions.filter((perm) => - matchesPermissionQuery(perm, userEffectiveSearchQuery) - ) || [] + effectivePermissions?.granted_permissions + .filter((perm) => !isHiddenAssignmentPermission(perm)) + .filter((perm) => matchesPermissionQuery(perm, userEffectiveSearchQuery)) || [] ); const filteredRevokedPermissions = $derived( - effectivePermissions?.revoked_permissions.filter((perm) => - matchesPermissionQuery(perm, userEffectiveSearchQuery) - ) || [] + effectivePermissions?.revoked_permissions + .filter((perm) => !isHiddenAssignmentPermission(perm)) + .filter((perm) => matchesPermissionQuery(perm, userEffectiveSearchQuery)) || [] ); const filteredInheritedPermissions = $derived( - effectivePermissions?.role_permissions.filter((perm) => - matchesPermissionQuery(perm, userEffectiveSearchQuery) - ) || [] + effectivePermissions?.role_permissions + .filter((perm) => !isHiddenAssignmentPermission(perm)) + .filter((perm) => matchesPermissionQuery(perm, userEffectiveSearchQuery)) || [] + ); + + const effectiveGrantedVisibleCount = $derived( + effectivePermissions?.granted_permissions.filter((p) => !isHiddenAssignmentPermission(p)).length ?? 0 + ); + const effectiveRevokedVisibleCount = $derived( + effectivePermissions?.revoked_permissions.filter((p) => !isHiddenAssignmentPermission(p)).length ?? 0 + ); + const effectiveInheritedVisibleCount = $derived( + effectivePermissions?.role_permissions.filter((p) => !isHiddenAssignmentPermission(p)).length ?? 0 ); const hasUserEffectiveSearchMatches = $derived( @@ -1054,7 +1077,7 @@ async function loadAllPermissionsForRoles() { try { const response = await permissionsAPI.list({ page_size: 1000 }); - allPermissionsState = response?.items || []; + allPermissionsState = (response?.items || []).filter((p) => !isHiddenAssignmentPermission(p)); if (selectedRoleForPermissions) { updateAvailablePermissionsForRole(); } @@ -1662,10 +1685,10 @@

{#if rolePermissionsSearchQuery.trim()} - Mostrando {filteredRolePermissions.length} de {rolePermissionsState.length}{' '} + Mostrando {filteredRolePermissions.length} de {rolePermissionsVisibleCount}{' '} permisos asignados {:else} - {rolePermissionsState.length} permisos asignados + {rolePermissionsVisibleCount} permisos asignados {/if}

@@ -1704,7 +1727,7 @@ Cargando permisos... - {:else if rolePermissionsState.length === 0} + {:else if rolePermissionsVisibleCount === 0} Este rol no tiene permisos asignados @@ -2778,19 +2801,19 @@
- {effectivePermissions.role_permissions.length} + {effectiveInheritedVisibleCount}
Del Rol
- {effectivePermissions.granted_permissions.length} + {effectiveGrantedVisibleCount}
Concedidos Extra
- {effectivePermissions.revoked_permissions.length} + {effectiveRevokedVisibleCount}
Revocados
@@ -2807,7 +2830,7 @@
- {#if effectivePermissions.granted_permissions.length > 0} + {#if effectiveGrantedVisibleCount > 0}
- {filteredGrantedPermissions.length}/{effectivePermissions.granted_permissions.length} + {filteredGrantedPermissions.length}/{effectiveGrantedVisibleCount}
@@ -2846,7 +2869,7 @@ {/if} - {#if effectivePermissions.revoked_permissions.length > 0} + {#if effectiveRevokedVisibleCount > 0}
- {filteredRevokedPermissions.length}/{effectivePermissions.revoked_permissions.length} + {filteredRevokedPermissions.length}/{effectiveRevokedVisibleCount}
@@ -2887,12 +2910,12 @@ {/if} - {#if effectivePermissions.role_permissions.length > 0} + {#if effectiveInheritedVisibleCount > 0}
- {filteredInheritedPermissions.length}/{effectivePermissions.role_permissions.length} + {filteredInheritedPermissions.length}/{effectiveInheritedVisibleCount}