From 7bd247f0c5ff162e7604f8456f7f9a4e105e08e8 Mon Sep 17 00:00:00 2001 From: "edgar.mendez" Date: Wed, 25 Feb 2026 17:39:44 -0600 Subject: [PATCH] feat: implement units of measure management with CRUD operations and SAT unit integration --- .../{UnitOfMeasure.vue => units/Units.vue} | 223 ++------------- .../catalog/components/units/UnitsForm.vue | 261 ++++++++++++++++++ .../catalog/services/sat-units.services.ts | 17 ++ .../catalog/services/unit-measure.services.ts | 21 +- .../catalog/stores/unitOfMeasureStore.ts | 21 +- .../catalog/types/unit-measure.interfaces.ts | 126 +++++---- src/router/index.ts | 4 +- 7 files changed, 401 insertions(+), 272 deletions(-) rename src/modules/catalog/components/{UnitOfMeasure.vue => units/Units.vue} (57%) create mode 100644 src/modules/catalog/components/units/UnitsForm.vue create mode 100644 src/modules/catalog/services/sat-units.services.ts diff --git a/src/modules/catalog/components/UnitOfMeasure.vue b/src/modules/catalog/components/units/Units.vue similarity index 57% rename from src/modules/catalog/components/UnitOfMeasure.vue rename to src/modules/catalog/components/units/Units.vue index efd4826..e6e4568 100644 --- a/src/modules/catalog/components/UnitOfMeasure.vue +++ b/src/modules/catalog/components/units/Units.vue @@ -8,17 +8,13 @@ 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/unit-measure.interfaces'; +import { useUnitOfMeasureStore } from '../../stores/unitOfMeasureStore'; +import UnitsForm from './UnitsForm.vue'; +import type { UnitOfMeasure, CreateUnitOfMeasureData } from '../../types/unit-measure.interfaces'; const router = useRouter(); const toast = useToast(); @@ -39,27 +35,12 @@ const home = ref({ // State const showDialog = ref(false); const isEditing = ref(false); -const formData = ref({ - code: '', - name: '', - type: 4, // Default: Unidad - abbreviation: '', - is_active: 1 -}); -const editingId = ref(null); - -// Type options - loaded from API -const typeOptions = ref<{ label: string; value: number }[]>([]); -const loadingTypes = ref(false); +const selectedUnit = ref(null); // 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' } @@ -69,47 +50,20 @@ const getStatusConfig = (isActive: number) => { // Methods const openCreateDialog = () => { isEditing.value = false; - editingId.value = null; - formData.value = { - code: '', - name: '', - type: 4, - abbreviation: '', - is_active: 1 - }; + selectedUnit.value = null; 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 - }; + selectedUnit.value = unit; 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 () => { +const handleSaveUnit = async (data: CreateUnitOfMeasureData) => { try { - if (isEditing.value && editingId.value) { - await unitStore.updateUnit(editingId.value, formData.value); + if (isEditing.value && selectedUnit.value) { + await unitStore.updateUnit(selectedUnit.value.id, data); toast.add({ severity: 'success', summary: 'Actualización Exitosa', @@ -117,7 +71,7 @@ const saveUnit = async () => { life: 3000 }); } else { - await unitStore.createUnit(formData.value); + await unitStore.createUnit(data); toast.add({ severity: 'success', summary: 'Creación Exitosa', @@ -125,7 +79,7 @@ const saveUnit = async () => { life: 3000 }); } - closeDialog(); + showDialog.value = false; } catch (error) { console.error('Error saving unit:', error); 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 onMounted(async () => { - await Promise.all([ - unitStore.fetchUnits(), - loadUnitTypes() - ]); + await unitStore.fetchUnits(); }); @@ -263,14 +191,6 @@ onMounted(async () => { paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown CurrentPageReport" currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} unidades" > - - - - - + + + + + @@ -336,107 +264,12 @@ onMounted(async () => { - - + -
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- -
- - - {{ formData.is_active === 1 ? 'Activa' : 'Inactiva' }} - -
-
-
- - -
+ :unit="selectedUnit" + :is-editing="isEditing" + @save="handleSaveUnit" + /> diff --git a/src/modules/catalog/components/units/UnitsForm.vue b/src/modules/catalog/components/units/UnitsForm.vue new file mode 100644 index 0000000..5267e5d --- /dev/null +++ b/src/modules/catalog/components/units/UnitsForm.vue @@ -0,0 +1,261 @@ + + + diff --git a/src/modules/catalog/services/sat-units.services.ts b/src/modules/catalog/services/sat-units.services.ts new file mode 100644 index 0000000..8e278e3 --- /dev/null +++ b/src/modules/catalog/services/sat-units.services.ts @@ -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 { + const response = await api.get(`/api/sat/units?search=${search}`); + return response.data; + } +}; diff --git a/src/modules/catalog/services/unit-measure.services.ts b/src/modules/catalog/services/unit-measure.services.ts index 76bab99..67c5cea 100644 --- a/src/modules/catalog/services/unit-measure.services.ts +++ b/src/modules/catalog/services/unit-measure.services.ts @@ -1,28 +1,27 @@ import api from '../../../services/api'; import type { - UnitOfMeasureResponse, - CreateUnitOfMeasureData, + UnitOfMeasurePaginatedResponse, + UnitOfMeasureUnpaginatedResponse, + CreateUnitOfMeasureData, UpdateUnitOfMeasureData, - SingleUnitOfMeasureResponse + SingleUnitOfMeasureResponse, + UnitOfMeasureResponseById } from '../types/unit-measure.interfaces'; + 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 { - const response = await api.get(`/api/catalogs/units-of-measure`, { - params: { page, per_page: perPage } - }); - - console.log('Units of Measure response:', response); + async getUnits(paginate: boolean = true, search: string = ''): Promise { + const response = await api.get(`/api/catalogs/units-of-measure?paginate=${paginate}&search=${search}`); return response.data; }, /** * Get a single unit of measure by ID */ - async getUnitById(id: number): Promise { + async getUnitById(id: number): Promise { const response = await api.get(`/api/catalogs/units-of-measure/${id}`); return response.data; }, diff --git a/src/modules/catalog/stores/unitOfMeasureStore.ts b/src/modules/catalog/stores/unitOfMeasureStore.ts index 2aeb4da..0ff506c 100644 --- a/src/modules/catalog/stores/unitOfMeasureStore.ts +++ b/src/modules/catalog/stores/unitOfMeasureStore.ts @@ -1,7 +1,11 @@ import { defineStore } from 'pinia'; import { ref, computed } from 'vue'; 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', () => { // State @@ -31,7 +35,7 @@ export const useUnitOfMeasureStore = defineStore('unitOfMeasure', () => { }); // 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 if (loaded.value && !force) { console.log('Units of measure already loaded from store'); @@ -42,8 +46,17 @@ export const useUnitOfMeasureStore = defineStore('unitOfMeasure', () => { loading.value = true; error.value = null; - const response = await unitOfMeasureService.getUnits(); - units.value = response.data.units_of_measure.data; + const response = await unitOfMeasureService.getUnits(paginate, search); + + // 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; console.log('Units of measure loaded into store:', units.value.length); diff --git a/src/modules/catalog/types/unit-measure.interfaces.ts b/src/modules/catalog/types/unit-measure.interfaces.ts index 7f13497..0ab0ff9 100644 --- a/src/modules/catalog/types/unit-measure.interfaces.ts +++ b/src/modules/catalog/types/unit-measure.interfaces.ts @@ -1,75 +1,81 @@ -/** - * 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; +// Interface para la unidad SAT +export interface SatUnit { + id: number; + code: string; + name: string; + symbol: string; + created_at: string; + updated_at: string; } +// Interface para la Unidad de Medida +export interface UnitOfMeasure { + id: number; + name: string; + abbreviation: string; + is_active: number; + created_at: string; + updated_at: string; + deleted_at: string | null; + code_sat: number; + sat_unit: SatUnit; +} + +// Interfaces para crear y actualizar unidades de medida export interface CreateUnitOfMeasureData { - code: string; - name: string; - type: number; - abbreviation: string; - is_active?: number; + name: string; + abbreviation: string; + code_sat: number | null; + is_active: number; } export interface UpdateUnitOfMeasureData { - code?: string; - name?: string; - type?: number; - abbreviation?: string; - is_active?: number; + name?: string; + abbreviation?: string; + code_sat?: number | null; + 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; +// Interface para los links de paginación +export interface PaginationLink { + url: string | null; + label: string; + active: boolean; } -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; +// Interface genérica para respuestas paginadas +export interface PaginatedResponse { + current_page: number; + data: T[]; + first_page_url: string; + from: number; + last_page: number; + last_page_url: string; + links: PaginationLink[]; + 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; - }; +// Interface genérica para respuestas sin paginación +export interface UnpaginatedResponse { + data: T[]; } +// Tipo específico para la respuesta paginada de Unidades de Medida +export type UnitOfMeasurePaginatedResponse = PaginatedResponse; + +// Tipo específico para la respuesta no paginada de Unidades de Medida +export type UnitOfMeasureUnpaginatedResponse = UnpaginatedResponse; + +export type UnitOfMeasureResponseById = { + data: UnitOfMeasure; +}; + export interface SingleUnitOfMeasureResponse { - status: string; - data: { - unit_of_measure: UnitOfMeasure; - }; -} + message: string; + data: UnitOfMeasure; +} \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts index 957a4bd..44f63e3 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -8,7 +8,7 @@ import WarehouseIndex from '../modules/warehouse/components/WarehouseIndex.vue'; import WarehouseForm from '../modules/warehouse/components/WarehouseForm.vue'; import WarehouseDetails from '../modules/warehouse/components/WarehouseDetails.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 ProductsIndex from '../modules/products/components/ProductsIndex.vue'; import ProductForm from '../modules/products/components/ProductForm.vue'; @@ -141,7 +141,7 @@ const routes: RouteRecordRaw[] = [ { path: 'units-of-measure', name: 'UnitsOfMeasure', - component: UnitOfMeasure, + component: Units, meta: { title: 'Unidades de Medida', requiresAuth: true