++ HANDOFF e README allineati all'ultima versione delle funzionalita'
HANDOFF: stato attuale vero (produzione dal 19/10, cosa c'e' solo su dev e come rilasciarlo), sezione dei gruppi di servizio descritta com'e' oggi con la storia delle decisioni in coda, lavori recenti in ordine, cronologia di luglio-agosto in "Da dove veniamo", ambienti con autodeploy che non parte e test sul server prima del push, copertura dei test (577 + 13), frasi superate su WhatsApp/email/contatti corrette. README: impostazioni reali della pagina, due liste della Home, soglia di priorita' in giorni e ordine di priorita', deploy a mano, struttura del progetto. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
624
HANDOFF.md
624
HANDOFF.md
@@ -4,236 +4,59 @@ Stato del progetto per chi ci mette mano dopo. Per installazione, deploy e uso
|
||||
quotidiano vedi [README.md](README.md); qui c'è il contesto che il README non
|
||||
può dare: perché le cose sono come sono, e cosa resta aperto.
|
||||
|
||||
Ultimo aggiornamento: **20 ottobre 2026** (gruppi di servizio, solo su dev) — vedi [Lavori in corso](#lavori-in-corso--12-ottobre-2026-riprendere-da-qui).
|
||||
Ultimo aggiornamento: **20 ottobre 2026** — si riprende da [Stato attuale](#stato-attuale).
|
||||
|
||||
---
|
||||
|
||||
## Stato attuale
|
||||
|
||||
L'applicazione è **pronta per il deploy in produzione su Dokploy**. Lo stack è
|
||||
stato verificato end-to-end: build dell'immagine, primo deploy su volumi vuoti,
|
||||
redeploy, login, tutte le pagine, code e scheduler.
|
||||
**Produzione** (`main` → compose `termanager2-core-production`) in esercizio
|
||||
su Dokploy dal 19 ottobre 2026. Ultimo rilascio: `d3314a9` (proclamatori senza
|
||||
contatti, il link si copia o si condivide). Backup fatti prima dei rilasci in
|
||||
`/etc/dokploy/termanager2-backups/` sul server.
|
||||
|
||||
**Demo** (`dev` → compose `termanager2-core-demo`,
|
||||
`demo-termanager.pyconetwork.it`): avanti di una ventina di commit, tutti
|
||||
provati con la suite sul server e pubblicati sulla demo. **Non ancora in
|
||||
produzione** — l'utente ha chiesto di aspettare il suo ok:
|
||||
|
||||
- [gruppi di servizio](#gruppi-di-servizio-19-20-ottobre-2026-solo-su-dev):
|
||||
sorvegliante con email fittizia, pacchetto di territori, "Il mio gruppo",
|
||||
scioglimento; migrazioni `2026_10_20_000010` … `000040`;
|
||||
- priorità e filtri nel pacchetto, con la regola di priorità della Home in un
|
||||
metodo solo (`Territorio::ordinaPerPriorita()`);
|
||||
- pulsante **Rientro** nell'elenco dei territori assegnati;
|
||||
- storico assegnazioni in una tabella sola, con le colonne in linea;
|
||||
- *Assegna* aperta da "Assegna →" della Home senza l'elenco dei territori
|
||||
assegnati.
|
||||
|
||||
**Per portarli in produzione**: backup del database di produzione, `main`
|
||||
avanti fino a `dev` (fast-forward), deploy (l'autodeploy non parte: vedi
|
||||
[Dove gira davvero](#dove-gira-davvero)), poi controllo di migrazioni e
|
||||
conteggi. In produzione non ci sono gruppi: le quattro migrazioni non hanno
|
||||
dati da convertire.
|
||||
|
||||
| Aspetto | Stato |
|
||||
|-----------------------------|-------|
|
||||
| Deploy Dokploy (Compose) | ✅ verificato |
|
||||
| Deploy Dokploy (Compose) | ✅ produzione e demo |
|
||||
| Autodeploy al push | ⚠️ configurato, ma dal 19/10 il push non fa partire il deploy: si avvia a mano (`compose.deploy`) |
|
||||
| Primo avvio a zero-config | ✅ migrazioni + seed + admin automatici |
|
||||
| Redeploy senza interventi | ✅ ~25 secondi |
|
||||
| Sviluppo locale | ✅ stesso compose, profilo `dev` |
|
||||
| Backup / ripristino / azzeramento | ✅ verificati end-to-end in locale |
|
||||
| Backup / ripristino / azzeramento | ✅ verificati end-to-end |
|
||||
| Ripristino da browser in linea | ⚠️ **limitato a 100 MB da Cloudflare** — vedi [Cosa resta aperto](#cosa-resta-aperto) |
|
||||
| Test automatici | ⚠️ **impalcatura pronta, 294 test** — copertura ancora parziale |
|
||||
| Test automatici | ✅ **577 test PHP + 13 JavaScript, verdi** (20/10/2026); copertura ancora parziale |
|
||||
| Prove nel browser | ⚠️ le funzioni del 19-20 ottobre sono verificate dai test e sul server; nel browser le ha usate l'utente sulla demo |
|
||||
|
||||
**Punto in cui ci si è fermati (30/07/2026).** Su `develop` è pubblicato tutto:
|
||||
backup completo `.zip`, ripristino, azzeramento, cambio password forzato, barre
|
||||
di avanzamento reali e console di log (commit `f909791`, deploy verificato
|
||||
confrontando l'impronta degli asset). `MAX_UPLOAD_MB=2048` è impostato **solo**
|
||||
su develop; la produzione non è stata toccata e resta sul suo deploy
|
||||
precedente, senza quella variabile.
|
||||
|
||||
**Il 31/07 è nata la sotto-app "Piantine"** (nuova voce nel menu
|
||||
Amministrazione): KML dell'area città, editor dei poligoni su OpenStreetMap,
|
||||
immagini HD dal container `map-renderer` e PDF sul modulo S-12 con storico
|
||||
versioni. Architettura e agganci sono descritti in [Decisioni
|
||||
architetturali](#le-piantine-sono-una-sotto-app-renderer-esterno-agganci-minimi).
|
||||
Al primo deploy il compose costruisce anche l'immagine del renderer; non
|
||||
servono variabili nuove in Dokploy.
|
||||
|
||||
**Il 28/08 la sotto-app è stata rifatta e l'interfaccia ripassata.** Le piantine
|
||||
si sono staccate dai territori (una piantina vive anche scollegata, il territorio
|
||||
si crea dall'editor), il disegno è passato su una pagina a schermo intero, la
|
||||
cartolina S-12 è stata reimpaginata (confini sulla prima pagina, piantina al 98%
|
||||
sulla seconda) e la rigenerazione massiva è stata rimossa. In più il sistema
|
||||
visivo è stato ripassato su tutta l'applicazione, non solo qui: vedi [Il sistema
|
||||
visivo sta nei token](#il-sistema-visivo-sta-nei-token-non-nelle-viste).
|
||||
|
||||
Nello stesso giro è emerso e corretto un guasto d'ambiente che bloccava **tutti**
|
||||
gli upload in sviluppo: vedi [`HTTP_HOST` a nginx deve conservare la
|
||||
porta](#http_host-a-nginx-deve-conservare-la-porta).
|
||||
|
||||
Il 30/07 sono state fatte due cose.
|
||||
|
||||
**La suite di test, che prima non esisteva**: 79 test eseguibili con un comando,
|
||||
senza dipendere da altri servizi. Sono **verdi** ma coprono ancora una frazione
|
||||
della logica di business — vedi *Cosa resta aperto*. Non fanno parte
|
||||
dell'immagine di produzione e non girano su un deploy: sono uno strumento
|
||||
locale.
|
||||
|
||||
**La pagina "Backup e import" è stata riorganizzata**: cinque zone numerate con
|
||||
un indice che ci salta sopra evidenziandole, un solo sistema di pulsanti (con
|
||||
gli stati di focus, che prima non c'erano), e **una sola console** per tutte le
|
||||
operazioni, agganciata in fondo alla finestra, al posto dei cinque riquadri di
|
||||
log sparsi. L'azzeramento è diventato un componente a sé per poter stare in
|
||||
fondo alla pagina. Tutto verificato lato server dai test; **il comportamento nel
|
||||
browser — apertura automatica della console, scorrimento, lampeggio delle zone —
|
||||
non è stato provato**, perché richiede un browser: è la prima cosa da guardare
|
||||
aprendo la pagina.
|
||||
|
||||
Il ripristino dall'interfaccia resta impraticabile per pacchetti grandi finché
|
||||
non si decide come aggirare Cloudflare: è la prima voce di *Cosa resta aperto*
|
||||
e l'unica che richiede una decisione, non del lavoro.
|
||||
La storia precedente (luglio-agosto: backup e ripristino, sotto-app Piantine,
|
||||
sistema visivo, prima suite di test) è in [Da dove veniamo](#da-dove-veniamo).
|
||||
|
||||
---
|
||||
|
||||
## Lavori in corso — 12 ottobre 2026 (riprendere da qui)
|
||||
## Lavori recenti — ottobre 2026
|
||||
|
||||
Commit su `dev`, **nessuno pubblicato** (il push fa partire il deploy sulla demo):
|
||||
- `d433c93` — Modulo S-13 sul modulo ufficiale (regole in
|
||||
`app/Services/Registro/ModuloS13.php`) e blocco 1 della revisione
|
||||
(integrità dati).
|
||||
- `056d95c` — Editor piantine: bozza automatica, un solo "Pubblica" con
|
||||
anteprima, stati Bozza / Pubblicata / Modifiche da pubblicare. Come funziona:
|
||||
[Le piantine sono una sotto-app](#le-piantine-sono-una-sotto-app-renderer-esterno-agganci-minimi).
|
||||
- `426a952` — blocchi 2 e 3 della revisione (sotto); le ultime due righe
|
||||
della tabella sono nel commit successivo.
|
||||
|
||||
Produzione: al 12/10/2026 non esiste ancora (il sito gira in Docker sul PC di
|
||||
sviluppo, più la demo da `dev`). Nessuna correzione una tantum da fare.
|
||||
|
||||
### Posta, contatti dei proclamatori, conferma email (12-13 ottobre 2026)
|
||||
|
||||
Richieste dell'utente, con le sue scelte:
|
||||
|
||||
- **Server di posta dall'app** (Impostazioni → Server di posta, rotta
|
||||
`posta.edit`, `Settings\PostaElettronica`): host, porta, cifratura
|
||||
(automatica / SSL / nessuna), utente, password cifrata, mittente; pulsante
|
||||
"Invia email di prova". Se compilato prende il posto del `.env`
|
||||
(`ConfigurazionePosta::applica()`, agganciata alla creazione del mail
|
||||
manager e a ogni job della coda). Non si esporta nell'XML e l'import non la
|
||||
cancella (`Setting::CAMPI_POSTA`). Nel `.env.example` `MAIL_SCHEME=tls` era
|
||||
un valore non valido per Laravel 11: ora vuoto.
|
||||
- **Proclamatori: email e telefono facoltativi**, cifrati come nome e cognome,
|
||||
nell'export/import XML, cancellati dall'anonimizzazione. Il numero apre
|
||||
WhatsApp (`<x-whatsapp>`, `Proclamatore::whatsappUrl()`: senza prefisso si
|
||||
considera italiano).
|
||||
- **Dopo l'assegnazione** — *rivisto il 19 ottobre 2026, vedi "Invio del link
|
||||
dopo l'assegnazione" qui sotto*. Il riquadro in sessione
|
||||
(`Assegna::DA_INVIARE`) non esiste più.
|
||||
- **Conferma email obbligatoria** per tutti gli utenti, anche quelli già
|
||||
esistenti (la migrazione `2026_10_13_000010` azzera `email_verified_at`).
|
||||
L'admin crea l'utente con la password come prima; parte l'email di conferma
|
||||
(in italiano, `VerifyEmail::toMailUsing`); il link funziona anche senza
|
||||
login. Cambiare email (profilo o admin) la rende da confermare e rimanda il
|
||||
link. In Utenti: stato "da confermare" e "Reinvia conferma".
|
||||
Raggiungibili senza conferma: profilo, cambio password, pagina di conferma
|
||||
e — per l'amministratore — il server di posta.
|
||||
Emergenza: `php artisan utenti:conferma-email <email>`. Il primo admin
|
||||
creato da riga di comando nasce confermato.
|
||||
- Nei test gli utenti nascono confermati (`tests/TestCase.php`,
|
||||
`$confermaUtentiCreati`).
|
||||
|
||||
Provato nel browser in locale con mailpit come server configurato dall'app:
|
||||
email di prova e di conferma arrivate, link aperto senza login, account
|
||||
sbloccato; riquadro dopo l'assegnazione con WhatsApp e scelta. Configurazione
|
||||
e dati di prova rimossi dopo.
|
||||
|
||||
### Gruppi di servizio (19 ottobre 2026, solo su dev)
|
||||
|
||||
Richiesta dell'utente: un **gruppo di servizio** con un sorvegliante che,
|
||||
entrato con una sua utenza, distribuisce ai membri un pacchetto di territori
|
||||
e non vede nient'altro. Scelte dell'utente:
|
||||
|
||||
- Il gruppo ha **membri** (proclamatori); un proclamatore sta in **un solo**
|
||||
gruppo; il sorvegliante è anche membro e assegna solo ai membri, a sé
|
||||
compreso.
|
||||
- Il **pacchetto non è un'assegnazione**: "la media non deve essere alterata".
|
||||
I territori del pacchetto sono "In gruppo …" (stato `in_gruppo`), fuori da
|
||||
"Da assegnare" e dalla pagina *Assegna* (`Territorio::scopeInReparto`
|
||||
esclude `gruppo_servizio_id`).
|
||||
- Quando il sorvegliante dà un territorio nasce un'**assegnazione normale**,
|
||||
tracciata con `assegnazioni.gruppo_servizio_id` ("via gruppo …" nel
|
||||
registro e nella scheda del territorio).
|
||||
- Al **rientro** il territorio esce dal pacchetto e torna in reparto
|
||||
(hook `updated` in `Assegnazione`, vale da qualunque pagina).
|
||||
- Il sorvegliante: assegna, manda il link (copia/condividi), registra i
|
||||
rientri, apre la piantina, rimanda in reparto i non dati. Gruppi, membri e
|
||||
pacchetti li gestisce **solo l'amministratore**.
|
||||
- Utenza: password temporanea da cambiare al primo accesso, **senza
|
||||
conferma dell'email** (`User::hasVerifiedEmail()` è vero per il
|
||||
sorvegliante). *Dal 20 ottobre l'email è fittizia, creata dall'app — vedi
|
||||
sotto.*
|
||||
- **Togliere il ruolo = sciogliere il gruppo** (`GruppiServizio::sciogli`):
|
||||
non dati in reparto, membri liberi. I territori già dati restano ai membri.
|
||||
Si riparte con un gruppo nuovo. *Dal 20 ottobre gruppo e utenza del
|
||||
sorvegliante si eliminano — vedi sotto.*
|
||||
|
||||
Decisioni successive dell'utente (20 ottobre 2026):
|
||||
- **Sorvegliante non più attivo ⇒ gruppo sciolto** e territori non dati
|
||||
rilasciati, come "Togli il ruolo". La regola sta nel modello
|
||||
(`Proclamatore::booted`, eventi `updated` su `attivo` e `deleted`), così
|
||||
vale dall'elenco, dalla modifica, dal cestino e dall'anonimizzazione; il
|
||||
motivo finisce nel registro (`gruppo_sciolto`, proprietà `motivo`).
|
||||
L'elenco chiede conferma prima di disattivare o cestinare un sorvegliante;
|
||||
la modifica lo avvisa sotto la spunta "Attivo". Il cestino e
|
||||
l'anonimizzazione erano prima bloccati per il sorvegliante: li ho allineati
|
||||
alla stessa regola. Un membro non attivo resta membro (non riceve
|
||||
territori); nel cestino esce dal gruppo.
|
||||
- **Dettagli nel profilo**: la scheda del proclamatore (`ProclamatoreShow`)
|
||||
mostra il gruppo che guida con i numeri (`GruppoServizio::riepilogo()`),
|
||||
l'utenza (email solo all'amministratore, password temporanea o no,
|
||||
ultimo accesso dal registro), oppure il gruppo di cui è membro. Il
|
||||
*Profilo* del sorvegliante mostra il suo gruppo con gli stessi numeri e
|
||||
"Torna al mio gruppo".
|
||||
- **Il gruppo sciolto non resta nello storico**: `sciogli()` lo elimina
|
||||
(colonna `sciolto_at` tolta dalla migrazione `2026_10_20_000020`, che
|
||||
elimina anche i gruppi già sciolti). I territori solo nel pacchetto tornano
|
||||
in reparto (conferma esplicita dell'utente); quelli già dati restano ai
|
||||
membri come assegnazioni qualsiasi, che conservano il nome del gruppo
|
||||
(`assegnazioni.gruppo_nome`, "via gruppo …" in registro e scheda
|
||||
territorio): **confermato dall'utente il 20 ottobre 2026**. Dopo lo
|
||||
scioglimento si torna all'elenco dei gruppi; nella scheda del proclamatore
|
||||
non c'è più "In passato sorvegliante di".
|
||||
- **Email fittizia, niente nome utente** (seconda decisione del 20 ottobre,
|
||||
che sostituisce il nome utente ricavato dal nome del gruppo fatto qualche
|
||||
ora prima): creando il gruppo l'email non si chiede,
|
||||
`GruppiServizio::emailFittizia()` la ricava da iniziale del nome e cognome,
|
||||
minuscoli, senza accenti, spazi né apostrofi, sul dominio
|
||||
`GruppiServizio::DOMINIO_EMAIL` = `termanager.it` (Francesco Fontana →
|
||||
`ffontana@termanager.it`, D'Angelo → `fdangelo@…`, numero in coda se già
|
||||
presa) — la stessa convenzione degli altri utenti. Il login è di nuovo solo
|
||||
per email. Il profilo del sorvegliante mostra l'email ma non la fa
|
||||
cambiare. Il nome del gruppo è unico.
|
||||
- **Sciolto il gruppo, l'utenza del sorvegliante si elimina** ("torna
|
||||
proclamatore normale, non tenere traccia"): `User::eliminaConservandoStorico()`
|
||||
— la stessa eliminazione della pagina *Utenti*, ora in un metodo solo — passa
|
||||
le assegnazioni che aveva fatto o fatto rientrare a chi scioglie (o al primo
|
||||
amministratore) e lascia le sue voci nel registro a nome "Sorvegliante del
|
||||
gruppo …", senza email. Niente più utenze disattivate: via
|
||||
`users.disattivato_at` e i controlli in `LoginController` e
|
||||
`LimitaSorvegliante`; una sessione aperta resta senza utente.
|
||||
`Proclamatore::anonimizza()` non tocca più utenze: se era sorvegliante, il
|
||||
salvataggio scioglie il gruppo e l'utenza sparisce.
|
||||
- Migrazione `2026_10_20_000030_sorveglianti_con_email_fittizia`: elimina le
|
||||
utenze già disattivate (assegnazioni al primo amministratore, registro a
|
||||
nome "Ex sorvegliante di gruppo"), dà l'email fittizia a chi non l'aveva,
|
||||
toglie `users.username` e `users.disattivato_at`, rende di nuovo
|
||||
obbligatoria `users.email`. XML: niente più `<username>` né
|
||||
`<disattivato_at>`; da un file vecchio non si importano le utenze
|
||||
disattivate né i gruppi con `<sciolto_at>`.
|
||||
- **Primo accesso del sorvegliante**: la pagina del cambio password obbligato
|
||||
diceva sempre "Ripristino da backup eseguito" (era l'unico motivo
|
||||
previsto). Ora `users.password_temporanea` (migrazione `000040`, che la
|
||||
accende per i sorveglianti che non hanno ancora cambiato la password)
|
||||
distingue la password provvisoria data dall'amministratore — creazione del
|
||||
gruppo, "Nuova password temporanea" — e la pagina dice "Password
|
||||
temporanea". Si spegne a password cambiata (pagina obbligatoria o
|
||||
profilo); il ripristino e l'import XML la spengono per tutti, perché lì il
|
||||
motivo è il ripristino.
|
||||
- **Bug: ogni clic del sorvegliante finiva in 403.** In Livewire 3.7 la
|
||||
rotta delle azioni si chiama `default.livewire.update`, e
|
||||
`LimitaSorvegliante` lasciava passare solo `livewire.*`: ora `*livewire.*`.
|
||||
I test non lo vedevano perché `Livewire::test()` non passa dai middleware
|
||||
delle rotte; ora un test chiama la rotta vera.
|
||||
- **Priorità e filtri** (richiesta dell'utente): la regola della Home ("Da
|
||||
assegnare": prioritari — spunta o giacenza oltre `giorni_giacenza_prioritari`
|
||||
— poi giacenza, poi numero) è ora `Territorio::ordinaPerPriorita()`, usata
|
||||
dalla Home, dall'aggiunta al pacchetto (`GruppoShow`: ricerca, zona,
|
||||
tipologia, solo prioritari, "Seleziona tutti quelli mostrati") e da "Il mio
|
||||
gruppo" (prima i da dare in ordine di priorità, poi i già dati dal più
|
||||
vecchio; filtri stato, zona, tipologia, solo prioritari, nell'indirizzo).
|
||||
Gli attributi calcolati si chiamano `giacenza_giorni` e `in_priorita`
|
||||
(erano `home_giorni_giacenza` e `home_is_prioritario`, anche nel PDF).
|
||||
- "Il mio gruppo" mostra sotto il titolo nome e cognome del sorvegliante.
|
||||
Dal più recente. Le sezioni del 12-19 ottobre sono in produzione; quelle del
|
||||
20 ottobre e i gruppi di servizio solo sulla demo.
|
||||
|
||||
### Assegna aperta dalla Home: senza l'elenco dei territori assegnati (20 ottobre 2026)
|
||||
|
||||
@@ -271,51 +94,124 @@ Anche le righe senza PDF lo hanno. L'elenco ha un tetto di peso per riga
|
||||
starci l'indirizzo è relativo (`route(..., false)`, `data-ritorno` lo
|
||||
gestisce) e il pulsante sta sulla stessa riga del `<div>`, senza a capo né
|
||||
commenti dentro il ciclo (i commenti Blade spariscono, gli spazi no).
|
||||
- **Anche i membri si segnalano**: `Proclamatore::gruppo_etichetta`
|
||||
("Gruppo Zisa" / "Sorvegliante del gruppo Zisa") come badge nell'elenco
|
||||
(più il filtro *In un gruppo di servizio*, `filtroStato=gruppo`) e in testa
|
||||
alla scheda; nelle opzioni di `<x-scelta>` come `dettaglio` (Assegna,
|
||||
Registro), non come `motivo`: assegnare direttamente a un membro resta
|
||||
possibile. In "Il mio gruppo" la nota non c'è (`opzioniScelta(...,
|
||||
conGruppo: false)`).
|
||||
- L'utenza del sorvegliante porta nome ed email del proclamatore: nel registro
|
||||
delle attività `User::tapActivity()` li sostituisce con "proclamatore #id",
|
||||
come vale per tutti i proclamatori. Restano invece in chiaro nella tabella
|
||||
`users`, come per ogni utente.
|
||||
|
||||
Dove sta:
|
||||
- Migrazione `2026_10_20_000010_gruppi_di_servizio`: tabella
|
||||
`gruppi_servizio`, colonne `gruppo_servizio_id` su proclamatori, territori e
|
||||
assegnazioni, `users.proclamatore_id` (unico) e `users.disattivato_at`
|
||||
(tolto dalla `000030`), permesso `gruppo.gestisci` e ruolo `sorvegliante`. Il seeder dà
|
||||
all'amministratore tutti i permessi **tranne** `gruppo.gestisci`.
|
||||
- Regole in `App\Services\GruppiServizio` (crea, aggiungiMembri, togliMembro,
|
||||
aggiungiAlPacchetto, rimandaInReparto, assegna, sciogli); i componenti
|
||||
`Gruppi\GruppiIndex`, `Gruppi\GruppoShow` (amministratore) e
|
||||
`Gruppi\MioGruppo` (sorvegliante) le chiamano soltanto.
|
||||
- `LimitaSorvegliante` (gruppo `web`): chi ha **solo** il ruolo sorvegliante
|
||||
raggiunge `mio-gruppo*`, `assegnazioni.rientra` (solo per il suo gruppo,
|
||||
controllo in `Rientra::autorizza`), profilo e cambio password; la Home lo
|
||||
rimanda al gruppo; il resto è 403.
|
||||
- L'invio del link è diventato il trait `Concerns\InviaLink` + il partial
|
||||
`assegnazioni/_invio-link`, usati da *Assegna* e da *Il mio gruppo*.
|
||||
- Il ruolo sorvegliante non compare nella pagina *Utenti* e le utenze legate a
|
||||
un proclamatore lì non si modificano né si eliminano: passano dal gruppo.
|
||||
- La voce di menu "Il mio gruppo" guarda il **ruolo**, non `@can`:
|
||||
l'amministratore passa ogni `@can` (`Gate::before`).
|
||||
- XML: si esportano e reimportano gruppi, membri, pacchetto, provenienza delle
|
||||
assegnazioni (id e nome del gruppo) e legame utenza ↔
|
||||
proclamatore.
|
||||
- Informativa privacy aggiornata (utenza del sorvegliante, gruppo tra i dati
|
||||
del proclamatore).
|
||||
- L'azzeramento dell'applicazione (*Zona pericolosa*) cancella anche i gruppi.
|
||||
- Test: `tests/Feature/GruppiServizioTest.php`. Eseguiti sul server **prima**
|
||||
del push, su una copia della checkout di dev con i commit portati da un
|
||||
`git bundle` (la checkout di Dokploy è shallow: il bundle deve partire dal
|
||||
commit che ha, `git log -1` lì dentro).
|
||||
### Gruppi di servizio (19-20 ottobre 2026, solo su dev)
|
||||
|
||||
Non provato nel browser. **In produzione non c'è**: l'utente ha chiesto
|
||||
(20 ottobre 2026) di non portarlo ancora in produzione; si aspetta il suo ok.
|
||||
Richiesta dell'utente: un **gruppo di servizio** con un sorvegliante che,
|
||||
entrato con una sua utenza, distribuisce ai membri un pacchetto di territori
|
||||
e non vede nient'altro. Le decisioni sono cambiate più volte in due giorni:
|
||||
qui sotto **com'è oggi**, in fondo la storia.
|
||||
|
||||
**Come funziona**
|
||||
|
||||
- **Chi**: un gruppo ha un nome (unico), un sorvegliante (proclamatore
|
||||
attivo) e dei membri. Un proclamatore sta in **un solo** gruppo; il
|
||||
sorvegliante è anche membro e assegna solo ai membri, a sé compreso.
|
||||
Gruppi, membri e pacchetti li gestisce **solo l'amministratore** (menu
|
||||
*Gruppi di servizio*).
|
||||
- **Utenza del sorvegliante**: nasce con il gruppo. Email **fittizia**
|
||||
(`GruppiServizio::emailFittizia()`: iniziale del nome e cognome, minuscoli,
|
||||
senza accenti, spazi né apostrofi, su `GruppiServizio::DOMINIO_EMAIL` =
|
||||
`termanager.it` — Francesco Fontana → `ffontana@termanager.it`, numero in
|
||||
coda se già presa), la stessa convenzione degli altri utenti. Password
|
||||
temporanea, mostrata una volta: al primo accesso la pagina del cambio dice
|
||||
"Password temporanea" (`users.password_temporanea`, che il ripristino e
|
||||
l'import XML spengono, perché lì il motivo è il ripristino). Nessuna
|
||||
conferma dell'email (`User::hasVerifiedEmail()` vero per il sorvegliante),
|
||||
e dal profilo l'email non si cambia. Ruolo `sorvegliante`, con il solo
|
||||
permesso `gruppo.gestisci`; non compare nella pagina *Utenti*, dove le
|
||||
utenze legate a un proclamatore non si modificano né si eliminano.
|
||||
"Nuova password temporanea" dalla pagina del gruppo.
|
||||
- **Cosa vede**: `LimitaSorvegliante` (gruppo `web`) lascia a chi ha **solo**
|
||||
quel ruolo `mio-gruppo*`, `assegnazioni.rientra` (solo per il suo gruppo:
|
||||
`Rientra::autorizza`), profilo, cambio password e le azioni Livewire —
|
||||
`*livewire.*`, perché in Livewire 3.7 la rotta si chiama
|
||||
`default.livewire.update` (con `livewire.*` ogni clic era un 403, e
|
||||
`Livewire::test()` non passa dai middleware: un test chiama la rotta vera).
|
||||
La Home lo rimanda al gruppo, il resto è 403. La voce di menu "Il mio
|
||||
gruppo" guarda il **ruolo**, non `@can`: l'amministratore passa ogni `@can`
|
||||
(`Gate::before`).
|
||||
- **Pacchetto**: **non è un'assegnazione** ("la media non deve essere
|
||||
alterata"): i territori sono "In gruppo …" (stato `in_gruppo`), fuori da
|
||||
"Da assegnare" e dalla pagina *Assegna* (`Territorio::scopeInReparto`
|
||||
esclude `gruppo_servizio_id`). Si aggiungono in blocco dai territori in
|
||||
reparto in ordine di priorità (`Territorio::ordinaPerPriorita()`, la regola
|
||||
della Home), con ricerca, zona, tipologia, "solo prioritari" e "Seleziona
|
||||
tutti quelli mostrati" (`GruppoShow`).
|
||||
- **"Il mio gruppo"** (`MioGruppo`): sotto il titolo nome e cognome del
|
||||
sorvegliante; prima i territori da dare in ordine di priorità, poi i già
|
||||
dati dal più vecchio; filtri stato, zona, tipologia, "solo prioritari"
|
||||
(nell'indirizzo). Il sorvegliante assegna, manda il link (copia o
|
||||
condividi: trait `Concerns\InviaLink` + partial `assegnazioni/_invio-link`,
|
||||
gli stessi di *Assegna*), registra i rientri, apre la piantina, rimanda in
|
||||
reparto quelli non dati.
|
||||
- **Assegnazione dal pacchetto**: un'**assegnazione normale**, con
|
||||
`gruppo_servizio_id` e `gruppo_nome` ("via gruppo …" in registro e schede).
|
||||
`GruppiServizio::assegna()` rilegge territorio e membro e ricontrolla il
|
||||
pacchetto sotto lock. Al **rientro** il territorio esce dal pacchetto e
|
||||
torna in reparto (hook `updated` in `Assegnazione`, da qualunque pagina).
|
||||
- **Scioglimento**: "Togli il ruolo e sciogli il gruppo", oppure il
|
||||
sorvegliante non è più attivo — reso non attivo, nel cestino o anonimizzato
|
||||
(`Proclamatore::booted`, eventi `updated` su `attivo` e `deleted`; l'elenco
|
||||
chiede conferma, la modifica avvisa sotto "Attivo"). `GruppiServizio::sciogli()`
|
||||
**elimina il gruppo**: i territori solo nel pacchetto tornano in reparto;
|
||||
quelli già dati restano ai membri e l'assegnazione ne conserva il nome
|
||||
(confermato dall'utente); i membri restano senza gruppo; l'**utenza del
|
||||
sorvegliante si elimina** con `User::eliminaConservandoStorico()` — la
|
||||
stessa eliminazione della pagina *Utenti* — che passa le sue assegnazioni a
|
||||
chi scioglie (o al primo amministratore) e lascia le sue voci nel registro
|
||||
a nome "Sorvegliante del gruppo …". Il motivo va nel registro
|
||||
(`gruppo_sciolto`, `motivo`). Un membro non attivo resta nel gruppo ma non
|
||||
riceve territori; nel cestino ne esce.
|
||||
- **Segnalazioni**: `Proclamatore::gruppo_etichetta` ("Gruppo Zisa" /
|
||||
"Sorvegliante del gruppo Zisa") come badge nell'elenco dei proclamatori
|
||||
(filtro *In un gruppo di servizio*, `filtroStato=gruppo`) e in testa alla
|
||||
scheda; nelle opzioni di `<x-scelta>` come `dettaglio` (Assegna, Registro),
|
||||
non come `motivo`: assegnare direttamente a un membro resta possibile. La
|
||||
scheda del proclamatore mostra il gruppo che guida
|
||||
(`GruppoServizio::riepilogo()`, email, password temporanea o no, ultimo
|
||||
accesso) o quello di cui è membro; il *Profilo* del sorvegliante il suo
|
||||
gruppo.
|
||||
- **Dati personali**: nel registro delle attività `User::tapActivity()`
|
||||
sostituisce nome ed email dell'utenza del sorvegliante con
|
||||
"proclamatore #id", come per tutti i proclamatori; nella tabella `users`
|
||||
restano in chiaro, come per ogni utente. Informativa privacy aggiornata.
|
||||
- **Backup**: l'XML porta gruppi, membri, pacchetto, provenienza delle
|
||||
assegnazioni (id e nome del gruppo) e il legame utenza ↔ proclamatore; da
|
||||
un file del 19-20 ottobre non si importano i gruppi con `<sciolto_at>` né
|
||||
le utenze con `<disattivato_at>`. L'azzeramento (*Zona pericolosa*)
|
||||
cancella anche i gruppi.
|
||||
|
||||
**Dove sta**
|
||||
|
||||
- Migrazioni: `2026_10_20_000010_gruppi_di_servizio` (tabella, colonne
|
||||
`gruppo_servizio_id` su proclamatori, territori e assegnazioni,
|
||||
`users.proclamatore_id` unico, permesso e ruolo; il seeder dà
|
||||
all'amministratore tutti i permessi **tranne** `gruppo.gestisci`),
|
||||
`000020` (`assegnazioni.gruppo_nome`, via `sciolto_at` e i gruppi sciolti),
|
||||
`000030` (via `users.username` e `users.disattivato_at` e le utenze
|
||||
disattivate, email fittizia a chi non l'aveva, email di nuovo obbligatoria),
|
||||
`000040` (`users.password_temporanea`).
|
||||
- Regole in `App\Services\GruppiServizio`; i componenti `Gruppi\GruppiIndex`,
|
||||
`Gruppi\GruppoShow` (amministratore) e `Gruppi\MioGruppo` (sorvegliante) le
|
||||
chiamano soltanto.
|
||||
- Test: `tests/Feature/GruppiServizioTest.php`.
|
||||
|
||||
**Com'è cambiato** (decisioni dell'utente, in ordine)
|
||||
|
||||
1. 19/10 — gruppo con membri; il pacchetto non conta nelle statistiche;
|
||||
utenza con email e password, senza conferma; togliere il ruolo scioglie il
|
||||
gruppo, che restava archiviato (`sciolto_at`) con l'utenza disattivata.
|
||||
2. 20/10 — sorvegliante non più attivo ⇒ gruppo sciolto; dettagli nella
|
||||
scheda e nel profilo; anche i membri segnalati.
|
||||
3. 20/10 — il gruppo sciolto non resta nello storico (si elimina); traccia
|
||||
"via gruppo" sulle assegnazioni confermata. Per qualche ora il sorvegliante
|
||||
è entrato con un nome utente ricavato dal nome del gruppo.
|
||||
4. 20/10 — al posto del nome utente l'email fittizia iniziale+cognome; sciolto
|
||||
il gruppo l'utenza si elimina ("torna proclamatore normale, non tenere
|
||||
traccia").
|
||||
5. 20/10 — "Password temporanea" al primo accesso; il 403 sulle azioni;
|
||||
priorità e filtri; nome del sorvegliante sotto il titolo.
|
||||
|
||||
### Proclamatori senza contatti; il link si copia o si condivide (19 ottobre 2026)
|
||||
|
||||
@@ -416,15 +312,17 @@ Proclamatore Semplificato" che non esistono).
|
||||
non è HTTPS), i ruoli con i permessi veri (`Privacy::PERMESSI` li spiega) e
|
||||
quanti dati sono conservati (contati in SQL: i campi cifrati vuoti restano
|
||||
NULL, non serve decifrare). `Privacy::REVISIONE` è la data del testo.
|
||||
- Aggiunte: email/telefono cifrati, link alla piantina, invio via WhatsApp
|
||||
(parte dal telefono di chi assegna) ed email (server di posta configurato),
|
||||
mappe OpenStreetMap, conferma email, blocco dopo troppi tentativi.
|
||||
- Aggiunte: link alla piantina, invio dal telefono di chi assegna, mappe
|
||||
OpenStreetMap, conferma email, blocco dopo troppi tentativi. (Email e
|
||||
telefono dei proclamatori, che il primo testo citava, sono stati tolti la
|
||||
sera stessa; dal 20 ottobre il testo parla anche del gruppo di servizio e
|
||||
dell'utenza del sorvegliante.)
|
||||
- Titolare e base giuridica sono rimasti com'erano: sono valutazioni legali,
|
||||
da far rivedere a chi ne ha la competenza.
|
||||
- **Aperto — backup in chiaro**: il pacchetto di backup usa l'export XML, che
|
||||
legge i proclamatori dal modello e quindi esporta nome, cognome, email e
|
||||
telefono **decifrati**. L'informativa ora lo dice; valutare se cifrare
|
||||
l'archivio (per esempio con una password scelta al download).
|
||||
legge i proclamatori dal modello e quindi esporta nome e cognome
|
||||
**decifrati**. L'informativa lo dice; valutare se cifrare l'archivio (per
|
||||
esempio con una password scelta al download).
|
||||
- `PrivacyTest` verifica che i valori cambino con le impostazioni, i ruoli,
|
||||
i conteggi senza nomi e l'accesso solo amministratore.
|
||||
|
||||
@@ -518,14 +416,14 @@ all'**elenco filtrato**, non alla scheda (scelta esplicita).
|
||||
Richieste dell'utente:
|
||||
|
||||
- **Dopo l'assegnazione non si va avanti** finché non si è deciso del link: si
|
||||
apre una finestra che blocca la pagina (`Assegna::apriInvio`) con i canali
|
||||
apre una finestra che blocca la pagina (oggi trait `Concerns\InviaLink`,
|
||||
usato anche da "Il mio gruppo") con i canali. *Superato la sera stessa:
|
||||
WhatsApp ed email sono stati tolti, restano Copia link e Condividi… — vedi
|
||||
"Proclamatori senza contatti".* Com'era il primo giorno:
|
||||
- *Copia link*: sempre;
|
||||
- *WhatsApp*: solo se il proclamatore ha un numero utilizzabile;
|
||||
- *Email*: solo se ha un indirizzo **e** la posta funziona
|
||||
(`ConfigurazionePosta::funzionante`). La manda l'app con
|
||||
`Notification::route('mail', …)->notifyNow(new LinkTerritorio)`: **mai in
|
||||
coda**, perché il job salverebbe in chiaro su Redis/`failed_jobs`
|
||||
l'indirizzo che nel database è cifrato.
|
||||
(`ConfigurazionePosta::funzionante`), inviata subito e mai in coda.
|
||||
|
||||
Poi "Hai inviato il link?": *Sì* vuole un canale usato, *No* è sempre
|
||||
possibile. Solo dopo la risposta si ricarica la pagina.
|
||||
@@ -537,17 +435,62 @@ Richieste dell'utente:
|
||||
"Non inviato / Link inviato" dell'elenco.
|
||||
- Colonne nuove (migrazione `2026_10_19_000010`): `link_da_inviare` (le
|
||||
assegnazioni già esistenti restano false: niente avvisi a posteriori su
|
||||
link di cui non si sa niente) e `link_canale` (`copia`/`whatsapp`/`email`,
|
||||
`Assegnazione::CANALI_LINK`). Entrambe nell'XML.
|
||||
link di cui non si sa niente) e `link_canale` (oggi `copia`/`condividi`,
|
||||
`Assegnazione::CANALI_ATTIVI`; `whatsapp`/`email` restano in
|
||||
`CANALI_LINK` solo per le assegnazioni già inviate così). Entrambe nell'XML.
|
||||
- Nel log `link_inviato` / `link_non_inviato` con canale e `proclamatore_id`:
|
||||
mai nome, numero o email. Email e telefono restano solo, cifrati, nel
|
||||
proclamatore.
|
||||
mai nomi.
|
||||
- **"Nuovo territorio"** (elenco territori) apre il designer delle piantine
|
||||
con `?nuovo=1` (`PiantineEditor::$creaTerritorio`): la mappa parte già in
|
||||
disegno e la prima piantina disegnata si apre con "nuovo territorio" scelto.
|
||||
L'editor è solo dell'amministratore: chi ha `territori.manage` ma non
|
||||
`settings.manage` resta sul form `territori.create`, che esiste ancora.
|
||||
|
||||
### Posta, contatti dei proclamatori, conferma email (12-13 ottobre 2026)
|
||||
|
||||
Richieste dell'utente, con le sue scelte:
|
||||
|
||||
- **Server di posta dall'app** (Impostazioni → Server di posta, rotta
|
||||
`posta.edit`, `Settings\PostaElettronica`): host, porta, cifratura
|
||||
(automatica / SSL / nessuna), utente, password cifrata, mittente; pulsante
|
||||
"Invia email di prova". Se compilato prende il posto del `.env`
|
||||
(`ConfigurazionePosta::applica()`, agganciata alla creazione del mail
|
||||
manager e a ogni job della coda). Non si esporta nell'XML e l'import non la
|
||||
cancella (`Setting::CAMPI_POSTA`). Nel `.env.example` `MAIL_SCHEME=tls` era
|
||||
un valore non valido per Laravel 11: ora vuoto.
|
||||
- ~~Proclamatori: email e telefono facoltativi~~ — *tolti il 19 ottobre 2026,
|
||||
vedi "Proclamatori senza contatti" qui sotto*.
|
||||
- **Dopo l'assegnazione** — *rivisto il 19 ottobre 2026, vedi "Invio del link
|
||||
dopo l'assegnazione" qui sotto*. Il riquadro in sessione
|
||||
(`Assegna::DA_INVIARE`) non esiste più.
|
||||
- **Conferma email obbligatoria** per tutti gli utenti (tranne il sorvegliante
|
||||
di gruppo), anche quelli già esistenti (la migrazione `2026_10_13_000010`
|
||||
azzera `email_verified_at`). L'admin crea l'utente con la password come
|
||||
prima; parte l'email di conferma (in italiano, `VerifyEmail::toMailUsing`);
|
||||
il link funziona anche senza login. Cambiare email (profilo o admin) la
|
||||
rende da confermare e rimanda il link. In Utenti: stato "da confermare" e
|
||||
"Reinvia conferma". Raggiungibili senza conferma: profilo, cambio password,
|
||||
pagina di conferma e — per l'amministratore — il server di posta.
|
||||
Emergenza: `php artisan utenti:conferma-email <email>`. Il primo admin
|
||||
creato da riga di comando nasce confermato.
|
||||
- Nei test gli utenti nascono confermati (`tests/TestCase.php`,
|
||||
`$confermaUtentiCreati`).
|
||||
|
||||
Provato nel browser in locale con mailpit come server configurato dall'app:
|
||||
email di prova e di conferma arrivate, link aperto senza login, account
|
||||
sbloccato. Configurazione e dati di prova rimossi dopo.
|
||||
|
||||
### Modulo S-13, editor piantine, revisione (12 ottobre 2026)
|
||||
|
||||
In produzione dal 19 ottobre 2026 (erano su `dev` dal 12):
|
||||
- `d433c93` — Modulo S-13 sul modulo ufficiale (regole in
|
||||
`app/Services/Registro/ModuloS13.php`) e blocco 1 della revisione
|
||||
(integrità dati).
|
||||
- `056d95c` — Editor piantine: bozza automatica, un solo "Pubblica" con
|
||||
anteprima, stati Bozza / Pubblicata / Modifiche da pubblicare. Come funziona:
|
||||
[Le piantine sono una sotto-app](#le-piantine-sono-una-sotto-app-renderer-esterno-agganci-minimi).
|
||||
- `426a952` — blocchi 2 e 3 della revisione (sotto).
|
||||
|
||||
### Revisione profonda: blocchi 2 e 3 chiusi
|
||||
|
||||
| Problema | Correzione |
|
||||
@@ -602,20 +545,92 @@ a ogni deploy: lento, dipendente dalla rete, e capace di fallire in produzione.
|
||||
|
||||
Da qui la ristrutturazione descritta sotto.
|
||||
|
||||
### Luglio-agosto 2026
|
||||
|
||||
**Punto in cui ci si è fermati (30/07/2026).** Su `develop` è pubblicato tutto:
|
||||
backup completo `.zip`, ripristino, azzeramento, cambio password forzato, barre
|
||||
di avanzamento reali e console di log (commit `f909791`, deploy verificato
|
||||
confrontando l'impronta degli asset). `MAX_UPLOAD_MB=2048` è impostato **solo**
|
||||
su develop; la produzione non è stata toccata e resta sul suo deploy
|
||||
precedente, senza quella variabile.
|
||||
|
||||
**Il 31/07 è nata la sotto-app "Piantine"** (nuova voce nel menu
|
||||
Amministrazione): KML dell'area città, editor dei poligoni su OpenStreetMap,
|
||||
immagini HD dal container `map-renderer` e PDF sul modulo S-12 con storico
|
||||
versioni. Architettura e agganci sono descritti in [Decisioni
|
||||
architetturali](#le-piantine-sono-una-sotto-app-renderer-esterno-agganci-minimi).
|
||||
Al primo deploy il compose costruisce anche l'immagine del renderer; non
|
||||
servono variabili nuove in Dokploy.
|
||||
|
||||
**Il 28/08 la sotto-app è stata rifatta e l'interfaccia ripassata.** Le piantine
|
||||
si sono staccate dai territori (una piantina vive anche scollegata, il territorio
|
||||
si crea dall'editor), il disegno è passato su una pagina a schermo intero, la
|
||||
cartolina S-12 è stata reimpaginata (confini sulla prima pagina, piantina al 98%
|
||||
sulla seconda) e la rigenerazione massiva è stata rimossa. In più il sistema
|
||||
visivo è stato ripassato su tutta l'applicazione, non solo qui: vedi [Il sistema
|
||||
visivo sta nei token](#il-sistema-visivo-sta-nei-token-non-nelle-viste).
|
||||
|
||||
Nello stesso giro è emerso e corretto un guasto d'ambiente che bloccava **tutti**
|
||||
gli upload in sviluppo: vedi [`HTTP_HOST` a nginx deve conservare la
|
||||
porta](#http_host-a-nginx-deve-conservare-la-porta).
|
||||
|
||||
Il 30/07 sono state fatte due cose.
|
||||
|
||||
**La suite di test, che prima non esisteva**: 79 test eseguibili con un comando,
|
||||
senza dipendere da altri servizi. Sono **verdi** ma coprono ancora una frazione
|
||||
della logica di business — vedi *Cosa resta aperto*. Non fanno parte
|
||||
dell'immagine di produzione e non girano su un deploy: sono uno strumento
|
||||
locale.
|
||||
|
||||
**La pagina "Backup e import" è stata riorganizzata**: cinque zone numerate con
|
||||
un indice che ci salta sopra evidenziandole, un solo sistema di pulsanti (con
|
||||
gli stati di focus, che prima non c'erano), e **una sola console** per tutte le
|
||||
operazioni, agganciata in fondo alla finestra, al posto dei cinque riquadri di
|
||||
log sparsi. L'azzeramento è diventato un componente a sé per poter stare in
|
||||
fondo alla pagina. Tutto verificato lato server dai test; **il comportamento nel
|
||||
browser — apertura automatica della console, scorrimento, lampeggio delle zone —
|
||||
non è stato provato**, perché richiede un browser: è la prima cosa da guardare
|
||||
aprendo la pagina.
|
||||
|
||||
Il ripristino dall'interfaccia resta impraticabile per pacchetti grandi finché
|
||||
non si decide come aggirare Cloudflare: è la prima voce di *Cosa resta aperto*
|
||||
e l'unica che richiede una decisione, non del lavoro.
|
||||
|
||||
---
|
||||
|
||||
## Dove gira davvero
|
||||
|
||||
Due ambienti su Dokploy, stesso progetto `TerManager2`, entrambi alimentati dal
|
||||
repository Gitea `francesco/termanager2` con **autoDeploy attivo**.
|
||||
repository Gitea `francesco/termanager2` su `repository.pyconetwork.it` (il
|
||||
vecchio `git.pyconetwork.it` non è più la sorgente).
|
||||
|
||||
| Ambiente | Branch | Dominio | Compose (`appName`) |
|
||||
|--------------|--------|-------------------------------------|----------------------------|
|
||||
| `production` | `main` | `termanager2.pyconetwork.it` | `termanager2-core-vizcxr` |
|
||||
| `develop` | `dev` | `demo-termanager.pyconetwork.it` | `termanager2-core-vbekis` |
|
||||
| Ambiente | Branch | Dominio | Compose (nome / `appName` / `composeId`) |
|
||||
|--------------|--------|-------------------------------------|------------------------------------------|
|
||||
| `production` | `main` | `termanager2.pyconetwork.it` | `termanager2-core-production` / `termanager2-core-vizcxr` / `vh_NtgaPyfQintCbcB3p6` |
|
||||
| `develop` | `dev` | `demo-termanager.pyconetwork.it` | `termanager2-core-demo` / `termanager2-core-vbekis` / `20DoOKciTzmhlPWdcqcxz` |
|
||||
|
||||
Conseguenze pratiche, tutte imparate sul campo:
|
||||
|
||||
- **L'autodeploy non parte.** È attivo in Dokploy, ma dal 19 ottobre 2026
|
||||
nessun push ha avviato un deploy (il webhook di Gitea è da verificare):
|
||||
dopo un push si aspettano un paio di minuti e, se in `deployment` non
|
||||
compare niente di nuovo, si avvia a mano (`compose.deploy` via API, o
|
||||
*Deploy* nell'interfaccia). Controllare prima di avviare, per non fare
|
||||
deploy doppi.
|
||||
|
||||
- **Test sul server, prima del push.** In locale (Windows) non ci sono PHP,
|
||||
Docker né Node: la suite gira sul server Dokploy con una *schedule* di tipo
|
||||
`dokploy-server` usa e getta, che costruisce lo stage `test` del Dockerfile
|
||||
e lancia anche `node --test tests/js/`. Per provare un commit non ancora
|
||||
pubblicato (il push su `dev` lo pubblicherebbe): `git bundle create x
|
||||
<commit della checkout>..dev`, bundle in base64 dentro lo script, `git
|
||||
clone` della checkout di Dokploy in `/tmp` e `git fetch` del bundle. La
|
||||
checkout di Dokploy (`/etc/dokploy/compose/<appName>/code`) è **shallow** e
|
||||
può essere indietro rispetto a `origin/dev` (i commit di sola
|
||||
documentazione non la aggiornano): il bundle parte dal suo `git log -1`. La
|
||||
schedule gira dentro il container di Dokploy: `docker run -v /tmp/...` non
|
||||
vede quei file, si passano con `tar | docker run -i`.
|
||||
|
||||
- **Il branch decide l'ambiente.** Pubblicare su `dev` non tocca la produzione,
|
||||
che segue `main`. È la separazione su cui contare quando si vuole provare
|
||||
qualcosa senza rischi.
|
||||
@@ -1590,7 +1605,9 @@ blocchi che impedivano di scrivere il primo test sono stati rimossi: stage
|
||||
docker compose --profile test run --build --rm test
|
||||
```
|
||||
|
||||
**Coperto oggi — 294 test, tutti verdi:**
|
||||
**Coperto oggi — 577 test PHP e 13 JavaScript, tutti verdi (20/10/2026).**
|
||||
Senza Docker in locale si fanno girare sul server: vedi [Dove gira
|
||||
davvero](#dove-gira-davvero). I file storici, con cosa proteggono:
|
||||
|
||||
| File | Cosa protegge |
|
||||
|------|---------------|
|
||||
@@ -1607,6 +1624,21 @@ docker compose --profile test run --build --rm test
|
||||
| `tests/Unit/PiantinaRendererTest.php` | Il client del renderer: formato, qualita' e lato massimo davvero spediti, JPEG salvato con l'estensione giusta, il file di un formato precedente rimosso invece che lasciato orfano, errori e risposte non-immagine trasformati in messaggi leggibili, geometria corrotta bloccata prima della chiamata |
|
||||
| `tests/Feature/HomeMediaDurataTest.php` | La "media percorrenza congregazione": unità di misura, esclusione delle assegnazioni aperte, dei rientri nello stesso giorno, delle date impossibili ereditate dalla vecchia applicazione e degli altri anni di servizio |
|
||||
|
||||
Arrivati dopo (ottobre 2026), uno per funzione: `GruppiServizioTest` (gruppi
|
||||
di servizio, compresa la rotta vera delle azioni Livewire del sorvegliante,
|
||||
priorità e filtri), `InvioLinkTest` (invio del link, pulsante Rientro, Assegna
|
||||
dalla Home), `StoricoAssegnazioniTest`, `SceltaRicercaTest` e
|
||||
`tests/js/scelta.test.mjs` (scelta con ricerca), `RitornoTest`,
|
||||
`AssegnaPrestazioniTest` (peso della pagina Assegna e delle righe
|
||||
dell'elenco), `AssegnazioneSovrapposizioneTest`, `CampagnaCalcoliTest`,
|
||||
`CambioPasswordTest`, `ConfermaEmailTest`, `PostaElettronicaTest`,
|
||||
`PrivacyTest`, `LogSenzaNomiTest`, `AvvisiTest`, `CodaTest`,
|
||||
`UltimoAmministratoreTest`, i test di import (`XmlExchangeImportTest`,
|
||||
`ImportXmlFileOrfaniTest`, `ImportPdfTerritoriTest`, `PostoUnicoImportTest`,
|
||||
`RipristinoBackupTest`) e dei file (`TerritorioFileTest`,
|
||||
`TerritoriFileOrfaniTest`, `PubblicazionePiantinaTest`,
|
||||
`AzzeramentoPiantineTest`, `PiantineAreaTest`).
|
||||
|
||||
Il primo giro ha già trovato tre difetti reali, tutti corretti: il warning di
|
||||
`ZipArchive::addFile()` che copriva il messaggio d'errore vero, il `GREATEST()`
|
||||
in migrazione, e `@disabled` dentro un tag di componente (vedi *Bug corretti*).
|
||||
@@ -1621,22 +1653,25 @@ nel browser, e nella suite non c'è un browser. Quella parte va provata a mano.
|
||||
entry, dimensione della singola entry, totale decompresso. Sono tre `if` che
|
||||
nessun test tocca, e il caso in cui contano non si presenta mai per sbaglio.
|
||||
- `Jobs/RestoreBackupArchive` e `Jobs/ResetApplication` **nel loro insieme**:
|
||||
l'ordine staging → database → file, il `failed()` che libera il posto, la
|
||||
sopravvivenza dell'amministratore che ha chiesto l'azzeramento, il
|
||||
`password_change_required` acceso su tutte le righe. È la logica il cui
|
||||
errore si scopre solo quando è troppo tardi.
|
||||
- `Livewire/Settings/XmlExchange`: il **round-trip** export → import senza
|
||||
perdite, che è il presidio contro il tipo di bug già visto con `legacy_id`.
|
||||
in parte coperti (`RipristinoBackupTest`, `ZonaPericolosaTest`,
|
||||
`AzzeramentoPiantineTest`, l'azzeramento in `GruppiServizioTest`); restano
|
||||
da verificare l'ordine staging → database → file e il `failed()` che libera
|
||||
il posto. È la logica il cui errore si scopre solo quando è troppo tardi.
|
||||
- `Livewire/Settings/XmlExchange`: il round-trip export → import è coperto per
|
||||
le parti nuove (gruppi, invio del link) e da `XmlExchangeImportTest`, ma
|
||||
non campo per campo su tutto il dataset.
|
||||
- Le regole di business: anno teocratico (`AnnoTeocratico::perData` a cavallo
|
||||
del 1º settembre), `Territorio::stato` e la soglia `giorni_per_smarrito`,
|
||||
priorità automatica, percentuale di percorrenza delle campagne.
|
||||
del 1º settembre), `Territorio::stato` e la soglia `giorni_per_smarrito`.
|
||||
La priorità (`Territorio::ordinaPerPriorita`) e la percorrenza delle
|
||||
campagne ora hanno i loro test.
|
||||
- Il permesso come contratto: che un `assistente` **non** possa modificare o
|
||||
eliminare un'assegnazione, una campagna, un territorio o un proclamatore. È
|
||||
scritto in tre posti (rotta, componente, vista) e un test di richiesta lo
|
||||
verificherebbe tutto insieme.
|
||||
|
||||
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.
|
||||
Attenzione a non scrivere test su ciò che SQLite non sa eseguire: resta solo
|
||||
l'ordinamento di `Registro::render()`, che usa SQL specifico di MySQL (la Home
|
||||
non più: vedi *La suite di test ha un'immagine sua*).
|
||||
|
||||
### 2. Race su doppia assegnazione — chiusa il 09/10/2026
|
||||
|
||||
@@ -1703,6 +1738,18 @@ e `territorio_pdf_versioni`, con il numero del territorio per ricollegarle) e
|
||||
la cartella `piantine/` dell'area KML. Un archivio di formato 1 si ripristina
|
||||
lo stesso: piantine e storico restano vuoti, l'area in uso non si tocca.
|
||||
|
||||
### 8. L'autodeploy non parte
|
||||
|
||||
Dal 19 ottobre 2026 nessun push su `dev` o `main` ha avviato un deploy, pur
|
||||
con autoDeploy attivo in Dokploy. Da verificare il webhook del repository in
|
||||
Gitea (`repository.pyconetwork.it`). Intanto ogni deploy si avvia a mano:
|
||||
vedi [Dove gira davvero](#dove-gira-davvero).
|
||||
|
||||
### 9. Gruppi di servizio e lavori del 20 ottobre non ancora in produzione
|
||||
|
||||
Sono sulla demo e l'utente li sta provando lì; per il rilascio serve il suo
|
||||
ok. Come portarli in produzione: [Stato attuale](#stato-attuale).
|
||||
|
||||
---
|
||||
|
||||
## Trappole da conoscere
|
||||
@@ -1797,7 +1844,8 @@ cp .env.example .env
|
||||
# SESSION_SECURE_COOKIE=false, e le tre password
|
||||
docker network create dokploy-network
|
||||
|
||||
# 0. La suite passa (non serve nient'altro acceso, ~15 s)
|
||||
# 0. La suite passa (non serve nient'altro acceso). Senza Docker in locale:
|
||||
# sul server Dokploy, vedi "Dove gira davvero"
|
||||
docker compose --profile test run --build --rm test
|
||||
|
||||
# 1. L'immagine builda
|
||||
|
||||
70
README.md
70
README.md
@@ -1,8 +1,9 @@
|
||||
# TerManager2
|
||||
|
||||
Applicazione web per la gestione dell'assegnazione e del rientro dei territori
|
||||
(cartoline) di congregazione, con statistiche di percorrenza, campagne speciali
|
||||
e registro storico completo.
|
||||
(cartoline) di congregazione, con statistiche di percorrenza, campagne speciali,
|
||||
gruppi di servizio (un sorvegliante distribuisce ai membri un pacchetto di
|
||||
territori) e registro storico completo.
|
||||
|
||||
I dati personali dei proclamatori sono **cifrati nel database**, e ogni
|
||||
operazione è tracciata in un **audit log** consultabile.
|
||||
@@ -410,24 +411,27 @@ Redis, quindi puntarli su un driver diverso interrompe l'elaborazione dei job.
|
||||
Configurabili dall'interfaccia (**Impostazioni**, permesso `settings.manage`),
|
||||
non da variabili d'ambiente:
|
||||
|
||||
| Impostazione | Descrizione |
|
||||
|--------------------------------|------------------------------------------------------|
|
||||
| Nome congregazione | Mostrato nell'intestazione e nei PDF |
|
||||
| Logo | Immagine nell'intestazione |
|
||||
| URL pubblico base | Base dei link PDF condivisibili |
|
||||
| Giorni giacenza "da assegnare" | Soglia oltre cui un territorio è da assegnare |
|
||||
| Mesi giacenza "prioritari" | Soglia oltre cui un territorio diventa prioritario |
|
||||
| Giorni per "smarrito" | Soglia oltre cui un'assegnazione è da rientrare |
|
||||
| Elementi nelle liste della home| Quanti record mostrare nelle tre liste rapide |
|
||||
| Validità link PDF (mesi) | Scadenza dei link pubblici alle cartoline |
|
||||
| Download PDF visibile | Mostra o nasconde il pulsante di download |
|
||||
| Retention audit log (giorni) | Dopo quanti giorni i log vengono cancellati |
|
||||
| Impostazione | Descrizione |
|
||||
|------------------------------------|------------------------------------------------------|
|
||||
| Nome congregazione | Mostrato nell'intestazione, nei PDF e nell'informativa |
|
||||
| Soglia da assegnare (giorni) | Dopo quanti giorni dal rientro un territorio è "da assegnare" |
|
||||
| Soglia priorità (giorni) | Dopo quanti giorni dal rientro un territorio diventa prioritario |
|
||||
| Soglia rientro (giorni) | Oltre questa un'assegnazione è "da rientrare"; è anche la durata del link alla piantina |
|
||||
| Limite liste Home | Quanti territori mostrare nelle due liste della Home |
|
||||
| "Scarica PDF" nel viewer | Mostra o nasconde il pulsante di download a chi apre il link |
|
||||
| Conservazione audit (giorni) | Dopo quanti giorni le voci del registro delle attività si cancellano |
|
||||
|
||||
Il server di posta ha una pagina sua (*Impostazioni → Server di posta*). Il
|
||||
logo nell'intestazione, se c'è, arriva da un backup: la pagina non lo carica.
|
||||
|
||||
---
|
||||
|
||||
## Aggiornamenti
|
||||
|
||||
Push su `main` → **Deploy** su Dokploy. Ad ogni avvio il container:
|
||||
Push su `main` → **Deploy** su Dokploy. Se l'autodeploy non parte entro un
|
||||
paio di minuti (succede: vedi HANDOFF, *Dove gira davvero*), il deploy si
|
||||
avvia a mano dall'interfaccia o con `compose.deploy`. Ad ogni avvio il
|
||||
container:
|
||||
|
||||
1. ricrea le directory di storage e risolve `APP_KEY`
|
||||
2. attende il database
|
||||
@@ -687,9 +691,11 @@ docker compose --profile dev up -d --build
|
||||
- Anno teocratico corrente (creato automaticamente, settembre → agosto)
|
||||
- Media mensile di percorrenza e durata media delle assegnazioni
|
||||
- Campagna attiva con barra di progresso
|
||||
- Tre liste rapide con azione diretta: **da assegnare**, **prioritari**,
|
||||
**da rientrare**; quando l'elenco è troncato, **"Vedi tutti"** è un pulsante
|
||||
in testata che apre l'elenco territori già filtrato
|
||||
- Due liste rapide con azione diretta: **da assegnare** (prima i prioritari,
|
||||
con il badge ★, poi quelli fermi da più tempo) e **da rientrare**; quando
|
||||
l'elenco è troncato, **"Vedi tutti"** è un pulsante in testata che apre
|
||||
l'elenco territori già filtrato. "Assegna →" apre la pagina *Assegna* con
|
||||
il territorio scelto e senza l'elenco dei territori assegnati
|
||||
|
||||
### Territori
|
||||
- CRUD completo con upload PDF e miniatura generata automaticamente
|
||||
@@ -949,10 +955,16 @@ Calcolato dinamicamente, non memorizzato:
|
||||
| `inattivo` | Contrassegnato come non attivo |
|
||||
|
||||
### Priorità
|
||||
Un territorio è prioritario se lo è per **flag manuale** o perché non viene
|
||||
assegnato da più mesi della soglia configurata. La soglia automatica **vince
|
||||
sempre** sul flag: l'amministratore può aggiungere priorità, non toglierla
|
||||
quando la soglia è scattata.
|
||||
Un territorio è prioritario se lo è per **flag manuale** o perché è fermo
|
||||
(dall'ultimo rientro, o dalla creazione se mai assegnato) da più giorni della
|
||||
soglia configurata. La soglia automatica **vince sempre** sul flag:
|
||||
l'amministratore può aggiungere priorità, non toglierla quando la soglia è
|
||||
scattata. Assegnare il territorio azzera il flag.
|
||||
|
||||
Dove si sceglie un territorio da dare — "Da assegnare" nella Home, l'aggiunta
|
||||
al pacchetto di un gruppo, "Il mio gruppo" — l'ordine è lo stesso: prima i
|
||||
prioritari, poi quelli fermi da più tempo, poi il numero
|
||||
(`Territorio::ordinaPerPriorita()`).
|
||||
|
||||
### Percentuale di percorrenza delle campagne
|
||||
- **Denominatore**: tutti i territori attivi
|
||||
@@ -1000,14 +1012,16 @@ sempre.
|
||||
```
|
||||
TerManager2/
|
||||
├── app/
|
||||
│ ├── Console/Commands/ # audit:cleanup, backup:cleanup, app:create-initial-admin
|
||||
│ ├── Console/Commands/ # audit:cleanup, backup:cleanup, app:create-initial-admin,
|
||||
│ │ # utenti:conferma-email, territori:file-orfani
|
||||
│ ├── Http/Controllers/ # PDF pubblici, import, backup, login, cambio password
|
||||
│ ├── Http/Middleware/ # RequirePasswordChange
|
||||
│ ├── Http/Middleware/ # RequirePasswordChange, LimitaSorvegliante
|
||||
│ ├── Jobs/ # Import PDF, backup, ripristino, azzeramento (in coda)
|
||||
│ ├── Livewire/ # Tutti i componenti di interfaccia
|
||||
│ ├── Models/ # 9 modelli Eloquent
|
||||
│ ├── Livewire/ # Tutti i componenti di interfaccia (Gruppi/: gruppi di servizio)
|
||||
│ ├── Models/ # 12 modelli Eloquent
|
||||
│ ├── Providers/ # AppServiceProvider
|
||||
│ └── Services/ # Miniature PDF, stato import, archivio backup
|
||||
│ └── Services/ # Miniature PDF, import, archivio backup, posta,
|
||||
│ # piantine, modulo S-13, gruppi di servizio
|
||||
├── bootstrap/app.php # Middleware, proxy e host fidati
|
||||
├── config/ # Configurazione Laravel
|
||||
├── database/
|
||||
@@ -1029,7 +1043,7 @@ TerManager2/
|
||||
│ ├── components/btn.blade.php # L'unico pulsante dell'applicazione
|
||||
│ └── livewire/ # Una vista per componente
|
||||
├── routes/web.php # Rotte con i relativi permessi
|
||||
├── tests/ # Suite PHPUnit (profilo compose "test")
|
||||
├── tests/ # Suite PHPUnit (profilo compose "test") e tests/js (Node)
|
||||
├── phpunit.xml # Configurazione e ambiente dei test
|
||||
├── docker-compose.yml # Unico, produzione e sviluppo
|
||||
├── .env.example # Tutta la configurazione
|
||||
|
||||
Reference in New Issue
Block a user