2026-05-26 08:14:29 +02:00
|
|
|
<?php
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
2026-05-26 08:14:29 +02:00
|
|
|
namespace App\Http\Controllers;
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
use App\Helpers\ReportColumnRegistry;
|
2026-05-26 08:14:29 +02:00
|
|
|
use App\Models\Gruppo;
|
2026-05-27 10:45:05 +02:00
|
|
|
use App\Models\Individuo;
|
2026-05-26 08:14:29 +02:00
|
|
|
use App\Models\Evento;
|
|
|
|
|
use App\Models\Documento;
|
|
|
|
|
use App\Models\MailingList;
|
|
|
|
|
use App\Models\Contatto;
|
2026-05-27 10:45:05 +02:00
|
|
|
use App\Models\TipologiaDocumento;
|
|
|
|
|
use App\Models\ReportCustom;
|
2026-05-26 08:14:29 +02:00
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
2026-05-27 10:45:05 +02:00
|
|
|
use Illuminate\Support\Str;
|
2026-05-26 08:14:29 +02:00
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
$customReports = ReportCustom::where('user_id', auth()->id())
|
2026-05-26 08:14:29 +02:00
|
|
|
->orderBy('created_at', 'desc')
|
|
|
|
|
->get();
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
$tipologieDocumento = TipologiaDocumento::attive()->pluck('nome');
|
|
|
|
|
|
|
|
|
|
$columnOptions = [];
|
|
|
|
|
foreach (ReportColumnRegistry::allEntityTypes() as $type) {
|
|
|
|
|
$columnOptions[$type] = ReportColumnRegistry::getColumns($type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return view('report.index', compact(
|
|
|
|
|
'stats',
|
|
|
|
|
'prebuiltReports',
|
|
|
|
|
'customReports',
|
|
|
|
|
'tipologieDocumento',
|
|
|
|
|
'columnOptions'
|
|
|
|
|
));
|
2026-05-26 08:14:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function run(Request $request)
|
|
|
|
|
{
|
|
|
|
|
if (!auth()->user()->canAccess('report')) {
|
|
|
|
|
abort(403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$reportType = $request->input('report');
|
2026-05-27 10:45:05 +02:00
|
|
|
$customReportId = $request->input('custom_id');
|
2026-05-26 08:14:29 +02:00
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
if ($customReportId) {
|
|
|
|
|
$report = ReportCustom::findOrFail($customReportId);
|
|
|
|
|
if ($report->user_id !== auth()->id()) {
|
|
|
|
|
abort(403);
|
|
|
|
|
}
|
|
|
|
|
$data = $this->runCustomReport($report);
|
|
|
|
|
return view('report.result', compact('data', 'reportType', 'report'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = $this->resolveReport($request);
|
2026-05-26 08:14:29 +02:00
|
|
|
|
|
|
|
|
if (!$data) {
|
|
|
|
|
return redirect()->route('report.index')->with('error', 'Report non trovato.');
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
return view('report.result', compact('data', 'reportType'));
|
2026-05-26 08:14:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function runCustom($id)
|
|
|
|
|
{
|
|
|
|
|
if (!auth()->user()->canAccess('report')) {
|
|
|
|
|
abort(403);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
$report = ReportCustom::findOrFail($id);
|
|
|
|
|
|
2026-05-26 08:14:29 +02:00
|
|
|
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',
|
|
|
|
|
]);
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
$config = [];
|
|
|
|
|
|
|
|
|
|
if ($request->has('columns')) {
|
|
|
|
|
$columns = $request->input('columns');
|
|
|
|
|
$config['columns'] = is_array($columns) ? $columns : array_map('trim', explode(',', $columns));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($request->filled('sort_by')) {
|
|
|
|
|
$config['sort_by'] = $request->input('sort_by');
|
|
|
|
|
$config['sort_direction'] = $request->input('sort_direction', 'asc');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($request->filled('limit')) {
|
|
|
|
|
$config['limit'] = (int) $request->input('limit');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($request->filled('search')) {
|
|
|
|
|
$config['search'] = $request->input('search');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($request->has('search_columns')) {
|
|
|
|
|
$config['search_columns'] = $request->input('search_columns');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ReportCustom::create([
|
2026-05-26 08:14:29 +02:00
|
|
|
'user_id' => auth()->id(),
|
|
|
|
|
'nome' => $data['nome'],
|
|
|
|
|
'descrizione' => $data['descrizione'] ?? null,
|
|
|
|
|
'tipo_report' => $data['tipo_report'],
|
2026-05-27 10:45:05 +02:00
|
|
|
'config' => $config,
|
2026-05-26 08:14:29 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return redirect()->route('report.index')->with('success', 'Report personalizzato creato.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function destroyCustom($id)
|
|
|
|
|
{
|
|
|
|
|
if (!auth()->user()->canAccess('report')) {
|
|
|
|
|
abort(403);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
$report = ReportCustom::where('user_id', auth()->id())->findOrFail($id);
|
2026-05-26 08:14:29 +02:00
|
|
|
$report->delete();
|
|
|
|
|
|
|
|
|
|
return redirect()->route('report.index')->with('success', 'Report personalizzato eliminato.');
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-26 11:47:36 +02:00
|
|
|
public function printPreview(Request $request)
|
|
|
|
|
{
|
|
|
|
|
if (!auth()->user()->canAccess('report')) {
|
|
|
|
|
abort(403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$customReportId = $request->input('custom_id');
|
|
|
|
|
|
|
|
|
|
if ($customReportId) {
|
2026-05-27 10:45:05 +02:00
|
|
|
$report = ReportCustom::findOrFail($customReportId);
|
2026-05-26 11:47:36 +02:00
|
|
|
if ($report->user_id !== auth()->id()) {
|
|
|
|
|
abort(403);
|
|
|
|
|
}
|
|
|
|
|
$data = $this->runCustomReport($report);
|
2026-05-27 10:45:05 +02:00
|
|
|
return view('report.print-preview', compact('data', 'report'));
|
2026-05-26 11:47:36 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
$data = $this->resolveReport($request);
|
2026-05-26 11:47:36 +02:00
|
|
|
|
|
|
|
|
if (!$data) {
|
|
|
|
|
return redirect()->route('report.index')->with('error', 'Report non trovato.');
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
return view('report.print-preview', compact('data'));
|
2026-05-26 11:47:36 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-26 08:14:29 +02:00
|
|
|
public function exportCSV(Request $request)
|
|
|
|
|
{
|
|
|
|
|
if (!auth()->user()->canAccess('report')) {
|
|
|
|
|
abort(403);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
$customReportId = $request->input('custom_id');
|
2026-05-26 08:14:29 +02:00
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
if ($customReportId) {
|
|
|
|
|
$report = ReportCustom::findOrFail($customReportId);
|
|
|
|
|
if ($report->user_id !== auth()->id()) {
|
|
|
|
|
abort(403);
|
|
|
|
|
}
|
|
|
|
|
$data = $this->runCustomReport($report);
|
|
|
|
|
$filename = 'report_' . Str::slug($report->nome) . '_' . now()->format('Y-m-d') . '.csv';
|
|
|
|
|
} else {
|
|
|
|
|
$data = $this->resolveReport($request);
|
|
|
|
|
if (!$data) {
|
|
|
|
|
return redirect()->route('report.index')->with('error', 'Report non trovato.');
|
|
|
|
|
}
|
|
|
|
|
$filename = 'report_' . $request->input('report') . '_' . now()->format('Y-m-d') . '.csv';
|
2026-05-26 08:14:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$response = new \Symfony\Component\HttpFoundation\StreamedResponse(function () use ($data) {
|
|
|
|
|
$handle = fopen('php://output', 'w');
|
2026-05-27 10:45:05 +02:00
|
|
|
fprintf($handle, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
|
|
|
|
|
2026-05-26 08:14:29 +02:00
|
|
|
if (!empty($data['headers']) && !empty($data['rows'])) {
|
|
|
|
|
fputcsv($handle, $data['headers']);
|
|
|
|
|
foreach ($data['rows'] as $row) {
|
|
|
|
|
fputcsv($handle, $row);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-27 10:45:05 +02:00
|
|
|
|
2026-05-26 08:14:29 +02:00
|
|
|
fclose($handle);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
|
2026-05-27 10:45:05 +02:00
|
|
|
$response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"');
|
2026-05-26 08:14:29 +02:00
|
|
|
|
|
|
|
|
return $response;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
protected function resolveReport(Request $request): ?array
|
|
|
|
|
{
|
|
|
|
|
$reportType = $request->input('report');
|
|
|
|
|
$tipologiaFilter = $request->input('tipologia_documento');
|
|
|
|
|
|
|
|
|
|
return 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(),
|
|
|
|
|
'individui_completo' => $this->reportIndividuiCompleto(),
|
|
|
|
|
'gruppi_completo' => $this->reportGruppiCompleto(),
|
|
|
|
|
'eventi_completo' => $this->reportEventiCompleto(),
|
|
|
|
|
'documenti_completo' => $this->reportDocumentiCompleto($tipologiaFilter),
|
|
|
|
|
'contatti_completo' => $this->reportContattiCompleto(),
|
|
|
|
|
'scadenze_documenti' => $this->reportScadenzeDocumenti($tipologiaFilter),
|
|
|
|
|
default => null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-26 08:14:29 +02:00
|
|
|
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',
|
2026-05-27 10:45:05 +02:00
|
|
|
'has_tipologia_filter' => false,
|
2026-05-26 08:14:29 +02:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'individui_per_eta',
|
|
|
|
|
'nome' => 'Individui per Fascia d\'Età',
|
|
|
|
|
'descrizione' => 'Distribuzione degli individui per fasce d\'età',
|
|
|
|
|
'icona' => 'fa-birthday-cake',
|
|
|
|
|
'colore' => 'success',
|
2026-05-27 10:45:05 +02:00
|
|
|
'has_tipologia_filter' => false,
|
2026-05-26 08:14:29 +02:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'gruppi_gerarchia',
|
|
|
|
|
'nome' => 'Gerarchia Gruppi',
|
|
|
|
|
'descrizione' => 'Struttura ad albero completa di tutti i gruppi con livelli',
|
|
|
|
|
'icona' => 'fa-sitemap',
|
|
|
|
|
'colore' => 'warning',
|
2026-05-27 10:45:05 +02:00
|
|
|
'has_tipologia_filter' => false,
|
2026-05-26 08:14:29 +02:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'gruppi_membri',
|
|
|
|
|
'nome' => 'Gruppi e Membri',
|
|
|
|
|
'descrizione' => 'Elenco di tutti i gruppi con il conteggio dei membri',
|
|
|
|
|
'icona' => 'fa-users',
|
|
|
|
|
'colore' => 'primary',
|
2026-05-27 10:45:05 +02:00
|
|
|
'has_tipologia_filter' => false,
|
2026-05-26 08:14:29 +02:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'eventi_calendario',
|
|
|
|
|
'nome' => 'Eventi del Mese',
|
|
|
|
|
'descrizione' => 'Tutti gli eventi programmati per il mese corrente',
|
|
|
|
|
'icona' => 'fa-calendar-alt',
|
|
|
|
|
'colore' => 'danger',
|
2026-05-27 10:45:05 +02:00
|
|
|
'has_tipologia_filter' => false,
|
2026-05-26 08:14:29 +02:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'documenti_per_tipo',
|
|
|
|
|
'nome' => 'Documenti per Tipologia',
|
|
|
|
|
'descrizione' => 'Distribuzione dei documenti per tipologia',
|
|
|
|
|
'icona' => 'fa-file-alt',
|
|
|
|
|
'colore' => 'secondary',
|
2026-05-27 10:45:05 +02:00
|
|
|
'has_tipologia_filter' => false,
|
2026-05-26 08:14:29 +02:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'contatti_per_tipo',
|
|
|
|
|
'nome' => 'Contatti per Tipo',
|
|
|
|
|
'descrizione' => 'Distribuzione dei contatti per tipo (telefono, email, ecc.)',
|
|
|
|
|
'icona' => 'fa-address-book',
|
|
|
|
|
'colore' => 'teal',
|
2026-05-27 10:45:05 +02:00
|
|
|
'has_tipologia_filter' => false,
|
2026-05-26 08:14:29 +02:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'mailing_list_dettaglio',
|
|
|
|
|
'nome' => 'Dettaglio Mailing List',
|
|
|
|
|
'descrizione' => 'Elenco completo delle mailing list con i contatti associati',
|
|
|
|
|
'icona' => 'fa-envelope',
|
|
|
|
|
'colore' => 'purple',
|
2026-05-27 10:45:05 +02:00
|
|
|
'has_tipologia_filter' => false,
|
2026-05-26 08:14:29 +02:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'individui_senza_contatti',
|
|
|
|
|
'nome' => 'Individui senza Contatti',
|
|
|
|
|
'descrizione' => 'Lista degli individui che non hanno nessun contatto registrato',
|
|
|
|
|
'icona' => 'fa-user-slash',
|
|
|
|
|
'colore' => 'dark',
|
2026-05-27 10:45:05 +02:00
|
|
|
'has_tipologia_filter' => false,
|
2026-05-26 08:14:29 +02:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'individui_senza_gruppo',
|
|
|
|
|
'nome' => 'Individui senza Gruppo',
|
|
|
|
|
'descrizione' => 'Lista degli individui non associati a nessun gruppo',
|
|
|
|
|
'icona' => 'fa-user-friends',
|
|
|
|
|
'colore' => 'orange',
|
2026-05-27 10:45:05 +02:00
|
|
|
'has_tipologia_filter' => false,
|
2026-05-26 08:14:29 +02:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'eventi_per_gruppo',
|
|
|
|
|
'nome' => 'Eventi per Gruppo',
|
|
|
|
|
'descrizione' => 'Distribuzione degli eventi per gruppo di appartenenza',
|
|
|
|
|
'icona' => 'fa-calendar-check',
|
|
|
|
|
'colore' => 'pink',
|
2026-05-27 10:45:05 +02:00
|
|
|
'has_tipologia_filter' => false,
|
2026-05-26 08:14:29 +02:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'documenti_orfani',
|
|
|
|
|
'nome' => 'Documenti Orfani',
|
|
|
|
|
'descrizione' => 'Documenti non collegati a nessun individuo, gruppo o evento',
|
|
|
|
|
'icona' => 'fa-file-excel',
|
|
|
|
|
'colore' => 'gray',
|
2026-05-27 10:45:05 +02:00
|
|
|
'has_tipologia_filter' => false,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'documenti_completo',
|
|
|
|
|
'nome' => 'Documenti (dettaglio completo)',
|
|
|
|
|
'descrizione' => 'Elenco completo documenti con uploader, visibilità e filtro per tipologia',
|
|
|
|
|
'icona' => 'fa-file-alt',
|
|
|
|
|
'colore' => 'indigo',
|
|
|
|
|
'has_tipologia_filter' => true,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'scadenze_documenti',
|
|
|
|
|
'nome' => 'Scadenze Documenti',
|
|
|
|
|
'descrizione' => 'Individui con scadenza documento, filtrabile per tipo documento',
|
|
|
|
|
'icona' => 'fa-hourglass-half',
|
|
|
|
|
'colore' => 'danger',
|
|
|
|
|
'has_tipologia_filter' => true,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'individui_completo',
|
|
|
|
|
'nome' => 'Anagrafica Completa',
|
|
|
|
|
'descrizione' => 'Elenco completo individui con contatti email e gruppi di appartenenza',
|
|
|
|
|
'icona' => 'fa-address-card',
|
|
|
|
|
'colore' => 'info',
|
|
|
|
|
'has_tipologia_filter' => false,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'gruppi_completo',
|
|
|
|
|
'nome' => 'Gruppi (dettaglio completo)',
|
|
|
|
|
'descrizione' => 'Gruppi con conteggio membri, diocesi e sottogruppi',
|
|
|
|
|
'icona' => 'fa-sitemap',
|
|
|
|
|
'colore' => 'warning',
|
|
|
|
|
'has_tipologia_filter' => false,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'eventi_completo',
|
|
|
|
|
'nome' => 'Eventi (dettaglio completo)',
|
|
|
|
|
'descrizione' => 'Tutti gli eventi con gruppi associati, responsabili e luogo',
|
|
|
|
|
'icona' => 'fa-calendar-check',
|
|
|
|
|
'colore' => 'success',
|
|
|
|
|
'has_tipologia_filter' => false,
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'id' => 'contatti_completo',
|
|
|
|
|
'nome' => 'Contatti (dettaglio completo)',
|
|
|
|
|
'descrizione' => 'Elenco completo contatti con individuo associato',
|
|
|
|
|
'icona' => 'fa-address-book',
|
|
|
|
|
'colore' => 'teal',
|
|
|
|
|
'has_tipologia_filter' => false,
|
2026-05-26 08:14:29 +02:00
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
2026-05-27 10:45:05 +02:00
|
|
|
|
2026-05-26 08:14:29 +02:00
|
|
|
$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
|
|
|
|
|
{
|
2026-05-27 10:45:05 +02:00
|
|
|
$rootGruppi = Gruppo::whereNull('parent_id')->with(['children', 'diocesi'])->orderBy('nome')->get();
|
2026-05-26 08:14:29 +02:00
|
|
|
|
|
|
|
|
$rows = [];
|
2026-05-27 10:45:05 +02:00
|
|
|
|
2026-05-26 08:14:29 +02:00
|
|
|
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'],
|
2026-05-27 10:45:05 +02:00
|
|
|
'rows' => $rows->map(fn($r) => [ucfirst((string) $r->tipologia), $r->totale])->toArray(),
|
2026-05-26 08:14:29 +02:00
|
|
|
'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'],
|
2026-05-27 10:45:05 +02:00
|
|
|
'rows' => $rows->map(fn($r) => [ucfirst((string) $r->tipo), $r->totale])->toArray(),
|
2026-05-26 08:14:29 +02:00
|
|
|
'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',
|
2026-06-02 20:29:47 +02:00
|
|
|
'email' => $contatto->valore ?? '-',
|
2026-05-26 08:14:29 +02:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
2026-05-27 10:45:05 +02:00
|
|
|
'tipologia' => ucfirst($doc->tipologia ?? ''),
|
|
|
|
|
'visibilita' => ucfirst($doc->visibilita ?? ''),
|
2026-05-26 08:14:29 +02:00
|
|
|
'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,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
protected function reportDocumentiCompleto(?string $tipologiaFilter = null): array
|
|
|
|
|
{
|
|
|
|
|
$query = Documento::with('user')
|
|
|
|
|
->orderBy('created_at', 'desc');
|
|
|
|
|
|
|
|
|
|
if ($tipologiaFilter) {
|
|
|
|
|
$query->where('tipologia', $tipologiaFilter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$documenti = $query->get();
|
|
|
|
|
|
|
|
|
|
$rows = $documenti->map(function ($doc) {
|
|
|
|
|
return [
|
|
|
|
|
'nome' => $doc->nome_file,
|
|
|
|
|
'tipologia' => ucfirst($doc->tipologia ?? ''),
|
|
|
|
|
'visibilita' => ucfirst($doc->visibilita ?? ''),
|
|
|
|
|
'dimensione' => $doc->dimensione ? number_format($doc->dimensione / 1024, 1) . ' KB' : '-',
|
|
|
|
|
'uploader' => $doc->user?->name ?? '-',
|
|
|
|
|
'data' => $doc->created_at->format('d/m/Y'),
|
|
|
|
|
];
|
|
|
|
|
})->toArray();
|
|
|
|
|
|
|
|
|
|
$title = $tipologiaFilter
|
|
|
|
|
? 'Documenti (tipologia: ' . ucfirst($tipologiaFilter) . ')'
|
|
|
|
|
: 'Documenti (dettaglio completo)';
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'title' => $title,
|
|
|
|
|
'headers' => ['Nome File', 'Tipologia', 'Visibilità', 'Dimensione', 'Caricato Da', 'Data'],
|
|
|
|
|
'rows' => array_map(fn($r) => [$r['nome'], $r['tipologia'], $r['visibilita'], $r['dimensione'], $r['uploader'], $r['data']], $rows),
|
|
|
|
|
'summary' => 'Totale documenti: ' . $documenti->count(),
|
|
|
|
|
'detail_rows' => $rows,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function reportScadenzeDocumenti(?string $tipologiaFilter = null): array
|
|
|
|
|
{
|
|
|
|
|
$query = Individuo::whereNotNull('scadenza_documento')
|
|
|
|
|
->whereNotNull('tipo_documento')
|
|
|
|
|
->orderBy('scadenza_documento');
|
|
|
|
|
|
|
|
|
|
if ($tipologiaFilter) {
|
|
|
|
|
$query->where('tipo_documento', $tipologiaFilter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$individui = $query->get();
|
|
|
|
|
|
|
|
|
|
$rows = $individui->map(function ($ind) {
|
|
|
|
|
$scadenza = $ind->scadenza_documento;
|
|
|
|
|
$giorniMancanti = $scadenza ? now()->diffInDays($scadenza, false) : null;
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'codice' => $ind->codice_id,
|
|
|
|
|
'cognome' => $ind->cognome,
|
|
|
|
|
'nome' => $ind->nome,
|
|
|
|
|
'tipo_documento' => ucfirst($ind->tipo_documento ?? ''),
|
|
|
|
|
'numero' => $ind->numero_documento ?? '-',
|
|
|
|
|
'scadenza' => $scadenza?->format('d/m/Y') ?? '-',
|
|
|
|
|
'stato' => $giorniMancanti !== null
|
|
|
|
|
? ($giorniMancanti < 0
|
|
|
|
|
? ('Scaduto da ' . abs($giorniMancanti) . ' gg')
|
|
|
|
|
: ($giorniMancanti <= 30
|
|
|
|
|
? 'Scade tra ' . $giorniMancanti . ' gg'
|
|
|
|
|
: 'In regola (' . $giorniMancanti . ' gg)'))
|
|
|
|
|
: '-',
|
|
|
|
|
];
|
|
|
|
|
})->toArray();
|
|
|
|
|
|
|
|
|
|
$title = $tipologiaFilter
|
|
|
|
|
? 'Scadenze Documenti (tipo: ' . ucfirst($tipologiaFilter) . ')'
|
|
|
|
|
: 'Scadenze Documenti';
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'title' => $title,
|
|
|
|
|
'headers' => ['Codice', 'Cognome', 'Nome', 'Tipo Documento', 'Numero', 'Scadenza', 'Stato'],
|
|
|
|
|
'rows' => array_map(fn($r) => [$r['codice'], $r['cognome'], $r['nome'], $r['tipo_documento'], $r['numero'], $r['scadenza'], $r['stato']], $rows),
|
|
|
|
|
'summary' => 'Individui con documento: ' . $individui->count(),
|
|
|
|
|
'detail_rows' => $rows,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function reportIndividuiCompleto(): array
|
|
|
|
|
{
|
|
|
|
|
$individui = Individuo::with(['contatti', 'gruppi'])
|
|
|
|
|
->orderBy('cognome')
|
|
|
|
|
->orderBy('nome')
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
$rows = $individui->map(function ($ind) {
|
|
|
|
|
$email = $ind->contatti->where('tipo', 'email')->first()?->valore
|
|
|
|
|
?? $ind->email
|
|
|
|
|
?? '-';
|
|
|
|
|
$telefono = $ind->contatti->whereIn('tipo', ['telefono', 'cellulare'])->first()?->valore
|
|
|
|
|
?? $ind->telefono
|
|
|
|
|
?? '-';
|
|
|
|
|
$gruppi = $ind->gruppi->pluck('nome')->implode(', ') ?: '-';
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'codice' => $ind->codice_id,
|
|
|
|
|
'cognome' => $ind->cognome,
|
|
|
|
|
'nome' => $ind->nome,
|
|
|
|
|
'data_nascita' => $ind->data_nascita?->format('d/m/Y') ?? '-',
|
|
|
|
|
'genere' => match ($ind->genere) { 'M' => 'M', 'F' => 'F', default => '-' },
|
|
|
|
|
'email' => $email,
|
|
|
|
|
'telefono' => $telefono,
|
|
|
|
|
'città' => $ind->città ?? '-',
|
|
|
|
|
'gruppi' => $gruppi,
|
|
|
|
|
];
|
|
|
|
|
})->toArray();
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'title' => 'Anagrafica Completa Individui',
|
|
|
|
|
'headers' => ['Codice', 'Cognome', 'Nome', 'Data Nascita', 'Genere', 'Email', 'Telefono', 'Città', 'Gruppi'],
|
|
|
|
|
'rows' => array_map(fn($r) => [$r['codice'], $r['cognome'], $r['nome'], $r['data_nascita'], $r['genere'], $r['email'], $r['telefono'], $r['città'], $r['gruppi']], $rows),
|
|
|
|
|
'summary' => 'Totale individui: ' . $individui->count(),
|
|
|
|
|
'detail_rows' => $rows,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function reportGruppiCompleto(): array
|
|
|
|
|
{
|
|
|
|
|
$gruppi = Gruppo::withCount('individui')
|
|
|
|
|
->with('diocesi')
|
|
|
|
|
->orderBy('nome')
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
$rows = $gruppi->map(function ($g) {
|
|
|
|
|
$parent = $g->parent;
|
|
|
|
|
return [
|
|
|
|
|
'nome' => $g->nome,
|
|
|
|
|
'descrizione' => $g->descrizione ?? '-',
|
|
|
|
|
'diocesi' => $g->diocesi?->nome ?? '-',
|
|
|
|
|
'livello' => $g->parent_id ? (count($g->getAncestors()) + 1) : 0,
|
|
|
|
|
'membri' => $g->individui_count,
|
|
|
|
|
'padre' => $parent?->nome ?? '-',
|
|
|
|
|
'città' => $g->città_incontro ?? '-',
|
|
|
|
|
];
|
|
|
|
|
})->toArray();
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'title' => 'Gruppi (dettaglio completo)',
|
|
|
|
|
'headers' => ['Nome', 'Descrizione', 'Diocesi', 'Livello', 'Membri', 'Padre', 'Città'],
|
|
|
|
|
'rows' => array_map(fn($r) => [$r['nome'], $r['descrizione'], $r['diocesi'], $r['livello'], $r['membri'], $r['padre'], $r['città']], $rows),
|
|
|
|
|
'summary' => 'Totale gruppi: ' . $gruppi->count() . ' | Totale membri: ' . $gruppi->sum('individui_count'),
|
|
|
|
|
'detail_rows' => $rows,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function reportEventiCompleto(): array
|
|
|
|
|
{
|
|
|
|
|
$eventi = Evento::with(['gruppi', 'responsabili'])
|
|
|
|
|
->orderBy('data_specifica', 'desc')
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
$rows = $eventi->map(function ($e) {
|
|
|
|
|
return [
|
|
|
|
|
'nome' => $e->nome_evento,
|
|
|
|
|
'data' => $e->data_specifica?->format('d/m/Y') ?? '-',
|
|
|
|
|
'tipo_evento' => $e->tipo_evento ?? '-',
|
|
|
|
|
'ricorrenza' => 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 (dettaglio completo)',
|
|
|
|
|
'headers' => ['Evento', 'Data', 'Tipo Evento', 'Ricorrenza', 'Gruppi', 'Responsabili', 'Luogo'],
|
|
|
|
|
'rows' => array_map(fn($r) => [$r['nome'], $r['data'], $r['tipo_evento'], $r['ricorrenza'], $r['gruppi'], $r['responsabili'], $r['luogo']], $rows),
|
|
|
|
|
'summary' => 'Totale eventi: ' . $eventi->count(),
|
|
|
|
|
'detail_rows' => $rows,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function reportContattiCompleto(): array
|
|
|
|
|
{
|
|
|
|
|
$contatti = Contatto::with('individuo')
|
|
|
|
|
->orderBy('tipo')
|
|
|
|
|
->orderBy('valore')
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
$rows = $contatti->map(function ($c) {
|
|
|
|
|
return [
|
|
|
|
|
'tipo' => ucfirst($c->tipo),
|
|
|
|
|
'valore' => $c->valore,
|
|
|
|
|
'etichetta' => $c->etichetta ?? '-',
|
|
|
|
|
'primario' => $c->is_primary ? 'Si' : 'No',
|
|
|
|
|
'individuo' => $c->individuo?->nome_completo ?? '-',
|
|
|
|
|
'codice' => $c->individuo?->codice_id ?? '-',
|
|
|
|
|
];
|
|
|
|
|
})->toArray();
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'title' => 'Contatti (dettaglio completo)',
|
|
|
|
|
'headers' => ['Tipo', 'Valore', 'Etichetta', 'Primario', 'Individuo', 'Codice'],
|
|
|
|
|
'rows' => array_map(fn($r) => [$r['tipo'], $r['valore'], $r['etichetta'], $r['primario'], $r['individuo'], $r['codice']], $rows),
|
|
|
|
|
'summary' => 'Totale contatti: ' . $contatti->count(),
|
|
|
|
|
'detail_rows' => $rows,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function runCustomReport(ReportCustom $report): array
|
2026-05-26 08:14:29 +02:00
|
|
|
{
|
|
|
|
|
$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' => [],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
$columns = $config['columns'] ?? [];
|
|
|
|
|
$hasRelations = false;
|
|
|
|
|
|
|
|
|
|
foreach ($columns as $col) {
|
|
|
|
|
if (str_contains($col, '.')) {
|
|
|
|
|
$hasRelations = true;
|
|
|
|
|
$relation = explode('.', $col)[0];
|
|
|
|
|
$query->with($relation);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-26 08:14:29 +02:00
|
|
|
if (!empty($config['search'])) {
|
|
|
|
|
$search = $config['search'];
|
2026-05-27 10:45:05 +02:00
|
|
|
$query->where(function ($q) use ($config, $search) {
|
2026-05-26 08:14:29 +02:00
|
|
|
foreach ($config['search_columns'] ?? [] as $col) {
|
2026-05-27 10:45:05 +02:00
|
|
|
if (!str_contains($col, '.')) {
|
|
|
|
|
$q->orWhere($col, 'like', '%' . $search . '%');
|
|
|
|
|
}
|
2026-05-26 08:14:29 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!empty($config['sort_by'])) {
|
2026-05-27 10:45:05 +02:00
|
|
|
$sortBy = $config['sort_by'];
|
|
|
|
|
if (!str_contains($sortBy, '.')) {
|
|
|
|
|
$query->orderBy($sortBy, $config['sort_direction'] ?? 'asc');
|
|
|
|
|
}
|
2026-05-26 08:14:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!empty($config['limit'])) {
|
|
|
|
|
$query->limit((int) $config['limit']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$results = $query->get();
|
|
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
if (empty($columns)) {
|
|
|
|
|
$first = $results->first();
|
|
|
|
|
$columns = $first ? array_keys($first->toArray()) : [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$headers = array_map(function ($col) use ($tipo) {
|
|
|
|
|
return ReportColumnRegistry::getColumnLabel($tipo, $col) ?? $col;
|
|
|
|
|
}, $columns);
|
2026-05-26 08:14:29 +02:00
|
|
|
|
2026-05-27 10:45:05 +02:00
|
|
|
$rows = $results->map(function ($item) use ($columns, $tipo) {
|
2026-05-26 08:14:29 +02:00
|
|
|
$row = [];
|
2026-05-27 10:45:05 +02:00
|
|
|
foreach ($columns as $col) {
|
|
|
|
|
if (str_contains($col, '.')) {
|
|
|
|
|
$row[] = ReportColumnRegistry::resolveRelationValue($item, $col);
|
|
|
|
|
} else {
|
|
|
|
|
$value = $item->{$col} ?? '';
|
|
|
|
|
if ($value instanceof \Carbon\Carbon) {
|
|
|
|
|
$value = $value->format('d/m/Y');
|
|
|
|
|
} elseif (is_bool($value)) {
|
|
|
|
|
$value = $value ? 'Si' : 'No';
|
|
|
|
|
}
|
|
|
|
|
$row[] = $value;
|
2026-05-26 08:14:29 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $row;
|
|
|
|
|
})->toArray();
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'title' => $report->nome,
|
|
|
|
|
'headers' => $headers,
|
|
|
|
|
'rows' => $rows,
|
|
|
|
|
'summary' => 'Risultati: ' . count($rows),
|
|
|
|
|
'detail_rows' => $results->map(fn($r) => $r->toArray())->toArray(),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
}
|