diff --git a/src/pages/POS/Movements/DetailModal.vue b/src/pages/POS/Movements/DetailModal.vue index 6692a04..9ce1756 100644 --- a/src/pages/POS/Movements/DetailModal.vue +++ b/src/pages/POS/Movements/DetailModal.vue @@ -78,7 +78,25 @@ const handleClose = () => { }; const handleEdit = () => { - emit('edit', movement.value); + // Si es un movimiento múltiple, usar el valor actual + if (isMultiProduct.value) { + emit('edit', movement.value); + return; + } + + // Para movimientos individuales, hacer fetch para obtener datos completos (incluidos seriales) + loading.value = true; + api.get(apiURL(`movimientos/${movement.value.id}`), { + onSuccess: (data) => { + emit('edit', data.movement || data); + }, + onError: () => { + window.Notify.error('Error al cargar el movimiento'); + }, + onFinish: () => { + loading.value = false; + } + }); }; const handleEditProduct = (product) => { diff --git a/src/pages/POS/Movements/Edit.vue b/src/pages/POS/Movements/Edit.vue index 27069d2..1627eb9 100644 --- a/src/pages/POS/Movements/Edit.vue +++ b/src/pages/POS/Movements/Edit.vue @@ -32,9 +32,13 @@ const form = useForm({ origin_warehouse_id: '', destination_warehouse_id: '', invoice_reference: '', - notes: '' + notes: '', + serial_numbers: [] // Array de números de serie }); +/** Estado para manejo de seriales */ +const serialsText = ref(''); // Texto editable (uno por línea) + /** Computed */ const movementTypeInfo = computed(() => { const types = { @@ -71,6 +75,43 @@ const allowsDecimals = computed(() => { return props.movement?.inventory?.unit_of_measure?.allows_decimals || false; }); +const hasSerials = computed(() => { + return props.movement?.inventory?.track_serials || false; +}); + +const serialsArray = computed(() => { + return serialsText.value + .split('\n') + .map(s => s.trim()) + .filter(s => s.length > 0); +}); + +const serialsValidation = computed(() => { + if (!hasSerials.value) return { valid: true, message: '' }; + + const quantity = Number(form.quantity); + const serialCount = serialsArray.value.length; + + if (quantity > 0 && serialCount === 0) { + return { valid: false, message: 'Debe ingresar números de serie' }; + } + + if (serialCount !== quantity) { + return { + valid: false, + message: `Debe ingresar ${quantity} número(s) de serie (actual: ${serialCount})` + }; + } + + // Verificar duplicados + const uniqueSerials = new Set(serialsArray.value); + if (uniqueSerials.size !== serialCount) { + return { valid: false, message: 'Hay números de serie duplicados' }; + } + + return { valid: true, message: '' }; +}); + /** Métodos */ const loadWarehouses = () => { loading.value = true; @@ -91,12 +132,28 @@ const loadWarehouses = () => { }; const updateMovement = () => { + // Validar seriales si el producto los requiere + if (hasSerials.value && !serialsValidation.value.valid) { + window.Notify.warning(serialsValidation.value.message); + return; + } + // Preparar datos según el tipo de movimiento const data = { quantity: Number(form.quantity), // Común para todos los tipos notes: form.notes || null }; + // Siempre enviar serial_numbers si el producto requiere seriales + if (hasSerials.value) { + // Validar que haya seriales + if (serialsArray.value.length === 0) { + window.Notify.warning('Debe ingresar números de serie para este producto'); + return; + } + data.serial_numbers = serialsArray.value; + } + // Campos específicos por tipo if (props.movement.movement_type === 'entry') { data.unit_cost = Number(form.unit_cost); @@ -110,9 +167,19 @@ const updateMovement = () => { data.warehouse_to_id = form.destination_warehouse_id; } + console.log('📤 Datos a enviar al backend:', { + movement_id: props.movement.id, + data: data, + serial_numbers_count: data.serial_numbers?.length || 0, + hasSerials: hasSerials.value + }); + api.put(apiURL(`movimientos/${props.movement.id}`), { data, - onSuccess: () => { + onSuccess: (response) => { + console.log('✅ Respuesta del backend (completa):', JSON.stringify(response, null, 2)); + console.log('✅ Tipo de respuesta:', typeof response); + console.log('✅ Keys:', Object.keys(response || {})); window.Notify.success('Movimiento actualizado correctamente'); emit('updated'); closeModal(); @@ -144,6 +211,21 @@ watch(() => props.show, (isShown) => { form.invoice_reference = props.movement.invoice_reference || ''; form.notes = props.movement.notes || ''; + // Cargar números de serie si existen + let serialNumbers = []; + + if (props.movement.serial_numbers && props.movement.serial_numbers.length > 0) { + serialNumbers = props.movement.serial_numbers; + } else if (props.movement.serials && props.movement.serials.length > 0) { + serialNumbers = props.movement.serials.map(s => s.serial_number); + } + + if (serialNumbers.length > 0) { + serialsText.value = serialNumbers.join('\n'); + } else { + serialsText.value = ''; + } + // Almacenes según tipo if (props.movement.movement_type === 'entry') { form.destination_warehouse_id = props.movement.warehouse_id || ''; @@ -153,7 +235,19 @@ watch(() => props.show, (isShown) => { form.origin_warehouse_id = props.movement.origin_warehouse_id || ''; form.destination_warehouse_id = props.movement.destination_warehouse_id || ''; } + console.log('🔍 Debug Movement:', { + movement: props.movement, + has_inventory: !!props.movement.inventory, + track_serials: props.movement?.inventory?.track_serials, + serials_relation: props.movement.serials, + hasSerials_computed: hasSerials.value, + serialNumbers_loaded: serialNumbers, + serialsText_final: serialsText.value + }); } + } else { + // Limpiar seriales al cerrar + serialsText.value = ''; } }, { immediate: true }); @@ -194,6 +288,13 @@ watch(() => props.show, (isShown) => {

Producto

+ + + Con seriales +

{{ movement?.inventory?.name || 'N/A' }} @@ -222,6 +323,48 @@ watch(() => props.show, (isShown) => { + +

+
+ + +
+

+ Ingresa un número de serie por línea. Debe coincidir con la cantidad ({{ form.quantity }}). +

+ + + +
+

+ {{ serialsArray.length }} de {{ form.quantity }} seriales +

+

+ {{ serialsValidation.message }} +

+

+ + Válido +

+
+
+