longevity: gestionale trainer (clienti, check-up, scheda cliente) #2

Closed
opened 2026-09-26 12:22:13 +00:00 by Adriano · 5 comments
Owner

COSA: pagine /longevity/gestionale per trainer e admin: elenco clienti, nuovo fascicolo, scheda cliente (sessioni, referto, misure fuori range), inserimento check-up con misure fisiche. API sotto /api/longevity/gestionale/.
PERCHÉ: metà del prodotto; oggi l'unico ingresso di dati è il questionario del cliente, i fascicoli si aprono solo da scripts/crea-fascicolo.ts.
FATTO QUANDO: un trainer crea un cliente, inserisce un check-up, vede il referto aggiornato; nomi letti solo tramite anagrafica.ts; test verdi.
DIPENDE DA: issue test fisici nel registro.

COSA: pagine /longevity/gestionale per trainer e admin: elenco clienti, nuovo fascicolo, scheda cliente (sessioni, referto, misure fuori range), inserimento check-up con misure fisiche. API sotto /api/longevity/gestionale/. PERCHÉ: metà del prodotto; oggi l'unico ingresso di dati è il questionario del cliente, i fascicoli si aprono solo da scripts/crea-fascicolo.ts. FATTO QUANDO: un trainer crea un cliente, inserisce un check-up, vede il referto aggiornato; nomi letti solo tramite anagrafica.ts; test verdi. DIPENDE DA: issue test fisici nel registro.
Adriano added the sviluppo label 2026-09-26 12:22:13 +00:00
Adriano added the assegnata:goldrake label 2026-09-26 12:24:46 +00:00
Author
Owner

🤖 goldrake — preso in carico 2026-09-26T12:24:46Z
ramo: lavoro/goldrake · capacità: docker, pubblica, sempre-accesa, systemd

🤖 goldrake — **preso in carico** 2026-09-26T12:24:46Z ramo: `lavoro/goldrake` · capacità: docker, pubblica, sempre-accesa, systemd
Author
Owner

ESITO: richiesta
RIF: gitea:Adriano/InsanityLab-web#2
COSA: analisi (passo 1). Gestionale trainer in due unità: 2a libreria + API, 2b pagine. Parte dopo #1 (usa calcolaSessione con le sessioni combinate).
RAMO: lavoro/goldrake — clone goldrake:~/lavoro/InsanityLab-web, app apps/platforms

Vincoli verificati

  • src/lib/auth.ts RULES: /longevity/gestionale e /api/longevity/gestionale già riservati a admin+trainer. Tutto il resto di /api/longevity/ è aperto ai clienti: ogni endpoint nuovo sta sotto /api/longevity/gestionale/.
  • tests/longevity/anagrafica.test.ts: fuori da lib/longevity/anagrafica.ts e db.ts nessun file .ts/.astro può contenere insieme le stringhe Identity e Longevity. Le pagine importano il layout Longevity → le pagine non nominano mai identity: i nomi arrivano da funzioni di anagrafica.ts che aprono identity da sole (come codicePerUtenteLoggato).
  • src/layouts/Longevity.astro: prop variante: 'referto' | 'gestionale', la seconda dichiarata e non implementata.
  • src/pages/longevity/index.astro: redirect fisso a /longevity/io.

Unità 2a — libreria e API

  1. lib/longevity/anagrafica.ts, funzioni nuove sul singleton privato getIdentityDb(), con getLongevityDb() da db.ts dove serve:
    • elencoClienti(): { client_code, nome, cognome, data_nascita: string|null, ha_accesso: boolean }[] ordinato per cognome, nome
    • clienteDaCodice(code): { client_code, nome, cognome, data_nascita, email, telefono, ha_accesso } | null
    • etaDelCliente(code, alla: string): number | null (via etaAllaData; null senza data di nascita)
    • apriFascicolo(dati): creaCliente(getIdentityDb(), getLongevityDb(), dati), senza user_id (l'accesso è #8)
  2. Nuovo lib/longevity/gestionale.ts (solo longevity, mai identity):
    • campiDelCheckup(db, alla): testAttivi filtrati sul prefisso f_, raggruppati per asse nell'ordine dei pesi; stesso tipo di CampoQuestionario (vista.ts)
    • salvaCheckup(db, { client_code, data, eta: number|null, operatore, misure: MisuraIn[] }) → { ok: true, sessioneId, fuoriRange: string[] } | { ok: false, errore }. Rifiuta: nessuna misura; test_id che non inizia per f_; soggetto inesistente. Validazione prima, poi una transazione: apriSessione tipo checkup, registraMisure fonte manuale, salvaScore(calcolaSessione). Stesso schema di salvaDalForm (vista.ts).
    • sessioniDi(db, code): id, data, tipo, operatore, numero misure, fitness age congelata (o null), ordine data DESC, id DESC
    • misureFuoriRange(db, code): test_id, etichetta, valore, unità, data sessione
  3. API (form POST, risposta 303; errore = 303 alla pagina di partenza con ?errore=<testo>):
    • pages/api/longevity/gestionale/clienti.ts POST: nome, cognome, sesso M|F obbligatori; data_nascita (AAAA-MM-GG, non futura), email, telefono facoltativi → apriFascicolo → 303 a /longevity/gestionale/cliente/<code>
    • pages/api/longevity/gestionale/checkup.ts POST: client_code (formato ^ISL-\d{4}$ e presente in soggetti), data (AAAA-MM-GG, non futura), campi f_*. Un campo vuoto = non misurato. La virgola decimale si accetta ("32,5"). Un campo non numerico → errore, niente sessione. Chiavi che non sono campi attivi f_* ignorate. operatore = locals.user.username, mai dal corpo. eta = etaDelCliente(code, data). → 303 alla scheda cliente con ?salvato=<sessioneId>; se ci sono misure fuori range, anche &fuori=<n>.
      Il client_code qui arriva dal corpo, ed è corretto: il trainer lavora su tutti i fascicoli. Il ruolo lo garantisce il middleware.
      TEST 2a: tests/longevity/gestionale.test.ts
  • salvaCheckup scrive una sessione checkup, fonte manuale, score congelati; dopo un questionario (salvaDalForm) il referto del check-up ha gli assi del questionario e quelli fisici (sessioni combinate da #1)
  • test_id sconosciuto, q_* nel check-up, nessuna misura → ok:false e nessuna riga in sessioni
  • misura fuori range: salvata, marcata, elencata da misureFuoriRange
  • anagrafica: elencoClienti / clienteDaCodice / etaDelCliente (null senza nascita) / apriFascicolo, sui DB :memory: passati come fanno i test esistenti (se il singleton lo impedisce: variante con parametri, come codicePerUtente accanto a codicePerUtenteLoggato)
  • endpoint: una funzione pura di parsing del form (es. misureDalForm(form, campi)) esportata e testata: virgola, vuoto, non numerico, chiave estranea
  • canAccessPath('cliente', '/api/longevity/gestionale/checkup') = false, trainer = true

Unità 2b — pagine

  1. layouts/Longevity.astro variante gestionale: barra laterale sul modello di layouts/StressIndex.astro, voci «Clienti» (/longevity/gestionale) e «Nuovo cliente». CSS in styles/longevity.css, solo classi lg-* e colori ammessi dal test in tests/longevity/pagine.test.ts.
  2. Pagine in pages/longevity/gestionale/:
    • index.astro: tabella clienti (codice, cognome nome, età, accesso sì/no, data e tipo dell'ultima sessione) con link alla scheda
    • nuovo.astro: form verso l'API clienti
    • cliente/[code].astro: anagrafica; pulsante «Nuovo check-up»; referto dell'ultima sessione (riusa refertoDi, Radar, MacroScore); tabella sessioni; misure fuori range (colore --lg-fuori-range); esiti ?salvato/?fuori/?errore mostrati. Codice inesistente → 404.
    • cliente/[code]/checkup.astro: data (default oggi), campi da campiDelCheckup raggruppati per asse, etichetta + unità, input inputmode="decimal", tutti facoltativi.
  3. pages/longevity/index.astro: trainer e admin → /longevity/gestionale; cliente → /longevity/io come oggi. Nel gestionale, un link «Il mio referto» a /longevity/io.
    TEST 2b: estendere tests/longevity/pagine.test.ts (controlli statici come gli esistenti): le pagine del gestionale usano variante="gestionale", non contengono Identity, non interrogano il DB direttamente (.prepare( assente nelle .astro); index.astro smista per ruolo.

NON TOCCARE

ESITO: richiesta RIF: gitea:Adriano/InsanityLab-web#2 COSA: analisi (passo 1). Gestionale trainer in due unità: 2a libreria + API, 2b pagine. Parte dopo #1 (usa calcolaSessione con le sessioni combinate). RAMO: lavoro/goldrake — clone goldrake:~/lavoro/InsanityLab-web, app apps/platforms ## Vincoli verificati - `src/lib/auth.ts` RULES: `/longevity/gestionale` e `/api/longevity/gestionale` già riservati a admin+trainer. Tutto il resto di `/api/longevity/` è aperto ai clienti: **ogni endpoint nuovo sta sotto `/api/longevity/gestionale/`**. - `tests/longevity/anagrafica.test.ts`: fuori da `lib/longevity/anagrafica.ts` e `db.ts` nessun file .ts/.astro può contenere insieme le stringhe `Identity` e `Longevity`. Le pagine importano il layout `Longevity` → **le pagine non nominano mai identity**: i nomi arrivano da funzioni di anagrafica.ts che aprono identity da sole (come `codicePerUtenteLoggato`). - `src/layouts/Longevity.astro`: prop `variante: 'referto' | 'gestionale'`, la seconda dichiarata e non implementata. - `src/pages/longevity/index.astro`: redirect fisso a `/longevity/io`. ## Unità 2a — libreria e API 1. `lib/longevity/anagrafica.ts`, funzioni nuove sul singleton privato `getIdentityDb()`, con `getLongevityDb()` da db.ts dove serve: - `elencoClienti(): { client_code, nome, cognome, data_nascita: string|null, ha_accesso: boolean }[]` ordinato per cognome, nome - `clienteDaCodice(code): { client_code, nome, cognome, data_nascita, email, telefono, ha_accesso } | null` - `etaDelCliente(code, alla: string): number | null` (via `etaAllaData`; null senza data di nascita) - `apriFascicolo(dati)`: `creaCliente(getIdentityDb(), getLongevityDb(), dati)`, senza user_id (l'accesso è #8) 2. Nuovo `lib/longevity/gestionale.ts` (solo longevity, mai identity): - `campiDelCheckup(db, alla)`: `testAttivi` filtrati sul prefisso `f_`, raggruppati per `asse` nell'ordine dei pesi; stesso tipo di `CampoQuestionario` (`vista.ts`) - `salvaCheckup(db, { client_code, data, eta: number|null, operatore, misure: MisuraIn[] })` → `{ ok: true, sessioneId, fuoriRange: string[] } | { ok: false, errore }`. Rifiuta: nessuna misura; test_id che non inizia per `f_`; soggetto inesistente. Validazione prima, poi una transazione: `apriSessione` tipo `checkup`, `registraMisure` fonte `manuale`, `salvaScore(calcolaSessione)`. Stesso schema di `salvaDalForm` (vista.ts). - `sessioniDi(db, code)`: id, data, tipo, operatore, numero misure, fitness age congelata (o null), ordine data DESC, id DESC - `misureFuoriRange(db, code)`: test_id, etichetta, valore, unità, data sessione 3. API (form POST, risposta 303; errore = 303 alla pagina di partenza con `?errore=<testo>`): - `pages/api/longevity/gestionale/clienti.ts` POST: nome, cognome, sesso M|F obbligatori; data_nascita (AAAA-MM-GG, non futura), email, telefono facoltativi → `apriFascicolo` → 303 a `/longevity/gestionale/cliente/<code>` - `pages/api/longevity/gestionale/checkup.ts` POST: `client_code` (formato `^ISL-\d{4}$` e presente in `soggetti`), `data` (AAAA-MM-GG, non futura), campi `f_*`. Un campo vuoto = non misurato. La virgola decimale si accetta ("32,5"). Un campo non numerico → errore, niente sessione. Chiavi che non sono campi attivi `f_*` ignorate. `operatore` = `locals.user.username`, mai dal corpo. `eta` = `etaDelCliente(code, data)`. → 303 alla scheda cliente con `?salvato=<sessioneId>`; se ci sono misure fuori range, anche `&fuori=<n>`. Il `client_code` qui arriva dal corpo, ed è corretto: il trainer lavora su tutti i fascicoli. Il ruolo lo garantisce il middleware. TEST 2a: `tests/longevity/gestionale.test.ts` - salvaCheckup scrive una sessione `checkup`, fonte `manuale`, score congelati; dopo un questionario (salvaDalForm) il referto del check-up ha gli assi del questionario e quelli fisici (sessioni combinate da #1) - test_id sconosciuto, `q_*` nel check-up, nessuna misura → `ok:false` e nessuna riga in `sessioni` - misura fuori range: salvata, marcata, elencata da `misureFuoriRange` - anagrafica: elencoClienti / clienteDaCodice / etaDelCliente (null senza nascita) / apriFascicolo, sui DB `:memory:` passati come fanno i test esistenti (se il singleton lo impedisce: variante con parametri, come `codicePerUtente` accanto a `codicePerUtenteLoggato`) - endpoint: una funzione pura di parsing del form (es. `misureDalForm(form, campi)`) esportata e testata: virgola, vuoto, non numerico, chiave estranea - `canAccessPath('cliente', '/api/longevity/gestionale/checkup')` = false, trainer = true ## Unità 2b — pagine 4. `layouts/Longevity.astro` variante `gestionale`: barra laterale sul modello di `layouts/StressIndex.astro`, voci «Clienti» (`/longevity/gestionale`) e «Nuovo cliente». CSS in `styles/longevity.css`, solo classi `lg-*` e colori ammessi dal test in `tests/longevity/pagine.test.ts`. 5. Pagine in `pages/longevity/gestionale/`: - `index.astro`: tabella clienti (codice, cognome nome, età, accesso sì/no, data e tipo dell'ultima sessione) con link alla scheda - `nuovo.astro`: form verso l'API clienti - `cliente/[code].astro`: anagrafica; pulsante «Nuovo check-up»; referto dell'ultima sessione (riusa `refertoDi`, `Radar`, `MacroScore`); tabella sessioni; misure fuori range (colore `--lg-fuori-range`); esiti `?salvato`/`?fuori`/`?errore` mostrati. Codice inesistente → 404. - `cliente/[code]/checkup.astro`: data (default oggi), campi da `campiDelCheckup` raggruppati per asse, etichetta + unità, input `inputmode="decimal"`, tutti facoltativi. 6. `pages/longevity/index.astro`: trainer e admin → `/longevity/gestionale`; cliente → `/longevity/io` come oggi. Nel gestionale, un link «Il mio referto» a `/longevity/io`. TEST 2b: estendere `tests/longevity/pagine.test.ts` (controlli statici come gli esistenti): le pagine del gestionale usano `variante="gestionale"`, non contengono `Identity`, non interrogano il DB direttamente (`.prepare(` assente nelle .astro); index.astro smista per ruolo. ## NON TOCCARE - schema DB (`db.ts`), `auth.ts` RULES, motore (`motore/*`), `registro.ts`: sono di #1 - `pages/api/longevity/questionario.ts` e il flusso del cliente - `apps/sito/` - creazione di account utente dal gestionale (#8), conferma dei fuori range (#7), import (#3)
Author
Owner

ESITO: fatto (unità 2a)
RIF: gitea:Adriano/InsanityLab-web#2
COMMIT: 8e0b9e4 anagrafica del gestionale · 758463b gestionale.ts · 95ffa08 API clienti e checkup + 17 test (ramo lavoro/goldrake, pushato)
PROVA: npx vitest run → 229 passed; tsc → 0 errori
CONTROLLO FINALE (Opus): diff conforme all'analisi. Deviazioni accettate: varianti *Param per i test; misureDalForm riceve i campi da testAttivi.
PROSSIMO: unità 2b (pagine).

ESITO: fatto (unità 2a) RIF: gitea:Adriano/InsanityLab-web#2 COMMIT: 8e0b9e4 anagrafica del gestionale · 758463b gestionale.ts · 95ffa08 API clienti e checkup + 17 test (ramo lavoro/goldrake, pushato) PROVA: npx vitest run → 229 passed; tsc → 0 errori CONTROLLO FINALE (Opus): diff conforme all'analisi. Deviazioni accettate: varianti *Param per i test; misureDalForm riceve i campi da testAttivi. PROSSIMO: unità 2b (pagine).
Author
Owner

Soluzione

Gestionale trainer. anagrafica.ts: elencoClienti, clienteDaCodice, etaDelCliente, apriFascicolo. lib/longevity/gestionale.ts: campiDelCheckup, salvaCheckup, sessioniDi. API: pages/api/longevity/gestionale/clienti.ts e checkup.ts. Pagine: pages/longevity/gestionale/index.astro, nuovo.astro, cliente/[code].astro (anagrafica, referto, sessioni, misure da verificare), cliente/[code]/checkup.astro; layout Longevity con barra laterale; /longevity instrada admin/trainer al gestionale. Commit 8e0b9e4..948068b, 2c9a74f. Stato: sul ramo lavoro/goldrake (commit 2c9a74f), NON ancora in main né in produzione. PROVA: vitest 259/259, tsc 0 errori, build ok.

**Soluzione** Gestionale trainer. anagrafica.ts: elencoClienti, clienteDaCodice, etaDelCliente, apriFascicolo. lib/longevity/gestionale.ts: campiDelCheckup, salvaCheckup, sessioniDi. API: pages/api/longevity/gestionale/clienti.ts e checkup.ts. Pagine: pages/longevity/gestionale/index.astro, nuovo.astro, cliente/[code].astro (anagrafica, referto, sessioni, misure da verificare), cliente/[code]/checkup.astro; layout Longevity con barra laterale; /longevity instrada admin/trainer al gestionale. Commit 8e0b9e4..948068b, 2c9a74f. Stato: sul ramo lavoro/goldrake (commit 2c9a74f), NON ancora in main né in produzione. PROVA: vitest 259/259, tsc 0 errori, build ok.
Adriano removed the assegnata:goldrake label 2026-09-26 13:12:25 +00:00
Author
Owner

ESITO: fatto
COSA: in produzione su piattaforme.tielogic.xyz, 2026-09-26 13:17 UTC. main 12b2e91.
PROVA: docker compose up -d --build → container Up; migra() → colonne verifica/verificata_da/verificata_at presenti; registro_test f_ = 24; misure 20 prima e dopo.
BACKUP: /opt/docker/insanitylab-platforms/backup/*-pre-gestionale (sqlite3 .backup, integrity ok).

ESITO: fatto COSA: in produzione su piattaforme.tielogic.xyz, 2026-09-26 13:17 UTC. main 12b2e91. PROVA: docker compose up -d --build → container Up; migra() → colonne verifica/verificata_da/verificata_at presenti; registro_test f_ = 24; misure 20 prima e dopo. BACKUP: /opt/docker/insanitylab-platforms/backup/*-pre-gestionale (sqlite3 .backup, integrity ok).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Adriano/InsanityLab-web#2