feat: implement units of measure management with CRUD operations and SAT unit integration
This commit is contained in:
parent
3cb7264b0a
commit
7bd247f0c5
@ -8,17 +8,13 @@ import Button from 'primevue/button';
|
|||||||
import Card from 'primevue/card';
|
import Card from 'primevue/card';
|
||||||
import DataTable from 'primevue/datatable';
|
import DataTable from 'primevue/datatable';
|
||||||
import Column from 'primevue/column';
|
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 Tag from 'primevue/tag';
|
||||||
import Toast from 'primevue/toast';
|
import Toast from 'primevue/toast';
|
||||||
import ConfirmDialog from 'primevue/confirmdialog';
|
import ConfirmDialog from 'primevue/confirmdialog';
|
||||||
import ProgressSpinner from 'primevue/progressspinner';
|
import ProgressSpinner from 'primevue/progressspinner';
|
||||||
import { useUnitOfMeasureStore } from '../stores/unitOfMeasureStore';
|
import { useUnitOfMeasureStore } from '../../stores/unitOfMeasureStore';
|
||||||
import { unitTypesService } from '../services/unitsTypes';
|
import UnitsForm from './UnitsForm.vue';
|
||||||
import type { UnitOfMeasure, CreateUnitOfMeasureData } from '../types/unit-measure.interfaces';
|
import type { UnitOfMeasure, CreateUnitOfMeasureData } from '../../types/unit-measure.interfaces';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@ -39,27 +35,12 @@ const home = ref({
|
|||||||
// State
|
// State
|
||||||
const showDialog = ref(false);
|
const showDialog = ref(false);
|
||||||
const isEditing = ref(false);
|
const isEditing = ref(false);
|
||||||
const formData = ref<CreateUnitOfMeasureData>({
|
const selectedUnit = ref<UnitOfMeasure | null>(null);
|
||||||
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
|
// Computed
|
||||||
const units = computed(() => unitStore.units);
|
const units = computed(() => unitStore.units);
|
||||||
const loading = computed(() => unitStore.loading);
|
const loading = computed(() => unitStore.loading);
|
||||||
|
|
||||||
const dialogTitle = computed(() =>
|
|
||||||
isEditing.value ? 'Editar Unidad de Medida' : 'Nueva Unidad de Medida'
|
|
||||||
);
|
|
||||||
|
|
||||||
const getStatusConfig = (isActive: number) => {
|
const getStatusConfig = (isActive: number) => {
|
||||||
return isActive === 1
|
return isActive === 1
|
||||||
? { label: 'Activa', severity: 'success' }
|
? { label: 'Activa', severity: 'success' }
|
||||||
@ -69,47 +50,20 @@ const getStatusConfig = (isActive: number) => {
|
|||||||
// Methods
|
// Methods
|
||||||
const openCreateDialog = () => {
|
const openCreateDialog = () => {
|
||||||
isEditing.value = false;
|
isEditing.value = false;
|
||||||
editingId.value = null;
|
selectedUnit.value = null;
|
||||||
formData.value = {
|
|
||||||
code: '',
|
|
||||||
name: '',
|
|
||||||
type: 4,
|
|
||||||
abbreviation: '',
|
|
||||||
is_active: 1
|
|
||||||
};
|
|
||||||
showDialog.value = true;
|
showDialog.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const openEditDialog = (unit: UnitOfMeasure) => {
|
const openEditDialog = (unit: UnitOfMeasure) => {
|
||||||
isEditing.value = true;
|
isEditing.value = true;
|
||||||
editingId.value = unit.id;
|
selectedUnit.value = unit;
|
||||||
formData.value = {
|
|
||||||
code: unit.code,
|
|
||||||
name: unit.name,
|
|
||||||
type: unit.type,
|
|
||||||
abbreviation: unit.abbreviation,
|
|
||||||
is_active: unit.is_active
|
|
||||||
};
|
|
||||||
showDialog.value = true;
|
showDialog.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeDialog = () => {
|
const handleSaveUnit = async (data: CreateUnitOfMeasureData) => {
|
||||||
showDialog.value = false;
|
|
||||||
formData.value = {
|
|
||||||
code: '',
|
|
||||||
name: '',
|
|
||||||
type: 4,
|
|
||||||
abbreviation: '',
|
|
||||||
is_active: 1
|
|
||||||
};
|
|
||||||
editingId.value = null;
|
|
||||||
isEditing.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveUnit = async () => {
|
|
||||||
try {
|
try {
|
||||||
if (isEditing.value && editingId.value) {
|
if (isEditing.value && selectedUnit.value) {
|
||||||
await unitStore.updateUnit(editingId.value, formData.value);
|
await unitStore.updateUnit(selectedUnit.value.id, data);
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: 'success',
|
severity: 'success',
|
||||||
summary: 'Actualización Exitosa',
|
summary: 'Actualización Exitosa',
|
||||||
@ -117,7 +71,7 @@ const saveUnit = async () => {
|
|||||||
life: 3000
|
life: 3000
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await unitStore.createUnit(formData.value);
|
await unitStore.createUnit(data);
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: 'success',
|
severity: 'success',
|
||||||
summary: 'Creación Exitosa',
|
summary: 'Creación Exitosa',
|
||||||
@ -125,7 +79,7 @@ const saveUnit = async () => {
|
|||||||
life: 3000
|
life: 3000
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
closeDialog();
|
showDialog.value = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving unit:', error);
|
console.error('Error saving unit:', error);
|
||||||
toast.add({
|
toast.add({
|
||||||
@ -169,35 +123,9 @@ const deleteUnit = async (id: number) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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
|
// Lifecycle
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([
|
await unitStore.fetchUnits();
|
||||||
unitStore.fetchUnits(),
|
|
||||||
loadUnitTypes()
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -263,14 +191,6 @@ onMounted(async () => {
|
|||||||
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown CurrentPageReport"
|
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown CurrentPageReport"
|
||||||
currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} unidades"
|
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">
|
<Column field="abbreviation" header="Abreviatura" sortable style="width: 150px">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
<span class="font-mono font-semibold text-primary">
|
<span class="font-mono font-semibold text-primary">
|
||||||
@ -287,10 +207,18 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
<Column field="type_name" header="Tipo" sortable style="width: 150px">
|
<Column field="sat_unit.name" header="Unidad SAT" sortable style="width: 200px">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
<span class="text-sm text-surface-600 dark:text-surface-400">
|
<span class="text-sm text-surface-600 dark:text-surface-400">
|
||||||
{{ slotProps.data.type_name }}
|
{{ slotProps.data.sat_unit?.name || 'N/A' }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="sat_unit.code" header="Código SAT" sortable style="width: 120px">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<span class="font-mono text-sm text-surface-600 dark:text-surface-400">
|
||||||
|
{{ slotProps.data.sat_unit?.code || 'N/A' }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
@ -336,107 +264,12 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Create/Edit Dialog -->
|
<!-- Create/Edit Form Dialog -->
|
||||||
<Dialog
|
<UnitsForm
|
||||||
v-model:visible="showDialog"
|
v-model:visible="showDialog"
|
||||||
:header="dialogTitle"
|
:unit="selectedUnit"
|
||||||
:modal="true"
|
:is-editing="isEditing"
|
||||||
:closable="true"
|
@save="handleSaveUnit"
|
||||||
: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>
|
</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>
|
</template>
|
||||||
261
src/modules/catalog/components/units/UnitsForm.vue
Normal file
261
src/modules/catalog/components/units/UnitsForm.vue
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import Dialog from 'primevue/dialog';
|
||||||
|
import Button from 'primevue/button';
|
||||||
|
import InputText from 'primevue/inputtext';
|
||||||
|
import Dropdown from 'primevue/dropdown';
|
||||||
|
import InputSwitch from 'primevue/inputswitch';
|
||||||
|
import { satUnitsService } from '../../services/sat-units.services';
|
||||||
|
import type { UnitOfMeasure, CreateUnitOfMeasureData, SatUnit } from '../../types/unit-measure.interfaces';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible: boolean;
|
||||||
|
unit?: UnitOfMeasure | null;
|
||||||
|
isEditing?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:visible', value: boolean): void;
|
||||||
|
(e: 'save', data: CreateUnitOfMeasureData): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
visible: false,
|
||||||
|
unit: null,
|
||||||
|
isEditing: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
// Form data
|
||||||
|
const formData = ref<CreateUnitOfMeasureData>({
|
||||||
|
name: '',
|
||||||
|
abbreviation: '',
|
||||||
|
code_sat: 1,
|
||||||
|
is_active: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
// Estado interno del switch (boolean para el UI)
|
||||||
|
const isActiveSwitch = ref(true);
|
||||||
|
|
||||||
|
// SAT Units
|
||||||
|
const satUnits = ref<SatUnit[]>([]);
|
||||||
|
const loadingSatUnits = ref(false);
|
||||||
|
const searchQuery = ref('');
|
||||||
|
let searchTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
// Options para el dropdown
|
||||||
|
const satUnitOptions = computed(() =>
|
||||||
|
satUnits.value.map(unit => ({
|
||||||
|
label: `${unit.code} - ${unit.name}`,
|
||||||
|
value: unit.id
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
const dialogTitle = computed(() =>
|
||||||
|
props.isEditing ? 'Editar Unidad de Medida' : 'Nueva Unidad de Medida'
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFormValid = computed(() => {
|
||||||
|
return !!(formData.value.name && formData.value.abbreviation);
|
||||||
|
});
|
||||||
|
|
||||||
|
const emptyMessage = computed(() => {
|
||||||
|
if (props.isEditing && satUnits.value.length === 1) {
|
||||||
|
return 'Escribe para buscar otra unidad SAT...';
|
||||||
|
}
|
||||||
|
return 'No se encontraron unidades. Escribe para buscar.';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load SAT Units
|
||||||
|
const loadSatUnits = async (search: string) => {
|
||||||
|
// Solo buscar si hay texto
|
||||||
|
if (!search || search.trim().length === 0) {
|
||||||
|
// Si estamos editando y hay una unidad actual, mantenerla
|
||||||
|
if (props.unit?.sat_unit) {
|
||||||
|
satUnits.value = [props.unit.sat_unit];
|
||||||
|
} else {
|
||||||
|
satUnits.value = [];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
loadingSatUnits.value = true;
|
||||||
|
const response = await satUnitsService.getSatUnits(search);
|
||||||
|
satUnits.value = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading SAT units:', error);
|
||||||
|
// Si hay error y estamos editando, mantener la unidad actual
|
||||||
|
if (props.unit?.sat_unit) {
|
||||||
|
satUnits.value = [props.unit.sat_unit];
|
||||||
|
} else {
|
||||||
|
satUnits.value = [];
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loadingSatUnits.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Debounced search
|
||||||
|
const handleSearchChange = (event: any) => {
|
||||||
|
const query = event.value || '';
|
||||||
|
searchQuery.value = query;
|
||||||
|
|
||||||
|
// Limpiar timeout anterior
|
||||||
|
if (searchTimeout) {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aplicar debounce de 500ms
|
||||||
|
searchTimeout = setTimeout(() => {
|
||||||
|
loadSatUnits(query);
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
name: '',
|
||||||
|
abbreviation: '',
|
||||||
|
code_sat: null,
|
||||||
|
is_active: 1
|
||||||
|
};
|
||||||
|
isActiveSwitch.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Watch para actualizar el formulario cuando cambie la unidad
|
||||||
|
watch(() => props.unit, (newUnit) => {
|
||||||
|
if (newUnit) {
|
||||||
|
formData.value = {
|
||||||
|
name: newUnit.name,
|
||||||
|
abbreviation: newUnit.abbreviation,
|
||||||
|
code_sat: newUnit.code_sat,
|
||||||
|
is_active: newUnit.is_active
|
||||||
|
};
|
||||||
|
isActiveSwitch.value = newUnit.is_active === 1;
|
||||||
|
|
||||||
|
// Precargar la unidad SAT actual en el dropdown
|
||||||
|
if (newUnit.sat_unit) {
|
||||||
|
satUnits.value = [newUnit.sat_unit];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resetForm();
|
||||||
|
satUnits.value = []; // Limpiar el dropdown
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
satUnits.value = []; // Limpiar las opciones del dropdown
|
||||||
|
resetForm();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
// Convertir el switch boolean a number para el backend
|
||||||
|
formData.value.is_active = isActiveSwitch.value ? 1 : 0;
|
||||||
|
|
||||||
|
emit('save', { ...formData.value });
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dialog
|
||||||
|
:visible="visible"
|
||||||
|
@update:visible="emit('update:visible', $event)"
|
||||||
|
:header="dialogTitle"
|
||||||
|
:modal="true"
|
||||||
|
:closable="true"
|
||||||
|
:draggable="false"
|
||||||
|
class="w-full max-w-md"
|
||||||
|
>
|
||||||
|
<div class="space-y-4 pt-4">
|
||||||
|
<!-- 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>
|
||||||
|
|
||||||
|
<!-- Code SAT -->
|
||||||
|
<div>
|
||||||
|
<label for="code_sat" class="block text-sm font-medium mb-2">
|
||||||
|
Unidad SAT <span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<Dropdown
|
||||||
|
id="code_sat"
|
||||||
|
v-model="formData.code_sat"
|
||||||
|
:options="satUnitOptions"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
:loading="loadingSatUnits"
|
||||||
|
placeholder="Escribe para buscar una unidad SAT..."
|
||||||
|
class="w-full"
|
||||||
|
:filter="true"
|
||||||
|
filterPlaceholder="Buscar unidad SAT"
|
||||||
|
:showClear="false"
|
||||||
|
@filter="handleSearchChange"
|
||||||
|
:emptyFilterMessage="emptyMessage"
|
||||||
|
/>
|
||||||
|
<small class="text-surface-500 dark:text-surface-400">
|
||||||
|
Unidad del catálogo SAT
|
||||||
|
</small>
|
||||||
|
</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"
|
||||||
|
v-model="isActiveSwitch"
|
||||||
|
/>
|
||||||
|
<span class="text-sm font-medium">
|
||||||
|
{{ isActiveSwitch ? 'Activa' : 'Inactiva' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
label="Cancelar"
|
||||||
|
severity="secondary"
|
||||||
|
outlined
|
||||||
|
@click="handleClose"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:label="isEditing ? 'Actualizar' : 'Crear'"
|
||||||
|
:disabled="!isFormValid"
|
||||||
|
@click="handleSave"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
17
src/modules/catalog/services/sat-units.services.ts
Normal file
17
src/modules/catalog/services/sat-units.services.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import api from '../../../services/api';
|
||||||
|
import type { SatUnit } from '../types/unit-measure.interfaces';
|
||||||
|
|
||||||
|
// Respuesta del endpoint de unidades SAT
|
||||||
|
export interface SatUnitsResponse {
|
||||||
|
data: SatUnit[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const satUnitsService = {
|
||||||
|
/**
|
||||||
|
* Get all SAT units with search filter
|
||||||
|
*/
|
||||||
|
async getSatUnits(search: string = ''): Promise<SatUnitsResponse> {
|
||||||
|
const response = await api.get(`/api/sat/units?search=${search}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,28 +1,27 @@
|
|||||||
import api from '../../../services/api';
|
import api from '../../../services/api';
|
||||||
import type {
|
import type {
|
||||||
UnitOfMeasureResponse,
|
UnitOfMeasurePaginatedResponse,
|
||||||
|
UnitOfMeasureUnpaginatedResponse,
|
||||||
CreateUnitOfMeasureData,
|
CreateUnitOfMeasureData,
|
||||||
UpdateUnitOfMeasureData,
|
UpdateUnitOfMeasureData,
|
||||||
SingleUnitOfMeasureResponse
|
SingleUnitOfMeasureResponse,
|
||||||
|
UnitOfMeasureResponseById
|
||||||
} from '../types/unit-measure.interfaces';
|
} from '../types/unit-measure.interfaces';
|
||||||
|
|
||||||
|
|
||||||
export const unitOfMeasureService = {
|
export const unitOfMeasureService = {
|
||||||
/**
|
/**
|
||||||
* Get all units of measure with pagination
|
* Get all units of measure with optional pagination and search
|
||||||
*/
|
*/
|
||||||
async getUnits(page = 1, perPage = 10): Promise<UnitOfMeasureResponse> {
|
async getUnits(paginate: boolean = true, search: string = ''): Promise<UnitOfMeasurePaginatedResponse | UnitOfMeasureUnpaginatedResponse> {
|
||||||
const response = await api.get(`/api/catalogs/units-of-measure`, {
|
const response = await api.get(`/api/catalogs/units-of-measure?paginate=${paginate}&search=${search}`);
|
||||||
params: { page, per_page: perPage }
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Units of Measure response:', response);
|
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a single unit of measure by ID
|
* Get a single unit of measure by ID
|
||||||
*/
|
*/
|
||||||
async getUnitById(id: number): Promise<SingleUnitOfMeasureResponse> {
|
async getUnitById(id: number): Promise<UnitOfMeasureResponseById> {
|
||||||
const response = await api.get(`/api/catalogs/units-of-measure/${id}`);
|
const response = await api.get(`/api/catalogs/units-of-measure/${id}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import { unitOfMeasureService } from '../services/unit-measure.services';
|
import { unitOfMeasureService } from '../services/unit-measure.services';
|
||||||
import type { UnitOfMeasure, CreateUnitOfMeasureData, UpdateUnitOfMeasureData } from '../types/unit-measure.interfaces';
|
import type {
|
||||||
|
UnitOfMeasure,
|
||||||
|
CreateUnitOfMeasureData,
|
||||||
|
UpdateUnitOfMeasureData
|
||||||
|
} from '../types/unit-measure.interfaces';
|
||||||
|
|
||||||
export const useUnitOfMeasureStore = defineStore('unitOfMeasure', () => {
|
export const useUnitOfMeasureStore = defineStore('unitOfMeasure', () => {
|
||||||
// State
|
// State
|
||||||
@ -31,7 +35,7 @@ export const useUnitOfMeasureStore = defineStore('unitOfMeasure', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
const fetchUnits = async (force = false) => {
|
const fetchUnits = async (force = false, paginate = true, search = '') => {
|
||||||
// Si ya están cargados y no se fuerza la recarga, no hacer nada
|
// Si ya están cargados y no se fuerza la recarga, no hacer nada
|
||||||
if (loaded.value && !force) {
|
if (loaded.value && !force) {
|
||||||
console.log('Units of measure already loaded from store');
|
console.log('Units of measure already loaded from store');
|
||||||
@ -42,8 +46,17 @@ export const useUnitOfMeasureStore = defineStore('unitOfMeasure', () => {
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
error.value = null;
|
error.value = null;
|
||||||
|
|
||||||
const response = await unitOfMeasureService.getUnits();
|
const response = await unitOfMeasureService.getUnits(paginate, search);
|
||||||
units.value = response.data.units_of_measure.data;
|
|
||||||
|
// Manejar respuesta paginada o no paginada
|
||||||
|
if ('current_page' in response) {
|
||||||
|
// Respuesta paginada
|
||||||
|
units.value = response.data;
|
||||||
|
} else {
|
||||||
|
// Respuesta no paginada
|
||||||
|
units.value = response.data;
|
||||||
|
}
|
||||||
|
|
||||||
loaded.value = true;
|
loaded.value = true;
|
||||||
|
|
||||||
console.log('Units of measure loaded into store:', units.value.length);
|
console.log('Units of measure loaded into store:', units.value.length);
|
||||||
|
|||||||
@ -1,57 +1,57 @@
|
|||||||
/**
|
// Interface para la unidad SAT
|
||||||
* Unit of Measure Type Definitions
|
export interface SatUnit {
|
||||||
*/
|
|
||||||
|
|
||||||
export interface UnitOfMeasure {
|
|
||||||
id: number;
|
id: number;
|
||||||
code: string;
|
code: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: number;
|
symbol: string;
|
||||||
type_name: string;
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface para la Unidad de Medida
|
||||||
|
export interface UnitOfMeasure {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
abbreviation: string;
|
abbreviation: string;
|
||||||
is_active: number; // API returns 0 or 1
|
is_active: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
deleted_at: string | null;
|
deleted_at: string | null;
|
||||||
|
code_sat: number;
|
||||||
|
sat_unit: SatUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Interfaces para crear y actualizar unidades de medida
|
||||||
export interface CreateUnitOfMeasureData {
|
export interface CreateUnitOfMeasureData {
|
||||||
code: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
type: number;
|
|
||||||
abbreviation: string;
|
abbreviation: string;
|
||||||
is_active?: number;
|
code_sat: number | null;
|
||||||
|
is_active: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateUnitOfMeasureData {
|
export interface UpdateUnitOfMeasureData {
|
||||||
code?: string;
|
|
||||||
name?: string;
|
name?: string;
|
||||||
type?: number;
|
|
||||||
abbreviation?: string;
|
abbreviation?: string;
|
||||||
|
code_sat?: number | null;
|
||||||
is_active?: number;
|
is_active?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnitOfMeasurePagination {
|
// Interface para los links de paginación
|
||||||
current_page: number;
|
export interface PaginationLink {
|
||||||
last_page: number;
|
url: string | null;
|
||||||
per_page: number;
|
label: string;
|
||||||
total: number;
|
active: boolean;
|
||||||
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 {
|
// Interface genérica para respuestas paginadas
|
||||||
|
export interface PaginatedResponse<T> {
|
||||||
current_page: number;
|
current_page: number;
|
||||||
data: UnitOfMeasure[];
|
data: T[];
|
||||||
first_page_url: string;
|
first_page_url: string;
|
||||||
from: number;
|
from: number;
|
||||||
last_page: number;
|
last_page: number;
|
||||||
last_page_url: string;
|
last_page_url: string;
|
||||||
|
links: PaginationLink[];
|
||||||
next_page_url: string | null;
|
next_page_url: string | null;
|
||||||
path: string;
|
path: string;
|
||||||
per_page: number;
|
per_page: number;
|
||||||
@ -60,16 +60,22 @@ export interface UnitOfMeasureData {
|
|||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnitOfMeasureResponse {
|
// Interface genérica para respuestas sin paginación
|
||||||
status: string;
|
export interface UnpaginatedResponse<T> {
|
||||||
data: {
|
data: T[];
|
||||||
units_of_measure: UnitOfMeasureData;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tipo específico para la respuesta paginada de Unidades de Medida
|
||||||
|
export type UnitOfMeasurePaginatedResponse = PaginatedResponse<UnitOfMeasure>;
|
||||||
|
|
||||||
|
// Tipo específico para la respuesta no paginada de Unidades de Medida
|
||||||
|
export type UnitOfMeasureUnpaginatedResponse = UnpaginatedResponse<UnitOfMeasure>;
|
||||||
|
|
||||||
|
export type UnitOfMeasureResponseById = {
|
||||||
|
data: UnitOfMeasure;
|
||||||
|
};
|
||||||
|
|
||||||
export interface SingleUnitOfMeasureResponse {
|
export interface SingleUnitOfMeasureResponse {
|
||||||
status: string;
|
message: string;
|
||||||
data: {
|
data: UnitOfMeasure;
|
||||||
unit_of_measure: UnitOfMeasure;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
@ -8,7 +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 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 UnitOfMeasure from '../modules/catalog/components/UnitOfMeasure.vue';
|
import Units from '../modules/catalog/components/units/Units.vue';
|
||||||
import ComercialClassification from '../modules/catalog/components/ComercialClassification.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';
|
||||||
@ -141,7 +141,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: 'units-of-measure',
|
path: 'units-of-measure',
|
||||||
name: 'UnitsOfMeasure',
|
name: 'UnitsOfMeasure',
|
||||||
component: UnitOfMeasure,
|
component: Units,
|
||||||
meta: {
|
meta: {
|
||||||
title: 'Unidades de Medida',
|
title: 'Unidades de Medida',
|
||||||
requiresAuth: true
|
requiresAuth: true
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user