Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
- SEO per ambiente:
robots.txtdinamico,sitemap.xmle meta robots (app/routes/main.py,app/__init__.py,app/templates/base.html):GET /robots.txtrisponde in produzione conAllow: /,Disallow: /admin/,Disallow: /operator/,Disallow: /api/eSitemap: https://emptyleg.dev/sitemap.xml, mentre in staging (o in qualsiasi ambiente diverso daproduction) imponeDisallow: /;GET /sitemap.xmlelenca le pagine pubbliche (/,/how-it-works,/credits,/privacy,/terms,/help,/changelog,/contact) e i soli voli in statoAVAILABLE(nessun volo con Coming Soon attivo) e risponde 404 in staging; il layout base emetteindex, followin produzione enoindex, nofollowin staging (IS_STAGING).robots.txtesitemap.xmlsono esenti dal gate Coming Soon (COMING_SOON_PUBLIC_ENDPOINTS), altrimenti i crawler leggerebbero un redirect. Nuova suitetests/test_robots_and_legal_pages.py(9 test); suite totale 94 test, tutti verdi. - Termini e modale di prenotazione: servizi inclusi/esclusi e politica di cancellazione (
app/templates/terms.html,app/templates/flight_public.html): i Termini hanno ora la sezione 4. Servizi inclusi ed esclusi (posto e bagaglio secondo franchigia, equipaggio, carburante, tasse aeroportuali, handling standard, servizi di bordo; esclusi de-icing, catering speciale, trasferimenti terrestri, bagaglio oltre franchigia, pernottamenti) e la sezione 5. Politica di cancellazione e penali con ancoraid="cancellazione", richiamo all'art. 59, comma 1, lettera b, del D.Lgs. 206/2005 e scaglioni 10% / 30% / 50% / 75% / 100% sul prezzo per passeggero; le sezioni successive sono rinumerate (6. Responsabilità, 7. Legge applicabile). Il modale di checkout mostra gli stessi contenuti (inclusi, esclusioni, scaglioni) e il link a/terms#cancellazione. Le nuove stringhe sono tradotte in inglese (%%mantenuto nellemsgid/msgstrper le percentuali).
Changed
- Proposta dei posti nel form volo (
app/templates/operator/flight_form.html): scegliendo un aeromobile il campo Posti propone la capienza massima della flotta quando è vuoto o non valido e riduce il valore solo se supera il limite, senza sovrascrivere una scelta valida; senza aeromobile selezionato il limite resta 500. - Documentazione e i18n: aggiornati
docs/MANUALE_PUBBLICO.md(inclusi/esclusi e scaglioni nel modale, sezioni 4-5 dei Termini),docs/MANUALE_OPERATORI_ADMIN.md(nuova sezione Indicizzazione e SEO per ambiente, ancora#cancellazione, comportamento posti, conteggi 94 test e 266 voci) edocs/development/project-map.md(route SEO, esenzioni del gate, suite di test). Catalogoena 266 voci, 0msgstrvuote, 0 fuzzy, nessuna obsoleta,.po/.mocompilati. - Versione invariata:
VERSIONeversion.txtrestano0.9.6(decisione del 2026-10-16: un ulteriore incremento richiede una nuova decisione esplicita). I cataloghi i18n riportanoProject-Id-Version: EmptyLeg.dev 0.9.6.
Fixed
- Modalità Coming Soon: pulsante «Come Funziona» e link informativi senza effetto (
app/__init__.py): con il gate attivo in produzione anche/how-it-works,/credits,/privacy,/terms,/help(alias/manuale) e/changelogvenivano reindirizzati a/coming-soon, quindi il pulsante Come Funziona della pagina Coming Soon e i link informativi del menu e del piè di pagina rimbalzavano sulla pagina stessa. Le pagine informative e legali pubbliche sono ora esenti dal gate (COMING_SOON_PUBLIC_ENDPOINTS); vetrina, schede volo, richieste di prenotazione e aree riservate restano chiuse. - Layout di
/coming-soon(app/templates/coming_soon.html): la sezione hero usavamin-h-screenep-4 md:p-8dentro il<main>dibase.html(doppio padding e grande spazio vuoto prima del piè di pagina) e l'intera pagina era vincolata amax-w-xl, comprimendo la griglia dei voli dimostrativi in colonne strette. Ora hero centrato amax-w-2xlcon pulsanti in riga su schermi larghi (impilati su mobile) e griglia DEMO a piena larghezza. Nessuna nuova stringa traducibile: catalogo invariato (248 voci, 0msgstrvuote, 0 fuzzy).
Changed
- Prezzi dei voli DEMO espressi come quota per posto (
app/demo_flights.py): le quattro tratte dimostrative mostravano 4.850 / 3.400 / 5.900 / 4.200 EUR, cioè il totale del volo (il prezzo dell'intero velivolo) sotto l'etichetta "/ passeggero" della card e nella tabella del manuale pubblico — fino a sette volte la quota di mercato. I valori sono ora le quote per posto di un empty leg: 810 EUR LIML→LFPB (Phenom 300, 6 posti), 850 EUR LIRA→LFMN (Citation Mustang, 4), 740 EUR LIPE→EGSS (King Air B200, 8), 700 EUR LIPZ→LSGG (Citation CJ2, 6); il totale del velivolo (prezzo × posti: 4.860 / 3.400 / 5.920 / 4.200 EUR) resta invariato, circa il 45% in meno di un charter completo, e le tratte coprono 1,0-2,6 ore di volo (648/486/1155/490 km calcolati daapp/flightcalc.pysulle coordinate reali degli aeroporti). Aggiornatidocs/MANUALE_PUBBLICO.md(tabella DEMO con il totale per velivolo),docs/development/project-map.mde il nuovo test di regressionetest_demo_prices_are_per_seat_quotes_of_an_empty_leg. - Eccezioni al gate Coming Soon documentate e coperte da test: aggiornati
docs/MANUALE_OPERATORI_ADMIN.md(§ Modalità Coming Soon),docs/MANUALE_PUBBLICO.md(§ Sito in preparazione) edocs/development/project-map.md;tests/test_promotion_and_coming_soon.pyverifica che le pagine informative rispondano 200 e che vetrina e aree riservate restino 302 verso/coming-soon. - Versione invariata: questa correzione non incrementa la versione applicativa, che resta
0.9.6(VERSION,version.txt, intestazione dei cataloghi i18n).
[0.9.6] - 2026-10-16
Fixed
- Modalità Coming Soon: HTTP 500 su
/coming-soon(app/templates/coming_soon.html): il template conteneva un blocco header con delimitatori Jinja errati ({%% ... %%}→TemplateSyntaxError: tag name expected) e il vecchio corpo HTML completo (<html>/<head>/<body>/<main>/<footer>) rimasto dopo l'aggiornamento abase.html. Con la modalità Coming Soon attiva in produzione ogni pagina pubblica reindirizza i visitatori anonimi a/coming-soon, quindi l'errore di compilazione restituiva 500 Internal Server Error; ora il template eredita dabase.html(menu, selettore lingua e footer del sito) e la pagina risponde 200 con i voli dimostrativi. - Pubblicazione voli in produzione: HTTP 500 (
app/routes/operator.py):prod_engine,sync_flighteremove_flighterano usati inpromote_flighteunpublish_flightma mai importati, quindiPOST /operator/flight/<id>/promotee/unpublish(disponibili in staging) sollevavanoNameError→ 500 Internal Server Error; aggiuntofrom app.release import prod_engine, remove_flight, sync_flight. - Richieste di eliminazione volo e approvazione prenotazioni: HTTP 500 (
app/routes/operator.py):send_system_emailera usato inflight_delete(percorso operatore → richiesta all'ADMIN) e inbooking_action(approve→ email al cliente) ma non era importato:NameError→ 500 Internal Server Error suPOST /operator/flight/<id>/delete(operatore) ePOST /operator/bookings/<id>/approve. Aggiuntofrom app.mail import send_system_email; l'approvazione perdeva inoltre il cambio di stato, perché l'eccezione scattava prima deldb.session.commit(). - Import mancante latente (
app/routes/operator.py):Airportè usato in_resolve_airport, funzione attualmente non raggiungibile perché_parse_flight_formviene riassegnata aapp.flight_helpers.parse_flight_form(riga 285); il nome è stato comunque reimportato per non lasciare unNameErrornel modulo. - Radice comune: il commit
d8e3711(v0.8.0) ha riscritto l'header di import diapp/routes/operator.pyperdendoAirport,send_system_emaile gli import daapp.release. I ramiorigin/maineorigin/developrisultano identici, quindi le correzioni devono raggiungere la produzione. - Test di regressione (
tests/test_promotion_and_coming_soon.py): compilazione di tutti i template Jinja, rendering di/coming-soonin configurazione di produzione (gate anonimo → 302, pagina → 200), ciclo completopromote(creazione/aggiornamento del volo e valorizzazione diprod_flight_id) eunpublish(rimozione dal DB di produzione) su database SQLite temporanei, senza contattare il DB reale, richiesta di eliminazione volo e approvazione prenotazione (percorsi che inviano email di sistema), pagine del form volo e verifica cheapp/routes/operator.pyimporti i nomi che utilizza. - Interfaccia inglese su
/coming-soon: due stringhe non tradotte (app/translations/):Stiamo per decollareeSiamo in fase di lancio. Presto potrai prenotare i voli executive di ritorno a prezzo dedicato., introdotte con l'aggiornamento del template abase.html, non erano presenti nel catalogoen: con la lingua inglese la pagina mostrava i due testi in italiano. Cataloghi rigenerati con il flusso documentato (pybabel extract -F babel.cfg --project="EmptyLeg.dev" --version=0.9.5→pybabel update -l en --ignore-obsolete --no-fuzzy-matching→pybabel compile): il catalogo passa da 251 a 248 voci (2 stringhe aggiunte, 5 voci del template precedente rimosse perché non più referenziate), 0msgstrvuote e 0 fuzzy,.poe.mosincronizzati eProject-Id-Versionallineato al valore runtime dell'epoca (allora0.9.5, portato a0.9.6con il rilascio: vediChanged). PROD_DATABASE_URLnon valida: HTTP 500 su promozione/rilascio (app/release.py):prod_engine()passava il valore direttamente acreate_engine(), quindi una stringa non utilizzabile (tipicamente l'URL del webhook di deploy incollato al posto della connessione) sollevavaNoSuchModuleErrornon gestito → 500 suPOST /operator/flight/<id>/promote,/unpublish,/admin/promote/<kind>/<id>e sull'approvazione delle eliminazioni. Oraprod_url()rimuove spazi e normalizza gli alias di schema (postgres://,postgresql+psycopg://) eprod_engine()valida lo schema (solopostgresql*/sqlite) sollevando il nuovoProdDatabaseError, che le route coinvolte (app/routes/operator.py,app/routes/admin.py,app/flight_helpers.py) mostrano come avviso all'ADMIN.- Impostazioni non leggibili: HTTP 500 su tutte le pagine pubbliche (
app/settings.py): il gate Coming Soon legge i toggle dalla tabellasite_settingsa ogni richiesta; se la tabella manca (migrazione6565216cd766non applicata) ogni pagina rispondeva 500.feature_enabled()ora esegue il rollback della sessione, registra un warning e ricade sul default di configurazione. - Riferimenti di versione incoerenti:
version.txtconteneva la stringa malformata1 | 0.9.5(residuo del commit di rollback4e1ba55) ed è stato normalizzato e allineato aVERSION; il numero di rilascio di questa correzione è0.9.6. - Test di regressione aggiunti (
tests/test_promotion_and_coming_soon.py): quattro casi sulla validazione diPROD_DATABASE_URL(schemi legacy normalizzati, configurazione assente →None, URL inutilizzabili →ProdDatabaseError, URL valida → engine), due sul percorso route con URL non valida (302 con avviso, nessuna scrittura in produzione) e due sulla tabellasite_settingsassente (default di configurazione usato, sito raggiungibile).
Changed
- Documentazione interna e manuali:
docs/internal.mdè ora annotato come nota storica superata (dichiarava una versione1.2.0annullata, un changelog1.2.0inesistente e citavaREADME.md/docs/templates.md, assenti);docs/MANUALE_OPERATORI_ADMIN.mddocumenta il riallineamento i18n (248 voci,Project-Id-Version=VERSION, limite dipybabel updatesull'intestazione), il fallback dei toggle quandosite_settingsnon è leggibile e l'avviso diPROD_DATABASE_URLnon valida;docs/MANUALE_PUBBLICO.mdedocs/development/project-map.mdsono allineati a0.9.6. - Versione applicativa
0.9.6: decisione del proprietario del 2026-10-16 sul numero di rilascio da adottare per questa correzione.VERSION(letto daconfig.pyinAPP_VERSION, mostrato nel piè di pagina e in/credits) eversion.txtpassano da0.9.5a0.9.6; i cataloghi i18n riportanoProject-Id-Version: EmptyLeg.dev 0.9.6con conteggio invariato (248 voci, 0msgstrvuote, 0 fuzzy,.po/.mosincronizzati). La voce storica[0.9.5]di questo changelog non è stata riscritta (continua a duplicare i paragrafiAdded/Changeddi[0.9.4]).
[0.9.4] - 2026-10-12
Added
- Download dei PDF ufficiali riservato agli utenti registrati: sulle pagine pubbliche
/privacye/termsil pulsante Scarica … (PDF) apre i documenti ufficiali (app/static/docs/privacy-policy.pdf,app/static/docs/termini-condizioni.pdf). Per gli utenti autenticati il download diretto da/static/docs/è consentito; per i visitatori anonimi la richiesta del file viene bloccata da un guardbefore_requestincreate_app(gate_official_documents), con l'avviso Il download dei documenti PDF ufficiali è riservato agli utenti registrati. e il reindirizzamento a/login(parametronextsulla pagina di provenienza, validato conis_safe_next). Le due pagine mostrano inoltre una nota ambra per i non registrati. - Service worker:
app/static/sw.jsesclude i documenti in/static/docs/dalla cache (CACHE_NAMEaemptyleg-static-v5), così i PDF riservati vengono sempre richiesti alla rete e il controllo di autenticazione lato server non è aggirabile tramite cache.
Changed
- Test:
tests/test_booking_consents.pycopre il download riservato (anonimo → 302 verso/loginsu entrambi i PDF, utente autenticato → 200application/pdf); i18n riallineato al 0.9.4 (251 voci,Project-Id-Version0.9.4). - PDF ufficiali riallineati ai testi pubblicati:
app/static/docs/privacy-policy.pdfeapp/static/docs/termini-condizioni.pdfsono stati rigenerati (stile Brand Identity: Tecnodream / Piattaforma Digitale: EmptyLeg.dev): rimossi la numerazione DOCUMENTO 7/8, l'indirizzo di sede, il C.F., il codice SDI e la PEC dal blocco anagrafico (sostituiti dal modulo di contatto del portale,https://emptyleg.dev) e aggiornato il piè di pagina in EmptyLeg.dev by Simone Grandi – P.IVA IT04350021202 – Calderara di Reno (BO) – https://emptyleg.dev.
[0.9.5] - 2026-10-15
Reverted
- Version rollback: Reverted from v1.2.0 to v0.9.5 due to critical issues identified in the 1.2.0 release. All changes from 1.2.0 are reverted, and the project is now aligned with the stable 0.9.5 version.
Added
- Download dei PDF ufficiali riservato agli utenti registrati: sulle pagine pubbliche
/privacye/termsil pulsante Scarica … (PDF) apre i documenti ufficiali (app/static/docs/privacy-policy.pdf,app/static/docs/termini-condizioni.pdf). Per gli utenti autenticati il download diretto da/static/docs/è consentito; per i visitatori anonimi la richiesta del file viene bloccata da un guardbefore_requestincreate_app(gate_official_documents), con l'avviso Il download dei documenti PDF ufficiali è riservato agli utenti registrati. e il reindirizzamento a/login(parametronextsulla pagina di provenienza, validato conis_safe_next). Le due pagine mostrano inoltre una nota ambra per i non registrati. - Service worker:
app/static/sw.jsesclude i documenti in/static/docs/dalla cache (CACHE_NAMEaemptyleg-static-v5), così i PDF riservati vengono sempre richiesti alla rete e il controllo di autenticazione lato server non è aggirabile tramite cache.
Changed
- Test:
tests/test_booking_consents.pycopre il download riservato (anonimo → 302 verso/loginsu entrambi i PDF, utente autenticato → 200application/pdf); i18n riallineato al 0.9.4 (251 voci,Project-Id-Version0.9.4). - PDF ufficiali riallineati ai testi pubblicati:
app/static/docs/privacy-policy.pdfeapp/static/docs/termini-condizioni.pdfsono stati rigenerati (stile Brand Identity: Tecnodream / Piattaforma Digitale: EmptyLeg.dev): rimossi la numerazione DOCUMENTO 7/8, l'indirizzo di sede, il C.F., il codice SDI e la PEC dal blocco anagrafico (sostituiti dal modulo di contatto del portale,https://emptyleg.dev) e aggiornato il piè di pagina in EmptyLeg.dev by Simone Grandi – P.IVA IT04350021202 – Calderara di Reno (BO) – https://emptyleg.dev.
[0.9.3] - 2026-10-11
Fixed
- Preventivatore voli (
app/flightcalc.py) riscritto e verificato: la distanza è ora la corretta distanza ortodromica in km (formula dell'emisenoverso conEARTH_RADIUS_KM = 6371.0e conversione esplicita in radianti conmath.radians), con parametri in ordine rigoroso(lat1, lon1, lat2, lon2),distance_km(origin, destination)simmetrica eNonequando le coordinate non sono valide;flight_minutes()(distanza ÷ km/h × 60 + 20 minuti di overhead),estimate_arrival()edestimate_flight_cost()(costo totale e prezzo/pax inDecimalarrotondati a 2 decimali) rifattorizzati sulla stessa base di calcolo. Riferimento verificato sui dati reali didata/airports.csv: LIEE (Cagliari) → LGAV (Atene) = 1300,8 km / 171 min a 280 kts. ⚠️ Il valore 1.168 km atteso in fase di verifica non è geografico: due metodi indipendenti (emisenoverso R = 6371 km → 1300,8 km; ellissoide WGS84/Vincenty → 1303,9 km) e i dati del catalogo aeroporti concordano, e i test automatici fissano il dato corretto così che un eventuale "aggiustamento" delle coordinate per far tornare 1.168 km fallisca. - Sanificazione delle coordinate degli aeroporti (
coordinates_error()inapp/flightcalc.py):GET /api/flights/estimaterisponde 400 con messaggio dettagliato se latitudine/longitudine di origine o destinazione sono assenti, non numeriche oNaN, scambiate o fuori dai range validi (lat -90/90, lon -180/180), inclusi i valori ancora in gradi/minuti/secondi non convertiti (DMS): mai una stima con dati incoerenti. Il box Preventivatore costo volo mostra il motivo (Stima non disponibile: …).
Added
- Suite di test
tests/(59 test,python -m unittest discover -s tests -t . -v): unit test su distanza ortodromica, tempo di volo, validazione coordinate e stima costo, più un test end-to-end diGET /api/flights/estimatesu database SQLite temporaneo (anonimo → 302, CUSTOMER → 403, OPERATOR/ADMIN → 200 con distanza/tempo/costo attesi, coordinate mancanti o fuori range → 400, aeroporto inesistente → 400). Il DB di sviluppo/produzione non viene mai toccato. - Consensi GDPR in registrazione: la casella Privacy + Termini è obbligatoria (senza spunta il form viene ricaricato con l'avviso Per completare la registrazione devi accettare l'Informativa Privacy e i Termini e Condizioni), la newsletter è facoltativa; l'account salva
accepted_privacy_terms,privacy_consent_at(UTC) emarketing_consent(nuove colonne inoperators). - Pagine legali pubbliche:
GET /privacy(main.privacy) eGET /terms(main.terms) con i testi integrali, i PDF ufficiali (app/static/docs/privacy-policy.pdf,app/static/docs/termini-condizioni.pdf) e i link fissi nel piè di pagina (base.html); nei Termini la sezione 4 Politica di cancellazione e penali ha l'ancora#cancellazione, richiamata dal checkout. - Checkout pre-pagamento
POST /api/bookings: accetta JSON oform-encodedconflight_id,name,email,seats, i tre consensi (accepted_terms,accepted_cancellation_policy,accepted_privacy) econsent_timestampISO 8601 (convertito in UTC); CSRF obbligatorio (X-CSRF-Token,_csrfo corpo) → 403, payload/email/posti/consensi non validi → 400, volo inesistente o nonAVAILABLE→ 404, volo DEMO → 409, successo → 201 conbooking_id,status,seats_requestedeseats_total. Nessun pagamento: la richiesta restaPENDING_OPERATOR_APPROVALe la notifica a[email protected]riporta i consensi con data e ora UTC. - Consensi tracciati sulla prenotazione: nuove colonne
accepted_terms,accepted_cancellation_policy,accepted_privacyeconsent_timestampinbookings(migrazioned18ea904a853,server_default false, applicabile su tabelle già popolate). - Modale di prenotazione (
flight_public.html): il pulsante Prenota ora apre il modale Conferma prenotazione (role="dialog", chiusura conESC, clic sullo sfondo o Annulla, focus sul primo campo) con nome, email, posti (1…disponibili), riepilogo prezzo/passeggero, posti e totale stimato aggiornato in tempo reale, tre caselle di consenso obbligatorie e invio viafetchcon headerX-CSRF-Tokene timestamp ISO; gli errori dell'API compaiono nel modale e la richiesta resta in attesa di approvazione manuale. - Prezzo multiprezzo e capienza (
app/flight_helpers.py): nuove funzioniresolve_price()(price_total→ prezzo unitariototale ÷ postiarrotondato a 2 decimali conROUND_HALF_UP;price_per_passenger→ prezzo già unitario) eaircraft_capacity()(limitemax_seats,Nonese non configurato), usate daparse_flight_form()e dal form operatore: campi sincronizzati Prezzo per passeggero / Totale volo e Posti limitati almax_seatsdell'aereo selezionato (riallineati al cambio aeromobile). - Test ampliati a 59: oltre ai 34 test del preventivatore, la nuova
tests/test_booking_consents.py(25 test) copre i consensi di registrazione e di checkout,POST /api/bookings(CSRF → 403, volo inesistente → 404, DEMO → 409, email non valida → 400, posti oltre la disponibilità → 400, successo → 201 con consensi persistiti e timestamp convertito in UTC), multiprezzo/capienza (resolve_price,ROUND_HALF_UP,aircraft_capacity), le pagine/privacye/termse il modale di prenotazione.
Changed
- Audit delle coordinate aeroporti: verificate tutte le 8.674 righe della tabella
airports— 0 coordinate nulle, 0 fuori range, LIEE/LGAV/LFPG/KJFK identici adata/airports.csv;GET /healthzrisponde{"ok": true, "tables_missing": []}. - Cataloghi i18n riallineati (
.poteen/LC_MESSAGES/messages.po|.mo: 250 voci, 0 vuote / 0 fuzzy,Project-Id-Version0.9.3) con le nuove stringhe di privacy/termini, consensi di registrazione e del modale di prenotazione; manuali aggiornati —docs/MANUALE_OPERATORI_ADMIN.md(formula e default del preventivatore, sanificazione coordinate, multiprezzo e capienza, nuova sezione Consensi GDPR, documenti legali e checkout, paragrafo Test automatici, i18n e checklist di rilascio) edocs/MANUALE_PUBBLICO.md(arrivo stimato, prezzo per passeggero e totale velivolo, consensi in registrazione, modale di prenotazione e nuova sezione Privacy, Termini e consensi) — eVERSIONa 0.9.3. - PDF legali spostati da
app/static/aapp/static/docs/(privacy-policy.pdf,termini-condizioni.pdf): i template li servono conurl_for('static', filename='docs/...').
[0.9.2] - 2026-10-11
Added
- Voli dimostrativi (DEMO) in modalità Coming Soon: nuovo modulo
app/demo_flights.py(DEMO_FLIGHTS,demo_flights(),demo_flight(slug),demo_notice()) con 4 tratte fittizie e sempre future — LIML→LFPB (Embraer Phenom 300), LIRA→LFMN (Cessna Citation Mustang), LIPE→EGSS (Beechcraft King Air B200), LIPZ→LSGG (Cessna Citation CJ2) — mostrate con badge ambraDEMOin vetrina (sezione Voli dimostrativi (DEMO)) e nella pagina/coming-soon. Dati non persistiti: nessuna migrazione, nessun record creato. - Tentativo di prenotazione su volo DEMO: nuova rotta
POST /demo-flight/<slug>/request(main.demo_request, aggiunta all'allowlist del gate Coming Soon) che non crea alcuna prenotazione e restituisce l'avviso «Piattaforma in arrivo. Questo è un volo dimostrativo per mostrare la UX del servizio.»; lato browser la stessa frase compare in un modale informativo (#demo-modal, template_demo_flights.html) con fallback senza JavaScript tramite il submit del form. Anche i voli reali con flagis_demousano ora lo stesso testo. - Voce fissa «Voli Disponibili» (Available Flights) nell'header pubblico (desktop e mobile): primo elemento del menu, porta alla sezione voli della vetrina (
/→ ancora#flights, che sostituisce la vecchia#vetrina). - Riquadro promo B2B collassabile: la sezione
#b2b-promodella vetrina si riduce automaticamente dopo 8 secondi (data-collapse-after) in una card compatta (#b2b-promo-mini, badgeB2B+ pulsante Scopri i vantaggi); il clic riapre il contenuto completo (#b2b-promo-full) e qualsiasi interazione annulla la riduzione automatica. - Banner di stato in vetrina: con la modalità Coming Soon attiva la vetrina mostra un avviso ambra che segnala l'anteprima con le tratte dimostrative.
Changed
- Menu backoffice riorganizzato in macro-categorie (dropdown): le voci riservate sono ora raggruppate nei macro
nav_group()/nav_item()dibase.html, identici su desktop (pannellodata-nav-panelancorato, chiusura al clic esterno/ESC, nessun hover richiesto) e su mobile (accordion nel pannello#mobile-nav): ✈️ Voli & Operazioni (Lista Voli, Prenotazioni, Nuovo Volo, Eliminazioni Pending solo ADMIN), 🛩️ Flotta & Azienda (I Miei Aerei, Profilo AOC / Dati Fatturazione, Collegamento Utenti solo ADMIN), 📊 Commerciale & Promo (Listino Prezzi B2B, Promozioni Social One-Shot, Manuale Operatori) e ⚙️ Admin Tools (solo ADMIN: Gestione Utenti, Operatori (COA), Impostazioni Globali, Sync Staging → Prod e Rilascio Produzione in Staging). Imposta password ed Esci restano voci fisse; le route mantengono@roles_required. operator/pricing.html: la sezione Pacchetti Social Promo ha ora l'ancora#promo(usata dal menu Promozioni Social One-Shot).coming_soon.html: layout riscritto (hero + sezione voli dimostrativi con CTA Guarda i voli dimostrativi/Contattaci e selettore lingua).- Cataloghi i18n riallineati a 195 voci (
.pot,en/LC_MESSAGES/messages.po|.mo, EN completo, 0 vuote / 0 fuzzy), incluse le nuove etichette di menu e i voli demo (nomi di città tradotti con markerN_()); manualidocs/MANUALE_PUBBLICO.mdedocs/MANUALE_OPERATORI_ADMIN.mdaggiornati (nuova sezione Internationalizzazione (i18n));VERSIONa 0.9.2.
[0.9.1] - 2026-10-11
Added
- Menu collassabile (hamburger) responsive: l'header di
base.htmlè ora composto da barra desktop e pannello mobile#mobile-nav(chiuso di default, aperto dal pulsante#nav-toggleconaria-expanded/aria-controlsaggiornati via JS, icone hamburger/chiudi e fallback<noscript>); le voci di navigazione sono generate una sola volta dal macronav_items()e riusate su desktop e mobile. - Voce fissa «Per Operatori» + modal B2B: nuovo pulsante nell'header (desktop e mobile) che apre il modal
#b2b-modalcon il form Richiedi info e prezzi (_b2b_form.html); il riquadro «Sei un operatore o un broker?» della vetrina e la sezione B2B di/contactcompaiono esclusivamente per utenti non autenticati. Al primo accesso dell'utente non autenticato alla vetrina il modal si apre automaticamente (controllolocalStorage, chiaveemptyleg.b2b.welcome) e si chiude da pulsante, click sullo sfondo o tastoESC. - Pagina
/changelog: nuova rottadocs.changelogche rendeCHANGELOG.md(markdown → templatechangelog.html); il numero di versione nel piè di pagina e in/creditsè ora un link a questa pagina. - Vetrina: fascia B2B/B2C ad alto impatto: hero in testa alla vetrina con headline, CTA Guarda i voli disponibili / Per Operatori e due card evidenziate — Zero commissioni per il cliente finale (B2C) e €0/anno di canone, Piano Free per i COA (B2B, solo visitatori non autenticati); sezione B2B a fondo pagina ridisegnata in due colonne con elenco vantaggi e box prezzo.
Changed
credits.html: intestazione aziendale pulita «EmptyLeg.dev by Tecnodream di Simone Grandi - P.IVA: 04350021202», logostatic/icons/logo.png, chip versione collegata a/changeloge link rapidi a Cronologia, Come Funziona e Contatti.- Header: badge di stato in testata confermato come Official (su mobile abbreviato a chip), header sticky con sfondo sfumato;
bodyflessibile (flex flex-col) emaina piena altezza per il piè di pagina sempre in fondo. - PWA verificata:
static/manifest.json(name/short_name/start_url/scope/display/colori/icone 192-512) estatic/sw.js(cacheemptyleg-static-v4) collegati dal layout base (rel="manifest", registrazione/sw.jsservita conService-Worker-Allowed: /). - Cataloghi
app/translations/riallineati (.pot,en/LC_MESSAGES/messages.po|.mo: IT come lingua sorgente, EN completo, 0 fuzzy);docs/MANUALE_PUBBLICO.mdedocs/MANUALE_OPERATORI_ADMIN.mdaggiornati;VERSIONa 0.9.1.
[0.9.0] - 2026-10-11
Added
- Impostazioni globali (
/admin/settings, solo ADMIN): pagina unica con i tre interruttori di stato — Coming Soon, Pagamenti live (ENABLE_LIVE_PAYMENTS) e Promozioni (ENABLE_PROMOTIONS) — persistiti insite_settingse con fallback sulle variabili d'ambiente. Nuovo moduloapp/settings.py(feature_enabled/set_feature/coming_soon_enabled/live_payments_enabled/promotions_enabled) e nuovi default inconfig.py. - Collegamento multi-utente → singolo operatore (COA): il campo Operatore collegato del form utente elenca ora solo gli account Operatore (
operator_accounts()inapp/flight_helpers.py), non più tutti gli utenti; più utenti possono essere collegati allo stesso operatore (linked_operator_id, FKoperators.id). Nuova azione rapidaPOST /admin/users/<id>/linkper associare/rimuovere il COA dalla lista utenti. - Creazione operatore (COA) da backoffice (
GET|POST /admin/operators/new, templateadmin/operator_new.html): ragione sociale, email di login, password iniziale, accredito immediato e anagrafica completa (P.IVA/CF/SDI/PEC/indirizzo/IBAN/fee/penale). - Promozione estesa Staging → Produzione per Operatori (COA), Utenti abilitati e Schede Aerei, non solo i singoli voli: nuova pagina
/admin/promote(solo Staging) e rottePOST /admin/promote/<kind>/<id>; nuove funzionisync_user/promote_operator/promote_user/promote_aircraftinapp/release.py. Pulsanti Pubblica / Aggiorna in Operatori, Utenti e Flotta. - Flag promo per singolo volo: colonne
empty_legs.is_promoted,promo_package(BASIC/STANDARD/PREMIUM) epromo_paid(migrazione674ebbb9ad71); blocco promozione nei form volo (visibile quando le promozioni sono attive),promo_paidimpostabile solo dall'ADMIN e indicazione dello stato nel dettaglio volo. I flag vengono propagati in produzione dalsync_flight. - SEO/OpenGraph/Schema.org: blocco
head_metasovrascrivibile inbase.html(description, canonical,og:*,twitter:*) e dati strutturati JSON-LDWebSite+ItemListdiFlight/Offerin vetrina eFlight/Offernel dettaglio volo. - Lingua interfaccia nel backoffice operatore: il selettore IT | EN è presente nell'header di tutte le pagine riservate ed è replicato nel blocco Lingua interfaccia in cima a Profilo operatore (
/operator/profile); la scelta è salvata in sessione (GET /set-language/<lang>) e vale per tutto il backoffice. Cataloghiapp/translations/riallineati: 145 voci, 0 fuzzy,.po/.moEN completi (IT è la lingua sorgente/msgid).
Changed
- Canone del Piano Flotta per flotte da 10 o più aerei aggiornato a €210 (era €250) nella pagina
/operator/pricing. sw.js(cacheemptyleg-static-v4) precarica ancheapple-touch-icon.png; verificata la coerenza dimanifest.json(name/short_name/start_url/display/colori/icone 192-512).- Manuali e
CHANGELOG.mdaggiornati;VERSIONa 0.9.0.
[0.8.2] - 2026-10-10
Added
- Conferma dell'indirizzo email alla registrazione: campi
email_verified/email_verified_atsuOperator(migrazionec1a5e7b9d3f2), token firmato conitsdangerous(generate_email_token/parse_email_tokeninapp/security.py, saltemail-verify, scadenzaEMAIL_VERIFICATION_MAX_AGE, default 48h), email di conferma dal mittente[email protected](send_verification_emailinapp/mail.py) e pagina esitoGET /verify-email/<token>(auth.verify_email) con stati verificata / già verificata / non valida o scaduta. - Reinvia email di conferma: banner nella parte alta della pagina per gli account non ancora verificati, con
POST /resend-verificationprotetto da CSRF (auth.resend_verification). Gli account creati via Google risultano automaticamente verificati al primo accesso (indirizzo già confermato da Google).
Changed
- Template
auth/verify_email.htmle messaggi di registrazione/verifica internazionalizzati IT/EN; cataloghiapp/translations/(messages.pot,en/LC_MESSAGES/messages.po|mo) rigenerati e riallineati.
[0.8.1] - 2026-10-10
Added
- Multilingua IT/EN con Flask-Babel:
LANGUAGES,BABEL_DEFAULT_LOCALE,BABEL_DEFAULT_TIMEZONEeBABEL_TRANSLATION_DIRECTORIESinconfig.py; selettore di lingua IT | EN nell'header (e nella pagina Coming Soon) tramiteGET /set-language/<lang>, salvato in sessione con fallbackAccept-Language; attributo<html lang>dinamico ecurrent_localeesposto ai template. Cataloghi inapp/translations/(messages.pot,en/LC_MESSAGES/messages.po|mo) estratti conbabel.cfg. - Form B2B Richiedi info e prezzi (Ragione Sociale, Referente, Email/PEC, Telefono, N° Aerei, messaggio opzionale) in
/contacte nella sezione B2B della vetrina pubblica (form_type=operator_info): notifica a[email protected]conReply-Todel richiedente e conferma al richiedente dal mittente[email protected](send_operator_info_request/send_operator_info_confirmationinapp/mail.py). - Pagina Prezzi per operatori autenticati
/operator/pricing(solo OPERATOR/ADMIN, link nel menu): Piano Free (7% e nessun canone), Piano Flotta (5% + canone mensile scalare €400/€350/€290/€250 per fascia di flotta) e pacchetti Social Promo (Basic €50, Standard €100, Premium €200), con la commissione attiva sul proprio account.
Changed
/how-it-worksriscritta in due sezioni — lato cliente (zero costi aggiuntivi di servizio: si paga solo ed esclusivamente il prezzo puro del volo) e lato operatore (costi fissi azzerati con il piano free, totale controllo operativo, monetizzazione delle tratte a vuoto) — senza alcuna cifra o percentuale riservata.- Viste pubbliche internazionalizzate (vetrina, dettaglio volo, contatti, credits, coming soon, menu e footer del backoffice) e flash messages delle rotte pubbliche;
main.set_languageè esente dal gate Coming Soon.
[0.8.0] - 2026-10-10
Added
- Anagrafica operatori
/admin/operators(solo ADMIN): accreditois_approved(solo accreditati nel menu voli),operational_email, penalecancellation_penalty_percent(default 15%), fee personalizzata; creazione volo admin/admin/flight/newcon scelta operatore accreditato. - Flotta
Aircraft(/operator/fleet): marca/tail unica, modello, posti max, attivo, scadenza abbonamento; nei form volo solo aerei attivi dell'operatore scelto, posti entro capienza. - Prenotazioni ad approvazione manuale: nessun pagamento automatico, stati
PENDING_OPERATOR_APPROVAL→APPROVED_PENDING_PAYMENT(il cliente può saldare) /REJECTED; gestione da/operator/bookings; cancellazione EmptyLeg con penale registrata (penalty_percent/penalty_amount). - Helper condivisi
app/flight_helpers.py(parsing voli/flotta, owner, eliminazione) usati da operator e admin. - Prezzo sempre per passeggero con totale prezzo × posti; autocomplete aeroporti
/api/airports/searchnei form. - Modifica dati operatore da ADMIN (
/admin/operators/<id>/edit, alias/admin/operator/<id>): ragione sociale, P.IVA/codice fiscale, indirizzo/PEC, commissionecustom_fee_percent, email operativaoperational_email, penalecancellation_penalty_percent, statois_approved— con validatori e aggiornamento DB. - Preventivatore costo volo (
/api/flights/estimate+ pulsante "Calcola Stima Volo" nei form/operator/flight/newed edit): distanza GPS dalla tabellaAirport, tempo stimato daAircraft.cruise_speed_kts, costo totale daAircraft.hourly_ratee prezzo/pax suggerito; nuovi campi flotta (velocità, costo orario). - Gestione utenti estesa (
/admin/users→/admin/users/new,/admin/users/<id>/edit,/admin/users/<id>/password): creazione/modifica/disattivazione per ruoli ADMIN/OPERATOR/USER, reset password, associazione/dissociazione aOperator(linked_operator_id); dettagli anagrafici (telefono, referente, email operativa, città) ed email di login modificabile dall'admin. - Password post Google OAuth2: flag
has_custom_password(True sepassword_hashlocale presente), banner/modale e pagina/set-passwordal primo accesso senza password locale per abilitare anche il login email+password.
[0.7.0] - 2026-10-10
Added
- Flag
is_demosui voli (solo ADMIN): visibili in vetrina e nel dettaglio, la richiesta di prenotazione è bloccata con messaggio DEMO. - Richiesta di prenotazione dal dettaglio volo pubblico (email all'admin).
- Eliminazione voli: l'operatore richiede (
PENDING_DELETION, volo nascosto e email ad [email protected]), l'admin elimina direttamente o approva da/admin/pending-deletions. - "Rimuovi dalla Produzione" per il singolo volo; "Pubblica / Aggiorna in Produzione" evidenziato.
- Rilascio: merge automatico
develop→mainvia API GitHub (GITHUB_TOKEN,GITHUB_REPOSITORY) dopo migrazioni e aeroporti; in tal casoPROD_DEPLOY_HOOK_URLnon è obbligatoria.
[0.6.3] - 2026-10-10
Changed
- Il rilascio Staging → Produzione non sincronizza più operatori e voli: allinea solo migrazioni e aeroporti, poi chiama il webhook di deploy. I dati reali di produzione non vengono toccati.
[0.6.2] - 2026-10-10
Changed
- Rilascio Staging → Produzione:
PROD_DATABASE_URLePROD_DEPLOY_HOOK_URLsono entrambe obbligatorie e lette daos.environ; se mancano,/admin/releasemostra istruzioni per Render e disabilita "Pubblica in Produzione" senza errori. Il webhook di deploy non è più opzionale.
[0.6.1] - 2026-10-09
Fixed
- Il seeding aeroporti esegue upsert PostgreSQL a batch per evitare statement troppo grandi e riporta il numero effettivo di record sincronizzati.
[0.6.0] - 2026-10-09
Added
- Rilascio automatizzato dell'intero portale da
/admin/release(solo ADMIN, solo staging): migrazioni, aeroporti, operatori e voli, webhook di deploy opzionale (PROD_DEPLOY_HOOK_URL). - Pagina pubblica di dettaglio volo
/flight/<id>e route/favicon.ico. - Calcolo automatico dell'arrivo stimato (distanza ortodromica) quando non inserito;
Airport.latitude/longitude,EmptyLeg.arrival_is_estimated. - Operatore mostrato in dashboard e nel dettaglio volo.
Changed
- La vetrina mostra città/nome aeroporto sotto il codice ICAO.
- Icone e logo rigenerati dagli asset originali, aggiunta
apple-touch-icon.png, URL versionati e cache del service workerv3. - La promozione del singolo volo riusa la logica condivisa di
app/release.py.
[0.5.0] - 2026-10-09
Added
- Modello
Airport(ICAO/IATA/nome/città/paese), datasetdata/airports.csv(~8.600 aeroporti, fonte OurAirports), scriptseed_airports.pye APIGET /api/airports/search?q=. - Autocomplete aeroporti nei form volo.
- Profilo operatore
/operator/profilecon dati di fatturazione, contatti e incasso; campocustom_fee_percentmodificabile solo dall'ADMIN (da Utenti). - CRUD voli:
/operator/flight/new, dettaglio, modifica, annullamento ed eliminazione bozze; stati DRAFT/AVAILABLE/BOOKED/CANCELLED/EXPIRED; arrivo stimato. - "Pubblica / Aggiorna in Produzione": aggiorna il volo già pubblicato (
prod_flight_id).
Changed
price_totalsostituito daprice_per_passenger(prezzo per singolo passeggero) con proprietàprice_total_flight; la migrazione converte i dati esistenti.- Fee del volo = fee personalizzata dell'operatore se presente, altrimenti
PLATFORM_FEE_PERCENT.
[0.4.0] - 2026-10-09
Added
- Modalità Coming Soon per la produzione: pagina
/coming-soon, controllobefore_request, toggle admin in Utenti, tabellasite_settings, variabileCOMING_SOON. - Pagine
/how-it-works,/creditse/contactcon link nel footer. - Form di contatto con invio SMTP (
app/mail.py): contatti ->[email protected], conferme[email protected], notifiche[email protected]. - Footer aziendale (Tecnodream, P.IVA 04350021202).
Changed
- Rotta di promozione ora
POST /operator/flight/<id>/promote; pulsante "Pubblica in Produzione" e messaggio di conferma aggiornati. index.htmlestendebase.html(header, logoh-8e footer condivisi).
[0.3.1] - 2026-10-09
Added
- Favicon dinamica per ambiente (
favicon-staging.png/favicon-prod.png) e logo nell'header. - Partial
_head.htmlcondiviso (manifest, meta PWA, favicon). idnel manifest PWA; service worker v2 con precache di logo e favicon.
Changed
- Il service worker è registrato anche nelle pagine che estendono
base.html.
[0.3.0] - 2026-10-09
Added
- RBAC a 3 ruoli: ADMIN, OPERATOR, CUSTOMER (
roles_required). - Pagina admin
/admin/usersper cambiare ruolo e attivare/disattivare account. - Manuali
docs/MANUALE_PUBBLICO.md(/help,/manuale) edocs/MANUALE_OPERATORI_ADMIN.md(/dashboard/help). - Istruzioni operative in
.github/copilot-instructions.md. - Registrazione con scelta Cliente/Operatore.
Changed
- Ruolo predefinito dei nuovi utenti (anche Google): CUSTOMER; la dashboard richiede OPERATOR o ADMIN.
APP_VERSIONletta daconfig.py.- Migrazione:
rolenon nullo con default CUSTOMER; i ruoli esistenti vengono allineati.
[0.2.1] - 2026-10-09
Changed
- La promozione Staging -> Produzione è riservata all'admin (visibile solo in staging; in produzione la rotta risponde 404).
Added
- Login con Google OAuth2 (Authlib): rotte
/auth/googlee/auth/google/callback. - Campi
google_id,is_adminerolesuoperators; l'emailADMIN_EMAILottiene automaticamente il ruolo ADMIN.
[0.2.0] - 2026-10-09
Added
- feat: aggiunto sistema di autenticazione e gestione ambiente Staging/Prod.
- Registrazione/login/logout operatori (Flask-Login, password hash Werkzeug), predisposizione Google OAuth2.
- Dashboard operatore per inserire e visualizzare Empty Leg.
- Promozione voli da Staging a Produzione tramite
PROD_DATABASE_URL(solo con FLASK_ENV=development).
Changed
- Il badge ambiente nella vetrina mostra "Staging Mode" o "Official Vetrina" in base a FLASK_ENV.
- Migrazione: campo
password_hashsuoperators.
[0.1.0] - 2026-10-09
Added
- Flask-Migrate integration for database migrations.
- Progressive Web App support: web manifest, service worker and mobile meta tags.
- Project versioning (
VERSIONfile) and footer with credits. - Initial database migration.