add: reportes por rango de fecha

This commit is contained in:
Juan Felipe Zapata Moreno 2026-01-05 15:52:08 -06:00
parent d5665f0448
commit 40bc2b5735
2 changed files with 15 additions and 14 deletions

View File

@ -15,22 +15,22 @@ public function __construct(
/** /**
* Obtener el producto más vendido * Obtener el producto más vendido
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/ */
public function topSellingProduct(Request $request) public function topSellingProduct(Request $request)
{ {
$request->validate([ $request->validate([
'include_stock_value' => ['nullable', 'boolean'],
'from_date' => ['nullable', 'date_format:Y-m-d'], 'from_date' => ['nullable', 'date_format:Y-m-d'],
'to_date' => ['nullable', 'date_format:Y-m-d', 'after_or_equal:from_date', 'required_with:from_date'], 'to_date' => ['nullable', 'date_format:Y-m-d', 'after_or_equal:from_date', 'required_with:from_date'],
], [ ], [
'include_stock_value.boolean' => 'El parámetro de valor de stock debe ser verdadero o falso.',
'from_date.date_format' => 'La fecha inicial debe tener el formato Y-m-d (ejemplo: 2025-01-01).', 'from_date.date_format' => 'La fecha inicial debe tener el formato Y-m-d (ejemplo: 2025-01-01).',
'to_date.date_format' => 'La fecha final debe tener el formato Y-m-d (ejemplo: 2025-01-31).', 'to_date.date_format' => 'La fecha final debe tener el formato Y-m-d (ejemplo: 2025-01-31).',
'to_date.after_or_equal' => 'La fecha final debe ser igual o posterior a la fecha inicial.', 'to_date.after_or_equal' => 'La fecha final debe ser igual o posterior a la fecha inicial.',
'to_date.required_with' => 'La fecha final es obligatoria cuando se proporciona fecha inicial.', 'to_date.required_with' => 'La fecha final es obligatoria cuando se proporciona fecha inicial.',
]); ]);
try { try {
$product = $this->reportService->getTopSellingProduct( $product = $this->reportService->getTopSellingProduct(
fromDate: $request->input('from_date'), fromDate: $request->input('from_date'),
@ -57,25 +57,27 @@ public function topSellingProduct(Request $request)
/** /**
* Obtener productos sin movimiento * Obtener productos sin movimiento
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/ */
public function productsWithoutMovement(Request $request) public function productsWithoutMovement(Request $request)
{ {
$request->validate([ $request->validate([
'days_threshold' => ['nullable', 'integer', 'min:1'],
'include_stock_value' => ['nullable', 'boolean'], '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'],
], [ ], [
'days_threshold.integer' => 'El umbral de días debe ser un número entero.',
'days_threshold.min' => 'El umbral de días debe ser al menos 1.',
'include_stock_value.boolean' => 'El parámetro de valor de stock debe ser verdadero o falso.', '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.',
'to_date.date_format' => 'La fecha final debe tener el formato Y-m-d (ejemplo: 2025-01-31).',
'to_date.after_or_equal' => 'La fecha final debe ser igual o posterior a la fecha inicial.',
]); ]);
try { try {
$products = $this->reportService->getProductsWithoutMovement( $products = $this->reportService->getProductsWithoutMovement(
daysThreshold: (int)($request->input('days_threshold', 30)), includeStockValue: (bool)($request->input('include_stock_value', true)),
includeStockValue: (bool)($request->input('include_stock_value', true)) fromDate: $request->input('from_date'),
toDate: $request->input('to_date')
); );
return ApiResponse::OK->response([ return ApiResponse::OK->response([

View File

@ -56,13 +56,14 @@ public function getTopSellingProduct(?string $fromDate = null, ?string $toDate =
* @param bool $includeStockValue Incluir valor del inventario (default: true) * @param bool $includeStockValue Incluir valor del inventario (default: true)
* @return array Lista de productos sin ventas * @return array Lista de productos sin ventas
*/ */
public function getProductsWithoutMovement(int $daysThreshold = 30, bool $includeStockValue = true): array public function getProductsWithoutMovement(bool $includeStockValue = true, ?string $fromDate = null, ?string $toDate = null): array
{ {
// Obtener IDs de productos que SÍ tienen ventas // Obtener IDs de productos que SÍ tienen ventas
$inventoriesWithSales = SaleDetail::query() $inventoriesWithSales = SaleDetail::query()
->join('sales', 'sale_details.sale_id', '=', 'sales.id') ->join('sales', 'sale_details.sale_id', '=', 'sales.id')
->where('sales.status', 'completed') ->where('sales.status', 'completed')
->whereNull('sales.deleted_at') ->whereNull('sales.deleted_at')
->whereBetween(DB::raw('DATE(sales.created_at)'), [$fromDate, $toDate])
->distinct() ->distinct()
->pluck('sale_details.inventory_id') ->pluck('sale_details.inventory_id')
->toArray(); ->toArray();
@ -76,7 +77,6 @@ public function getProductsWithoutMovement(int $daysThreshold = 30, bool $includ
inventories.stock, inventories.stock,
categories.name as category_name, categories.name as category_name,
inventories.created_at as date_added, inventories.created_at as date_added,
DATEDIFF(NOW(), inventories.created_at) as days_without_movement,
prices.retail_price prices.retail_price
'); ');
@ -93,7 +93,6 @@ public function getProductsWithoutMovement(int $daysThreshold = 30, bool $includ
->where('inventories.is_active', true) ->where('inventories.is_active', true)
->whereNull('inventories.deleted_at') ->whereNull('inventories.deleted_at')
->whereNotIn('inventories.id', $inventoriesWithSales) ->whereNotIn('inventories.id', $inventoriesWithSales)
->havingRaw('DATEDIFF(NOW(), inventories.created_at) >= ?', [$daysThreshold])
->orderBy('inventories.created_at') ->orderBy('inventories.created_at')
->get() ->get()
->toArray(); ->toArray();