feat(units): add permission checks for unit management actions in Units component

This commit is contained in:
edgar.mendez 2026-03-21 20:49:01 -06:00
parent 1189b7b02e
commit 47cc7cdb8e
2 changed files with 107 additions and 8 deletions

View File

@ -25,7 +25,18 @@ const menuItems = ref<MenuItem[]>([
label: 'Catálogo', label: 'Catálogo',
icon: 'pi pi-book', icon: 'pi pi-book',
items: [ items: [
{ label: 'Unidades de Medida', icon: 'pi pi-calculator', to: '/catalog/units-of-measure' }, {
label: 'Unidades de Medida',
icon: 'pi pi-calculator',
to: '/catalog/units-of-measure',
permission: [
'units-of-measure.index',
'units-of-measure.show',
'units-of-measure.store',
'units-of-measure.update',
'units-of-measure.destroy',
],
},
{ label: 'Clasificaciones Comerciales', icon: 'pi pi-tags', to: '/catalog/classifications-comercial' }, { label: 'Clasificaciones Comerciales', icon: 'pi pi-tags', to: '/catalog/classifications-comercial' },
{ label: 'Proveedores', icon: 'pi pi-briefcase', to: '/catalog/suppliers' }, { label: 'Proveedores', icon: 'pi pi-briefcase', to: '/catalog/suppliers' },
{ label: 'Documentos del Modelo', icon: 'pi pi-file', to: '/catalog/model-documents' }, { label: 'Documentos del Modelo', icon: 'pi pi-file', to: '/catalog/model-documents' },

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue'; import { ref, computed, watch } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useToast } from 'primevue/usetoast'; import { useToast } from 'primevue/usetoast';
import { useConfirm } from 'primevue/useconfirm'; import { useConfirm } from 'primevue/useconfirm';
@ -17,11 +17,13 @@ import Select from 'primevue/select';
import { useUnitOfMeasureStore } from '../../stores/unitOfMeasureStore'; import { useUnitOfMeasureStore } from '../../stores/unitOfMeasureStore';
import UnitsForm from './UnitsForm.vue'; import UnitsForm from './UnitsForm.vue';
import type { UnitOfMeasure, CreateUnitOfMeasureData } from '../../types/unit-measure.interfaces'; import type { UnitOfMeasure, CreateUnitOfMeasureData } from '../../types/unit-measure.interfaces';
import { useAuth } from '@/modules/auth/composables/useAuth';
const router = useRouter(); const router = useRouter();
const toast = useToast(); const toast = useToast();
const confirm = useConfirm(); const confirm = useConfirm();
const unitStore = useUnitOfMeasureStore(); const unitStore = useUnitOfMeasureStore();
const { hasPermission } = useAuth();
// Breadcrumb // Breadcrumb
const breadcrumbItems = ref([ const breadcrumbItems = ref([
@ -57,6 +59,19 @@ const getStatusConfig = (isActive: boolean) => {
: { label: 'Inactiva', severity: 'secondary' }; : { label: 'Inactiva', severity: 'secondary' };
}; };
const canViewUnits = computed(() =>
hasPermission([
'units-of-measure.index',
'units-of-measure.show',
'units-of-measure.store',
'units-of-measure.update',
'units-of-measure.destroy',
])
);
const canCreateUnit = computed(() => hasPermission('units-of-measure.store'));
const canUpdateUnit = computed(() => hasPermission('units-of-measure.update'));
const canDeleteUnit = computed(() => hasPermission('units-of-measure.destroy'));
// Methods // Methods
const buildFilters = () => ({ const buildFilters = () => ({
paginate: true, paginate: true,
@ -66,6 +81,11 @@ const buildFilters = () => ({
}); });
const loadUnits = async (force = false) => { const loadUnits = async (force = false) => {
if (!canViewUnits.value) {
unitStore.clearUnits();
return;
}
await unitStore.fetchUnits({ await unitStore.fetchUnits({
force, force,
...buildFilters(), ...buildFilters(),
@ -73,12 +93,30 @@ const loadUnits = async (force = false) => {
}; };
const openCreateDialog = () => { const openCreateDialog = () => {
if (!canCreateUnit.value) {
toast.add({
severity: 'warn',
summary: 'Sin permisos',
detail: 'No tienes permisos para crear unidades de medida.',
life: 4000,
});
return;
}
isEditing.value = false; isEditing.value = false;
selectedUnit.value = null; selectedUnit.value = null;
showDialog.value = true; showDialog.value = true;
}; };
const openEditDialog = (unit: UnitOfMeasure) => { const openEditDialog = (unit: UnitOfMeasure) => {
if (!canUpdateUnit.value) {
toast.add({
severity: 'warn',
summary: 'Sin permisos',
detail: 'No tienes permisos para actualizar unidades de medida.',
life: 4000,
});
return;
}
isEditing.value = true; isEditing.value = true;
selectedUnit.value = unit; selectedUnit.value = unit;
showDialog.value = true; showDialog.value = true;
@ -107,8 +145,14 @@ const handleUpdateUnit = async (id: number, data: CreateUnitOfMeasureData) => {
const handleSaveUnit = async (data: CreateUnitOfMeasureData) => { const handleSaveUnit = async (data: CreateUnitOfMeasureData) => {
try { try {
if (isEditing.value && selectedUnit.value) { if (isEditing.value && selectedUnit.value) {
if (!canUpdateUnit.value) {
throw new Error('without-update-permission');
}
await handleUpdateUnit(selectedUnit.value.id, data); await handleUpdateUnit(selectedUnit.value.id, data);
} else { } else {
if (!canCreateUnit.value) {
throw new Error('without-create-permission');
}
await handleCreateUnit(data); await handleCreateUnit(data);
} }
@ -119,6 +163,7 @@ const handleSaveUnit = async (data: CreateUnitOfMeasureData) => {
console.error('Error saving unit:', error); console.error('Error saving unit:', error);
const requestError = error as { const requestError = error as {
message?: string;
response?: { response?: {
status?: number; status?: number;
data?: { data?: {
@ -143,13 +188,28 @@ const handleSaveUnit = async (data: CreateUnitOfMeasureData) => {
toast.add({ toast.add({
severity: 'error', severity: 'error',
summary: 'Error', summary: 'Error',
detail: requestError.response?.data?.message || 'No se pudo guardar la unidad de medida. Por favor, intenta nuevamente.', detail:
requestError.response?.data?.message ||
(requestError.message === 'without-update-permission'
? 'No tienes permisos para actualizar unidades de medida.'
: requestError.message === 'without-create-permission'
? 'No tienes permisos para crear unidades de medida.'
: 'No se pudo guardar la unidad de medida. Por favor, intenta nuevamente.'),
life: 3000, life: 3000,
}); });
} }
}; };
const confirmDelete = (unit: UnitOfMeasure) => { const confirmDelete = (unit: UnitOfMeasure) => {
if (!canDeleteUnit.value) {
toast.add({
severity: 'warn',
summary: 'Sin permisos',
detail: 'No tienes permisos para eliminar unidades de medida.',
life: 4000,
});
return;
}
confirm.require({ confirm.require({
message: `¿Estás seguro de eliminar la unidad de medida "${unit.name}"?`, message: `¿Estás seguro de eliminar la unidad de medida "${unit.name}"?`,
header: 'Confirmar Eliminación', header: 'Confirmar Eliminación',
@ -162,6 +222,15 @@ const confirmDelete = (unit: UnitOfMeasure) => {
}; };
const deleteUnit = async (id: number) => { const deleteUnit = async (id: number) => {
if (!canDeleteUnit.value) {
toast.add({
severity: 'warn',
summary: 'Sin permisos',
detail: 'No tienes permisos para eliminar unidades de medida.',
life: 4000,
});
return;
}
try { try {
await unitStore.deleteUnit(id); await unitStore.deleteUnit(id);
await loadUnits(true); await loadUnits(true);
@ -192,10 +261,17 @@ const clearFilters = async () => {
await loadUnits(true); await loadUnits(true);
}; };
// Lifecycle watch(
onMounted(async () => { () => canViewUnits.value,
await loadUnits(); async (allowed) => {
}); if (allowed) {
await loadUnits();
} else {
unitStore.clearUnits();
}
},
{ immediate: true }
);
</script> </script>
<template> <template>
@ -228,6 +304,7 @@ onMounted(async () => {
Unidades de Medida Unidades de Medida
</h1> </h1>
<Button <Button
v-if="canCreateUnit"
label="Nueva Unidad" label="Nueva Unidad"
icon="pi pi-plus" icon="pi pi-plus"
@click="openCreateDialog" @click="openCreateDialog"
@ -236,7 +313,7 @@ onMounted(async () => {
</div> </div>
<!-- Main Card --> <!-- Main Card -->
<Card> <Card v-if="canViewUnits">
<template #content> <template #content>
<div class="flex flex-col md:flex-row md:items-end gap-3 mb-4"> <div class="flex flex-col md:flex-row md:items-end gap-3 mb-4">
<div class="flex-1"> <div class="flex-1">
@ -341,6 +418,7 @@ onMounted(async () => {
<template #body="slotProps"> <template #body="slotProps">
<div class="flex gap-2"> <div class="flex gap-2">
<Button <Button
v-if="canUpdateUnit"
icon="pi pi-pencil" icon="pi pi-pencil"
text text
rounded rounded
@ -349,6 +427,7 @@ onMounted(async () => {
v-tooltip.top="'Editar'" v-tooltip.top="'Editar'"
/> />
<Button <Button
v-if="canDeleteUnit"
icon="pi pi-trash" icon="pi pi-trash"
text text
rounded rounded
@ -369,6 +448,15 @@ onMounted(async () => {
</template> </template>
</Card> </Card>
<Card v-else>
<template #content>
<div class="text-center py-10 text-surface-500 dark:text-surface-400">
<i class="pi pi-lock text-4xl mb-3"></i>
<p>No tienes permisos para visualizar este módulo.</p>
</div>
</template>
</Card>
<!-- Create/Edit Form Dialog --> <!-- Create/Edit Form Dialog -->
<UnitsForm <UnitsForm
ref="unitsFormRef" ref="unitsFormRef"