Juan Felipe Zapata Moreno fb37a2d62f feat: Refactorización de la gestión de series en POS
- Se eliminó el modal para eliminar series en Inventory/Serials.vue y se ajustó el diseño de la tabla.
- Se actualizó Movements/Edit.vue para usar el componente SerialInputList en el manejo de entrada de series.
- Se mejoró EntryModal.vue para utilizar SerialInputList en la captura de series.
- Se introdujo BundleSerialSelector.vue para seleccionar series desde paquetes (bundles).
- Se implementaron mejoras en la gestión de series en cart.js para manejar paquetes con series.
- Se agregó un nuevo método de servicio en serialService.js para obtener componentes de paquetes con seguimiento por series.
- Se creó el componente SerialInputList.vue para una mejor gestión de entrada de series.
- Se limpió ReturnDetail.vue eliminando la funcionalidad de cancelación de devoluciones.
2026-02-18 21:40:31 -06:00

235 lines
9.6 KiB
JavaScript

import { defineStore } from 'pinia';
const useCart = defineStore('cart', {
state: () => ({
items: [], // Productos en el carrito
paymentMethod: 'cash' // Método de pago por defecto
}),
getters: {
// Total de items
itemCount: (state) => state.items.reduce((sum, item) => sum + item.quantity, 0),
// Subtotal (sin impuestos)
subtotal: (state) => state.items.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0),
// Total de impuestos
tax: (state) => {
return state.items.reduce((sum, item) => {
const itemSubtotal = item.unit_price * item.quantity;
const itemTax = (itemSubtotal * item.tax_rate) / 100;
return sum + itemTax;
}, 0);
},
// Total final
total: (state) => {
const subtotal = state.items.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0);
const tax = state.items.reduce((sum, item) => {
const itemSubtotal = item.unit_price * item.quantity;
const itemTax = (itemSubtotal * item.tax_rate) / 100;
return sum + itemTax;
}, 0);
return subtotal + tax;
},
// Verificar si el carrito está vacío
isEmpty: (state) => state.items.length === 0
},
actions: {
// Agregar producto al carrito
addProduct(product, serialConfig = null) {
const key = 'p:' + product.id;
const existingItem = this.items.find(item => item.item_key === key);
if (existingItem) {
// Si ya existe y tiene seriales, abrir selector de nuevo
if (existingItem.track_serials) {
window.Notify.warning('Este producto requiere selección de seriales');
// Emitir evento para que Point.vue abra el selector
window.dispatchEvent(new CustomEvent('open-serial-selector', {
detail: { productId: product.id }
}));
return;
}
// Si NO tiene seriales, incrementar normalmente
if (existingItem.quantity < product.stock) {
existingItem.quantity++;
} else {
window.Notify.warning('No hay suficiente stock disponible');
}
} else {
// Agregar nuevo item
this.items.push({
item_key: key,
inventory_id: product.id,
bundle_id: null,
is_bundle: false,
product_name: product.name,
sku: product.sku,
quantity: 1,
unit_price: parseFloat(product.price?.retail_price || 0),
tax_rate: parseFloat(product.price?.tax || 16),
max_stock: product.stock,
// Campos para seriales
track_serials: product.track_serials || false,
serial_numbers: serialConfig?.serialNumbers || [],
serial_selection_mode: serialConfig?.selectionMode || null,
// Campos para unidad de medida
unit_of_measure: product.unit_of_measure || null,
allows_decimals: product.unit_of_measure?.allows_decimals || false
});
}
},
// Agregar bundle/kit al carrito
addBundle(bundle, serialConfig = null) {
const key = 'b:' + bundle.id;
const hasSerials = serialConfig && Object.keys(serialConfig.serialNumbers || {}).length > 0;
const existingItem = this.items.find(item => item.item_key === key);
if (existingItem) {
// Bundles con seriales no se pueden incrementar, se deben agregar de nuevo
if (existingItem.track_serials) {
window.Notify.warning('Este paquete requiere selección de seriales. Elimínalo y agrégalo de nuevo.');
return;
}
if (existingItem.quantity < bundle.available_stock) {
existingItem.quantity++;
} else {
window.Notify.warning('No hay suficiente stock disponible');
}
} else {
this.items.push({
item_key: key,
bundle_id: bundle.id,
inventory_id: null,
is_bundle: true,
product_name: bundle.name,
sku: bundle.sku,
quantity: 1,
unit_price: parseFloat(bundle.price?.retail_price || 0),
tax_rate: parseFloat(bundle.price?.tax || 16),
max_stock: bundle.available_stock,
track_serials: hasSerials,
serial_numbers: hasSerials ? serialConfig.serialNumbers : {},
serial_selection_mode: null,
unit_of_measure: null,
allows_decimals: false,
});
}
},
// Agregar producto con seriales ya configurados
addProductWithSerials(product, quantity, serialConfig) {
const key = 'p:' + product.id;
const existingItem = this.items.find(item => item.item_key === key);
const newSerials = serialConfig.serialNumbers || [];
if (existingItem) {
// COMBINAR seriales con los existentes
const combinedSerials = [...existingItem.serial_numbers, ...newSerials];
// Eliminar duplicados
existingItem.serial_numbers = [...new Set(combinedSerials)];
existingItem.quantity = existingItem.serial_numbers.length;
} else {
// Agregar nuevo item con seriales
this.items.push({
item_key: key,
inventory_id: product.id,
bundle_id: null,
is_bundle: false,
product_name: product.name,
sku: product.sku,
quantity: quantity,
unit_price: parseFloat(product.price?.retail_price || 0),
tax_rate: parseFloat(product.price?.tax || 16),
max_stock: product.stock,
track_serials: product.track_serials,
serial_numbers: newSerials,
// Campos para unidad de medida
unit_of_measure: product.unit_of_measure || null,
allows_decimals: product.unit_of_measure?.allows_decimals || false
});
}
},
// Actualizar seriales de un item
updateSerials(itemKey, serialConfig) {
const item = this.items.find(i => i.item_key === itemKey);
if (item) {
item.serial_numbers = serialConfig.serialNumbers || [];
item.serial_selection_mode = serialConfig.selectionMode;
}
},
// Obtener seriales ya seleccionados (para excluir del selector)
getSelectedSerials() {
const serials = [];
this.items.forEach(item => {
if (!item.track_serials) return;
if (item.is_bundle && item.serial_numbers) {
// Bundle: serial_numbers es { inventoryId: [serial_number, ...] }
Object.values(item.serial_numbers).forEach(arr => serials.push(...arr));
} else if (Array.isArray(item.serial_numbers)) {
// Producto: serial_numbers es [serial_number, ...]
serials.push(...item.serial_numbers);
}
});
return serials;
},
// Verificar si un item necesita selección de seriales
needsSerialSelection(itemKey) {
const item = this.items.find(i => i.item_key === itemKey);
if (!item || !item.track_serials) return false;
// Necesita selección si es manual y no tiene suficientes seriales
if (item.serial_selection_mode === 'manual') {
return (item.serial_numbers?.length || 0) < item.quantity;
}
return false;
},
// Actualizar cantidad de un item (por item_key)
updateQuantity(itemKey, quantity) {
const item = this.items.find(i => i.item_key === itemKey);
if (item) {
// Convertir a número (puede ser decimal)
const numQuantity = parseFloat(quantity);
if (isNaN(numQuantity) || numQuantity <= 0) {
this.removeProduct(itemKey);
} else if (numQuantity <= item.max_stock) {
// Si NO permite decimales, redondear a entero
item.quantity = item.allows_decimals ? numQuantity : Math.floor(numQuantity);
} else {
window.Notify.warning('No hay suficiente stock disponible');
}
}
},
// Remover producto o bundle (por item_key)
removeProduct(itemKey) {
const index = this.items.findIndex(i => i.item_key === itemKey);
if (index !== -1) {
this.items.splice(index, 1);
}
},
// Limpiar carrito
clear() {
this.items = [];
this.paymentMethod = 'cash';
},
// Cambiar método de pago
setPaymentMethod(method) {
this.paymentMethod = method;
}
}
});
export default useCart;