Rende dinamica anche la scadenza di "Tutti rispondono alle convocazioni"

La scadenza era una data fissa (2026-09-30), stesso problema già risolto per
"presenze del mese" e "evento di squadra al mese": ora segue l'ultimo giorno
del mese corrente. Aggiunti unit test e integration test end-to-end contro il
database locale (isolando gli eventi di test, perché questo obiettivo aggrega
su tutti gli eventi indipendentemente dal mese).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-08 11:14:57 +02:00
co-authored by Claude Sonnet 5
parent 2b30dd4708
commit e13722eae9
4 changed files with 93 additions and 8 deletions
+11 -5
View File
@@ -59,8 +59,14 @@ smart (`notifiche-smart.ts`).
"90% presenze del mese" e "1 evento di squadra al mese" si azzerano automaticamente a ogni
cambio mese: il mese di riferimento è calcolato dalla data corrente (fuso Europe/Rome), non più
una costante fissa. Per il primo, titolo ("90% di presenze ad agosto" / "a settembre" / ...) e
scadenza (ultimo giorno del mese) seguono di conseguenza. Per i test, `obiettiviSquadra`/
`obiettiviOrdinati` accettano un terzo parametro opzionale `oggi: Date` per iniettare una data
deterministica. Coperti sia da unit test (`test/unit/obiettivi.test.ts`, funzione pura) sia da
un integration test end-to-end (`test/integration/obiettivi.test.ts`, scrive/rilegge righe vere
su `eventi_app` e `risposte_presenze` sullo stack Supabase locale).
scadenza (ultimo giorno del mese) seguono di conseguenza.
"Tutti rispondono alle convocazioni" non si azzera (aggrega su tutti gli eventi in programma,
non solo quelli del mese corrente), ma la sua `scadenza` mostrata in interfaccia era anch'essa
una data fissa (`"2026-09-30"`): ora è anch'essa l'ultimo giorno del mese corrente.
Per i test, `obiettiviSquadra`/`obiettiviOrdinati` accettano un terzo parametro opzionale
`oggi: Date` per iniettare una data deterministica. Coperti sia da unit test
(`test/unit/obiettivi.test.ts`, funzione pura) sia da un integration test end-to-end
(`test/integration/obiettivi.test.ts`, scrive/rilegge righe vere su `eventi_app` e
`risposte_presenze` sullo stack Supabase locale).
+1 -1
View File
@@ -102,7 +102,7 @@ export function obiettiviSquadra(
valore: percentualeRisposte(ctx, rosa.length),
target: 90,
unita: "%",
scadenza: "2026-09-30",
scadenza: fineMese(oggi),
emoji: "⚡",
impatto: "Bastano pochi tap per far quadrare i conti a chi organizza.",
},
+74 -2
View File
@@ -1,6 +1,7 @@
/**
* Obiettivi mensili di squadra ("presenze del mese" e "evento di squadra al mese")
* end-to-end contro il database locale: `bun test/integration/obiettivi.test.ts`.
* Obiettivi di squadra con scadenza/mese dinamici ("presenze del mese", "evento di squadra al
* mese" e "tutti rispondono alle convocazioni") end-to-end contro il database locale:
* `bun test/integration/obiettivi.test.ts`.
*
* I test unitari (`test/unit/obiettivi.test.ts`) verificano `obiettiviSquadra()` come funzione
* pura, con un `ContestoObiettivi` costruito a mano. Qui invece si scrivono righe vere su
@@ -207,6 +208,77 @@ if (!locale) {
);
},
);
await prova(
"o2 legge dal database risposte reali ed esclude i compleanni, con scadenza dinamica",
async () => {
// o2 (percentualeRisposte) aggrega su TUTTI gli eventi non-compleanno, senza filtro di
// mese: per un'asserzione deterministica isoliamo dal risultato reale solo i due eventi
// di questo test, invece di dipendere dal numero di eventi già presenti nel database
// (seed incluso).
const partitaId = `${PREFISSO}-o2-partita`;
const compleannoId = `${PREFISSO}-o2-compleanno`;
const inseritaPartita = await rest("eventi_app", {
method: "POST",
body: JSON.stringify({
id: partitaId,
tipo: "partita",
titolo: "Test obiettivi o2",
data: `${MESE_TEST}-05`,
}),
});
if (!inseritaPartita.ok) {
throw new Error(`inserimento evento fallito: ${await inseritaPartita.text()}`);
}
const inseritoCompleanno = await rest("eventi_app", {
method: "POST",
body: JSON.stringify({
id: compleannoId,
tipo: "compleanno",
titolo: "Test obiettivi o2 - compleanno",
data: `${MESE_TEST}-06`,
}),
});
if (!inseritoCompleanno.ok) {
throw new Error(`inserimento evento fallito: ${await inseritoCompleanno.text()}`);
}
// Solo 12 giocatori su tutta la rosa rispondono alla partita; nessuno "risponde" al
// compleanno, perché non richiede risposta.
const rispondenti = giocatori.slice(0, 12);
const righe = rispondenti.map((g, i) => ({
evento_id: partitaId,
giocatore_id: g.id,
stato: i % 2 === 0 ? "presente" : "forse",
}));
const inseriteRisposte = await rest("risposte_presenze", {
method: "POST",
body: JSON.stringify(righe),
});
if (!inseriteRisposte.ok) {
throw new Error(`inserimento presenze fallito: ${await inseriteRisposte.text()}`);
}
const eventiReali = (await leggiEventi()).filter(
(e) => e.id === partitaId || e.id === compleannoId,
);
const presenzeReali = await leggiPresenze(partitaId);
const o2 = obiettiviSquadra(
giocatori,
{ eventi: eventiReali, presenze: presenzeReali, pagelle: [] },
OGGI,
).find((o) => o.id === "o2")!;
assert.equal(
o2.valore,
Math.round((12 / giocatori.length) * 100),
"12 risposte reali su un solo evento che le richiede (il compleanno è escluso)",
);
assert.equal(o2.scadenza, "2099-03-31", "scadenza o2 = ultimo giorno del mese iniettato");
},
);
} finally {
await rest(`risposte_presenze?evento_id=like.${PREFISSO}*`, { method: "DELETE" });
await rest(`eventi_app?id=like.${PREFISSO}*`, { method: "DELETE" });
+7
View File
@@ -127,6 +127,13 @@ assert.equal(trova(obiettiviSquadra(giocatori, soloCompleanni, OGGI_AGOSTO), "o2
const o1Settembre = trova(obiettiviSquadra(giocatori, contestoVuoto, OGGI_SETTEMBRE), "o1");
assert.ok(o1Settembre.titolo.includes("settembre"), "titolo o1 riflette il mese iniettato (settembre)");
assert.equal(o1Settembre.scadenza, "2026-09-30", "scadenza = ultimo giorno di settembre (30 gg)");
// La scadenza di o2 ("Tutti rispondono alle convocazioni") era una data fissa
// ("2026-09-30"): ora segue lo stesso mese dinamico di o1.
const o2Agosto = trova(obiettiviSquadra(giocatori, contestoVuoto, OGGI_AGOSTO), "o2");
assert.equal(o2Agosto.scadenza, "2026-08-31", "scadenza o2 = ultimo giorno del mese iniettato");
const o2Settembre = trova(obiettiviSquadra(giocatori, contestoVuoto, OGGI_SETTEMBRE), "o2");
assert.equal(o2Settembre.scadenza, "2026-09-30", "scadenza o2 cambia con il mese iniettato");
}
// --- o6: evento di squadra al mese, si azzera come o1 ------------------------