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.
I dati personali dei proclamatori sono cifrati nel database, e ogni operazione è tracciata in un audit log consultabile.
Indice
Per iniziare
- Deploy su Dokploy — la via consigliata per la produzione
- Deploy su altri sistemi — Coolify, Portainer, server con Docker
- Sviluppo locale
Gestione quotidiana
- Variabili d'ambiente
- Impostazioni applicative
- Aggiornamenti
- Backup e ripristino
- Comandi utili
- Troubleshooting
Riferimento
Deploy su Dokploy
Come funziona
L'immagine Docker è autosufficiente: dipendenze PHP e asset frontend sono compilati durante il build, non all'avvio. Nginx gira nello stesso container di php-fpm. Un deploy quindi è semplicemente: build → migrazioni → online.
Non serve il codice sorgente sul server, non ci sono bind mount, e non viene
eseguito né composer install né npm build all'avvio.
| Servizio | Ruolo | Esposto |
|---|---|---|
app |
nginx + php-fpm — l'applicazione | via Traefik |
queue-worker |
Job in coda (import PDF massivi) | no |
scheduler |
Task pianificati (pulizia audit log) | no |
mariadb |
Database | no |
redis |
Cache, sessioni, code | no |
Solo app è raggiungibile dall'esterno. Database e Redis restano su una rete
interna senza rotta verso Internet.
Passo 1 — Crea l'applicazione
Su Dokploy: Create Application → tipo Compose.
| Campo | Valore |
|---|---|
| Repository | il tuo repo Git |
| Branch | main |
| Compose Path | docker-compose.yml |
Passo 2 — Genera le credenziali
Sul tuo computer, esegui questi comandi e tieni da parte l'output:
echo "APP_KEY=base64:$(openssl rand -base64 32)"
echo "DB_PASSWORD=$(openssl rand -base64 24)"
echo "DB_ROOT_PASSWORD=$(openssl rand -base64 24)"
echo "REDIS_PASSWORD=$(openssl rand -base64 24)"
Conserva
APP_KEYin un password manager. I dati personali dei proclamatori sono cifrati con questa chiave: se la perdi, non sono più leggibili nemmeno avendo il backup del database.
Passo 3 — Imposta le variabili d'ambiente
Nella sezione Environment di Dokploy, incolla questo blocco e sostituisci i valori:
# --- Applicazione ---
APP_NAME=TerManager2
APP_ENV=production
APP_DEBUG=false
APP_TIMEZONE=Europe/Rome
# Deve combaciare con l'indirizzo da cui accedi, porta inclusa.
# per IP:porta -> http://IP-DEL-SERVER:8080
# con dominio -> https://territori.tuodominio.it
APP_URL=http://IP-DEL-SERVER:8080
# --- Chiavi e password (dal Passo 2) ---
APP_KEY=base64:...
DB_PASSWORD=...
DB_ROOT_PASSWORD=...
REDIS_PASSWORD=...
# --- Database (mariadb e' il nome del servizio, non cambiarlo) ---
DB_CONNECTION=mysql
DB_HOST=mariadb
DB_PORT=3306
DB_DATABASE=termanager2
DB_USERNAME=termanager2
# --- Redis (idem: redis e' il nome del servizio) ---
REDIS_HOST=redis
REDIS_PORT=6379
CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
# --- Primo amministratore (creato al primo avvio) ---
INITIAL_ADMIN_NAME=Nome Cognome
INITIAL_ADMIN_EMAIL=admin@tuodominio.it
INITIAL_ADMIN_PASSWORD=UnaPasswordSolida
# --- Porte esposte sull'host ---
APP_BIND=0.0.0.0 # 127.0.0.1 se ci metti davanti un reverse proxy
APP_PORT=8080
DB_BIND=127.0.0.1 # database raggiungibile solo via tunnel SSH
DB_PORT_HOST=3306
# --- Sessioni e log ---
# false con accesso HTTP, true solo in HTTPS: vedi .env.example
SESSION_SECURE_COOKIE=false
LOG_CHANNEL=stderr
LOG_LEVEL=warning
# --- Posta (SMTP esterno) ---
MAIL_MAILER=smtp
MAIL_HOST=smtp.tuoprovider.it
MAIL_PORT=587
MAIL_USERNAME=utente
MAIL_PASSWORD=password
MAIL_SCHEME=tls
MAIL_FROM_ADDRESS=noreply@tuodominio.it
APP_URLdeve corrispondere esattamente al dominio che servirai, schemahttps://incluso. L'applicazione rifiuta richieste con unHostdiverso: è la protezione contro l'host header injection, ed è la causa più comune di "pagina che non si apre" al primo deploy.
Passo 4 — Assegna il dominio (facoltativo)
Con la configurazione qui sopra l'applicazione risponde gia' su
http://IP-DEL-SERVER:8080. Questo passo serve solo se vuoi un dominio con
HTTPS — consigliato, perche' altrimenti password e sessioni viaggiano in
chiaro.
Quando lo fai, cambia anche: APP_URL=https://tuodominio.it,
SESSION_SECURE_COOKIE=true e APP_BIND=127.0.0.1.
Domains → Add Domain:
| Campo | Valore |
|---|---|
| Service Name | app |
| Container Port | 80 |
| Host | il tuo dominio |
| HTTPS | attivo, Let's Encrypt |
Dokploy genera da sé le label Traefik: non vanno scritte nel compose.
Passo 5 — Deploy
Premi Deploy. Il primo avvio esegue migrazioni, seed di ruoli e permessi, e crea l'amministratore. Richiede in genere uno o due minuti.
Nei log dovresti vedere:
[✓] Storage directories ready.
[✓] APP_KEY provided via environment.
[✓] Database reachable.
[✓] Migrations applied.
[✓] Seed completed.
Initial admin created: admin@tuodominio.it
[✓] Caches warmed.
TerManager2 — ready
Accedi con le credenziali INITIAL_ADMIN_* e cambia subito la password.
Se qualcosa non parte
Lo stack si rifiuta di avviarsi quando manca una variabile essenziale, con un messaggio esplicito:
| Messaggio nei log | Cosa fare |
|---|---|
required variable DB_PASSWORD is missing a value |
Imposta DB_PASSWORD |
required variable REDIS_PASSWORD is missing a value |
Imposta REDIS_PASSWORD |
[✗] APP_URL is not set |
Imposta APP_URL |
[✗] APP_URL must include the scheme |
Aggiungi https:// davanti |
È voluto: meglio un errore immediato che un database in produzione con la password di default.
Deploy su altri sistemi
Lo stack è Docker Compose standard e funziona su Coolify, Portainer, CapRover o un semplice server con Docker.
Le uniche due particolarità sono:
-
Il file
.env. Dokploy lo genera dalle variabili della sua interfaccia. Altrove crealo a mano:cp .env.example .enve compila i valori. -
La rete
dokploy-network. In fondo adocker-compose.ymlc'è una rete esterna che serve al Traefik di Dokploy per raggiungere il container. Hai due possibilità:- Tenerla, creandola una volta sola:
docker network create dokploy-network - Rimuoverla: elimina
- dokploy-networkdalle reti del servizioappe il bloccodokploy-network:in fondo al file.
- Tenerla, creandola una volta sola:
Poi imposta nel .env la porta su cui esporre l'applicazione e avvia:
# ascolta su tutte le interfacce, per metterci davanti un reverse proxy
echo "APP_PORT=0.0.0.0:8080" >> .env
docker compose up -d --build
docker compose logs -f app
Infine metti il tuo reverse proxy (Nginx, Caddy, Traefik) davanti alla porta
8080, con certificato TLS.
Sviluppo locale
Si usa lo stesso docker-compose.yml della produzione, con il profilo dev
che aggiunge Mailpit:
cp .env.example .env
docker network create dokploy-network # una volta sola
# nel .env, per l'uso locale:
# APP_URL=http://localhost:8080
# APP_ENV=local
# MAIL_MAILER=smtp MAIL_HOST=mailpit MAIL_PORT=1025
# e le tre password a piacere
# (SESSION_SECURE_COOKIE=false e APP_BIND/APP_PORT vanno gia' bene)
docker compose --profile dev up -d --build
| Cosa | Dove |
|---|---|
| Applicazione | http://localhost:8080 |
| Mailpit (posta test) | http://localhost:8025 |
| Login | quello che hai messo in INITIAL_ADMIN_* |
Se la porta 8080 è occupata, cambia APP_PORT nel .env
(es. APP_PORT=127.0.0.1:8081).
Modifiche al codice senza ricostruire l'immagine
L'immagine è autosufficiente: vendor/ e public/build stanno dentro, quindi
di norma una modifica al codice richiede docker compose up -d --build.
Per avere invece le modifiche PHP immediate, crea un
docker-compose.override.yml — Docker Compose lo applica da solo, senza
bisogno di alcun flag:
services:
app:
environment:
PHP_OPCACHE_VALIDATE_TIMESTAMPS: 1
volumes:
- ./:/var/www/html
# vendor e build restano quelli dell'immagine, non quelli dell'host
- /var/www/html/vendor
- /var/www/html/public/build
Il file è già in .gitignore, quindi resta solo tuo.
Per lavorare sugli asset frontend con hot reload:
npm install
npm run dev
Variabili d'ambiente
Il container legge la configurazione dall'ambiente di processo (su Dokploy: le
variabili della UI, salvate in .env). Il file docker-compose.yml non
ridichiara le variabili applicative: le prende dal .env così com'è.
Obbligatorie
| Variabile | Esempio | Note |
|---|---|---|
APP_URL |
https://territori.esempio.it |
Deve combaciare col dominio servito |
APP_KEY |
base64:... |
Cifra i dati personali — da custodire |
DB_PASSWORD |
— | Nessun default |
DB_ROOT_PASSWORD |
— | Nessun default |
REDIS_PASSWORD |
— | Nessun default |
Principali facoltative
| Variabile | Default | Descrizione |
|---|---|---|
APP_NAME |
TerManager2 |
Nome mostrato nell'interfaccia |
APP_ENV |
production |
local in sviluppo |
APP_DEBUG |
false |
Mai true in produzione |
APP_TIMEZONE |
Europe/Rome |
Fuso orario |
LOG_LEVEL |
warning |
debug per diagnosticare |
SESSION_SECURE_COOKIE |
false |
true solo servendo in HTTPS |
APP_BIND |
0.0.0.0 |
127.0.0.1 dietro un reverse proxy |
APP_PORT |
8080 |
Porta dell'applicazione sull'host |
DB_BIND |
127.0.0.1 |
Interfaccia della porta MariaDB |
DB_PORT_HOST |
3306 |
Cambiala se la 3306 e' occupata |
INITIAL_ADMIN_EMAIL |
— | Admin creato al primo avvio |
INITIAL_ADMIN_PASSWORD |
— | Idem |
INITIAL_ADMIN_NAME |
Amministratore |
Idem |
MAIL_MAILER |
log |
smtp per inviare davvero |
RUN_MIGRATIONS |
true |
Migrazioni automatiche all'avvio |
RUN_DB_SEED_ON_FIRST_START |
true |
Seed al primo avvio |
Da non cambiare
Questi valori sono nomi di servizi definiti in docker-compose.yml, non
indirizzi di rete. Se li modifichi il container non trova più il database o
Redis:
DB_HOST=mariadb
REDIS_HOST=redis
Nemmeno CACHE_STORE, SESSION_DRIVER e QUEUE_CONNECTION andrebbero
spostati altrove: lo stack fornisce Redis e il queue worker consuma la coda
Redis, quindi puntarli su un driver diverso interrompe l'elaborazione dei job.
Impostazioni applicative
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 |
Aggiornamenti
Push su main → Deploy su Dokploy. Ad ogni avvio il container:
- ricrea le directory di storage e risolve
APP_KEY - attende il database
- applica le migrazioni pendenti
- salta il seed (già eseguito — il marker è nel volume storage)
- rigenera le cache di configurazione, rotte e viste
Se le migrazioni falliscono il container non parte: preferibile a servire traffico su uno schema applicato a metà.
Un aggiornamento ordinario non richiede alcun intervento manuale.
Backup e ripristino
Dall'applicazione: pacchetto .zip (consigliato)
Backup e import → Crea e scarica backup. Produce un unico .zip con
tutti i dati gestionali, gli utenti e tutti i file caricati (PDF dei territori,
miniature, logo). È la via da usare per spostare o rimettere in piedi
un'installazione senza toccare i container.
L'archivio si prepara in background e la pagina mostra l'avanzamento reale (file compressi / file totali); il download parte da solo appena è pronto. Stessa cosa per il ripristino: prima la barra di caricamento del pacchetto, poi quella delle fasi di lavoro (estrazione, database, file).
Sulla stessa pagina, Ripristina backup ricarica il pacchetto: sostituisce i dati gestionali correnti e i file caricati.
Dopo un ripristino tutti gli utenti — amministratore compreso — devono impostare una nuova password. Il pacchetto contiene gli hash delle password di ogni account: chi lo ha avuto in mano ha potuto tentarne il crack offline, quindi le vecchie credenziali non sono più considerate sicure. Al primo accesso l'applicazione resta bloccata sulla schermata di cambio password.
Il pacchetto non contiene APP_KEY: i nomi dei proclamatori sono cifrati
con quella chiave, quindi il ripristino va fatto su un'installazione che ha la
stessa APP_KEY di quando il backup è stato creato.
Il tetto per l'upload è 256 MB (client_max_body_size in
docker/nginx/default.conf e upload_max_filesize/post_max_size in
docker/php/php.ini). Oltre quella soglia usa il backup dei volumi qui sotto.
Zona pericolosa: azzera tutto
In fondo alla stessa pagina, Scarica backup e azzera tutto riporta
l'applicazione allo stato di installazione appena eseguita. Va scritto
AZZERA TUTTO per sbloccare il pulsante, e l'operazione procede in due tempi:
- Viene creato e scaricato un backup completo di tutto ciò che c'è.
- L'operazione si ferma e aspetta: controlli il file scaricato e confermi con un secondo clic. Fino a lì non viene toccato nulla, e puoi annullare.
- Solo dopo la conferma vengono eliminati territori, proclamatori, assegnazioni, campagne, zone, tipologie, anni di servizio, il registro attività e tutti i file caricati; le impostazioni tornano ai default e i ruoli vengono riseminati.
Tutti gli altri utenti vengono eliminati. Resta solo l'account dell'amministratore che ha eseguito l'operazione, con la sua password attuale — come un'installazione nuova, che ha un amministratore solo. Cancellarli tutti lascerebbe l'applicazione senza nessuno che possa accedere fino al riavvio del container.
Il backup scaricato al punto 1 è l'unico modo per tornare indietro.
Dai volumi: cosa salvare
| Volume | Contenuto | Nel backup |
|---|---|---|
db_data |
Database MariaDB | ✅ |
storage_data |
PDF territori, miniature, log, APP_KEY di fallback |
✅ |
redis_data |
Cache e sessioni — rigenerabili | — |
Più il valore di APP_KEY. Il database da solo non basta: senza la chiave
i dati personali cifrati non sono leggibili.
Backup
# Database
docker exec <container-db> mariadb-dump -u root -p"$DB_ROOT_PASSWORD" \
termanager2 | gzip > termanager2_$(date +%Y%m%d).sql.gz
# File caricati (PDF e miniature)
docker run --rm -v <progetto>_storage_data:/data -v "$PWD":/backup \
alpine tar czf /backup/storage_$(date +%Y%m%d).tar.gz -C /data .
Automatizzabile con cron:
0 2 * * * docker exec <container-db> mariadb-dump -u root -p"$DB_ROOT_PASSWORD" termanager2 | gzip > /backups/termanager2_$(date +\%Y\%m\%d).sql.gz
Ripristino
gunzip -c termanager2_20260718.sql.gz | \
docker exec -i <container-db> mariadb -u root -p"$DB_ROOT_PASSWORD" termanager2
docker exec <container-app> php artisan migrate --force
docker exec <container-app> php artisan cache:clear
Assicurati che APP_KEY sia la stessa di quando il backup è stato creato.
Comandi utili
Sostituisci <app> con il nome del container (lo trovi con docker ps).
# Log in tempo reale
docker logs -f <app>
# Stato delle migrazioni
docker exec -it <app> php artisan migrate:status
# Shell interattiva Laravel
docker exec -it <app> php artisan tinker
# Reimpostare la password di un utente
docker exec -it <app> php artisan tinker --execute="
\$u = App\Models\User::where('email','admin@tuodominio.it')->first();
\$u->password = Hash::make('NuovaPassword');
\$u->save();
"
# Creare un altro amministratore
docker exec -it <app> php artisan app:create-initial-admin \
--name="Nome" --email="mail@dominio.it" --password="Password"
# Pulizia manuale dell'audit log
docker exec -it <app> php artisan audit:cleanup
# Svuotare le cache
docker exec -it <app> php artisan optimize:clear
Troubleshooting
Il container app resta unhealthy
L'health check interroga /up. Guarda i log (docker logs <app>): quasi
sempre è il database irraggiungibile o una migrazione fallita.
Errore 400 o pagina bianca sul dominio
APP_URL non corrisponde al dominio servito. Deve combaciare esattamente,
schema https:// incluso. È la protezione contro l'host header injection.
La pagina appare senza stile (CSS mancante)
Gli asset sono compilati dentro l'immagine. Se mancano, è fallito lo stage di
build frontend: cerca npm run build nel log di build del deploy.
I job restano in coda
Verifica che queue-worker sia healthy e che REDIS_PASSWORD sia identica per
tutti i servizi.
L'import PDF non termina
L'import ha un timeout di 15 minuti; oltre, il job viene marcato failed e i file temporanei rimossi. Controlla i log del worker per individuare il PDF responsabile.
I dati cifrati non sono più leggibili
APP_KEY è cambiata. Ripristina il valore originale: non esiste altro modo di
recuperare i dati. Se la chiave è persa definitivamente, i proclamatori vanno
reinseriti (territori, assegnazioni e storico restano intatti — non sono
cifrati).
Reset completo (solo sviluppo)
docker compose --profile dev down -v
docker compose --profile dev up -d --build
-v cancella i volumi: database, Redis e file caricati vengono persi.
Funzionalità
Dashboard
- 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
Territori
- CRUD completo con upload PDF e miniatura generata automaticamente
- Filtri per zona, tipologia e stato
- Badge di stato colorati e indicatore di priorità (manuale o automatica)
- Dettaglio con visualizzatore PDF e storico assegnazioni per anno teocratico
- Link PDF pubblico condivisibile, a scadenza configurabile
- Soft delete con cestino (ripristino o eliminazione definitiva)
- Import massivo di PDF da cartella o archivio ZIP, elaborato in background
Proclamatori
- CRUD con nome e cognome cifrati nel database
- Conteggio dei territori attualmente assegnati
- Anonimizzazione GDPR irreversibile
- Soft delete con cestino
Assegnazione e rientro
- Selezione territorio disponibile e proclamatore attivo
- Anno teocratico rilevato automaticamente dalla data
- Validazione: territorio non già assegnato, entrambi attivi
- Rientro con calcolo automatico dei giorni
- Prompt campagna: se il rientro ricade nel periodo di una campagna, viene chiesto se conteggiarlo
Campagne
- CRUD con date di inizio e fine
- Stato automatico: attiva, futura o conclusa
- Percentuale di percorrenza in tempo reale
- Dettaglio territorio per territorio
Registro assegnazioni
- Filtri per anno teocratico, zona, tipologia, stato e ricerca testuale
- Colonne: territorio, proclamatore, date, giorni, campagna
Audit log
- Tracciamento automatico su tutti i modelli
- Eventi di flusso espliciti: assegnazione, rientro, login, logout
- Filtri per utente e tipo evento, con diff prima/dopo
- Pulizia automatica schedulata secondo la retention configurata
Backup e import
- Backup completo
.zip: dati gestionali, utenti e file caricati (PDF territori, miniature, logo) in un unico pacchetto scaricabile e ripristinabile - Barra di avanzamento reale su backup e ripristino (lavoro svolto, non tempo)
- Ripristino da pacchetto, con cambio password obbligatorio per tutti gli utenti
- Zona pericolosa: azzeramento completo con backup obbligatorio scaricato e confermato prima di procedere
- Import ed export XML dell'intero set di dati gestionali (senza i file)
- Conversione di dump SQL legacy in XML compatibile
- Import massivo dei PDF dei territori da cartella o archivio ZIP
Zone e tipologie
- Gestione inline: aggiungi, rinomina, attiva/disattiva, elimina
- Non eliminabili se hanno territori associati
Ruoli e permessi
Tre ruoli con permessi granulari (spatie/laravel-permission):
| Permesso | Amministratore | Assistente | Operatore |
|---|---|---|---|
settings.manage |
✅ | ||
territori.manage |
✅ | ||
proclamatori.manage |
✅ | ✅ | |
campagne.manage |
✅ | ✅ | |
registro.view |
✅ | ✅ | |
registro.export |
✅ | ||
territori.assign |
✅ | ✅ | ✅ |
territori.return |
✅ | ✅ | ✅ |
audit.view |
✅ | ||
audit.export |
✅ |
L'amministratore ha un bypass completo (Gate::before in AppServiceProvider).
Il menu laterale mostra solo le voci per cui l'utente ha i permessi.
I permessi sono verificati sia sulle rotte sia sulle richieste Livewire, quindi una revoca ha effetto immediato senza bisogno di ricaricare la pagina.
Sicurezza e GDPR
Sicurezza
- Cifratura a riposo: nome e cognome dei proclamatori cifrati con AES-256-CBC
tramite
APP_KEY - Password: hash bcrypt
- RBAC: middleware
permission:sulle rotte, riapplicato anche alle richieste Livewire - CSRF: token su tutti i form
- Rate limiting: sul login
- Host header: accettate solo richieste il cui
Hostcorrisponde aAPP_URL - Scheme forwarding: lo schema è deciso dal server, non dall'header del client, per non falsare la validazione dei link firmati
- Security headers:
X-Frame-Options,X-Content-Type-Options,Referrer-Policy - Sessioni: in Redis, cookie httpOnly e
Securein produzione - Esecuzione PHP: solo
index.phpè un entrypoint valido; ogni altro.phprestituisce 404
GDPR
- Minimizzazione: dei proclamatori si conservano solo nome, cognome e stato
- Cifratura a riposo: i dati sono illeggibili anche con accesso diretto al DB
- Audit trail: registro completo delle operazioni
- Diritto all'oblio: funzione di anonimizzazione irreversibile
- Retention: cancellazione automatica dei log oltre il periodo configurato
- No dati sensibili nei log: l'audit registra gli identificativi, non i nomi in chiaro
Modello dati
| Tabella | Descrizione |
|---|---|
users |
Utenti dell'applicazione |
settings |
Configurazione singleton |
zone |
Zone territoriali |
tipologie |
Tipologie di territorio |
proclamatori |
Assegnatari, con nome/cognome cifrati e soft delete |
territori |
Cartoline con zona, tipologia, PDF, miniatura, soft delete |
anni_teocratici |
Periodi settembre → agosto |
campagne |
Periodi speciali con date e descrizione |
assegnazioni |
Territorio ↔ proclamatore, con date, anno e campagna |
activity_log |
Audit trail |
Vincoli e indici principali
territori.numeroeanni_teocratici.label— univociassegnazioni(territorio_id, returned_at)— indice composito, è la ricerca più frequente dell'applicazioneassegnazioni(proclamatore_id, returned_at),assegnazioni(assigned_at)territori(attivo, deleted_at),proclamatori(deleted_at)- Foreign key con
RESTRICTsu territorio e proclamatore, a protezione dell'integrità dello storico
Regole di business
Anno teocratico
Va da settembre ad agosto: da settembre in poi l'anno è corrente/corrente+1,
prima è corrente-1/corrente. Viene creato automaticamente quando serve.
L'assegnazione conta per l'anno determinato dalla data di assegnazione.
Stato del territorio
Calcolato dinamicamente, non memorizzato:
| Stato | Condizione |
|---|---|
in_reparto |
Attivo, nessuna assegnazione aperta |
assegnato |
Ha un'assegnazione con returned_at nullo |
da_rientrare |
Assegnato da più giorni della soglia configurata |
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.
Percentuale di percorrenza delle campagne
- Denominatore: tutti i territori assegnati nel periodo della campagna
- Numeratore: le assegnazioni contrassegnate come conteggiate
Architettura
Stack
| Componente | Tecnologia |
|---|---|
| Framework | Laravel 11 |
| PHP | 8.3 (FPM) |
| Interfaccia | Livewire 3.5 + TailwindCSS 4 + Alpine.js |
| Database | MariaDB 11 |
| Cache/Sessioni/Code | Redis 7 |
| Web server | Nginx, nello stesso container di php-fpm |
| RBAC | spatie/laravel-permission |
| Audit trail | spatie/laravel-activitylog |
| barryvdh/laravel-dompdf + poppler-utils | |
| Build asset | Vite 6 (in fase di build immagine) |
Struttura del progetto
TerManager2/
├── app/
│ ├── Console/Commands/ # audit:cleanup, app:create-initial-admin
│ ├── Http/Controllers/ # PDF pubblici, import, login
│ ├── Jobs/ # Import PDF massivo (in coda)
│ ├── Livewire/ # Tutti i componenti di interfaccia
│ ├── Models/ # 9 modelli Eloquent
│ ├── Providers/ # AppServiceProvider
│ └── Services/ # Miniature PDF, stato import
├── bootstrap/app.php # Middleware, proxy e host fidati
├── config/ # Configurazione Laravel
├── database/
│ ├── migrations/
│ └── seeders/ # Ruoli, permessi, dati di sviluppo
├── docker/
│ ├── nginx/default.conf # Vhost
│ ├── php/
│ │ ├── Dockerfile # Build multi-stage
│ │ ├── entrypoint.sh # Avvio: chiave, migrazioni, cache
│ │ ├── queue-entrypoint.sh # Avvio worker e scheduler
│ │ ├── php.ini
│ │ └── www.conf
│ └── supervisor/ # nginx + php-fpm nello stesso container
├── resources/ # CSS, JS, viste Blade
├── routes/web.php # Rotte con i relativi permessi
├── docker-compose.yml # Unico, produzione e sviluppo
├── .env.example # Tutta la configurazione
├── HANDOFF.md # Stato del progetto per chi subentra
└── README.md
Perché nginx e php-fpm nello stesso container
Con container separati, nginx avrebbe bisogno di una propria copia di public/
e del volume storage solo per servire i file statici. Per un'applicazione di
questa scala è complessità senza beneficio: un solo container significa un solo
health check, un solo target per il proxy e nessun volume condiviso da tenere
allineato.
Licenza
Proprietaria. Tutti i diritti riservati.