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:
41
HANDOFF.md
41
HANDOFF.md
@@ -938,10 +938,12 @@ Le scelte che vale la pena conoscere:
|
||||
non parla di cifratura riesce a scrivere una riga. Non ha alcun rapporto con
|
||||
la chiave di produzione, che vive nell'ambiente e nel volume.
|
||||
|
||||
- **`righeS13()` si chiama in riflessione.** È privata di proposito (non è
|
||||
un'azione Livewire) ma è il calcolo, non il PDF, quello che va protetto:
|
||||
renderla pubblica solo per poterla testare avrebbe allargato la superficie
|
||||
del componente per comodità del test.
|
||||
- **Il modulo S-13 è un servizio, non un metodo del componente.** Calcolo e
|
||||
PDF stanno in `App\Services\Registro\ModuloS13`, che i test chiamano
|
||||
direttamente; il componente `Registro` si limita a validare l'anno e
|
||||
scaricare. Il PDF scrive sopra il modulo ufficiale
|
||||
(`resources/pdf/S-13_I.pdf`, convertito in PDF 1.4 per FPDI) alle
|
||||
coordinate della sua griglia, come la cartolina S-12.
|
||||
|
||||
### Un solo container esegue le migrazioni
|
||||
|
||||
@@ -956,6 +958,12 @@ mancante — con il rischio di chiavi diverse a ogni richiesta e dati cifrati
|
||||
irrecuperabili. Ora è risolta dall'entrypoint (env → volume → generata) e il
|
||||
provider **solleva un'eccezione** se manca, invece di rigenerarla.
|
||||
|
||||
Dal 09/10/2026 l'entrypoint **si rifiuta di partire** se `APP_KEY`
|
||||
nell'ambiente è diversa dalla copia in `storage/app/.app_key`: prima la
|
||||
sovrascriveva, e un valore sbagliato in Dokploy distruggeva l'unica copia della
|
||||
chiave giusta. Per un cambio voluto si aggiunge `APP_KEY_ROTATE=1` (la chiave
|
||||
vecchia resta in `.app_key.prev-<data>`) e poi la si toglie.
|
||||
|
||||
---
|
||||
|
||||
## Bug corretti — i più significativi
|
||||
@@ -984,6 +992,14 @@ Contesto utile se ti imbatti in comportamenti che sembrano regressioni.
|
||||
| Eliminazione definitiva territorio | La riga spariva, mappa e miniatura restavano orfane sul volume per sempre | `Territori/TerritorioCestino` |
|
||||
| Virgolette doppie in `x-data` | Attributo HTML chiuso a metà: il JavaScript finiva a video come testo | `backup-manager.blade.php` |
|
||||
| Warning di `addFile()` al posto del messaggio | Su file illeggibile l'amministratore leggeva `ZipArchive::addFile(): No such file or directory` invece della frase che dice quale file e cosa controllare | `Services/BackupArchive` |
|
||||
| Date esportate in UTC | Ogni backup → ripristino (e export → import XML) spostava **tutte** le date indietro di un giorno, sommandosi a ogni giro. I file vecchi vengono riportati al giorno giusto all'import | `XmlExchange::righeEsportate()`, `normalizeDateForDb()` |
|
||||
| Import di qualsiasi XML | Un XML ben formato ma estraneo svuotava il database con "completato con successo". Ora servono la radice `<termanager2>` e le sezioni principali | `XmlExchange::importXmlFromContent()` |
|
||||
| Caratteri di controllo nell'export | Uno solo, in una nota, rendeva il file (e il `data.xml` del backup) non reimportabile | `XmlExchange::addXmlText()` |
|
||||
| Territori senza zona scartati all'import | Sparivano con tutto il loro storico di assegnazioni | `XmlExchange::importXmlFromContent()` |
|
||||
| Import che estromette chi lo esegue | Password vecchia e ruoli del file applicati anche all'amministratore che importava | `XmlExchange::importXmlFromContent()` |
|
||||
| `whereNull` fuori da `latestOfMany` | Con una chiusa più recente dell'aperta il territorio risultava libero mentre era fuori, e assegnabile due volte | `Territorio::assegnazioneCorrente()` |
|
||||
| Periodi di assegnazione accavallati | Uscite retrodatate a prima dell'ultimo rientro, doppie aperte dal Registro, anno scelto a mano incoerente con la data | `Assegnazione::sovrappostaA()`, `Assegna`, `Registro` |
|
||||
| Declassamento dell'ultimo amministratore | Togliendosi il ruolo l'installazione restava senza nessuno che potesse gestirla | `Settings/UsersIndex` |
|
||||
| `GREATEST()` in migrazione | Sintassi MySQL: lo schema non si costruiva su SQLite, quindi la suite di test non poteva partire | migrazione `2026_04_08_123500` |
|
||||
| `@disabled` dentro `<x-btn>` | Direttiva non compilata: il tag restava letterale e la pagina moriva con «unexpected token endif» | viste di `settings` |
|
||||
| Segno del download consumato dal componente sbagliato | L'archivio pronto non sarebbe arrivato a nessuno — e per l'azzeramento è il passo che sblocca la cancellazione | `SegueOperazioneBackup::scaricaOra()` |
|
||||
@@ -1031,7 +1047,7 @@ docker compose --profile test run --build --rm test
|
||||
|------|---------------|
|
||||
| `tests/Unit/BackupArchiveTest.php` | Round-trip crea → riestrai, manifest, avanzamento fino al totale, fallimento su file illeggibile con archivio rimosso, rifiuto di manifest mancante/illeggibile/di formato futuro e di `data.xml` mancante o vuoto, zip slip (percorso assoluto, `..`, unità Windows) e normalizzazione dei percorsi validi, staging ripulito dopo un fallimento, sostituzione totale del disco al ripristino, svuotamento con conteggio |
|
||||
| `tests/Unit/BackupOperationStateTest.php` | Il posto unico: occupazione, rilascio a completamento e a fallimento, puntatore orfano, operazione già chiusa, chiusura tardiva che non deve liberare il posto della nuova, attesa-download che lo tiene occupato, azzeramento della barra al cambio fase, coda dei log limitata |
|
||||
| `tests/Feature/RegistroS13Test.php` | Il ciclo da quattro gruppi del modulo S-13 (1/3/4/5/8/9 assegnazioni), la colonna "Restituito l'ultima volta il" compreso il caso in cui l'ultima del giro precedente è ancora aperta, esclusione di territori disattivati e cestinati, filtro sull'anno di servizio, ordinamento naturale dei numeri |
|
||||
| `tests/Feature/RegistroS13Test.php` | Il ciclo da quattro gruppi del modulo S-13 (1/3/4/5/8/9 assegnazioni), la colonna "Restituito l'ultima volta il" (fine ciclo e ultimo rientro prima dell'anno), assegnazioni a cavallo d'anno su entrambi i fogli, una riga per ogni territorio attivo anche senza assegnazioni, esclusione di territori disattivati e cestinati, ordinamento naturale dei numeri, fogli da 20 righe e generazione del PDF |
|
||||
| `tests/Feature/ZonaPericolosaTest.php` | I due tempi dell'azzeramento: frase di conferma, backup preventivo senza cancellazione, rifiuto se il posto è occupato, conferma senza archivio servito, senza password, con password sbagliata, blocco dopo cinque tentativi, annullamento che libera il posto lasciando il backup valido. In più: che ogni operazione abbia un solo componente a raccontarla, e che il segno del download non venga consumato da chi non la possiede |
|
||||
| `tests/Feature/PaginaBackupImportTest.php` | La pagina servita per intero: permessi, le cinque zone con la loro ancora **e nel loro ordine**, la console unica, l'assenza dei vecchi riquadri di log, che nessun tag `<x-btn` sia rimasto da compilare, che i pulsanti usino il sistema unico, e che la console sia definita prima del pacchetto che avvia Alpine |
|
||||
| `tests/Feature/PagineApplicativeTest.php` | **Ogni** pagina dell'applicazione si apre davvero (200) e non ha markup rimasto da compilare. È la rete che serve quando una modifica tocca tutte le viste in una volta: un errore di markup non rompe niente finché qualcuno non apre quella pagina |
|
||||
@@ -1073,15 +1089,14 @@ nel browser, e nella suite non c'è un browser. Quella parte va provata a mano.
|
||||
Attenzione a non scrivere test su ciò che SQLite non sa eseguire: la dashboard
|
||||
(`Home`) e l'ordinamento di `Registro::render()` usano SQL specifico di MySQL.
|
||||
|
||||
### 2. Race su doppia assegnazione
|
||||
### 2. Race su doppia assegnazione — chiusa il 09/10/2026
|
||||
|
||||
`Assegna::save()` controlla che il territorio sia libero e poi crea la riga, ma
|
||||
nulla a livello di database impedisce due assegnazioni aperte sullo stesso
|
||||
territorio (doppio click, due schede). `assegnazioneCorrente` usa `latestOfMany`,
|
||||
quindi la seconda resta invisibile nell'interfaccia mentre falsa i conteggi.
|
||||
|
||||
Rimedio: transazione con `lockForUpdate()`, oppure un indice univoco su colonna
|
||||
generata che imponga una sola assegnazione aperta per territorio.
|
||||
`Assegna::save()` e il salvataggio del Registro controllano e scrivono dentro
|
||||
una transazione con `lockForUpdate()` sulla riga del territorio, e rifiutano
|
||||
ogni periodo che si accavalla a un altro (`Assegnazione::sovrappostaA()`). Non
|
||||
c'è ancora un vincolo nel database: l'import XML e i dati storici possono
|
||||
contenere sovrapposizioni, che il controllo segnala solo quando si modifica
|
||||
una di quelle righe.
|
||||
|
||||
### 3. Scrittura durante il rendering
|
||||
|
||||
|
||||
Reference in New Issue
Block a user