Organizzazioni e Festival: Strutture Complesse per Eventi Complessi
Quando un singolo utente non basta più, entrano in gioco le organizzazioni. In Play The Event un'organizzazione può essere un'azienda o un'associazione, ciascuna con il proprio profilo, i propri membri e i propri ruoli. E quando un'organizzazione vuole gestire eventi su più giorni, con sale, stand e palinsesti articolati, entra in gioco il modulo Festival.
Questo articolo esplora i due sotto-domini che permettono a Play The Event di scalare dalla gestione di un singolo evento alla gestione di manifestazioni multi-giornata con decine di attori coinvolti.
Cosa Troverai in Questo Articolo
- I due tipi di organizzazione: Azienda e Associazione
- Membership e ruoli gerarchici con workflow bidirezionale
- Inviti via email per utenti non ancora registrati
- Il Festival come Aggregate Root per eventi multi-giorno
- Giornate, sale, stand e la loro orchestrazione
- Sistema di prenotazione stand con ciclo di vita
- Il Value Object Money per la gestione del budget
Due Tipi di Organizzazione
La piattaforma supporta quattro tipi di account tramite l'enum TipoAccount: INDIVIDUO, AZIENDA, ASSOCIAZIONE e SUPER_AMMINISTRATORE. I primi tre sono selezionabili in fase di registrazione. Quando un utente sceglie AZIENDA o ASSOCIAZIONE, il sistema richiede la compilazione di un profilo aggiuntivo specifico.
TipoAccount
├── INDIVIDUO → Nessun profilo aggiuntivo
├── AZIENDA → Richiede ProfiloAzienda
├── ASSOCIAZIONE → Richiede ProfiloAssociazione
└── SUPER_AMMINISTRATORE → Non selezionabile in registrazione
ProfiloAzienda
Un'azienda ha un profilo ricco di dati fiscali internazionali: ragione sociale, paese (codice ISO 3166-1 alpha-2), VAT Number, Tax ID e numero di registrazione imprese. Include anche i dati di contatto (email aziendale, PEC), la sede legale tramite il Value Object Indirizzo, il referente aziendale e il settore di appartenenza.
@Entity
public class ProfiloAzienda {
private Long id;
private User user; // Relazione 1:1
// Identificazione
private String ragioneSociale; // "TechCorp S.r.l."
private String paese; // "IT" (ISO 3166-1 alpha-2)
private String vatNumber; // "IT12345678901"
private String taxId; // Codice Fiscale
private String companyRegistrationNumber;
// Contatti
private String emailAziendale;
private String pec; // Per aziende italiane
// Sede e referente
private Indirizzo sedeLegale; // Value Object embedded
private String referenteNome;
private String referenteCognome;
private String referenteRuolo;
private SettoreAzienda settore; // Classificazione
}
ProfiloAssociazione
Le associazioni hanno un profilo diverso, orientato al mondo no-profit. Il campo chiave e il TipoAssociazione, che varia in base al paese: per l'Italia supporta APS, ASD, ODV, ETS, ONLUS; per gli USA 501(c)(3); per il Regno Unito Charity. Ogni tipo e validato rispetto al paese selezionato.
@Entity
public class ProfiloAssociazione {
private Long id;
private User user; // Relazione 1:1
private String nomeAssociazione; // "ASD Runners Bari"
private String paese; // "IT"
private TipoAssociazione tipoAssociazione; // APS, ASD, ODV, 501C3...
private String registrationNumber; // RUNTS, EIN, etc.
private String taxId;
private Indirizzo sede;
private String rappresentanteLegaleNome;
private String rappresentanteLegaleCognome;
}
// Validazione tipo-paese:
// TipoAssociazione.ASD.isValidoPerPaese("IT") → true
// TipoAssociazione.ASD.isValidoPerPaese("US") → false
// TipoAssociazione._501C3.isValidoPerPaese("US") → true
Differenze tra i Due Profili
- ProfiloAzienda: dati fiscali completi (VAT, PEC, registro imprese), referente aziendale, settore di appartenenza, piani BUSINESS
- ProfiloAssociazione: tipo associazione validato per paese, rappresentante legale, numero registrazione (RUNTS/EIN), piani ASSOCIAZIONE con sconto 35-38%
Membership e Ruoli: AppartenenzaOrganizzazione
Un utente con account AZIENDA o ASSOCIAZIONE diventa un'organizzazione a cui altri utenti possono appartenere. La relazione tra organizzazione e membro e modellata dall'entità AppartenenzaOrganizzazione, che funge da Aggregate Root per il sotto-dominio delle membership.
Ruoli Gerarchici
I ruoli sono definiti dall'enum RuoloOrganizzazione con quattro livelli di autorizzazione crescente.
public enum RuoloOrganizzazione {
MEMBRO(10), // Partecipa e riceve benefici piano
RESPONSABILE(50), // Crea eventi/viaggi per l'organizzazione
ADMIN(80), // Gestisce membri e ruoli
OWNER(100); // Controllo completo (uno solo per org)
}
PERMESSI:
MEMBRO RESPONSABILE ADMIN OWNER
Ricevere benefici ✓ ✓ ✓ ✓
Creare eventi ✗ ✓ ✓ ✓
Gestire membri ✗ ✗ ✓ ✓
Cambiare ruoli ✗ ✗ ✓ ✓
Invitare membri ✗ ✗ ✓ ✓
Rimuovere membri ✗ ✗ ✓ ✓
Il ruolo OWNER e speciale: viene assegnato automaticamente
al creatore dell'organizzazione, non può essere assegnato tramite invito e
non può essere sospeso. Per trasferire la proprietà esiste un metodo dedicato
trasferisciOwnership().
Workflow Bidirezionale
L'appartenenza supporta due flussi: l'organizzazione invita un membro (stato iniziale PENDING_INVITO) oppure un membro richiede di unirsi (stato iniziale PENDING_RICHIESTA). In entrambi i casi il destinatario può accettare o rifiutare.
public enum StatoAppartenenza {
PENDING_INVITO, // L'org ha invitato, in attesa del membro
PENDING_RICHIESTA, // Il membro ha richiesto, in attesa dell'org
ATTIVO, // Membro attivo nell'organizzazione
SOSPESO, // Temporaneamente sospeso
RIFIUTATO // Invito/richiesta rifiutata
}
FLUSSO INVITO (dall'organizzazione):
PENDING_INVITO ──accetta──► ATTIVO ──sospendi──► SOSPESO
│ │
└──rifiuta──► RIFIUTATO riattiva ◄───┘
FLUSSO RICHIESTA (dal membro):
PENDING_RICHIESTA ──approva──► ATTIVO
│
└──rifiuta──► RIFIUTATO
Il sistema supporta anche la multi-appartenenza: un utente può appartenere a più organizzazioni contemporaneamente, con ruoli diversi in ciascuna. La tabella ha un vincolo di unicita sulla coppia (organizzazione_id, membro_id) per evitare duplicati.
Inviti via Email: InvitoOrganizzazioneEmail
Come invitare qualcuno che non e ancora registrato sulla piattaforma? Attraverso InvitoOrganizzazioneEmail. Questa entità gestisce l'intero flusso: dall'invio dell'email con un token univoco fino alla conversione in un'AppartenenzaOrganizzazione quando l'utente accetta.
1. Admin/Owner crea invito con email destinatario
2. Sistema genera token SecureRandom (48 bytes, Base64 URL-safe)
3. Email inviata con link contenente il token
4. Destinatario clicca il link
├── Se registrato → Accetta direttamente
└── Se non registrato → Si registra, poi accetta
5. Invito convertito in AppartenenzaOrganizzazione ATTIVA
STATI INVITO:
PENDING ──accetta──► ACCETTATO
│
├──annulla──► ANNULLATO (dall'org)
├──scade────► SCADUTO (dopo N giorni, default 7)
└──rinnova──► PENDING (nuovo token, nuova scadenza)
Sicurezza degli Inviti
Ogni invito genera un token crittograficamente sicuro di 48 bytes tramite SecureRandom. Il token ha una scadenza configurabile (default 7 giorni) e viene rigenerato quando l'invito viene rinnovato. L'email viene normalizzata (lowercase, trim) e validata prima dell'invio.
Festival: L'Aggregate Root per Eventi Multi-Giorno
Passiamo ora al secondo grande tema dell'articolo. Un Festival in Play The Event e un aggregato complesso che modella eventi multi-giornata con giornate programmate, sale attrezzate, stand espositivi e un palinsesto strutturato. Seguendo i principi DDD, il Festival e l'Aggregate Root con un boundary di consistenza forte: tutte le modifiche passano attraverso di esso.
Festival (Aggregate Root)
├── id, titolo, descrizione
├── organizzatoreId // Chi organizza
├── luogoId // Location principale
├── dataInizio / dataFine
├── stato: StatoFestival
├── budget: Money // Value Object embedded
├── maxPartecipantiTotali
├── tokenCondivisione // QR / link sharing
│
├── giornate: Set<GiornataFestival>
│ └── ogni giornata ha i suoi EventoFestival
│
├── sale: Set<SalaFestival>
│ └── ogni sala ha le sue ConfigurazioneSala
│
└── stand: Set<StandFestival>
└── ogni stand ha le sue PrenotazioneStand
Ciclo di Vita del Festival
Il festival attraversa cinque stati ben definiti, con transizioni controllate e validazioni ad ogni passaggio.
public enum StatoFestival {
DRAFT, // Bozza: in preparazione, non visibile
PUBLISHED, // Pubblicato: visibile, in attesa dell'inizio
IN_CORSO, // In svolgimento
COMPLETATO, // Concluso con successo
ANNULLATO // Annullato
}
TRANSIZIONI:
DRAFT ──pubblica──► PUBLISHED ──avvia──► IN_CORSO ──completa──► COMPLETATO
│ │
└──annulla──► ANNULLATO ◄──annulla───┘
▲
DRAFT ────────────────annulla────────────┘
PERMESSI PER STATO:
DRAFT PUBLISHED IN_CORSO COMPLETATO ANNULLATO
Modificabile ✓ ✓ ✗ ✗ ✗
Accetta eventi ✓ ✓ ✓ ✗ ✗
Accetta prenotaz. ✓ ✓ ✗ ✗ ✗
Annullabile ✓ ✓ ✓ ✗ ✗
Per pubblicare un festival e necessario che abbia almeno una giornata
configurata. Il sistema verifica questo requisito nel metodo
pubblica() e lancia un'eccezione se non e soddisfatto.
Giornate Festival
Ogni GiornataFestival rappresenta un singolo giorno del festival. Ha una data, un titolo opzionale, orari di inizio e fine (default 09:00 - 23:00) e un ordine progressivo.
@Entity
public class GiornataFestival {
private Long id;
private Long festivalId;
private LocalDate data;
private String titolo; // "Giorno 1 - Apertura"
private String descrizione;
private LocalTime oraInizio; // Default: 09:00
private LocalTime oraFine; // Default: 23:00
private Integer ordine; // Posizione nel calendario
private Set<EventoFestival> eventi; // Eventi programmati
}
VALIDAZIONI:
- La data deve essere compresa tra dataInizio e dataFine del festival
- Non possono esistere due giornate con la stessa data
- Gli orari degli eventi devono rientrare negli orari della giornata
- Non si può rimuovere una giornata con eventi programmati
Il Festival offre anche un metodo generaGiornate() che crea
automaticamente una GiornataFestival per ogni giorno del periodo, evitando
duplicati con le giornate già esistenti.
Sale: SalaFestival e Configurazioni
Le SalaFestival rappresentano gli spazi fisici del festival. Ogni sala ha un tipo che ne determina le capacità e l'uso.
public enum TipoSala {
SALA_CONFERENZE // Conferenze e presentazioni (seduti, eventi)
AUDITORIUM // Grande sala per molti partecipanti (seduti)
PALCO // Spettacoli e performance (eventi)
AREA_ESTERNA // Spazio all'aperto (eventi)
STAND_AREA // Area dedicata agli stand espositivi
SALA_WORKSHOP // Workshop pratici (seduti, eventi)
FOYER // Area accoglienza e networking
SALA_STAMPA // Per giornalisti e media (seduti, eventi)
}
capacità:
Posti Seduti Ospita Eventi
SALA_CONFERENZE ✓ ✓
AUDITORIUM ✓ ✗
PALCO ✗ ✓
AREA_ESTERNA ✗ ✓
STAND_AREA ✗ ✗
SALA_WORKSHOP ✓ ✓
FOYER ✗ ✗
SALA_STAMPA ✓ ✓
ConfigurazioneSala
Una sala può avere multiple configurazioni con capienze diverse. Ad esempio, una sala conferenze può essere allestita in modalità "Teatro" (200 posti seduti), "Platea" (150 seduti + 50 in piedi) o "Standing" (300 in piedi). Le configurazioni possono essere attivate o disattivate.
SalaFestival: "Sala Magna" (SALA_CONFERENZE)
├── Configurazione "Teatro"
│ ├── Posti seduti: 200
│ ├── Posti in piedi: 0
│ └── Attiva: true
├── Configurazione "Platea"
│ ├── Posti seduti: 150
│ ├── Posti in piedi: 50
│ └── Attiva: true
└── Configurazione "Standing"
├── Posti seduti: 0
├── Posti in piedi: 300
└── Attiva: false
Capienza massima: max(200, 200, 300) = 300 (tra configurazioni attive: 200)
Stand: StandFestival e Prenotazioni
Gli StandFestival rappresentano gli spazi espositivi. La distinzione fondamentale e tra stand PROPRIO (gestito dall'organizzazione) e AFFITTABILE (noleggiabile a terzi con un prezzo giornaliero).
StandFestival
├── codice: "A01" // Identificativo univoco
├── nome: "Stand Principale"
├── tipo: PROPRIO | AFFITTABILE
├── dimensioniMq: 25.50
├── prezzoGiornaliero: Money // Solo per AFFITTABILE
├── dotazioni: "Tavolo, sedie, corrente elettrica"
├── posizioneMappa: "Padiglione A, Fila 1"
│
└── prenotazioni: Set<PrenotazioneStand>
└── PrenotazioneStand
├── giornataId // Per quale giorno
├── affittuarioNome // Chi affitta
├── affittuarioEmail
├── attivitaNome // Cosa esporra
├── stato: StatoPrenotazione
└── importoPagato: Money // Tracking pagamenti
Ciclo di Vita della Prenotazione
Ogni prenotazione attraversa un ciclo semplice ma efficace. Uno stand può essere prenotato per una specifica giornata, con un vincolo di unicita: non possono esistere due prenotazioni attive per lo stesso stand nella stessa giornata.
public enum StatoPrenotazione {
PRENOTATO, // Prenotazione effettuata, in attesa di conferma
CONFERMATO, // Prenotazione confermata e attiva
ANNULLATO // Prenotazione annullata
}
TRANSIZIONI:
PRENOTATO ──conferma──► CONFERMATO
│ │
└──annulla──► ANNULLATO ◄┘
VINCOLI:
- Solo stand AFFITTABILE possono essere prenotati
- Un solo affittuario per stand per giornata
- La prenotazione include tracking dei pagamenti via Money
EventoFestival: Collegamento Evento-Giornata-Sala
L'entità EventoFestival e il ponte tra un evento e il contesto del festival. Collega un evento a una specifica giornata, con un orario preciso e l'assegnazione opzionale a una sala. Il sistema verifica automaticamente che non ci siano sovrapposizioni nella stessa sala.
public enum TipoEventoFestival {
// Eventi principali (main stage)
MAIN_EVENT, KEYNOTE, CERIMONIA, CONCERTO, SPETTACOLO
// Eventi formativi
TALK, WORKSHOP, PANEL
// Networking e servizi
NETWORKING, REGISTRAZIONE, BREAK, ALTRO
}
ESEMPIO PALINSESTO - Giorno 1:
09:00-09:30 REGISTRAZIONE (Foyer)
09:30-10:30 CERIMONIA apertura (Auditorium)
10:30-11:00 BREAK (Foyer)
11:00-12:00 KEYNOTE speaker (Auditorium)
11:00-12:30 WORKSHOP Angular (Sala Workshop A)
11:00-12:30 WORKSHOP Spring Boot (Sala Workshop B)
12:30-14:00 BREAK pranzo
14:00-14:45 TALK microservizi (Sala Conferenze 1)
14:00-14:45 TALK frontend (Sala Conferenze 2)
15:00-16:30 PANEL "Il futuro del web" (Auditorium)
17:00-18:00 NETWORKING (Area Esterna)
21:00-23:00 CONCERTO (Palco)
Controllo Sovrapposizioni
Quando un evento viene assegnato a una sala, il sistema verifica che non
ci siano sovrapposizioni temporali con altri eventi nella stessa sala.
Due eventi si sovrappongono se A.oraInizio < B.oraFine AND
A.oraFine > B.oraInizio. Gli eventi senza sala assegnata non
vengono controllati.
Budget con il Value Object Money
Sia il Festival che gli stand utilizzano il Value Object Money
per gestire importi monetari. Money e un oggetto immutabile che incapsula
un importo (BigDecimal con scala 2) e una valuta
(String ISO 4217).
@Embeddable
public class Money implements Comparable<Money> {
private BigDecimal amount; // Scala 2, HALF_UP
private String currency; // ISO 4217 (EUR, USD, GBP...)
// Factory methods
Money.of(100.00, "EUR")
Money.euro(50.00)
Money.zero("EUR")
// Operazioni aritmetiche (stessa valuta obbligatoria)
money1.add(money2) // Somma
money1.subtract(money2) // Sottrazione
money1.multiply(1.22) // Moltiplicazione (es: IVA)
money1.divide(3) // Divisione
// Query
money.isPositive()
money.isZero()
money.isGreaterThan(other)
}
USO NEL FESTIVAL:
Festival.budget = Money.euro(50000.00) // Budget totale
StandFestival.prezzoGiornaliero = Money.euro(150.00) // Prezzo affitto/giorno
PrenotazioneStand.importoPagato = Money.euro(450.00) // 3 giorni x 150
L'uso di Money come Value Object garantisce che non si possano sommare importi in valute diverse (il sistema lancia un'eccezione) e che tutti gli arrotondamenti siano consistenti.
Condivisione del Festival
Ogni festival può generare un token di condivisione (8 caratteri alfanumerici) che permette di condividere rapidamente il festival tramite link o QR code. La condivisione può essere attivata e disattivata dall'organizzatore in qualsiasi momento.
Punti Chiave
- Due profili organizzazione (Azienda e Associazione) con validazione specifica per paese
- AppartenenzaOrganizzazione con 4 ruoli gerarchici e workflow bidirezionale invito/richiesta
- InvitoOrganizzazioneEmail per invitare utenti non registrati con token sicuro e scadenza
- Festival come Aggregate Root DDD con 5 stati nel ciclo di vita
- GiornataFestival con orari configurabili e generazione automatica
- 8 tipi di sala con configurazioni multiple e controllo capienza
- Stand PROPRIO e AFFITTABILE con sistema di prenotazione per giornata
- 12 tipi di evento festival con controllo sovrapposizioni per sala
- Money Value Object per gestione budget e pagamenti type-safe
Il codice sorgente e disponibile su GitHub. Per esplorare la gestione di organizzazioni e festival in azione, visita www.playtheevent.com.







