feat: add commercial classifications management and integrate SAT code products in product form
This commit is contained in:
parent
7bd247f0c5
commit
0071b7f4dc
@ -28,6 +28,11 @@ const menuItems = ref<MenuItem[]>([
|
|||||||
{ label: 'Documentos del Modelo', icon: 'pi pi-file', to: '/catalog/model-documents' }
|
{ label: 'Documentos del Modelo', icon: 'pi pi-file', to: '/catalog/model-documents' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Productos',
|
||||||
|
icon: 'pi pi-shopping-cart',
|
||||||
|
to: '/products'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Requisiciones',
|
label: 'Requisiciones',
|
||||||
icon: 'pi pi-file-edit',
|
icon: 'pi pi-file-edit',
|
||||||
@ -61,11 +66,6 @@ const menuItems = ref<MenuItem[]>([
|
|||||||
{ label: 'Departamentos', icon: 'pi pi-briefcase', to: '/rh/departments' }
|
{ label: 'Departamentos', icon: 'pi pi-briefcase', to: '/rh/departments' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Productos',
|
|
||||||
icon: 'pi pi-shopping-cart',
|
|
||||||
to: '/products'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Puntos de venta',
|
label: 'Puntos de venta',
|
||||||
icon: 'pi pi-cog',
|
icon: 'pi pi-cog',
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<p>
|
|
||||||
Clasificaciones Comerciales
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
@ -0,0 +1,660 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { useConfirm } from 'primevue/useconfirm';
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import Button from 'primevue/button';
|
||||||
|
import Card from 'primevue/card';
|
||||||
|
import Column from 'primevue/column';
|
||||||
|
import ConfirmDialog from 'primevue/confirmdialog';
|
||||||
|
import DataTable from 'primevue/datatable';
|
||||||
|
import Dialog from 'primevue/dialog';
|
||||||
|
import Dropdown from 'primevue/dropdown';
|
||||||
|
import InputText from 'primevue/inputtext';
|
||||||
|
import Textarea from 'primevue/textarea';
|
||||||
|
import InputSwitch from 'primevue/inputswitch';
|
||||||
|
import ProgressSpinner from 'primevue/progressspinner';
|
||||||
|
import Toast from 'primevue/toast';
|
||||||
|
import { useComercialClassificationStore } from '../../stores/comercialClassificationStore';
|
||||||
|
import type { ComercialClassification } from '../../types/comercialClassification';
|
||||||
|
|
||||||
|
const confirm = useConfirm();
|
||||||
|
const toast = useToast();
|
||||||
|
const classificationStore = useComercialClassificationStore();
|
||||||
|
|
||||||
|
interface Category {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
parent_id: number | null;
|
||||||
|
is_active: number;
|
||||||
|
created_at: string;
|
||||||
|
subcategories: Category[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedCategory = ref<Category | null>(null);
|
||||||
|
const showCreateModal = ref(false);
|
||||||
|
const isSubmitting = ref(false);
|
||||||
|
const isEditMode = ref(false);
|
||||||
|
const editingId = ref<number | null>(null);
|
||||||
|
|
||||||
|
// Computed properties from store
|
||||||
|
const loading = computed(() => classificationStore.loading);
|
||||||
|
const categories = computed(() => transformClassifications(classificationStore.classifications));
|
||||||
|
|
||||||
|
// Form data
|
||||||
|
const formData = ref({
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
parent_id: null as number | null,
|
||||||
|
is_active: 1 as number
|
||||||
|
});
|
||||||
|
|
||||||
|
// Computed para el switch (convierte number a boolean y viceversa)
|
||||||
|
const isActiveSwitch = computed({
|
||||||
|
get: () => formData.value.is_active === 1,
|
||||||
|
set: (value: boolean) => {
|
||||||
|
formData.value.is_active = value ? 1 : 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Transform API data to component structure
|
||||||
|
const transformClassifications = (classifications: ComercialClassification[]): Category[] => {
|
||||||
|
return classifications.map(cls => ({
|
||||||
|
id: cls.id,
|
||||||
|
code: cls.code,
|
||||||
|
name: cls.name,
|
||||||
|
description: cls.description || '',
|
||||||
|
parent_id: cls.parent_id,
|
||||||
|
is_active: cls.is_active,
|
||||||
|
created_at: cls.created_at,
|
||||||
|
subcategories: cls.children ? cls.children.map(child => ({
|
||||||
|
id: child.id,
|
||||||
|
code: child.code,
|
||||||
|
name: child.name,
|
||||||
|
description: child.description || '',
|
||||||
|
parent_id: child.parent_id,
|
||||||
|
is_active: child.is_active,
|
||||||
|
created_at: child.created_at,
|
||||||
|
subcategories: []
|
||||||
|
})) : []
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadClassifications = async () => {
|
||||||
|
try {
|
||||||
|
await classificationStore.fetchClassifications();
|
||||||
|
|
||||||
|
// Select first category by default
|
||||||
|
if (categories.value.length > 0 && categories.value[0]) {
|
||||||
|
selectedCategory.value = categories.value[0];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading classifications:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'No se pudieron cargar las clasificaciones comerciales.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectCategory = (category: Category) => {
|
||||||
|
selectedCategory.value = category;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addNewCategory = () => {
|
||||||
|
// Reset form
|
||||||
|
isEditMode.value = false;
|
||||||
|
editingId.value = null;
|
||||||
|
formData.value = {
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
parent_id: null,
|
||||||
|
is_active: 1
|
||||||
|
};
|
||||||
|
showCreateModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
showCreateModal.value = false;
|
||||||
|
isEditMode.value = false;
|
||||||
|
editingId.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createClassification = async () => {
|
||||||
|
try {
|
||||||
|
isSubmitting.value = true;
|
||||||
|
|
||||||
|
if (isEditMode.value && editingId.value) {
|
||||||
|
// Update existing classification
|
||||||
|
await classificationStore.updateClassification(editingId.value, formData.value);
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Clasificación Actualizada',
|
||||||
|
detail: `La clasificación "${formData.value.name}" ha sido actualizada exitosamente.`,
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
|
||||||
|
// Actualizar la categoría seleccionada si fue la que se editó
|
||||||
|
if (selectedCategory.value?.id === editingId.value) {
|
||||||
|
const updatedCategory = categories.value.find(c => c.id === editingId.value);
|
||||||
|
if (updatedCategory) {
|
||||||
|
selectedCategory.value = updatedCategory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create new classification
|
||||||
|
await classificationStore.createClassification(formData.value);
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Clasificación Creada',
|
||||||
|
detail: `La clasificación "${formData.value.name}" ha sido creada exitosamente.`,
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
|
||||||
|
// Si se creó una subclasificación, actualizar la vista de la categoría padre
|
||||||
|
if (formData.value.parent_id && selectedCategory.value?.id === formData.value.parent_id) {
|
||||||
|
const parentCategory = categories.value.find(c => c.id === formData.value.parent_id);
|
||||||
|
if (parentCategory) {
|
||||||
|
selectedCategory.value = parentCategory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showCreateModal.value = false;
|
||||||
|
isEditMode.value = false;
|
||||||
|
editingId.value = null;
|
||||||
|
// Reset form
|
||||||
|
formData.value = {
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
parent_id: null,
|
||||||
|
is_active: 1
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving classification:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: isEditMode.value
|
||||||
|
? 'No se pudo actualizar la clasificación. Por favor, intenta nuevamente.'
|
||||||
|
: 'No se pudo crear la clasificación. Por favor, intenta nuevamente.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
isSubmitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addSubcategory = () => {
|
||||||
|
if (selectedCategory.value) {
|
||||||
|
isEditMode.value = false;
|
||||||
|
editingId.value = null;
|
||||||
|
formData.value = {
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
parent_id: selectedCategory.value.id,
|
||||||
|
is_active: 1
|
||||||
|
};
|
||||||
|
showCreateModal.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const editCategory = () => {
|
||||||
|
if (!selectedCategory.value) return;
|
||||||
|
|
||||||
|
isEditMode.value = true;
|
||||||
|
editingId.value = selectedCategory.value.id;
|
||||||
|
formData.value = {
|
||||||
|
code: selectedCategory.value.code,
|
||||||
|
name: selectedCategory.value.name,
|
||||||
|
description: selectedCategory.value.description,
|
||||||
|
parent_id: selectedCategory.value.parent_id,
|
||||||
|
is_active: selectedCategory.value.is_active
|
||||||
|
};
|
||||||
|
showCreateModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteCategory = () => {
|
||||||
|
if (!selectedCategory.value) return;
|
||||||
|
|
||||||
|
confirm.require({
|
||||||
|
message: `¿Estás seguro de eliminar la clasificación comercial "${selectedCategory.value.name}"?`,
|
||||||
|
header: 'Confirmar Eliminación',
|
||||||
|
icon: 'pi pi-exclamation-triangle',
|
||||||
|
rejectLabel: 'Cancelar',
|
||||||
|
acceptLabel: 'Eliminar',
|
||||||
|
rejectClass: 'p-button-secondary p-button-outlined',
|
||||||
|
acceptClass: 'p-button-danger',
|
||||||
|
accept: async () => {
|
||||||
|
try {
|
||||||
|
const categoryName = selectedCategory.value!.name;
|
||||||
|
const categoryId = selectedCategory.value!.id;
|
||||||
|
await classificationStore.deleteClassification(categoryId);
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Clasificación Eliminada',
|
||||||
|
detail: `La clasificación "${categoryName}" ha sido eliminada exitosamente.`,
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
|
||||||
|
// Seleccionar otra categoría o limpiar la selección
|
||||||
|
if (categories.value.length > 0) {
|
||||||
|
selectedCategory.value = categories.value[0] || null;
|
||||||
|
} else {
|
||||||
|
selectedCategory.value = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting classification:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error al Eliminar',
|
||||||
|
detail: 'No se pudo eliminar la clasificación. Puede estar en uso.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const editSubcategory = (subcategory: Category) => {
|
||||||
|
isEditMode.value = true;
|
||||||
|
editingId.value = subcategory.id;
|
||||||
|
formData.value = {
|
||||||
|
code: subcategory.code,
|
||||||
|
name: subcategory.name,
|
||||||
|
description: subcategory.description,
|
||||||
|
parent_id: subcategory.parent_id,
|
||||||
|
is_active: subcategory.is_active
|
||||||
|
};
|
||||||
|
showCreateModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSubcategory = (subcategory: Category) => {
|
||||||
|
confirm.require({
|
||||||
|
message: `¿Estás seguro de eliminar la subclasificación "${subcategory.name}"?`,
|
||||||
|
header: 'Confirmar Eliminación',
|
||||||
|
icon: 'pi pi-exclamation-triangle',
|
||||||
|
rejectLabel: 'Cancelar',
|
||||||
|
acceptLabel: 'Eliminar',
|
||||||
|
rejectClass: 'p-button-secondary p-button-outlined',
|
||||||
|
acceptClass: 'p-button-danger',
|
||||||
|
accept: async () => {
|
||||||
|
try {
|
||||||
|
await classificationStore.deleteClassification(subcategory.id);
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Subclasificación Eliminada',
|
||||||
|
detail: `La subclasificación "${subcategory.name}" ha sido eliminada exitosamente.`,
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
|
||||||
|
// Actualizar la vista de la categoría padre para reflejar los cambios
|
||||||
|
if (selectedCategory.value) {
|
||||||
|
const updatedParent = categories.value.find(c => c.id === selectedCategory.value!.id);
|
||||||
|
if (updatedParent) {
|
||||||
|
selectedCategory.value = updatedParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting subcategory:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error al Eliminar',
|
||||||
|
detail: 'No se pudo eliminar la subclasificación.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadClassifications();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- Toast Notifications -->
|
||||||
|
<Toast position="bottom-right" />
|
||||||
|
|
||||||
|
<!-- Confirm Dialog -->
|
||||||
|
<ConfirmDialog></ConfirmDialog>
|
||||||
|
|
||||||
|
<!-- Page Heading -->
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||||
|
<h1 class="text-2xl font-bold leading-tight tracking-tight text-surface-900 dark:text-white">
|
||||||
|
Clasificaciones Comerciales
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Two-Panel Layout -->
|
||||||
|
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||||
|
<!-- Left Panel (Category List) -->
|
||||||
|
<Card class="lg:col-span-1">
|
||||||
|
<template #content>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<!-- Add Category Button -->
|
||||||
|
<Button
|
||||||
|
label="Nueva Clasificación"
|
||||||
|
icon="pi pi-plus"
|
||||||
|
class="w-full"
|
||||||
|
@click="addNewCategory"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Categories List -->
|
||||||
|
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||||
|
<ProgressSpinner style="width: 50px; height: 50px" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="categories.length === 0" class="flex flex-col items-center justify-center py-8 text-center">
|
||||||
|
<i class="pi pi-inbox text-4xl text-surface-300 dark:text-surface-600 mb-4"></i>
|
||||||
|
<p class="text-sm text-surface-500 dark:text-surface-400">
|
||||||
|
No hay clasificaciones comerciales creadas
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex flex-col gap-1">
|
||||||
|
<div
|
||||||
|
v-for="category in categories"
|
||||||
|
:key="category.id"
|
||||||
|
:class="[
|
||||||
|
'flex cursor-pointer items-center justify-between gap-4 rounded-lg px-4 py-3 transition-colors',
|
||||||
|
selectedCategory?.id === category.id
|
||||||
|
? 'bg-primary/10 text-primary'
|
||||||
|
: 'hover:bg-surface-100 dark:hover:bg-surface-800'
|
||||||
|
]"
|
||||||
|
@click="selectCategory(category)"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'flex size-10 shrink-0 items-center justify-center rounded-lg',
|
||||||
|
selectedCategory?.id === category.id
|
||||||
|
? 'bg-primary/20 text-primary'
|
||||||
|
: 'bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-400'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<i class="pi pi-shopping-bag"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p
|
||||||
|
:class="[
|
||||||
|
'truncate text-base',
|
||||||
|
selectedCategory?.id === category.id
|
||||||
|
? 'font-semibold text-primary'
|
||||||
|
: 'font-normal text-surface-800 dark:text-surface-300'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ category.name }}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-surface-500 dark:text-surface-400 truncate">
|
||||||
|
{{ category.code }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :class="selectedCategory?.id === category.id ? 'text-primary' : 'text-surface-400'">
|
||||||
|
<i class="pi pi-chevron-right"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Right Panel (Details View) -->
|
||||||
|
<Card v-if="!selectedCategory" class="lg:col-span-2">
|
||||||
|
<template #content>
|
||||||
|
<div class="flex flex-col items-center justify-center py-16">
|
||||||
|
<i class="pi pi-shopping-bag text-6xl text-surface-300 dark:text-surface-600 mb-4"></i>
|
||||||
|
<h3 class="text-xl font-semibold text-surface-700 dark:text-surface-300 mb-2">
|
||||||
|
Selecciona una clasificación
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-surface-500 dark:text-surface-400">
|
||||||
|
Elige una clasificación comercial de la lista para ver sus detalles
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card v-else class="lg:col-span-2">
|
||||||
|
<template #content>
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-xs font-medium uppercase tracking-wider text-surface-500 dark:text-surface-400">
|
||||||
|
Detalles de la Clasificación Comercial
|
||||||
|
</p>
|
||||||
|
<h3 class="text-xl font-bold text-surface-900 dark:text-white mt-1">
|
||||||
|
{{ selectedCategory.name }}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 text-sm font-mono text-surface-600 dark:text-surface-400">
|
||||||
|
Código: {{ selectedCategory.code }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-pencil"
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
@click="editCategory"
|
||||||
|
v-tooltip.top="'Editar'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-trash"
|
||||||
|
severity="danger"
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
@click="deleteCategory"
|
||||||
|
v-tooltip.top="'Eliminar'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Subcategory Section -->
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="mb-4 flex items-center justify-between">
|
||||||
|
<h4 class="font-semibold text-surface-800 dark:text-surface-200">
|
||||||
|
Subclasificaciones ({{ selectedCategory.subcategories.length }})
|
||||||
|
</h4>
|
||||||
|
<Button
|
||||||
|
label="Agregar Subclasificación"
|
||||||
|
icon="pi pi-plus"
|
||||||
|
size="small"
|
||||||
|
@click="addSubcategory"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Subcategory Table -->
|
||||||
|
<DataTable
|
||||||
|
:value="selectedCategory.subcategories"
|
||||||
|
stripedRows
|
||||||
|
responsiveLayout="scroll"
|
||||||
|
>
|
||||||
|
<Column field="name" header="Nombre" sortable>
|
||||||
|
<template #body="slotProps">
|
||||||
|
<span class="font-medium text-surface-900 dark:text-white">
|
||||||
|
{{ slotProps.data.name }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="code" header="Código" sortable>
|
||||||
|
<template #body="slotProps">
|
||||||
|
<span class="text-surface-500 dark:text-surface-400 font-mono">
|
||||||
|
{{ slotProps.data.code }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="created_at" header="Fecha de Creación" sortable>
|
||||||
|
<template #body="slotProps">
|
||||||
|
<span class="text-surface-500 dark:text-surface-400">
|
||||||
|
{{ new Date(slotProps.data.created_at).toLocaleDateString('es-MX', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
}) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column header="Acciones" headerStyle="text-align: right" bodyStyle="text-align: right">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<div class="flex items-center justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-pencil"
|
||||||
|
text
|
||||||
|
rounded
|
||||||
|
size="small"
|
||||||
|
@click="editSubcategory(slotProps.data)"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-trash"
|
||||||
|
text
|
||||||
|
rounded
|
||||||
|
size="small"
|
||||||
|
severity="danger"
|
||||||
|
@click="deleteSubcategory(slotProps.data)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<template #empty>
|
||||||
|
<div class="flex flex-col items-center justify-center py-8 text-center">
|
||||||
|
<i class="pi pi-inbox text-4xl text-surface-300 dark:text-surface-600 mb-4"></i>
|
||||||
|
<h3 class="text-lg font-semibold text-surface-900 dark:text-surface-0">
|
||||||
|
No hay subclasificaciones
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-surface-500 dark:text-surface-400 mt-2">
|
||||||
|
Agrega subclasificaciones para organizar mejor esta categoría
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Create/Edit Classification Modal -->
|
||||||
|
<Dialog
|
||||||
|
v-model:visible="showCreateModal"
|
||||||
|
modal
|
||||||
|
:header="isEditMode ? 'Editar Clasificación Comercial' : 'Nueva Clasificación Comercial'"
|
||||||
|
:style="{ width: '500px' }"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-4 py-4">
|
||||||
|
<!-- Code -->
|
||||||
|
<div>
|
||||||
|
<label for="code" class="block text-sm font-medium mb-2">
|
||||||
|
Código <span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<InputText
|
||||||
|
id="code"
|
||||||
|
v-model="formData.code"
|
||||||
|
class="w-full"
|
||||||
|
placeholder="Ej: COM-001"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Name -->
|
||||||
|
<div>
|
||||||
|
<label for="name" class="block text-sm font-medium mb-2">
|
||||||
|
Nombre <span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<InputText
|
||||||
|
id="name"
|
||||||
|
v-model="formData.name"
|
||||||
|
class="w-full"
|
||||||
|
placeholder="Ej: PRODUCTOS DE CONSUMO"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Description -->
|
||||||
|
<div>
|
||||||
|
<label for="description" class="block text-sm font-medium mb-2">
|
||||||
|
Descripción
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
id="description"
|
||||||
|
v-model="formData.description"
|
||||||
|
rows="3"
|
||||||
|
class="w-full"
|
||||||
|
placeholder="Descripción de la clasificación comercial"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Parent Category -->
|
||||||
|
<div>
|
||||||
|
<label for="parent" class="block text-sm font-medium mb-2">
|
||||||
|
Clasificación Padre (Opcional)
|
||||||
|
</label>
|
||||||
|
<Dropdown
|
||||||
|
id="parent"
|
||||||
|
v-model="formData.parent_id"
|
||||||
|
:options="categories"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="id"
|
||||||
|
placeholder="Seleccionar clasificación padre..."
|
||||||
|
class="w-full"
|
||||||
|
showClear
|
||||||
|
/>
|
||||||
|
<small class="text-surface-500 dark:text-surface-400">
|
||||||
|
Deja vacío para crear una clasificación raíz
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Active Status -->
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<label for="is_active" class="block text-sm font-medium mb-1">
|
||||||
|
Estado
|
||||||
|
</label>
|
||||||
|
<small class="text-surface-500 dark:text-surface-400">
|
||||||
|
Activa o desactiva esta clasificación comercial
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<InputSwitch
|
||||||
|
id="is_active"
|
||||||
|
v-model="isActiveSwitch"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
label="Cancelar"
|
||||||
|
severity="secondary"
|
||||||
|
outlined
|
||||||
|
@click="closeModal"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:label="isEditMode ? 'Actualizar' : 'Crear'"
|
||||||
|
@click="createClassification"
|
||||||
|
:loading="isSubmitting"
|
||||||
|
:disabled="!formData.code || !formData.name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
18
src/modules/catalog/services/sat-code-products.services.ts
Normal file
18
src/modules/catalog/services/sat-code-products.services.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import api from "../../../services/api";
|
||||||
|
|
||||||
|
export interface SatCodeProduct {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SatCodeProductResponse {
|
||||||
|
data: SatCodeProduct[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const satCodeProductsService = {
|
||||||
|
async getSatCodeProducts(search: string = ''): Promise<SatCodeProductResponse> {
|
||||||
|
const response = await api.get(`/api/sat/products?search=${search}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -5,8 +5,10 @@ import InputText from 'primevue/inputtext';
|
|||||||
import Textarea from 'primevue/textarea';
|
import Textarea from 'primevue/textarea';
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
import FileUpload from 'primevue/fileupload';
|
import FileUpload from 'primevue/fileupload';
|
||||||
|
import Dropdown from 'primevue/dropdown';
|
||||||
import { useComercialClassificationStore } from '../../catalog/stores/comercialClassificationStore';
|
import { useComercialClassificationStore } from '../../catalog/stores/comercialClassificationStore';
|
||||||
import { useUnitOfMeasureStore } from '../../catalog/stores/unitOfMeasureStore';
|
import { useUnitOfMeasureStore } from '../../catalog/stores/unitOfMeasureStore';
|
||||||
|
import { satCodeProductsService, type SatCodeProduct } from '../../catalog/services/sat-code-products.services';
|
||||||
import type { Product, CreateProductData } from '../types/product';
|
import type { Product, CreateProductData } from '../types/product';
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
@ -39,14 +41,22 @@ const formData = ref<CreateProductData>({
|
|||||||
description: '',
|
description: '',
|
||||||
unit_of_measure_id: 0,
|
unit_of_measure_id: 0,
|
||||||
suggested_sale_price: 0,
|
suggested_sale_price: 0,
|
||||||
attributes: {},
|
attributes: null,
|
||||||
is_active: true,
|
is_active: true,
|
||||||
|
is_serial: false,
|
||||||
|
sat_code_product_id: null,
|
||||||
classifications: []
|
classifications: []
|
||||||
});
|
});
|
||||||
|
|
||||||
// Temporary string for price input
|
// Temporary string for price input
|
||||||
const priceInput = ref<string>('0');
|
const priceInput = ref<string>('0');
|
||||||
|
|
||||||
|
// SAT Code Products
|
||||||
|
const satCodeProducts = ref<SatCodeProduct[]>([]);
|
||||||
|
const loadingSatCodeProducts = ref(false);
|
||||||
|
const searchSatQuery = ref('');
|
||||||
|
let searchSatTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
// Attributes management
|
// Attributes management
|
||||||
interface AttributeValue {
|
interface AttributeValue {
|
||||||
id: string;
|
id: string;
|
||||||
@ -131,6 +141,67 @@ const toggleClassification = (classificationId: number) => {
|
|||||||
|
|
||||||
const loadingClassifications = computed(() => clasificationsStore.loading);
|
const loadingClassifications = computed(() => clasificationsStore.loading);
|
||||||
|
|
||||||
|
// Options para el dropdown de SAT
|
||||||
|
const satCodeProductOptions = computed(() =>
|
||||||
|
satCodeProducts.value.map(product => ({
|
||||||
|
label: `${product.code} - ${product.description}`,
|
||||||
|
value: product.id
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const satEmptyMessage = computed(() => {
|
||||||
|
if (props.isEditing && satCodeProducts.value.length === 1) {
|
||||||
|
return 'Escribe para buscar otro código SAT...';
|
||||||
|
}
|
||||||
|
return 'No se encontraron códigos. Escribe para buscar.';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load SAT Code Products
|
||||||
|
const loadSatCodeProducts = async (search: string) => {
|
||||||
|
// Solo buscar si hay texto
|
||||||
|
if (!search || search.trim().length === 0) {
|
||||||
|
// Si estamos editando y hay un código actual, mantenerlo
|
||||||
|
if (props.product?.sat_code_product) {
|
||||||
|
satCodeProducts.value = [props.product.sat_code_product];
|
||||||
|
} else {
|
||||||
|
satCodeProducts.value = [];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
loadingSatCodeProducts.value = true;
|
||||||
|
const response = await satCodeProductsService.getSatCodeProducts(search);
|
||||||
|
satCodeProducts.value = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading SAT code products:', error);
|
||||||
|
// Si hay error y estamos editando, mantener el código actual
|
||||||
|
if (props.product?.sat_code_product) {
|
||||||
|
satCodeProducts.value = [props.product.sat_code_product];
|
||||||
|
} else {
|
||||||
|
satCodeProducts.value = [];
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loadingSatCodeProducts.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Debounced search for SAT codes
|
||||||
|
const handleSatSearchChange = (event: any) => {
|
||||||
|
const query = event.value || '';
|
||||||
|
searchSatQuery.value = query;
|
||||||
|
|
||||||
|
// Limpiar timeout anterior
|
||||||
|
if (searchSatTimeout) {
|
||||||
|
clearTimeout(searchSatTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplicar debounce de 500ms
|
||||||
|
searchSatTimeout = setTimeout(() => {
|
||||||
|
loadSatCodeProducts(query);
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
// Load classifications on mount
|
// Load classifications on mount
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
console.log('🔄 ProductForm mounted, loading classifications and units of measure...');
|
console.log('🔄 ProductForm mounted, loading classifications and units of measure...');
|
||||||
@ -159,9 +230,16 @@ watch(() => props.product, (newProduct) => {
|
|||||||
suggested_sale_price: newProduct.suggested_sale_price,
|
suggested_sale_price: newProduct.suggested_sale_price,
|
||||||
attributes: newProduct.attributes || {},
|
attributes: newProduct.attributes || {},
|
||||||
is_active: newProduct.is_active,
|
is_active: newProduct.is_active,
|
||||||
|
is_serial: newProduct.is_serial || false,
|
||||||
|
sat_code_product_id: newProduct.sat_code_product_id || null,
|
||||||
classifications: newProduct.classifications?.map(c => c.id) || []
|
classifications: newProduct.classifications?.map(c => c.id) || []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Precargar el código SAT actual en el dropdown
|
||||||
|
if (newProduct.sat_code_product) {
|
||||||
|
satCodeProducts.value = [newProduct.sat_code_product];
|
||||||
|
}
|
||||||
|
|
||||||
priceInput.value = newProduct.suggested_sale_price.toString();
|
priceInput.value = newProduct.suggested_sale_price.toString();
|
||||||
|
|
||||||
// Convert attributes object to array format
|
// Convert attributes object to array format
|
||||||
@ -349,6 +427,48 @@ const onUpload = (event: any) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- SAT Code Product -->
|
||||||
|
<div class="sm:col-span-2">
|
||||||
|
<label for="sat-code-product" class="block text-sm font-medium mb-2">
|
||||||
|
Código SAT de Producto
|
||||||
|
</label>
|
||||||
|
<Dropdown
|
||||||
|
id="sat-code-product"
|
||||||
|
v-model="formData.sat_code_product_id"
|
||||||
|
:options="satCodeProductOptions"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
:loading="loadingSatCodeProducts"
|
||||||
|
placeholder="Escribe para buscar un código SAT..."
|
||||||
|
class="w-full"
|
||||||
|
:filter="true"
|
||||||
|
filterPlaceholder="Buscar código SAT"
|
||||||
|
:showClear="true"
|
||||||
|
@filter="handleSatSearchChange"
|
||||||
|
:emptyFilterMessage="satEmptyMessage"
|
||||||
|
/>
|
||||||
|
<small class="text-surface-500 dark:text-surface-400">
|
||||||
|
Código del catálogo SAT para facturación
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Is Serial -->
|
||||||
|
<div class="sm:col-span-2">
|
||||||
|
<label class="flex items-center space-x-3 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
v-model="formData.is_serial"
|
||||||
|
class="w-5 h-5 rounded border-surface-300 dark:border-surface-600 text-primary focus:ring-primary/50"
|
||||||
|
/>
|
||||||
|
<span class="text-sm font-medium">
|
||||||
|
Producto requiere número de serie
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-surface-500 mt-1 ml-8">
|
||||||
|
Marca esta opción si cada unidad del producto debe tener un número de serie único
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<label for="description" class="block text-sm font-medium mb-2">
|
<label for="description" class="block text-sm font-medium mb-2">
|
||||||
@ -369,7 +489,7 @@ const onUpload = (event: any) => {
|
|||||||
<!-- Product Attributes Card -->
|
<!-- Product Attributes Card -->
|
||||||
<Card>
|
<Card>
|
||||||
<template #title>
|
<template #title>
|
||||||
<h2 class="text-lg font-bold">Atributos del Producto</h2>
|
<h2 class="text-lg font-bold">Características del Producto</h2>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
@ -381,7 +501,7 @@ const onUpload = (event: any) => {
|
|||||||
>
|
>
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<label class="block text-sm font-medium">
|
<label class="block text-sm font-medium">
|
||||||
Nombre del Atributo
|
Nombre de la característica
|
||||||
</label>
|
</label>
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-trash"
|
icon="pi pi-trash"
|
||||||
@ -399,7 +519,7 @@ const onUpload = (event: any) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<label class="block text-sm font-medium mb-2">
|
<label class="block text-sm font-medium mb-2">
|
||||||
Valores del Atributo
|
Valores de la característica
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<!-- Attribute values -->
|
<!-- Attribute values -->
|
||||||
@ -434,7 +554,7 @@ const onUpload = (event: any) => {
|
|||||||
|
|
||||||
<!-- Add attribute button -->
|
<!-- Add attribute button -->
|
||||||
<Button
|
<Button
|
||||||
label="Agregar Otro Atributo"
|
label="Agregar Otra Característica"
|
||||||
icon="pi pi-plus"
|
icon="pi pi-plus"
|
||||||
severity="secondary"
|
severity="secondary"
|
||||||
outlined
|
outlined
|
||||||
|
|||||||
@ -282,6 +282,16 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
|
<!-- Is Serial -->
|
||||||
|
<Column field="is_serial" header="N° Serie" sortable style="min-width: 100px">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<Tag
|
||||||
|
:value="slotProps.data.is_serial ? 'Sí' : 'No'"
|
||||||
|
:severity="slotProps.data.is_serial ? 'info' : 'secondary'"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<Column header="Acciones" headerStyle="text-align: right" bodyStyle="text-align: right" style="min-width: 120px">
|
<Column header="Acciones" headerStyle="text-align: right" bodyStyle="text-align: right" style="min-width: 120px">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
|
|||||||
13
src/modules/products/types/product.d.ts
vendored
13
src/modules/products/types/product.d.ts
vendored
@ -2,6 +2,8 @@
|
|||||||
* Product Type Definitions
|
* Product Type Definitions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { SatCodeProduct } from '../../catalog/services/sat-code-products.services';
|
||||||
|
|
||||||
export interface Product {
|
export interface Product {
|
||||||
id: number;
|
id: number;
|
||||||
code: string;
|
code: string;
|
||||||
@ -13,6 +15,9 @@ export interface Product {
|
|||||||
suggested_sale_price: number;
|
suggested_sale_price: number;
|
||||||
attributes?: Record<string, string[]>;
|
attributes?: Record<string, string[]>;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
|
is_serial: boolean;
|
||||||
|
sat_code_product_id?: number | null;
|
||||||
|
sat_code_product?: SatCodeProduct;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
deleted_at?: string | null;
|
deleted_at?: string | null;
|
||||||
@ -28,8 +33,10 @@ export interface CreateProductData {
|
|||||||
description?: string;
|
description?: string;
|
||||||
unit_of_measure_id: number;
|
unit_of_measure_id: number;
|
||||||
suggested_sale_price: number;
|
suggested_sale_price: number;
|
||||||
attributes?: Record<string, string[]>;
|
attributes?: Record<string, string[]> | null;
|
||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
|
is_serial?: boolean;
|
||||||
|
sat_code_product_id?: number | null;
|
||||||
classifications?: number[];
|
classifications?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,8 +48,10 @@ export interface UpdateProductData {
|
|||||||
description?: string;
|
description?: string;
|
||||||
unit_of_measure_id?: number;
|
unit_of_measure_id?: number;
|
||||||
suggested_sale_price?: number;
|
suggested_sale_price?: number;
|
||||||
attributes?: Record<string, string[]>;
|
attributes?: Record<string, string[]> | null;
|
||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
|
is_serial?: boolean;
|
||||||
|
sat_code_product_id?: number | null;
|
||||||
classifications?: number[];
|
classifications?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import WarehouseForm from '../modules/warehouse/components/WarehouseForm.vue';
|
|||||||
import WarehouseDetails from '../modules/warehouse/components/WarehouseDetails.vue';
|
import WarehouseDetails from '../modules/warehouse/components/WarehouseDetails.vue';
|
||||||
import WarehouseClassification from '../modules/warehouse/components/WarehouseClassification.vue';
|
import WarehouseClassification from '../modules/warehouse/components/WarehouseClassification.vue';
|
||||||
import Units from '../modules/catalog/components/units/Units.vue';
|
import Units from '../modules/catalog/components/units/Units.vue';
|
||||||
import ComercialClassification from '../modules/catalog/components/ComercialClassification.vue';
|
|
||||||
import ProductsIndex from '../modules/products/components/ProductsIndex.vue';
|
import ProductsIndex from '../modules/products/components/ProductsIndex.vue';
|
||||||
import ProductForm from '../modules/products/components/ProductForm.vue';
|
import ProductForm from '../modules/products/components/ProductForm.vue';
|
||||||
import StoresIndex from '../modules/stores/components/StoresIndex.vue';
|
import StoresIndex from '../modules/stores/components/StoresIndex.vue';
|
||||||
@ -30,6 +29,7 @@ import WarehouseAddInventory from '../modules/warehouse/components/WarehouseAddI
|
|||||||
import ModelDocuments from '../modules/catalog/components/ModelDocuments.vue';
|
import ModelDocuments from '../modules/catalog/components/ModelDocuments.vue';
|
||||||
import Requisitions from '../modules/requisitions/Requisitions.vue';
|
import Requisitions from '../modules/requisitions/Requisitions.vue';
|
||||||
import CreateRequisition from '../modules/requisitions/CreateRequisition.vue';
|
import CreateRequisition from '../modules/requisitions/CreateRequisition.vue';
|
||||||
|
import ClassificationsComercial from '../modules/catalog/components/comercial-classification/ClassificationsComercial.vue';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
@ -150,7 +150,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: 'classifications-comercial',
|
path: 'classifications-comercial',
|
||||||
name: 'ClassificationsComercial',
|
name: 'ClassificationsComercial',
|
||||||
component: ComercialClassification,
|
component: ClassificationsComercial,
|
||||||
meta: {
|
meta: {
|
||||||
title: 'Clasificaciones Comerciales',
|
title: 'Clasificaciones Comerciales',
|
||||||
requiresAuth: true
|
requiresAuth: true
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user