feature-comercial-module-ts #13
1
components.d.ts
vendored
1
components.d.ts
vendored
@ -45,5 +45,6 @@ declare module 'vue' {
|
|||||||
}
|
}
|
||||||
export interface GlobalDirectives {
|
export interface GlobalDirectives {
|
||||||
StyleClass: typeof import('primevue/styleclass')['default']
|
StyleClass: typeof import('primevue/styleclass')['default']
|
||||||
|
Tooltip: typeof import('primevue/tooltip')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,13 @@ const menuItems = ref<MenuItem[]>([
|
|||||||
{ label: 'Administrar Clasificaciones', icon: 'pi pi-sitemap', to: '/warehouse/classifications' }
|
{ label: 'Administrar Clasificaciones', icon: 'pi pi-sitemap', to: '/warehouse/classifications' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Catálogo',
|
||||||
|
icon: 'pi pi-book',
|
||||||
|
items: [
|
||||||
|
{ label: 'Unidades de Medida', icon: 'pi pi-calculator', to: '/catalog/units-of-measure' }
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Ventas',
|
label: 'Ventas',
|
||||||
icon: 'pi pi-shopping-cart',
|
icon: 'pi pi-shopping-cart',
|
||||||
|
|||||||
443
src/modules/catalog/components/UnitOfMeasure.vue
Normal file
443
src/modules/catalog/components/UnitOfMeasure.vue
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import { useConfirm } from 'primevue/useconfirm';
|
||||||
|
import Breadcrumb from 'primevue/breadcrumb';
|
||||||
|
import Button from 'primevue/button';
|
||||||
|
import Card from 'primevue/card';
|
||||||
|
import DataTable from 'primevue/datatable';
|
||||||
|
import Column from 'primevue/column';
|
||||||
|
import Dialog from 'primevue/dialog';
|
||||||
|
import InputText from 'primevue/inputtext';
|
||||||
|
import Dropdown from 'primevue/dropdown';
|
||||||
|
import InputSwitch from 'primevue/inputswitch';
|
||||||
|
import Tag from 'primevue/tag';
|
||||||
|
import Toast from 'primevue/toast';
|
||||||
|
import ConfirmDialog from 'primevue/confirmdialog';
|
||||||
|
import ProgressSpinner from 'primevue/progressspinner';
|
||||||
|
import { useUnitOfMeasureStore } from '../stores/unitOfMeasureStore';
|
||||||
|
import { unitTypesService } from '../services/unitsTypes';
|
||||||
|
import type { UnitOfMeasure, CreateUnitOfMeasureData } from '../types/unitOfMeasure';
|
||||||
|
import type { UnitType } from '../types/unitTypes';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const toast = useToast();
|
||||||
|
const confirm = useConfirm();
|
||||||
|
const unitStore = useUnitOfMeasureStore();
|
||||||
|
|
||||||
|
// Breadcrumb
|
||||||
|
const breadcrumbItems = ref([
|
||||||
|
{ label: 'Catálogo', route: '/catalog' },
|
||||||
|
{ label: 'Unidades de Medida' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const home = ref({
|
||||||
|
icon: 'pi pi-home',
|
||||||
|
route: '/'
|
||||||
|
});
|
||||||
|
|
||||||
|
// State
|
||||||
|
const showDialog = ref(false);
|
||||||
|
const isEditing = ref(false);
|
||||||
|
const formData = ref<CreateUnitOfMeasureData>({
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
type: 4, // Default: Unidad
|
||||||
|
abbreviation: '',
|
||||||
|
is_active: 1
|
||||||
|
});
|
||||||
|
const editingId = ref<number | null>(null);
|
||||||
|
|
||||||
|
// Type options - loaded from API
|
||||||
|
const typeOptions = ref<{ label: string; value: number }[]>([]);
|
||||||
|
const loadingTypes = ref(false);
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
const units = computed(() => unitStore.units);
|
||||||
|
const loading = computed(() => unitStore.loading);
|
||||||
|
|
||||||
|
const dialogTitle = computed(() =>
|
||||||
|
isEditing.value ? 'Editar Unidad de Medida' : 'Nueva Unidad de Medida'
|
||||||
|
);
|
||||||
|
|
||||||
|
const getStatusConfig = (isActive: number) => {
|
||||||
|
return isActive === 1
|
||||||
|
? { label: 'Activa', severity: 'success' }
|
||||||
|
: { label: 'Inactiva', severity: 'secondary' };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const openCreateDialog = () => {
|
||||||
|
isEditing.value = false;
|
||||||
|
editingId.value = null;
|
||||||
|
formData.value = {
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
type: 4,
|
||||||
|
abbreviation: '',
|
||||||
|
is_active: 1
|
||||||
|
};
|
||||||
|
showDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEditDialog = (unit: UnitOfMeasure) => {
|
||||||
|
isEditing.value = true;
|
||||||
|
editingId.value = unit.id;
|
||||||
|
formData.value = {
|
||||||
|
code: unit.code,
|
||||||
|
name: unit.name,
|
||||||
|
type: unit.type,
|
||||||
|
abbreviation: unit.abbreviation,
|
||||||
|
is_active: unit.is_active
|
||||||
|
};
|
||||||
|
showDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
showDialog.value = false;
|
||||||
|
formData.value = {
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
type: 4,
|
||||||
|
abbreviation: '',
|
||||||
|
is_active: 1
|
||||||
|
};
|
||||||
|
editingId.value = null;
|
||||||
|
isEditing.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveUnit = async () => {
|
||||||
|
try {
|
||||||
|
if (isEditing.value && editingId.value) {
|
||||||
|
await unitStore.updateUnit(editingId.value, formData.value);
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Actualización Exitosa',
|
||||||
|
detail: 'La unidad de medida ha sido actualizada correctamente.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await unitStore.createUnit(formData.value);
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Creación Exitosa',
|
||||||
|
detail: 'La unidad de medida ha sido creada correctamente.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
closeDialog();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving unit:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'No se pudo guardar la unidad de medida. Por favor, intenta nuevamente.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = (unit: UnitOfMeasure) => {
|
||||||
|
confirm.require({
|
||||||
|
message: `¿Estás seguro de eliminar la unidad de medida "${unit.name}"?`,
|
||||||
|
header: 'Confirmar Eliminación',
|
||||||
|
icon: 'pi pi-exclamation-triangle',
|
||||||
|
acceptLabel: 'Sí, eliminar',
|
||||||
|
rejectLabel: 'Cancelar',
|
||||||
|
acceptClass: 'p-button-danger',
|
||||||
|
accept: () => deleteUnit(unit.id)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteUnit = async (id: number) => {
|
||||||
|
try {
|
||||||
|
await unitStore.deleteUnit(id);
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Eliminación Exitosa',
|
||||||
|
detail: 'La unidad de medida ha sido eliminada correctamente.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting unit:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'No se pudo eliminar la unidad de medida. Puede estar en uso.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load unit types from API
|
||||||
|
const loadUnitTypes = async () => {
|
||||||
|
try {
|
||||||
|
loadingTypes.value = true;
|
||||||
|
const response = await unitTypesService.getUnitTypes();
|
||||||
|
typeOptions.value = response.data.unit_types.map(type => ({
|
||||||
|
label: type.name,
|
||||||
|
value: type.id
|
||||||
|
}));
|
||||||
|
console.log('Unit types loaded:', typeOptions.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading unit types:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'No se pudieron cargar los tipos de unidades.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loadingTypes.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
onMounted(async () => {
|
||||||
|
await Promise.all([
|
||||||
|
unitStore.fetchUnits(),
|
||||||
|
loadUnitTypes()
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- Toast & Confirm Dialog -->
|
||||||
|
<Toast position="bottom-right" />
|
||||||
|
<ConfirmDialog />
|
||||||
|
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Breadcrumb :home="home" :model="breadcrumbItems">
|
||||||
|
<template #item="{ item }">
|
||||||
|
<a
|
||||||
|
v-if="item.route"
|
||||||
|
:href="item.route"
|
||||||
|
@click.prevent="router.push(item.route)"
|
||||||
|
class="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</a>
|
||||||
|
<span v-else class="text-surface-600 dark:text-surface-400">
|
||||||
|
{{ item.label }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Breadcrumb>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h1 class="text-3xl font-black leading-tight tracking-tight text-surface-900 dark:text-white">
|
||||||
|
Unidades de Medida
|
||||||
|
</h1>
|
||||||
|
<Button
|
||||||
|
label="Nueva Unidad"
|
||||||
|
icon="pi pi-plus"
|
||||||
|
@click="openCreateDialog"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Card -->
|
||||||
|
<Card>
|
||||||
|
<template #content>
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div v-if="loading && units.length === 0" class="flex justify-center items-center py-12">
|
||||||
|
<ProgressSpinner
|
||||||
|
style="width: 50px; height: 50px"
|
||||||
|
strokeWidth="4"
|
||||||
|
animationDuration="1s"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Data Table -->
|
||||||
|
<DataTable
|
||||||
|
v-else
|
||||||
|
:value="units"
|
||||||
|
:loading="loading"
|
||||||
|
stripedRows
|
||||||
|
responsiveLayout="scroll"
|
||||||
|
:paginator="true"
|
||||||
|
:rows="10"
|
||||||
|
:rowsPerPageOptions="[5, 10, 20, 50]"
|
||||||
|
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown CurrentPageReport"
|
||||||
|
currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} unidades"
|
||||||
|
>
|
||||||
|
<Column field="code" header="Código" sortable style="width: 120px">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<span class="font-mono font-medium text-surface-700 dark:text-surface-300">
|
||||||
|
{{ slotProps.data.code }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="abbreviation" header="Abreviatura" sortable style="width: 150px">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<span class="font-mono font-semibold text-primary">
|
||||||
|
{{ slotProps.data.abbreviation }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<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="type_name" header="Tipo" sortable style="width: 150px">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<span class="text-sm text-surface-600 dark:text-surface-400">
|
||||||
|
{{ slotProps.data.type_name }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="is_active" header="Estado" sortable style="width: 120px">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<Tag
|
||||||
|
:value="getStatusConfig(slotProps.data.is_active).label"
|
||||||
|
:severity="getStatusConfig(slotProps.data.is_active).severity"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column header="Acciones" style="width: 150px">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-pencil"
|
||||||
|
text
|
||||||
|
rounded
|
||||||
|
severity="secondary"
|
||||||
|
@click="openEditDialog(slotProps.data)"
|
||||||
|
v-tooltip.top="'Editar'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-trash"
|
||||||
|
text
|
||||||
|
rounded
|
||||||
|
severity="danger"
|
||||||
|
@click="confirmDelete(slotProps.data)"
|
||||||
|
v-tooltip.top="'Eliminar'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<template #empty>
|
||||||
|
<div class="text-center py-8 text-surface-500 dark:text-surface-400">
|
||||||
|
No hay unidades de medida registradas.
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- Create/Edit Dialog -->
|
||||||
|
<Dialog
|
||||||
|
v-model:visible="showDialog"
|
||||||
|
:header="dialogTitle"
|
||||||
|
:modal="true"
|
||||||
|
:closable="true"
|
||||||
|
:draggable="false"
|
||||||
|
class="w-full max-w-md"
|
||||||
|
>
|
||||||
|
<div class="space-y-4 pt-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: GS1, KG01"
|
||||||
|
:required="true"
|
||||||
|
/>
|
||||||
|
</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: PIEZA, KILOGRAMO, METRO"
|
||||||
|
:required="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Abbreviation -->
|
||||||
|
<div>
|
||||||
|
<label for="abbreviation" class="block text-sm font-medium mb-2">
|
||||||
|
Abreviatura <span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<InputText
|
||||||
|
id="abbreviation"
|
||||||
|
v-model="formData.abbreviation"
|
||||||
|
class="w-full"
|
||||||
|
placeholder="Ej: PZA, kg, m"
|
||||||
|
:required="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Type -->
|
||||||
|
<div>
|
||||||
|
<label for="type" class="block text-sm font-medium mb-2">
|
||||||
|
Tipo <span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<Dropdown
|
||||||
|
id="type"
|
||||||
|
v-model="formData.type"
|
||||||
|
:options="typeOptions"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
class="w-full"
|
||||||
|
placeholder="Selecciona un tipo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status -->
|
||||||
|
<div>
|
||||||
|
<label for="is_active" class="block text-sm font-medium mb-2">
|
||||||
|
Estado
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<InputSwitch
|
||||||
|
id="is_active"
|
||||||
|
:model-value="formData.is_active === 1"
|
||||||
|
@update:model-value="formData.is_active = $event ? 1 : 0"
|
||||||
|
/>
|
||||||
|
<span class="text-sm font-medium">
|
||||||
|
{{ formData.is_active === 1 ? 'Activa' : 'Inactiva' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
label="Cancelar"
|
||||||
|
severity="secondary"
|
||||||
|
outlined
|
||||||
|
@click="closeDialog"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:label="isEditing ? 'Actualizar' : 'Crear'"
|
||||||
|
:disabled="!formData.code || !formData.name || !formData.abbreviation"
|
||||||
|
@click="saveUnit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
53
src/modules/catalog/services/unitOfMeasureService.ts
Normal file
53
src/modules/catalog/services/unitOfMeasureService.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import api from '../../../services/api';
|
||||||
|
import type {
|
||||||
|
UnitOfMeasureResponse,
|
||||||
|
CreateUnitOfMeasureData,
|
||||||
|
UpdateUnitOfMeasureData,
|
||||||
|
SingleUnitOfMeasureResponse
|
||||||
|
} from '../types/unitOfMeasure';
|
||||||
|
|
||||||
|
export const unitOfMeasureService = {
|
||||||
|
/**
|
||||||
|
* Get all units of measure with pagination
|
||||||
|
*/
|
||||||
|
async getUnits(page = 1, perPage = 10): Promise<UnitOfMeasureResponse> {
|
||||||
|
const response = await api.get(`/api/catalogs/units-of-measure`, {
|
||||||
|
params: { page, per_page: perPage }
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Units of Measure response:', response);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single unit of measure by ID
|
||||||
|
*/
|
||||||
|
async getUnitById(id: number): Promise<SingleUnitOfMeasureResponse> {
|
||||||
|
const response = await api.get(`/api/catalogs/units-of-measure/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new unit of measure
|
||||||
|
*/
|
||||||
|
async createUnit(data: CreateUnitOfMeasureData): Promise<SingleUnitOfMeasureResponse> {
|
||||||
|
const response = await api.post(`/api/catalogs/units-of-measure`, data);
|
||||||
|
console.log('Create Unit response:', response);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing unit of measure
|
||||||
|
*/
|
||||||
|
async updateUnit(id: number, data: UpdateUnitOfMeasureData): Promise<SingleUnitOfMeasureResponse> {
|
||||||
|
const response = await api.put(`/api/catalogs/units-of-measure/${id}`, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a unit of measure
|
||||||
|
*/
|
||||||
|
async deleteUnit(id: number): Promise<void> {
|
||||||
|
await api.delete(`/api/catalogs/units-of-measure/${id}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
14
src/modules/catalog/services/unitsTypes.ts
Normal file
14
src/modules/catalog/services/unitsTypes.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import api from '../../../services/api';
|
||||||
|
import type { UnitTypesResponse } from '../types/unitTypes';
|
||||||
|
|
||||||
|
export const unitTypesService = {
|
||||||
|
/**
|
||||||
|
* Get all unit types
|
||||||
|
* Returns the list of available unit types (Unidad, Peso, Volumen, Longitud, etc.)
|
||||||
|
*/
|
||||||
|
async getUnitTypes(): Promise<UnitTypesResponse> {
|
||||||
|
const response = await api.get<UnitTypesResponse>(`/api/catalogs/unit-types`);
|
||||||
|
console.log('Unit Types response:', response.data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
149
src/modules/catalog/stores/unitOfMeasureStore.ts
Normal file
149
src/modules/catalog/stores/unitOfMeasureStore.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { unitOfMeasureService } from '../services/unitOfMeasureService';
|
||||||
|
import type { UnitOfMeasure, CreateUnitOfMeasureData, UpdateUnitOfMeasureData } from '../types/unitOfMeasure';
|
||||||
|
|
||||||
|
export const useUnitOfMeasureStore = defineStore('unitOfMeasure', () => {
|
||||||
|
// State
|
||||||
|
const units = ref<UnitOfMeasure[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const loaded = ref(false);
|
||||||
|
const error = ref<string | null>(null);
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
const activeUnits = computed(() =>
|
||||||
|
units.value.filter(unit => unit.is_active === 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
const inactiveUnits = computed(() =>
|
||||||
|
units.value.filter(unit => unit.is_active === 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
const unitCount = computed(() => units.value.length);
|
||||||
|
|
||||||
|
const getUnitById = computed(() => {
|
||||||
|
return (id: number) => units.value.find(unit => unit.id === id);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getUnitByAbbreviation = computed(() => {
|
||||||
|
return (abbreviation: string) =>
|
||||||
|
units.value.find(unit => unit.abbreviation.toLowerCase() === abbreviation.toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
const fetchUnits = async (force = false) => {
|
||||||
|
// Si ya están cargados y no se fuerza la recarga, no hacer nada
|
||||||
|
if (loaded.value && !force) {
|
||||||
|
console.log('Units of measure already loaded from store');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
const response = await unitOfMeasureService.getUnits();
|
||||||
|
units.value = response.data.units_of_measure.data;
|
||||||
|
loaded.value = true;
|
||||||
|
|
||||||
|
console.log('Units of measure loaded into store:', units.value.length);
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err instanceof Error ? err.message : 'Error loading units of measure';
|
||||||
|
console.error('Error in unit of measure store:', err);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshUnits = () => {
|
||||||
|
return fetchUnits(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createUnit = async (data: CreateUnitOfMeasureData) => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
await unitOfMeasureService.createUnit(data);
|
||||||
|
|
||||||
|
// Refrescar la lista después de crear
|
||||||
|
await refreshUnits();
|
||||||
|
|
||||||
|
console.log('Unit of measure created successfully');
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err instanceof Error ? err.message : 'Error creating unit of measure';
|
||||||
|
console.error('Error creating unit of measure:', err);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUnit = async (id: number, data: UpdateUnitOfMeasureData) => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
await unitOfMeasureService.updateUnit(id, data);
|
||||||
|
|
||||||
|
// Refrescar la lista después de actualizar
|
||||||
|
await refreshUnits();
|
||||||
|
|
||||||
|
console.log('Unit of measure updated successfully');
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err instanceof Error ? err.message : 'Error updating unit of measure';
|
||||||
|
console.error('Error updating unit of measure:', err);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteUnit = async (id: number) => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
await unitOfMeasureService.deleteUnit(id);
|
||||||
|
|
||||||
|
// Refrescar la lista después de eliminar
|
||||||
|
await refreshUnits();
|
||||||
|
|
||||||
|
console.log('Unit of measure deleted successfully');
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err instanceof Error ? err.message : 'Error deleting unit of measure';
|
||||||
|
console.error('Error deleting unit of measure:', err);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearUnits = () => {
|
||||||
|
units.value = [];
|
||||||
|
loaded.value = false;
|
||||||
|
error.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// State
|
||||||
|
units,
|
||||||
|
loading,
|
||||||
|
loaded,
|
||||||
|
error,
|
||||||
|
// Getters
|
||||||
|
activeUnits,
|
||||||
|
inactiveUnits,
|
||||||
|
unitCount,
|
||||||
|
getUnitById,
|
||||||
|
getUnitByAbbreviation,
|
||||||
|
// Actions
|
||||||
|
fetchUnits,
|
||||||
|
refreshUnits,
|
||||||
|
createUnit,
|
||||||
|
updateUnit,
|
||||||
|
deleteUnit,
|
||||||
|
clearUnits,
|
||||||
|
};
|
||||||
|
});
|
||||||
75
src/modules/catalog/types/unitOfMeasure.d.ts
vendored
Normal file
75
src/modules/catalog/types/unitOfMeasure.d.ts
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Unit of Measure Type Definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface UnitOfMeasure {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
type: number;
|
||||||
|
type_name: string;
|
||||||
|
abbreviation: string;
|
||||||
|
is_active: number; // API returns 0 or 1
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
deleted_at: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateUnitOfMeasureData {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
type: number;
|
||||||
|
abbreviation: string;
|
||||||
|
is_active?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateUnitOfMeasureData {
|
||||||
|
code?: string;
|
||||||
|
name?: string;
|
||||||
|
type?: number;
|
||||||
|
abbreviation?: string;
|
||||||
|
is_active?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnitOfMeasurePagination {
|
||||||
|
current_page: number;
|
||||||
|
last_page: number;
|
||||||
|
per_page: number;
|
||||||
|
total: number;
|
||||||
|
from: number;
|
||||||
|
to: number;
|
||||||
|
first_page_url: string;
|
||||||
|
last_page_url: string;
|
||||||
|
next_page_url: string | null;
|
||||||
|
prev_page_url: string | null;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnitOfMeasureData {
|
||||||
|
current_page: number;
|
||||||
|
data: UnitOfMeasure[];
|
||||||
|
first_page_url: string;
|
||||||
|
from: number;
|
||||||
|
last_page: number;
|
||||||
|
last_page_url: string;
|
||||||
|
next_page_url: string | null;
|
||||||
|
path: string;
|
||||||
|
per_page: number;
|
||||||
|
prev_page_url: string | null;
|
||||||
|
to: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnitOfMeasureResponse {
|
||||||
|
status: string;
|
||||||
|
data: {
|
||||||
|
units_of_measure: UnitOfMeasureData;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SingleUnitOfMeasureResponse {
|
||||||
|
status: string;
|
||||||
|
data: {
|
||||||
|
unit_of_measure: UnitOfMeasure;
|
||||||
|
};
|
||||||
|
}
|
||||||
15
src/modules/catalog/types/unitTypes.d.ts
vendored
Normal file
15
src/modules/catalog/types/unitTypes.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Unit Types Definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface UnitType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnitTypesResponse {
|
||||||
|
status: string;
|
||||||
|
data: {
|
||||||
|
unit_types: UnitType[];
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import WarehouseIndex from '../modules/warehouse/components/WarehouseIndex.vue';
|
|||||||
import WarehouseForm from '../modules/warehouse/components/WarehouseForm.vue';
|
import WarehouseForm from '../modules/warehouse/components/WarehouseForm.vue';
|
||||||
import WarehouseCategory from '../modules/warehouse/components/WarehouseCategory.vue';
|
import WarehouseCategory from '../modules/warehouse/components/WarehouseCategory.vue';
|
||||||
import WarehouseClassification from '../modules/warehouse/components/WarehouseClassification.vue';
|
import WarehouseClassification from '../modules/warehouse/components/WarehouseClassification.vue';
|
||||||
|
import UnitOfMeasure from '../modules/catalog/components/UnitOfMeasure.vue';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
@ -89,6 +90,25 @@ const routes: RouteRecordRaw[] = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'catalog',
|
||||||
|
name: 'Catalog',
|
||||||
|
meta: {
|
||||||
|
title: 'Catálogo',
|
||||||
|
requiresAuth: true
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'units-of-measure',
|
||||||
|
name: 'UnitsOfMeasure',
|
||||||
|
component: UnitOfMeasure,
|
||||||
|
meta: {
|
||||||
|
title: 'Unidades de Medida',
|
||||||
|
requiresAuth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user