Turno palloni: un solo avviso, 3 ore prima dell'evento (DD-042)

La migration m27 riduce il job automatico alla sola fascia delle 3 ore:
l'avviso arriva una volta a chi deve portare i palloni e a chi li deve
prendere. I tipi turno_palloni_12h e _6h restano ammessi per le notifiche
già generate e per la revoca. Migration applicata in produzione.

Test di integrazione aggiornati (nessun avviso a 4,5 e 9 ore); documentati
DD-042, moduli, DATABASE, ROADMAP, glossario e changelog.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-01 14:33:38 +02:00
1 parent 4b5ffc6664
commit cdf8b144e2
14 files changed
+175 -90

No files matched your search

+6
View File
@@ -15,6 +15,12 @@ versioni `0.y.z` erano pre-release.
### Modificato ### Modificato
- **Turno palloni: un solo avviso, 3 ore prima** — a chi deve portare i palloni e a chi li deve
prendere; prima ne partivano tre (12, 6 e 3 ore). Migration `m27_palloni_solo_3h`, applicata in
produzione il 01/10/2026 (DD-042).
### Modificato
- **Avviso certificati: giocatore a 30 giorni** — il giocatore vede l'avviso giallo sul proprio - **Avviso certificati: giocatore a 30 giorni** — il giocatore vede l'avviso giallo sul proprio
certificato medico da un mese prima della scadenza (prima 7 giorni). Per gli admin non cambia certificato medico da un mese prima della scadenza (prima 7 giorni). Per gli admin non cambia
nulla: 7 giorni prima in giallo, nero dopo la scadenza (DD-041). nulla: 7 giorni prima in giallo, nero dopo la scadenza (DD-041).
+9 -9
View File
@@ -151,15 +151,15 @@ Le notifiche automatiche nascono nel database, da job `pg_cron` che chiamano fun
riservate alla service role (anon e utenti autenticati non possono chiamarle). Testi e regole sono in riservate alla service role (anon e utenti autenticati non possono chiamarle). Testi e regole sono in
[modules/notifiche.md](modules/notifiche.md); le decisioni in DD-037, DD-038 e DD-040. [modules/notifiche.md](modules/notifiche.md); le decisioni in DD-037, DD-038 e DD-040.
| Funzione (migration) | Cosa fa | Job `pg_cron` | | Funzione (migration) | Cosa fa | Job `pg_cron` |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| `genera_promemoria_eventi(tipo, finestra)` (M17, M23, M26) | Promemoria a 24 e 3 ore, con «oggi/domani», data, ora e luogo. | `promemoria-eventi-24h` (ogni ora), `-3h` (15 min) | | `genera_promemoria_eventi(tipo, finestra)` (M17, M23, M26) | Promemoria a 24 e 3 ore, con «oggi/domani», data, ora e luogo. | `promemoria-eventi-24h` (ogni ora), `-3h` (15 min) |
| `genera_avvisi_palloni()` (M26), con `genera_avvisi_palloni_fascia()` | Avvisi del turno palloni a 12, 6 e 3 ore per gli eventi con un incaricato confermato; poi chiama `genera_revoche_palloni()`. | `avvisi-palloni-automatici` (15 min) | | `genera_avvisi_palloni()` (M26, M27), con `genera_avvisi_palloni_fascia()` | Avviso del turno palloni a 3 ore (M27; in M26 erano 12, 6 e 3) per gli eventi con un incaricato confermato; poi chiama `genera_revoche_palloni()`. | `avvisi-palloni-automatici` (15 min) |
| `genera_revoche_palloni()` (M26) | Una revoca per chi aveva un avviso automatico e non è più destinatario, per eventi non ancora iniziati; guarda il registro. | (dentro `genera_avvisi_palloni()`) | | `genera_revoche_palloni()` (M26) | Una revoca per chi aveva un avviso automatico e non è più destinatario, per eventi non ancora iniziati; guarda il registro. | (dentro `genera_avvisi_palloni()`) |
| `genera_solleciti_presenze()` (M26), con `..._fascia()` | Solleciti a 24, 12 e 6 ore ai destinatari dell'evento senza risposta o con «forse». | `solleciti-presenze-automatici` (15 min) | | `genera_solleciti_presenze()` (M26), con `..._fascia()` | Solleciti a 24, 12 e 6 ore ai destinatari dell'evento senza risposta o con «forse». | `solleciti-presenze-automatici` (15 min) |
| `inizio_evento(data, ora)` (M26) | Istante d'inizio di un evento in fuso `Europe/Rome`; `NULL` se l'ora non è valida, e l'evento viene solo escluso. | — | | `inizio_evento(data, ora)` (M26) | Istante d'inizio di un evento in fuso `Europe/Rome`; `NULL` se l'ora non è valida, e l'evento viene solo escluso. | — |
| `giocatori_destinatari_evento(evento)` (M17, M21, M25) | Giocatori attivi destinatari: i convocati, o tutta la rosa se `convocati` è vuoto; mai gli allenatori. | — | | `giocatori_destinatari_evento(evento)` (M17, M21, M25) | Giocatori attivi destinatari: i convocati, o tutta la rosa se `convocati` è vuoto; mai gli allenatori. | — |
| `accoda_notifica_email()`, `accoda_push_promemoria()` (M22, M24, M26) | Trigger su `notifiche_utente`: la mail salta i tipi `turno_palloni*`; la push accoda i tipi automatici (non quelli dei pulsanti). | — | | `accoda_notifica_email()`, `accoda_push_promemoria()` (M22, M24, M26) | Trigger su `notifiche_utente`: la mail salta i tipi `turno_palloni*`; la push accoda i tipi automatici (non quelli dei pulsanti). | — |
## Badge ## Badge
+1 -1
View File
@@ -26,4 +26,4 @@ il campionato. Dove serve il dettaglio c'è il rimando al modulo.
| **Worker** | Il container Docker in `mailer/` che invia email e push dei promemoria con la service role ([WORKER_EMAIL.md](WORKER_EMAIL.md)). | | **Worker** | Il container Docker in `mailer/` che invia email e push dei promemoria con la service role ([WORKER_EMAIL.md](WORKER_EMAIL.md)). |
| **Service role** | Chiave Supabase che bypassa la RLS. Solo server e worker, mai il client ([OPERATIONS.md](OPERATIONS.md)). | | **Service role** | Chiave Supabase che bypassa la RLS. Solo server e worker, mai il client ([OPERATIONS.md](OPERATIONS.md)). |
| **DD-XXX** | Decisione registrata in [decisions/](decisions/README.md). | | **DD-XXX** | Decisione registrata in [decisions/](decisions/README.md). |
| **M1…M26** | Numero d'ordine delle migration (`m26_notifiche_automatiche`…) in `supabase/migrations/`. | | **M1…M27** | Numero d'ordine delle migration (`m27_palloni_solo_3h`…) in `supabase/migrations/`. |
+1 -1
View File
@@ -44,7 +44,7 @@ Tutto quello che è rilasciato, fino alla versione 1.0.0 (vedi `CHANGELOG.md`).
- [x] Notifiche email — ogni notifica in-app inviata anche per email agli account registrati, - [x] Notifiche email — ogni notifica in-app inviata anche per email agli account registrati,
da un worker Docker sull'host di casa via Gmail, con interruttore «Email» in Profilo da un worker Docker sull'host di casa via Gmail, con interruttore «Email» in Profilo
([specifica](modules/notifiche-email.md), DD-036) ([specifica](modules/notifiche-email.md), DD-036)
- [x] Notifiche automatiche — promemoria con «oggi/domani», turno palloni 12, 6 e 3 ore prima, sollecito - [x] Notifiche automatiche — promemoria con «oggi/domani», turno palloni 3 ore prima, sollecito
presenze a 24, 12 e 6 ore (i pulsanti «Avvisa chi è di turno» e «Sollecita» restano) ([specifica](modules/notifiche.md#testi-delle-notifiche), DD-040) presenze a 24, 12 e 6 ore (i pulsanti «Avvisa chi è di turno» e «Sollecita» restano) ([specifica](modules/notifiche.md#testi-delle-notifiche), DD-040)
## Prossimo ## Prossimo
+1 -1
View File
@@ -1,7 +1,7 @@
# Worker email: installazione e gestione # Worker email: installazione e gestione
Come si mette in funzione, si controlla e si diagnostica il worker (`mailer/`) che invia le notifiche Come si mette in funzione, si controlla e si diagnostica il worker (`mailer/`) che invia le notifiche
per email e le push delle notifiche che nascono nel database: promemoria a 24 e 3 ore, turno palloni a 12, 6 e 3 ore con revoca, solleciti presenze a 24, 12 e 6 ore (DD-040). Cosa fa il canale email sta in per email e le push delle notifiche che nascono nel database: promemoria a 24 e 3 ore, turno palloni a 3 ore con revoca, solleciti presenze a 24, 12 e 6 ore (DD-040). Cosa fa il canale email sta in
[modules/notifiche-email.md](modules/notifiche-email.md), la push dei promemoria in [modules/notifiche-email.md](modules/notifiche-email.md), la push dei promemoria in
[modules/notifiche.md](modules/notifiche.md#push-dei-promemoria-m24); perché è fatto così in [modules/notifiche.md](modules/notifiche.md#push-dei-promemoria-m24); perché è fatto così in
[DD-036](decisions/DD-036.md) [DD-036](decisions/DD-036.md)
+2 -2
View File
@@ -50,5 +50,5 @@ torna utile, e con lui il segreto: DD-024 descrive già come farlo.
--- ---
**Aggiornamento (30 settembre 2026)** — DD-040 affianca al pulsante un invio automatico a 12, 6 e 3 ore **Aggiornamento (30 settembre 2026)** — DD-040 affianca al pulsante un invio automatico (poi ridotto a uno solo, 3 ore
prima dell'evento, per gli eventi con un incaricato confermato; il pulsante e la route restano. prima, da DD-042), per gli eventi con un incaricato confermato; il pulsante e la route restano.
+1 -2
View File
@@ -1,8 +1,7 @@
# DD-040 — Promemoria con «oggi» o «domani», turno palloni e solleciti automatici # DD-040 — Promemoria con «oggi» o «domani», turno palloni e solleciti automatici
**Data:** 30 settembre 2026 **Data:** 30 settembre 2026
**Stato:** Accettata (implementata e verificata in locale: migration `m26`, job, route e test; la **Stato:** Accettata (implementata: migration `m26`, applicata in produzione il 30 settembre 2026, job, route e test). Il turno palloni è poi passato a un solo avviso a 3 ore da [DD-042](DD-042.md).
migration non è ancora applicata in produzione)
**Contesto** **Contesto**
Quattro notifiche su sei partivano da un pulsante premuto da un admin (DD-025 per i palloni): se nessuno Quattro notifiche su sei partivano da un pulsante premuto da un admin (DD-025 per i palloni): se nessuno
+36
View File
@@ -0,0 +1,36 @@
# DD-042 — Turno palloni: un solo avviso, 3 ore prima
**Data:** 1 ottobre 2026
**Stato:** Accettata (migration `m27`, applicata in produzione il 1 ottobre 2026; modifica la parte «turno palloni» di [DD-040](DD-040.md))
**Contesto**
DD-040 manda l'avviso del turno palloni a 12, 6 e 3 ore prima dell'evento: tre messaggi identici per
chi deve portare i palloni e per chi deve prenderli. Sono troppi per un'informazione che non cambia.
**Decisione**
1. L'avviso automatico parte **una sola volta, 3 ore prima** dell'inizio (finestra «meno di 3 ore»), a
chi deve portare i palloni («incarico assegnato») e a chi li deve prendere («riconsegna», se è
un'altra persona). Testi, destinatari, push senza email e condizione dell'incaricato confermato
restano quelli di DD-040.
2. La revoca resta: chi è stato avvisato e poi non è più destinatario riceve una sola volta «incarico
revocato». Un evento creato o assegnato a meno di 3 ore dall'inizio riceve l'avviso al giro
successivo del job.
3. **Migration `m27`:** `genera_avvisi_palloni()` chiama solo la fascia `turno_palloni_3h`. I tipi
`turno_palloni_12h` e `_6h` **restano nei `CHECK`** e nella revoca, perché notifiche e righe di
registro già generate li usano; non ne nascono di nuovi.
4. Il pulsante «Avvisa chi è di turno» non cambia. I solleciti presenze (24, 12 e 6 ore) non cambiano.
**Alternative scartate**
- Togliere i tipi `12h` e `6h` dai `CHECK` → le righe già generate in produzione non sarebbero più
valide e la revoca non vedrebbe più chi ha ricevuto quegli avvisi.
- Tenere anche un avviso a 12 ore → scartata su indicazione esplicita: uno solo.
**Conseguenze**
- Chi non apre l'app nelle 3 ore prima può non vedere l'avviso; la push è il canale principale.
- Per le notifiche palloni già generate con DD-040 non cambia nulla.
**Riesame**
Se 3 ore risultano poche per procurarsi i palloni, alzare la finestra (una sola, mai più avvisi).
+1
View File
@@ -86,6 +86,7 @@ Una decisione sostituita non si cancella: si cambia il suo **Stato** e si rimand
| [DD-038](DD-038.md) | Tre canali per ogni notifica | | [DD-038](DD-038.md) | Tre canali per ogni notifica |
| [DD-039](DD-039.md) | Niente promemoria all'allenatore | | [DD-039](DD-039.md) | Niente promemoria all'allenatore |
| [DD-040](DD-040.md) | Palloni e solleciti automatici | | [DD-040](DD-040.md) | Palloni e solleciti automatici |
| [DD-042](DD-042.md) | Turno palloni: un solo avviso a 3 ore |
| [DD-041](DD-041.md) | Avviso certificati: giocatore a 30 giorni | | [DD-041](DD-041.md) | Avviso certificati: giocatore a 30 giorni |
**In valutazione** **In valutazione**
+22 -22
View File
@@ -34,15 +34,15 @@ in-app** (storico in `notifiche_utente`, per tutti i giocatori) ed **email**
([notifiche-email.md](notifiche-email.md): ogni riga di `notifiche_utente` genera una mail, **tranne ([notifiche-email.md](notifiche-email.md): ogni riga di `notifiche_utente` genera una mail, **tranne
quelle del turno palloni**, che restano solo push e in-app: DD-040). quelle del turno palloni**, che restano solo push e in-app: DD-040).
| # | Notifica | Cosa la fa partire | Destinatari | Push | In-app | Email | | # | Notifica | Cosa la fa partire | Destinatari | Push | In-app | Email |
| --- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------ | --------------- | | --- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------ | --------------- |
| 1 | **Promemoria a 24 ore** — «Promemoria evento di _domani_ (o _oggi_): _titolo_», con data, ora e luogo | Job `pg_cron` `promemoria-eventi-24h`, ogni ora, per gli eventi che iniziano entro le prossime 24 ore. Tipo `evento_promemoria_24h`. La push la manda il worker (M24). | I convocati dell'evento, o tutta la rosa attiva se `convocati` è vuoto; mai gli allenatori (DD-039). | sì (worker) | sì | sì | | 1 | **Promemoria a 24 ore** — «Promemoria evento di _domani_ (o _oggi_): _titolo_», con data, ora e luogo | Job `pg_cron` `promemoria-eventi-24h`, ogni ora, per gli eventi che iniziano entro le prossime 24 ore. Tipo `evento_promemoria_24h`. La push la manda il worker (M24). | I convocati dell'evento, o tutta la rosa attiva se `convocati` è vuoto; mai gli allenatori (DD-039). | sì (worker) | sì | sì |
| 2 | **Promemoria a 3 ore** — stesso testo | Job `pg_cron` `promemoria-eventi-3h`, ogni 15 minuti, per gli eventi che iniziano entro le prossime 3 ore. Tipo `evento_promemoria_3h`. La push la manda il worker (M24). | Come il promemoria a 24 ore. | sì (worker) | sì | sì | | 2 | **Promemoria a 3 ore** — stesso testo | Job `pg_cron` `promemoria-eventi-3h`, ogni 15 minuti, per gli eventi che iniziano entro le prossime 3 ore. Tipo `evento_promemoria_3h`. La push la manda il worker (M24). | Come il promemoria a 24 ore. | sì (worker) | sì | sì |
| 3 | **Messaggio dello staff** — titolo fisso «Messaggio dallo staff», corpo il testo scritto (max 300 caratteri) | Un admin, dalla tab «Notifiche» della dashboard: «Invia messaggio a tutti» o il pulsante della riga di un giocatore. Route `notifica-personalizzata`, tipo `admin`. | Push: tutti i dispositivi iscritti, o quelli del giocatore scelto. In-app ed email: tutta la rosa attiva (allenatori compresi, anche senza dispositivi iscritti), o il giocatore scelto. | sì | sì | sì | | 3 | **Messaggio dello staff** — titolo fisso «Messaggio dallo staff», corpo il testo scritto (max 300 caratteri) | Un admin, dalla tab «Notifiche» della dashboard: «Invia messaggio a tutti» o il pulsante della riga di un giocatore. Route `notifica-personalizzata`, tipo `admin`. | Push: tutti i dispositivi iscritti, o quelli del giocatore scelto. In-app ed email: tutta la rosa attiva (allenatori compresi, anche senza dispositivi iscritti), o il giocatore scelto. | sì | sì | sì |
| 4 | **Turno palloni** — «Turno palloni: incarico assegnato» / «Turno palloni: riconsegna» | **Automatica**, **12, 6 e 3 ore prima** dell'evento (DD-040): job `pg_cron` ogni 15 minuti, solo per allenamenti e partite con un incaricato confermato; tipi `turno_palloni_12h`, `_6h` e `_3h`, push dal worker. Se l'incaricato cambia, chi aveva già ricevuto un avviso riceve una volta «Turno palloni: incarico revocato» (tipo `turno_palloni_revocato`). **Manuale**, in più: un admin, con il pulsante nella pagina dell'evento; route `promemoria-palloni`, tipo `turno_palloni`, push dalla route. | L'incaricato del turno di quell'evento e, se è un'altra persona, chi aveva i palloni all'evento precedente. Se per l'evento non c'è un incaricato non parte nulla. | sì | sì | **no** (DD-040) | | 4 | **Turno palloni** — «Turno palloni: incarico assegnato» / «Turno palloni: riconsegna» | **Automatica**, **una sola volta, 3 ore prima** dell'evento (DD-040, DD-042): job `pg_cron` ogni 15 minuti, solo per allenamenti e partite con un incaricato confermato; tipo `turno_palloni_3h` (`_12h` e `_6h` esistono solo nelle notifiche generate prima di `m27`), push dal worker. Se l'incaricato cambia, chi aveva già ricevuto un avviso riceve una volta «Turno palloni: incarico revocato» (tipo `turno_palloni_revocato`). **Manuale**, in più: un admin, con il pulsante nella pagina dell'evento; route `promemoria-palloni`, tipo `turno_palloni`, push dalla route. | L'incaricato del turno di quell'evento e, se è un'altra persona, chi aveva i palloni all'evento precedente. Se per l'evento non c'è un incaricato non parte nulla. | sì | sì | **no** (DD-040) |
| 5 | **Sollecito presenze** — «Conferma di partecipazione richiesta: _titolo_» | **Automatica**, in tre tempi (DD-040): **24, 12 e 6 ore** prima dell'evento, dallo stesso job; tipi `sollecita_presenze_24h`, `_12h`, `_6h`, push dal worker. **Manuale**, in più: un admin o un allenatore con il pulsante nella pagina dell'evento; route `sollecita-presenze`, tipo `sollecita_presenze`, push dalla route. | Automatica e manuale: i destinatari dell'evento (convocati, o tutta la rosa attiva se `convocati` è vuoto; mai gli allenatori) che in quel momento non hanno risposto o hanno risposto «forse». | sì | sì | sì | | 5 | **Sollecito presenze** — «Conferma di partecipazione richiesta: _titolo_» | **Automatica**, in tre tempi (DD-040): **24, 12 e 6 ore** prima dell'evento, dallo stesso job; tipi `sollecita_presenze_24h`, `_12h`, `_6h`, push dal worker. **Manuale**, in più: un admin o un allenatore con il pulsante nella pagina dell'evento; route `sollecita-presenze`, tipo `sollecita_presenze`, push dalla route. | Automatica e manuale: i destinatari dell'evento (convocati, o tutta la rosa attiva se `convocati` è vuoto; mai gli allenatori) che in quel momento non hanno risposto o hanno risposto «forse». | sì | sì | sì |
| 6 | **Sondaggio pre-partita** — «💩 Sondaggio pre-partita aperto» | Un admin, dalla pagina della partita. Route `apri-sondaggio` (vedi [Scout Live](scout-live.md)). | Push: tutti i dispositivi iscritti, tranne quelli degli allenatori. In-app ed email (tipo `sondaggio_cacche`): i giocatori attivi, allenatori esclusi, anche senza dispositivi iscritti. | sì | sì | sì | | 6 | **Sondaggio pre-partita** — «💩 Sondaggio pre-partita aperto» | Un admin, dalla pagina della partita. Route `apri-sondaggio` (vedi [Scout Live](scout-live.md)). | Push: tutti i dispositivi iscritti, tranne quelli degli allenatori. In-app ed email (tipo `sondaggio_cacche`): i giocatori attivi, allenatori esclusi, anche senza dispositivi iscritti. | sì | sì | sì |
| 7 | **Notifiche smart** — badge appena sbloccato, serie a un traguardo, obiettivo di squadra tra il 90 e il 100%, badge social vinto | L'app, mentre è aperta: sono calcolate in locale da `calcolaNotifiche()`. | Chi ha l'app aperta con uno slot giocatore in rosa selezionato; **mai l'allenatore**. Nessun invio verso altri dispositivi. | no (solo locale) | no | no | | 7 | **Notifiche smart** — badge appena sbloccato, serie a un traguardo, obiettivo di squadra tra il 90 e il 100%, badge social vinto | L'app, mentre è aperta: sono calcolate in locale da `calcolaNotifiche()`. | Chi ha l'app aperta con uno slot giocatore in rosa selezionato; **mai l'allenatore**. Nessun invio verso altri dispositivi. | no (solo locale) | no | no |
Le notifiche generate dal server (righe 1-6) hanno tutti e tre i canali (DD-038), tranne il turno palloni (riga 4), che non manda email (DD-040). La Le notifiche generate dal server (righe 1-6) hanno tutti e tre i canali (DD-038), tranne il turno palloni (riga 4), che non manda email (DD-040). La
differenza sta in come parte la push: quelle dei pulsanti (3 e 6) la mandano le route dell'app, quelle differenza sta in come parte la push: quelle dei pulsanti (3 e 6) la mandano le route dell'app, quelle
@@ -51,8 +51,8 @@ promemoria»). Il sondaggio riscrive la notifica esistente se l'admin preme di n
lo stesso evento (vedi «Centro notifiche in-app»). lo stesso evento (vedi «Centro notifiche in-app»).
> **Righe 1, 2, 4 e 5 (DD-040).** Implementate con la migration `m26` (job `pg_cron`, testi, revoca) e > **Righe 1, 2, 4 e 5 (DD-040).** Implementate con la migration `m26` (job `pg_cron`, testi, revoca) e
> verificate in locale; la migration va ancora applicata in produzione. Finché non lo è, l'app si comporta > applicate in produzione (`m26` il 30/09/2026; `m27` il 01/10/2026, che porta il turno palloni a un solo
> come in 1.2.0: promemoria con il testo breve, turno palloni e sollecito presenze solo da pulsante. > avviso 3 ore prima, DD-042).
Cosa riceve l'allenatore, notifica per notifica: [allenatore.md](allenatore.md#notifiche). Cosa riceve l'allenatore, notifica per notifica: [allenatore.md](allenatore.md#notifiche).
@@ -146,16 +146,16 @@ Sono l'effetto attuale della logica, non scelte documentate altrove:
### Test che verificano il catalogo ### Test che verificano il catalogo
| Cosa | Test | | Cosa | Test |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Promemoria 24 e 3 ore: destinatari (convocati, tutti i giocatori attivi se `convocati` è vuoto, mai l'allenatore), deduplica, eliminazione, coda push ed email | `test/integration/destinatari-notifiche.test.ts`, `promemoria-eventi.test.ts`, `push-promemoria.test.ts`, `notifiche-email.test.ts` | | Promemoria 24 e 3 ore: destinatari (convocati, tutti i giocatori attivi se `convocati` è vuoto, mai l'allenatore), deduplica, eliminazione, coda push ed email | `test/integration/destinatari-notifiche.test.ts`, `promemoria-eventi.test.ts`, `push-promemoria.test.ts`, `notifiche-email.test.ts` |
| Messaggio dello staff: push a tutti i dispositivi (allenatore compreso) o a uno solo, in-app a tutta la rosa attiva, riservato agli admin | `destinatari-notifiche.test.ts`, `notifiche-utente.test.ts` | | Messaggio dello staff: push a tutti i dispositivi (allenatore compreso) o a uno solo, in-app a tutta la rosa attiva, riservato agli admin | `destinatari-notifiche.test.ts`, `notifiche-utente.test.ts` |
| Turno palloni: incaricato e chi li aveva prima, solo allenamenti e partite, riservato agli admin | `destinatari-notifiche.test.ts`, `unit/palloni-core.test.ts` | | Turno palloni: incaricato e chi li aveva prima, solo allenamenti e partite, riservato agli admin | `destinatari-notifiche.test.ts`, `unit/palloni-core.test.ts` |
| Sollecito presenze: chi non ha risposto o ha detto «forse», mai l'allenatore; permesso ad allenatore e admin, non ai giocatori | `destinatari-notifiche.test.ts`, `unit/presenze.test.ts` | | Sollecito presenze: chi non ha risposto o ha detto «forse», mai l'allenatore; permesso ad allenatore e admin, non ai giocatori | `destinatari-notifiche.test.ts`, `unit/presenze.test.ts` |
| Sondaggio pre-partita: push senza allenatori, avviso in-app ed email ai giocatori attivi, `upsert`, riservato agli admin | `destinatari-notifiche.test.ts`, `sondaggio-notifiche.test.ts`, `unit/cacche.test.ts` | | Sondaggio pre-partita: push senza allenatori, avviso in-app ed email ai giocatori attivi, `upsert`, riservato agli admin | `destinatari-notifiche.test.ts`, `sondaggio-notifiche.test.ts`, `unit/cacche.test.ts` |
| Promemoria con «oggi/domani», turno palloni a 12·6·3 ore e revoca, solleciti a 24·12·6 ore (manuale compreso): finestre, destinatari, una sola volta, registro (DD-040, `m26`) | `integration/notifiche-automatiche.test.ts` (nuovo), `promemoria-eventi.test.ts` (testo oggi/domani), `destinatari-notifiche.test.ts` (pulsanti), `unit/palloni-core.test.ts`, `unit/presenze.test.ts` | | Promemoria con «oggi/domani», turno palloni a 3 ore e revoca, solleciti a 24·12·6 ore (manuale compreso): finestre, destinatari, una sola volta, registro (DD-040, `m26`; palloni a 3 ore: DD-042, `m27`) | `integration/notifiche-automatiche.test.ts`, `promemoria-eventi.test.ts` (testo oggi/domani), `destinatari-notifiche.test.ts` (pulsanti), `unit/palloni-core.test.ts`, `unit/presenze.test.ts` |
| Worker: esiti, backoff, tetto giornaliero, iscrizioni scadute | `unit/mailer-core.test.ts` | | Worker: esiti, backoff, tetto giornaliero, iscrizioni scadute | `unit/mailer-core.test.ts` |
| L'allenatore è fuori dalla rosa di gioco | `unit/giocatori-squadra.test.ts` (`inRosa`) | | L'allenatore è fuori dalla rosa di gioco | `unit/giocatori-squadra.test.ts` (`inRosa`) |
Le push dei test vanno a un servizio push finto in locale: si verifica **a chi** arrivano, non la Le push dei test vanno a un servizio push finto in locale: si verifica **a chi** arrivano, non la
consegna su un telefono. Non ha un test l'esclusione dell'allenatore dalle **notifiche smart**: la consegna su un telefono. Non ha un test l'esclusione dell'allenatore dalle **notifiche smart**: la
@@ -282,7 +282,7 @@ non richiede che il dispositivo abbia attivato «Notifiche», ha uno storico per
stessa cosa delle notifiche smart (quelle restano locali, non salvate a database). stessa cosa delle notifiche smart (quelle restano locali, non salvate a database).
Cinque sorgenti scrivono in `notifiche_utente`, mai il client (con DD-040 le sorgenti 3 e 4 hanno in più un Cinque sorgenti scrivono in `notifiche_utente`, mai il client (con DD-040 le sorgenti 3 e 4 hanno in più un
job `pg_cron` come la 2, che scrive `turno_palloni_12h`/`_6h`/`_3h`/`_revocato` e `sollecita_presenze_24h`/ job `pg_cron` come la 2, che scrive `turno_palloni_3h`/`_revocato` (`_12h`/`_6h` solo fino a `m26`) e `sollecita_presenze_24h`/
`_12h`/`_6h`; i pulsanti restano e scrivono `turno_palloni` e `sollecita_presenze`): `_12h`/`_6h`; i pulsanti restano e scrivono `turno_palloni` e `sollecita_presenze`):
1. **Messaggio admin** — `notifica-personalizzata.ts` inserisce una riga per destinatario 1. **Messaggio admin** — `notifica-personalizzata.ts` inserisce una riga per destinatario
+2 -2
View File
@@ -11,7 +11,7 @@
Gestire un turno a rotazione condiviso per chi porta e riporta i palloni ad allenamenti e Gestire un turno a rotazione condiviso per chi porta e riporta i palloni ad allenamenti e
partite, con proposta automatica, possibilità di modifica manuale e avvisi push, nel centro partite, con proposta automatica, possibilità di modifica manuale e avvisi push, nel centro
notifiche e senza email a 12, 6 e 3 ore dall'evento (DD-040). Gli eventi extra-campo (`tipo === "evento"`, es. cena di squadra) e i notifiche e senza email 3 ore prima dell'evento (DD-040, DD-042). Gli eventi extra-campo (`tipo === "evento"`, es. cena di squadra) e i
compleanni non c'entrano: non richiedono palloni. compleanni non c'entrano: non richiedono palloni.
--- ---
@@ -56,7 +56,7 @@ oggi`, stesso criterio delle presenze): un turno assegnato in anticipo per un al
## Route API pubblica `/api/public/promemoria-palloni` ## Route API pubblica `/api/public/promemoria-palloni`
> **Affiancata da un invio automatico (DD-040, migration `m26`).** Oltre al pulsante, un job > **Affiancata da un invio automatico (DD-040, migration `m26`).** Oltre al pulsante, un job
> `pg_cron` ogni 15 minuti manda l'avviso **12, 6 e 3 ore prima** dell'evento, solo se per l'evento c'è un > `pg_cron` ogni 15 minuti manda l'avviso **una sola volta, 3 ore prima** dell'evento (DD-042, migration `m27`), solo se per l'evento c'è un
> incaricato confermato in `turni_palloni`. Il pulsante e la route **restano**, con tipo > incaricato confermato in `turni_palloni`. Il pulsante e la route **restano**, con tipo
> `turno_palloni` e push dalla route. I due destinatari e i testi nuovi («incarico assegnato» / > `turno_palloni` e push dalla route. I due destinatari e i testi nuovi («incarico assegnato» /
> «riconsegna», validi per il pulsante e per i job, più la revoca se l'incaricato cambia) sono nel > «riconsegna», validi per il pulsante e per i job, più la revoca se l'incaricato cambia) sono nel
@@ -0,0 +1,22 @@
-- M27 — Turno palloni: un solo avviso, 3 ore prima dell'evento (DD-042)
--
-- Con M26 il job mandava l'avviso a 12, 6 e 3 ore. Ne basta uno, 3 ore prima, a chi deve portare
-- i palloni e a chi li deve prendere. Cambia solo il job: i tipi `turno_palloni_12h` e `_6h`
-- restano ammessi dai CHECK perché esistono notifiche e righe di registro già generate (anche la
-- revoca le guarda), ma non ne nascono di nuove. Le funzioni `genera_avvisi_palloni_fascia` e
-- `genera_revoche_palloni` non cambiano.
CREATE OR REPLACE FUNCTION public.genera_avvisi_palloni()
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
PERFORM public.genera_avvisi_palloni_fascia('turno_palloni_3h', interval '0', interval '3 hours');
PERFORM public.genera_revoche_palloni();
END;
$$;
REVOKE ALL ON FUNCTION public.genera_avvisi_palloni() FROM PUBLIC, anon, authenticated;
GRANT EXECUTE ON FUNCTION public.genera_avvisi_palloni() TO service_role;
+6 -6
View File
@@ -24,12 +24,12 @@ la consegna effettiva a schermo bloccato richiede un telefono e il servizio push
## Struttura ## Struttura
| Cartella | Cosa verifica | Serve rete? | | Cartella | Cosa verifica | Serve rete? |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi, notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar, guardia admin delle route, lettura del `.env` ed elenco dei bucket dello script di backup, logica del worker in `mailer/` (email e push dei promemoria), preferenza «Email» e avviso del sondaggio) | No | | `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi, notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar, guardia admin delle route, lettura del `.env` ed elenco dei bucket dello script di backup, logica del worker in `mailer/` (email e push dei promemoria), preferenza «Email» e avviso del sondaggio) | No |
| `integration/` | Le route `/api/public/*` sul server di sviluppo: risposte, cache, validazione degli input. Più schema e permessi del Profilo Giocatore (`schema-profili`) contro il database configurato; permessi per ruolo, incluse le policy di M11, le deroghe dell'amministratore e le tabelle lasciate aperte di proposito (`permessi`), accesso alle route di notifica (`permessi-route`), semantica degli upsert e vincoli (`scritture`), le letture lato server delle route push (`lettori-server`) e la pulizia a cascata dei dati collegati alla cancellazione di un evento (`pulizia-evento`, M14) la funzione di bonifica delle righe orfane (`bonifica-evento`, M16), il centro notifiche in-app (`notifiche-utente`, M17), la coda delle email e le sue funzioni RPC (`notifiche-email`, M22), i promemoria evento che non ricompaiono se eliminati (`promemoria-eventi`, M23), la coda delle push dei promemoria (`push-promemoria`, M24), i job automatici di turno palloni e sollecito presenze con revoca, finestre e testi identici al codice (`notifiche-automatiche`, M26, DD-040) e il sondaggio in-app/email (`sondaggio-notifiche`, M24), i destinatari delle notifiche per ruolo (`destinatari-notifiche`: promemoria, messaggio dello staff, sondaggio, sollecito e turno palloni con allenatore e giocatore), la nascita pubblica sincronizzata dal profilo (`nascita-pubblica`, M18) e il suo backfill per i profili preesistenti (`backfill-nascita-esistenti`, M19 — unico test che esegue SQL diretto via `docker exec` nel container Postgres, perché PostgREST non espone quell'update multi-tabella come RPC) sul database locale | Sì | | `integration/` | Le route `/api/public/*` sul server di sviluppo: risposte, cache, validazione degli input. Più schema e permessi del Profilo Giocatore (`schema-profili`) contro il database configurato; permessi per ruolo, incluse le policy di M11, le deroghe dell'amministratore e le tabelle lasciate aperte di proposito (`permessi`), accesso alle route di notifica (`permessi-route`), semantica degli upsert e vincoli (`scritture`), le letture lato server delle route push (`lettori-server`) e la pulizia a cascata dei dati collegati alla cancellazione di un evento (`pulizia-evento`, M14) la funzione di bonifica delle righe orfane (`bonifica-evento`, M16), il centro notifiche in-app (`notifiche-utente`, M17), la coda delle email e le sue funzioni RPC (`notifiche-email`, M22), i promemoria evento che non ricompaiono se eliminati (`promemoria-eventi`, M23), la coda delle push dei promemoria (`push-promemoria`, M24), i job automatici di turno palloni e sollecito presenze con revoca, finestre e testi identici al codice (`notifiche-automatiche`, M26 e M27, DD-040 e DD-042: palloni un solo avviso a 3 ore) e il sondaggio in-app/email (`sondaggio-notifiche`, M24), i destinatari delle notifiche per ruolo (`destinatari-notifiche`: promemoria, messaggio dello staff, sondaggio, sollecito e turno palloni con allenatore e giocatore), la nascita pubblica sincronizzata dal profilo (`nascita-pubblica`, M18) e il suo backfill per i profili preesistenti (`backfill-nascita-esistenti`, M19 — unico test che esegue SQL diretto via `docker exec` nel container Postgres, perché PostgREST non espone quell'update multi-tabella come RPC) sul database locale | Sì |
| `e2e/` | Percorsi completi sull'app servita: schermate, dati CSI fino alla pagina, file PWA, 404 | Sì | | `e2e/` | Percorsi completi sull'app servita: schermate, dati CSI fino alla pagina, file PWA, 404 | Sì |
| `helpers/` | Avvio del server di test e mini-harness condiviso | — | | `helpers/` | Avvio del server di test e mini-harness condiviso | — |
## Server di sviluppo con Node vecchio ## Server di sviluppo con Node vecchio
+65 -44
View File
@@ -5,7 +5,8 @@
* Esegue i job veri del database (`genera_avvisi_palloni`, `genera_solleciti_presenze`) su eventi * Esegue i job veri del database (`genera_avvisi_palloni`, `genera_solleciti_presenze`) su eventi
* creati con orari relativi a «adesso» e verifica: * creati con orari relativi a «adesso» e verifica:
* *
* - le finestre contigue e senza sovrapposizione (palloni 12·6·3 ore, solleciti 24·12·6 ore); * - le finestre (palloni: un solo avviso nelle 3 ore prima; solleciti 24·12·6 ore, contigue e senza
* sovrapposizione);
* - i destinatari (incaricato e turno precedente; convocati senza risposta o con «forse»; mai * - i destinatari (incaricato e turno precedente; convocati senza risposta o con «forse»; mai
* l'allenatore né chi non è più attivo); * l'allenatore né chi non è più attivo);
* - una sola generazione per giocatore, evento e tipo, anche se la notifica viene eliminata; * - una sola generazione per giocatore, evento e tipo, anche se la notifica viene eliminata;
@@ -46,12 +47,14 @@ if (!locale) {
const INATTIVO = "g9"; const INATTIVO = "g9";
const ALLENATORE = "g10"; const ALLENATORE = "g10";
// Palloni: in ordine cronologico PASSATO < PREC < MAIN < EV6 < EV12 < LONTANO. // Palloni: in ordine cronologico PASSATO < PREC < MAIN < SECONDO < TERZO < FUORI4 < FUORI9 < LONTANO.
const PASSATO = `${PREFISSO}-passato`; const PASSATO = `${PREFISSO}-passato`;
const PREC = `${PREFISSO}-prec`; const PREC = `${PREFISSO}-prec`;
const MAIN = `${PREFISSO}-main`; const MAIN = `${PREFISSO}-main`;
const EV6 = `${PREFISSO}-ev6`; const SECONDO = `${PREFISSO}-secondo`;
const EV12 = `${PREFISSO}-ev12`; const TERZO = `${PREFISSO}-terzo`;
const FUORI4 = `${PREFISSO}-fuori4`;
const FUORI9 = `${PREFISSO}-fuori9`;
const LONTANO = `${PREFISSO}-lontano`; const LONTANO = `${PREFISSO}-lontano`;
const GENERICO = `${PREFISSO}-generico`; const GENERICO = `${PREFISSO}-generico`;
const COPPIA_A = `${PREFISSO}-coppia-a`; const COPPIA_A = `${PREFISSO}-coppia-a`;
@@ -68,8 +71,10 @@ if (!locale) {
PASSATO, PASSATO,
PREC, PREC,
MAIN, MAIN,
EV6, SECONDO,
EV12, TERZO,
FUORI4,
FUORI9,
LONTANO, LONTANO,
GENERICO, GENERICO,
COPPIA_A, COPPIA_A,
@@ -266,36 +271,51 @@ if (!locale) {
); );
await scrivi(`giocatori_squadra?id=eq.${INATTIVO}`, "PATCH", { attivo: false }, tokenAdmin); await scrivi(`giocatori_squadra?id=eq.${INATTIVO}`, "PATCH", { attivo: false }, tokenAdmin);
// Palloni. Le ore scelte stanno a più di mezz'ora dai confini delle finestre. // Palloni (DD-042): un solo avviso, nelle 3 ore prima. MAIN, SECONDO e TERZO sono dentro la
// finestra; FUORI4 e FUORI9 hanno un incaricato ma sono ancora fuori (tra 4,5 e 9 ore).
// Le ore scelte stanno a più di mezz'ora dai confini della finestra.
await creaEvento(PASSATO, "partita", tra(-60)); await creaEvento(PASSATO, "partita", tra(-60));
await creaEvento(PREC, "allenamento", tra(-30)); await creaEvento(PREC, "allenamento", tra(-30));
await creaEvento(MAIN, "allenamento", tra(2.5)); await creaEvento(MAIN, "allenamento", tra(0.8));
await creaEvento(EV6, "partita", tra(4.5)); await creaEvento(SECONDO, "partita", tra(1.5));
await creaEvento(EV12, "allenamento", tra(9)); await creaEvento(TERZO, "allenamento", tra(2.2));
await creaEvento(FUORI4, "allenamento", tra(4.5));
await creaEvento(FUORI9, "allenamento", tra(9));
await creaEvento(LONTANO, "allenamento", tra(40)); await creaEvento(LONTANO, "allenamento", tra(40));
await creaEvento(GENERICO, "evento", tra(2.2)); await creaEvento(GENERICO, "evento", tra(1.9));
await assegna(PREC, G5); await assegna(PREC, G5);
await assegna(MAIN, G4); await assegna(MAIN, G4);
await assegna(EV6, G7); await assegna(SECONDO, G7);
await assegna(EV12, G8); await assegna(TERZO, G8);
await assegna(FUORI4, G7);
await assegna(FUORI9, G8);
await assegna(GENERICO, G4); // extra-campo: il turno non conta await assegna(GENERICO, G4); // extra-campo: il turno non conta
// --- Turno palloni: finestre e destinatari --------------------------------------------- // --- Turno palloni: finestre e destinatari ---------------------------------------------
await prova("ogni evento riceve l'avviso della sua fascia e di nessun'altra", async () => { await prova(
await esegui("genera_avvisi_palloni"); "un solo avviso, 3 ore prima, a chi porta i palloni e a chi li prende",
assert.deepEqual(await destinatari(MAIN, "turno_palloni_3h"), [G4, G5]); async () => {
assert.deepEqual(await destinatari(EV6, "turno_palloni_6h"), [G4, G7]); await esegui("genera_avvisi_palloni");
assert.deepEqual(await destinatari(EV12, "turno_palloni_12h"), [G7, G8]); assert.deepEqual(await destinatari(MAIN, "turno_palloni_3h"), [G4, G5]);
for (const [evento, altri] of [ assert.deepEqual(await destinatari(SECONDO, "turno_palloni_3h"), [G4, G7]);
[MAIN, ["turno_palloni_6h", "turno_palloni_12h"]], assert.deepEqual(await destinatari(TERZO, "turno_palloni_3h"), [G7, G8]);
[EV6, ["turno_palloni_3h", "turno_palloni_12h"]], for (const evento of [MAIN, SECONDO, TERZO]) {
[EV12, ["turno_palloni_3h", "turno_palloni_6h"]], for (const tipo of ["turno_palloni_12h", "turno_palloni_6h"]) {
] as const) { assert.equal((await notifiche(evento, tipo)).length, 0, `${evento} non ha ${tipo}`);
for (const tipo of altri) { }
assert.equal((await notifiche(evento, tipo)).length, 0, `${evento} non ha ${tipo}`);
} }
} },
}); );
await prova(
"a 4,5 e 9 ore dall'inizio non parte nessun avviso, anche con l'incaricato",
async () => {
assert.equal((await notifiche(FUORI4)).length, 0, "tra 4,5 ore: prima era la fascia 6 ore");
assert.equal((await notifiche(FUORI9)).length, 0, "tra 9 ore: prima era la fascia 12 ore");
assert.equal((await registro(FUORI4)).length, 0);
assert.equal((await registro(FUORI9)).length, 0);
},
);
await prova( await prova(
"nessun avviso per eventi lontani, passati, extra-campo o senza incaricato", "nessun avviso per eventi lontani, passati, extra-campo o senza incaricato",
@@ -314,8 +334,8 @@ if (!locale) {
const turni = await turniDelDatabase(); const turni = await turniDelDatabase();
for (const [evento, tipo] of [ for (const [evento, tipo] of [
[MAIN, "turno_palloni_3h"], [MAIN, "turno_palloni_3h"],
[EV6, "turno_palloni_6h"], [SECONDO, "turno_palloni_3h"],
[EV12, "turno_palloni_12h"], [TERZO, "turno_palloni_3h"],
] as const) { ] as const) {
const attesi = avvisiPalloniEvento(turni, eventi, evento); const attesi = avvisiPalloniEvento(turni, eventi, evento);
assert.equal(attesi.length, 2, evento); assert.equal(attesi.length, 2, evento);
@@ -330,7 +350,7 @@ if (!locale) {
const [incarico] = (await notifiche(MAIN, "turno_palloni_3h")).filter( const [incarico] = (await notifiche(MAIN, "turno_palloni_3h")).filter(
(r) => r.giocatore_id === G4, (r) => r.giocatore_id === G4,
); );
const eventiEv6 = eventi.find((e) => e.id === EV6)!; const eventoSecondo = eventi.find((e) => e.id === SECONDO)!;
const main = eventi.find((e) => e.id === MAIN)!; const main = eventi.find((e) => e.id === MAIN)!;
assert.equal( assert.equal(
incarico?.corpo, incarico?.corpo,
@@ -338,7 +358,7 @@ if (!locale) {
`Evento: ${PREFISSO} main`, `Evento: ${PREFISSO} main`,
`Data: ${gg(main.data)}, ore ${main.ora}`, `Data: ${gg(main.data)}, ore ${main.ora}`,
"Incarico: custodia dei palloni al termine dell'evento", "Incarico: custodia dei palloni al termine dell'evento",
`Riconsegna: ${gg(eventiEv6.data)}`, `Riconsegna: ${gg(eventoSecondo.data)}`,
].join("\n"), ].join("\n"),
"la riconsegna è la data dell'evento successivo", "la riconsegna è la data dell'evento successivo",
); );
@@ -349,8 +369,8 @@ if (!locale) {
assert.equal((await registro(MAIN, "turno_palloni_3h")).length, 2); assert.equal((await registro(MAIN, "turno_palloni_3h")).length, 2);
const righe = [ const righe = [
...(await notifiche(MAIN, "turno_palloni_3h")), ...(await notifiche(MAIN, "turno_palloni_3h")),
...(await notifiche(EV6, "turno_palloni_6h")), ...(await notifiche(SECONDO, "turno_palloni_3h")),
...(await notifiche(EV12, "turno_palloni_12h")), ...(await notifiche(TERZO, "turno_palloni_3h")),
]; ];
const ids = righe.map((r) => r.id); const ids = righe.map((r) => r.id);
assert.equal(ids.length, 6); assert.equal(ids.length, 6);
@@ -385,13 +405,13 @@ if (!locale) {
"ora non valida: escluso, senza errori", "ora non valida: escluso, senza errori",
); );
await scrivi(`turni_palloni?evento_id=eq.${EV12}`, "PATCH", { giocatore_id: INATTIVO }); await scrivi(`turni_palloni?evento_id=eq.${TERZO}`, "PATCH", { giocatore_id: INATTIVO });
await esegui("genera_avvisi_palloni"); await esegui("genera_avvisi_palloni");
assert.ok( assert.ok(
!(await destinatari(EV12, "turno_palloni_12h")).includes(INATTIVO), !(await destinatari(TERZO, "turno_palloni_3h")).includes(INATTIVO),
"chi non è più attivo non riceve nulla", "chi non è più attivo non riceve nulla",
); );
await cambiaTurno(EV12, G8); await cambiaTurno(TERZO, G8);
}, },
); );
@@ -452,18 +472,19 @@ if (!locale) {
"turno tolto: chi era stato avvisato, incaricato o riconsegna, viene revocato", "turno tolto: chi era stato avvisato, incaricato o riconsegna, viene revocato",
async () => { async () => {
// La notifica dell'avviso può essere stata eliminata: conta il registro. // La notifica dell'avviso può essere stata eliminata: conta il registro.
const [avvisoG7] = (await notifiche(EV6, "turno_palloni_6h")).filter( const [avvisoG7] = (await notifiche(SECONDO, "turno_palloni_3h")).filter(
(r) => r.giocatore_id === G7, (r) => r.giocatore_id === G7,
); );
await scrivi(`notifiche_utente?id=eq.${avvisoG7!.id}`, "DELETE"); await scrivi(`notifiche_utente?id=eq.${avvisoG7!.id}`, "DELETE");
await scrivi(`turni_palloni?evento_id=eq.${EV6}`, "DELETE"); await scrivi(`turni_palloni?evento_id=eq.${SECONDO}`, "DELETE");
await esegui("genera_avvisi_palloni"); await esegui("genera_avvisi_palloni");
const revocati = await destinatari(EV6, "turno_palloni_revocato"); const revocati = await destinatari(SECONDO, "turno_palloni_revocato");
assert.ok(revocati.includes(G7), "g7 aveva eliminato l'avviso: conta il registro"); assert.ok(revocati.includes(G7), "g7 aveva eliminato l'avviso: conta il registro");
assert.ok(!revocati.includes(G8), "g8 non c'entra con questo evento"); assert.ok(!revocati.includes(G8), "g8 non c'entra con questo evento");
// Senza incaricato non parte nulla: nemmeno un nuovo avviso di fascia per nessuno. // Senza incaricato non parte nulla di nuovo: restano g4 e g6, avvisati prima come
assert.equal((await notifiche(EV6, "turno_palloni_3h")).length, 0); // «riconsegna» quando il turno di MAIN è passato a g6 e poi tornato a g4.
await assegna(EV6, G7); assert.deepEqual(await destinatari(SECONDO, "turno_palloni_3h"), [G4, G6]);
await assegna(SECONDO, G7);
}, },
); );
@@ -511,12 +532,12 @@ if (!locale) {
// Solo i due eventi della coppia cadono nella fascia: 8 giorni ± un giorno. // Solo i due eventi della coppia cadono nella fascia: 8 giorni ± un giorno.
await esegui("genera_avvisi_palloni_fascia", { await esegui("genera_avvisi_palloni_fascia", {
p_tipo: "turno_palloni_12h", p_tipo: "turno_palloni_3h",
p_da: `${24 * 7} hours`, p_da: `${24 * 7} hours`,
p_a: `${24 * 9} hours`, p_a: `${24 * 9} hours`,
}); });
for (const evento of [COPPIA_A, COPPIA_B]) { for (const evento of [COPPIA_A, COPPIA_B]) {
const righe = await notifiche(evento, "turno_palloni_12h"); const righe = await notifiche(evento, "turno_palloni_3h");
const attesi = avvisiPalloniEvento(turni, eventi, evento); const attesi = avvisiPalloniEvento(turni, eventi, evento);
assert.equal(righe.length, attesi.length, evento); assert.equal(righe.length, attesi.length, evento);
for (const a of attesi) { for (const a of attesi) {