77 KiB
Crewli
Product Design & Technical Specification
Full Stack SaaS — Event & Festival Management Platform
Version: 1.3 | Datum: Maart 2026 | Status: Concept | Tech Stack: Laravel 12 + Vue 3
1. Product Vision & Scope
Wat is Crewli en voor wie is het gebouwd?
1.1 Samenvatting
Crewli is een multi-tenant SaaS platform voor de professionele organisatie van evenementen en festivals. Het platform ondersteunt de volledige operationele cyclus: van artiestenbooking en advancing, via personeelsplanning en vrijwilligersbeheer, tot accreditatie, briefings, en real-time show-day operaties (Mission Control).
Het platform wordt gebouwd als een API-first Laravel 12 backend met een Vue 3 + Vuexy frontend, en is ontworpen om meerdere organisaties (klanten) te bedienen vanuit één installatie, met volledige data-isolatie per organisatie.
| KERNPRINCIPE | Crewli combineert de sterkste onderdelen van drie marktleiders: de accreditatie-engine en Mission Control van In2Event, het sectie-gebaseerde advance-portal en vrijwilligersbeheer van Crescat, en de mission/task-planning, SMS-communicatie en formulierbouwer van WeezCrew — aangevuld met differentiërende functies die geen van deze platformen biedt. |
1.2 Doelgroepen
| Rol | Beschrijving | Primaire module(s) |
|---|---|---|
| Super Admin | Platform-eigenaar (jij/Anthropic). Beheert organisaties, billing, globale instellingen. | Admin panel |
| Organisation Admin | Klant-hoofdbeheerder. Beheert evenementen, teamleden, globale instellingen per organisatie. | Org. management, events |
| Event Manager | Operationele verantwoordelijke per evenement. Beheert alle modules binnen dat event. | Alle event-modules |
| Staff Coordinator | Beheert bemanning, diensten, accreditatie voor specifieke secties. | Staff, Shifts, Accreditation |
| Artist Manager | Beheert artiesten, advancing, timetable. | Artists, Advancing, Timetable |
| Volunteer Coordinator | Beheert vrijwilligersregistraties, toewijzingen, communicatie. | Volunteers, Time Slots, Shifts |
| Crew Member (staff) | Externe medewerker. Ontvangt briefing, ziet eigen rooster, checkt in. | Briefing portal (read-only) |
| Volunteer | Zelfregistrerende vrijwilliger. Registreert, selecteert beschikbaarheid, claimt shifts. | Volunteer self-service portal |
| Artist / Tour Manager | Externe artiest-vertegenwoordiger. Vult eigen advance-portal in. | Artist advance portal |
| Supplier / Partner | Externe leverancier. Vult productieverzoek in, beheert eigen lijsten. | Supplier portal |
1.3 Kernbegrippen & Terminologie
De volgende termen worden door het hele document en de codebase consistent gehanteerd:
| Term | Definitie |
|---|---|
| Organisation | Een klant/organisatie die Crewli gebruikt. Heeft eigen evenementen, teamleden en instellingen. Equivalent aan 'Launchpad' in In2Event. |
| Event | Een specifiek evenement of festival binnen een organisatie. Alle operationele data is event-scoped. |
| Festival Section | Een operationele eenheid binnen een event: Bar, Hospitality, Technical, Security, etc. Heeft eigen Crew, Volunteers en Shifts. |
| Crowd Type | Classificatie van deelnemers: Crew, Volunteer, Artist, Guest, Press, Partner, Supplier. Configureerbaar per organisatie. |
| Person | Individuele deelnemer gekoppeld aan een event via een Crowd Type. |
| Time Slot | Event-niveau tijdvenster met persoonscategorie (CREW / VOLUNTEER / PRESS). Aangemaakt door de organisator als temporeel raamwerk. |
| Shift | Sectie-specifieke toewijzing binnen een Time Slot. Koppelt een Festival Section aan een tijdvenster met capaciteit. |
| Slot (capacity) | Het aantal personen dat een Shift kan bevatten. Opgesplitst in Admin-assigned en Open for Claiming. |
| Open for Claiming | Aantal shift-slots dat zichtbaar en claimbaar is in het vrijwilligersportal. (Crescat: 'For Sale') |
| Accreditation | Een recht of artikel toegekend aan een persoon: toegangszones, items te ontvangen (polsbandje, maaltijd, portofoon). |
| Advance / Advancing | Het proces waarbij artiestenvereisten worden verzameld vóór het evenement via een sectie-gebaseerd portal. |
| Mission Control | Real-time operationele hub op show-dag: inchecken, accreditatie uitreiken, artiestenstatus. |
| Briefing | Gepersonaliseerde communicatie (e-mail + PDF) naar deelnemers met e-tickets, informatie en taakbeschrijving. |
| Infosheet / Advance Section | Configureerbaar formulier-sectie dat naar artiesten of leveranciers gestuurd wordt voor het ophalen van informatie. |
2. Tech Stack & Infrastructuur
Bestaande en te installeren technologieen
2.1 Backend
| Component | Keuze / Versie |
|---|---|
| Language | PHP 8.2+ |
| Framework | Laravel 12 (laravel/framework ^12.0) |
| Authentication | Laravel Sanctum ^4.0 (SPA token auth) |
| Database | MySQL 8.x (primair), Redis (cache, queues, sessions) |
| Queue Driver | Redis via Laravel Horizon (monitoring + priority queues) |
| Real-time | Laravel Echo + Pusher / Soketi (zelf-gehoste WebSocket server) |
| File Storage | Laravel Filesystem: S3 (productie) / local (dev). Signed URLs voor beveiligde toegang. |
| Laravel Mailable + SendGrid of Postmark. Queue-based batch sending. | |
| PDF / Ticket | Laravel Browsershot (Puppeteer) of DomPDF voor server-side PDF generatie. |
| Barcode | picqer/php-barcode-generator. Alphanumeriek + numeriek. QR-code via endroid/qr-code. |
| Testing | PHPUnit ^11.5 + Pest. Feature tests per API route. |
| Tooling | Laravel Pint (CS), Laravel Sail (Docker dev), Laravel Pail (log viewer), Tinker. |
| Roles & Perms | Spatie laravel-permission (te installeren). Drie niveaus: App, Organisation, Event. |
| Multi-tenancy | Custom: organisation_id scoping op alle queries. Geen package-tenancy. |
| SMS + WhatsApp | Zender (zelf-gehoste instantie) via HTTP API. Zender gebruikt Android-apparaten als SMS/WhatsApp gateway. Configureerbare API endpoint + API key in .env. Fallback: Twilio voor pure SMS zonder WhatsApp. |
2.2 Frontend
| Component | Keuze / Versie |
|---|---|
| Language | TypeScript 5.9.3 |
| Framework | Vue 3.5.22 (Composition API + <script setup>) |
| Build | Vite 7.1.12 |
| UI Template | Vuexy 9.5.0 (Vue.js Admin Template) + Vuetify 3.10.8 |
| State | Pinia 3.0.3 |
| Router | Vue Router 4.5.1 |
| HTTP / API State | Axios ^1.13.2 + TanStack Query / Vue Query (te installeren: @tanstack/vue-query) |
| i18n | Vue I18n 11.1.12 |
| Calendar / Timetable | FullCalendar (al in stack) — timeline view voor stage timetable |
| Charts | ApexCharts (al in stack) |
| Rich Text | TipTap (al in stack) — voor briefing template builder |
| Drag & Drop | VueDraggable (Sortable.js wrapper) voor form builder en timetable |
| Maps | Leaflet.js of Google Maps embed voor locatie in shift briefing PDF |
| Forms | VeeValidate + Zod (te installeren) voor type-safe form validatie |
2.3 Applicatie-frontends
Er zijn meerdere Vue-applicaties in de monorepo. Elke app heeft een eigen scope:
| App | Doel en doelgroep |
|---|---|
| admin/ | Super Admin dashboard: organisatiebeheer, billing, platform-instellingen. Toegang: Super Admin only. |
| app/ | Hoofdapplicatie voor Organisation Admins, Event Managers en alle interne rollen. Volledig featured. |
| portal/ | Externe portals op basis van subdomain routing: volunteer.crewli.app/[slug], advance.crewli.app/[slug], briefing.crewli.app/[slug]. Server-rendered + PWA. |
2.4 Te installeren packages
| Package | Doel |
|---|---|
| spatie/laravel-permission | Rol- en permissiebeheer (App + Org + Event niveau) |
| @tanstack/vue-query | API state management in Vue frontends (caching, loading states, optimistic updates) |
| vee-validate + zod | Type-safe formuliervalidatie in Vue |
| vuedraggable@next | Drag-and-drop voor form builder, timetable, prioriteitsranking |
| Zender HTTP API (custom Laravel ZenderService) | SMS + WhatsApp verzenden via zelf-gehoste Zender. Config: ZENDER_API_URL + ZENDER_API_KEY. |
| endroid/qr-code | QR-code generatie voor e-tickets en allocatiesheets |
| spatie/laravel-media-library | Bestandsbeheer (uploads, conversions, signed URLs) |
| barryvdh/laravel-dompdf of spatie/browsershot | Server-side PDF generatie (briefings, allocatiesheets) |
3. Systeem Architectuur
Multi-tenancy, rollen, en data-isolatie
3.1 Multi-Tenant Data Model
Crewli gebruikt een gedeeld database-schema (shared schema) met organisatie-scoping op alle tabellen. Er is geen row-level security op databaseniveau; scoping wordt afgedwongen via Laravel policies en Global Scopes.
| SCOPING REGEL | Elke query op event-data MOET een organisation_id scope hebben. Global Scopes in Eloquent models zorgen dat dit automatisch wordt toegepast. Nooit direct queryen zonder organisatiecontext. |
3.1.1 Tenancy Hierarchy
De hiërarchie van boven naar beneden:
Platform (Super Admin) └─ Organisation (klant A) └─ Event (evenement 1) └─ Festival Section (Bar, Hospitality, Technical, ...) ├─ Time Slots (DAY1-EARLY-CREW, DAY1-EARLY-VOLUNTEER, ...) └─ Shifts (Bar × DAY1-EARLY-VOLUNTEER, 5 slots)
3.2 Drie-niveau Rol- en Permissiemodel
Gebruikersaccounts zijn platform-breed en uniek per e-mailadres. Eén account geeft toegang tot alle organisaties en evenementen waarvoor een gebruiker is uitgenodigd — elk met een eigen rol. Er is geen aparte registratie per organisatie. Via Spatie laravel-permission worden rollen op drie niveaus beheerd:
| Niveau | Scope | Voorbeeld Rollen |
|---|---|---|
| App Level | Geldig voor het hele platform, ongeacht organisatie. | super_admin, support_agent |
| Organisation Level | Geldig binnen één specifieke organisatie. | org_admin, org_member, org_readonly |
| Event Level | Geldig binnen één specifiek evenement. | event_manager, artist_manager, staff_coordinator, volunteer_coordinator, accreditation_officer |
| IMPLEMENTATIE | Gebruik Spatie's team-based permissions: elke Organisation is een 'team'. Event-level rollen worden opgeslagen in een pivot tabel: user_event_roles (user_id, event_id, role_id). Middleware: OrganisationRoleMiddleware en EventRoleMiddleware controleren per route. |
3.3 Gebruikersaccount Model & Uitnodigingsflow
Crewli hanteert een strict één-account-per-e-mailadres model. Er bestaat geen account per organisatie of per evenement — een account is platform-breed en wordt gekoppeld aan organisaties en evenementen via uitnodigingen.
3.3.1 Account aanmaakstrategieën
| Gebruikerstype | Account aanmaak methode |
|---|---|
| Interne medewerkers (staff, coordinatoren) | Organisator nodigt uit via e-mail. Ontvanger accepteert uitnodiging via link. Nieuw account aangemaakt óf bestaand account gekoppeld als e-mail al bestaat. |
| Vrijwilligers | Twee paden: (1) Organisator nodigt uit via e-mail (zelfde flow als intern). (2) Vrijwilliger vult publiek registratieformulier in → na goedkeuring door organisator wordt automatisch een account aangemaakt (of gekoppeld als e-mail al bestaat) en login-credentials worden verstuurd. |
| Artiesten / Tour managers | Ontvangen een tokengebaseerde link naar het advance portaal. Geen platform-account vereist — authenticatie via uniek token in URL. |
| Leveranciers / Partners | Ontvangen een tokengebaseerde link naar het leveranciersportaal. Geen platform-account vereist. |
3.3.2 Uitnodigingsflow (intern)
-
Organisator voert e-mailadres in en selecteert rol (organisatie- of event-niveau).
-
Systeem controleert: bestaat er al een account met dit e-mailadres?
-
Nee → uitnodigingsmail verstuurd met activatielink (24 uur geldig). Account aangemaakt na activatie.
-
Ja → uitnodigingsmail verstuurd. Na acceptatie wordt de bestaande gebruiker gekoppeld aan de nieuwe organisatie/event-rol. Geen nieuw account.
-
Gebruiker kan in het platform schakelen tussen alle organisaties waar hij/zij toegang toe heeft via een organisatieswitcher in de navigatie.
3.3.3 Vrijwilligersregistratie + goedkeuringsflow
-
Vrijwilliger vult publiek registratieformulier in (multi-step, zie sectie 4.4).
-
Na submit: Person record aangemaakt met status = 'pending'. Organisator ontvangt notificatie.
-
Organisator beoordeelt en keurt goed (of wijst af). Status → 'approved'.
-
Bij goedkeuring: systeem controleert of e-mailadres al een platform-account heeft.
-
Ja → bestaand account gekoppeld aan event als vrijwilliger. Bevestigingsmail met inloglink.
-
Nee → nieuw account aangemaakt. Welkomstmail met tijdelijk wachtwoord of magic link.
-
-
Vrijwilliger logt in op het volunteer portal en ziet eigen shifts, formulieren en event-informatie.
3.3.4 Claim Shift goedkeuringsflow
Wanneer een vrijwilliger een shift claimt via het portal, wordt een goedkeuringsworkflow geactiveerd:
-
Vrijwilliger claimt shift → shift_assignment aangemaakt met status = 'pending_approval'.
-
Relevante event-beheerder/coordinator ontvangt notificatie (in-app + e-mail).
-
Coordinator keurt goed → status = 'approved'. Vrijwilliger ontvangt bevestigingsmail.
-
Coordinator wijst af → status = 'rejected'. Vrijwilliger ontvangt afwijzingsmail met optionele reden. Slot komt weer beschikbaar.
-
Optioneel per shift: auto-approve instellen. Bij auto-approve: status direct 'approved' na claim, zonder handmatige stap.
| SHIFT STATUS ENUM | pending_approval → approved → completed | rejected (eindstatus) | cancelled (door vrijwilliger of organisator) |
3.4 Event Lifecycle
Elk evenement doorloopt een vaste lifecycle. De dashboard UI en beschikbare acties passen zich automatisch aan de huidige fase aan:
| Fase | Beschrijving & actieve modules |
|---|---|
| Draft | Evenement aangemaakt maar niet gepubliceerd. Alleen beheerder ziet het. Instellingen worden geconfigureerd. |
| Published | Evenement actief in planning. Alle interne modules beschikbaar. Externe portals nog gesloten. |
| Registration Open | Vrijwilligersregistratie en artist advance portals zijn open. Externe deelnemers kunnen zich aanmelden. |
| Build-Up | Opbouwdagen. Crew-shifts beginnen. Accreditatie uitgifte start. |
| Show Day(s) | Actieve eventdagen. Mission Control actief. Real-time check-in. Timetable live. |
| Tear-Down | Afbouwdagen. Inventaris terugname. Afsluiting van shifts. |
| Closed | Evenement afgerond. Read-only. Rapporten beschikbaar. Data wordt gearchiveerd. |
3.5 Core Database Schema
Onderstaand het volledige, productie-waardige datamodel. Alle 12 bevindingen uit de database review (v1.3) zijn verwerkt. Tabellen zijn gegroepeerd per domein.
| PRIMAIRE SLEUTELS: ULID | Alle tabellen gebruiken ULID (Universally Unique Lexicographically Sortable Identifier) als primaire sleutel — GEEN UUID v4. Reden: UUID v4 is random, wat B-tree index-fragmentatie veroorzaakt in InnoDB bij elke INSERT. ULID is monotoon stijgend (tijd-geordend) en behoudt index-lokaliteit. Laravel: gebruik Str::ulid() of de HasUlids trait. Migraties: $table->ulid('id')->primary(). Extern zichtbare ID's (URL's, barcodes, API) gebruiken ULID. Interne pivot tabellen mogen auto-increment integer PK gebruiken voor join-performance. |
3.5.1 Kern (foundation)
| Tabel | Belangrijkste kolommen | Relaties, constraints & opmerkingen |
|---|---|---|
| users | id (ULID), name, email, password, timezone, locale, avatar, email_verified_at, deleted_at | HasUlids trait. belongsToMany organisations (via organisation_user), belongsToMany events (via event_user_roles). Soft delete: ja. |
| organisations | id (ULID), name, slug, billing_status, settings (JSON: display prefs only), created_at, deleted_at | hasMany events, crowd_types, accreditation_categories. settings JSON alleen voor opaque UI-config, niet voor queryable data. Soft delete: ja. |
| organisation_user | id (int AI), user_id, organisation_id, role | Pivot. Integer PK voor join-performance. FK: users, organisations. Spatie role via pivot. |
| user_invitations | id (ULID), email, invited_by_user_id, organisation_id, event_id (nullable), role, token (ULID, unique), status (pending|accepted|expired), expires_at | Token in uitnodigingsmail. Bij accept: zoek bestaand account op email of maak nieuw aan. INDEX: (token), (email, status). |
| events | id (ULID), organisation_id, name, slug, start_date, end_date, timezone, status (draft|published|registration_open|buildup|showday|teardown|closed), deleted_at | belongsTo organisation. hasMany festival_sections, time_slots, persons, artists, briefings. Soft delete: ja. INDEX: (organisation_id, status). |
| event_user_roles | id (int AI), user_id, event_id, role | Pivot. Integer PK. FK: users, events. |
3.5.2 Locaties (nieuw — oplossing probleem 3)
De locations tabel was gerefereerd door shifts maar niet gedefinieerd. Locaties zijn event-scoped en herbruikbaar over secties.
| Tabel | Belangrijkste kolommen | Relaties, constraints & opmerkingen |
|---|---|---|
| locations | id (ULID), event_id, name, address, lat (decimal 10,8), lng (decimal 11,8), description, access_instructions | Herbruikbare locaties per event. Gerefereerd door shifts.location_id. INDEX: (event_id). |
3.5.3 Festival Sections, Time Slots & Shifts
Drielaags Crescat-model. Kritieke verbetering: time_slot_id gedenormaliseerd naar shift_assignments voor DB-afdwingbare conflictdetectie (probleem 2). Shift-swaps gesplitst in twee tabellen (probleem 10).
| Tabel | Belangrijkste kolommen | Relaties, constraints & opmerkingen |
|---|---|---|
| festival_sections | id (ULID), event_id, name, sort_order, deleted_at | hasMany shifts. Soft delete: ja. INDEX: (event_id, sort_order). |
| time_slots | id (ULID), event_id, name, person_type (CREW|VOLUNTEER|PRESS|PHOTO|PARTNER), date, start_time, end_time, duration_hours | hasMany shifts. person_type bepaalt zichtbaarheid in registratieformulier. INDEX: (event_id, person_type, date). |
| shifts | id (ULID), festival_section_id, time_slot_id, location_id, slots_total, slots_open_for_claiming, assigned_crew_id, events_during_shift (JSON: array of performance_ids), status, deleted_at | belongsTo festival_section, time_slot, location. hasMany shift_assignments. JSON hier OK: performance_ids zijn opaque referentie-lijst zonder filtering. INDEX: (festival_section_id, time_slot_id), (time_slot_id, status). Soft delete: ja. |
| shift_assignments | id (ULID), shift_id, person_id, time_slot_id (FK: gedenormaliseerd), status (pending_approval|approved|rejected|cancelled|completed), auto_approved, assigned_by, assigned_at, approved_by, approved_at, rejection_reason, deleted_at | UNIEK constraint: UNIQUE(person_id, time_slot_id) — DB-afdwingbare conflictdetectie. time_slot_id gedenormaliseerd van shifts voor deze constraint. Soft delete: ja. INDEX: (shift_id, status), (person_id, status), (person_id, time_slot_id). |
| volunteer_availabilities | id (ULID), person_id, time_slot_id, submitted_at | Vrijwilliger kiest beschikbare Time Slots. Basis voor shift-matching. UNIQUE(person_id, time_slot_id). INDEX: (time_slot_id). |
| shift_absences | id (ULID), shift_assignment_id, person_id, reason (sick|personal|other), reported_at, status (open|filled|closed), closed_at | Nieuw (splitsing probleem 10). Vrijwilliger meldt zich af — shift_slot komt vrij. Triggert wachtlijst-notificatie. INDEX: (shift_assignment_id), (status). |
| shift_swap_requests | id (ULID), from_assignment_id, to_person_id, message, status (pending|accepted|rejected|cancelled|completed), reviewed_by, reviewed_at, auto_approved | Nieuw (splitsing probleem 10). A vraagt B om te ruilen. Na akkoord beide: coordinator bevestigt (of auto-approve). INDEX: (from_assignment_id), (to_person_id, status). |
| shift_waitlist | id (ULID), shift_id, person_id, position (int), added_at, notified_at | Wachtlijst per shift. UNIQUE(shift_id, person_id). Bij uitval: positie 1 automatisch aangeschreven. INDEX: (shift_id, position). |
3.5.4 Vrijwilligersprofiel & Geschiedenis
| Tabel | Belangrijkste kolommen | Relaties, constraints & opmerkingen |
|---|---|---|
| volunteer_profiles | id (ULID), user_id (FK unique), bio, photo_url, tshirt_size, first_aid (bool), driving_licence (bool), allergies, access_requirements, emergency_contact_name, emergency_contact_phone, reliability_score (decimal 3,2), is_ambassador | Platform-breed, 1-op-1 met users. reliability_score 0.00-5.00, berekend via scheduled job. UNIQUE(user_id). |
| volunteer_festival_history | id (ULID), user_id, event_id, organisation_id, hours_planned, hours_completed, no_show_count, coordinator_rating (tinyint 1-5), coordinator_notes, would_reinvite (bool) | Per gebruiker per festival. Nooit zichtbaar voor vrijwilliger zelf. INDEX: (user_id, event_id), UNIQUE(user_id, event_id). |
| post_festival_evaluations | id (ULID), event_id, person_id, shift_id (nullable), overall_rating (tinyint 1-5), shift_rating (tinyint 1-5), would_return (bool), feedback_text, improvement_suggestion, submitted_at, is_anonymous | Vrijwilliger evalueert na afloop. INDEX: (event_id, is_anonymous), (person_id). |
| festival_retrospectives | id (ULID), event_id (unique), generated_at, volunteers_planned (int), volunteers_completed (int), no_show_count (int), no_show_pct (decimal 5,2), avg_overall_satisfaction (decimal 3,2), avg_shift_satisfaction (decimal 3,2), would_return_pct (decimal 5,2), sections_understaffed (int), sections_overstaffed (int), top_feedback (JSON: array of strings), notes (text) | Oplossing probleem 8: alle KPIs als concrete kolommen ipv JSON blob. Trendanalyse over meerdere jaren mogelijk. JSON alleen voor vrije-tekst feedback array. |
3.5.5 Crowd Types, Persons & Crowd Lists
Oplossing probleem 1 (identiteitsfragmentatie): persons krijgt user_id (nullable) als canonieke koppeling naar platform-account. Oplossing probleem 4: crowd_list_persons pivot toegevoegd. Oplossing probleem 9: persons.email als geindexeerde deduplicatie-sleutel.
| Tabel | Belangrijkste kolommen | Relaties, constraints & opmerkingen |
|---|---|---|
| crowd_types | id (ULID), organisation_id, name, system_type (CREW|GUEST|ARTIST|VOLUNTEER|PRESS|PARTNER|SUPPLIER), color, icon, is_active | Org-level configuratie. INDEX: (organisation_id, system_type). |
| persons | id (ULID), user_id (nullable FK users), event_id, crowd_type_id, company_id (nullable), name, email, phone, status (invited|applied|pending|approved|rejected|no_show), is_blacklisted, admin_notes, custom_fields (JSON), deleted_at | user_id nullable: externe gasten/artiesten hebben geen platform-account. UNIQUE(event_id, user_id) WHERE user_id IS NOT NULL. INDEX: (event_id, crowd_type_id, status), (email, event_id), (user_id, event_id). custom_fields JSON OK: event-specifieke velden, niet queryable. Soft delete: ja. |
| companies | id (ULID), organisation_id, name, type (supplier|partner|agency|venue|other), contact_name, contact_email, contact_phone, deleted_at | Gedeeld over events binnen org. Soft delete: ja. INDEX: (organisation_id). |
| crowd_lists | id (ULID), event_id, crowd_type_id, name, type (internal|external), recipient_company_id (nullable), auto_approve (bool), max_persons (int nullable) | hasMany persons via crowd_list_persons pivot. INDEX: (event_id, type). |
| crowd_list_persons | id (int AI), crowd_list_id, person_id, added_at, added_by_user_id | Nieuw pivot (oplossing probleem 4). Koppelt person aan crowd_list. UNIQUE(crowd_list_id, person_id). INDEX: (person_id). |
3.5.6 Accreditatie Engine
Oplossing probleem 5: event_accreditation_items activeert org-level items per event. Accreditatie-items zijn nu org-level geconfigureerd en per event geactiveerd met event-specifieke limieten.
| Tabel | Belangrijkste kolommen | Relaties, constraints & opmerkingen |
|---|---|---|
| accreditation_categories | id (ULID), organisation_id, name, sort_order, icon | Org-level. Bijv. Wristband, Food & Beverage, Communication, Clothing. INDEX: (organisation_id). |
| accreditation_items | id (ULID), accreditation_category_id, name, is_date_dependent (bool), barcode_type (qr|code128|ean13), ticket_visual_url, cost_price (decimal 8,2), sort_order | Org-level items. Worden per event geactiveerd via event_accreditation_items. |
| event_accreditation_items | id (ULID), event_id, accreditation_item_id, max_quantity_per_person (int nullable), total_budget_quantity (int nullable), is_active (bool), notes | Nieuw (oplossing probleem 5). Activeert een org-level item voor een specifiek event met event-specifieke limieten. UNIQUE(event_id, accreditation_item_id). INDEX: (event_id, is_active). |
| accreditation_assignments | id (ULID), person_id, accreditation_item_id, event_id, date (nullable, voor date_dependent items), quantity, is_handed_out, handed_out_at, handed_out_by_user_id | Bijhouding per persoon per item. FK naar event_accreditation_items voor validatie. INDEX: (person_id, event_id), (accreditation_item_id, is_handed_out). |
| access_zones | id (ULID), event_id, name, zone_code (varchar 20, unique per event), description | Bijv. Backstage, VIP, Main Stage. Dag-koppeling via access_zone_days pivot. INDEX: (event_id). |
| access_zone_days | id (int AI), access_zone_id, day_date (date) | Nieuw (oplossing probleem 8: vervangt JSON days kolom). Queryable: welke zones zijn actief op datum X? UNIQUE(access_zone_id, day_date). INDEX: (day_date). |
| person_access_zones | id (int AI), person_id, access_zone_id, valid_from (datetime), valid_to (datetime nullable) | Pivot: toegangsrecht per persoon per zone. INDEX: (person_id), (access_zone_id). |
3.5.7 Artists & Advancing
Oplossing probleem 8: stages.active_days JSON vervangen door stage_days pivot. milestone_flags JSON blijft (opaque toggle-set, nooit gefilterd).
| Tabel | Belangrijkste kolommen | Relaties, constraints & opmerkingen |
|---|---|---|
| artists | id (ULID), event_id, name, booking_status (concept|requested|option|confirmed|contracted|cancelled), star_rating (tinyint 1-5), project_leader_id, milestone_flags (JSON), advance_open_from, advance_open_to, portal_token (ULID unique), deleted_at | portal_token: toegang artiestenportaal zonder account. hasMany performances, advance_sections, artist_contacts, artist_riders. milestone_flags JSON OK: binaire toggle-set. Soft delete: ja. |
| performances | id (ULID), artist_id, stage_id, date, start_time, end_time, booking_status, check_in_status (expected|checked_in|no_show) | B2B detectie via overlap-query op stage_id + datum + tijdvenster. INDEX: (stage_id, date, start_time, end_time). |
| stages | id (ULID), event_id, name, color (hex), capacity (int nullable) | Dag-activatie via stage_days pivot (oplossing probleem 8). hasMany performances. INDEX: (event_id). |
| stage_days | id (int AI), stage_id, day_date (date) | Nieuw (oplossing probleem 8: vervangt stages.active_days JSON). UNIQUE(stage_id, day_date). |
| advance_sections | id (ULID), artist_id, name, type (guest_list|contacts|production|custom), is_open, open_from, open_to, sort_order | Crescat sectie-model. Elke sectie onafhankelijk submitbaar. INDEX: (artist_id, is_open). |
| advance_submissions | id (ULID), advance_section_id, submitted_by_name, submitted_by_email, submitted_at, status (pending|accepted|declined), reviewed_by, reviewed_at, data (JSON) | data JSON OK: vrije formulierdata, niet queryable. INDEX: (advance_section_id, status). |
| artist_contacts | id (ULID), artist_id, name, email, phone, role, receives_briefing (bool), receives_infosheet (bool) | Tour manager, agent, booker. INDEX: (artist_id). |
| artist_riders | id (ULID), artist_id, category (technical|hospitality), items (JSON) | items JSON OK: ongestructureerde rider-data. INDEX: (artist_id, category). |
| itinerary_items | id (ULID), artist_id, type (transfer|pickup|delivery|checkin|performance), datetime, from_location, to_location, notes | Vluchten/hotels: Out of Scope. INDEX: (artist_id, datetime). |
3.5.8 Communicatie & Briefings
Oplossing probleem 11: broadcast_messages uitgebreid met polymorfisch broadcast_message_targets voor flexibele doelgroep-definitie.
| Tabel | Belangrijkste kolommen | Relaties, constraints & opmerkingen |
|---|---|---|
| briefing_templates | id (ULID), event_id, name, type (crowd|artist|volunteer|supplier), blocks (JSON), is_default | blocks JSON OK: drag-and-drop blok-configuratie, nooit gefilterd. INDEX: (event_id, type). |
| briefings | id (ULID), event_id, briefing_template_id, name, target_crowd_types (JSON), send_from, send_until, status (draft|queued|sending|sent|paused) | target_crowd_types JSON OK: array van crowd_type IDs. INDEX: (event_id, status). |
| briefing_sends | id (ULID), briefing_id, person_id, status (queued|sent|opened|downloaded), sent_at, opened_at | Track per persoon per briefing. INDEX: (status, briefing_id) — queue processing. INDEX: (person_id). |
| communication_campaigns | id (ULID), event_id, type (email|sms|whatsapp), name, body, recipient_group (JSON), status (draft|scheduled|sending|sent|cancelled), scheduled_at, sent_at, sent_count, failed_count | Bulk campagnes. SMS+WhatsApp via Zender. recipient_group JSON: beschrijving doelgroep-filter. INDEX: (event_id, type, status). |
| messages | id (ULID), event_id, sender_user_id, recipient_person_id, body, urgency (normal|urgent|emergency), channel_used (email|sms|whatsapp), read_at, replied_at, created_at | 1-op-1 berichten. urgency bepaalt kanaal via ZenderService. INDEX: (event_id, recipient_person_id), (recipient_person_id, read_at). |
| message_replies | id (ULID), message_id, person_id, body, status_update (on_my_way|arrived|sick|other), created_at | Vrijwilliger reageert via portal. INDEX: (message_id). |
| broadcast_messages | id (ULID), event_id, sender_user_id, body, urgency, channel_used, sent_at, recipient_count, read_count | Groepsbericht. Doelgroep via broadcast_message_targets (oplossing probleem 11). INDEX: (event_id, sent_at). |
| broadcast_message_targets | id (int AI), broadcast_message_id, target_type (event|section|shift|crowd_type|custom_list), target_id (ULID nullable) | Nieuw polymorfisch target model (oplossing probleem 11). Meerdere targets per bericht mogelijk. target_id NULL bij type=event (heel event). INDEX: (broadcast_message_id). |
3.5.9 Formulieren, Check-In & Operationeel
| Tabel | Belangrijkste kolommen | Relaties, constraints & opmerkingen |
|---|---|---|
| public_forms | id (ULID), event_id, name, crowd_type_id, fields (JSON), conditional_logic (JSON), iframe_token (ULID unique), confirmation_email_template, is_active | fields + conditional_logic JSON OK: formulier-configuratie, niet gefilterd. INDEX: (event_id, crowd_type_id, is_active). |
| form_submissions | id (ULID), public_form_id, person_id, data (JSON), submitted_at | data JSON OK: vrije formulierresultaten. INDEX: (public_form_id, submitted_at), (person_id). |
| check_ins | id (ULID), event_id, person_id, scanned_by_user_id, scanner_id, scanned_at, location_id | Immutable audit-record: GEEN soft delete. INDEX: (event_id, person_id, scanned_at), (event_id, scanned_at). |
| show_day_absence_alerts | id (ULID), event_id, shift_id, person_id, alert_sent_at, response_status (no_response|confirmed|absent|late), resolved_at | Immutable audit-record: GEEN soft delete. INDEX: (shift_id, response_status), (event_id, alert_sent_at). |
| scanners | id (ULID), event_id, name, type (crowd|zone|accreditation), scope (JSON), pairing_code (varchar 8, unique), last_active_at | scope JSON OK: scanner-configuratie. INDEX: (event_id), (pairing_code). |
| inventory_items | id (ULID), event_id, name, item_code (varchar 50), assigned_to_person_id (nullable), assigned_at, returned_at, returned_by_user_id | Portofoons, hesjes, sleutels. INDEX: (event_id, assigned_to_person_id), (item_code). |
| event_info_blocks | id (ULID), event_id, type (description|route|parking|contacts|marketing|custom), title, content (text), files (JSON), sort_order, is_published | files JSON OK: array van bestandspaden. Zichtbaarheid per crowd_type via event_info_block_crowd_types. INDEX: (event_id, type, is_published). |
| event_info_block_crowd_types | id (int AI), event_info_block_id, crowd_type_id | Pivot: welke crowd_types zien welk info-blok. Vervangt visible_to_crowd_types JSON (oplossing probleem 8). UNIQUE(event_info_block_id, crowd_type_id). |
| production_requests | id (ULID), event_id, company_id, title, status (draft|sent|in_progress|submitted|approved|rejected), token (ULID unique), sent_at, submitted_at, reviewed_by, reviewed_at, deleted_at | Nieuw (oplossing probleem 3: ontbrekende tabel). Hoofdrecord per leverancier. token: portaaltoegang zonder account. hasMany material_requests. INDEX: (event_id, status), (company_id). |
| material_requests | id (ULID), production_request_id, category (heavy_equipment|tools|vehicles|other), name, description, quantity (int), period_from, period_to, status (requested|approved|rejected|fulfilled), notes | Onderdeel van production_request. INDEX: (production_request_id, status). |
3.5.10 Database Ontwerpregels & Index Strategie
| REGEL 1 — ULID als primaire sleutel | Alle business-tabellen: $table->ulid('id')->primary() + HasUlids trait. Pure pivot/koppel-tabellen (geen eigen lifecycle): $table->id() (auto-increment integer) voor join-performance. Nooit UUID v4 — vermijdt InnoDB B-tree fragmentatie. |
| REGEL 2 — JSON kolommen: wanneer wel, wanneer niet | WEL JSON: opaque configuratie (blocks, fields, settings, items), toggle-sets (milestone_flags), vrije tekst arrays (top_feedback). NOOIT JSON voor: datums/periodes, status-waarden, foreign keys, boolean flags, alles waarop je filtert/sorteert/aggregeert. Voorbeelden vervangen: access_zone_days (was days JSON), stage_days (was active_days JSON), broadcast_message_targets (was target JSON), event_info_block_crowd_types (was visible_to_crowd_types JSON), festival_retrospectives kolommen (waren data JSON blob). |
| REGEL 3 — Soft delete strategie | Soft delete (deleted_at) op: organisations, events, festival_sections, shifts, shift_assignments, persons, artists, companies, production_requests. GEEN soft delete op: check_ins, show_day_absence_alerts, briefing_sends, message_replies, audit_log, shift_waitlist, volunteer_festival_history. Rationale: audit-records zijn immutable. Soft delete op audit-records geeft een vals beeld van de werkelijkheid. |
| REGEL 4 — Verplichte indexes (minimum set) | persons: (event_id, crowd_type_id, status), (email, event_id), (user_id, event_id). shift_assignments: UNIQUE(person_id, time_slot_id), (shift_id, status), (person_id, status). check_ins: (event_id, person_id, scanned_at), (event_id, scanned_at). briefing_sends: (status, briefing_id) — queue processing. shift_waitlist: (shift_id, position). performances: (stage_id, date, start_time, end_time) — B2B overlap detectie. Voeg EXPLAIN ANALYZE toe aan queries die > 100ms duren. Target: alle lijst-queries < 50ms. |
| REGEL 5 — Multi-tenancy scoping | Elke query op event-data MOET scopen op organisation_id via Eloquent Global Scope (OrganisationScope). Gebruik Laravel policies voor autorisatie: nooit directe id-checks in controllers. Audit log: Spatie laravel-activitylog op: persons, accreditation_assignments, shift_assignments, check_ins, production_requests. |
4. Functionele Modules
Gedetailleerde beschrijving van alle modules
4.1 Dashboard & Event Health Check
Het event dashboard is de landing page voor elke event-context. Het past zijn primaire CTA aan op basis van de huidige event lifecycle fase en toont een overzicht van openstaande acties.
Kerncomponenten van het dashboard:
-
Attention matrix: tabel met crowd types als rijen en 'Pending approval', 'Missing briefing', 'Incomplete accreditation', 'Attention required' als kolommen. Elke cel toont een klikbaar getal. (In2Event patroon)
-
Event Health widget: configuratieproblemen als oranje/rode items met directe actielinks: niet-geverifieerde e-mailadressen, accreditatiegoed keuring achterstand, artiesten met ontbrekende riders, productieaanvragen zonder reactie. (WeezCrew patroon)
-
Fase-aware CTA banner: verandert per event fase. Show Day: 'Ga naar Mission Control'. Planning: 'Openstaande goedkeuringen'. Registratie: 'Vrijwilligersregistraties bekijken'.
-
Bezetting per dag: staafdiagram (ApexCharts) met crew/volunteer bezetting per eventdag. Toont over-/onderbezette dagen in een oogopslag.
-
Shifts Per Day tabel: per sectie en per dag: #Assigned/#Count voor Crew en Volunteers apart. (Crescat Festival Section dashboard patroon)
4.2 Festival Sections
Festival Sections zijn de operationele eenheden binnen een evenement. Elke sectie heeft een eigen tabblad-gebaseerde interface met: Dashboard, Shifts, Scheduler, Crew, Volunteers, Info Section, Accreditation.
Sectie dashboard (per section):
-
Filled Shifts teller: X/Y shifts gevuld. Opgesplitst Crew en Volunteers.
-
Per-dag tabel: #Assigned/#Count, Executed/Planned uren per dag, voor zowel Crew als Volunteers (Responders).
-
Assigned summary rechts: Status (Full/Missing), Missing count, Assigned/Required ratio. Apart voor Crew en Volunteers.
-
'Open' knop bij Volunteers: opent het publieke vrijwilligersregistratieformulier voor die sectie.
Shifts tab (binnen een sectie):
-
Datum-navigatie aan de linker zijkant: per dag met samenvatting Shifts X/Y, Slots X/Y.
-
Shift lijst: gegroepeerd per Time Slot naam. Per shift: When (tijd + Time Slot naam), Title, Where (locatie), Events (artiesten tijdens shift), Assigned Crew (supervisor + status), Assigned/Slots badge, For Sale count, actieknoppen (Assign, Add Shift, menu).
-
'+ Create Shift' button: maakt een nieuwe shift aan gekoppeld aan een bestaand Time Slot.
-
Assign Shift side panel: Title, Where, Assigned Crew (dropdown). When sectie: Time Slot selector + datum + tijden (overgenomen van Time Slot). Slots sectie: totaal slots input, slots open for claiming input, Assigned counter, Available counter. Assignees grid: tot 6 personen zichtbaar met email/telefoon iconen.
4.3 Time Slots & Shift Planning Model
Dit is een van de meest onderscheidende architecturale keuzes van Crewli, gebaseerd op de Crescat aanpak zoals geanalyseerd uit de screenshots.
| HET MODEL IN EEN ZIN | Time Slots = het WAN-NEER (event-niveau, per persoonscategorie). Shifts = het WAT+WAAR+HOEVEEL (sectie-niveau, met capaciteit). Vrijwilligers kiezen Time Slots. Organisator maakt Shifts per Time Slot per sectie. |
Time Slot aanmaken (organisator):
-
Naam: beschrijvend, incl. persoonscategorie. Bijv. 'DAY 1 - EARLY - VOLUNTEER', 'FRIDAY - PRESS'.
-
Persoonscategorie (person_type): CREW | VOLUNTEER | PRESS | PHOTO | PARTNER. Bepaalt wie dit slot ziet in registratieformulier.
-
Datum, start- en eindtijd, berekende duur.
-
Time Slots worden éénmalig aangemaakt op event-niveau en hergebruikt door alle Festival Sections.
Shift aanmaken (per Festival Section):
-
Selecteer een Time Slot (erft datum + tijden + persoonscategorie).
-
Kies locatie/venue.
-
Stel capaciteit in: slots_total (totaal) en slots_open_for_claiming (claimbaar via portal).
-
Wijs supervisor toe (Assigned Crew).
-
Events during Shift: artiesten die op dat tijdstip spelen (automatisch gevuld vanuit timetable).
Shift claiming (vrijwilliger, via portal):
-
Vrijwilliger ziet alleen shifts waarvan de Time Slot person_type = VOLUNTEER.
-
Vrijwilliger ziet alleen shifts met slots_open_for_claiming > 0.
-
Conflictdetectie: als vrijwilliger al een shift heeft op hetzelfde time_slot_id, is die shift als '(Time Conflict)' gemarkeerd en niet claimbaar.
-
Vrijwilliger ziet welke artiesten er spelen tijdens de shift — dit vergroot betrokkenheid.
Toewijzingsstrategieën (drie opties per shift):
-
Volledig gecontroleerd: slots_open_for_claiming = 0. Organisator wijst handmatig toe. Shift zichtbaar in organisator backend, niet in volunteer portal.
-
Volledig zelfservice: slots_open_for_claiming = slots_total. Vrijwilligers vullen alle plekken zelf.
-
Hybride: slots_open_for_claiming < slots_total. Bijv. 5 plekken totaal, 3 open voor claiming, 2 gereserveerd voor handmatige toewijzing (vaste krachten).
4.4 Vrijwilligersbeheer & Registratieportaal
Vrijwilligers zijn de kern van elke festival-organisatie. Dit module ontlast de organisator maximaal bij de administratie en bouwt binding en trots op bij de vrijwilliger zelf. Vrijwilligers zijn geen datapunten — ze zijn de ambassadeurs van jullie festival.
4.4.1 Publiek registratieformulier (meerdelige structuur):
-
Deel 1 — Over jou: Naam, e-mail, telefoon (met landcode).
-
Deel 2 — Meer over jou: Shirtmaat, EHBO, allergieën, toegangsbehoeften, rijbewijs. Geconfigureerd via de formulierbouwer.
-
Deel 3 — Motivatie: Waarom wil je vrijwilliger zijn? Dropdown + vrije tekst.
-
Deel 4 — Voorkeurssecties: Selecteer secties en rangschik prioriteit 1-5 (drag-to-prioritize).
-
Deel 5 — Beschikbaarheid: Selecteer Time Slots (gefilterd op VOLUNTEER type). Toont minimumurendrempel voor festivalpas.
-
Deel 6 — Admin only: Blacklist toggle, betaalstatus, algemene notities.
4.4.2 Vrijwilligersprofiel (platform-breed, suggestie 1):
Elke vrijwilliger heeft één platform-breed profiel. Eenmalig invullen, herbruikbaar bij elk volgend festival — ook bij andere organisaties.
-
Profielfoto, bio, contactgegevens, EHBO-certificaat, rijbewijs, shirtmaat, noodcontact.
-
Festival-paspoort: visuele tijdlijn van alle deelgenomen festivals. '5 festivals, 84 uur, altijd bij Hospitality.'
-
Intern prestatiebadge systeem (nooit zichtbaar voor vrijwilliger zelf): Betrouwbaar (0 no-shows), Veteraan (5+ festivals), EHBO-gecertificeerd, Terugkerende kracht, Ambassador.
-
reliability_score (0.0–5.0): automatisch berekend op basis van no-shows, tijdigheid check-in en coordinator-ratings over alle festivals.
-
Bij herinvitatie: organisator ziet reliability score, festival-geschiedenis en 'would_reinvite' oordeel van vorig jaar — direct naast de naam in de lijst.
4.4.3 Organisator back-end (per vrijwilliger):
-
Side panel tabs: General Info, Responder, Shifts, Communication, Accreditations, Openings, Documents, History.
-
Work Hours tracker: Expected / Assigned / Completed uren.
-
History tab: overzicht alle voorgaande festivals. Per festival: geplande uren, voltooide uren, no-shows, coordinator-rating (sterren), notities. Reliability score als visuele scorebalk.
-
Herinvitatie-snelheid indicator: hoe snel reageerde deze persoon op vorige uitnodigingen? Hoge snelheid = stuur als eerste uit.
4.4.4 Vrijwilligersportaal (zelfservice):
-
Home: persoonlijke welkomstboodschap met festivalnaam, shifts-samenvatting en openstaande acties ('Je claim wacht op goedkeuring').
-
My Shifts: per datum. Supervisor, locatie, artiesten tijdens shift. Knop: 'Ik kan toch niet komen' (triggert absence-workflow).
-
Claim Shifts: open shifts per datum. Conflictdetectie rood. Na claimen: 'Wachten op goedkeuring'. Bij vol: wachtlijst-knop. Bij goedkeuring/afwijzing: notificatie via e-mail + WhatsApp (Zender).
-
Dienst wisselen: open swap (iedereen mag reageren) of persoonlijke swap (specifieke collega). Na akkoord beide partijen: eenmalige bevestiging coordinator.
-
Berichten: inbox voor 1-op-1 berichten van coordinatoren. Vrijwilliger kan reageren met kort antwoord of statusupdate: 'Ik ben onderweg' / 'Ik ben er' / 'Ik ben ziek'.
-
Mijn Profiel: gegevens bewerken. Festival-paspoort inzien.
4.4.5 Shift swap & wachtlijst workflow (suggestie 5):
| SWAP WORKFLOW | Open swap: vrijwilliger meldt zich af → shift_swap.status = open → coordinator notificatie → wachtlijst positie 1 automatisch aangeschreven. Persoonlijke swap: A vraagt B → B accepteert → pending_swap → coordinator keurt goed (of auto-approve als secties identiek) → assignments gewisseld. Wachtlijst: shift vol → 'Op wachtlijst' knop → bij uitval: automatisch aangeschreven op volgorde. |
4.4.6 Post-festival evaluatie & retrospectief (suggestie 4):
-
24u na laatste shift: automatische evaluatiemail naar alle deelgenomen vrijwilligers. Max. 5 vragen: algehele beleving (1-5), shiftkwaliteit (1-5), terugkomen? (ja/nee), vrije feedback, verbeterpunt.
-
Anoniem of op naam: instelbaar per event.
-
Coordinator-beoordeling (parallel, intern): per vrijwilliger een rating (1-5), notities, 'herinviteren?' (ja/nee/misschien). Nooit zichtbaar voor de vrijwilliger.
-
Resultaten worden verwerkt in het festival-retrospectief rapport (zie 4.12).
-
Na afloop: automatisch bedankbericht aan alle vrijwilligers. Gepersonaliseerd: naam, sectie, uren gedraaid, 'Zonder jou was dit festival niet mogelijk.'
4.5 Communicatiehub (suggestie 2)
Communicatie is op show-dag een van de grootste operationele risico's. Het platform biedt een centrale hub met drie niveaus van urgentie. De coordinator kiest urgentie — het systeem kiest het juiste kanaal.
| URGENTIENIVEAUS | Normaal: e-mail. Voor planningswijzigingen, briefings, updates. Niet-tijdkritisch. Dringend: WhatsApp (Zender). Voor dag-van-het-festival meldingen, last-minute wijzigingen. Noodgeval: SMS + WhatsApp gelijktijdig. Voor acute veiligheidssituaties, volledige evacuatie-achtige scenario's. |
Groepsberichten (broadcast):
-
'Een knop stuur bericht naar ploeg': per shift, per sectie of per heel event. Coordinator kiest urgentie, systeem kiest kanaal.
-
Leesbevestiging: '14 van 17 vrijwilligers hebben dit bericht gelezen.' Live bijgehouden via WebSocket.
-
Reactiemogelijkheid voor vrijwilliger: kort antwoord of statusupdate ('Ik ben onderweg', 'Ik ben er', 'Ik ben ziek'). Coordinator ziet live overzicht van alle reacties per shift.
No-show automatisering (suggestie 3 — show day):
-
30 minuten voor shiftstart: automatisch WhatsApp-bericht aan iedereen die nog niet is ingecheckt: 'Hey [naam], je dienst bij [sectie] begint over 30 minuten. Laat weten of je er bent.'
-
Vrijwilliger kan direct reageren via WhatsApp of portal. Reactie verschijnt live bij coordinator.
-
show_day_absence_alerts tabel logt elke alert, response-status en afdoening.
-
Coordinator-dashboard toont real-time: per shift hoeveel ingecheckt, hoeveel no-response, hoeveel bevestigd absent.
4.6 Show Day Mode (suggestie 3)
Op de dag van het festival heeft niemand tijd voor complexe navigatie. Show Day Mode activeert automatisch op de event-datum en biedt een radicaal vereenvoudigde interface die werkt op telefoon, tablet en grote schermen.
Activatie:
-
Automatisch: systeem schakelt naar Show Day Mode zodra de datum van de eerste show-dag is bereikt.
-
Handmatig override: event-manager kan de mode vroeger activeren (bijv. tijdens opbouw).
Show Day dashboard (vereenvoudigd):
-
Groot, hoog contrast, werkt op telefoon — ontworpen voor gebruik buiten, in drukte, met handschoenen.
-
Per sectie: grote tegel met naam, # ingecheckt / totaal, gekleurde statusring (rood/oranje/groen).
-
One-tap acties: 'Ping iedereen in deze sectie', 'Bekijk wie er niet is', 'Stuur noodbericht.'
-
Live incheckoverzicht: per minuut bijgewerkt teller. Geen filters, geen tabellen.
Snelle QR check-in (zonder hardware):
-
Vrijwilliger toont QR-code uit eigen portal op telefoon.
-
Coordinator scant met eigen telefoon-camera (geen dedicated scanner hardware nodig voor kleine events).
-
Check-in gelogd, accreditatie-items automatisch als 'uitgedeeld' gemarkeerd indien geconfigureerd.
Artiest show-dag overzicht:
-
Per podium: running order met live checkmarks. Groen = ingecheckt, rood = nog niet gezien.
-
Live statusbericht: '17 artiesten ingecheckt van 32.'
-
One-tap artiest bellen: telefoon direct bellen via opgeslagen contactnummer.
4.7 Staff & Crew Management
Kernfuncties:
-
Crowd pool per organisatie: medewerkers herbruikbaar over evenementen. Cross-event geschiedenis en reliability score.
-
Status KPI tiles: Total, Approved, Pending, Other — klikbare tellers.
-
Quick-action popup: klik op persoon → popup met accreditatie, Approve, Send message.
-
Accreditation matrix view: spreadsheet met items als kolommen, personen als rijen.
-
Externe lijst: deelbaar met partner, binnen organisator-ingestelde limieten.
-
Blacklist toggle, admin-only notities, EHBO/rijbewijs metadata.
4.8 Accreditatie Engine
Configuratie (org-niveau):
-
Categorieën: Wristband, Food & Beverage, Communication (portofoons), Clothing (shirts), Transport, Custom.
-
Items per categorie: naam, date-dependent flag, quantity, barcode type, ticket visual, kostprijs.
-
Templates: herbruikbare sets van items+limieten, activeerbaar per event.
-
Access Zones: Backstage, VIP, Main Stage, Production, Parking — zone_code voor scanners.
Check-in modal (Mission Control):
-
Zoek op naam of scan barcode.
-
Accreditatie tab: items per categorie, checkbox per item, 'Check all items' shortcut.
-
Check-in button → logt check_in, markeert items als uitgedeeld.
4.9 Briefing & Communicatie
Briefings (e-mail + PDF):
-
Briefing template builder: drag-and-drop blokken (tekst, afbeelding, kaart, accreditatielijst). TipTap + VueDraggable.
-
Selectieve blokzichtbaarheid: blok verschijnt alleen als ontvanger een specifiek accreditatie-item heeft.
-
Queue-based verzending: organisator ziet '134 bevestigingsmails wachten'. Handmatige 'Verstuur' trigger.
-
Status tracking: Queued → Sent → Opened → Tickets Downloaded.
-
Tags: __FIRSTNAME__, __LASTNAME__, __COMPANY__, __ARTIST__, __SHIFT__.
Allocatiesheet (PDF per crew/vrijwilliger):
-
Server-side via Laravel Browsershot. Bevat: taaknaam, datum/tijd, beschrijving, materialen, transport, kaart embed, persoonlijke QR-code.
-
Configureerbare opmaak: logo, achtergrondafbeelding, lettertype.
Campagnes:
-
E-mail campagnes: samengesteld bericht naar geselecteerde doelgroep.
-
SMS + WhatsApp campagnes via Zender (zelf-gehoste instantie). Doelgroep selecteerbaar, tekentelling zichtbaar, verzending planbaar.
-
1-op-1 berichten: gethread berichtenuitwisseling per persoon. Vrijwilliger kan reageren met statusupdate.
4.10 Mission Control (Show Day)
Sub-modules:
-
Crowd Check-In: zoek of scan, modal met accreditatie + items, 'Check all items', print polsband.
-
Artist Handling: live telling ingecheckte artiesten, per-stage running order met live checkmarks.
-
Check-In Insights: live aanwezigheidsanalyse per uur, per crowd type.
-
Scanner beheer: configureer scanstations. Koppeling via QR pairing code.
-
Inventarisbeheer: wijs fysieke items uit, markeer als teruggegeven, bulk-teruggave.
-
Show Day Mode activatie: grote knop bovenaan — schakel naar mobiel-geoptimaliseerde weergave (zie 4.6).
4.11 Formulierbouwer
-
Drag-sorteerbare veldenlijst: sleepgreep, naam, type (Text/Dropdown/Checkbox/Custom), required toggle.
-
Live voorbeeldpaneel: toont formulier real-time tijdens het bouwen.
-
Conditionele logica: veld X is alleen zichtbaar als antwoord op veld Y = waarde Z.
-
Integratiepaneel: deelbare link + <iframe> embed code.
-
E-mailtemplate tab: bevestigingsmail na invullen.
4.12 Suppliers & Productie
-
Productieverzoek workflow: aanmaken → versturen → leverancier vult in → organisator keurt goed.
-
Icon-kolommen: mensen/tech/stroom/materiaal als kolommen met groene vinkjes.
Leveranciersportal — Algemene informatie tab:
- Eventbeschrijving, route (vrachtwagenvriendelijk), vrachttoegang/laad-lostijden, contacten.
Leveranciersportal — Informatie invullen:
-
Personeelsaccreditatie: medewerkersnamen + accreditatiecategorieën.
-
Stroomvoorziening: type aansluiting, vermogen (kW), aantal aansluitpunten.
-
Materiaalbehoeften: heftruck, manitou, aggregaat, aanhanger, pallet jack. Velden: omschrijving, hoeveelheid, periode (van/tot). Workflow: Requested → Approved/Rejected → Fulfilled.
-
Externe personeelslijst: deelbare spreadsheet met accreditatie-items als bewerkbare kolommen.
4.13 Rapportage & Inzichten
| Rapport | Inhoud |
|---|---|
| Accreditatie totalen | Aangevraagd / Goedgekeurd / Gescand per dag per item, per bedrijf. |
| Artiest/Program | Rider totalen, riders per artiest, per dag, per kleedkamer. |
| Bezetting per dag | Crew en vrijwilligers per sectie per dag. Staafdiagram + tabel. |
| Accreditatie type | Staafdiagram per type met fill rate. |
| Aanwezigheid | Check-in statistieken per uur, per crowd type, live en historisch. |
| Inventaris | Uitgedeelde items, niet teruggegeven items, per persoon. |
| Communicatie | Leespercentage per campagne, per kanaal (e-mail/SMS/WhatsApp). Response rates. |
| Shift swaps | Aantal swaps per event, reden (absent/ruil), no-show percentage per sectie. |
| Vrijwilligerstevredenheid | Gem. evaluatiescore per sectie, per shift, per event. Trend over meerdere festivals. |
| Festival retrospectief (suggestie 4) | Auto-gegenereerd na afloop: bezetting vs. gepland, no-show %, gem. tevredenheid, secties met tekort/overschot, herinvitatie-aanbeveling, vergelijking vorig jaar. |
| Financieel | Kosten per shift, betaalde accreditatie-items, artiestenkosten. |
| Duplicaten | Personen meerdere keren toegevoegd aan lijsten. |
5. API Design & Integraties
REST API, WebSockets, externe koppelingen
5.1 API Architectuurprincipes
-
API-first: alle functionaliteit is beschikbaar via de REST API. De frontend is een API consumer, geen uitzondering.
-
Versioning: /api/v1/ prefix. Breaking changes krijgen een nieuw versienummer.
-
Auth: Laravel Sanctum SPA tokens voor de eigen frontends. API keys (hashed in DB) voor externe integraties.
-
Resource-based routing: standaard Laravel resourcecontrollers. index, show, store, update, destroy.
-
JSON:API-stijl responses: { data: {...}, meta: {...}, links: {...} } voor paginering.
-
Rate limiting: per API key configureerbaar. Standaard 60 req/min voor publieke portals.
5.2 Kern API Routes
| Route groep | Voorbeeldroutes |
|---|---|
| Auth | POST /auth/login, POST /auth/logout, POST /auth/refresh |
| Organisations | GET|POST /organisations, GET|PUT|DELETE /organisations/{id} |
| Events | GET|POST /organisations/{org}/events, PUT /events/{id}/status |
| Festival Sections | GET|POST /events/{event}/sections, GET /sections/{id}/dashboard |
| Time Slots | GET|POST /events/{event}/time-slots, PUT|DELETE /time-slots/{id} |
| Shifts | GET|POST /sections/{section}/shifts, PUT /shifts/{id}, POST /shifts/{id}/assign |
| Persons | GET|POST /events/{event}/persons, POST /persons/{id}/approve, POST /persons/{id}/checkin |
| Artists | GET|POST /events/{event}/artists, GET /artists/{id}/advance-portal |
| Advance Sections | GET|POST /artists/{id}/sections, POST /sections/{id}/submit |
| Volunteers | GET /events/{event}/volunteers, GET /volunteers/portal/{token} |
| Accreditations | GET|POST /events/{event}/accreditation-items, POST /persons/{id}/accreditations |
| Briefings | GET|POST /events/{event}/briefings, POST /briefings/{id}/send |
| Campaigns | GET|POST /events/{event}/campaigns, POST /campaigns/{id}/send |
| Mission Control | GET /events/{event}/mission-control, POST /persons/{id}/checkin-item |
| Scanners | GET|POST /events/{event}/scanners, POST /scan |
| Reports | GET /events/{event}/reports/{type} |
| Public (no auth) | POST /public/forms/{token}/submit, GET /public/advance/{token}, GET /public/briefing/{token} |
5.3 Real-time Events (WebSocket)
Via Laravel Echo + Pusher/Soketi. Alle kritieke statuswijzigingen worden als events uitgezonden:
| Event naam | Trigger & payload |
|---|---|
| PersonCheckedIn | Na succesvolle check-in. Payload: person_id, name, crowd_type, timestamp. |
| ShiftFillRateChanged | Na elke shift assignment/unassignment. Payload: shift_id, filled, total. |
| ArtistCheckInStatusChanged | Artiest ingecheckt op show dag. Payload: artist_id, performance_id, status. |
| AdvanceSectionSubmitted | Tour manager heeft een advance sectie ingediend. Payload: section_id, artist_id, submitted_by. |
| AccreditationItemHandedOut | Item uitgedeeld via Mission Control. Payload: person_id, item_id, qty. |
| BriefingSendQueued | Briefingverzending in queue geplaatst. Payload: briefing_id, recipient_count. |
5.4 Externe Integraties
| Integratie | Doel & implementatie |
|---|---|
| Ticketing (Eventix, See Tickets) | Import tickets via REST API per event. Tickets worden gekoppeld aan persons voor check-in en accreditatie matching. |
| SMS + WhatsApp (Zender) | Bulk SMS + WhatsApp campagnes via Zender HTTP API. Laravel ZenderService wraps de API calls. Individuele notificaties en bulk campagnes worden beide ondersteund. |
| E-mail (SendGrid / Postmark) | Transactionele e-mail en bulk campagnes. Queue-based via Laravel Mailable. |
| Google Maps / Leaflet | Locatie embed in allocatiesheet PDF en shift location configuratie. |
| Wristband printers | Integratie via USB/network printer API op check-in station. |
| Externe scanners | Hardware koppeling via scanner configuratie + pairing QR code. |
6. UX & UI Ontwerppatronen
Gevalideerde patronen uit de concurrentieanalyse
6.1 Statusbadges systeem
Gebruik consistent kleurgecodeerde pill-badges door de hele applicatie:
| Badge type | Waarden | Kleuren |
|---|---|---|
| Person status | Draft → Invited → Applied → Pending → Approved → Rejected | Grijs / Blauw / Geel / Oranje / Groen / Rood |
| Booking status | Concept → Requested → Option → Confirmed → Contracted → Cancelled | Grijs / Blauw / Paars / Groen / Donkergroen / Rood |
| Shift fill status | Empty / Partial / Full / Over-quota | Rood / Oranje / Groen / Paars |
| Event status | Draft / Published / Reg.Open / Build-Up / Show Day / Tear-Down / Closed | Grijs tot groen verloop per fase |
| Crowd type badge | Per crowd type kleur (Volunteer=groen, Artist=paars, Press=blauw, VIP=goud, Staff=blauw) | Per type configureerbaar in crowd_types.color |
6.2 Quick-action popup patroon
Gebruik compacte overlay-popups (niet volledige paginanavigatie) voor de meest voorkomende acties. Vermijd context-switching bij operationele taken:
-
Klik op persoon in lijst → popup met naam, bedrijf, accreditatiestatus, Approve/Reject knoppen, Send message.
-
Klik op performance blok in timetable → popup met artiestnaam, status, advancing checklist, 'Manage' link.
-
Klik op accreditatie-item in check-in modal → markeer uitgedeeld, inline, geen paginaovergang.
6.3 Fill-rate progress bars
Gebruik kleurgecodeerde horizontale voortgangsbalkjes overal waar capaciteit vs. bezetting relevant is:
-
Groen (≥80%): shift is goed gevuld
-
Oranje (50-79%): gedeeltelijk gevuld
-
Rood (<50%): onvoldoende gevuld
Toepassen op: shift lijst, sectie dashboard, accreditatie allocatie vs. limiet, advancing sectie completering.
6.4 Duaal portal architectuur
Scheiding tussen de interne beheerapplicatie (app/) en externe portals (portal/). Externe portals zijn:
-
advance.crewli.app/[event-slug]/[artist-token] — Artist advance portal
-
volunteers.crewli.app/[event-slug] — Vrijwilligersregistratie en portal
-
briefing.crewli.app/[token] — Persoonlijke briefing pagina (geen login)
-
supplier.crewli.app/[event-slug]/[supplier-token] — Leveranciersportal
Externe portals hebben event-branding (logo, kleuren) maar bieden een vereenvoudigde, rol-beperkte weergave. Ze zijn PWA-geschikt voor mobiel gebruik.
6.5 Inline validatie met 'Missing Items' blok
Formuliervalidatie sectie-voor-sectie. Toon een 'Missing Items' samenvatting onderin elke sectie die persistente zichtbaarheid heeft totdat alle vereiste velden zijn ingevuld. Geen fouten alleen bij submit.
6.6 Kaartintegratie in crew-documenten
Per locatie/shift is een adres, coördinaten en kaartpreview (Google Maps of Leaflet embed) configureerbaar. De kaart wordt opgenomen in de server-side gegenereerde allocatiesheet PDF. Dit is essentieel voor grote outdoor festivallocaties.
7. Ontwikkelroadmap & Prioriteiten
Aanbevolen bouwvolgorde per fase
7.1 Prioriteitsmatrix
| Module | Reden | Prioriteit | Fase |
|---|---|---|---|
| Multi-tenant architectuur + Auth | Alle overige modules zijn afhankelijk van org/event scoping en rollen. | P0 Fundament | 1 |
| Gebruikers, Rollen & Permissies | Drie-niveau permissiemodel vereist vanaf dag 1. | P0 Fundament | 1 |
| Organisaties & Evenementbeheer | Kerndata-containers voor alle operationele data. | P0 Fundament | 1 |
| Crowd Types & Segmentatie | Basis voor alle deelnemersbeheer. | P1 Kern | 1 |
| Festival Sections + Time Slots + Shifts | Uniek planningsmodel, kern van het platform. | P1 Kern | 1 |
| Personen & Crowdlijsten | Deelnemer data model inclusief externe lijsten. | P1 Kern | 1 |
| Accreditatie Engine | Centraal rechtensysteem, gebruikt door alle modules. | P1 Kern | 1 |
| Briefings & Communicatie | Primair communicatiekanaal naar deelnemers. | P1 Kern | 2 |
| Staff & Crew Management | Primaire use-case voor doelmarkt. | P1 Kern | 2 |
| Vrijwilligersbeheer + Portaal | Zelfregistratie, prioriteitsranking, shift claiming. | P2 Hoog | 2 |
| Formulierbouwer | Geconfigureerd publiek formulier met conditionele logica. | P2 Hoog | 2 |
| Artist Advancing + Portaal | Sectie-gebaseerd advancing, milestone pipeline. | P2 Hoog | 2 |
| Timetable (stage + drag-drop) | FullCalendar timeline, B2B detectie. | P2 Hoog | 2 |
| Guests & Hospitality | VIP beheer, RSVP, paid guest lists. | P2 Hoog | 3 |
| Suppliers & Productie | Leveranciersportal, productieverzoeken. | P2 Hoog | 3 |
| Mission Control / Show Day | Real-time check-in, artiest handling, inventaris. | P2 Hoog | 3 |
| Communicatiecampagnes (Email + SMS) | Bulk email/SMS via Twilio. Bevestigingswachtrij. | P3 Medium | 3 |
| Allocatiesheet PDF generator | Branded PDF per crew met kaart + QR. | P3 Medium | 3 |
| Scaninfrastructuur | Scanstations configureren, hardware koppelen. | P3 Medium | 3 |
| Rapportage & Inzichten | Gebouwd naast elke module. Progressief uitbreiden. | P3 Medium | 3 |
| Show Day Mode (mobiel-first) | Vereenvoudigde show-dag interface, QR check-in op telefoon. | P2 Hoog | 2 |
| No-show automatisering | 30-min alerts voor niet-ingecheckte vrijwilligers via Zender. | P2 Hoog | 3 |
| Shift swap & wachtlijst | Vrijwilliger meldt zich af, wachtlijst automatisch aangeschreven. | P2 Hoog | 2 |
| Vrijwilligersprofiel + festival-paspoort | Platform-breed profiel, reliability score, festival-geschiedenis. | P2 Hoog | 2 |
| Post-festival evaluatie + retrospectief | Automatische enquête, coordinator-beoordeling, gegenereerd rapport. | P2 Hoog | 3 |
| Communicatiehub met urgentielevels | Normaal/Dringend/Noodgeval → juist kanaal, leesbevestiging, reacties. | P2 Hoog | 2 |
| Real-time WebSocket notificaties | Laravel Echo + Soketi. Differentiator. | P4 Differentiator | 4 |
| Cross-event crew pool + reliabilityscore | Hergebruik medewerkers over events. | P4 Differentiator | 4 |
| Globale zoekfunctie (cmd+K) | Cross-entiteit zoeken: artiest, persoon, shift. | P4 Differentiator | 4 |
| Crew PWA (mobiel) | On-site zelfservice, shifts, briefing, clock-in. | P4 Differentiator | 4 |
| Publieke REST API + webhooks | Third-party integraties voor enterprise klanten. | P4 Differentiator | 4 |
7.2 Fasering
Fase 1 — Fundament (bouw eerst)
-
Laravel project setup: multi-tenant scoping, Sanctum auth, Spatie permissions.
-
Database migraties: alle kern-tabellen (users, organisations, events, crowd_types, festival_sections, time_slots, shifts).
-
API: auth endpoints, organisaties, evenementen, CRUD voor alle kern-entiteiten.
-
Vue frontend: login, organisatieswitcher, event overview, festival sections overzicht.
-
Aandacht-aware dashboard met event health widget.
Fase 2 — Kern Operaties (bouw tweede)
-
Personen & crowdlijsten (incl. externe lijsten).
-
Accreditatie engine: categorieën, items, toewijzingen.
-
Time Slots & Shifts: volledig planning model incl. Assign Shift panel.
-
Vrijwilligersregistratie: meerdelig formulier, beschikbaarheid, sectievoorkeuren.
-
Vrijwilligersportaal: My Shifts, Claim Shifts met conflictdetectie.
-
Briefing systeem: template builder, queue-based verzending.
Fase 3 — Advancing & Show Day (bouw derde)
-
Artist advancing: sectie-gebaseerd portaal, milestone pipeline, timetable.
-
Mission Control: check-in station, accreditatie uitgifte, artiest handling.
-
Formulierbouwer met conditionele logica en iframe embed.
-
Allocatiesheet PDF generator.
-
Communicatiecampagnes: email + SMS.
-
Leveranciersportal en productieverzoeken.
Fase 4 — Differentiators (bouw vierde)
-
Real-time WebSocket events.
-
Cross-event crew pool met betrouwbaarheidsscore.
-
Globale zoekfunctie.
-
Crew PWA.
-
Publieke REST API + webhook systeem.
-
CO2/duurzaamheidsrapportage.
8. Concurrentieanalyse Samenvatting
Wat te adopteren uit In2Event, Crescat en WeezCrew
8.1 In2Event — Adopteer
-
Dashboard 'Attention required' kolom met klikbare aantallen per crowd type.
-
Check-in modal: categorie-gegroepeerde items, 'Check all items' shortcut.
-
Accreditatie engine: onbeperkte categorieën incl. apparatuur en kleding.
-
Externe crowdlijst: deelbaar spreadsheet met accreditatie-items als bewerkbare kolommen.
-
Mission Control artist handling: live telling, per-stage running order met checkmarks.
-
Productieverzoek icon-kolommen: mensen/tech/stroom/accommodatie.
8.2 Crescat — Adopteer
-
Sectie-gebaseerd advance portaal: elke sectie onafhankelijk submitbaar.
-
Booking milestone pipeline bar: Offer In → Advance Received, klikbaar.
-
Productie-sectie: equipment toggles, riser afmetingen, document uploads.
-
Festival Sections architectuur: per sectie eigen Dashboard/Shifts/Scheduler/Crew/Volunteers tabs.
-
Time Slot + Shift tweelaags planningsmodel (zie sectie 4.3 en 3.4.2).
-
'Slots open for claiming' (Crescat: 'For Sale'): zichtbaarheid per shift in vrijwilligersportaal.
-
Vrijwilligersregistratie: prioriteitsranking secties, admin-only velden, betaalstatus.
-
Artiest in volunteer portal: toon welke acts spelen tijdens shift.
-
Selectieve announce-zichtbaarheid per artiest in advance portaal.
8.3 WeezCrew — Adopteer
-
Fill-rate progress bars: groen/oranje/rood op alle capaciteitsweergaven.
-
Per-shift geo-locatie: adres, kaartpreview, routetekening.
-
Task manager rollen per shift: Editor vs. Read-only.
-
Formulierbouwer: drag-sorteerbaar, conditionele logica, live preview, iframe embed.
-
SMS + WhatsApp campagnes via Zender (zelf-gehoste instantie).
-
Branded allocatiesheet PDF: taak, beschrijving, kaart, QR-code, configureerbare opmaak.
-
Event health / pending tasks widget op dashboard.
-
Accreditatierapportage: staafdiagram per type + fill-rate lijsten.
-
'Previously appointed' indicator bij recurrente shifts.
8.4 Differentiators — Geen van de drie heeft dit
-
Real-time WebSocket push notificaties voor alle kritieke statuswijzigingen.
-
Cross-event organisatie-niveau crew pool met betrouwbaarheidsscore en eenklik-heruitnodiging.
-
Globale zoekopdracht (cmd+K) over alle entiteitstypen.
-
Crew PWA: on-site zelfservice, shifts, briefing, clock-in, push notificaties.
-
Open publieke REST API + webhook systeem voor enterprise-integraties.
-
Fase-aware dashboard: CTA en context verandert per event lifecycle fase.
9. Cursor & Claude Code Instructies
Aanbevelingen voor AI-geassisteerde ontwikkeling
9.1 Workspace Rules (.cursorrules / CLAUDE.md)
Maak een CLAUDE.md bestand in de root van de repository met de volgende instructies voor Cursor en Claude Code:
| CURSOR / CLAUDE CODE | Stack: PHP 8.2 + Laravel 12, TypeScript + Vue 3 + Vuexy/Vuetify + Pinia. Backend: gebruik Resource Controllers, Form Requests voor validatie, API Resources voor responses. Multi-tenancy: alle queries scopen op organisation_id. Gebruik OrganisationScope Global Scope. Rollen: Spatie laravel-permission. Check rollen via $user->hasRole() en policies. Frontend: gebruik <script setup> + Composition API. Pinia voor state. TanStack Query voor API calls. Naamgeving: snake_case DB kolommen, camelCase JS variabelen, PascalCase Vue componenten. Testen: PHPUnit feature tests per API endpoint. Minimum: happy path + unauthorised test. UUIDs: gebruik op alle public-facing ID's (models + migrations). |
9.2 Aanbevolen ontwikkelstrategie
-
Begin altijd met de database migratie en het Eloquent model voordat je de controller schrijft.
-
Schrijf API routes en Form Request validaties voor de business logica.
-
Gebruik Laravel API Resources voor consistente JSON responses. Nooit direct model attributes teruggeven.
-
Voor elke module: maak eerst een PHPUnit feature test die de API route test met een factory.
-
Vue componenten: begin met de pagina-shell, dan de API call via useQuery (TanStack), dan de UI.
-
Vuexy/Vuetify componenten altijd gebruiken voor custom CSS te schrijven.
-
Pinia stores per domein: useOrganisationStore, useEventStore, useShiftStore, etc.
9.3 Prioritaire bestanden om eerst te genereren
Vraag Cursor/Claude Code de volgende bestanden in volgorde te genereren voor Fase 1:
| Bestand | Inhoud |
|---|---|
| database/migrations/*_create_organisations_table.php | Organisations + organisation_user pivot met role kolom. |
| database/migrations/*_create_events_table.php | Events met alle kolommen uit sectie 3.4.1. |
| database/migrations/*_create_festival_sections_table.php | Festival sections + time_slots + shifts + shift_assignments. |
| database/migrations/*_create_persons_table.php | Persons + crowd_types + accreditation tabellen. |
| app/Models/Organisation.php | Met OrganisationScope, hasMany relaties, Spatie Teamable trait. |
| app/Models/Event.php | Met scope, lifecycle status enum, hasMany festival_sections. |
| app/Models/Shift.php | Met time_slot relatie, fill_rate accessor, available_slots accessor. |
| app/Http/Controllers/Api/V1/ShiftController.php | CRUD + assign actie. Gebruikt OrganisationPolicy. |
| app/Http/Requests/StoreShiftRequest.php | Validatie: time_slot_id, slots_total, slots_open_for_claiming. |
| resources/js/stores/useShiftStore.ts | Pinia store met TanStack Query voor shift data. |
| resources/js/pages/sections/[id]/shifts.vue | Shift overzicht pagina met date-navigatie en shift lijst. |
10. Out of Scope — Toekomstige Versies
Functionaliteit die bewust buiten de huidige ontwikkelscope valt
De volgende functionaliteiten zijn expliciet buiten scope voor versies 1.0 t/m 1.x. Ze zijn geïdentificeerd als potentiële toevoegingen voor toekomstige versies. Het datamodel houdt waar mogelijk rekening met hun toekomstige integratie (geen breaking changes nodig).
| Feature | Toelichting & toekomstige aanpak |
|---|---|
| Reisbeheer (vluchten, bustransport) | Vluchten, bus- en treinreizen van artiesten en crew worden niet beheerd. Toekomstig: itinerary_items uitbreiden met type=flight|train|bus, vlucht- of treinnummer, vertrek-/aankomsttijden, airline/carrier. |
| Accommodatiebeheer (hotels, B&B) | Hotelboekingen en kamerindelingen voor artiesten en crew zijn buiten scope. Toekomstig: accommodations tabel met type, locatie, incheck-/uitcheckdatum, kamerindeling, koppeling aan artiest of person. |
| Travel Party beheer | Het beheren van de volledige tourgroep als eenheid voor accreditatie is buiten scope. Toekomstig: travel_party tabel als groep gelinkt aan artiest, met eigen accreditatielimiet. |
| CO2 / Duurzaamheidsrapportage | Emissieberekeningen op basis van transport en energieverbruik zijn buiten scope voor v1. Toekomstig: CO2-berekening per transport-type op basis van afstand en voertuigtype. |
| Native mobiele apps (iOS / Android) | Dedicated native apps voor scanning en crew zelfservice zijn buiten scope. Toekomstig: React Native of Flutter app, of uitbreiding van de PWA met native plugins. |
| Companion app voor VIP gasten | Mobiele app voor VIP gasten om evenementinfo te bekijken en te netwerken is buiten scope voor v1. |
| Kaartverkoop / Ticketing eigen systeem | Crewli beheert accreditaties en e-tickets voor niet-betalende deelnemers. Het verkopen van publieke tickets is buiten scope. Integratie met externe ticketingpartners (Eventix, See Tickets) via API is wel gepland (fase 4). |
| ONTWERPPRINCIPE | Tabellen en velden die gerelateerd zijn aan Out of Scope features worden NIET aangemaakt in de database. Placeholders of lege JSON velden worden ook niet ingebouwd. Wanneer een feature in scope komt, worden nieuwe migraties geschreven. |
11. Begrippenlijst
Alle termen gebruikt in dit document en de codebase
| Term | Definitie |
|---|---|
| Accreditation | Een recht of artikel toegekend aan een persoon: toegangszones, te ontvangen items (polsband, maaltijd, portofoon). |
| Admin-only field | Formulierveld dat alleen voor de organisator zichtbaar is, niet voor de externe indiener. |
| Advance / Advancing | Het verzamelen en bevestigen van alle artistenvereisten voor het evenement via een sectie-gebaseerd portaal. |
| Allocation sheet | Gepersonaliseerd PDF-document per crew/vrijwilliger met taakomschrijving, tijden, locatiekaart en QR-code. |
| B2B Performance | Twee artiesten die tegelijk optreden op hetzelfde podium in hetzelfde tijdslot. |
| Briefing | Gepersonaliseerde e-mailcommunicatie naar eventdeelnemers met e-tickets, informatie en taakbeschrijving. |
| Claim (shift) | Vrijwilliger reserveert zelf een open shift via het zelfserviceportaal. |
| Crowd Type | Classificatie van deelnemers: Crew, Volunteer, Artist, Guest, Press, Partner, Supplier. |
| Crowd List | Gestructureerde lijst van personen. Intern (org-gevuld) of extern (partner-gevuld). |
| E-ticket | Digitaal ticket gegenereerd voor goedgekeurde personen met accreditatiebarcodes. |
| Event Health | Dashboard widget die configuratieproblemen en openstaande acties toont als klikbare items. |
| Festival Section | Operationele eenheid binnen een event: Bar, Hospitality, Technical, Security, etc. |
| Fill rate | Percentage gevulde slots ten opzichte van het totaal voor een shift of accreditatieitem. |
| For Sale (Crescat) | Aantal shift-slots claimbaar via vrijwilligersportaal. In Crewli: 'slots_open_for_claiming'. |
| Infosheet | Gegevensverzamelformulier gestuurd naar artistcontacten voor het ophalen van rider- en logistieke info. |
| Inventory Item | Fysiek item gevolgd op locatie: portofoon, hesje, sleutel, wristband printer. |
| Itinerary | Volledige dagsplanning van een artiest: vluchten, transfers, optredens, activiteiten. |
| Milestone pipeline | Visuele voortgangsbalk met fases in de boekingslevenscyclus (Crescat patroon). |
| Mission Control | Real-time operationele hub op show-dag voor check-in en tracking. |
| Open for Claiming | Aantal shift-slots dat zichtbaar en claimbaar is in het vrijwilligersportaal. |
| Organisation | Een klant/organisatie die Crewli gebruikt. Bevat meerdere evenementen. |
| Person | Individuele deelnemer gekoppeld aan een event via een Crowd Type. |
| Production Request | Digitaal intakeformulier gestuurd naar leveranciers voor het verzamelen van logistieke vereisten. |
| Rider | Lijst van technische en hospitality vereisten van een artiest. |
| RSVP | Bevestigingsstroom waarbij deelnemers een uitnodiging accepteren of weigeren. |
| Scanner | Geconfigureerd scanstation (browser of hardware) gekoppeld aan specifieke crowd types/zones. |
| Segment | Onderverdeling van een crowd type voor granulaire communicatie en rapportage. |
| Shift | Sectie-specifieke toewijzing binnen een Time Slot. Koppelt sectie + tijdvenster + capaciteit. |
| Slot (capacity) | Het aantal personen dat een Shift kan bevatten. |
| Stage | Podiumnaam binnen een event. Heeft performances op specifieke dagen. |
| Time Slot | Event-niveau tijdvenster met persoonscategorie (CREW/VOLUNTEER/PRESS). Aangemaakt per event. |
| Timetable | Visueel drag-and-drop schema met podiums als rijen en tijd op de x-as. |
| Travel Party | De volledige tourgroep van een artiest als een eenheid voor accreditatie. |
| Zone / Access Zone | Gedefinieerd gebiedsonderdeel van het terrein met specifieke toegangspermissies. |
| Ambassador badge | Intern badge voor vrijwilligers die als eerste reageren op uitnodigingen en altijd aanwezig zijn. Nooit zichtbaar voor de vrijwilliger zelf. |
| Festival-paspoort | Visuele tijdlijn in het vrijwilligersprofiel van alle festivals waaraan iemand heeft deelgenomen, met uren en secties. |
| No-show alert | Automatisch WhatsApp-bericht 30 minuten voor shiftstart aan niet-ingecheckte vrijwilligers. |
| Open swap | Dienstwisselverzoek waarbij de vrijwilliger zich afmeldt en de shift vrijkomt voor de wachtlijst of opnieuw voor claiming. |
| Persoonlijke swap | Dienstwisselverzoek waarbij vrijwilliger A een specifieke collega B vraagt om te ruilen. Vereist akkoord van B en bevestiging coordinator. |
| Reliability score | Berekende score (0.0–5.0) per vrijwilliger op basis van no-shows, tijdigheid check-in en coordinator-ratings over alle festivals. |
| Retrospectief rapport | Automatisch gegenereerd rapport na afloop van een festival: bezetting vs. gepland, no-show percentage, tevredenheid, aanbevelingen voor volgend jaar. |
| Show Day Mode | Radicaal vereenvoudigde, mobiel-first interface die automatisch activeert op de event-datum. Grote tegels, hoog contrast, minimale navigatie. |
| Urgentieniveau | Classificatie van een bericht: Normaal (e-mail), Dringend (WhatsApp), Noodgeval (SMS + WhatsApp). Bepaalt welk kanaal Zender gebruikt. |
| Wachtlijst (shift) | Lijst van vrijwilligers die zich aangemeld hebben voor een volle shift. Bij uitval worden ze automatisch op volgorde aangeschreven. |
| Zender | Zelf-gehoste multi-channel communicatieplatform (codecanyon) dat Android-apparaten gebruikt als SMS en WhatsApp gateway. Aangestuurd via HTTP API. |
Crewli Product Design & Technical Specification — Vertrouwelijk
Versie 1.3 — Maart 2026 — Gebaseerd op analyse van In2Event, Crescat & WeezCrew