refactor
This commit is contained in:
parent
46b155c2c8
commit
83dd71f80f
@ -14,10 +14,6 @@ const props = defineProps({
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
quantity: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
excludeSerials: {
|
excludeSerials: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
@ -31,7 +27,6 @@ const emit = defineEmits(['close', 'confirm']);
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const availableSerials = ref([]);
|
const availableSerials = ref([]);
|
||||||
const selectedSerials = ref([]);
|
const selectedSerials = ref([]);
|
||||||
const selectionMode = ref('auto'); // 'auto' | 'manual'
|
|
||||||
const searchQuery = ref('');
|
const searchQuery = ref('');
|
||||||
|
|
||||||
/** Computados */
|
/** Computados */
|
||||||
@ -47,22 +42,12 @@ const filteredSerials = computed(() => {
|
|||||||
|
|
||||||
const selectedCount = computed(() => selectedSerials.value.length);
|
const selectedCount = computed(() => selectedSerials.value.length);
|
||||||
|
|
||||||
const isComplete = computed(() => {
|
const hasEnoughStock = computed(() => {
|
||||||
if (selectionMode.value === 'auto') {
|
return availableSerials.value.length > 0;
|
||||||
return availableSerials.value.length >= props.quantity;
|
|
||||||
}
|
|
||||||
return selectedSerials.value.length === props.quantity;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const canConfirm = computed(() => {
|
const canConfirm = computed(() => {
|
||||||
if (selectionMode.value === 'auto') {
|
return selectedSerials.value.length > 0;
|
||||||
return availableSerials.value.length >= props.quantity;
|
|
||||||
}
|
|
||||||
return selectedSerials.value.length === props.quantity;
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasEnoughStock = computed(() => {
|
|
||||||
return availableSerials.value.length >= props.quantity;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Métodos */
|
/** Métodos */
|
||||||
@ -87,11 +72,7 @@ const toggleSerial = (serial) => {
|
|||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
selectedSerials.value.splice(index, 1);
|
selectedSerials.value.splice(index, 1);
|
||||||
} else {
|
} else {
|
||||||
if (selectedSerials.value.length < props.quantity) {
|
selectedSerials.value.push(serial);
|
||||||
selectedSerials.value.push(serial);
|
|
||||||
} else {
|
|
||||||
Notify.warning(`Solo puedes seleccionar ${props.quantity} serial(es)`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -100,7 +81,7 @@ const isSelected = (serial) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selectAll = () => {
|
const selectAll = () => {
|
||||||
selectedSerials.value = availableSerials.value.slice(0, props.quantity);
|
selectedSerials.value = [...availableSerials.value];
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearSelection = () => {
|
const clearSelection = () => {
|
||||||
@ -108,19 +89,9 @@ const clearSelection = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
let serialNumbers = [];
|
|
||||||
|
|
||||||
if (selectionMode.value === 'auto') {
|
|
||||||
// En modo automático, el backend asignará los seriales
|
|
||||||
serialNumbers = null;
|
|
||||||
} else {
|
|
||||||
// En modo manual, enviamos los seriales seleccionados
|
|
||||||
serialNumbers = selectedSerials.value.map(s => s.serial_number);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit('confirm', {
|
emit('confirm', {
|
||||||
selectionMode: selectionMode.value,
|
serialNumbers: selectedSerials.value.map(s => s.serial_number),
|
||||||
serialNumbers: serialNumbers
|
quantity: selectedSerials.value.length
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -131,7 +102,6 @@ const handleClose = () => {
|
|||||||
const resetState = () => {
|
const resetState = () => {
|
||||||
selectedSerials.value = [];
|
selectedSerials.value = [];
|
||||||
searchQuery.value = '';
|
searchQuery.value = '';
|
||||||
selectionMode.value = 'auto';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Watchers */
|
/** Watchers */
|
||||||
@ -140,13 +110,7 @@ watch(() => props.show, (isShown) => {
|
|||||||
resetState();
|
resetState();
|
||||||
loadSerials();
|
loadSerials();
|
||||||
}
|
}
|
||||||
});
|
},{ immediate: true });
|
||||||
|
|
||||||
watch(selectionMode, (newMode) => {
|
|
||||||
if (newMode === 'auto') {
|
|
||||||
selectedSerials.value = [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -163,7 +127,7 @@ watch(selectionMode, (newMode) => {
|
|||||||
Seleccionar Números de Serie
|
Seleccionar Números de Serie
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
{{ product.name }} - Cantidad: {{ quantity }}
|
{{ product.name }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -199,66 +163,9 @@ watch(selectionMode, (newMode) => {
|
|||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div v-else class="space-y-6">
|
<div v-else class="space-y-6">
|
||||||
<!-- Modo de selección -->
|
|
||||||
<div>
|
|
||||||
<h4 class="text-sm font-bold text-gray-700 dark:text-gray-300 mb-3">
|
|
||||||
Modo de asignación
|
|
||||||
</h4>
|
|
||||||
<div class="grid grid-cols-2 gap-3">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="relative flex items-center gap-3 p-4 rounded-xl border-2 transition-all"
|
|
||||||
:class="{
|
|
||||||
'border-emerald-500 bg-emerald-50 dark:bg-emerald-900/20 ring-2 ring-emerald-500': selectionMode === 'auto',
|
|
||||||
'border-gray-200 dark:border-gray-700 hover:border-gray-300': selectionMode !== 'auto'
|
|
||||||
}"
|
|
||||||
@click="selectionMode = 'auto'"
|
|
||||||
>
|
|
||||||
<div class="w-10 h-10 rounded-lg bg-emerald-500 flex items-center justify-center">
|
|
||||||
<GoogleIcon name="auto_awesome" class="text-xl text-white" />
|
|
||||||
</div>
|
|
||||||
<div class="text-left">
|
|
||||||
<p class="text-sm font-bold text-gray-800 dark:text-gray-200">
|
|
||||||
Automático
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
Asignar primeros disponibles
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div v-if="selectionMode === 'auto'" class="absolute top-2 right-2">
|
|
||||||
<GoogleIcon name="check_circle" class="text-emerald-500" />
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="relative flex items-center gap-3 p-4 rounded-xl border-2 transition-all"
|
|
||||||
:class="{
|
|
||||||
'border-indigo-500 bg-indigo-50 dark:bg-indigo-900/20 ring-2 ring-indigo-500': selectionMode === 'manual',
|
|
||||||
'border-gray-200 dark:border-gray-700 hover:border-gray-300': selectionMode !== 'manual'
|
|
||||||
}"
|
|
||||||
@click="selectionMode = 'manual'"
|
|
||||||
>
|
|
||||||
<div class="w-10 h-10 rounded-lg bg-indigo-500 flex items-center justify-center">
|
|
||||||
<GoogleIcon name="touch_app" class="text-xl text-white" />
|
|
||||||
</div>
|
|
||||||
<div class="text-left">
|
|
||||||
<p class="text-sm font-bold text-gray-800 dark:text-gray-200">
|
|
||||||
Manual
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
Elegir seriales específicos
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div v-if="selectionMode === 'manual'" class="absolute top-2 right-2">
|
|
||||||
<GoogleIcon name="check_circle" class="text-indigo-500" />
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Selección manual -->
|
<!-- Selección manual -->
|
||||||
<div v-if="selectionMode === 'manual'" class="space-y-4">
|
<div class="space-y-4">
|
||||||
<!-- Buscador y acciones -->
|
<!-- Buscador y acciones -->
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="flex-1 relative">
|
<div class="flex-1 relative">
|
||||||
@ -274,11 +181,11 @@ watch(selectionMode, (newMode) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-if="selectedCount < quantity"
|
v-if="selectedCount < availableSerials.length"
|
||||||
@click="selectAll"
|
@click="selectAll"
|
||||||
class="px-3 py-2 text-xs font-medium text-indigo-600 bg-indigo-50 rounded-lg hover:bg-indigo-100 transition-colors"
|
class="px-3 py-2 text-xs font-medium text-indigo-600 bg-indigo-50 rounded-lg hover:bg-indigo-100 transition-colors"
|
||||||
>
|
>
|
||||||
Seleccionar primeros {{ quantity }}
|
Seleccionar todos
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="selectedCount > 0"
|
v-if="selectedCount > 0"
|
||||||
@ -297,12 +204,11 @@ watch(selectionMode, (newMode) => {
|
|||||||
<span
|
<span
|
||||||
class="font-semibold"
|
class="font-semibold"
|
||||||
:class="{
|
:class="{
|
||||||
'text-green-600': selectedCount === quantity,
|
'text-green-600': selectedCount > 0,
|
||||||
'text-amber-600': selectedCount > 0 && selectedCount < quantity,
|
|
||||||
'text-gray-500': selectedCount === 0
|
'text-gray-500': selectedCount === 0
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ selectedCount }} / {{ quantity }} seleccionado(s)
|
{{ selectedCount }} seleccionado(s)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -349,21 +255,6 @@ watch(selectionMode, (newMode) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Info modo automático -->
|
|
||||||
<div v-else class="bg-emerald-50 dark:bg-emerald-900/20 border border-emerald-200 dark:border-emerald-800 rounded-lg p-4">
|
|
||||||
<div class="flex gap-3">
|
|
||||||
<GoogleIcon name="info" class="text-emerald-600 dark:text-emerald-400 text-xl shrink-0" />
|
|
||||||
<div>
|
|
||||||
<p class="text-sm font-semibold text-emerald-800 dark:text-emerald-300">
|
|
||||||
Asignación automática
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-emerald-700 dark:text-emerald-400 mt-1">
|
|
||||||
Se asignarán automáticamente los primeros {{ quantity }} número(s) de serie disponible(s) al confirmar la venta.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
|
|||||||
@ -154,51 +154,6 @@ const confirmDelete = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Importación masiva
|
|
||||||
const openBulkModal = () => {
|
|
||||||
bulkSerials.value = '';
|
|
||||||
showBulkModal.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeBulkModal = () => {
|
|
||||||
showBulkModal.value = false;
|
|
||||||
bulkSerials.value = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const bulkImport = async () => {
|
|
||||||
const lines = bulkSerials.value
|
|
||||||
.split('\n')
|
|
||||||
.map(line => line.trim())
|
|
||||||
.filter(line => line.length > 0);
|
|
||||||
|
|
||||||
if (lines.length === 0) {
|
|
||||||
Notify.warning('Ingresa al menos un número de serie');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bulkProcessing.value = true;
|
|
||||||
try {
|
|
||||||
const response = await serialService.bulkImport(inventoryId.value, lines);
|
|
||||||
Notify.success(`${response.count || lines.length} números de serie importados`);
|
|
||||||
if (response.inventory) {
|
|
||||||
inventory.value = response.inventory;
|
|
||||||
}
|
|
||||||
closeBulkModal();
|
|
||||||
loadSerials();
|
|
||||||
} catch (error) {
|
|
||||||
Notify.error('Error al importar números de serie');
|
|
||||||
} finally {
|
|
||||||
bulkProcessing.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const bulkCount = computed(() => {
|
|
||||||
return bulkSerials.value
|
|
||||||
.split('\n')
|
|
||||||
.map(line => line.trim())
|
|
||||||
.filter(line => line.length > 0).length;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Navegación
|
// Navegación
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
router.push({ name: 'pos.inventory.index' });
|
router.push({ name: 'pos.inventory.index' });
|
||||||
@ -276,15 +231,6 @@ onMounted(() => {
|
|||||||
<option value="disponible">Disponible</option>
|
<option value="disponible">Disponible</option>
|
||||||
<option value="vendido">Vendido</option>
|
<option value="vendido">Vendido</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<button
|
|
||||||
class="flex items-center gap-2 px-3 py-2 bg-green-600 hover:bg-green-700 text-white text-sm font-semibold rounded-lg transition-colors shadow-sm"
|
|
||||||
@click="openBulkModal"
|
|
||||||
title="Importar múltiples números de serie"
|
|
||||||
>
|
|
||||||
<GoogleIcon name="upload" class="text-xl" />
|
|
||||||
Importar
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
class="flex items-center gap-2 px-3 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-semibold rounded-lg transition-colors shadow-sm"
|
class="flex items-center gap-2 px-3 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-semibold rounded-lg transition-colors shadow-sm"
|
||||||
@click="openCreateModal"
|
@click="openCreateModal"
|
||||||
@ -360,7 +306,7 @@ onMounted(() => {
|
|||||||
class="text-gray-400 text-xs"
|
class="text-gray-400 text-xs"
|
||||||
title="No se puede editar/eliminar un serial vendido"
|
title="No se puede editar/eliminar un serial vendido"
|
||||||
>
|
>
|
||||||
Venta #{{ serial.sale_detail_id }}
|
Venta {{ serial.sale_detail?.sale?.invoice_number || `#${serial.sale_detail_id}` }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -554,56 +500,5 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<!-- Modal Importar Múltiples -->
|
|
||||||
<Modal :show="showBulkModal" max-width="md" @close="closeBulkModal">
|
|
||||||
<div class="p-6">
|
|
||||||
<div class="flex items-center justify-between mb-6">
|
|
||||||
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100">
|
|
||||||
Importar Números de Serie
|
|
||||||
</h3>
|
|
||||||
<button
|
|
||||||
@click="closeBulkModal"
|
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
|
||||||
>
|
|
||||||
<GoogleIcon name="close" class="text-xl" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label class="block text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase mb-1.5">
|
|
||||||
NÚMEROS DE SERIE (UNO POR LÍNEA)
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
v-model="bulkSerials"
|
|
||||||
class="w-full px-3 py-2 text-sm font-mono border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100"
|
|
||||||
rows="10"
|
|
||||||
placeholder="ABC123456789 DEF987654321 GHI456123789 ..."
|
|
||||||
></textarea>
|
|
||||||
<p class="text-xs text-gray-500 mt-1">
|
|
||||||
{{ bulkCount }} número(s) de serie detectado(s)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-end gap-3 pt-4">
|
|
||||||
<button
|
|
||||||
@click="closeBulkModal"
|
|
||||||
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
|
||||||
>
|
|
||||||
Cancelar
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="bulkImport"
|
|
||||||
:disabled="bulkProcessing || bulkCount === 0"
|
|
||||||
class="px-4 py-2 text-sm font-semibold text-white bg-green-600 rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
||||||
>
|
|
||||||
<span v-if="bulkProcessing">Importando...</span>
|
|
||||||
<span v-else>Importar {{ bulkCount }} serial(es)</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -34,7 +34,6 @@ const lastSaleData = ref(null);
|
|||||||
// Estado para selector de seriales
|
// Estado para selector de seriales
|
||||||
const showSerialSelector = ref(false);
|
const showSerialSelector = ref(false);
|
||||||
const serialSelectorProduct = ref(null);
|
const serialSelectorProduct = ref(null);
|
||||||
const serialSelectorQuantity = ref(1);
|
|
||||||
|
|
||||||
/** Buscador de productos */
|
/** Buscador de productos */
|
||||||
const searcher = useSearcher({
|
const searcher = useSearcher({
|
||||||
@ -69,7 +68,6 @@ const addToCart = (product) => {
|
|||||||
// Si el producto tiene seriales, mostrar selector
|
// Si el producto tiene seriales, mostrar selector
|
||||||
if (product.has_serials) {
|
if (product.has_serials) {
|
||||||
serialSelectorProduct.value = product;
|
serialSelectorProduct.value = product;
|
||||||
serialSelectorQuantity.value = 1;
|
|
||||||
showSerialSelector.value = true;
|
showSerialSelector.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -84,7 +82,6 @@ const addToCart = (product) => {
|
|||||||
const closeSerialSelector = () => {
|
const closeSerialSelector = () => {
|
||||||
showSerialSelector.value = false;
|
showSerialSelector.value = false;
|
||||||
serialSelectorProduct.value = null;
|
serialSelectorProduct.value = null;
|
||||||
serialSelectorQuantity.value = 1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSerialConfirm = (serialConfig) => {
|
const handleSerialConfirm = (serialConfig) => {
|
||||||
@ -92,7 +89,7 @@ const handleSerialConfirm = (serialConfig) => {
|
|||||||
|
|
||||||
cart.addProductWithSerials(
|
cart.addProductWithSerials(
|
||||||
serialSelectorProduct.value,
|
serialSelectorProduct.value,
|
||||||
serialSelectorQuantity.value,
|
serialConfig.quantity,
|
||||||
serialConfig
|
serialConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -468,7 +465,6 @@ onMounted(() => {
|
|||||||
:show="showClientModal"
|
:show="showClientModal"
|
||||||
:sale-data="lastSaleData"
|
:sale-data="lastSaleData"
|
||||||
@close="closeClientModal"
|
@close="closeClientModal"
|
||||||
@save="handleClientSave"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Modal de Selección de Seriales -->
|
<!-- Modal de Selección de Seriales -->
|
||||||
@ -476,7 +472,6 @@ onMounted(() => {
|
|||||||
v-if="serialSelectorProduct"
|
v-if="serialSelectorProduct"
|
||||||
:show="showSerialSelector"
|
:show="showSerialSelector"
|
||||||
:product="serialSelectorProduct"
|
:product="serialSelectorProduct"
|
||||||
:quantity="serialSelectorQuantity"
|
|
||||||
:exclude-serials="cart.getSelectedSerials()"
|
:exclude-serials="cart.getSelectedSerials()"
|
||||||
@close="closeSerialSelector"
|
@close="closeSerialSelector"
|
||||||
@confirm="handleSerialConfirm"
|
@confirm="handleSerialConfirm"
|
||||||
|
|||||||
@ -229,8 +229,9 @@ watch(() => props.show, () => {
|
|||||||
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
{{ item.product_name }}
|
{{ item.product_name }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="item.inventory?.sku" class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
<!-- Muestra los seriales si existen -->
|
||||||
SKU: {{ item.inventory.sku }}
|
<p v-if="item.serials && item.serials.length > 0" class="text-xs text-gray-500 dark:text-gray-400 mt-0.5 font-mono">
|
||||||
|
{{ item.serials.map(s => s.serial_number).join(', ') }}
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-center">
|
<td class="px-4 py-3 text-center">
|
||||||
|
|||||||
@ -74,22 +74,28 @@ const useCart = defineStore('cart', {
|
|||||||
|
|
||||||
// Agregar producto con seriales ya configurados
|
// Agregar producto con seriales ya configurados
|
||||||
addProductWithSerials(product, quantity, serialConfig) {
|
addProductWithSerials(product, quantity, serialConfig) {
|
||||||
// Eliminar item existente si hay
|
const existingItem = this.items.find(item => item.inventory_id === product.id);
|
||||||
this.removeProduct(product.id);
|
const newSerials = serialConfig.serialNumbers || [];
|
||||||
|
|
||||||
// Agregar nuevo item con seriales
|
if (existingItem) {
|
||||||
this.items.push({
|
// Combinar seriales existentes con los nuevos
|
||||||
inventory_id: product.id,
|
const combinedSerials = [...existingItem.serial_numbers, ...newSerials];
|
||||||
product_name: product.name,
|
existingItem.serial_numbers = combinedSerials;
|
||||||
sku: product.sku,
|
existingItem.quantity = combinedSerials.length;
|
||||||
quantity: quantity,
|
} else {
|
||||||
unit_price: parseFloat(product.price?.retail_price || 0),
|
// Agregar nuevo item con seriales
|
||||||
tax_rate: parseFloat(product.price?.tax || 16),
|
this.items.push({
|
||||||
max_stock: product.stock,
|
inventory_id: product.id,
|
||||||
has_serials: true,
|
product_name: product.name,
|
||||||
serial_numbers: serialConfig.serialNumbers || [],
|
sku: product.sku,
|
||||||
serial_selection_mode: serialConfig.selectionMode
|
quantity: quantity,
|
||||||
});
|
unit_price: parseFloat(product.price?.retail_price || 0),
|
||||||
|
tax_rate: parseFloat(product.price?.tax || 16),
|
||||||
|
max_stock: product.stock,
|
||||||
|
has_serials: true,
|
||||||
|
serial_numbers: newSerials
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Actualizar seriales de un item
|
// Actualizar seriales de un item
|
||||||
|
|||||||
@ -29,7 +29,10 @@ export const formatCurrency = (amount) => {
|
|||||||
* formatMoney(null) // "0.00"
|
* formatMoney(null) // "0.00"
|
||||||
*/
|
*/
|
||||||
export const formatMoney = (amount) => {
|
export const formatMoney = (amount) => {
|
||||||
return parseFloat(amount || 0).toFixed(2);
|
return new Intl.NumberFormat('es-MX', {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2
|
||||||
|
}).format(amount || 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user