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:
@@ -2,27 +2,23 @@
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Livewire\Registro;
|
||||
use App\Models\AnnoTeocratico;
|
||||
use App\Models\Assegnazione;
|
||||
use App\Models\Proclamatore;
|
||||
use App\Models\Territorio;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use App\Services\Registro\ModuloS13;
|
||||
use Illuminate\Support\Collection;
|
||||
use ReflectionMethod;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Modulo S-13-I: il ciclo da quattro gruppi "Assegnato a" e la colonna
|
||||
* "Restituito l'ultima volta il".
|
||||
* Modulo S-13-I: il ciclo da quattro gruppi "Assegnato a", la colonna
|
||||
* "Restituito l'ultima volta il" e le assegnazioni a cavallo d'anno.
|
||||
*
|
||||
* E' la regola meno intuitiva del Registro — la quinta assegnazione non si
|
||||
* aggiunge alle quattro precedenti, le sostituisce — e finisce su un modulo che
|
||||
* viene firmato e archiviato: se sbaglia, sbaglia su carta.
|
||||
*
|
||||
* righeS13() e' privata di proposito (non e' un'azione Livewire); qui la si
|
||||
* chiama in riflessione perche' e' il calcolo, non il PDF, quello da proteggere.
|
||||
*/
|
||||
class RegistroS13Test extends TestCase
|
||||
{
|
||||
@@ -68,7 +64,7 @@ class RegistroS13Test extends TestCase
|
||||
$this->assertSame('', $riga['riporto']);
|
||||
$this->assertCount(4, $riga['assegnazioni']);
|
||||
$this->assertSame(
|
||||
['nome' => 'Rossi Mario', 'assegnato_il' => '10/09/2025', 'restituito_il' => '15/10/2025'],
|
||||
['nome' => 'Rossi M.', 'assegnato_il' => '10/09/2025', 'restituito_il' => '15/10/2025'],
|
||||
$riga['assegnazioni'][0]
|
||||
);
|
||||
|
||||
@@ -82,6 +78,19 @@ class RegistroS13Test extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_il_nome_e_cognome_piu_iniziale_del_nome(): void
|
||||
{
|
||||
$proclamatore = Proclamatore::create([
|
||||
'nome' => 'élena Maria',
|
||||
'cognome' => "Dell'Acqua Bianchi",
|
||||
'attivo' => true,
|
||||
]);
|
||||
$territorio = $this->territorio('1');
|
||||
$this->assegna($territorio, '2025-09-10')->update(['proclamatore_id' => $proclamatore->id]);
|
||||
|
||||
$this->assertSame("Dell'Acqua Bianchi É.", $this->righe()->sole()['assegnazioni'][0]['nome']);
|
||||
}
|
||||
|
||||
public function test_un_assegnazione_ancora_aperta_ha_la_data_di_rientro_vuota(): void
|
||||
{
|
||||
$territorio = $this->territorio('1');
|
||||
@@ -255,17 +264,11 @@ class RegistroS13Test extends TestCase
|
||||
$this->assertSame(['1'], $this->righe()->pluck('numero')->all());
|
||||
}
|
||||
|
||||
public function test_le_assegnazioni_di_un_altro_anno_di_servizio_non_compaiono(): void
|
||||
public function test_le_assegnazioni_chiuse_di_un_altro_anno_di_servizio_non_compaiono(): void
|
||||
{
|
||||
$altroAnno = AnnoTeocratico::create([
|
||||
'label' => '2024-2025',
|
||||
'start_date' => '2024-09-01',
|
||||
'end_date' => '2025-08-31',
|
||||
]);
|
||||
|
||||
$territorio = $this->territorio('1');
|
||||
$this->assegna($territorio, '2025-09-01', '2025-09-20');
|
||||
$this->assegna($territorio, '2025-01-10', '2025-02-10', $altroAnno);
|
||||
$this->assegna($territorio, '2025-01-10', '2025-02-10', $this->annoPrecedente());
|
||||
|
||||
$riga = $this->righe()->sole();
|
||||
|
||||
@@ -275,11 +278,168 @@ class RegistroS13Test extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function test_un_territorio_senza_assegnazioni_nell_anno_non_produce_una_riga(): void
|
||||
/**
|
||||
* Il registro serve a vedere anche i territori fermi: una riga vuota con
|
||||
* la data dell'ultimo rientro dice da quanto non escono.
|
||||
*/
|
||||
public function test_un_territorio_senza_assegnazioni_nell_anno_ha_comunque_la_sua_riga(): void
|
||||
{
|
||||
$territorio = $this->territorio('1');
|
||||
$this->assegna($territorio, '2025-03-01', '2025-04-12', $this->annoPrecedente());
|
||||
|
||||
$riga = $this->righe()->sole();
|
||||
|
||||
$this->assertSame('1', $riga['numero']);
|
||||
$this->assertSame('12/04/2025', $riga['riporto']);
|
||||
$this->assertSame(['', '', '', ''], array_column($riga['assegnazioni'], 'nome'));
|
||||
}
|
||||
|
||||
public function test_un_territorio_mai_assegnato_ha_la_riga_vuota(): void
|
||||
{
|
||||
$this->territorio('1');
|
||||
|
||||
$this->assertTrue($this->righe()->isEmpty());
|
||||
$riga = $this->righe()->sole();
|
||||
|
||||
$this->assertSame('', $riga['riporto']);
|
||||
$this->assertSame(['', '', '', ''], array_column($riga['assegnazioni'], 'assegnato_il'));
|
||||
}
|
||||
|
||||
// ─── Il riporto a inizio anno ───────────────────────────────
|
||||
|
||||
/**
|
||||
* La nota del modulo: "quando si inizia un nuovo foglio, registrare in
|
||||
* questa colonna l'ultima data in cui e' stato restituito ogni territorio".
|
||||
*/
|
||||
public function test_nel_primo_ciclo_il_riporto_e_l_ultimo_rientro_prima_dell_anno(): void
|
||||
{
|
||||
$precedente = $this->annoPrecedente();
|
||||
$territorio = $this->territorio('1');
|
||||
$this->assegna($territorio, '2025-01-10', '2025-02-10', $precedente);
|
||||
$this->assegna($territorio, '2025-05-02', '2025-06-30', $precedente);
|
||||
$this->assegna($territorio, '2025-10-01', '2025-10-20');
|
||||
|
||||
$riga = $this->righe()->sole();
|
||||
|
||||
$this->assertSame('30/06/2025', $riga['riporto']);
|
||||
$this->assertSame('01/10/2025', $riga['assegnazioni'][0]['assegnato_il']);
|
||||
}
|
||||
|
||||
// ─── A cavallo d'anno ───────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Uscito ad agosto, rientrato a ottobre: sul foglio nuovo occupa il primo
|
||||
* gruppo con entrambe le date, e il riporto e' il rientro precedente.
|
||||
*/
|
||||
public function test_l_assegnazione_a_cavallo_apre_il_foglio_nuovo_con_entrambe_le_date(): void
|
||||
{
|
||||
$precedente = $this->annoPrecedente();
|
||||
$territorio = $this->territorio('1');
|
||||
$this->assegna($territorio, '2025-05-02', '2025-06-30', $precedente);
|
||||
$this->assegna($territorio, '2025-08-10', '2025-10-05', $precedente);
|
||||
$this->assegna($territorio, '2025-11-01', '2025-11-20');
|
||||
|
||||
$riga = $this->righe()->sole();
|
||||
|
||||
$this->assertSame('30/06/2025', $riga['riporto']);
|
||||
$this->assertSame(
|
||||
['nome' => 'Rossi M.', 'assegnato_il' => '10/08/2025', 'restituito_il' => '05/10/2025'],
|
||||
$riga['assegnazioni'][0]
|
||||
);
|
||||
$this->assertSame('01/11/2025', $riga['assegnazioni'][1]['assegnato_il']);
|
||||
}
|
||||
|
||||
public function test_sul_foglio_vecchio_l_assegnazione_a_cavallo_resta_senza_rientro(): void
|
||||
{
|
||||
$precedente = $this->annoPrecedente();
|
||||
$territorio = $this->territorio('1');
|
||||
$this->assegna($territorio, '2025-08-10', '2025-10-05', $precedente);
|
||||
|
||||
$riga = $this->righe($precedente)->sole();
|
||||
|
||||
$this->assertSame('10/08/2025', $riga['assegnazioni'][0]['assegnato_il']);
|
||||
$this->assertSame('', $riga['assegnazioni'][0]['restituito_il']);
|
||||
}
|
||||
|
||||
public function test_un_assegnazione_ancora_fuori_da_un_anno_precedente_compare_sul_foglio_nuovo(): void
|
||||
{
|
||||
$territorio = $this->territorio('1');
|
||||
$this->assegna($territorio, '2025-07-01', null, $this->annoPrecedente());
|
||||
|
||||
$riga = $this->righe()->sole();
|
||||
|
||||
$this->assertSame('01/07/2025', $riga['assegnazioni'][0]['assegnato_il']);
|
||||
$this->assertSame('', $riga['assegnazioni'][0]['restituito_il']);
|
||||
}
|
||||
|
||||
/** L'assegnazione a cavallo conta nel ciclo: con altre quattro nell'anno, la riga ricomincia. */
|
||||
public function test_l_assegnazione_a_cavallo_conta_nel_ciclo_da_quattro(): void
|
||||
{
|
||||
$territorio = $this->territorio('1');
|
||||
$this->assegna($territorio, '2025-08-10', '2025-09-15', $this->annoPrecedente());
|
||||
$this->assegna($territorio, '2025-10-01', '2025-10-20');
|
||||
$this->assegna($territorio, '2025-11-01', '2025-11-20');
|
||||
$this->assegna($territorio, '2025-12-01', '2025-12-24');
|
||||
$this->assegna($territorio, '2026-01-05');
|
||||
|
||||
$riga = $this->righe()->sole();
|
||||
|
||||
$this->assertSame('24/12/2025', $riga['riporto']);
|
||||
$this->assertSame(['05/01/2026', '', '', ''], array_column($riga['assegnazioni'], 'assegnato_il'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Il foglio sceglie per data, non per anno_teocratico_id: una data
|
||||
* corretta a mano nel Registro o un import legacy possono lasciare l'anno
|
||||
* registrato incoerente con il giorno di uscita.
|
||||
*/
|
||||
public function test_conta_la_data_di_uscita_non_l_anno_registrato(): void
|
||||
{
|
||||
$precedente = $this->annoPrecedente();
|
||||
$territorio = $this->territorio('1');
|
||||
$this->assegna($territorio, '2025-10-01', '2025-10-20', $precedente);
|
||||
|
||||
$this->assertSame('01/10/2025', $this->righe()->sole()['assegnazioni'][0]['assegnato_il']);
|
||||
$this->assertSame('', $this->righe($precedente)->sole()['assegnazioni'][0]['assegnato_il']);
|
||||
}
|
||||
|
||||
public function test_l_ultimo_giorno_dell_anno_appartiene_al_foglio(): void
|
||||
{
|
||||
$territorio = $this->territorio('1');
|
||||
$this->assegna($territorio, '2026-08-31', '2026-08-31');
|
||||
|
||||
$riga = $this->righe()->sole();
|
||||
|
||||
$this->assertSame('31/08/2026', $riga['assegnazioni'][0]['assegnato_il']);
|
||||
$this->assertSame('31/08/2026', $riga['assegnazioni'][0]['restituito_il']);
|
||||
}
|
||||
|
||||
// ─── Impaginazione e PDF ────────────────────────────────────
|
||||
|
||||
public function test_ogni_foglio_ha_venti_righe_come_il_modulo(): void
|
||||
{
|
||||
foreach (range(1, 21) as $numero) {
|
||||
$this->territorio((string) $numero);
|
||||
}
|
||||
|
||||
$pagine = (new ModuloS13())->pagine($this->righe());
|
||||
|
||||
$this->assertCount(2, $pagine);
|
||||
$this->assertCount(20, $pagine[0]);
|
||||
$this->assertCount(20, $pagine[1]);
|
||||
$this->assertSame('21', $pagine[1][0]['numero']);
|
||||
$this->assertSame('', $pagine[1][1]['numero']);
|
||||
}
|
||||
|
||||
public function test_il_pdf_e_il_modulo_ufficiale_con_una_pagina_per_foglio(): void
|
||||
{
|
||||
foreach (range(1, 21) as $numero) {
|
||||
$this->assegna($this->territorio((string) $numero), '2025-09-01');
|
||||
}
|
||||
|
||||
$pdf = (new ModuloS13())->genera($this->anno);
|
||||
|
||||
$this->assertStringStartsWith('%PDF-', $pdf);
|
||||
$this->assertSame(2, preg_match_all('#/Type /Page\b(?!s)#', $pdf));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,8 +486,15 @@ class RegistroS13Test extends TestCase
|
||||
*/
|
||||
private function righe(?AnnoTeocratico $anno = null): Collection
|
||||
{
|
||||
$metodo = new ReflectionMethod(Registro::class, 'righeS13');
|
||||
return (new ModuloS13())->righe($anno ?? $this->anno);
|
||||
}
|
||||
|
||||
return $metodo->invoke(new Registro(), $anno ?? $this->anno);
|
||||
private function annoPrecedente(): AnnoTeocratico
|
||||
{
|
||||
return AnnoTeocratico::create([
|
||||
'label' => '2024-2025',
|
||||
'start_date' => '2024-09-01',
|
||||
'end_date' => '2025-08-31',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user