feat: agregar cálculo del valor total del inventario y filtros adicionales en el controlador de inventario
This commit is contained in:
parent
a0e8c70624
commit
656492251b
@ -8,6 +8,7 @@
|
||||
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;
|
||||
@ -27,6 +28,7 @@ public function index(Request $request)
|
||||
->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}%")
|
||||
@ -35,11 +37,22 @@ public function index(Request $request)
|
||||
});
|
||||
}
|
||||
|
||||
// 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 = Inventory::join('prices', 'inventories.id', '=', 'prices.inventory_id')
|
||||
->where('inventories.is_active', true)
|
||||
->sum(DB::raw('inventories.stock * prices.cost'));
|
||||
|
||||
$products = $products->orderBy('name')
|
||||
->paginate(config('app.pagination'));
|
||||
|
||||
return ApiResponse::OK->response([
|
||||
'products' => $products
|
||||
'products' => $products,
|
||||
'total_inventory_value' => round($totalInventoryValue, 2)
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -61,11 +61,9 @@ public function topSellingProduct(Request $request)
|
||||
public function productsWithoutMovement(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'include_stock_value' => ['nullable', 'boolean'],
|
||||
'from_date' => ['required', 'date_format:Y-m-d'],
|
||||
'to_date' => ['required', 'date_format:Y-m-d', 'after_or_equal:from_date'],
|
||||
], [
|
||||
'include_stock_value.boolean' => 'El parámetro de valor de stock debe ser verdadero o falso.',
|
||||
'from_date.required' => 'La fecha inicial es obligatoria.',
|
||||
'from_date.date_format' => 'La fecha inicial debe tener el formato Y-m-d (ejemplo: 2025-01-01).',
|
||||
'to_date.required' => 'La fecha final es obligatoria.',
|
||||
@ -75,14 +73,12 @@ public function productsWithoutMovement(Request $request)
|
||||
|
||||
try {
|
||||
$products = $this->reportService->getProductsWithoutMovement(
|
||||
includeStockValue: (bool)($request->input('include_stock_value', true)),
|
||||
fromDate: $request->input('from_date'),
|
||||
toDate: $request->input('to_date')
|
||||
);
|
||||
|
||||
return ApiResponse::OK->response([
|
||||
'products' => $products,
|
||||
'total_products' => count($products),
|
||||
'message' => 'Reporte de productos sin movimiento generado exitosamente.'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@ -30,7 +30,7 @@ class Inventory extends Model
|
||||
'track_serials' => 'boolean',
|
||||
];
|
||||
|
||||
protected $appends = ['has_serials'];
|
||||
protected $appends = ['has_serials', 'inventory_value'];
|
||||
|
||||
public function category()
|
||||
{
|
||||
@ -78,4 +78,12 @@ public function getHasSerialsAttribute(): bool
|
||||
{
|
||||
return isset($this->attributes['serials_count']) && $this->attributes['serials_count'] > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcular el valor total del inventario para este producto (stock * costo)
|
||||
*/
|
||||
public function getInventoryValueAttribute(): float
|
||||
{
|
||||
return $this->stock * ($this->price?->cost ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,12 +51,8 @@ public function getTopSellingProduct(?string $fromDate = null, ?string $toDate =
|
||||
|
||||
/**
|
||||
* Obtener productos sin movimiento
|
||||
*
|
||||
* @param int $daysThreshold Días mínimos sin movimiento (default: 30)
|
||||
* @param bool $includeStockValue Incluir valor del inventario (default: true)
|
||||
* @return array Lista de productos sin ventas
|
||||
*/
|
||||
public function getProductsWithoutMovement(bool $includeStockValue = true, ?string $fromDate = null, ?string $toDate = null): array
|
||||
public function getProductsWithoutMovement(?string $fromDate = null, ?string $toDate = null)
|
||||
{
|
||||
// Obtener IDs de productos que SÍ tienen ventas
|
||||
$inventoriesWithSales = SaleDetail::query()
|
||||
@ -68,33 +64,13 @@ public function getProductsWithoutMovement(bool $includeStockValue = true, ?stri
|
||||
->pluck('sale_details.inventory_id')
|
||||
->toArray();
|
||||
|
||||
// Construir query para productos SIN ventas
|
||||
// Construir query para productos SIN ventas usando relaciones de Eloquent
|
||||
$query = Inventory::query()
|
||||
->selectRaw('
|
||||
inventories.id,
|
||||
inventories.name,
|
||||
inventories.sku,
|
||||
inventories.stock,
|
||||
categories.name as category_name,
|
||||
inventories.created_at as date_added,
|
||||
prices.retail_price
|
||||
');
|
||||
->with(['category', 'price'])
|
||||
->where('is_active', true)
|
||||
->whereNotIn('id', $inventoriesWithSales)
|
||||
->orderBy('created_at');
|
||||
|
||||
// Agregar valor del inventario si se solicita
|
||||
if ($includeStockValue) {
|
||||
$query->addSelect(
|
||||
DB::raw('(inventories.stock * COALESCE(prices.cost, 0)) as inventory_value')
|
||||
);
|
||||
}
|
||||
|
||||
return $query
|
||||
->leftJoin('categories', 'inventories.category_id', '=', 'categories.id')
|
||||
->leftJoin('prices', 'inventories.id', '=', 'prices.inventory_id')
|
||||
->where('inventories.is_active', true)
|
||||
->whereNull('inventories.deleted_at')
|
||||
->whereNotIn('inventories.id', $inventoriesWithSales)
|
||||
->orderBy('inventories.created_at')
|
||||
->get()
|
||||
->toArray();
|
||||
return $query->paginate(config('app.pagination', 10));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user