# Integrazione FTWEB (Forma.Temp): connessione, fasce, presenze

Status: validated (16/09/2026, 6/6 criteri con driver fake; fasi 1-4 implementate sulle assunzioni qui sotto e coperte da test con driver `fake` e
`Http::fake`; collaudo contro `ftweb-col` ancora da fare — la lista di cosa verificare è in
`docs/formatemp/piano-integrazione.md` §5-bis)

## Context

Il documento "FTWEB WS APL v7.0" (con gli Excel incorporati, ora in `docs/formatemp/allegati/`) descrive
un'API REST con autenticazione a sessione (token CSRF + JSESSIONID) e un registro presenze per fascia oraria.
EasyForma ha già l'interfaccia `FormatempClient` con driver `fake` e un placeholder `api`, il modello
`FormatempSync` e le credenziali cifrate su `Company`. Note tecniche in `docs/formatemp/api-ftweb.md`.

## Assunzioni (in attesa delle risposte D1–D7 di `docs/formatemp/piano-integrazione.md` §8)

Ogni assunzione è una scelta di configurazione o di default, non di codice: quando arriva la risposta si cambia un
valore in `config/formatemp.php` o un campo nelle impostazioni azienda.

- **A1 (D1) Ambiente e credenziali.** Due ambienti in `config/formatemp.php` (`collaudo`, `produzione`) con
  `base_url` da env (`FORMATEMP_BASE_URL_TEST`, `FORMATEMP_BASE_URL_PROD`); l'ambiente si sceglie per company
  (`companies.formatemp_environment`, default `collaudo`). Il dominio `SM_SDOMAIN` è un campo opzionale delle
  credenziali: se vuoto non viene inviato; l'utente si invia così com'è (nessuna normalizzazione `RETE\utente`).
- **A2 (D2) Tipo di utenza.** L'utenza configurata dall'ente è un **referente rilevazione presenze** e si usano i
  servizi del cap. 6 (`/services/rs/fasce`, `/visitare/registropresenze/...`, `/visitare/consolida/...`). I servizi
  APL del cap. 3 restano dietro lo stesso client ma non sono usati dal flusso.
- **A3 (D3) Registro FAD.** In FAD sincrona il registro è quello per fascia (`AssPreFasPar_`), identico all'aula
  fisica: `orePresenza` in `HH:MM`, `oraFirmaInizioLezione` = primo ingresso, `oraFirmaFineLezione` = ultima uscita.
- **A4 (D4) Giornate e fasce.** Le crea l'ente in FTWEB; EasyForma le legge e le abbina alle lezioni
  (`FasciaMatcher`: stesso `codiceProgetto`, stessa data, orari con tolleranza configurabile, default 15 minuti).
  Nessuna creazione di giornate da EasyForma (fase 5 fuori perimetro).
- **A5 (D5) Firme.** Nessuna immagine di firma: i campi firma grafica non vengono inviati; si inviano solo gli orari.
- **A6 (D6) Piattaforma FAD.** Nessun censimento automatico; il campo `piattaformaFad`/riferimenti, se richiesto
  dal registro, prende un valore configurabile per company (`companies.formatemp_fad_platform`, default
  "EasyForma (BigBlueButton)").
- **A7 (D7) Discenti.** `tipoDestinatario` e crediti (sicurezza generale, diritti e doveri) sono valori per
  company con default in config (`formatemp.defaults.tipo_destinatario`, crediti a `null` = non inviati); la
  creazione anagrafica (2.29/2.30) è implementata ma facoltativa (`formatemp.create_missing_learners`, default
  false): senza il flag, un discente non trovato in FTWEB produce un errore leggibile nel sync.
- **Sessione.** Token CSRF + `JSESSIONID` cachati per company per 40 minuti (la sessione FTWEB dura 45); su
  `httpCode` 401/403 o `returnCode KO` con messaggio di sessione scaduta si ri-autentica e si ripete una volta.
- **Formati.** Risposte lette dall'envelope `{httpCode, returnCode, okMessages, koMessages, ...}`; ID con prefisso
  come da `docs/formatemp/api-ftweb.md`; date `dd/MM/yyyy`, orari `HH:mm` (formati centralizzati in un unico
  `FtwebFormat`, così un cambio di formato è una riga).
- **Mezzanotte.** `oraFirmaInizioLezione` e `oraFirmaFineLezione` sono `HH:mm` senza data, e `FasciaMatcher` pretende
  che fascia e lezione cadano nello stesso giorno: una lezione a cavallo della mezzanotte (23:00-00:30) non trova
  fascia e si ferma con l'errore "nessuna fascia oraria corrisponde". È un limite accettato finché il collaudo non
  dice se FTWEB ammetta una fascia che attraversa la mezzanotte (§5-bis punto 11 del piano).
- **Sicurezza del trasporto.** Le credenziali sono header di ogni chiamata, quindi entrambi gli ambienti sono `https`.
  Il cleartext è un'eccezione da dichiarare (`FORMATEMP_ALLOW_INSECURE_TEST_URL`), valida solo su collaudo e mai in
  produzione. Il driver `fake` non si attiva per omissione: un valore ignoto è un errore, e in produzione serve
  `FORMATEMP_ALLOW_FAKE_IN_PRODUCTION`.
- **Dati di terzi.** La 6.3 risponde con il registro dell'intero progetto FTWEB: in `formatemp_syncs` va solo un
  riepilogo (involucro, id, conteggi), mai l'anagrafica di discenti fuori dall'aula.

## Requirements

- Client HTTP verso FTWEB con autenticazione, sessione cachata per company, re-auth e un retry.
- Verifica connessione dalle impostazioni azienda.
- Collegamento aula → progetto FTWEB e lezione → fascia oraria (match per data e orari).
- Invio presenze per partecipante (ore, primo ingresso, ultima uscita) e del docente come personale coinvolto.
- Consolidamento della fascia solo su conferma esplicita.
- Ogni chiamata tracciata in `formatemp_syncs` senza credenziali in chiaro.

## Acceptance criteria

1. `POST /settings/company/formatemp/test` con `FORMATEMP_DRIVER=fake` risponde con redirect e toast di successo
   e crea una riga `formatemp_syncs` con `type = connection` e `status = success`.
2. Test unitario con `Http::fake`: la prima chiamata invia `SM_USER` e `SM_PASSWORD` a
   `/services/autenticationcsrftoken`, le successive portano `X-XSRF-TOKEN` e `Cookie: JSESSIONID=...` presi dalla
   risposta; dopo un 401 il client ri-autentica e ripete la chiamata una sola volta.
3. `POST /classrooms/{id}/formatemp/link` (driver fake con tre fasce, di cui due coincidenti con lezioni) salva
   `formatemp_fascia_id` sulle due lezioni e restituisce nel toast il titolo della lezione non collegata.
4. `POST /meetings/{id}/formatemp` per una lezione terminata con presenze 09:03–09:50 e 10:00–10:47 dello stesso
   partecipante invia una presenza con `orePresenza = "01:44"`, `oraFirmaInizioLezione = "09:03"`,
   `oraFirmaFineLezione = "10:47"`; una seconda chiamata non crea righe duplicate nel registro fake.
5. `POST /meetings/{id}/formatemp/consolidate` senza `confirm=1` restituisce 422; con `confirm=1` marca
   `formatemp_consolidated_at` e i successivi `POST /meetings/{id}/formatemp` rispondono 409.
6. Nessuna riga di `formatemp_syncs` contiene la password Forma.Temp in chiaro (asserzione sul JSON di request).

## Out of scope

Creazione progetti/moduli, rendicontazione, PFA, variazioni, iscrizione piattaforma FAD, creazione giornate da
EasyForma (fase 5 facoltativa), firme grafiche.
