++ 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:
Francesco Picone
2026-10-20 17:48:18 +02:00
parent 7d479a2c34
commit 5fb42b05c7
2 changed files with 378 additions and 316 deletions

View File

@@ -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

View File

@@ -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.
@@ -411,23 +412,26 @@ 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 |
|------------------------------------|------------------------------------------------------|
| 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