pdv.backend/app/Http/Controllers/App/ExcelController.php
2026-01-28 16:46:31 -06:00

227 lines
9.7 KiB
PHP

<?php
namespace App\Http\Controllers\App;
use App\Http\Controllers\Controller;
use App\Models\Client;
use App\Models\Sale;
use Illuminate\Http\Request;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use Carbon\Carbon;
class ExcelController extends Controller
{
/**
* Generar reporte Excel de descuentos a clientes
*/
public function clientDiscountsReport(Request $request)
{
// 1. VALIDACIÓN Y OBTENCIÓN DE DATOS
$request->validate([
'fecha_inicio' => 'required|date',
'fecha_fin' => 'required|date|after_or_equal:fecha_inicio',
'tier_id' => 'nullable|exists:client_tiers,id',
]);
$fechaInicio = Carbon::parse($request->fecha_inicio)->startOfDay();
$fechaFin = Carbon::parse($request->fecha_fin)->endOfDay();
$tierId = $request->tier_id;
// 2. OBTENER DATOS DE CLIENTES CON DESCUENTOS
$clients = Client::whereNotNull('tier_id')
->with('tier:id,tier_name,discount_percentage')
->whereHas('sales', function($q) use ($fechaInicio, $fechaFin) {
$q->where('discount_amount', '>', 0)
->whereBetween('created_at', [$fechaInicio, $fechaFin]);
})
->when($tierId, function($query) use ($tierId) {
$query->where('tier_id', $tierId);
})
->get();
if ($clients->isEmpty()) {
return response()->json(['message' => 'No se encontraron registros en el periodo especificado'], 404);
}
// 3. MAPEO DE DATOS
$data = $clients->map(function($client) use ($fechaInicio, $fechaFin) {
$sales = $client->sales()
->where('discount_amount', '>', 0)
->whereBetween('created_at', [$fechaInicio, $fechaFin])
->get();
return [
'numero' => $client->client_number,
'nombre' => $client->name,
'email' => $client->email ?? 'N/A',
'telefono' => $client->phone ?? 'N/A',
'tier' => $client->tier?->tier_name ?? 'N/A',
'descuento_porcentaje' => $client->tier?->discount_percentage ?? 0,
'total_compras' => $client->total_purchases,
'ventas_con_descuento' => $sales->count(),
'descuentos_recibidos' => $sales->sum('discount_amount'),
'promedio_descuento' => $sales->count() > 0 ? $sales->avg('discount_amount') : 0,
];
});
// 4. CONFIGURACIÓN EXCEL Y ESTILOS
$fileName = 'Reporte_Descuentos_Clientes_' . $fechaInicio->format('Ymd') . '.xlsx';
$filePath = storage_path('app/temp/' . $fileName);
if (!file_exists(dirname($filePath))) mkdir(dirname($filePath), 0755, true);
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// Fuente Global
$sheet->getParent()->getDefaultStyle()->getFont()->setName('Arial');
$sheet->getParent()->getDefaultStyle()->getFont()->setSize(10);
// Estilos Comunes
$styleBox = [
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => '000000']]],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER]
];
$styleLabel = [
'font' => ['size' => 12, 'bold' => true],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_RIGHT, 'vertical' => Alignment::VERTICAL_CENTER]
];
$styleTableHeader = [
'font' => ['bold' => true, 'size' => 10, 'color' => ['rgb' => 'FFFFFF']],
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '4472C4']],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true],
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]]
];
// --- ESTRUCTURA DEL DOCUMENTO ---
$sheet->getRowDimension(2)->setRowHeight(10);
$sheet->getRowDimension(3)->setRowHeight(25);
$sheet->getRowDimension(5)->setRowHeight(30);
// --- TÍTULO PRINCIPAL ---
$sheet->mergeCells('A3:J3');
$sheet->setCellValue('A3', 'REPORTE DE DESCUENTOS A CLIENTES');
$sheet->getStyle('A3')->applyFromArray([
'font' => ['bold' => true, 'size' => 16, 'color' => ['rgb' => '000000']],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER],
]);
// --- INFORMACIÓN DEL PERIODO ---
Carbon::setLocale('es');
if ($fechaInicio->format('m/Y') === $fechaFin->format('m/Y')) {
$periodoTexto = 'del ' . $fechaInicio->format('d') . ' al ' . $fechaFin->format('d') . ' de ' . $fechaFin->translatedFormat('F \d\e Y');
} else {
$periodoTexto = 'del ' . $fechaInicio->format('d/m/Y') . ' al ' . $fechaFin->format('d/m/Y');
}
$sheet->mergeCells('A5:B5');
$sheet->setCellValue('A5', 'PERÍODO:');
$sheet->getStyle('A5')->applyFromArray($styleLabel);
$sheet->mergeCells('C5:J5');
$sheet->setCellValue('C5', $periodoTexto);
$sheet->getStyle('C5:J5')->applyFromArray($styleBox);
$sheet->getStyle('C5')->getFont()->setSize(12);
// --- RESUMEN DE TOTALES ---
$totalClientes = $data->count();
$totalVentas = $data->sum('ventas_con_descuento');
$totalDescuentos = $data->sum('descuentos_recibidos');
$row = 7;
$sheet->setCellValue('A' . $row, 'TOTAL CLIENTES CON DESCUENTOS:');
$sheet->setCellValue('C' . $row, $totalClientes);
$sheet->setCellValue('E' . $row, 'TOTAL VENTAS:');
$sheet->setCellValue('G' . $row, $totalVentas);
$sheet->setCellValue('I' . $row, 'TOTAL DESCUENTOS:');
$sheet->setCellValue('J' . $row, '$' . number_format($totalDescuentos, 2));
$sheet->getStyle('A' . $row . ':J' . $row)->getFont()->setBold(true);
$sheet->getStyle('C' . $row)->getFont()->setSize(12)->getColor()->setRGB('4472C4');
$sheet->getStyle('G' . $row)->getFont()->setSize(12)->getColor()->setRGB('4472C4');
$sheet->getStyle('J' . $row)->getFont()->setSize(12)->getColor()->setRGB('008000');
// --- ENCABEZADOS DE TABLA ---
$h = 9;
$headers = [
'A' => 'No.',
'B' => "NÚMERO\nCLIENTE",
'C' => 'NOMBRE',
'D' => 'EMAIL',
'E' => 'TELÉFONO',
'F' => 'TIER',
'G' => "DESCUENTO\n(%)",
'H' => "VENTAS CON\nDESCUENTO",
'I' => "DESCUENTOS\nRECIBIDOS",
'J' => "PROMEDIO\nDESCUENTO"
];
foreach ($headers as $col => $text) {
$sheet->setCellValue("{$col}{$h}", $text);
}
$sheet->getStyle("A{$h}:J{$h}")->applyFromArray($styleTableHeader);
$sheet->getRowDimension($h)->setRowHeight(35);
// --- LLENADO DE DATOS ---
$row = 10;
$i = 1;
foreach ($data as $item) {
$sheet->setCellValue('A' . $row, $i);
$sheet->setCellValue('B' . $row, $item['numero']);
$sheet->setCellValue('C' . $row, $item['nombre']);
$sheet->setCellValue('D' . $row, $item['email']);
$sheet->setCellValue('E' . $row, $item['telefono']);
$sheet->setCellValue('F' . $row, $item['tier']);
$sheet->setCellValue('G' . $row, $item['descuento_porcentaje'] . '%');
$sheet->setCellValue('H' . $row, $item['ventas_con_descuento']);
$sheet->setCellValue('I' . $row, '$' . number_format($item['descuentos_recibidos'], 2));
$sheet->setCellValue('J' . $row, '$' . number_format($item['promedio_descuento'], 2));
// Estilos de fila
$sheet->getStyle("A{$row}:J{$row}")->applyFromArray([
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]],
'alignment' => ['vertical' => Alignment::VERTICAL_CENTER],
'font' => ['size' => 10]
]);
// Centrados
$sheet->getStyle("A{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle("B{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle("G{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle("H{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
// Color alterno de filas
if ($i % 2 == 0) {
$sheet->getStyle("A{$row}:J{$row}")->getFill()
->setFillType(Fill::FILL_SOLID)
->getStartColor()->setRGB('F2F2F2');
}
$row++;
$i++;
}
// --- ANCHOS DE COLUMNA ---
$sheet->getColumnDimension('A')->setWidth(5);
$sheet->getColumnDimension('B')->setWidth(15);
$sheet->getColumnDimension('C')->setWidth(25);
$sheet->getColumnDimension('D')->setWidth(25);
$sheet->getColumnDimension('E')->setWidth(15);
$sheet->getColumnDimension('F')->setWidth(12);
$sheet->getColumnDimension('G')->setWidth(12);
$sheet->getColumnDimension('H')->setWidth(15);
$sheet->getColumnDimension('I')->setWidth(18);
$sheet->getColumnDimension('J')->setWidth(18);
$writer = new Xlsx($spreadsheet);
$writer->save($filePath);
return response()->download($filePath, $fileName)->deleteFileAfterSend(true);
}
}