426 lines
31 KiB
Markdown
426 lines
31 KiB
Markdown
# 🧠 MEMORY.md — Stato Progetto
|
|
|
|
## Obiettivo
|
|
App gestionale Laravel 13 con AdminLTE 4 per gestione Persone e Gruppi.
|
|
|
|
## Funzionalità Implementate
|
|
|
|
### Page-length selector
|
|
- **4 pagine**: Individui, Eventi, Email, Gruppi
|
|
- **Valori**: 10, 20, 25, 50, 100 (default 20)
|
|
- Dropdown nel card-footer con campi hidden per preservare query params
|
|
- GruppoController: usa `LengthAwarePaginator` per paginare la collezione ordinata gerarchicamente
|
|
|
|
### CSV Import Gruppi
|
|
- Stesso pattern di `IndividuoController` (import GET, importStore POST, downloadTemplate GET)
|
|
- Colonne: nome, descrizione, parent_id, diocesi_id, indirizzo_incontro, cap_incontro, città_incontro, sigla_provincia_incontro
|
|
- Fault-tolerant: skip righe vuote, log errori, try/catch su ogni riga
|
|
|
|
### ICS Import Eventi
|
|
- Usa `Sabre\VObject\Reader::read()` già in vendor/
|
|
- Mapping: SUMMARY→nome_evento, DESCRIPTION→descrizione, DTSTART→data_specifica+ora_inizio, DTEND→durata_minuti, LOCATION→luogo_indirizzo, UID→uid_esterno
|
|
- Dedup via uid_esterno
|
|
- RRULE: import come evento singolo con prima occorrenza
|
|
|
|
### build-dist.sh (script di distribuzione)
|
|
- Genera `glastree-YYYYMMDD_HHMM.tar.gz`
|
|
- Include: sorgenti, vendor (production)
|
|
- Esclude: .git, node_modules, tests, cache, storage content, vendor/docs/tests, Docker
|
|
- **Istruzioni post-estrazione complete**: mkdir, permessi, configurazione, cache clear
|
|
|
|
## Tag System
|
|
|
|
### Implementazione Completa (Opzione B)
|
|
- **Migration**: `2026_06_08_194155_create_tags_tables.php` — tabelle `tags` + `taggables`
|
|
- **Model `Tag.php`**: morphToMany a 5 entity types, auto-slug su creating, color badge
|
|
- **Trait `HasTagsLight.php`**: `tags()` morphToMany, scopes `withAllTags`/`withAnyTags`
|
|
- **5 Models aggiornati**: Individuo, Gruppo, Evento, Documento, MailingList — usano `HasTagsLight`
|
|
- **ImpostazioniController**: CRUD completo per Tag (store, update, destroy, reorder) con tab nella pagina Impostazioni
|
|
- **Tag selector**: `_tag-selector.blade.php` — search + pill badges con colori, incluso in create/edit di Individui, Gruppi, Eventi, MailingList
|
|
- **Tag filtering**: `?tag[]=slug` supportato in `IndividuoController@index`, `GruppoController@index`, `EventoController@index`, `DocumentoController@index`, `MailingListController@index` via `withAnyTags` scope
|
|
- **Tag column in index views**: Colonna "Tag" con badge colorati cliccabili (collegamento a index filtrato) in individui, gruppi, eventi, documenti, mailing-liste
|
|
- **Tag filter widget**: `_tag-filter-bar.blade.php` — barra cliccabile con badge colorati sopra ogni index table (individui, gruppi, eventi, documenti, mailing-liste). Attiva/disattiva filtro `?tag[]=` con un click, evidenzia tag attivi, include "Cancella filtri" quando attivo
|
|
- **RicercaController**: Controller per ricerca unificata per tag — mostra risultati aggregati da tutte 5 entity types con info-box counters
|
|
- **Route `GET /ricerca`**: Registrata come `ricerca.index` → `RicercaController@index`
|
|
- **Sidebar link**: "Ricerca per Tag" (icona `fas fa-tags`) sotto la sezione Report, visibile con permesso `individui`
|
|
- **Mass tag action for Documenti**: `POST /documenti/mass-tag` → `DocumentoController@massTag` — modal with tag selector + assign/remove radio, processes selected documents in chunks of 100 via `syncWithoutDetaching()` (assign) or `detach()` (remove)
|
|
- **Report tag columns**: `tags.nome` disponibile come colonna per individui/gruppi/eventi/documenti nei report personalizzati
|
|
- **Report tag filters**: Custom report form include tag multi-select (Select2) + modalità OR/AND; `runCustomReport()` applica filtri via `withAllTags`/`withAnyTags`
|
|
|
|
## Bug Fix Recenti
|
|
|
|
### 2026-06-07 — Colonne mancanti in eventi e gruppo_individuo
|
|
- `descrizione_evento` e `is_incontro_gruppo` mancanti in install.sql → creata migration + ALTER TABLE
|
|
- `ruolo_nel_gruppo` mancante in install.sql per pivot `gruppo_individuo`
|
|
- `VistaReport.php`: aggiunto `is_default` a `$fillable`
|
|
|
|
### 2026-06-08 — build-dist.sh: "provide valid cache path" su server remoto
|
|
**Problema**: Le esclusioni in `build-dist.sh` usavano `--exclude='storage/framework/cache'` (e simili per sessions, views, logs), che escludevano **l'intera directory** dall'archivio. Dopo l'estrazione, le directory necessarie a Laravel non esistevano.
|
|
|
|
**Fix**:
|
|
1. Cambiati exclude patterns per **contenuti soltanto** (non la directory):
|
|
- `--exclude='storage/framework/cache/*'` (non `/cache`)
|
|
- `--exclude='storage/framework/sessions/*'`
|
|
- `--exclude='storage/framework/views/*'`
|
|
- `--exclude='storage/logs/*'`
|
|
- `--exclude='storage/debugbar/*'`
|
|
2. Aggiunte esclusioni per upload utente:
|
|
- `--exclude='storage/app/public'`
|
|
- `--exclude='storage/app/private'`
|
|
3. Creato `.gitignore` in `storage/framework/views/` (mancante)
|
|
4. Istruzioni post-estrazione riscritte con:
|
|
- `mkdir -p` per tutte le directory necessarie (cache, sessions, views, logs, public, backups, documenti, bootstrap/cache)
|
|
- `chmod -R 775` e `chown -R www-data:www-data` per permessi
|
|
- `php artisan config:clear`, `route:clear`, `view:clear`
|
|
|
|
## Problema 419 (Page Expired) su Produzione
|
|
Il codice NON ha errori di CSRF — tutte le form hanno `@csrf`. Il problema è configurazione sessione del server:
|
|
1. `storage/framework/sessions/` non scrivibile dal web server
|
|
2. `APP_URL` in `.env` non corrisponde all'URL reale
|
|
3. `SESSION_SECURE_COOKIE=true` ma sito in HTTP
|
|
|
|
Soluzione:
|
|
```bash
|
|
chmod -R 775 storage/framework/sessions
|
|
php artisan config:clear
|
|
```
|
|
|
|
### 2026-06-08 — build-dist.sh: impossibilità salvare logo su server remoto
|
|
**Problema**:
|
|
- `public/storage` era un symlink **assoluto** (`/var/www/html/glastree/storage/app/public`) incluso nell'archivio → all'estrazione su altro server puntava a path inesistente.
|
|
- `php artisan storage:link` falliva perché il symlink rotto esisteva già.
|
|
- Sottodirectory `logos/`, `documenti/eventi/`, `avatars/gruppi/` non create dal `mkdir -p`.
|
|
|
|
**Fix**:
|
|
1. Aggiunto `--exclude='public/storage'` al tar — il symlink va ricreato sul target.
|
|
2. Aggiunto `rm -f public/storage` prima di `php artisan storage:link` per evitare conflitto.
|
|
3. Usato `ln -sf ../storage/app/public public/storage` (invece di `php artisan storage:link --relative` che richiede `symfony/filesystem` non sempre presente).
|
|
4. Aggiunte al `mkdir -p` le sottodirectory mancanti:
|
|
- `storage/app/public/logos` (logo upload)
|
|
- `storage/app/public/documenti/eventi` (event documenti)
|
|
- `storage/app/private/avatars/gruppi` (avatar gruppi)
|
|
5. Aggiunta creazione `.gitignore` in `storage/app/public/` (placeholder Laravel).
|
|
|
|
### Fix form annidato salvataggio email
|
|
**2026-06-08 — Problema**: Il form di DELETE era **annidato dentro** il form di SAVE in entrambe le pagine:
|
|
- `resources/views/impostazioni/index.blade.php`
|
|
- `resources/views/admin/email-settings/index.blade.php`
|
|
- HTML invalido: il browser chiudeva implicitamente il form SAVE all'apertura del form DELETE, causando l'invio alla route sbagliata.
|
|
|
|
**Fix**: Spostato il form DELETE fuori dal form SAVE, separato in un `div` autonomo.
|
|
|
|
### Fix campi mancanti in validazione EmailSettingsController@save
|
|
**2026-06-08**: `signature`, `signature_enabled`, `smtp_username`, `smtp_password` non erano nelle regole di validazione di `save()`. Laravel scarta i campi non validati, quindi non venivano mai scritti nel DB.
|
|
|
|
**Fix**: Aggiunte le regole mancanti a `$request->validate()`.
|
|
|
|
### DiagnoseEmail Command
|
|
- `php artisan diagnose:email` — controlla APP_KEY, tabella email_settings, record, decrittazione password, sender_accounts, log errori, estensioni PHP.
|
|
|
|
### 2026-06-08 — Firma email ancora non salvata (secondo tentativo)
|
|
**Problema**: La firma (campo `signature`) non veniva salvata nonostante le regole di validazione fossero state aggiunte.
|
|
|
|
**Root cause analysis**:
|
|
1. HTML checkbox `signature_enabled` senza hidden fallback: quando non spuntato, il campo non veniva inviato → non presente in `$validated` → `updateOrCreate` non lo aggiornava mai a `false`.
|
|
2. La vecchia pagina `/impostazioni/` aveva l'handler `shown.bs.tab` per il tab Firma (`#email-firma`) in un solo punto su due, causando possibile mancata inizializzazione dell'editor Quill su alcuni flussi di navigazione.
|
|
3. La verifica nel controller controllava solo `imap_host` e `email_address` — anche se signature/signature_enabled fallivano, l'utente vedeva "salvato con successo".
|
|
|
|
**Fix**:
|
|
1. `resources/views/admin/email-settings/index.blade.php:268` — Aggiunto `<input type="hidden" name="signature_enabled" value="0">` prima della checkbox
|
|
2. `resources/views/impostazioni/index.blade.php:603` — Stesso hidden fallback
|
|
3. `resources/views/impostazioni/index.blade.php:1633` — Aggiunto `#email-firma` al primo handler `shown.bs.tab` (il secondo handler esisteva già ma il primo no)
|
|
4. `app/Http/Controllers/Admin/EmailSettingsController.php:70-82` — Aggiunto log di warning se signature o signature_enabled non corrispondono dopo il salvataggio
|
|
|
|
### 2026-06-08 — Sync email: "Nessuna configurazione email attiva" nonostante test IMAP funzioni
|
|
**Problema**: Cliccando "Sincronizza Ora" nelle impostazioni email o "Ricevi/Invia" nella pagina email, viene mostrato "Nessuna configurazione email attiva". Il test IMAP funziona correttamente.
|
|
|
|
**Root cause**: Stesso bug dei checkbox senza hidden fallback. Il campo `is_active` nel tab Sincronizzazione non veniva inviato quando non spuntato. Alla prima configurazione, se l'utente non visitava esplicitamente il tab Sync e attivava lo switch, `is_active` rimaneva `false` (default DB). Il test IMAP usa `EmailSetting::first()` (ignora `is_active`), mentre `syncEmails()` e `syncNow()` usano `EmailSetting::getActive()` che filtra per `is_active = true`.
|
|
|
|
**Fix**:
|
|
1. `resources/views/admin/email-settings/index.blade.php:251` — Aggiunto `<input type="hidden" name="is_active" value="0">` prima della checkbox
|
|
2. `resources/views/impostazioni/index.blade.php:586` — Stesso hidden fallback
|
|
3. `EmailSetting::getActive()` ora riceve sempre `is_active` dal form (0 o 1) grazie all'hidden fallback
|
|
|
|
## 2026-06-08 — Help page: aggiunto tab "Calendario" con documentazione OAuth
|
|
|
|
- **`resources/views/help/index.blade.php`**: Aggiunto tab "Calendario" nel menu laterale (dopo Google Drive) e relativo tab-pane content con:
|
|
- Requisiti (account Google, API Calendar, OAuth credentials)
|
|
- Passo 1: Google Cloud Console — abilitare Google Calendar API, creare OAuth Client ID (Web Application), redirect URI `https://developers.google.com/oauthplayground`
|
|
- Passo 2: Ottenere Refresh Token via Google OAuth Playground (Use your own OAuth credentials, scope `calendar`)
|
|
- Passo 3: Configurazione nell'app (Impostazioni → Calendario, nuova connessione Google Calendar, inserire Client ID/Secret/Refresh Token)
|
|
- Test connessione e sincronizzazione
|
|
- Nota: refresh token permanente finché non revocato
|
|
- **`resources/views/help/pdf.blade.php`**: Aggiunta sezione "3. Google Calendar" nel TOC e contenuto, rinumerazione da 4 a 8 per sezioni successive
|
|
|
|
### Differenza chiave Google Calendar vs Google Drive
|
|
- **Drive**: endpoint server `/auth/google-drive/callback` che gestisce automaticamente il callback OAuth e salva refresh token in `storage_repositories`
|
|
- **Calendar**: **NON** ha endpoint server dedicato. Refresh token ottenuto manualmente via Google OAuth Playground e incollato nel campo "Refresh Token" del form
|
|
|
|
## 2026-06-08 — Pulsante Sync nella pagina calendario eventi
|
|
|
|
- **`app/Http/Controllers/CalendarioConnessioneController.php`**: Aggiunto metodo `syncAll()` che itera tutte le connessioni attive (`is_active = true`), esegue `syncEvents()` per ognuna, e restituisce JSON con risultati aggregati.
|
|
- **`routes/web.php`**: Aggiunta route `POST /calendario-connessioni/sync-all` → `syncAll` (name: `calendario-connessioni.sync-all`).
|
|
- **`resources/views/eventi/calendar.blade.php`**: Aggiunto pulsante "Sync" nel card-tools che:
|
|
- Invia POST AJAX a `sync-all`
|
|
- Mostra spinner durante l'operazione
|
|
- Al completamento mostra alert con riepilogo (connessioni, importati, esportati)
|
|
- Richiama `window.calendar.refetchEvents()` per aggiornare la vista FullCalendar
|
|
- Esposto `window.calendar` globalmente per accesso dall'handler sync
|
|
|
|
## 2026-06-08 — Fix logo non visibile su installazione remota
|
|
|
|
**Problema**: Il logo uploadato non veniva visualizzato su installazioni remote, pur salvandosi correttamente su disco.
|
|
|
|
**Root cause**: `getLogoPath()` e `getLogoSmallPath()` in `AppSetting.php` usavano `file_exists(public_path('storage/' . $path))` per verificare l'esistenza del file prima di restituire l'URL. Questo controllo dipendeva dal symlink `public/storage → storage/app/public`. Su installazioni remote dove il symlink era assente o mal configurato, `file_exists` restituiva `false` → metodo restituiva `null` → logo non mostrato.
|
|
|
|
**Fix**:
|
|
1. **`app/Models/AppSetting.php`**: Sostituito `file_exists(public_path('storage/' . $path))` con `Storage::disk('public')->exists($path)` e `asset('storage/' . $path)` con `Storage::disk('public')->url($path)`. Il controllo ora avviene direttamente su `storage/app/public/` (disk root), bypassando il symlink.
|
|
2. **`app/Http/Controllers/ImpostazioniController.php`**: In `uploadLogo()`, aggiunta creazione automatica del symlink `public/storage → storage/app/public` se mancante, per garantire che il browser possa servire l'immagine.
|
|
|
|
## 2026-06-08 — Guardie Schema::hasColumn() aggiunte a 18 migration
|
|
|
|
**Problema**: 18 migration files aggiungevano colonne a tabelle esistenti senza `Schema::hasColumn()` guard. Se eseguite su DB dove le colonne già esistevano (es. da install.sql), causavano errore "Duplicate column".
|
|
|
|
**Fix**: Aggiunto `if (!Schema::hasColumn(...))` wrapper a ogni colonna aggiunta nelle seguenti migration:
|
|
1. `2026_06_02_000002_add_uid_esterno_to_eventi_table.php` — `uid_esterno`
|
|
2. `2026_05_12_000002_change_ruolo_to_multi.php` — `ruolo_ids` (up), `ruolo_id` (down)
|
|
3. `2026_05_28_000001_add_storage_disk_to_documenti.php` — `storage_disk`
|
|
4. `2026_05_27_113246_add_repository_id_to_documenti.php` — `repository_id`
|
|
5. `2026_05_26_000003_add_log_falliti_to_mailing_messaggi.php` — `log_falliti`, `mittente_nome`, `mittente_email`
|
|
6. `2026_05_27_000002_add_cartella_id_to_documenti.php` — `cartella_id`
|
|
7. `2026_05_16_000004_add_signature_enabled_to_email_settings.php` — `signature_enabled`
|
|
8. `2026_05_16_000003_add_signature_to_email_settings.php` — `signature`
|
|
9. `2024_01_01_000017_add_occorrenza_mese_to_eventi.php` — `occorrenza_mese`
|
|
10. `2024_01_01_000016_add_eventi_recurrence_fields.php` — `giorno_mese`, `mesi_recorrenza`, `mese_annuale`
|
|
11. `2026_05_10_000002_add_acl_tables.php` — `permissions` (users)
|
|
12. `2026_05_12_142009_add_location_to_eventi_table.php` — `luogo_indirizzo`, `luogo_url_maps`
|
|
13. `2026_05_12_000003_change_responsabile_to_multi.php` — `responsabile_ids` (up), `responsabile_id` (down)
|
|
14. `2024_01_01_000023_add_is_default_to_viste_report_table.php` — `is_default`
|
|
15. `2024_01_01_000024_add_user_id_to_mailing_lists_table.php` — `user_id`
|
|
16. `2026_05_12_000001_create_ruoli_table.php` — `ruolo_id` (gruppo_individuo) + data migration annessa
|
|
17. `2024_01_01_000021_add_user_id_to_documenti_table.php` — `user_id`
|
|
18. `2024_01_01_000013_add_numero_documento_to_individui_table.php` — `numero_documento`
|
|
|
|
Tutte verificate con `php -l` (nessun errore di sintassi).
|
|
|
|
### 2026-06-09 — `@stack('scripts')` mancante nel layout
|
|
|
|
**Problema**: Il partial `_tag-selector.blade.php` usa `@push('scripts')` per iniettare le funzioni JS `toggleTag`, `removeTag`, `filterTags`. Il layout `adminlte.blade.php` aveva solo `@yield('scripts')` (per `@section`), ma **non** `@stack('scripts')` (per `@push`). Di conseguenza il JavaScript interattivo del tag selector (clic sui badge, ricerca, rimozione) non veniva mai caricato su nessuna pagina create/edit.
|
|
|
|
**Fix**: `resources/views/layouts/adminlte.blade.php:305` — Aggiunto `@stack('scripts')` subito prima di `@yield('scripts')`.
|
|
|
|
### 2026-06-09 — Tag support completato per MailingList + Report + Ricerca
|
|
|
|
**MailingList — Controller & Views** (`app/Http/Controllers/MailingListController.php`):
|
|
- `index()`: eager-load `tags`, supporto `?tag[]=` filter via `withAnyTags`, pass `$allTags` per filter bar
|
|
- `create()`: pass `$tags` per tag selector
|
|
- `store()`: validazione `tags.* exists:tags,id`, sync dopo create. Auto-fetch email da `Individuo::contatti()` quando email non fornita
|
|
- `edit()`: eager-load `tags`, pass `$tags` + `$selectedTags`
|
|
- `update()`: validazione `tags.*`, sync se presente, detach se assente. Auto-fetch email da `Individuo::contatti()` quando email non fornita. Diff logica: `$toRemove = array_diff($existingIds, $newIds)` corretto — rimuove solo contatti deselezionati/esplicitamente rimossi
|
|
- `show()`: eager-load `tags`
|
|
|
|
**MailingList — Views**:
|
|
- `index.blade.php`: aggiunto `@include('partials._tag-filter-bar')`, colonna "Tag" con badge colorati cliccabili
|
|
- `create.blade.php`: incluso `_tag-selector` dopo descrizione
|
|
- `edit.blade.php`: incluso `_tag-selector` dopo descrizione (prima della sezione contatti). Submit handler include `#individui-select.selectedOptions` in `contatti_json` con `email: ''` (già presente da fix precedente)
|
|
- `show.blade.php`: aggiunta card Tag nel pannello sinistro con badge colorati (o "Nessun tag assegnato")
|
|
|
|
**ReportColumnRegistry** (`app/Helpers/ReportColumnRegistry.php`):
|
|
- Aggiunta colonna `'tags.nome'` con label 'Tag' a 4 entity types: individui, gruppi, eventi, documenti
|
|
|
|
**ReportController** (`app/Http/Controllers/ReportController.php`):
|
|
- `runCustomReport()`: supporta `config['tag_filter']` (array di tag IDs) + `config['tag_filter_mode']` ('any'/'all')
|
|
- `storeCustom()`: ora salva `tag_filter` e `tag_filter_mode` nella config del report salvato
|
|
- Carica `$allTags` nell'index e passa alla vista
|
|
- Converte IDs → slugs via `Tag::whereIn('id', ...)->pluck('slug')`, applica `withAllTags`/`withAnyTags`
|
|
- Eager-load `tags` automaticamente quando la colonna `tags.nome` è selezionata
|
|
|
|
**Report — View** (`resources/views/report/index.blade.php`):
|
|
- Aggiunto filtro tag multi-select nel form "Crea Report Personalizzato" (Select2 con badge colorati)
|
|
- Aggiunto select per modalità filtro (OR/AND)
|
|
- JS: `initTagFilterSelect2()` / `destroyTagFilterSelect2()` con template colorato
|
|
|
|
**RicercaController** (`app/Http/Controllers/RicercaController.php`):
|
|
- Aggiunta MailingList alle query per tag slug
|
|
- `results['mailingLists']` e `totals['mailingLists']` passati alla view
|
|
|
|
**Ricerca — View** (`resources/views/ricerca/index.blade.php`):
|
|
- Aggiunto info-box Mailing List (colore bg-purple) con link "Vedi tutti" → `mailing-liste.index?tag[]=`
|
|
- Aggiunta tabella dettaglio Mailing List nel risultato di ricerca (nome, contatti, stato, azione show)
|
|
- Fix: `route('documenti.edit')` → `url('/documenti/' . $doc->id . '/edit')` (route non esiste)
|
|
|
|
### 2026-06-09 — Fix: storeCustom() non salvava tag_filter
|
|
|
|
**Problema**: `ReportController@storeCustom()` non salvava `tag_filter` e `tag_filter_mode` nella config del report personalizzato. Quando un report salvato veniva eseguito, `runCustomReport()` non trovava i filtri tag nella config e non li applicava.
|
|
|
|
**Fix**: Aggiunte righe 143-148 in `ReportController.php`:
|
|
```php
|
|
if ($request->has('tag_filter')) {
|
|
$config['tag_filter'] = $request->input('tag_filter');
|
|
}
|
|
if ($request->filled('tag_filter_mode')) {
|
|
$config['tag_filter_mode'] = $request->input('tag_filter_mode');
|
|
}
|
|
```
|
|
|
|
### 2026-06-09 — Fix: MailingList edit — "Carica contatti" non inseriva i selezionati
|
|
|
|
**Problema**: Due bug nel JS `caricaSelezionati()` in `mailing-liste/edit.blade.php`:
|
|
1. **Typo**: `ind.cogname` invece di `ind.cognome` — il campo Cognome nella tabella era sempre vuoto/undefined (API restituisce `cognome`)
|
|
2. **Email gate**: `if (ind.emails && ind.emails.length > 0)` filtro bloccante — individui senza email salvata in `contatti` venivano SILENZIOSAMENTE scartati, mai aggiunti alla tabella
|
|
|
|
**Fix**:
|
|
1. `ind.cogname` → `ind.cognome` (righe 227 e 256)
|
|
2. Rimossa condizione `if (ind.emails...)` — ora anche individui senza email vengono aggiunti alla tabella
|
|
3. `email: ind.emails[0]` → `email: (ind.emails && ind.emails.length > 0) ? ind.emails[0] : ''` — email vuota se nessuna presente
|
|
4. Controller già fixato per auto-risolvere email da `Individuo::contatti()` al salvataggio
|
|
|
|
### 2026-06-09 — Fix: MailingListController email gate bloccava nuovi contatti senza email
|
|
|
|
**Problema**: `store()` e `update()` in `MailingListController` usavano `!empty($contatto['email'])` per decidere se creare un contatto. L'email era richiesta ma il MailingContact non memorizza l'email — la risolve dall'Individuo al momento del display/invio. Questo impediva di aggiungere individui senza email (o con email non nei contatti) alla mailing list.
|
|
|
|
**Fix**: Rimossa completamente la verifica email sia in `store()` che in `update()`. Ora ogni `individuo_id` valido viene aggiunto come MailingContact. L'email viene risolta al display dal model `$contact->individuo->contatti->where('tipo', 'email')->first()?->valore`.
|
|
|
|
Prima (bloccante):
|
|
```php
|
|
if (!empty($contatto['individuo_id']) && !empty($contatto['email'])) { create }
|
|
```
|
|
Dopo (semplice):
|
|
```php
|
|
if (!empty($contatto['individuo_id'])) { create }
|
|
```
|
|
|
|
## Prossimi Passi
|
|
- [DONE] ... (existing items)
|
|
- Verificare end-to-end su remote: tutte le nuove mass action, mailing list con contatti senza email, ricerca multi-tag
|
|
- [DONE] Fix performance: spostata query `VistaReport::where(...)->get()` da `@php` nel partial `table-settings.blade.php` a tutti i 5 controller (prima veniva eseguita 1 query per ogni pagina load per ogni entity, anche senza mai aprire la modale)
|
|
- [DONE] Rimosse vecchie modal legacy (`#saveVistaModal`, `#colonneModal`, `#vistaListModal`) da `individui` e `gruppi` — duplicate rispetto al nuovo modal unificato
|
|
- [DONE] Rimosse vecchie funzioni JS (`saveVista()`, `toggleColumn()`, `showSaveVistaModal()`) da `individui` e `gruppi`
|
|
- [DONE] Individui: `$allColumns` espanse da 6 a 17 colonne (aggiunte: data_nascita, genere, indirizzo, cap, citta, provincia, tipo_documento, numero_documento, scadenza_documento, note, created_at) — tutte con `<th>`/`<td>` condizionali nella vista
|
|
- [DONE] Individui: `getColumnIndex()` introdotta per lookup dinamico dell'indice colonna (sostituisce hardcoded `{codice:1, cognome:2, ...}` in `sortTable()` e `applyColumnFilter()`)
|
|
- [DONE] I controller per eventi, documenti, mailing-liste hanno già `$tableColumns` con flag `visible` — stesso meccanismo di gruppi. Nessuna modifica necessaria.
|
|
- [DONE] Fix: pulsante edit vista usa `data-*` attributi invece di `onclick` con `addslashes(json_encode(...))` — previene rottura HTML per JSON contenente `"`
|
|
- [DA FARE] Test browser: caricare ogni pagina entity, verificare default vista, toggle colonne, resize, drag-reorder, print
|
|
- [DA FARE] Test browser: flusso edit vista (✏️ → popola form → modifica → Aggiorna → reload)
|
|
- [DA FARE] Test browser: salvare nuova vista, switching tra viste, cancellazione vista
|
|
|
|
## 2026-06-10 — Per-User Column Views (colonne visibili, larghezze, ordine)
|
|
|
|
**Obiettivo**: Implementare viste colonne personalizzabili per utente su tutte le 5 entity list pages (individui, gruppi, eventi, documenti, mailing-liste), con persistenza tra login.
|
|
|
|
### Cosa è stato fatto
|
|
|
|
**Migration** (`2026_06_10_044946_add_column_widths_and_order_to_viste_report_table.php`):
|
|
- Aggiunge `colonne_larghezze` (json) e `colonne_ordine` (json) a `viste_report`
|
|
- Guardia `Schema::hasColumn` per compatibilità cross-DB (MySQL ↔ SQLite)
|
|
- Già eseguita in batch 38
|
|
|
|
**Backend**:
|
|
- `VistaReport.php`: `$fillable` + `$casts` (array) per entrambi i nuovi campi
|
|
- `VistaReportController@store`/`@update`: validazione estesa con `colonne_larghezze`, `colonne_ordine` (array)
|
|
- Tutti i 5 controller passano alla vista: `$tableColumns` (array `['key','label','visible']`), `$entityType`, `$columnWidths`, `$visibleColumns`, `$allColumns` (varia nome in base al controller)
|
|
- Tutti i 5 controller ora passano `$userVistas` (precaricate) invece di eseguire query nella view
|
|
- `IndividuoController`: `$allColumns` espanse con tutte le colonne DB disponibili (da 6 a 17)
|
|
- `IndividuoController`: aggiunto `use App\Models\VistaReport` per la query `$userVistas`
|
|
|
|
**Frontend JS** (`public/js/column-manager.js`):
|
|
- Classe `ColumnManager`: resize via mouse drag su handle, reorder via HTML5 DnD, persistenza larghezze in `sessionStorage`, fallback da `vista-data` DOM element, `printWithSettings()` che clona la tabella in finestra print-ready
|
|
|
|
**Modal** (`resources/views/partials/table-settings.blade.php`):
|
|
- Modal unificato con 3 tab: Colonne (checkbox visibilità + input larghezza + drag-reorder list), Viste (salva/carica/cancella), Stampa (pulsante print)
|
|
- Definisce `initTableSettings()` (NON auto-esegue) per evitare race condition con ColumnManager
|
|
- **RIMOSSA** query `VistaReport::where(...)->get()` dal `@php` block — ora usa `$userVistas` passato dal controller
|
|
- Non usa più `Auth::id()` direttamente — riceve `$userVistas` già popolato
|
|
|
|
**Views** — tutte e 5 aggiornate:
|
|
- `individui/index.blade.php`:
|
|
- `$allColumns` nella vista ora ha 17 colonne (vs 6 prima)
|
|
- `<th>` e `<td>` condizionali per tutte le nuove colonne (data_nascita, genere, indirizzo, cap, citta, provincia, tipo_documento, numero_documento, scadenza_documento, note, created_at)
|
|
- RIMOSSE modal legacy: `#saveVistaModal`, `#colonneModal`
|
|
- RIMOSSE funzioni JS legacy: `saveVista()`, `toggleColumn()`
|
|
- RIMOSSO sync checkboxes nel `DOMContentLoaded` (non più necessari)
|
|
- NUOVA: `getColumnIndex(colKey)` — lookup dinamico dell'indice colonna basato su `data-column`
|
|
- `sortTable()` e `applyColumnFilter()` ora usano `getColumnIndex()` invece di hardcoded mappa
|
|
- `gruppi/index.blade.php`:
|
|
- RIMOSSE modal legacy: `#saveVistaModal`, `#vistaListModal`
|
|
- RIMOSSO pulsante "Salva Vista" legacy
|
|
- RIMOSSE funzioni JS legacy: `showSaveVistaModal()`, `saveVista()`
|
|
- `eventi/index.blade.php`: colonne dinamiche via `$visibleColumns`, `@section('scripts')` con ColumnManager + `initTableSettings()`
|
|
- `mailing-liste/index.blade.php`: stessa struttura
|
|
- `documenti/index.blade.php`: ColumnManager init (non reorderable), column-manager.js incluso
|
|
- Tutti i `<th>` hanno `data-column` attributo per mapping JS
|
|
- Tutte chiamano `initTableSettings()` dopo `new ColumnManager()` nel `DOMContentLoaded`
|
|
|
|
**Performance fix**:
|
|
- Rimossa query `VistaReport::where(...)->get()` dal `@php` block in `table-settings.blade.php` (1 query extra per ogni pagina load su 5 entità)
|
|
- Spostata nei 5 controller come `$userVistas` passata via `compact()`
|
|
- Totale: -5 query per pagina load (una per ogni entity, anche su pagine non di index)
|
|
|
|
**Verifica**:
|
|
- JS brace balance: OK su tutti i 5 file (node check)
|
|
- PHP lint: OK su tutti i 5 controller
|
|
- Presenza `data-column`: individui=19, gruppi=14, eventi=9, mailing-liste=7, documenti=8
|
|
- Fallback `$visibleColumns` quando `$vista === null`: presente e corretto in tutti i 5 controller
|
|
- Nessun residuo di vecchie modal legacy in individui e gruppi
|
|
- Rimosso bottone "Salva Vista" orfano da `individui/index.blade.php` (chiamava `showSaveVistaModal()` non più esistente)
|
|
- Aggiunto pulsante modifica ✏️ nel tab Viste per ogni vista salvata: popola il form "Salva vista corrente" con nome, default flag e colonne visibili; cambia bottone in "Aggiorna" e fa PUT `/viste/{id}` invece di POST; zero query extra
|
|
### 2026-06-10 — Fix: editVista onclick → data-* attributes
|
|
|
|
**Problema**: Il pulsante modifica vista usava `onclick` con `addslashes(json_encode(...))`. In un attributo HTML delimitato da `"`, il backslash-escaping di `\"` non è gestito in modo standard dai browser — il JSON contenente `"` poteva rompere l'attributo HTML.
|
|
|
|
**Fix**:
|
|
1. Sostituito `onclick` con `data-*` attributi (`data-vista-id`, `data-vista-nome`, `data-vista-default`, `data-vista-colonne`) usando `{{ }}` di Blade (che applica `htmlspecialchars`, codificando `"` → `"` sicuro in HTML).
|
|
2. `editVista()` ora accetta un array direttamente (non più JSON string da parsare).
|
|
3. Click handler registrato in `initTableSettings()` via `document.querySelectorAll('.edit-vista-btn')` + `dataset` (browser decodifica automaticamente `"` → `"`).
|
|
|
|
### 2026-06-10 — Fix: 405 Method Not Allowed su update vista (via AJAX fetch)
|
|
|
|
**Problema**: Modificando una vista e cliccando "Aggiorna", `fetch()` inviava `PUT /viste/{id}` ma il controller `VistaReportController@update` restituiva `return back()` (302 redirect). `fetch()` seguiva il redirect — in alcuni browser il metodo PUT veniva preservato, ma la route `gruppi` accetta solo GET → 405 Method Not Allowed.
|
|
|
|
**Fix**:
|
|
1. `VistaReportController@update` (linea 101-103): aggiunto `if ($request->expectsJson()) { return response()->json([...]); }` — così il fetch riceve JSON 200, non un redirect 302.
|
|
2. Fetch headers in `table-settings.blade.php`: aggiunto `'Accept': 'application/json'` — necessario perché `expectsJson()` controlla l'header `Accept`, non `Content-Type`.
|
|
|
|
## 2026-06-10 — Fix: tutte le migration idempotenti + seeders idempotenti
|
|
|
|
**Problema**: `php artisan migrate` falliva su tabelle già esistenti. `php artisan db:seed` creava duplicati su re-run.
|
|
|
|
### Migration: hasTable guard su tutti i 27 file mancanti
|
|
|
|
Aggiunto `if (!Schema::hasTable('table_name')) { ... }` wrapper a tutte le 27 migration file (44 `Schema::create()` calls) che ne erano prive, usando script Node.js di trasformazione automatica:
|
|
- `2024_01_01_000002_create_comuni_diocesi_tables.php`
|
|
- `2024_01_01_000003_create_individui_table.php`
|
|
- `2024_01_01_000004_create_contatti_table.php`
|
|
- `2024_01_01_000005_create_gruppi_table.php`
|
|
- `2024_01_01_000006_create_gruppo_individuo_table.php`
|
|
- `2024_01_01_000007_create_eventi_table.php`
|
|
- `2024_01_01_000008_create_documenti_table.php`
|
|
- `2024_01_01_000009_create_mailing_tables.php`
|
|
- `2024_01_01_000010_create_notifiche_table.php`
|
|
- `2024_01_01_000011_create_users_table.php`
|
|
- `2024_01_01_000012_create_cache_and_jobs_tables.php`
|
|
- `2024_01_01_000019_create_eventi_documenti_table.php`
|
|
- `2024_01_01_000022_create_table_viste_report.php`
|
|
- `2026_05_10_000001_create_tipologie_documenti_table.php`
|
|
- `2026_05_10_000002_add_acl_tables.php`
|
|
- `2026_05_10_125210_create_permission_tables.php` (usa `$tableNames[...]` dinamici)
|
|
- `2026_05_11_000001_create_email_settings_table.php`
|
|
- `2026_05_11_000002_create_email_folders_table.php`
|
|
- `2026_05_11_000003_create_email_messages_table.php`
|
|
- `2026_05_11_000007_create_email_attachments_table.php`
|
|
- `2026_05_12_000001_create_ruoli_table.php`
|
|
- `2026_05_25_000001_create_report_custom_table.php`
|
|
- `2026_05_26_000001_create_tipologie_eventi_table.php`
|
|
- `2026_05_26_000002_create_sender_accounts_table.php`
|
|
- `2026_05_27_000001_create_documenti_cartelle_table.php`
|
|
- `2026_05_27_113245_create_storage_repositories_table.php`
|
|
- `2026_06_02_000001_create_calendario_connessioni_table.php`
|
|
|
|
### Seeder: firstOrCreate per idempotenza
|
|
|
|
- `DiocesiSeeder.php`: `Diocesi::create()` → `Diocesi::firstOrCreate(['nome' => $d['nome']], $d)`
|
|
- `ComuniSeeder.php`: `Comune::create()` → `Comune::firstOrCreate(['codice_istat' => $c['codice_istat']], $c)`
|
|
- `DatabaseSeeder.php`: `User::create()` → `User::firstOrCreate(['email' => 'admin@glastree.local'], [...])`, con guardia su `role_preset_id`
|
|
|
|
**Risultato**: `php artisan migrate --seed` è ora completamente idempotente. Zero errori su 62 migration + 6 seeder.
|