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:
2026-10-09 16:35:51 +02:00
parent 9c37c1ad7b
commit d433c9392d
17 changed files with 1361 additions and 457 deletions

View File

@@ -8,6 +8,7 @@ use App\Models\Territorio;
use App\Models\Proclamatore;
use App\Models\Assegnazione;
use App\Models\AnnoTeocratico;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class Assegna extends Component
@@ -43,34 +44,58 @@ class Assegna extends Component
$territorio = Territorio::findOrFail($this->territorio_id);
// Check territory is available (not currently assigned)
// Gli errori vanno sul campo e non in sessione: il messaggio flash
// sta nel telaio della pagina, che Livewire non ridisegna, e il
// rifiuto passerebbe inosservato.
if ($territorio->assegnazioneCorrente) {
session()->flash('error', "Il territorio {$territorio->numero} è già assegnato a {$territorio->assegnazioneCorrente->proclamatore->nome_completo}.");
$this->addError('territorio_id', "Il territorio {$territorio->numero} è già assegnato a {$territorio->assegnazioneCorrente->proclamatore->nome_completo}.");
return;
}
// Check territory is active
if (!$territorio->attivo) {
session()->flash('error', "Il territorio {$territorio->numero} è inattivo.");
$this->addError('territorio_id', "Il territorio {$territorio->numero} è inattivo.");
return;
}
$proclamatore = Proclamatore::findOrFail($this->proclamatore_id);
if (!$proclamatore->attivo) {
session()->flash('error', "Il proclamatore {$proclamatore->nome_completo} è inattivo.");
$this->addError('proclamatore_id', "Il proclamatore {$proclamatore->nome_completo} è inattivo.");
return;
}
$assignedDate = \Carbon\Carbon::parse($this->assigned_at);
$annoTeocratico = AnnoTeocratico::perData($assignedDate);
$assegnazione = Assegnazione::create([
'territorio_id' => $this->territorio_id,
'proclamatore_id' => $this->proclamatore_id,
'anno_teocratico_id' => $annoTeocratico->id,
'assigned_at' => $assignedDate,
'created_by' => auth()->id(),
]);
// Controllo e inserimento sotto lo stesso lock sulla riga del
// territorio: un doppio clic o due schede aperte non possono più
// superare entrambi il controllo e creare due assegnazioni aperte.
$esito = DB::transaction(function () use ($territorio, $assignedDate, $annoTeocratico) {
Territorio::whereKey($territorio->id)->lockForUpdate()->first();
$conflitto = Assegnazione::sovrappostaA($territorio->id, $assignedDate);
if ($conflitto) {
return "Il territorio {$territorio->numero} risulta assegnato {$conflitto->descrizionePeriodo()}: "
. ($conflitto->returned_at
? 'la data di assegnazione non può essere precedente al suo rientro.'
: 'registrane prima il rientro.');
}
return Assegnazione::create([
'territorio_id' => $this->territorio_id,
'proclamatore_id' => $this->proclamatore_id,
'anno_teocratico_id' => $annoTeocratico->id,
'assigned_at' => $assignedDate,
'created_by' => auth()->id(),
]);
});
if (is_string($esito)) {
$this->addError('assigned_at', $esito);
return;
}
$assegnazione = $esito;
activity()->causedBy(auth()->user())
->performedOn($assegnazione)