Files
termanager2/app/Livewire/Home.php
Francesco Picone 7904c96db8 ++ Richieste dal modulo pubblico, territori cartacei, S-13 mensile per email
- /richiesta: modulo pubblico (nuovo territorio o riconsegna) con campo
  trappola, tempo minimo e limite orario; nome e note cifrati, avviso per
  email a chi ha richieste.gestisci (amministratore, assistente)
- Home: riquadro Richieste con i proclamatori dal nome simile; porta su
  Assegna/Rientra già esistenti, che chiudono la richiesta; Scarta
- Cartaceo sull'assegnazione (Assegna e Il mio gruppo), etichetta
  nell'elenco degli assegnati, pagina Cartacei, passa per l'XML
- registro:invia-s13 agli amministratori l'ultima domenica del mese;
  richieste:pulizia dopo 90 giorni dalla gestione
- Privacy, README e HANDOFF aggiornati; test in RichiesteTest

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 17:32:09 +02:00

196 lines
7.4 KiB
PHP

<?php
namespace App\Livewire;
use Livewire\Component;
use App\Livewire\Concerns\Avvisa;
use App\Models\Territorio;
use App\Models\Proclamatore;
use App\Models\Assegnazione;
use App\Models\AnnoTeocratico;
use App\Models\Campagna;
use App\Models\Richiesta;
use App\Models\Setting;
use App\Services\RichiesteServizio;
use Barryvdh\DomPDF\Facade\Pdf;
class Home extends Component
{
use Avvisa;
/**
* Le richieste dal modulo pubblico si confermano portandole su Assegna o
* Rientra (link nel riquadro); qui si possono solo scartare.
*/
public function scartaRichiesta(int $id): void
{
$this->authorize('richieste.gestisci');
Richiesta::findOrFail($id)->scarta(auth()->user());
$this->avvisa('Richiesta scartata.');
}
/**
* Le richieste aperte, con quello che serve a confermarle: per un
* territorio nuovo i proclamatori dal nome simile a quello scritto, per
* una riconsegna l'assegnazione aperta di quel territorio.
*/
private function richiesteAperte(): \Illuminate\Support\Collection
{
if (! auth()->user()->can('richieste.gestisci')) {
return collect();
}
$aperte = Richiesta::aperte()->orderBy('created_at')->get();
if ($aperte->isEmpty()) {
return collect();
}
$servizio = app(RichiesteServizio::class);
$proclamatori = Proclamatore::attivi()->get();
return $aperte->map(fn (Richiesta $r) => [
'richiesta' => $r,
'simili' => $r->tipo === Richiesta::NUOVO ? $servizio->nomiSimili($r->nome, $proclamatori) : collect(),
'assegnazione' => $r->tipo === Richiesta::RICONSEGNA ? $servizio->assegnazioneAperta($r->numero_territorio) : null,
]);
}
public function downloadPdfDaAssegnare()
{
$this->authorize('territori.assign');
$settings = Setting::instance();
$territori = Territorio::ordinaPerPriorita(
Territorio::inReparto()->with('zona', 'tipologia', 'ultimaAssegnazione')->get()
);
$pdf = Pdf::loadView('pdf.territori-lista', [
'titolo' => 'Territori da Assegnare',
'congregazione' => $settings->congregazione_nome ?? 'TerManager2',
'data' => now()->format('d/m/Y'),
'territori' => $territori,
'tipo' => 'assegnare',
]);
return response()->streamDownload(
fn () => print($pdf->output()),
'territori-da-assegnare-' . now()->format('Y-m-d') . '.pdf'
);
}
public function downloadPdfDaRientrare()
{
$this->authorize('territori.return');
$settings = Setting::instance();
$territori = Territorio::daRientrare()
->with(['zona', 'assegnazioneCorrente.proclamatore'])
->get();
$pdf = Pdf::loadView('pdf.territori-lista', [
'titolo' => 'Territori da Rientrare',
'congregazione' => $settings->congregazione_nome ?? 'TerManager2',
'data' => now()->format('d/m/Y'),
'territori' => $territori,
'tipo' => 'rientrare',
]);
return response()->streamDownload(
fn () => print($pdf->output()),
'territori-da-rientrare-' . now()->format('Y-m-d') . '.pdf'
);
}
public function render()
{
$settings = Setting::instance();
$homeLimit = max(1, (int) ($settings->home_limit_list ?? 10));
$annoCorrente = AnnoTeocratico::corrente();
$campagnaAttiva = Campagna::attiva();
// Territory counts
$totTerritoriAttivi = Territorio::where('attivo', true)->count();
$totAssegnati = Territorio::assegnato()->count();
$totInReparto = Territorio::inReparto()->count();
// Coverage: returned territories per current theocratic year
$territoriPercorsi = 0;
if ($annoCorrente) {
$territoriPercorsi = Assegnazione::where('anno_teocratico_id', $annoCorrente->id)
->whereNotNull('returned_at')
->distinct('territorio_id')
->count('territorio_id');
}
// Monthly average (territories/month this year)
$mediaPercorrenzaMensile = 0;
if ($annoCorrente && $annoCorrente->mesi_trascorsi > 0) {
$mediaPercorrenzaMensile = round($territoriPercorsi / $annoCorrente->mesi_trascorsi, 1);
}
// Average assignment duration in months (current year only, matches old app "Media Percorrenza Congregazione")
//
// Il calcolo sta in PHP e non in SQL. La versione precedente usava
// DATEDIFF() e YEAR(), che sono funzioni MySQL: rendevano questa pagina
// — la piu' visitata dell'applicazione — l'unica non verificabile dalla
// suite di test, che gira su SQLite. Il costo e' due colonne di date per
// le assegnazioni chiuse di un anno di servizio: qualche centinaio di
// righe, non un tavolo intero.
//
// I due filtri sono quelli di prima: le date assurde arrivate dalla
// conversione del vecchio database, e le durate non positive, che
// altrimenti abbasserebbero la media senza significare niente.
$avgGiorni = null;
if ($annoCorrente) {
$durate = Assegnazione::where('anno_teocratico_id', $annoCorrente->id)
->whereNotNull('returned_at')
->get(['assigned_at', 'returned_at'])
->filter(fn (Assegnazione $a) => $a->assigned_at?->year >= 1900)
->map(fn (Assegnazione $a) => $a->assigned_at->diffInDays($a->returned_at, false))
->filter(fn (float $giorni) => $giorni > 0);
$avgGiorni = $durate->isEmpty() ? null : $durate->avg();
}
$mediaDurataPercorrenzaMesi = $avgGiorni ? round($avgGiorni / 30.44, 1) : 0;
// Campaign stats
$campagnaStats = null;
if ($campagnaAttiva) {
$campagnaStats = [
'descrizione' => $campagnaAttiva->descrizione,
'percentuale' => $campagnaAttiva->percentuale_percorrenza,
'fine' => $campagnaAttiva->end_date->format('d/m/Y'),
];
}
// Quick lists: la logica di priorità sta in Territorio::ordinaPerPriorita
// (la usano anche il pacchetto dei gruppi di servizio e "Il mio gruppo").
$territoriDaAssegnare = Territorio::ordinaPerPriorita(
Territorio::inReparto()->with('zona', 'tipologia', 'ultimaAssegnazione')->get()
)->take($homeLimit)->values();
$daRientrare = Territorio::daRientrare()
->with(['zona', 'assegnazioneCorrente.proclamatore'])
->take($homeLimit)
->get();
return view('livewire.home', [
'congregazione' => $settings->congregazione_nome ?? 'TerManager2',
'annoCorrente' => $annoCorrente,
'totTerritoriAttivi' => $totTerritoriAttivi,
'totAssegnati' => $totAssegnati,
'totInReparto' => $totInReparto,
'territoriPercorsi' => $territoriPercorsi,
'mediaPercorrenzaMensile' => $mediaPercorrenzaMensile,
'mediaDurataPercorrenzaMesi' => $mediaDurataPercorrenzaMesi,
'campagnaStats' => $campagnaStats,
'homeLimit' => $homeLimit,
'territoriDaAssegnare' => $territoriDaAssegnare,
'daRientrare' => $daRientrare,
'richieste' => $this->richiesteAperte(),
]);
}
}