Agile e Collaborazione Real-Time: Organizzare Eventi Come un Team di Sviluppo
Organizzare un evento con più persone è un progetto complesso: ci sono task da assegnare, scadenze da rispettare, priorità che cambiano e decisioni da prendere insieme. In Play The Event abbiamo integrato strumenti di project management agile e comunicazione real-time direttamente nella piattaforma, eliminando la necessità di tool esterni come Trello, Jira o gruppi WhatsApp.
Cosa Troverai in Questo Articolo
- Sprint management con stati (PIANIFICATO, ATTIVO, COMPLETATO, ANNULLATO)
- Task con sottotask, priorità, dipendenze e story points Fibonacci
- Viste multiple: Kanban board con drag-drop, Gantt timeline e lista raggruppata
- WebSocket con protocollo STOMP per aggiornamenti live
- Domain Events: CommentoTaskAggiuntoEvent, StatoTaskCambiatoEvent e altri
- Sistema di notifiche multi-canale (in-app + email)
- Activity tracking per dashboard e audit trail
- Planning Poker per votazione collaborativa degli story points
Sprint Management: Iterazioni per l'Organizzazione
Gli sprint non sono solo per team di sviluppo software. Quando organizzi un matrimonio, un festival o un evento aziendale, suddividere il lavoro in iterazioni con obiettivi chiari è fondamentale. In Play The Event ogni sprint ha un nome, una descrizione, un obiettivo specifico e un intervallo di date ben definito.
Il ciclo di vita di uno sprint segue transizioni controllate: parte dallo stato PIANIFICATO, viene avviato passando ad ATTIVO, e al termine può essere marcato come COMPLETATO o ANNULLATO. Le transizioni sono validate lato dominio: non è possibile avviare uno sprint già completato o annullare uno sprint in stato terminale.
public enum StatoSprint {
PIANIFICATO("Pianificato", "Planned"),
ATTIVO("Attivo", "Active"),
COMPLETATO("Completato", "Completed"),
ANNULLATO("Annullato", "Cancelled");
public boolean canTransitionTo(StatoSprint nuovoStato) {
return switch (this) {
case PIANIFICATO -> nuovoStato == ATTIVO || nuovoStato == ANNULLATO;
case ATTIVO -> nuovoStato == COMPLETATO || nuovoStato == ANNULLATO;
case COMPLETATO, ANNULLATO -> false; // Stati terminali
};
}
}
L'entità Sprint è un Aggregate Root nel contesto DDD. Può essere associata sia a un evento che a un viaggio (mutualmente esclusivi), ed è disponibile per account di tipo AZIENDA e ASSOCIAZIONE. Il sistema traccia automaticamente data di creazione e ultimo aggiornamento grazie a @CreatedDate e @LastModifiedDate di Spring Data JPA, e utilizza il @Version per gestire l'optimistic locking in scenari di accesso concorrente.
@Entity
public class Sprint {
private Long id;
private Long eventoId; // Associazione a evento
private Long viaggioId; // Associazione a viaggio (mutualmente esclusivo)
private String nome; // Max 100 caratteri
private String descrizione;
private String obiettivo; // Max 500 caratteri
private LocalDate dataInizio;
private LocalDate dataFine;
private StatoSprint stato; // PIANIFICATO, ATTIVO, COMPLETATO, ANNULLATO
private Integer ordine;
private Long creatoDaUserId;
// Factory methods per creazione controllata
public static Sprint creaPerEvento(Long eventoId, String nome, ...) { ... }
public static Sprint creaPerViaggio(Long viaggioId, String nome, ...) { ... }
// Business methods con validazione
public void avvia() { ... } // PIANIFICATO -> ATTIVO
public void completa() { ... } // ATTIVO -> COMPLETATO
public void annulla() { ... } // -> ANNULLATO (da PIANIFICATO o ATTIVO)
}
Task Management: Il Cuore dell'Organizzazione
Ogni task in Play The Event è un'entità ricca con molteplici attributi. Ha un titolo, una descrizione, può essere assegnato a un utente specifico, ha una priorità (importanza da 1 a 10), date di inizio e fine, e un sistema di dipendenze che permette di bloccare task tra loro.
Quattro Stati per il Flusso di Lavoro
Il ciclo di vita di un task segue quattro stati che mappano il classico workflow agile:
- TODO (Da fare) - Il task è stato creato ma non ancora iniziato
- IN_PROGRESS (In corso) - Qualcuno sta lavorando attivamente al task
- REVIEW (In revisione) - Il lavoro è completato e in attesa di verifica
- DONE (Completato) - Il task è concluso e verificato
Le transizioni sono flessibili: un task può tornare indietro da REVIEW a IN_PROGRESS se la verifica non va a buon fine, o essere riaperto da DONE se emergono problemi. Questo approccio riflette la realtà dell'organizzazione eventi, dove le cose cambiano continuamente.
public enum StatoTask {
TODO("Da fare"),
IN_PROGRESS("In corso"),
REVIEW("In revisione"),
DONE("Completato");
// Transizioni flessibili in entrambe le direzioni
public boolean canTransitionTo(StatoTask nuovoStato) {
return true; // TODO <-> IN_PROGRESS <-> REVIEW <-> DONE
}
public int getOrdineProgressione() {
return switch (this) {
case TODO -> 0;
case IN_PROGRESS -> 1;
case REVIEW -> 2;
case DONE -> 3;
};
}
}
Sottotask e Dipendenze
Un task può avere sottotask (tramite parentTaskId), creando una gerarchia che permette di scomporre attività complesse in unità più piccole e gestibili. Ad esempio, il task "Preparare location" può avere sottotask come "Prenotare sala", "Organizzare catering" e "Allestire decorazioni".
Il sistema di dipendenze (bloccaTaskId) permette di dichiarare che un task blocca un altro: finché "Confermare lista invitati" non è completato, "Stampare segnaposti" resta bloccato. Oltre al blocco automatico tramite dipendenze, è possibile impostare un blocco tecnico manuale con motivazione, utile per segnalare impedimenti esterni come "In attesa di conferma dal fornitore".
@Entity
public class TaskEvento {
private Long id;
private Long eventoId;
private String titolo; // Max 255 caratteri
private String descrizione;
private Long assegnatoAUserId;
private Long creatoDaUserId;
private Integer importanza; // 1-10 (8+ = alta priorità)
private StatoTask statoTask; // TODO, IN_PROGRESS, REVIEW, DONE
private Instant dataInizio;
private Instant dataFine;
private Long bloccaTaskId; // Dipendenza: questo task blocca un altro
private Boolean bloccatoManualmente;
private String motivoBloccoTecnico;
private Integer ordine;
private Long sprintId; // Associazione a sprint (opzionale)
private Integer storyPoints; // Fibonacci: 1, 2, 3, 5, 8, 13, 21
private Long parentTaskId; // Riferimento al task padre (sottotask)
// Valori Fibonacci validi per story points
private static final Set<Integer> FIBONACCI_VALIDI = Set.of(1, 2, 3, 5, 8, 13, 21);
// Query methods
public boolean isScaduto() { ... } // Data fine passata e non completato
public boolean isUrgente() { ... } // Scade entro 24 ore
public boolean isAltaPriorita() { return importanza >= 8; }
public boolean isSubtask() { return parentTaskId != null; }
}
Viste Multiple: Kanban, Gantt e Lista
Ogni organizzatore ha un modo diverso di visualizzare il lavoro. Per questo la piattaforma offre tre viste complementari che condividono gli stessi dati ma li presentano in modi diversi.
Kanban Board con Drag & Drop
La vista Kanban organizza i task in colonne corrispondenti agli stati: TODO, IN_PROGRESS, REVIEW e DONE. Il drag & drop permette di spostare un task da una colonna all'altra con un semplice trascinamento, aggiornando automaticamente lo stato nel backend tramite la PATCH API. Le card mostrano titolo, assegnatario, priorità (con badge colorato), story points e indicatori visivi per task scaduti, urgenti o bloccati.
Gantt Timeline
La vista Gantt visualizza i task su una timeline temporale, mostrando chiaramente le sovrapposizioni, le dipendenze tra task e i periodi critici. Le barre colorate rappresentano la durata di ogni task, con linee di collegamento che evidenziano le relazioni di blocco. Questa vista è particolarmente utile per gli organizzatori che devono avere una visione d'insieme delle scadenze.
Lista Raggruppata
La vista lista offre un elenco dettagliato dei task con opzioni di filtro avanzate: per stato, per assegnatario, per sprint o per priorità. I task possono essere ordinati per importanza, data di scadenza o ordine personalizzato. L'API supporta questi filtri direttamente lato server.
GET /api/v1/events/{eventId}/tasks
?stato=IN_PROGRESS
&assegnatoA=42
&ordinaPer=importanza
&direzione=DESC
GET /api/v1/events/{eventId}/sprints/{sprintId}/tasks
GET /api/v1/events/{eventId}/sprints/backlog // Task non in alcuno sprint
Story Points e Votazione Collaborativa (Planning Poker)
Stimare la complessità di un task non è facile, soprattutto quando più persone hanno prospettive diverse. Il sistema di Planning Poker integrato permette a tutti i partecipanti di votare la complessità di un task usando la sequenza di Fibonacci (1, 2, 3, 5, 8, 13, 21).
Ogni utente esprime il proprio voto in modo indipendente. Una volta che tutti hanno votato, il sistema calcola la media, suggerisce il valore Fibonacci più vicino, mostra la distribuzione dei voti e verifica se c'è consenso (tutti hanno votato lo stesso valore). Se non c'è consenso, il team può discutere e rivotare.
@Entity
@Table(uniqueConstraints = @UniqueConstraint(
columnNames = {"task_id", "utente_id"} // Un voto per utente per task
))
public class VotoStoryPoints {
public static final Set<Integer> VALORI_VALIDI = Set.of(1, 2, 3, 5, 8, 13, 21);
private Long id;
private Long taskId;
private Long utenteId;
private Integer valore; // Valore Fibonacci
private Instant votatoIl;
private Instant aggiornatoIl;
public static VotoStoryPoints crea(Long taskId, Long utenteId, Integer valore) {
if (!VALORI_VALIDI.contains(valore)) {
throw new IllegalArgumentException("Valore non valido: " + valore);
}
// ...
}
public void aggiornaValore(Integer nuovoValore) { ... }
}
GET /api/v1/events/{eventId}/tasks/{taskId}/votes
Response:
{
"taskId": 42,
"totaleVoti": 5,
"mediaVoti": 4.6,
"valoreSuggerito": 5, // Fibonacci più vicino alla media
"distribuzione": {
"3": 2,
"5": 2,
"8": 1
},
"consenso": false,
"votoCorrente": 5 // Voto dell'utente corrente
}
POST /api/v1/events/{eventId}/tasks/{taskId}/votes
Body: { "valore": 5 }
WebSocket e Comunicazione Real-Time
La collaborazione in tempo reale è uno dei pilastri di Play The Event. L'architettura WebSocket utilizza il protocollo STOMP (Simple Text Oriented Messaging Protocol) su WebSocket, con SockJS come fallback per browser non compatibili.
La configurazione definisce tre prefissi fondamentali: /topic per le subscription broadcast (tutti i client ricevono il messaggio), /queue per code punto-a-punto, e /user per messaggi privati destinati a utenti specifici. I client inviano messaggi al server usando il prefisso /app.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// Prefisso per subscription (broadcast e code)
registry.enableSimpleBroker("/topic", "/queue");
// Prefisso per messaggi client -> server
registry.setApplicationDestinationPrefixes("/app");
// Prefisso per messaggi privati a utenti specifici
registry.setUserDestinationPrefix("/user");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// Endpoint con SockJS fallback
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*")
.withSockJS();
// Endpoint WebSocket nativo
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*");
}
}
Collaborazione sulla Planimetria
Un esempio concreto di utilizzo dei WebSocket è l'editor collaborativo della planimetria. Più utenti possono lavorare contemporaneamente sulla disposizione di una location: spostare tavoli, aggiungere elementi, ridimensionare aree. Il sistema gestisce:
- Cursori in tempo reale - Ogni utente vede il cursore degli altri con colori distinti
- Lock sugli elementi - Quando un utente seleziona un elemento, nessun altro può modificarlo simultaneamente
- Presenza utenti - Lista degli utenti attivi con join/leave notifiche
- Sincronizzazione modifiche - Ogni modifica viene propagata istantaneamente a tutti i client connessi
@Controller
public class PlanimetriaWebSocketController {
// Utenti attivi per evento (ConcurrentHashMap per thread-safety)
private final Map<Long, Set<UtenteAttivoDTO>> utentiPerEvento = new ConcurrentHashMap<>();
// Lock sugli elementi: "evento:eventoId:elementoId" -> utenteId
private final Map<String, Long> lockElementi = new ConcurrentHashMap<>();
// Colori predefiniti: Blu, Verde, Arancione, Rosso, Viola, Rosa, Ciano, Lime
private static final String[] COLORI_UTENTI = {
"#3B82F6", "#10B981", "#F59E0B", "#EF4444",
"#8B5CF6", "#EC4899", "#06B6D4", "#84CC16"
};
@MessageMapping("/planimetria/evento/{eventoId}/join")
@SendTo("/topic/planimetria/evento/{eventoId}")
public PlanimetriaMessageDTO utenteJoinEvento(...) { ... }
@MessageMapping("/planimetria/evento/{eventoId}/cursor")
@SendTo("/topic/planimetria/evento/{eventoId}/cursori")
public PlanimetriaMessageDTO aggiornaCursoreEvento(...) { ... }
@MessageMapping("/planimetria/evento/{eventoId}/lock")
@SendTo("/topic/planimetria/evento/{eventoId}")
public PlanimetriaMessageDTO gestisciLockEvento(...) {
// Tentativo di acquisire lock
Long currentLock = lockElementi.get(lockKey);
if (currentLock == null) {
lockElementi.put(lockKey, utenteId); // Lock acquisito
return PlanimetriaMessageDTO.lockAcquired(...);
}
return null; // Lock già preso da altro utente
}
}
Domain Events: Comunicazione tra Bounded Context
Il sistema segue i principi del Domain-Driven Design con un pattern di Domain Events ben definito. Quando accade qualcosa di significativo nel dominio, viene pubblicato un evento che altri componenti possono ascoltare e reagire di conseguenza, mantenendo un accoppiamento lasco tra i moduli.
Tutti i domain events implementano l'interfaccia DomainEvent che espone il timestamp dell'evento e il nome del tipo. Questo contratto comune garantisce coerenza e tracciabilità in tutto il sistema.
public interface DomainEvent {
LocalDateTime occurredOn();
default String eventType() {
return this.getClass().getSimpleName();
}
}
Eventi Principali del Sistema
Il sistema definisce diversi domain events che coprono i momenti chiave dell'organizzazione:
- EventCreatedEvent - Pubblicato alla creazione di un nuovo evento (eventoId, titolo, organizzatoreId, dataInizio)
- EventPublishedEvent - Quando un evento viene pubblicato e reso disponibile per gli inviti
- ParticipantInvitedEvent - Quando un partecipante viene invitato (eventoId, utenteId, titoloEvento)
- StatoTaskCambiatoEvent - Cambio di stato di un task con vecchio e nuovo stato
- CommentoTaskAggiuntoEvent - Aggiunta di un commento a un task con testo troncato a 200 caratteri
// Evento: cambio stato di un task
public class StatoTaskCambiatoEvent implements DomainEvent {
private final Long taskId;
private final Long eventoId;
private final Long utenteIdCheCambia;
private final String titoloTask;
private final StatoTask vecchioStato;
private final StatoTask nuovoStato;
private final LocalDateTime occurredOn;
}
// Evento: nuovo commento su un task
public class CommentoTaskAggiuntoEvent implements DomainEvent {
private final Long taskId;
private final Long eventoId;
private final Long commentoId;
private final Long autoreUserId;
private final String titoloTask;
private final String testoCommento; // Troncato a 200 caratteri
private final LocalDateTime occurredOn;
}
Notifiche Real-Time: Multi-Canale e Intelligenti
Quando un domain event viene pubblicato, il TaskNotificaEventHandler entra in azione. Questo componente ascolta gli eventi usando @TransactionalEventListener con fase AFTER_COMMIT, garantendo che i dati siano effettivamente persistiti prima di inviare notifiche. L'elaborazione avviene in modo asincrono (@Async) in una transazione separata (REQUIRES_NEW) per non impattare la performance dell'operazione principale.
Per ogni evento, il sistema identifica i collaboratori del task (assegnatario, creatore, altri partecipanti all'evento che hanno interagito con il task) escludendo l'utente che ha generato l'azione, per evitare di notificare chi ha appena compiuto l'operazione.
@Component
public class TaskNotificaEventHandler {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void gestisciStatoTaskCambiato(StatoTaskCambiatoEvent event) {
// 1. Trova collaboratori (escluso chi ha cambiato lo stato)
List<User> collaboratori = collaboratoriTaskService
.trovaCollaboratori(event.getTaskId(), event.getEventoId(),
event.getUtenteIdCheCambia());
// 2. Per ogni collaboratore: notifica in-app + email
for (User destinatario : collaboratori) {
// Notifica in-app (persistita nel database)
NotificaUtente notifica = NotificaUtente.creaConRiferimento(
destinatario,
TipoNotifica.STATO_TASK_CAMBIATO,
"Task aggiornato",
nomeUtente + " ha cambiato lo stato da ... a ...",
event.getTaskId(), "TASK", link
);
// Email (solo se email verificata)
if (destinatario.getEmailVerificata()) {
emailService.inviaNotificaStatoTaskCambiato(...);
}
}
}
}
Tipi di Notifica
Il sistema supporta 17 tipi di notifica raggruppati per contesto. Ogni tipo ha un'icona associata (dalla libreria Lucide) per il rendering nel frontend e una descrizione di default.
public enum TipoNotifica {
// Notifiche eventi
INVITO_EVENTO("user-plus", "Sei stato invitato a un evento"),
EVENTO_MODIFICATO("edit", "Un evento è stato modificato"),
EVENTO_ANNULLATO("calendar-x", "Un evento è stato annullato"),
EVENTO_IMMINENTE("bell", "Un evento sta per iniziare"),
// Notifiche task
COMMENTO_TASK("message-circle", "Nuovo commento su un task"),
STATO_TASK_CAMBIATO("refresh-cw", "Lo stato di un task è cambiato"),
TASK_ASSEGNATO("user-check", "Ti è stato assegnato un task"),
// Notifiche spese
NUOVA_SPESA("wallet", "È stata aggiunta una nuova spesa"),
SPESA_ASSEGNATA("credit-card", "Ti è stata assegnata una spesa"),
PROMEMORIA_PAGAMENTO("alert-circle", "Promemoria pagamento"),
// Notifiche collegamenti
RICHIESTA_COLLEGAMENTO("user-plus", "Richiesta di collegamento"),
COLLEGAMENTO_ACCETTATO("user-check", "Collegamento accettato"),
// Sistema
MESSAGGIO_SISTEMA("info", "Messaggio di sistema");
}
Notifica In-App: Struttura Completa
- Destinatario - Utente che riceve la notifica (relazione ManyToOne)
- Tipo - Enum TipoNotifica con icona e descrizione
- Titolo - Max 200 caratteri, generato dinamicamente
- Messaggio - Max 500 caratteri, con dettagli dell'azione
- Stato lettura - Flag letta/non letta con timestamp di lettura
- Riferimento entità - entityId + entityType per navigazione
- Link - URL per navigare direttamente all'entità correlata
- Metadata - Campo JSON per dati aggiuntivi flessibili
Activity Tracking: Dashboard e Audit Trail
Ogni azione significativa dell'utente viene registrata nel sistema di Activity Tracking. L'entità AttivitaUtente cattura il tipo di attività, una descrizione leggibile, un timestamp, e opzionalmente un riferimento all'entità coinvolta e metadata JSON aggiuntivi.
Questo sistema serve a due scopi: fornire un feed attività nel dashboard personale dell'utente e creare un audit trail completo per gli organizzatori che vogliono sapere chi ha fatto cosa e quando.
public enum TipoAttivita {
// Attività evento
EVENTO_CREATO("calendar", "Evento creato"),
EVENTO_MODIFICATO("edit", "Evento modificato"),
EVENTO_PUBBLICATO("send", "Evento pubblicato"),
EVENTO_CONCLUSO("check-circle", "Evento concluso"),
// Attività partecipanti
PARTECIPANTE_AGGIUNTO("user-plus", "Partecipante aggiunto"),
PARTECIPANTE_CONFERMATO("user-check", "Partecipante confermato"),
// Attività task
TASK_CREATO("plus-square", "Task creato"),
TASK_MODIFICATO("edit", "Task modificato"),
TASK_COMPLETATO("check-square", "Task completato"),
TASK_ELIMINATO("trash-2", "Task eliminato"),
// Attività spese
SPESA_AGGIUNTA("wallet", "Spesa aggiunta"),
SPESA_MODIFICATA("edit-3", "Spesa modificata"),
// Attività inventario
MATERIALE_INVENTARIO_AGGIUNTO("package-plus", "Materiale aggiunto"),
MATERIALE_EVENTO_AGGIUNTO("box", "Materiale assegnato all'evento"),
// Attività sondaggi
SONDAGGIO_CREATO("vote", "Sondaggio creato"),
VOTO_REGISTRATO("check-square", "Voto registrato"),
// ... 40+ tipi totali con icona Lucide e descrizione
}
L'AttivitaUtente utilizza UUID come identificatore e supporta tre modalità di creazione tramite factory methods: creazione semplice, creazione con riferimento a un'entità (entityId + entityType) e creazione con metadata JSON personalizzati. Questo design flessibile permette di adattare il tracking a qualsiasi contesto futuro senza modificare lo schema del database.
@Entity
public class AttivitaUtente {
private UUID id;
private User utente; // Relazione ManyToOne
private TipoAttivita tipoAttivita;
private String descrizione; // Max 500 caratteri
private LocalDateTime timestamp;
private UUID entityId; // Riferimento opzionale
private String entityType; // "EVENTO", "TASK", "SPESA"...
private String metadata; // JSON aggiuntivo
// Tre factory methods per diversi scenari
public static AttivitaUtente crea(User u, TipoAttivita tipo, String desc) { ... }
public static AttivitaUtente creaConRiferimento(User u, TipoAttivita tipo,
String desc, UUID entityId, String entityType) { ... }
public static AttivitaUtente creaConMetadata(User u, TipoAttivita tipo,
String desc, String metadata) { ... }
}
Commenti sui Task: Comunicazione Contestuale
Ogni task ha un thread di commenti integrato. I partecipanti all'evento possono aggiungere commenti (fino a 2000 caratteri), e il sistema invia automaticamente notifiche ai collaboratori tramite il domain event CommentoTaskAggiuntoEvent. Solo l'autore del commento o l'organizzatore dell'evento possono eliminare un commento, garantendo controllo e trasparenza.
@Entity
public class CommentoTask {
private Long id;
private Long taskId;
private Long autoreUserId;
private String testo; // Max 2000 caratteri, obbligatorio
private Instant creatoIl; // Audit automatico
public static CommentoTask crea(Long taskId, Long autoreUserId, String testo) {
// Validazione: taskId, autoreUserId e testo obbligatori
// Testo max 2000 caratteri
return new CommentoTask(taskId, autoreUserId, testo);
}
public boolean isAutore(Long userId) {
return this.autoreUserId.equals(userId);
}
}
Sprint API: CRUD Completo con Statistiche
L'API REST per gli sprint espone endpoint per tutte le operazioni CRUD più funzionalità avanzate. La response di ogni sprint include statistiche calcolate in tempo reale: totale task, task completati, totale story points, story points completati e percentuali di progresso.
// CRUD Sprint
GET /api/v1/events/{eventId}/sprints // Lista con filtro stato
GET /api/v1/events/{eventId}/sprints/{id} // Dettaglio singolo
POST /api/v1/events/{eventId}/sprints // Creazione
PUT /api/v1/events/{eventId}/sprints/{id} // Aggiornamento
DELETE /api/v1/events/{eventId}/sprints/{id} // Eliminazione
PATCH /api/v1/events/{eventId}/sprints/{id}/status // Cambio stato
// Gestione task in sprint
GET /api/v1/events/{eventId}/sprints/{id}/tasks // Task nello sprint
POST /api/v1/events/{eventId}/sprints/{id}/tasks/{t} // Aggiungi task
DELETE /api/v1/events/{eventId}/sprints/{id}/tasks/{t} // Rimuovi task
GET /api/v1/events/{eventId}/sprints/backlog // Task senza sprint
// SprintResponse include statistiche calcolate
{
"id": 1,
"nome": "Sprint 1 - Preparazione Location",
"stato": "ATTIVO",
"totaleTask": 12,
"taskCompletati": 7,
"totaleStoryPoints": 42,
"storyPointsCompletati": 28,
"progressoTask": 58.3,
"progressoStoryPoints": 66.7,
"scaduto": false,
"durataGiorni": 14
}
Perché Agile nell'Organizzazione Eventi?
Un evento complesso come un matrimonio o un festival ha centinaia di task interdipendenti, scadenze rigide e molte persone coinvolte. L'approccio agile con sprint corti (1-2 settimane) permette di avere checkpoint frequenti, adattarsi ai cambiamenti e avere sempre visibilità sullo stato dell'organizzazione. Non serve essere sviluppatori per beneficiare di Kanban board e story points: basta pensare in termini di "cosa fare", "chi lo fa" e "quanto è complesso".
Chat Integrata: Addio Gruppi WhatsApp
Uno dei problemi più comuni nell'organizzazione eventi è la frammentazione della comunicazione: messaggi importanti si perdono in gruppi WhatsApp caotici, informazioni duplicate su email e chat, decisioni prese in canali non tracciati. La piattaforma integra un sistema di comunicazione contestuale all'evento, dove ogni conversazione è collegata al contesto giusto.
I commenti sui task, le notifiche in-app e il sistema di messaggistica interno permettono di mantenere tutte le comunicazioni organizzate e ricercabili. Ogni messaggio ha un contesto chiaro: è associato a un task, a una spesa, a un partecipante o all'evento nel suo complesso. L'infrastruttura WebSocket già in uso per la planimetria collaborativa fornisce la base tecnica per la messaggistica real-time.
Punti Chiave
- Sprint con 4 stati e transizioni validate (macchina a stati DDD)
- Task con sottotask, 4 stati, priorità 1-10, dipendenze e blocchi manuali
- Story points Fibonacci con Planning Poker collaborativo (media, distribuzione, consenso)
- Viste Kanban, Gantt e Lista con filtri avanzati lato server
- WebSocket STOMP con SockJS fallback, lock elementi e cursori real-time
- 5 Domain Events per comunicazione asincrona tra bounded context
- Notifiche multi-canale (in-app + email) con 17 tipi e icone Lucide
- Activity tracking con 40+ tipi di attività e metadata JSON flessibili
- API REST completa con statistiche sprint calcolate in tempo reale
Il codice sorgente del progetto è disponibile su GitHub. Per provare il sistema completo di gestione agile e collaborazione real-time, visita www.playtheevent.com.







