- Desglosa detalles de paquetes en la generación de tickets. - Mejora modales de edición y eliminación.
194 lines
7.2 KiB
Vue
194 lines
7.2 KiB
Vue
<script setup>
|
||
import { computed } from 'vue';
|
||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||
|
||
/** Props */
|
||
const props = defineProps({
|
||
item: {
|
||
type: Object,
|
||
required: true
|
||
}
|
||
});
|
||
|
||
/** Emits */
|
||
const emit = defineEmits(['update-quantity', 'remove', 'select-serials']);
|
||
|
||
/** Computados */
|
||
const formattedUnitPrice = computed(() => {
|
||
return new Intl.NumberFormat('es-MX', {
|
||
style: 'currency',
|
||
currency: 'MXN'
|
||
}).format(props.item.unit_price);
|
||
});
|
||
|
||
const formattedSubtotal = computed(() => {
|
||
const subtotal = props.item.unit_price * props.item.quantity;
|
||
return new Intl.NumberFormat('es-MX', {
|
||
style: 'currency',
|
||
currency: 'MXN'
|
||
}).format(subtotal);
|
||
});
|
||
|
||
const canIncrement = computed(() => {
|
||
// Si tiene seriales, no permitir incremento directo
|
||
if (props.item.track_serials) return false;
|
||
// Si permite decimales, incrementar en 1
|
||
const incrementValue = props.item.allows_decimals ? 1 : 1;
|
||
return (props.item.quantity + incrementValue) <= props.item.max_stock;
|
||
});
|
||
|
||
const canEditQuantity = computed(() => {
|
||
// No permitir edición directa si tiene seriales
|
||
return !props.item.track_serials;
|
||
});
|
||
|
||
const quantityStep = computed(() => {
|
||
return props.item.allows_decimals ? '0.001' : '1';
|
||
});
|
||
|
||
const quantityMin = computed(() => {
|
||
return props.item.allows_decimals ? '0.001' : '1';
|
||
});
|
||
|
||
const hasSerials = computed(() => props.item.track_serials);
|
||
const serialsSelected = computed(() => {
|
||
return props.item.serial_numbers && props.item.serial_numbers.length > 0;
|
||
});
|
||
|
||
const needsSerialSelection = computed(() => {
|
||
return hasSerials.value && (!serialsSelected.value || props.item.serial_numbers.length !== props.item.quantity);
|
||
});
|
||
|
||
/** Métodos */
|
||
const increment = () => {
|
||
if (canIncrement.value) {
|
||
const incrementValue = props.item.allows_decimals ? 1 : 1;
|
||
const newQuantity = props.item.quantity + incrementValue;
|
||
emit('update-quantity', props.item.item_key, newQuantity);
|
||
}
|
||
};
|
||
|
||
const decrement = () => {
|
||
const decrementValue = props.item.allows_decimals ? 1 : 1;
|
||
const minValue = props.item.allows_decimals ? 0.001 : 1;
|
||
|
||
if (props.item.quantity > minValue) {
|
||
const newQuantity = Math.max(minValue, props.item.quantity - decrementValue);
|
||
emit('update-quantity', props.item.item_key, newQuantity);
|
||
} else {
|
||
emit('remove', props.item.item_key);
|
||
}
|
||
};
|
||
|
||
const handleQuantityInput = (event) => {
|
||
const value = event.target.value;
|
||
const numValue = parseFloat(value);
|
||
|
||
if (!isNaN(numValue) && numValue > 0) {
|
||
emit('update-quantity', props.item.item_key, numValue);
|
||
}
|
||
};
|
||
|
||
const remove = () => {
|
||
emit('remove', props.item.item_key);
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-indigo-300 dark:hover:border-indigo-600 transition-colors p-3">
|
||
<!-- Fila principal: Nombre y botón eliminar -->
|
||
<div class="flex items-start justify-between mb-2">
|
||
<div class="flex items-center gap-1.5 pr-2 min-w-0">
|
||
<span
|
||
v-if="item.is_bundle"
|
||
class="shrink-0 px-1.5 py-0.5 text-xs font-bold rounded bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400"
|
||
>
|
||
KIT
|
||
</span>
|
||
<h4 class="text-sm font-semibold text-gray-800 dark:text-gray-200 line-clamp-2">
|
||
{{ item.product_name }}
|
||
</h4>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
class="shrink-0 w-6 h-6 flex items-center justify-center rounded-full bg-red-50 hover:bg-red-100 dark:bg-red-900/20 dark:hover:bg-red-900/30 text-red-600 dark:text-red-400 transition-colors"
|
||
@click="remove"
|
||
>
|
||
<GoogleIcon name="close" class="text-base" />
|
||
</button>
|
||
</div>
|
||
|
||
<!-- SKU y precio unitario -->
|
||
<div class="flex items-center gap-2 mb-1">
|
||
<span class="text-xs text-gray-500 dark:text-gray-400 font-mono">
|
||
{{ item.sku }}
|
||
</span>
|
||
<span class="text-xs text-gray-400 dark:text-gray-500">•</span>
|
||
<span class="text-xs font-medium text-indigo-600 dark:text-indigo-400">
|
||
{{ formattedUnitPrice }}
|
||
</span>
|
||
</div>
|
||
|
||
<!-- Mensaje para productos con decimales -->
|
||
<div v-if="item.allows_decimals && item.unit_of_measure" class="mb-2">
|
||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||
{{ item.unit_of_measure.name }} ({{ item.unit_of_measure.abbreviation }}) - Permite decimales
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Fila inferior: Controles de cantidad y subtotal -->
|
||
<div class="flex items-center justify-between">
|
||
<!-- Controles de cantidad -->
|
||
<div class="flex items-center gap-2">
|
||
<button
|
||
type="button"
|
||
class="w-7 h-7 flex items-center justify-center rounded-full bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-600 dark:text-gray-300 transition-colors"
|
||
@click="decrement"
|
||
>
|
||
<GoogleIcon
|
||
:name="item.quantity <= (item.allows_decimals ? 0.001 : 1) ? 'delete' : 'remove'"
|
||
class="text-base"
|
||
/>
|
||
</button>
|
||
|
||
<input
|
||
v-if="canEditQuantity"
|
||
type="number"
|
||
:value="item.quantity"
|
||
:min="quantityMin"
|
||
:step="quantityStep"
|
||
:max="item.max_stock"
|
||
@change="handleQuantityInput"
|
||
class="w-16 text-center text-base font-bold text-gray-800 dark:text-gray-200 bg-transparent border border-gray-300 dark:border-gray-600 rounded px-1 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
||
/>
|
||
<span v-else class="w-16 text-center text-base font-bold text-gray-800 dark:text-gray-200">
|
||
{{ item.quantity }}
|
||
</span>
|
||
|
||
<button
|
||
type="button"
|
||
class="w-7 h-7 flex items-center justify-center rounded-full transition-colors"
|
||
:class="{
|
||
'bg-indigo-100 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:hover:bg-indigo-900/50 text-indigo-600 dark:text-indigo-400': canIncrement,
|
||
'bg-gray-100 dark:bg-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed': !canIncrement
|
||
}"
|
||
:disabled="!canIncrement"
|
||
@click="increment"
|
||
>
|
||
<GoogleIcon name="add" class="text-base" />
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Subtotal -->
|
||
<div class="text-right">
|
||
<p class="text-lg font-bold text-gray-900 dark:text-gray-100">
|
||
{{ formattedSubtotal }}
|
||
</p>
|
||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||
{{ item.quantity }} × {{ formattedUnitPrice }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|