feat: agregar soporte para seriales transferidos y salidas en el control de movimientos de inventario
This commit is contained in:
parent
e5e3412fea
commit
1c21602b7e
@ -69,7 +69,7 @@ public function index(Request $request)
|
|||||||
*/
|
*/
|
||||||
public function show(int $id)
|
public function show(int $id)
|
||||||
{
|
{
|
||||||
$movement = InventoryMovement::with(['inventory', 'warehouseFrom', 'warehouseTo', 'user', 'supplier', 'serials'])
|
$movement = InventoryMovement::with(['inventory', 'warehouseFrom', 'warehouseTo', 'user', 'supplier', 'serials', 'transferredSerials', 'exitedSerials'])
|
||||||
->find($id);
|
->find($id);
|
||||||
|
|
||||||
if (!$movement) {
|
if (!$movement) {
|
||||||
@ -128,7 +128,7 @@ public function entry(InventoryEntryRequest $request)
|
|||||||
|
|
||||||
return ApiResponse::CREATED->response([
|
return ApiResponse::CREATED->response([
|
||||||
'message' => 'Entrada registrada correctamente',
|
'message' => 'Entrada registrada correctamente',
|
||||||
'movement' => $movement->load(['inventory', 'warehouseTo']),
|
'movement' => $movement->load(['inventory', 'warehouseTo', 'supplier']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ public function exit(InventoryExitRequest $request)
|
|||||||
|
|
||||||
return ApiResponse::CREATED->response([
|
return ApiResponse::CREATED->response([
|
||||||
'message' => 'Salida registrada correctamente',
|
'message' => 'Salida registrada correctamente',
|
||||||
'movement' => $movement->load(['inventory', 'warehouseFrom']),
|
'movement' => $movement->load(['inventory', 'warehouseFrom', 'exitedSerials']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@ -22,6 +22,7 @@ public function rules(): array
|
|||||||
'notes' => 'sometimes|nullable|string|max:500',
|
'notes' => 'sometimes|nullable|string|max:500',
|
||||||
'serial_numbers' => ['nullable', 'array'],
|
'serial_numbers' => ['nullable', 'array'],
|
||||||
'serial_numbers.*' => ['string', 'max:255'],
|
'serial_numbers.*' => ['string', 'max:255'],
|
||||||
|
'supplier_id' => 'sometimes|nullable|exists:suppliers,id',
|
||||||
'unit_of_measure_id' => 'sometimes|nullable|exists:units_of_measurement,id',
|
'unit_of_measure_id' => 'sometimes|nullable|exists:units_of_measurement,id',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,6 +76,16 @@ public function serials()
|
|||||||
return $this->hasMany(InventorySerial::class, 'movement_id');
|
return $this->hasMany(InventorySerial::class, 'movement_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function transferredSerials()
|
||||||
|
{
|
||||||
|
return $this->hasMany(InventorySerial::class, 'transfer_movement_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exitedSerials()
|
||||||
|
{
|
||||||
|
return $this->hasMany(InventorySerial::class, 'exit_movement_id');
|
||||||
|
}
|
||||||
|
|
||||||
// Relación polimórfica para la referencia
|
// Relación polimórfica para la referencia
|
||||||
public function reference()
|
public function reference()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -14,6 +14,8 @@ class InventorySerial extends Model
|
|||||||
'inventory_id',
|
'inventory_id',
|
||||||
'warehouse_id',
|
'warehouse_id',
|
||||||
'movement_id',
|
'movement_id',
|
||||||
|
'transfer_movement_id',
|
||||||
|
'exit_movement_id',
|
||||||
'serial_number',
|
'serial_number',
|
||||||
'status',
|
'status',
|
||||||
'sale_detail_id',
|
'sale_detail_id',
|
||||||
@ -40,6 +42,37 @@ public function movement()
|
|||||||
return $this->belongsTo(InventoryMovement::class);
|
return $this->belongsTo(InventoryMovement::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function transferMovement()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(InventoryMovement::class, 'transfer_movement_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exitMovement()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(InventoryMovement::class, 'exit_movement_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markAsExited(int $exitMovementId): void
|
||||||
|
{
|
||||||
|
$this->update([
|
||||||
|
'status' => 'salida',
|
||||||
|
'exit_movement_id' => $exitMovementId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restoreFromExit(): void
|
||||||
|
{
|
||||||
|
$this->update([
|
||||||
|
'status' => 'disponible',
|
||||||
|
'exit_movement_id' => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExited(): bool
|
||||||
|
{
|
||||||
|
return $this->status === 'salida';
|
||||||
|
}
|
||||||
|
|
||||||
public function saleDetail()
|
public function saleDetail()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(SaleDetail::class);
|
return $this->belongsTo(SaleDetail::class);
|
||||||
|
|||||||
@ -309,10 +309,26 @@ public function bulkExit(array $data): array
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eliminar los seriales (salida definitiva)
|
// Registrar movimiento PRIMERO para tener el ID disponible
|
||||||
|
$movement = InventoryMovement::create([
|
||||||
|
'inventory_id' => $inventory->id,
|
||||||
|
'warehouse_from_id' => $warehouse->id,
|
||||||
|
'warehouse_to_id' => null,
|
||||||
|
'movement_type' => 'exit',
|
||||||
|
'quantity' => $quantity,
|
||||||
|
'unit_of_measure_id' => $usesEquivalence ? $inputUnitId : null,
|
||||||
|
'unit_quantity' => $usesEquivalence ? $inputQuantity : null,
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'notes' => $data['notes'] ?? null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Marcar seriales como salida (no eliminar, para conservar historial)
|
||||||
InventorySerial::whereIn('serial_number', $serialNumbers)
|
InventorySerial::whereIn('serial_number', $serialNumbers)
|
||||||
->where('inventory_id', $inventory->id)
|
->where('inventory_id', $inventory->id)
|
||||||
->delete();
|
->update([
|
||||||
|
'status' => 'salida',
|
||||||
|
'exit_movement_id' => $movement->id,
|
||||||
|
]);
|
||||||
|
|
||||||
// Sincronizar stock desde seriales
|
// Sincronizar stock desde seriales
|
||||||
$inventory->syncStock();
|
$inventory->syncStock();
|
||||||
@ -320,20 +336,20 @@ public function bulkExit(array $data): array
|
|||||||
// Sin seriales, validar y decrementar stock manualmente
|
// Sin seriales, validar y decrementar stock manualmente
|
||||||
$this->validateStock($inventory->id, $warehouse->id, $quantity);
|
$this->validateStock($inventory->id, $warehouse->id, $quantity);
|
||||||
$this->updateWarehouseStock($inventory->id, $warehouse->id, -$quantity);
|
$this->updateWarehouseStock($inventory->id, $warehouse->id, -$quantity);
|
||||||
}
|
|
||||||
|
|
||||||
// Registrar movimiento
|
// Registrar movimiento
|
||||||
$movement = InventoryMovement::create([
|
$movement = InventoryMovement::create([
|
||||||
'inventory_id' => $inventory->id,
|
'inventory_id' => $inventory->id,
|
||||||
'warehouse_from_id' => $warehouse->id,
|
'warehouse_from_id' => $warehouse->id,
|
||||||
'warehouse_to_id' => null,
|
'warehouse_to_id' => null,
|
||||||
'movement_type' => 'exit',
|
'movement_type' => 'exit',
|
||||||
'quantity' => $quantity,
|
'quantity' => $quantity,
|
||||||
'unit_of_measure_id' => $usesEquivalence ? $inputUnitId : null,
|
'unit_of_measure_id' => $usesEquivalence ? $inputUnitId : null,
|
||||||
'unit_quantity' => $usesEquivalence ? $inputQuantity : null,
|
'unit_quantity' => $usesEquivalence ? $inputQuantity : null,
|
||||||
'user_id' => auth()->id(),
|
'user_id' => auth()->id(),
|
||||||
'notes' => $data['notes'] ?? null,
|
'notes' => $data['notes'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$movements[] = $movement->load(['inventory', 'warehouseFrom']);
|
$movements[] = $movement->load(['inventory', 'warehouseFrom']);
|
||||||
}
|
}
|
||||||
@ -408,10 +424,26 @@ public function bulkTransfer(array $data): array
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar warehouse_id de los seriales seleccionados
|
// Registrar movimiento PRIMERO para tener el ID disponible
|
||||||
|
$movement = InventoryMovement::create([
|
||||||
|
'inventory_id' => $inventory->id,
|
||||||
|
'warehouse_from_id' => $warehouseFrom->id,
|
||||||
|
'warehouse_to_id' => $warehouseTo->id,
|
||||||
|
'movement_type' => 'transfer',
|
||||||
|
'quantity' => $quantity,
|
||||||
|
'unit_of_measure_id' => $usesEquivalence ? $inputUnitId : null,
|
||||||
|
'unit_quantity' => $usesEquivalence ? $inputQuantity : null,
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'notes' => $data['notes'] ?? null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Actualizar warehouse_id y transfer_movement_id de los seriales seleccionados
|
||||||
InventorySerial::whereIn('serial_number', $serialNumbers)
|
InventorySerial::whereIn('serial_number', $serialNumbers)
|
||||||
->where('inventory_id', $inventory->id)
|
->where('inventory_id', $inventory->id)
|
||||||
->update(['warehouse_id' => $warehouseTo->id]);
|
->update([
|
||||||
|
'warehouse_id' => $warehouseTo->id,
|
||||||
|
'transfer_movement_id' => $movement->id,
|
||||||
|
]);
|
||||||
|
|
||||||
// Sincronizar stock desde seriales (actualiza ambos almacenes)
|
// Sincronizar stock desde seriales (actualiza ambos almacenes)
|
||||||
$inventory->syncStock();
|
$inventory->syncStock();
|
||||||
@ -420,20 +452,20 @@ public function bulkTransfer(array $data): array
|
|||||||
$this->validateStock($inventory->id, $warehouseFrom->id, $quantity);
|
$this->validateStock($inventory->id, $warehouseFrom->id, $quantity);
|
||||||
$this->updateWarehouseStock($inventory->id, $warehouseFrom->id, -$quantity);
|
$this->updateWarehouseStock($inventory->id, $warehouseFrom->id, -$quantity);
|
||||||
$this->updateWarehouseStock($inventory->id, $warehouseTo->id, $quantity);
|
$this->updateWarehouseStock($inventory->id, $warehouseTo->id, $quantity);
|
||||||
}
|
|
||||||
|
|
||||||
// Registrar movimiento
|
// Registrar movimiento
|
||||||
$movement = InventoryMovement::create([
|
$movement = InventoryMovement::create([
|
||||||
'inventory_id' => $inventory->id,
|
'inventory_id' => $inventory->id,
|
||||||
'warehouse_from_id' => $warehouseFrom->id,
|
'warehouse_from_id' => $warehouseFrom->id,
|
||||||
'warehouse_to_id' => $warehouseTo->id,
|
'warehouse_to_id' => $warehouseTo->id,
|
||||||
'movement_type' => 'transfer',
|
'movement_type' => 'transfer',
|
||||||
'quantity' => $quantity,
|
'quantity' => $quantity,
|
||||||
'unit_of_measure_id' => $usesEquivalence ? $inputUnitId : null,
|
'unit_of_measure_id' => $usesEquivalence ? $inputUnitId : null,
|
||||||
'unit_quantity' => $usesEquivalence ? $inputQuantity : null,
|
'unit_quantity' => $usesEquivalence ? $inputQuantity : null,
|
||||||
'user_id' => auth()->id(),
|
'user_id' => auth()->id(),
|
||||||
'notes' => $data['notes'] ?? null,
|
'notes' => $data['notes'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$movements[] = $movement->load(['inventory', 'warehouseFrom', 'warehouseTo']);
|
$movements[] = $movement->load(['inventory', 'warehouseFrom', 'warehouseTo']);
|
||||||
}
|
}
|
||||||
@ -531,20 +563,38 @@ public function exit(array $data): InventoryMovement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eliminar los seriales (salida definitiva)
|
// Registrar movimiento PRIMERO para tener el ID disponible
|
||||||
|
$movement = InventoryMovement::create([
|
||||||
|
'inventory_id' => $inventory->id,
|
||||||
|
'warehouse_from_id' => $warehouse->id,
|
||||||
|
'warehouse_to_id' => null,
|
||||||
|
'movement_type' => 'exit',
|
||||||
|
'quantity' => $quantity,
|
||||||
|
'unit_of_measure_id' => $usesEquivalence ? $inputUnitId : null,
|
||||||
|
'unit_quantity' => $usesEquivalence ? $inputQuantity : null,
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'notes' => $data['notes'] ?? null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Marcar seriales como salida (no eliminar, para conservar historial)
|
||||||
InventorySerial::whereIn('serial_number', $serialNumbers)
|
InventorySerial::whereIn('serial_number', $serialNumbers)
|
||||||
->where('inventory_id', $inventory->id)
|
->where('inventory_id', $inventory->id)
|
||||||
->delete();
|
->update([
|
||||||
|
'status' => 'salida',
|
||||||
|
'exit_movement_id' => $movement->id,
|
||||||
|
]);
|
||||||
|
|
||||||
// Sincronizar stock desde seriales
|
// Sincronizar stock desde seriales
|
||||||
$inventory->syncStock();
|
$inventory->syncStock();
|
||||||
|
|
||||||
|
return $movement;
|
||||||
} else {
|
} else {
|
||||||
// **SIN SERIALES**: Validar y decrementar stock manualmente
|
// **SIN SERIALES**: Validar y decrementar stock manualmente
|
||||||
$this->validateStock($inventory->id, $warehouse->id, $quantity);
|
$this->validateStock($inventory->id, $warehouse->id, $quantity);
|
||||||
$this->updateWarehouseStock($inventory->id, $warehouse->id, -$quantity);
|
$this->updateWarehouseStock($inventory->id, $warehouse->id, -$quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registrar movimiento
|
// Registrar movimiento (solo para caso sin seriales)
|
||||||
return InventoryMovement::create([
|
return InventoryMovement::create([
|
||||||
'inventory_id' => $inventory->id,
|
'inventory_id' => $inventory->id,
|
||||||
'warehouse_from_id' => $warehouse->id,
|
'warehouse_from_id' => $warehouse->id,
|
||||||
@ -625,13 +675,31 @@ public function transfer(array $data): InventoryMovement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar warehouse_id de los seriales seleccionados
|
// Registrar movimiento PRIMERO para tener el ID disponible
|
||||||
|
$movement = InventoryMovement::create([
|
||||||
|
'inventory_id' => $inventory->id,
|
||||||
|
'warehouse_from_id' => $warehouseFrom->id,
|
||||||
|
'warehouse_to_id' => $warehouseTo->id,
|
||||||
|
'movement_type' => 'transfer',
|
||||||
|
'quantity' => $quantity,
|
||||||
|
'unit_of_measure_id' => $usesEquivalence ? $inputUnitId : null,
|
||||||
|
'unit_quantity' => $usesEquivalence ? $inputQuantity : null,
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'notes' => $data['notes'] ?? null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Actualizar warehouse_id y transfer_movement_id de los seriales seleccionados
|
||||||
InventorySerial::whereIn('serial_number', $serialNumbers)
|
InventorySerial::whereIn('serial_number', $serialNumbers)
|
||||||
->where('inventory_id', $inventory->id)
|
->where('inventory_id', $inventory->id)
|
||||||
->update(['warehouse_id' => $warehouseTo->id]);
|
->update([
|
||||||
|
'warehouse_id' => $warehouseTo->id,
|
||||||
|
'transfer_movement_id' => $movement->id,
|
||||||
|
]);
|
||||||
|
|
||||||
// Sincronizar stock desde seriales (actualiza ambos almacenes)
|
// Sincronizar stock desde seriales (actualiza ambos almacenes)
|
||||||
$inventory->syncStock();
|
$inventory->syncStock();
|
||||||
|
|
||||||
|
return $movement;
|
||||||
} else {
|
} else {
|
||||||
// Sin seriales, validar y actualizar stock manualmente
|
// Sin seriales, validar y actualizar stock manualmente
|
||||||
$this->validateStock($inventory->id, $warehouseFrom->id, $quantity);
|
$this->validateStock($inventory->id, $warehouseFrom->id, $quantity);
|
||||||
@ -639,7 +707,7 @@ public function transfer(array $data): InventoryMovement
|
|||||||
$this->updateWarehouseStock($inventory->id, $warehouseTo->id, $quantity);
|
$this->updateWarehouseStock($inventory->id, $warehouseTo->id, $quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registrar movimiento
|
// Registrar movimiento (solo para caso sin seriales)
|
||||||
return InventoryMovement::create([
|
return InventoryMovement::create([
|
||||||
'inventory_id' => $inventory->id,
|
'inventory_id' => $inventory->id,
|
||||||
'warehouse_from_id' => $warehouseFrom->id,
|
'warehouse_from_id' => $warehouseFrom->id,
|
||||||
@ -775,7 +843,7 @@ public function updateMovement(int $movementId, array $data): InventoryMovement
|
|||||||
|
|
||||||
UserEvent::report(model: $movement, event: 'updated', key: 'movement_type');
|
UserEvent::report(model: $movement, event: 'updated', key: 'movement_type');
|
||||||
|
|
||||||
return $movement->load(['inventory', 'warehouseFrom', 'warehouseTo', 'user', 'serials']);
|
return $movement->load(['inventory', 'warehouseFrom', 'warehouseTo', 'user', 'supplier', 'serials', 'transferredSerials', 'exitedSerials']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -815,6 +883,7 @@ protected function prepareUpdateData(InventoryMovement $movement, array $data):
|
|||||||
$updateData['unit_cost_original'] = $usesEquivalence ? $inputUnitCost : null;
|
$updateData['unit_cost_original'] = $usesEquivalence ? $inputUnitCost : null;
|
||||||
$updateData['invoice_reference'] = $data['invoice_reference'] ?? $movement->invoice_reference;
|
$updateData['invoice_reference'] = $data['invoice_reference'] ?? $movement->invoice_reference;
|
||||||
$updateData['warehouse_to_id'] = $data['warehouse_to_id'] ?? $movement->warehouse_to_id;
|
$updateData['warehouse_to_id'] = $data['warehouse_to_id'] ?? $movement->warehouse_to_id;
|
||||||
|
$updateData['supplier_id'] = array_key_exists('supplier_id', $data) ? $data['supplier_id'] : $movement->supplier_id;
|
||||||
} elseif ($movement->movement_type === 'exit') {
|
} elseif ($movement->movement_type === 'exit') {
|
||||||
$updateData['warehouse_from_id'] = $data['warehouse_from_id'] ?? $movement->warehouse_from_id;
|
$updateData['warehouse_from_id'] = $data['warehouse_from_id'] ?? $movement->warehouse_from_id;
|
||||||
} elseif ($movement->movement_type === 'transfer') {
|
} elseif ($movement->movement_type === 'transfer') {
|
||||||
@ -834,12 +903,26 @@ protected function revertMovement(InventoryMovement $movement): void
|
|||||||
// Las entradas NO se revierten aquí, usan lógica delta en applyEntryDeltaUpdate()
|
// Las entradas NO se revierten aquí, usan lógica delta en applyEntryDeltaUpdate()
|
||||||
|
|
||||||
case 'exit':
|
case 'exit':
|
||||||
// Revertir salida: devolver al almacén origen
|
$inventory = $movement->inventory;
|
||||||
$this->updateWarehouseStock(
|
$inventory->load('unitOfMeasure');
|
||||||
$movement->inventory_id,
|
$usesSerials = $inventory->track_serials && ! $inventory->unitOfMeasure?->allows_decimals;
|
||||||
$movement->warehouse_from_id,
|
|
||||||
$movement->quantity
|
if ($usesSerials) {
|
||||||
);
|
// Revertir seriales: restaurar a disponible y limpiar exit_movement_id
|
||||||
|
InventorySerial::where('exit_movement_id', $movement->id)
|
||||||
|
->update([
|
||||||
|
'status' => 'disponible',
|
||||||
|
'exit_movement_id' => null,
|
||||||
|
]);
|
||||||
|
$inventory->syncStock();
|
||||||
|
} else {
|
||||||
|
// Revertir salida sin seriales: devolver stock al almacén origen
|
||||||
|
$this->updateWarehouseStock(
|
||||||
|
$movement->inventory_id,
|
||||||
|
$movement->warehouse_from_id,
|
||||||
|
$movement->quantity
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'transfer':
|
case 'transfer':
|
||||||
@ -950,14 +1033,58 @@ protected function applyEntryDeltaUpdate(InventoryMovement $movement, array $dat
|
|||||||
*/
|
*/
|
||||||
protected function applyExitUpdate(InventoryMovement $movement, array $data): void
|
protected function applyExitUpdate(InventoryMovement $movement, array $data): void
|
||||||
{
|
{
|
||||||
|
$inventory = $movement->inventory;
|
||||||
|
$inventory->load('unitOfMeasure');
|
||||||
|
$usesSerials = $inventory->track_serials && ! $inventory->unitOfMeasure?->allows_decimals;
|
||||||
$warehouseFromId = $data['warehouse_from_id'] ?? $movement->warehouse_from_id;
|
$warehouseFromId = $data['warehouse_from_id'] ?? $movement->warehouse_from_id;
|
||||||
$quantity = $data['quantity'] ?? $movement->quantity;
|
$quantity = $data['quantity'] ?? $movement->quantity;
|
||||||
|
|
||||||
// Validar stock disponible
|
if ($usesSerials) {
|
||||||
$this->validateStock($movement->inventory_id, $warehouseFromId, $quantity);
|
$serialNumbers = $data['serial_numbers'] ?? [];
|
||||||
|
|
||||||
// Aplicar nueva salida
|
if (empty($serialNumbers)) {
|
||||||
$this->updateWarehouseStock($movement->inventory_id, $warehouseFromId, -$quantity);
|
throw new \Exception('Debe proporcionar los números de serie para actualizar esta salida.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$serialNumbers = array_values(array_filter(array_map('trim', $serialNumbers)));
|
||||||
|
|
||||||
|
if (count($serialNumbers) != $quantity) {
|
||||||
|
throw new \Exception('La cantidad de seriales no coincide con la cantidad del movimiento.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar que los seriales existan, estén disponibles y en el almacén correcto
|
||||||
|
foreach ($serialNumbers as $serialNumber) {
|
||||||
|
$serial = InventorySerial::where('serial_number', $serialNumber)
|
||||||
|
->where('inventory_id', $inventory->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (! $serial) {
|
||||||
|
throw new \Exception("El serial '{$serialNumber}' no pertenece a este producto.");
|
||||||
|
}
|
||||||
|
if ($serial->status !== 'disponible') {
|
||||||
|
throw new \Exception("El serial '{$serialNumber}' no está disponible (estado: {$serial->status}).");
|
||||||
|
}
|
||||||
|
if ($serial->warehouse_id !== $warehouseFromId) {
|
||||||
|
throw new \Exception("El serial '{$serialNumber}' no está en el almacén seleccionado.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marcar los nuevos seriales como salida
|
||||||
|
InventorySerial::whereIn('serial_number', $serialNumbers)
|
||||||
|
->where('inventory_id', $inventory->id)
|
||||||
|
->update([
|
||||||
|
'status' => 'salida',
|
||||||
|
'exit_movement_id' => $movement->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$inventory->syncStock();
|
||||||
|
} else {
|
||||||
|
// Validar stock disponible
|
||||||
|
$this->validateStock($movement->inventory_id, $warehouseFromId, $quantity);
|
||||||
|
|
||||||
|
// Aplicar nueva salida
|
||||||
|
$this->updateWarehouseStock($movement->inventory_id, $warehouseFromId, -$quantity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1082,6 +1209,18 @@ protected function updateMovementSerials(InventoryMovement $movement, array $dat
|
|||||||
"No se pueden quitar los seriales [{$unavailableNumbers}] porque ya fueron vendidos o devueltos."
|
"No se pueden quitar los seriales [{$unavailableNumbers}] porque ya fueron vendidos o devueltos."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validar que los seriales a eliminar no hayan sido traspasados a otro almacén
|
||||||
|
$transferred = $currentSerials
|
||||||
|
->whereIn('serial_number', $serialesToRemove)
|
||||||
|
->where('warehouse_id', '!=', $movement->warehouse_to_id);
|
||||||
|
|
||||||
|
if ($transferred->isNotEmpty()) {
|
||||||
|
$transferredNumbers = $transferred->pluck('serial_number')->implode(', ');
|
||||||
|
throw new \Exception(
|
||||||
|
"No se pueden quitar los seriales [{$transferredNumbers}] porque fueron traspasados a otro almacén."
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validar que los nuevos seriales no existan ya en el sistema
|
// Validar que los nuevos seriales no existan ya en el sistema
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('inventory_serials', function (Blueprint $table) {
|
||||||
|
$table->foreignId('transfer_movement_id')
|
||||||
|
->nullable()
|
||||||
|
->after('movement_id')
|
||||||
|
->constrained('inventory_movements')
|
||||||
|
->nullOnDelete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('inventory_serials', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['transfer_movement_id']);
|
||||||
|
$table->dropColumn('transfer_movement_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
DB::statement("ALTER TABLE inventory_serials MODIFY COLUMN status ENUM('disponible', 'vendido', 'devuelto', 'salida') NOT NULL DEFAULT 'disponible'");
|
||||||
|
|
||||||
|
Schema::table('inventory_serials', function (Blueprint $table) {
|
||||||
|
$table->foreignId('exit_movement_id')
|
||||||
|
->nullable()
|
||||||
|
->after('transfer_movement_id')
|
||||||
|
->constrained('inventory_movements')
|
||||||
|
->nullOnDelete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('inventory_serials', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['exit_movement_id']);
|
||||||
|
$table->dropColumn('exit_movement_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
DB::statement("ALTER TABLE inventory_serials MODIFY COLUMN status ENUM('disponible', 'vendido', 'devuelto') NOT NULL DEFAULT 'disponible'");
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user