Files
glastree/app/Http/Controllers/ReportController.php
T
2026-05-26 08:14:29 +02:00

646 lines
23 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Individuo;
use App\Models\Gruppo;
use App\Models\Evento;
use App\Models\Documento;
use App\Models\MailingList;
use App\Models\Contatto;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class ReportController extends Controller
{
public function index()
{
if (!auth()->user()->canAccess('report')) {
abort(403);
}
$stats = [
'individui' => Individuo::count(),
'gruppi' => Gruppo::count(),
'eventi' => Evento::count(),
'documenti' => Documento::count(),
'mailing_liste' => MailingList::count(),
'contatti' => Contatto::count(),
];
$prebuiltReports = $this->getPrebuiltReports();
$customReports = \App\Models\ReportCustom::where('user_id', auth()->id())
->orderBy('created_at', 'desc')
->get();
return view('report.index', compact('stats', 'prebuiltReports', 'customReports'));
}
public function run(Request $request)
{
if (!auth()->user()->canAccess('report')) {
abort(403);
}
$reportType = $request->input('report');
$data = match ($reportType) {
'individui_per_genere' => $this->reportIndividuiPerGenere(),
'individui_per_eta' => $this->reportIndividuiPerEta(),
'gruppi_gerarchia' => $this->reportGruppiGerarchia(),
'gruppi_membri' => $this->reportGruppiMembri(),
'eventi_calendario' => $this->reportEventiCalendario(),
'documenti_per_tipo' => $this->reportDocumentiPerTipo(),
'contatti_per_tipo' => $this->reportContattiPerTipo(),
'mailing_list_dettaglio' => $this->reportMailingListDettaglio(),
'individui_senza_contatti' => $this->reportIndividuiSenzaContatti(),
'individui_senza_gruppo' => $this->reportIndividuiSenzaGruppo(),
'eventi_per_gruppo' => $this->reportEventiPerGruppo(),
'documenti_orfani' => $this->reportDocumentiOrfani(),
default => null,
};
if (!$data) {
return redirect()->route('report.index')->with('error', 'Report non trovato.');
}
return view('report.result', compact('reportType', 'data'));
}
public function runCustom($id)
{
if (!auth()->user()->canAccess('report')) {
abort(403);
}
$report = \App\Models\ReportCustom::findOrFail($id);
if ($report->user_id !== auth()->id()) {
abort(403);
}
$data = $this->runCustomReport($report);
return view('report.result', ['reportType' => 'custom', 'data' => $data, 'customReport' => $report]);
}
public function storeCustom(Request $request)
{
if (!auth()->user()->canAccess('report')) {
abort(403);
}
$data = $request->validate([
'nome' => 'required|string|max:255',
'descrizione' => 'nullable|string',
'tipo_report' => 'required|string',
'config' => 'nullable|array',
]);
\App\Models\ReportCustom::create([
'user_id' => auth()->id(),
'nome' => $data['nome'],
'descrizione' => $data['descrizione'] ?? null,
'tipo_report' => $data['tipo_report'],
'config' => $data['config'] ?? [],
]);
return redirect()->route('report.index')->with('success', 'Report personalizzato creato.');
}
public function destroyCustom($id)
{
if (!auth()->user()->canAccess('report')) {
abort(403);
}
$report = \App\Models\ReportCustom::where('user_id', auth()->id())->findOrFail($id);
$report->delete();
return redirect()->route('report.index')->with('success', 'Report personalizzato eliminato.');
}
public function exportCSV(Request $request)
{
if (!auth()->user()->canAccess('report')) {
abort(403);
}
$reportType = $request->input('report');
$data = match ($reportType) {
'individui_per_genere' => $this->reportIndividuiPerGenere(),
'individui_per_eta' => $this->reportIndividuiPerEta(),
'gruppi_gerarchia' => $this->reportGruppiGerarchia(),
'gruppi_membri' => $this->reportGruppiMembri(),
'eventi_calendario' => $this->reportEventiCalendario(),
'documenti_per_tipo' => $this->reportDocumentiPerTipo(),
'contatti_per_tipo' => $this->reportContattiPerTipo(),
'mailing_list_dettaglio' => $this->reportMailingListDettaglio(),
'individui_senza_contatti' => $this->reportIndividuiSenzaContatti(),
'individui_senza_gruppo' => $this->reportIndividuiSenzaGruppo(),
'eventi_per_gruppo' => $this->reportEventiPerGruppo(),
'documenti_orfani' => $this->reportDocumentiOrfani(),
default => null,
};
if (!$data) {
return redirect()->route('report.index')->with('error', 'Report non trovato.');
}
$response = new \Symfony\Component\HttpFoundation\StreamedResponse(function () use ($data) {
$handle = fopen('php://output', 'w');
if (!empty($data['headers']) && !empty($data['rows'])) {
fputcsv($handle, $data['headers']);
foreach ($data['rows'] as $row) {
fputcsv($handle, $row);
}
}
fclose($handle);
});
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
$response->headers->set('Content-Disposition', 'attachment; filename="report_' . $reportType . '_' . now()->format('Y-m-d') . '.csv"');
return $response;
}
protected function getPrebuiltReports(): array
{
return [
[
'id' => 'individui_per_genere',
'nome' => 'Individui per Genere',
'descrizione' => 'Distribuzione degli individui per genere (Maschio/Femmina)',
'icona' => 'fa-venus-mars',
'colore' => 'info',
],
[
'id' => 'individui_per_eta',
'nome' => 'Individui per Fascia d\'Età',
'descrizione' => 'Distribuzione degli individui per fasce d\'età',
'icona' => 'fa-birthday-cake',
'colore' => 'success',
],
[
'id' => 'gruppi_gerarchia',
'nome' => 'Gerarchia Gruppi',
'descrizione' => 'Struttura ad albero completa di tutti i gruppi con livelli',
'icona' => 'fa-sitemap',
'colore' => 'warning',
],
[
'id' => 'gruppi_membri',
'nome' => 'Gruppi e Membri',
'descrizione' => 'Elenco di tutti i gruppi con il conteggio dei membri',
'icona' => 'fa-users',
'colore' => 'primary',
],
[
'id' => 'eventi_calendario',
'nome' => 'Eventi del Mese',
'descrizione' => 'Tutti gli eventi programmati per il mese corrente',
'icona' => 'fa-calendar-alt',
'colore' => 'danger',
],
[
'id' => 'documenti_per_tipo',
'nome' => 'Documenti per Tipologia',
'descrizione' => 'Distribuzione dei documenti per tipologia',
'icona' => 'fa-file-alt',
'colore' => 'secondary',
],
[
'id' => 'contatti_per_tipo',
'nome' => 'Contatti per Tipo',
'descrizione' => 'Distribuzione dei contatti per tipo (telefono, email, ecc.)',
'icona' => 'fa-address-book',
'colore' => 'teal',
],
[
'id' => 'mailing_list_dettaglio',
'nome' => 'Dettaglio Mailing List',
'descrizione' => 'Elenco completo delle mailing list con i contatti associati',
'icona' => 'fa-envelope',
'colore' => 'purple',
],
[
'id' => 'individui_senza_contatti',
'nome' => 'Individui senza Contatti',
'descrizione' => 'Lista degli individui che non hanno nessun contatto registrato',
'icona' => 'fa-user-slash',
'colore' => 'dark',
],
[
'id' => 'individui_senza_gruppo',
'nome' => 'Individui senza Gruppo',
'descrizione' => 'Lista degli individui non associati a nessun gruppo',
'icona' => 'fa-user-friends',
'colore' => 'orange',
],
[
'id' => 'eventi_per_gruppo',
'nome' => 'Eventi per Gruppo',
'descrizione' => 'Distribuzione degli eventi per gruppo di appartenenza',
'icona' => 'fa-calendar-check',
'colore' => 'pink',
],
[
'id' => 'documenti_orfani',
'nome' => 'Documenti Orfani',
'descrizione' => 'Documenti non collegati a nessun individuo, gruppo o evento',
'icona' => 'fa-file-excel',
'colore' => 'gray',
],
];
}
protected function reportIndividuiPerGenere(): array
{
$rows = Individuo::select('genere', DB::raw('count(*) as totale'))
->groupBy('genere')
->get()
->map(function ($item) {
$item->genere_label = match ($item->genere) {
'M' => 'Maschio',
'F' => 'Femmina',
default => 'Non specificato',
};
return $item;
});
return [
'title' => 'Individui per Genere',
'headers' => ['Genere', 'Totale'],
'rows' => $rows->map(fn($r) => [$r->genere_label, $r->totale])->toArray(),
'summary' => 'Totale individui: ' . Individuo::count(),
'detail_rows' => $rows->map(fn($r) => (array) $r)->toArray(),
];
}
protected function reportIndividuiPerEta(): array
{
$now = now();
$fasce = [
['label' => '0-17 (Minorenni)', 'min' => 0, 'max' => 17],
['label' => '18-25 (Giovani)', 'min' => 18, 'max' => 25],
['label' => '26-40 (Adulti)', 'min' => 26, 'max' => 40],
['label' => '41-60 (Adulti)', 'min' => 41, 'max' => 60],
['label' => '61-75 (Senior)', 'min' => 61, 'max' => 75],
['label' => '75+ (Anziani)', 'min' => 76, 'max' => 200],
];
$rows = [];
$totalWithDate = 0;
foreach ($fasce as $fascia) {
$count = Individuo::whereNotNull('data_nascita')
->whereYear('data_nascita', '>=', $now->year - $fascia['max'])
->whereYear('data_nascita', '<=', $now->year - $fascia['min'])
->count();
$rows[] = ['fascia' => $fascia['label'], 'totale' => $count];
$totalWithDate += $count;
}
$noDate = Individuo::whereNull('data_nascita')->count();
$rows[] = ['fascia' => 'Data non specificata', 'totale' => $noDate];
return [
'title' => 'Individui per Fascia d\'Età',
'headers' => ['Fascia d\'Età', 'Totale'],
'rows' => array_map(fn($r) => [$r['fascia'], $r['totale']], $rows),
'summary' => 'Totale con data: ' . $totalWithDate . ' | Senza data: ' . $noDate,
'detail_rows' => $rows,
];
}
protected function reportGruppiGerarchia(): array
{
$rootGruppi = Gruppo::whereNull('parent_id')->with(['children.diocesi', 'children.children'])->orderBy('nome')->get();
$rows = [];
foreach ($rootGruppi as $gruppo) {
$rows[] = [
'livello' => 0,
'nome' => $gruppo->nome,
'diocesi' => $gruppo->diocesi?->nome ?? '-',
'membri' => $gruppo->individui()->count(),
'padre' => '-',
];
$this->addGruppoChildren($gruppo->children, 1, $rows);
}
return [
'title' => 'Gerarchia Gruppi',
'headers' => ['Livello', 'Nome Gruppo', 'Diocesi', 'Membri', 'Gruppo Padre'],
'rows' => array_map(fn($r) => [$r['livello'], $r['nome'], $r['diocesi'], $r['membri'], $r['padre']], $rows),
'summary' => 'Totale gruppi: ' . Gruppo::count(),
'detail_rows' => $rows,
];
}
protected function addGruppoChildren($children, $level, &$rows): void
{
foreach ($children as $child) {
$parent = $child->parent;
$rows[] = [
'livello' => $level,
'nome' => str_repeat(' ', $level) . $child->nome,
'diocesi' => $child->diocesi?->nome ?? '-',
'membri' => $child->individui()->count(),
'padre' => $parent?->nome ?? '-',
];
$this->addGruppoChildren($child->children, $level + 1, $rows);
}
}
protected function reportGruppiMembri(): array
{
$gruppi = Gruppo::withCount('individui')->orderBy('nome')->get();
$rows = $gruppi->map(function ($g) {
$parent = $g->parent;
return [
'nome' => $g->full_path,
'diocesi' => $g->diocesi?->nome ?? '-',
'membri' => $g->individui_count,
'padre' => $parent?->nome ?? '-',
'responsabili' => $g->getResponsabili()->pluck('cognome')->implode(', ') ?: '-',
];
})->toArray();
return [
'title' => 'Gruppi e Membri',
'headers' => ['Gruppo', 'Diocesi', 'Membri', 'Gruppo Padre', 'Responsabili'],
'rows' => array_map(fn($r) => [$r['nome'], $r['diocesi'], $r['membri'], $r['padre'], $r['responsabili']], $rows),
'summary' => 'Totale gruppi: ' . $gruppi->count() . ' | Totale membri: ' . $gruppi->sum('individui_count'),
'detail_rows' => $rows,
];
}
protected function reportEventiCalendario(): array
{
$now = now();
$eventi = Evento::whereBetween('data_specifica', [$now->startOfMonth(), $now->endOfMonth()])
->with(['gruppi', 'responsabili'])
->orderBy('data_specifica')
->get();
$rows = $eventi->map(function ($e) {
return [
'nome' => $e->nome_evento,
'data' => $e->data_specifica?->format('d/m/Y H:i') ?? '-',
'tipo' => ucfirst($e->tipo_recorrenza ?? 'singolo'),
'gruppi' => $e->gruppi->pluck('nome')->implode(', ') ?: '-',
'responsabili' => $e->responsabili->pluck('nome_completo')->implode(', ') ?: '-',
'luogo' => $e->luogo_indirizzo ?? '-',
];
})->toArray();
return [
'title' => 'Eventi del Mese (' . $now->format('F Y') . ')',
'headers' => ['Evento', 'Data', 'Tipo', 'Gruppi', 'Responsabili', 'Luogo'],
'rows' => array_map(fn($r) => [$r['nome'], $r['data'], $r['tipo'], $r['gruppi'], $r['responsabili'], $r['luogo']], $rows),
'summary' => 'Eventi nel mese: ' . $eventi->count(),
'detail_rows' => $rows,
];
}
protected function reportDocumentiPerTipo(): array
{
$rows = Documento::select('tipologia', DB::raw('count(*) as totale'))
->groupBy('tipologia')
->orderBy('totale', 'desc')
->get();
return [
'title' => 'Documenti per Tipologia',
'headers' => ['Tipologia', 'Totale'],
'rows' => $rows->map(fn($r) => [ucfirst($r->tipologia), $r->totale])->toArray(),
'summary' => 'Totale documenti: ' . Documento::count(),
'detail_rows' => $rows->map(fn($r) => (array) $r)->toArray(),
];
}
protected function reportContattiPerTipo(): array
{
$rows = Contatto::select('tipo', DB::raw('count(*) as totale'))
->groupBy('tipo')
->orderBy('totale', 'desc')
->get();
return [
'title' => 'Contatti per Tipo',
'headers' => ['Tipo', 'Totale'],
'rows' => $rows->map(fn($r) => [ucfirst($r->tipo), $r->totale])->toArray(),
'summary' => 'Totale contatti: ' . Contatto::count(),
'detail_rows' => $rows->map(fn($r) => (array) $r)->toArray(),
];
}
protected function reportMailingListDettaglio(): array
{
$mailingLists = MailingList::with(['contatti.individuo'])->orderBy('nome')->get();
$rows = [];
foreach ($mailingLists as $lista) {
foreach ($lista->contatti as $contatto) {
$rows[] = [
'lista' => $lista->nome,
'stato' => $lista->attiva ? 'Attiva' : 'Disattiva',
'individuo' => $contatto->individuo?->nome_completo ?? 'N/D',
'email' => $contatto->email ?? '-',
];
}
}
return [
'title' => 'Dettaglio Mailing List',
'headers' => ['Lista', 'Stato', 'Individuo', 'Email'],
'rows' => array_map(fn($r) => [$r['lista'], $r['stato'], $r['individuo'], $r['email']], $rows),
'summary' => 'Liste: ' . $mailingLists->count() . ' | Contatti totali: ' . count($rows),
'detail_rows' => $rows,
];
}
protected function reportIndividuiSenzaContatti(): array
{
$individui = Individuo::doesntHave('contatti')
->orderBy('cognome')
->orderBy('nome')
->get();
$rows = $individui->map(function ($ind) {
return [
'codice' => $ind->codice_id,
'cognome' => $ind->cognome,
'nome' => $ind->nome,
'email' => $ind->email ?? '-',
];
})->toArray();
return [
'title' => 'Individui senza Contatti',
'headers' => ['Codice', 'Cognome', 'Nome', 'Email'],
'rows' => array_map(fn($r) => [$r['codice'], $r['cognome'], $r['nome'], $r['email']], $rows),
'summary' => 'Individui senza contatti: ' . $individui->count(),
'detail_rows' => $rows,
];
}
protected function reportIndividuiSenzaGruppo(): array
{
$individui = Individuo::doesntHave('gruppi')
->orderBy('cognome')
->orderBy('nome')
->get();
$rows = $individui->map(function ($ind) {
return [
'codice' => $ind->codice_id,
'cognome' => $ind->cognome,
'nome' => $ind->nome,
'email' => $ind->email ?? '-',
];
})->toArray();
return [
'title' => 'Individui senza Gruppo',
'headers' => ['Codice', 'Cognome', 'Nome', 'Email'],
'rows' => array_map(fn($r) => [$r['codice'], $r['cognome'], $r['nome'], $r['email']], $rows),
'summary' => 'Individui senza gruppo: ' . $individui->count(),
'detail_rows' => $rows,
];
}
protected function reportEventiPerGruppo(): array
{
$gruppi = Gruppo::with(['eventi'])->orderBy('nome')->get();
$rows = [];
foreach ($gruppi as $gruppo) {
if ($gruppo->eventi->count() > 0) {
foreach ($gruppo->eventi as $evento) {
$rows[] = [
'gruppo' => $gruppo->nome,
'evento' => $evento->nome_evento,
'data' => $evento->data_specifica?->format('d/m/Y') ?? '-',
'tipo' => ucfirst($evento->tipo_recorrenza ?? 'singolo'),
];
}
} else {
$rows[] = [
'gruppo' => $gruppo->nome,
'evento' => '-',
'data' => '-',
'tipo' => '-',
];
}
}
return [
'title' => 'Eventi per Gruppo',
'headers' => ['Gruppo', 'Evento', 'Data', 'Tipo'],
'rows' => array_map(fn($r) => [$r['gruppo'], $r['evento'], $r['data'], $r['tipo']], $rows),
'summary' => 'Gruppi: ' . $gruppi->count(),
'detail_rows' => $rows,
];
}
protected function reportDocumentiOrfani(): array
{
$documenti = Documento::whereNull('visibilita_target_id')
->orWhere('visibilita', 'pubblico')
->orderBy('nome_file')
->get();
$rows = $documenti->map(function ($doc) {
return [
'nome' => $doc->nome_file,
'tipologia' => ucfirst($doc->tipologia),
'visibilita' => ucfirst($doc->visibilita),
'data' => $doc->created_at->format('d/m/Y'),
];
})->toArray();
return [
'title' => 'Documenti Orfani',
'headers' => ['Nome', 'Tipologia', 'Visibilità', 'Data'],
'rows' => array_map(fn($r) => [$r['nome'], $r['tipologia'], $r['visibilita'], $r['data']], $rows),
'summary' => 'Documenti orfani: ' . $documenti->count(),
'detail_rows' => $rows,
];
}
protected function runCustomReport(\App\Models\ReportCustom $report): array
{
$tipo = $report->tipo_report;
$config = $report->config ?? [];
$query = match ($tipo) {
'individui' => Individuo::query(),
'gruppi' => Gruppo::query(),
'eventi' => Evento::query(),
'documenti' => Documento::query(),
'contatti' => Contatto::query(),
default => null,
};
if (!$query) {
return [
'title' => $report->nome,
'headers' => ['Errore'],
'rows' => [['Tipo report non valido']],
'summary' => 'Configurazione non valida',
'detail_rows' => [],
];
}
if (!empty($config['search'])) {
$search = $config['search'];
$query->where(function ($q) use ($search) {
foreach ($config['search_columns'] ?? [] as $col) {
$q->orWhere($col, 'like', '%' . $search . '%');
}
});
}
if (!empty($config['sort_by'])) {
$query->orderBy($config['sort_by'], $config['sort_direction'] ?? 'asc');
}
if (!empty($config['limit'])) {
$query->limit((int) $config['limit']);
}
$results = $query->get();
$headers = !empty($config['columns']) ? $config['columns'] : array_keys($results->first()?->toArray() ?? []);
$rows = $results->map(function ($item) use ($headers) {
$row = [];
foreach ($headers as $col) {
$value = $item->{$col} ?? '';
if ($value instanceof \Carbon\Carbon) {
$value = $value->format('d/m/Y');
}
$row[] = $value;
}
return $row;
})->toArray();
return [
'title' => $report->nome,
'headers' => $headers,
'rows' => $rows,
'summary' => 'Risultati: ' . count($rows),
'detail_rows' => $results->map(fn($r) => $r->toArray())->toArray(),
];
}
}