feat: agregar opción para permitir eliminación de seriales y mejorar gestión de estado en componentes
This commit is contained in:
parent
44d86af459
commit
b2dea0785e
@ -10,6 +10,10 @@ const props = defineProps({
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
allowRemove: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
@ -133,17 +137,26 @@ watch(() => props.modelValue, (val) => {
|
||||
}"
|
||||
/>
|
||||
|
||||
<!-- Badge vendido -->
|
||||
<!-- Badge estado -->
|
||||
<span
|
||||
v-if="item.locked"
|
||||
class="shrink-0 text-xs px-1.5 py-0.5 rounded bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
|
||||
class="shrink-0 text-xs px-1.5 py-0.5 rounded"
|
||||
:class="item.lock_reason === 'traspasado'
|
||||
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
: item.lock_reason === 'salida'
|
||||
? 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||
: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'"
|
||||
>
|
||||
Vendido
|
||||
{{
|
||||
item.lock_reason === 'traspasado' ? 'Traspasado' :
|
||||
item.lock_reason === 'salida' ? 'Salida' :
|
||||
'Vendido'
|
||||
}}
|
||||
</span>
|
||||
|
||||
<!-- Botón eliminar -->
|
||||
<button
|
||||
v-if="!item.locked && !props.disabled"
|
||||
v-if="!item.locked && !props.disabled && props.allowRemove"
|
||||
type="button"
|
||||
@click="removeSerial(index)"
|
||||
class="shrink-0 p-1 text-gray-400 hover:text-red-500 transition-colors"
|
||||
@ -156,7 +169,7 @@ watch(() => props.modelValue, (val) => {
|
||||
|
||||
<!-- Botón agregar -->
|
||||
<button
|
||||
v-if="!props.disabled"
|
||||
v-if="!props.disabled && props.allowRemove"
|
||||
type="button"
|
||||
@click="addAndFocus"
|
||||
class="flex items-center gap-1.5 text-xs text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300 transition-colors py-1"
|
||||
|
||||
@ -84,9 +84,20 @@ const loadUnits = async () => {
|
||||
};
|
||||
|
||||
const validateSerialsAndUnit = () => {
|
||||
if (form.track_serials && selectedUnit.value && selectedUnit.value.allows_decimals) {
|
||||
Notify.warning('No se pueden usar números de serie con esta unidad de medida.');
|
||||
if (!selectedUnit.value) return;
|
||||
|
||||
if (selectedUnit.value.allows_decimals) {
|
||||
if (form.track_serials) {
|
||||
Notify.warning('No se pueden usar números de serie con esta unidad de medida.');
|
||||
}
|
||||
form.track_serials = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const unitName = (selectedUnit.value.name || '').toLowerCase();
|
||||
const unitAbbr = (selectedUnit.value.abbreviation || '').toLowerCase();
|
||||
if (unitName.includes('serial') || unitAbbr.includes('serial')) {
|
||||
form.track_serials = true;
|
||||
}
|
||||
};
|
||||
|
||||
@ -200,7 +211,7 @@ watch(() => form.track_serials, () => {
|
||||
v-model="form.barcode"
|
||||
type="text"
|
||||
placeholder="1234567890123"
|
||||
maxlength="100"
|
||||
maxlength="14"
|
||||
/>
|
||||
<FormError :message="form.errors?.barcode" />
|
||||
</div>
|
||||
|
||||
@ -134,9 +134,20 @@ const loadEquivalences = () => {
|
||||
};
|
||||
|
||||
const validateSerialsAndUnit = () => {
|
||||
if (form.track_serials && selectedUnit.value && selectedUnit.value.allows_decimals) {
|
||||
Notify.warning('No se pueden usar números de serie con esta unidad de medida.');
|
||||
if (!selectedUnit.value) return;
|
||||
|
||||
if (selectedUnit.value.allows_decimals) {
|
||||
if (form.track_serials) {
|
||||
Notify.warning('No se pueden usar números de serie con esta unidad de medida.');
|
||||
}
|
||||
form.track_serials = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const unitName = (selectedUnit.value.name || '').toLowerCase();
|
||||
const unitAbbr = (selectedUnit.value.abbreviation || '').toLowerCase();
|
||||
if (unitName.includes('serial') || unitAbbr.includes('serial')) {
|
||||
form.track_serials = true;
|
||||
}
|
||||
};
|
||||
|
||||
@ -282,7 +293,7 @@ watch(() => props.show, (newValue) => {
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => form.unit_of_measure_id, () => {
|
||||
watch(selectedUnit, () => {
|
||||
validateSerialsAndUnit();
|
||||
});
|
||||
|
||||
|
||||
@ -222,12 +222,40 @@ watch(() => props.show, (isShown) => {
|
||||
form.notes = props.movement.notes || '';
|
||||
form.supplier_id = props.movement.supplier_id || null;
|
||||
|
||||
// Cargar números de serie si existen
|
||||
if (props.movement.serials && props.movement.serials.length > 0) {
|
||||
serialsList.value = props.movement.serials.map(s => ({
|
||||
serial_number: s.serial_number,
|
||||
locked: s.status === 'vendido'
|
||||
}));
|
||||
// Cargar números de serie según tipo de movimiento
|
||||
const isTransfer = props.movement.movement_type === 'transfer';
|
||||
const isExit = props.movement.movement_type === 'exit';
|
||||
const rawSerials = isTransfer
|
||||
? (props.movement.transferred_serials || [])
|
||||
: isExit
|
||||
? (props.movement.exited_serials || [])
|
||||
: (props.movement.serials || []);
|
||||
|
||||
if (rawSerials.length > 0) {
|
||||
const movementWarehouseId = props.movement.warehouse_id || props.movement.warehouse_to?.id;
|
||||
|
||||
serialsList.value = rawSerials.map(s => {
|
||||
let locked = false;
|
||||
let lock_reason = null;
|
||||
|
||||
// Seriales con status='salida' son propios de este movimiento → editables
|
||||
const isOwnExitSerial = isExit && s.status === 'salida';
|
||||
|
||||
if (!isOwnExitSerial && s.status !== 'disponible') {
|
||||
locked = true;
|
||||
lock_reason = s.status;
|
||||
} else if (
|
||||
!isTransfer && !isExit &&
|
||||
s.warehouse_id &&
|
||||
movementWarehouseId &&
|
||||
s.warehouse_id !== movementWarehouseId
|
||||
) {
|
||||
locked = true;
|
||||
lock_reason = 'traspasado';
|
||||
}
|
||||
|
||||
return { serial_number: s.serial_number, locked, lock_reason };
|
||||
});
|
||||
} else if (props.movement.serial_numbers && props.movement.serial_numbers.length > 0) {
|
||||
serialsList.value = props.movement.serial_numbers.map(sn => ({
|
||||
serial_number: sn,
|
||||
@ -241,10 +269,10 @@ watch(() => props.show, (isShown) => {
|
||||
if (props.movement.movement_type === 'entry') {
|
||||
form.destination_warehouse_id = props.movement.warehouse_id || '';
|
||||
} else if (props.movement.movement_type === 'exit') {
|
||||
form.origin_warehouse_id = props.movement.warehouse_id || '';
|
||||
form.origin_warehouse_id = props.movement.warehouse_from_id || props.movement.warehouse_from?.id || '';
|
||||
} else if (props.movement.movement_type === 'transfer') {
|
||||
form.origin_warehouse_id = props.movement.origin_warehouse_id || '';
|
||||
form.destination_warehouse_id = props.movement.destination_warehouse_id || '';
|
||||
form.origin_warehouse_id = props.movement.warehouse_from_id || props.movement.warehouse_from?.id || '';
|
||||
form.destination_warehouse_id = props.movement.warehouse_to_id || props.movement.warehouse_to?.id || '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -337,7 +365,11 @@ watch(() => props.show, (isShown) => {
|
||||
NÚMEROS DE SERIE
|
||||
</label>
|
||||
</div>
|
||||
<SerialInputList v-model="serialsList" @update:model-value="updateQuantityFromSerials" />
|
||||
<SerialInputList
|
||||
v-model="serialsList"
|
||||
:allow-remove="movement?.movement_type !== 'transfer'"
|
||||
@update:model-value="updateQuantityFromSerials"
|
||||
/>
|
||||
|
||||
<!-- Validación -->
|
||||
<div class="mt-2 flex items-center justify-between">
|
||||
@ -410,7 +442,7 @@ watch(() => props.show, (isShown) => {
|
||||
</label>
|
||||
<select
|
||||
v-model="form.origin_warehouse_id"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
required
|
||||
>
|
||||
<option value="">Seleccionar almacén...</option>
|
||||
@ -429,7 +461,8 @@ watch(() => props.show, (isShown) => {
|
||||
</label>
|
||||
<select
|
||||
v-model="form.origin_warehouse_id"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled
|
||||
required
|
||||
>
|
||||
<option value="">Seleccionar...</option>
|
||||
@ -437,7 +470,6 @@ watch(() => props.show, (isShown) => {
|
||||
v-for="wh in warehouses"
|
||||
:key="wh.id"
|
||||
:value="wh.id"
|
||||
:disabled="wh.id === form.destination_warehouse_id"
|
||||
>
|
||||
{{ wh.name }} ({{ wh.code }})
|
||||
</option>
|
||||
@ -450,8 +482,9 @@ watch(() => props.show, (isShown) => {
|
||||
</label>
|
||||
<select
|
||||
v-model="form.destination_warehouse_id"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
required
|
||||
disabled
|
||||
>
|
||||
<option value="">Seleccionar...</option>
|
||||
<option
|
||||
@ -469,21 +502,14 @@ watch(() => props.show, (isShown) => {
|
||||
|
||||
<!-- Proveedor y Referencia (solo para entradas) -->
|
||||
<div v-if="movement?.movement_type === 'entry'" class="space-y-4">
|
||||
<!-- Proveedor -->
|
||||
<!-- Proveedor (solo lectura) -->
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase mb-1.5">
|
||||
PROVEEDOR <span class="text-gray-400 text-xs normal-case">(opcional)</span>
|
||||
PROVEEDOR
|
||||
</label>
|
||||
<select
|
||||
v-model="form.supplier_id"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
||||
>
|
||||
<option :value="null">Seleccionar proveedor...</option>
|
||||
<option v-for="supplier in suppliers" :key="supplier.id" :value="supplier.id">
|
||||
{{ supplier.business_name }}
|
||||
</option>
|
||||
</select>
|
||||
<FormError :message="form.errors?.supplier_id" />
|
||||
<div class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-900 text-sm text-gray-600 dark:text-gray-400 cursor-not-allowed">
|
||||
{{ movement?.supplier?.business_name || 'Sin proveedor' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Referencia de factura -->
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user