withCount('sales'); if ($request->has('status')) { $query->where('status', $request->status); } if ($request->has('date')) { $query->whereDate('close_date', $request->date); } $cashCloses = $query->orderBy('id', 'asc') ->paginate(config('app.pagination')); return ApiResponse::OK->response([ 'cash_closes' => $cashCloses, ]); } public function closeCashClose(Request $request) { $request->validate([ 'exit' => 'sometimes|numeric|min:0', ]); $cashClose = CashClose::open()->first(); if (!$cashClose) { return ApiResponse::NOT_FOUND->response([ 'message' => 'No hay un corte de caja abierto para cerrar.', ]); } $totalSales = Sale::where('cash_close_id', $cashClose->id)->sum('total_amount'); $paymentMethods = Sale::where('cash_close_id', $cashClose->id) ->select('payment_method', DB::raw('SUM(total_amount) as total')) ->groupBy('payment_method') ->pluck('total', 'payment_method'); $exit = $request->input('exit', 0); $cashClose->update([ 'closed_at' => now(), 'income' => $totalSales, 'exit' => $exit, 'income_cash' => $paymentMethods->get('cash', 0), 'income_card' => $paymentMethods->get('card', 0), 'income_transfer' => $paymentMethods->get('transfer', 0), 'status' => 'closed', ]); $balanceFinal = $cashClose->initial_balance + $totalSales - $exit; return ApiResponse::OK->response([ 'message' => 'Corte de caja cerrado exitosamente.', 'cash_close' => $cashClose->fresh('user'), 'resumen' => [ 'periodo' => [ 'apertura' => $cashClose->opened_at, 'cierre' => $cashClose->closed_at, ], 'totales' => [ 'fondo_inicial' => $cashClose->initial_balance, 'total_ventas' => $totalSales, 'efectivo' => $paymentMethods->get('cash', 0), 'tarjeta' => $paymentMethods->get('card', 0), 'transferencia' => $paymentMethods->get('transfer', 0), 'egresos' => $exit, 'balance_final' => $balanceFinal, ], ], ]); } public function report(Request $request) { $request->validate([ 'start_date' => 'nullable|date', 'end_date' => 'nullable|date|after_or_equal:start_date', ]); $query = CashClose::with('user:id,name')->withCount('sales'); if ($request->has('start_date') && $request->has('end_date')) { $query->whereBetween('close_date', [$request->start_date, $request->end_date]); } elseif ($request->has('start_date')) { $query->whereDate('close_date', '>=', $request->start_date); } elseif ($request->has('end_date')) { $query->whereDate('close_date', '<=', $request->end_date); } else { $query->closed()->orderBy('id', 'desc')->limit(1); } // Información del corte de caja $cashCloses = $query->orderBy('id', 'desc')->get(); if ($cashCloses->isEmpty()) { return ApiResponse::NOT_FOUND->response([ 'message' => 'No se encontraron cortes de caja en el rango de fechas especificado.', ]); } $cashCloseIds = $cashCloses->pluck('id')->toArray(); // Estadísticas por paquete (Total de Paquetes Vendidos por Tipo) $packageStats = DB::table('sale_items') ->join('sales', 'sale_items.sale_id', '=', 'sales.id') ->join('packages', 'sale_items.package_id', '=', 'packages.id') ->whereIn('sales.cash_close_id', $cashCloseIds) ->select( 'packages.name as paquete', DB::raw('COUNT(*) as total_vendidos'), DB::raw('SUM(packages.price) as total_ingresos') ) ->groupBy('packages.id', 'packages.name') ->get(); // Estadísticas por duración (Total de Ventas por Duración) $durationStats = DB::table('sale_items') ->join('sales', 'sale_items.sale_id', '=', 'sales.id') ->join('packages', 'sale_items.package_id', '=', 'packages.id') ->whereIn('sales.cash_close_id', $cashCloseIds) ->select( 'packages.period as duracion_dias', DB::raw('COUNT(DISTINCT sales.id) as total_ventas') ) ->groupBy('packages.period') ->orderBy('packages.period', 'asc') ->get(); // Reporte detallado de ventas $detailedSales = SaleItem::whereHas('sale', function ($query) use ($cashCloseIds) { $query->whereIn('cash_close_id', $cashCloseIds); }) ->with([ 'sale.client:id,name,paternal,maternal', 'sale:id,client_id,payment_method', 'simCard:id,iccid,msisdn', 'package:id,name,price' ]) ->orderBy('id', 'asc') ->paginate(config('app.pagination')) ->through(function ($item) { return [ 'nombre_comprador' => $item->sale->client->full_name, 'id_sim' => $item->simCard->iccid, 'numero_asignado' => $item->simCard->msisdn, 'paquete' => $item->package->name, 'costo' => $item->package->price, 'medio_pago' => $item->sale->payment_method ]; }); $totalIncome = $cashCloses->sum('income'); $totalExit = $cashCloses->sum('exit'); $totalCash = $cashCloses->sum('income_cash'); $totalCard = $cashCloses->sum('income_card'); $totalTransfer = $cashCloses->sum('income_transfer'); $balanceFinal = $cashCloses->sum('initial_balance') + $totalIncome - $totalExit; return ApiResponse::OK->response([ 'cash_closes' => $cashCloses, 'periodo' => [ 'inicio' => $cashCloses->last()?->opened_at, 'fin' => $cashCloses->first()?->closed_at, ], 'resumen_financiero' => [ 'total_ventas' => $totalIncome, 'efectivo' => $totalCash, 'tarjeta' => $totalCard, 'transferencia' => $totalTransfer, 'egresos' => $totalExit, 'balance_final' => $balanceFinal, ], 'ventas_paquete' => $packageStats, 'ventas_duracion' => $durationStats, 'ventas_detalladas' => $detailedSales, ]); } public function exportReport(Request $request) { $request->validate([ 'start_date' => 'nullable|date', 'end_date' => 'nullable|date|after_or_equal:start_date', ]); $query = CashClose::with('user:id,name')->withCount('sales'); if ($request->has('start_date') && $request->has('end_date')) { $query->whereBetween('close_at', [$request->start_date, $request->end_date]); } elseif ($request->has('start_date')) { $query->whereDate('close_at', '>=', $request->start_date); } elseif ($request->has('end_date')) { $query->whereDate('close_at', '<=', $request->end_date); } else { $query->closed()->orderBy('id', 'desc')->limit(1); } $cashCloses = $query->orderBy('id', 'desc')->get(); if ($cashCloses->isEmpty()) { return ApiResponse::NOT_FOUND->response([ 'message' => 'No se encontraron cortes de caja en el rango de fechas especificado.', ]); } $cashCloseIds = $cashCloses->pluck('id')->toArray(); // Obtener ventas detalladas $detailedSales = SaleItem::whereHas('sale', function ($query) use ($cashCloseIds) { $query->whereIn('cash_close_id', $cashCloseIds); }) ->with([ 'sale.client:id,name,paternal,maternal', 'sale:id,client_id,payment_method', 'simCard:id,iccid,msisdn', 'package:id,name,price' ]) ->orderBy('id', 'asc') ->get(); // Calcular totales $totalIncome = $cashCloses->sum('income'); $totalExit = $cashCloses->sum('exit'); $totalCash = $cashCloses->sum('income_cash'); $totalCard = $cashCloses->sum('income_card'); $totalTransfer = $cashCloses->sum('income_transfer'); // Crear el CSV $filename = 'reporte_corte_caja_' . date('Y-m-d_His') . '.csv'; $headers = [ 'Content-Type' => 'text/csv; charset=UTF-8', 'Content-Disposition' => 'attachment; filename="' . $filename . '"', ]; $callback = function () use ($cashCloses, $detailedSales, $totalIncome, $totalExit, $totalCash, $totalCard, $totalTransfer) { $file = fopen('php://output', 'w'); fprintf($file, chr(0xEF) . chr(0xBB) . chr(0xBF)); // RESUMEN FINANCIERO fputcsv($file, ['RESUMEN FINANCIERO', '']); fputcsv($file, ['Periodo Inicio', $cashCloses->last()?->opened_at]); fputcsv($file, ['Periodo Fin', $cashCloses->first()?->closed_at]); fputcsv($file, ['Total Ventas', number_format($totalIncome, 2)]); fputcsv($file, ['Efectivo', number_format($totalCash, 2)]); fputcsv($file, ['Tarjeta', number_format($totalCard, 2)]); fputcsv($file, ['Transferencia', number_format($totalTransfer, 2)]); fputcsv($file, ['Egresos', number_format($totalExit, 2)]); fputcsv($file, []); // VENTAS DETALLADAS fputcsv($file, ['VENTAS DETALLADAS']); fputcsv($file, ['Nombre Comprador', 'ID SIM', 'Número Asignado', 'Paquete', 'Costo', 'Medio de Pago']); foreach ($detailedSales as $item) { fputcsv($file, [ $item->sale->client->full_name, "'" . $item->simCard->iccid . "'", "'" . $item->simCard->msisdn . "'", $item->package->name, number_format($item->package->price, 2), ucfirst($item->sale->payment_method) ]); } fclose($file); }; return response()->stream($callback, 200, $headers); } }