Salta al contenuto principale

14 - Organizzazioni e Festival: Gestione Multi-Livello

Organizzazioni e Festival: Strutture Complesse per Eventi Complessi Quando un singolo utente non basta più, entrano in gioco le organizzazioni .

Cosa ne pensi di questo articolo?

14 - Organizzazioni e Festival: Gestione Multi-Livello
Play the EventFoto:PixabayPexels

In Breve

Organizzazioni e Festival: Strutture Complesse per Eventi Complessi Quando un singolo utente non basta più, entrano in gioco le organizzazioni .

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 e Profili
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.

Struttura ProfiloAzienda
@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.

Struttura ProfiloAssociazione
@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.

Gerarchia dei Ruoli
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.

Ciclo di Vita dell'Appartenenza
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.

Workflow Invito Email
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.

Struttura dell'Aggregato Festival
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.

StatoFestival
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.

GiornataFestival
@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.

Tipi di Sala
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.

Esempio: Configurazioni di una Sala
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).

Stand e Sistema di Prenotazione
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.

StatoPrenotazione
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.

Tipi di Evento nel Festival
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).

Money Value Object
@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.

Domande frequenti

Come si definisce un'organizzazione nella piattaforma Play The Event?

Un'organizzazione può essere un'azienda o un'associazione, ciascuna con il proprio profilo, i propri membri e i propri ruoli.

Quali sono le caratteristiche del profilo di un'azienda nella piattaforma Play The Event?

Il profilo di un'azienda include dati fiscali internazionali come ragione sociale, paese, VAT Number, Tax ID e numero di registrazione imprese, nonché i dati di contatto (email aziendale, PEC), la sede legale tramite il Value Object Indirizzo, il referente aziendale e il settore di appartenenza.

Come si gestiscono le membership e i ruoli all'interno di un'organizzazione nella piattaforma Play The Event?

Un utente con account AZIENDA o ASSOCIAZIONE diventa un' organizzazione a cui altri utenti possono appartenere. La relazione tra organizzazione e membro è modellata dall'entità AppartenenzaOrganizzazione, che funge da Aggregate Root per il sotto-dominio delle membership.

Quali sono le caratteristiche del Festival nella piattaforma Play The Event?

Il Festival rappresenta 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 è l'Aggregate Root con un boundary di consistenza forte: tutte le modifiche passano attraverso di esso.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti