- Elimina gestión de stock inicial en importación (solo catálogo). - Unifica validación de números de serie en todos los movimientos. - Restringe controlador de series a lectura y filtra rutas.
242 lines
7.7 KiB
PHP
242 lines
7.7 KiB
PHP
<?php namespace App\Http\Controllers\App;
|
|
|
|
use App\Models\Inventory;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\App\InventoryStoreRequest;
|
|
use App\Http\Requests\App\InventoryUpdateRequest;
|
|
use App\Http\Requests\App\InventoryImportRequest;
|
|
use App\Services\ProductService;
|
|
use App\Imports\ProductsImport;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
|
use Maatwebsite\Excel\Facades\Excel;
|
|
use Maatwebsite\Excel\Validators\ValidationException;
|
|
use Maatwebsite\Excel\Concerns\FromArray;
|
|
use Maatwebsite\Excel\Concerns\WithHeadings;
|
|
use Maatwebsite\Excel\Concerns\Exportable;
|
|
|
|
class InventoryController extends Controller
|
|
{
|
|
public function __construct(
|
|
protected ProductService $productService
|
|
) {}
|
|
|
|
public function index(Request $request)
|
|
{
|
|
$products = Inventory::with(['category', 'price'])->withCount('serials')
|
|
->where('is_active', true);
|
|
|
|
|
|
// Filtro por búsqueda de texto (nombre, SKU, código de barras)
|
|
if ($request->has('q') && $request->q) {
|
|
$products->where(function($query) use ($request) {
|
|
$query->where('name', 'like', "%{$request->q}%")
|
|
->orWhere('sku', 'like', "%{$request->q}%")
|
|
->orWhere('barcode', $request->q);
|
|
});
|
|
}
|
|
|
|
// Filtro por categoría (independiente de la búsqueda de texto)
|
|
if ($request->has('category_id') && $request->category_id) {
|
|
$products->where('category_id', $request->category_id);
|
|
}
|
|
|
|
// Calcular el valor total del inventario
|
|
$totalInventoryValue = DB::table('inventory_warehouse')
|
|
->join('prices', 'inventory_warehouse.inventory_id', '=', 'prices.inventory_id')
|
|
->join('inventories', 'inventory_warehouse.inventory_id', '=', 'inventories.id')
|
|
->where('inventories.is_active', true)
|
|
->sum(DB::raw('inventory_warehouse.stock * prices.cost'));
|
|
|
|
$products = $products->orderBy('name')
|
|
->paginate(config('app.pagination'));
|
|
|
|
return ApiResponse::OK->response([
|
|
'products' => $products,
|
|
'total_inventory_value' => round($totalInventoryValue, 2)
|
|
]);
|
|
}
|
|
|
|
public function show(Inventory $inventario)
|
|
{
|
|
return ApiResponse::OK->response([
|
|
'model' => $inventario->load(['category', 'price'])->loadCount('serials')
|
|
]);
|
|
}
|
|
|
|
public function store(InventoryStoreRequest $request)
|
|
{
|
|
$product = $this->productService->createProduct($request->validated());
|
|
|
|
return ApiResponse::OK->response([
|
|
'model' => $product
|
|
]);
|
|
}
|
|
|
|
public function update(InventoryUpdateRequest $request, Inventory $inventario)
|
|
{
|
|
$product = $this->productService->updateProduct($inventario, $request->validated());
|
|
|
|
return ApiResponse::OK->response([
|
|
'model' => $product
|
|
]);
|
|
}
|
|
|
|
public function destroy(Inventory $inventario)
|
|
{
|
|
$inventario->delete();
|
|
|
|
return ApiResponse::OK->response();
|
|
}
|
|
|
|
/**
|
|
* Obtener productos disponibles en un almacén específico
|
|
*/
|
|
public function getProductsByWarehouse(Request $request, int $warehouseId)
|
|
{
|
|
$query = Inventory::query()
|
|
->with(['category', 'price'])
|
|
->where('is_active', true)
|
|
->whereHas('warehouses', function ($q) use ($warehouseId) {
|
|
$q->where('warehouse_id', $warehouseId)
|
|
->where('stock', '>', 0);
|
|
});
|
|
|
|
// Filtro por búsqueda de texto
|
|
if ($request->has('q') && $request->q) {
|
|
$query->where(function($q) use ($request) {
|
|
$q->where('name', 'like', "%{$request->q}%")
|
|
->orWhere('sku', 'like', "%{$request->q}%")
|
|
->orWhere('barcode', $request->q);
|
|
});
|
|
}
|
|
|
|
// Filtro por categoría
|
|
if ($request->has('category_id') && $request->category_id) {
|
|
$query->where('category_id', $request->category_id);
|
|
}
|
|
|
|
$products = $query->orderBy('name')->get();
|
|
|
|
// Agregar el stock específico de este almacén a cada producto
|
|
$products->each(function ($product) use ($warehouseId) {
|
|
$warehouseStock = $product->warehouses()
|
|
->where('warehouse_id', $warehouseId)
|
|
->first();
|
|
|
|
$product->warehouse_stock = $warehouseStock ? $warehouseStock->pivot->stock : 0;
|
|
});
|
|
|
|
return ApiResponse::OK->response([
|
|
'products' => $products,
|
|
'warehouse_id' => $warehouseId,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Importar productos desde Excel
|
|
*/
|
|
public function import(InventoryImportRequest $request)
|
|
{
|
|
try {
|
|
$import = new ProductsImport();
|
|
|
|
Excel::import($import, $request->file('file'));
|
|
|
|
$stats = $import->getStats();
|
|
|
|
return ApiResponse::OK->response([
|
|
'message' => 'Importación completada exitosamente.',
|
|
'imported' => $stats['imported'],
|
|
'updated' => $stats['updated'],
|
|
'skipped' => $stats['skipped'],
|
|
'errors' => $stats['errors'],
|
|
]);
|
|
} catch (ValidationException $e) {
|
|
$failures = $e->failures();
|
|
$errors = [];
|
|
|
|
foreach ($failures as $failure) {
|
|
$errors[] = [
|
|
'row' => $failure->row(),
|
|
'attribute' => $failure->attribute(),
|
|
'errors' => $failure->errors(),
|
|
'values' => $failure->values(),
|
|
];
|
|
}
|
|
|
|
return ApiResponse::BAD_REQUEST->response([
|
|
'message' => 'Error de validación en el archivo.',
|
|
'errors' => $errors,
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return ApiResponse::INTERNAL_ERROR->response([
|
|
'message' => 'Error al importar productos: ' . $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Descargar plantilla de Excel para importación
|
|
*/
|
|
public function downloadTemplate()
|
|
{
|
|
$headers = [
|
|
'nombre',
|
|
'sku',
|
|
'codigo_barras',
|
|
'categoria',
|
|
'precio_venta',
|
|
'impuesto'
|
|
];
|
|
|
|
$exampleData = [
|
|
[
|
|
'nombre' => 'Samsung Galaxy A55',
|
|
'sku' => 'SAM-A55-BLK',
|
|
'codigo_barras' => '7502276853456',
|
|
'categoria' => 'Electrónica',
|
|
'precio_venta' => 7500.00,
|
|
'impuesto' => 16
|
|
],
|
|
[
|
|
'nombre' => 'Coca Cola 600ml',
|
|
'sku' => 'COCA-600',
|
|
'codigo_barras' => '750227686666',
|
|
'categoria' => 'Bebidas',
|
|
'precio_venta' => 18.00,
|
|
'impuesto' => 8
|
|
],
|
|
[
|
|
'nombre' => 'Laptop HP Pavilion 15',
|
|
'sku' => 'HP-LAP-15',
|
|
'codigo_barras' => '7502276854443',
|
|
'categoria' => 'Computadoras',
|
|
'precio_venta' => 12000.00,
|
|
'impuesto' => 16
|
|
],
|
|
];
|
|
|
|
return Excel::download(
|
|
new class($headers, $exampleData) implements FromArray, WithHeadings {
|
|
use Exportable;
|
|
|
|
public function __construct(private array $headers, private array $data) {}
|
|
|
|
public function array(): array
|
|
{
|
|
return $this->data;
|
|
}
|
|
|
|
public function headings(): array
|
|
{
|
|
return $this->headers;
|
|
}
|
|
},
|
|
'plantilla_productos.xlsx'
|
|
);
|
|
}
|
|
|
|
}
|