++ add creation S-13_I

This commit is contained in:
2026-07-28 17:04:04 +02:00
parent 561153c754
commit 94226e460a
5 changed files with 340 additions and 4 deletions

View File

@@ -11,11 +11,18 @@ use App\Models\Proclamatore;
use App\Models\Territorio;
use App\Models\Zona;
use App\Models\Tipologia;
use Barryvdh\DomPDF\Facade\Pdf;
class Registro extends Component
{
use WithPagination;
/** Blocchi-territorio per foglio, come nel modulo cartaceo S-13. */
private const S13_RIGHE_PER_PAGINA = 21;
/** Gruppi "Assegnato a" disponibili su ogni riga del modulo S-13. */
private const S13_ASSEGNAZIONI_PER_RIGA = 4;
public string $search = '';
public string $filtroAnno = '';
public string $filtroZona = '';
@@ -167,6 +174,156 @@ class Registro extends Component
$this->showDeleteConfirm = false;
}
// ─── Modulo S-13 ────────────────────────────────────────────
/**
* Modulo ufficiale S-13-I ("Registrazione delle assegnazioni del
* territorio") compilato con le assegnazioni dell'anno di servizio
* selezionato nei filtri.
*
* Solo amministratore, come il resto del CRUD di questa pagina: il modulo
* espone in un unico file l'anagrafica completa di chi ha avuto quale
* territorio, dati personali che il ruolo assegnatario non deve poter
* esportare in blocco.
*/
public function downloadS13()
{
abort_if(!auth()->user()->can('settings.manage'), 403);
// Il modulo è per definizione relativo a un singolo anno di servizio:
// con "Tutti gli anni" selezionato si ripiega sull'anno corrente.
$anno = $this->filtroAnno
? AnnoTeocratico::find($this->filtroAnno)
: null;
$anno ??= AnnoTeocratico::corrente();
$pdf = Pdf::loadView('pdf.registro-s13', [
'annoLabel' => $anno->label,
'pagine' => $this->impaginaS13($this->righeS13($anno)),
])->setPaper('a4', 'portrait');
return response()->streamDownload(
fn () => print($pdf->output()),
'S-13-registro-assegnazioni-' . $anno->label . '.pdf'
);
}
/**
* Impagina le righe in fogli da 21 blocchi, completando l'ultimo con
* blocchi vuoti: il foglio stampato resta identico al modulo cartaceo e le
* righe libere restano disponibili per la compilazione a mano.
*
* Senza righe da stampare si restituisce comunque un foglio vuoto, così il
* modulo scaricato è un S-13 in bianco anziché un PDF senza pagine.
*
* @param \Illuminate\Support\Collection<int, array> $righe
* @return \Illuminate\Support\Collection<int, \Illuminate\Support\Collection<int, array>>
*/
private function impaginaS13(\Illuminate\Support\Collection $righe): \Illuminate\Support\Collection
{
$vuota = [
'numero' => '',
'rientro_precedente' => '',
'assegnazioni' => $this->completaCelleS13([]),
];
$pagine = $righe->isEmpty()
? collect([collect()])
: $righe->chunk(self::S13_RIGHE_PER_PAGINA)->map->values()->values();
return $pagine->map(fn ($pagina) => collect(
array_pad($pagina->all(), self::S13_RIGHE_PER_PAGINA, $vuota)
));
}
/**
* Righe del modulo: un blocco per ogni territorio attivo con almeno
* un'assegnazione (chiusa o in corso) nell'anno indicato, in ordine di
* numero crescente.
*
* @return \Illuminate\Support\Collection<int, array>
*/
private function righeS13(AnnoTeocratico $anno): \Illuminate\Support\Collection
{
// territorio() è definita withTrashed(), quindi la condizione sui
// cestinati va scritta a mano: senza, i territori eliminati
// rientrerebbero nel modulo.
$assegnazioni = Assegnazione::with(['proclamatore', 'territorio'])
->where('anno_teocratico_id', $anno->id)
->whereHas('territorio', fn ($q) => $q
->where('attivo', true)
->whereNull('territori.deleted_at'))
->orderBy('assigned_at')
->orderBy('id')
->get()
->groupBy('territorio_id');
$rientriPrecedenti = $this->ultimiRientriPrimaDi(
$assegnazioni->keys()->all(),
$anno
);
return $assegnazioni
->map(function ($gruppo, $territorioId) use ($rientriPrecedenti) {
// Se un territorio è girato più di quattro volte nell'anno il
// modulo non ha spazio per tutte: si stampano le ultime
// quattro, mantenendo l'ordine cronologico da sinistra.
$ultime = $gruppo->slice(-self::S13_ASSEGNAZIONI_PER_RIGA)->values();
$celle = $ultime->map(fn (Assegnazione $a) => [
'nome' => $a->proclamatore?->nome_completo ?? '',
'assegnato_il' => $a->assigned_at?->format('d/m/Y') ?? '',
'restituito_il' => $a->returned_at?->format('d/m/Y') ?? '',
])->all();
$rientro = $rientriPrecedenti->get($territorioId);
return [
'numero' => $gruppo->first()->territorio?->numero ?? '',
'rientro_precedente' => $rientro ? \Carbon\Carbon::parse($rientro)->format('d/m/Y') : '',
'assegnazioni' => $this->completaCelleS13($celle),
];
})
->sort(fn (array $a, array $b) => strnatcasecmp((string) $a['numero'], (string) $b['numero']))
->values();
}
/**
* Ultima data di rientro di ogni territorio *precedente* all'inizio
* dell'anno di servizio: è il dato che la colonna "Restituito l'ultima
* volta il" del modulo chiede di riportare quando si apre un foglio nuovo.
*
* @param array<int, int> $territorioIds
* @return \Illuminate\Support\Collection<int, string>
*/
private function ultimiRientriPrimaDi(array $territorioIds, AnnoTeocratico $anno): \Illuminate\Support\Collection
{
if ($territorioIds === []) {
return collect();
}
return Assegnazione::whereIn('territorio_id', $territorioIds)
->whereNotNull('returned_at')
->whereDate('returned_at', '<', $anno->start_date)
->groupBy('territorio_id')
->selectRaw('territorio_id, MAX(returned_at) as ultimo_rientro')
->pluck('ultimo_rientro', 'territorio_id');
}
/**
* Porta le celle a quattro riempiendo con voci vuote: le colonne
* inutilizzate restano stampate e a disposizione della compilazione a mano.
*
* @param array<int, array> $celle
* @return array<int, array>
*/
private function completaCelleS13(array $celle): array
{
$vuota = ['nome' => '', 'assegnato_il' => '', 'restituito_il' => ''];
return array_pad($celle, self::S13_ASSEGNAZIONI_PER_RIGA, $vuota);
}
public function mount(): void
{
if ($this->filtroAnno === '') {