- /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>
196 lines
7.4 KiB
PHP
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(),
|
|
]);
|
|
}
|
|
}
|