- 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.
235 lines
9.6 KiB
JavaScript
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;
|