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: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
allowRemove: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -133,17 +137,26 @@ watch(() => props.modelValue, (val) => {
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Badge vendido -->
|
<!-- Badge estado -->
|
||||||
<span
|
<span
|
||||||
v-if="item.locked"
|
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>
|
</span>
|
||||||
|
|
||||||
<!-- Botón eliminar -->
|
<!-- Botón eliminar -->
|
||||||
<button
|
<button
|
||||||
v-if="!item.locked && !props.disabled"
|
v-if="!item.locked && !props.disabled && props.allowRemove"
|
||||||
type="button"
|
type="button"
|
||||||
@click="removeSerial(index)"
|
@click="removeSerial(index)"
|
||||||
class="shrink-0 p-1 text-gray-400 hover:text-red-500 transition-colors"
|
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 -->
|
<!-- Botón agregar -->
|
||||||
<button
|
<button
|
||||||
v-if="!props.disabled"
|
v-if="!props.disabled && props.allowRemove"
|
||||||
type="button"
|
type="button"
|
||||||
@click="addAndFocus"
|
@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"
|
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 = () => {
|
const validateSerialsAndUnit = () => {
|
||||||
if (form.track_serials && selectedUnit.value && selectedUnit.value.allows_decimals) {
|
if (!selectedUnit.value) return;
|
||||||
Notify.warning('No se pueden usar números de serie con esta unidad de medida.');
|
|
||||||
|
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;
|
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"
|
v-model="form.barcode"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="1234567890123"
|
placeholder="1234567890123"
|
||||||
maxlength="100"
|
maxlength="14"
|
||||||
/>
|
/>
|
||||||
<FormError :message="form.errors?.barcode" />
|
<FormError :message="form.errors?.barcode" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -134,9 +134,20 @@ const loadEquivalences = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const validateSerialsAndUnit = () => {
|
const validateSerialsAndUnit = () => {
|
||||||
if (form.track_serials && selectedUnit.value && selectedUnit.value.allows_decimals) {
|
if (!selectedUnit.value) return;
|
||||||
Notify.warning('No se pueden usar números de serie con esta unidad de medida.');
|
|
||||||
|
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;
|
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();
|
validateSerialsAndUnit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -222,12 +222,40 @@ watch(() => props.show, (isShown) => {
|
|||||||
form.notes = props.movement.notes || '';
|
form.notes = props.movement.notes || '';
|
||||||
form.supplier_id = props.movement.supplier_id || null;
|
form.supplier_id = props.movement.supplier_id || null;
|
||||||
|
|
||||||
// Cargar números de serie si existen
|
// Cargar números de serie según tipo de movimiento
|
||||||
if (props.movement.serials && props.movement.serials.length > 0) {
|
const isTransfer = props.movement.movement_type === 'transfer';
|
||||||
serialsList.value = props.movement.serials.map(s => ({
|
const isExit = props.movement.movement_type === 'exit';
|
||||||
serial_number: s.serial_number,
|
const rawSerials = isTransfer
|
||||||
locked: s.status === 'vendido'
|
? (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) {
|
} else if (props.movement.serial_numbers && props.movement.serial_numbers.length > 0) {
|
||||||
serialsList.value = props.movement.serial_numbers.map(sn => ({
|
serialsList.value = props.movement.serial_numbers.map(sn => ({
|
||||||
serial_number: sn,
|
serial_number: sn,
|
||||||
@ -241,10 +269,10 @@ watch(() => props.show, (isShown) => {
|
|||||||
if (props.movement.movement_type === 'entry') {
|
if (props.movement.movement_type === 'entry') {
|
||||||
form.destination_warehouse_id = props.movement.warehouse_id || '';
|
form.destination_warehouse_id = props.movement.warehouse_id || '';
|
||||||
} else if (props.movement.movement_type === 'exit') {
|
} 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') {
|
} else if (props.movement.movement_type === 'transfer') {
|
||||||
form.origin_warehouse_id = props.movement.origin_warehouse_id || '';
|
form.origin_warehouse_id = props.movement.warehouse_from_id || props.movement.warehouse_from?.id || '';
|
||||||
form.destination_warehouse_id = props.movement.destination_warehouse_id || '';
|
form.destination_warehouse_id = props.movement.warehouse_to_id || props.movement.warehouse_to?.id || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -337,7 +365,11 @@ watch(() => props.show, (isShown) => {
|
|||||||
NÚMEROS DE SERIE
|
NÚMEROS DE SERIE
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</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 -->
|
<!-- Validación -->
|
||||||
<div class="mt-2 flex items-center justify-between">
|
<div class="mt-2 flex items-center justify-between">
|
||||||
@ -410,7 +442,7 @@ watch(() => props.show, (isShown) => {
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
v-model="form.origin_warehouse_id"
|
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
|
required
|
||||||
>
|
>
|
||||||
<option value="">Seleccionar almacén...</option>
|
<option value="">Seleccionar almacén...</option>
|
||||||
@ -429,7 +461,8 @@ watch(() => props.show, (isShown) => {
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
v-model="form.origin_warehouse_id"
|
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
|
required
|
||||||
>
|
>
|
||||||
<option value="">Seleccionar...</option>
|
<option value="">Seleccionar...</option>
|
||||||
@ -437,7 +470,6 @@ watch(() => props.show, (isShown) => {
|
|||||||
v-for="wh in warehouses"
|
v-for="wh in warehouses"
|
||||||
:key="wh.id"
|
:key="wh.id"
|
||||||
:value="wh.id"
|
:value="wh.id"
|
||||||
:disabled="wh.id === form.destination_warehouse_id"
|
|
||||||
>
|
>
|
||||||
{{ wh.name }} ({{ wh.code }})
|
{{ wh.name }} ({{ wh.code }})
|
||||||
</option>
|
</option>
|
||||||
@ -450,8 +482,9 @@ watch(() => props.show, (isShown) => {
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
v-model="form.destination_warehouse_id"
|
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
|
required
|
||||||
|
disabled
|
||||||
>
|
>
|
||||||
<option value="">Seleccionar...</option>
|
<option value="">Seleccionar...</option>
|
||||||
<option
|
<option
|
||||||
@ -469,21 +502,14 @@ watch(() => props.show, (isShown) => {
|
|||||||
|
|
||||||
<!-- Proveedor y Referencia (solo para entradas) -->
|
<!-- Proveedor y Referencia (solo para entradas) -->
|
||||||
<div v-if="movement?.movement_type === 'entry'" class="space-y-4">
|
<div v-if="movement?.movement_type === 'entry'" class="space-y-4">
|
||||||
<!-- Proveedor -->
|
<!-- Proveedor (solo lectura) -->
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase mb-1.5">
|
<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>
|
</label>
|
||||||
<select
|
<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">
|
||||||
v-model="form.supplier_id"
|
{{ movement?.supplier?.business_name || 'Sin proveedor' }}
|
||||||
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"
|
</div>
|
||||||
>
|
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Referencia de factura -->
|
<!-- Referencia de factura -->
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user