# DPass 61D — Prenotazioni delle strutture

Base richiesta: progetto locale aggiornato a 61B v2 + 61C + 61C1, come confermato in chat.
Il locale `~/progetti/dpass` rimane la sorgente ufficiale. Nessuna pubblicazione automatica.

## Applicazione locale

Scaricare `dpass_patch_61D_prenotazioni_strutture.zip` in `C:\Users\massi\Downloads`.
In Ubuntu / WSL incollare l'intero blocco, comprese le parentesi:

```bash
(
  set -e
  cd ~/progetti/dpass
  PATCH_DIR="$(mktemp -d /tmp/dpass_patch_61D.XXXXXX)"
  unzip -q /mnt/c/Users/massi/Downloads/dpass_patch_61D_prenotazioni_strutture.zip -d "$PATCH_DIR"
  bash "$PATCH_DIR/apply_patch.sh" "$PWD"
)
```

Verifica prima che il `.env` del locale punti al database LOCALE da aggiornare. L'installer
rifiuta un `.env` con `APP_ENV=production`. La cartella temporanea viene conservata per i controlli.
Per il solo controllo preventivo: `bash "$PATCH_DIR/apply_patch.sh" "$PWD" --check`.

L'installer verifica integrità, marker 61C1 e hash, salva un backup dei sorgenti,
applica i file, esegue i controlli isolati e avvia **solo** la migration:
`2026_09_19_180000_create_facility_bookings`.
Non esegue tutte le migration arretrate; 61A e 61B devono già essere migrate nel locale.
Non modifica `.env`, `vendor`, asset compilati, cron, NPM o Composer.

Esito: `[61D] PATCH 61D APPLICATA E VERIFICATA.`

In assenza di `pdo_sqlite` l'installer segnala che i test DB isolati non sono stati eseguiti;
i controlli senza DB restano attivi. Non usa il DB applicativo per i test: l'unico accesso
applicativo voluto è la migration locale, al termine dei controlli.

## Impostazione operativa

In Configurazioni → Comuni abilitare Gestione Strutture. In Aree e strutture scegliere:
struttura attiva, pagamento anticipato, tariffe attive e dati d'accesso.
I nuovi campi sono **Limite giornaliero prenotazioni** e **Minuti riservati al pagamento**.
Limite vuoto: illimitato; zero: chiude nuove vendite; numero positivo: tetto per data di ingresso.
Il valore iniziale è vuoto: impostare il tetto prima di aprire le vendite quando necessario.
Tempo pagamento predefinito 20 minuti, configurabile da 5 a 120, mai oltre la fine della validità.
Il limite non viene ricavato dal Numero di posti monitorati e non lo modifica.

L'utente apre Strutture di sosta, sceglie la struttura e la data/ora, aggiorna le tariffe
per quella data, inserisce la targa e ottiene il riepilogo. Nessuna mappa/geolocalizzazione.
Le tariffe orarie riusano il calcolatore corrente, massimali, durata massima e cortesia.
Le tariffe fisse riusano il motore di validità e verificano anche giorni/festivi dell'ingresso.
Il titolo emesso da questo percorso è una ParkingSession collegata alla prenotazione,
anche per tariffe fisse: non viene creato anche un ParkingPass, evitando titoli duplicati.

Le modalità dipendono dal Comune e dalla configurazione esistente:
- Borsellino del Comune, con addebito atomico e saldo bonus/cash.
- PayPal diretto, senza ricarica; solo con pagamento diretto abilitato e un profilo PayPal
  attivo, predefinito per pagamento diretto, uso diretto/both, valuta e limiti compatibili.
  In produzione il profilo deve avere Webhook ID. Restano i callback PayPal già presenti.

Questa 61D collega **PayPal diretto**, non Fabrick diretto per le strutture.
Fabrick e gli altri provider non vengono modificati; una ricarica tramite provider già disponibile
può alimentare il borsellino secondo il comportamento precedente.
Le tariffe gratuite confermano senza gateway e senza addebito.

## Riepilogo contabile

Gli ingressi confermati con data futura compaiono nei servizi venduti alla data del pagamento,
anche mentre il titolo è programmato. Il pagamento PayPal è un incasso diretto del servizio,
non una ricarica. I pagamenti da verificare senza titolo restano negli incassi finanziari,
ma non generano un servizio venduto fittizio. Le sessioni programmate storiche non collegate
alle nuove prenotazioni conservano il comportamento precedente.

## Quota, titoli e anomalie

La quota è per singola struttura e per **giorno locale dell'inizio validità**, non giorno di acquisto.
Anche una validità su più giorni consuma un solo ingresso nella data iniziale.
Una sosta che termina non restituisce il numero di ingresso venduto.
Confermate + riserve di pagamento non scadute + titoli storici eleggibili della struttura
consumano quota. Il titolo generato da una prenotazione non è conteggiato una seconda volta.
Non sono conteggiate le riserve scadute, annullate, fallite o i pagamenti in verifica senza titolo.
Abbonamenti di accesso non diventano presenze fisiche e non consumano questa quota di prenotazioni.

Il controllo di vendita serializza le operazioni sulla riga dell'area, con letture bloccanti
entro transazione; richiede InnoDB. Idempotenza mediante token d'acquisto e legami univoci
tra prenotazione, transazione e titolo. HTTP PayPal avviato dopo commit della riserva.
Un errore di rete non annulla automaticamente una transazione dall'esito sconosciuto:
si riprende lo stesso ordine dalla prenotazione, senza creare un secondo pagamento.

Se il pagamento arriva dopo scadenza/annullamento, oltre il limite o dopo disabilitazione,
l'incasso viene registrato ma non viene emesso un ingresso. Stato **Pagamento da verificare**,
visibile nel registro: niente accredito automatico al wallet, niente rimborso automatico.
Il gestore verifica la transazione sul provider e gestisce l'eventuale rimborso da lì.
L'utente vede chiaramente che il pagamento non equivale a un titolo valido.
Il registro in questa fase segnala le anomalie: non offre un pulsante di emissione forzata
né una procedura automatica di rimborso/chiusura dell'anomalia.

Non occorre un nuovo cron per la quota: il conteggio esclude immediatamente le riserve scadute.
La pulizia dello stato e della cortesia riservata avviene alle letture operative/nuovi acquisti.
La conferma online continua a richiedere un ritorno PayPal verificabile oppure il webhook:
se entrambi mancano, l'ordine non viene riconciliato da un nuovo processo periodico in questa patch.

Le mie prenotazioni contiene conferme, tentativi, scadenze e link per riprendere il pagamento.
In admin: Gestione Strutture → Prenotazioni strutture, con data, struttura, targa, stato e CSV.
In dashboard il riepilogo degli ingressi odierni è separato dai posti stimati liberi.
I colori provengono dall'Identità e Branding. Le nuove pagine operative sono in italiano.

I percorsi storici di acquisto soste (web/mobile) escludono le strutture anche lato server.
Le prenotazioni non si prorogano/interrompono dai vecchi comandi delle soste: non si applicano
rimborsi residui orari a un ingresso fisso. Le API di verifica targa 61B non sono modificate.
Sono fuori fase: rilevazione presenze reali, apertura fisica sbarre, pagamento all'uscita,
rimborso automatico e cancellazione commerciale di una prenotazione già confermata.

## Controlli locali prima del rilascio

Usare una struttura di test, limite 2 e due targhe differenti. Confermare una prenotazione con
wallet e una con PayPal sandbox; la terza sullo stesso giorno deve essere respinta senza addebito.
Cambiare data: la quota deve essere indipendente. Verificare l'ordine PayPal dalla console sandbox,
compreso webhook con browser chiuso; confermare un solo incasso e un solo titolo.
Verificare anche annullamento, scadenza, ripresa e un pagamento tardivo, che deve restare senza titolo.
Controllare saldo e transazioni, filtro/CSV admin, brand e separazione dalla sosta su strada.
Ripetere login/cambio Comune con due utenti e controllare che ciascuno veda soltanto i propri dati.
Confrontare ricevuta e API ingresso/uscita con il periodo acquistato, inclusi i titoli programmati.

Prima della produzione serve anche un test di concorrenza **su MySQL/InnoDB reale** con due sessioni
utente sull'ultimo ingresso. I test SQLite non dimostrano il comportamento dei lock InnoDB.

## Backup e gestione errori

Backup sorgenti: `storage/app/patch-backups/dpass_patch_61D_<timestamp>_<id>`.
Marker: `storage/app/patches/dpass_patch_61D.json`.
Se fallisce un test PRIMA della migration, viene tentato il ripristino dei file.
Se la migration viene avviata e fallisce, i sorgenti e il backup vengono conservati; il marker
rimane `migration_pending`. Conservare l'errore e correggere i prerequisiti; la stessa patch
può riprendere senza sovrascrivere cambiamenti successivi non riconosciuti.
NON eseguire `migrate:rollback` e NON cancellare prenotazioni. La migration è additiva e il suo
`down()` si arresta intenzionalmente. Un rollback del codice dei callback dopo nuovi incassi
può compromettere la riconciliazione: `rollback.sh` lo impedisce una volta avviata la migration.

## Produzione

**Non applicare adesso lo SQL 61B separato né lo SQL cumulativo.**
Il cumulativo presente nello ZIP e nel progetto locale comprende 61A, 61B e 61D.
La copia SQL 61B originale rimane invariata come riferimento.
Seguire `PIANO_RILASCIO_CPANEL_61D.md` dopo il collaudo locale e su una copia del database reale.
