S-13 sul modulo ufficiale e correzioni all'integrità dei dati
Modulo S-13 - Il PDF scrive sopra il modulo ufficiale (FPDI, matrice convertita in PDF 1.4) invece di ricostruirlo in HTML: 20 righe per foglio come l'originale. - Una riga per ogni territorio attivo; riporto dell'ultimo rientro prima dell'anno; assegnazioni a cavallo d'anno su entrambi i fogli; scelta per data e non per anno registrato; nomi come "Cognome I.". - Logica spostata in App\Services\Registro\ModuloS13. Integrità dei dati - Export/import XML (e quindi backup/ripristino): le date non slittano più di un giorno; i file vecchi in UTC tornano al giorno giusto. - Import XML: rifiuta file estranei o incompleti senza toccare i dati, errore con riga e motivo, territori senza zona importati con il loro storico, caratteri di controllo rimossi in export, chi importa conserva password e ruolo di amministratore. - Assegnazioni: assegnazioneCorrente corretta (filtro dentro ofMany), periodi sovrapposti rifiutati in Assegna e Registro sotto lock, anno di servizio ricavato dalla data, niente date future nel Registro. - Utenti: non si declassa sé stessi né l'ultimo amministratore. - Entrypoint: non sovrascrive più la copia persistente di APP_KEY con un valore diverso; serve APP_KEY_ROTATE=1 per un cambio voluto. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -8,7 +8,7 @@
|
||||
Nuova voce
|
||||
</x-btn>
|
||||
<x-btn variante="secondary" wire:click="openS13"
|
||||
title="Scarica il modulo S-13 compilato con le ultime 4 assegnazioni di un anno di servizio">
|
||||
title="Scarica il modulo S-13 compilato per un anno di servizio">
|
||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3M3 17V7a2 2 0 012-2h5l2 2h7a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z"/></svg>
|
||||
Modulo S-13
|
||||
</x-btn>
|
||||
@@ -210,18 +210,8 @@
|
||||
@error('form_proclamatore_id') <span style="color:#dc2626;font-size:12px;">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
{{-- Anno teocratico --}}
|
||||
<div>
|
||||
<label style="font-size:13px;font-weight:600;color:#374151;display:block;margin-bottom:4px;">Anno teocratico *</label>
|
||||
<select wire:model="form_anno_id" style="width:100%;border:1px solid #d1d5db;border-radius:8px;padding:8px 10px;font-size:14px;">
|
||||
<option value="">-- seleziona --</option>
|
||||
@foreach($anni as $anno)
|
||||
<option value="{{ $anno->id }}">{{ $anno->label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('form_anno_id') <span style="color:#dc2626;font-size:12px;">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
{{-- L'anno di servizio non si sceglie: lo ricava il salvataggio
|
||||
dalla data di assegnazione. --}}
|
||||
{{-- Campagna --}}
|
||||
<div>
|
||||
<label style="font-size:13px;font-weight:600;color:#374151;display:block;margin-bottom:4px;">Campagna</label>
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
{{--
|
||||
Modulo S-13-I — Registrazione delle assegnazioni del territorio.
|
||||
|
||||
Ricostruzione fedele del modulo ufficiale, la cui copia è in
|
||||
resources/pdf/S-13_I.pdf e va usata come matrice per qualsiasi modifica
|
||||
alla griglia. Il modulo originale è un PDF 1.7 con font CID embeddati:
|
||||
sovrapporvi testo richiederebbe il parser a pagamento di FPDI, mentre
|
||||
dompdf è già in uso per gli altri export del progetto.
|
||||
|
||||
Struttura per pagina: 21 blocchi-territorio, ognuno su due righe — quella
|
||||
alta ospita i nomi dei proclamatori, quella bassa le date. Le colonne
|
||||
"Terr. N." e "Restituito l'ultima volta il" occupano entrambe le righe.
|
||||
|
||||
Il motore CSS di dompdf non è un browser: le larghezze stanno in <colgroup>
|
||||
(con i soli colspan sarebbero ripartite a caso) e l'interruzione di pagina è
|
||||
decisa dall'indice del ciclo, perché il supporto a :last-child non è
|
||||
garantito e un foglio bianco in coda sarebbe visibile.
|
||||
--}}
|
||||
@php
|
||||
/**
|
||||
* I nomi devono entrare nella loro cella senza sfondare la griglia: le
|
||||
* righe hanno altezza fissa, quindi una riga di troppo sballerebbe tutto
|
||||
* il foglio. Invece di andare a capo si scala il corpo.
|
||||
*
|
||||
* La larghezza si stima a caratteri: 0.20mm per carattere per punto di
|
||||
* corpo. È deliberatamente pessimistica — la media reale di DejaVu Sans è
|
||||
* ~0.52em (0.1835mm), ma sui nomi propri, pieni di maiuscole, la media non
|
||||
* regge e con zero margine il testo deborda dalla cella. Sotto i 4.5pt il
|
||||
* testo non sarebbe più leggibile, quindi oltre quel limite si tronca:
|
||||
* l'ellissi rende visibile l'accorciamento, mentre un nome che deborda
|
||||
* corromperebbe la griglia in silenzio.
|
||||
*
|
||||
* La larghezza qui sotto è misurata sul PDF renderizzato, non dedotta
|
||||
* dalle percentuali: al netto di padding e bordi la cella risulta 35.9mm,
|
||||
* e la costante tiene un margine su quel valore. Se cambiano le
|
||||
* percentuali delle colonne o i margini di pagina, va rimisurata.
|
||||
*
|
||||
* "Assegnato a" è nowrap su una riga sola: basta il corpo che ci sta, e
|
||||
* oltre il minimo si tronca. La colonna del riporto ospita solo una data,
|
||||
* quindi non ha bisogno di adattamenti.
|
||||
*/
|
||||
$MM_PER_CARATTERE_PER_PUNTO = 0.20;
|
||||
$CORPO_MIN_PT = 4.5;
|
||||
|
||||
$LARGHEZZA_NOME_MM = 34.0; // cella misurata 35.9mm
|
||||
|
||||
$corpoPer = static function (string $testo, float $larghezzaMm, float $corpoMaxPt) use ($MM_PER_CARATTERE_PER_PUNTO, $CORPO_MIN_PT): string {
|
||||
$len = mb_strlen($testo);
|
||||
|
||||
if ($len === 0) {
|
||||
return $corpoMaxPt . 'pt';
|
||||
}
|
||||
|
||||
$pt = $larghezzaMm / ($len * $MM_PER_CARATTERE_PER_PUNTO);
|
||||
|
||||
return round(max($CORPO_MIN_PT, min($corpoMaxPt, $pt)), 1) . 'pt';
|
||||
};
|
||||
|
||||
$troncaPer = static function (string $testo, float $larghezzaMm) use ($MM_PER_CARATTERE_PER_PUNTO, $CORPO_MIN_PT): string {
|
||||
$max = (int) floor($larghezzaMm / ($CORPO_MIN_PT * $MM_PER_CARATTERE_PER_PUNTO));
|
||||
|
||||
return mb_strlen($testo) > $max ? mb_substr($testo, 0, $max - 1) . '…' : $testo;
|
||||
};
|
||||
|
||||
$ultimaPagina = $pagine->count() - 1;
|
||||
@endphp
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>S-13 — Registrazione delle assegnazioni del territorio</title>
|
||||
<style>
|
||||
/* dompdf riporta i margini di @page sull'elemento body, quindi il
|
||||
reset universale qui sotto li azzererebbe: vanno ridichiarati su
|
||||
body, che per specificità vince su "*". Tenere i due valori
|
||||
allineati — è @page a definire l'area utile, body a farla
|
||||
sopravvivere al reset.
|
||||
I margini laterali sono i più larghi perché sono quelli che si
|
||||
vedono; in verticale si sta stretti, il foglio pieno misura ~266mm
|
||||
e ne restano 273. Alzando questi valori, o gli spazi sotto il
|
||||
titolo e sopra il codice, il piè di pagina scivola su una pagina
|
||||
nuova e ogni foglio ne occupa due: rimisurare dopo ogni modifica. */
|
||||
@page { margin: 14mm 16mm 10mm 16mm; }
|
||||
|
||||
* { margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
margin: 14mm 16mm 10mm 16mm;
|
||||
font-family: "DejaVu Sans", sans-serif;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.interrompi { page-break-after: always; }
|
||||
|
||||
h1 {
|
||||
font-size: 13pt;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
letter-spacing: 0.2pt;
|
||||
margin-bottom: 6mm;
|
||||
}
|
||||
|
||||
.anno {
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
margin-bottom: 3mm;
|
||||
}
|
||||
.anno .valore {
|
||||
display: inline-block;
|
||||
width: 32mm;
|
||||
border-bottom: 1px solid #000;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
padding-bottom: 0.4mm;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1.6pt solid #000;
|
||||
/* Obbligatorio, non cosmetico: in layout automatico le larghezze
|
||||
del <colgroup> sono solo un suggerimento e una cella con del
|
||||
testo lungo (un nome di proclamatore) si allarga schiacciando le
|
||||
altre. I corpi dei nomi sono calcolati su larghezze fisse,
|
||||
quindi a quel punto il testo deborda. */
|
||||
table-layout: fixed;
|
||||
}
|
||||
td, th {
|
||||
border: 0.5pt solid #000;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Intestazione */
|
||||
thead th { font-weight: normal; padding: 0.5mm 0.5mm; }
|
||||
th.terr { font-size: 7.5pt; line-height: 1.15; }
|
||||
/* 6.2pt e non di più: a 6.6pt "l'ultima volta il*" non entra nei
|
||||
~21mm utili della colonna e l'intestazione passa da due righe a tre,
|
||||
allontanandosi dal modulo originale. */
|
||||
th.restituito { font-size: 6.2pt; line-height: 1.15; white-space: nowrap; }
|
||||
th.assegnato-a { font-size: 8pt; }
|
||||
th.sotto { font-size: 6.4pt; }
|
||||
|
||||
/* Corpo */
|
||||
/* 9.4mm a blocco: 21 blocchi devono stare in un foglio come sul modulo
|
||||
cartaceo. Alzando queste altezze l'ultimo territorio scivola su una
|
||||
pagina nuova e ogni foglio ne occupa due. */
|
||||
tr.riga-nome td { height: 5.6mm; }
|
||||
tr.riga-date td { height: 3.8mm; }
|
||||
|
||||
td.numero { font-size: 9.5pt; font-weight: bold; }
|
||||
td.data { font-size: 7.5pt; }
|
||||
|
||||
/* La colonna del riporto è alta due righe e ospita solo la data di
|
||||
rientro con cui si chiudeva il ciclo precedente. */
|
||||
td.riporto { font-size: 7.5pt; padding: 0 0.5mm; }
|
||||
td.nome { line-height: 1.1; padding: 0 0.6mm; white-space: nowrap; }
|
||||
|
||||
.nota { font-size: 7.4pt; margin-top: 1.6mm; }
|
||||
.codice { font-size: 8pt; margin-top: 3mm; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@foreach($pagine as $indice => $righe)
|
||||
<div @if($indice < $ultimaPagina) class="interrompi" @endif>
|
||||
<h1>REGISTRAZIONE DELLE ASSEGNAZIONI DEL TERRITORIO</h1>
|
||||
|
||||
<div class="anno">Anno di servizio: <span class="valore">{{ $annoLabel }}</span></div>
|
||||
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width:6.5%">
|
||||
<col style="width:12.5%">
|
||||
@for($c = 0; $c < 8; $c++)
|
||||
<col style="width:10.125%">
|
||||
@endfor
|
||||
</colgroup>
|
||||
<thead>
|
||||
{{-- Le larghezze stanno sia nel <colgroup> sia qui: in layout
|
||||
fisso dompdf le ricava dalle celle della prima riga, e
|
||||
senza queste le due colonne rowspan si spartiscono lo
|
||||
spazio in parti uguali invece di rispettare il colgroup. --}}
|
||||
<tr>
|
||||
<th class="terr" rowspan="2" style="width:6.5%">Terr.<br>N.</th>
|
||||
<th class="restituito" rowspan="2" style="width:12.5%">Restituito<br>l’ultima volta il*</th>
|
||||
@for($g = 0; $g < 4; $g++)
|
||||
<th class="assegnato-a" colspan="2" style="width:20.25%">Assegnato a</th>
|
||||
@endfor
|
||||
</tr>
|
||||
<tr>
|
||||
@for($g = 0; $g < 4; $g++)
|
||||
<th class="sotto">Assegnato il</th>
|
||||
<th class="sotto">Restituito il</th>
|
||||
@endfor
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($righe as $riga)
|
||||
<tr class="riga-nome">
|
||||
<td class="numero" rowspan="2">{{ $riga['numero'] }}</td>
|
||||
<td class="riporto" rowspan="2">{{ $riga['riporto'] }}</td>
|
||||
@foreach($riga['assegnazioni'] as $a)
|
||||
@php($nome = $troncaPer($a['nome'], $LARGHEZZA_NOME_MM))
|
||||
<td class="nome" colspan="2" style="font-size:{{ $corpoPer($nome, $LARGHEZZA_NOME_MM, 8) }}">{{ $nome }}</td>
|
||||
@endforeach
|
||||
</tr>
|
||||
<tr class="riga-date">
|
||||
@foreach($riga['assegnazioni'] as $a)
|
||||
<td class="data">{{ $a['assegnato_il'] }}</td>
|
||||
<td class="data">{{ $a['restituito_il'] }}</td>
|
||||
@endforeach
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="nota">*Quando si inizia un nuovo foglio, registrare in questa colonna l’ultima data in cui è stato restituito ogni territorio.</div>
|
||||
<div class="codice">S-13-I 1/22</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user