Parent
#237
What to build
Create the foundational schema, models, and Filament Admin resources for the Events Participation module. This is the base layer that all other slices depend on.
Schema (7 tables via individual migrations):
events — id, tenant_id, event_type (enum: meetup/workshop/conference), active, slug, title, description, location, starts_at, ends_at, timestamps
events_enrollment_policies — event_id (unique FK), enrollment_method, check_in_method, capacity (nullable), has_waitlist, attendance_requirement, cancellation_deadline_hours, xp_reward_rsvp, xp_reward_checkin, xp_reward_attendance, application_form_schema (JSONB), timestamps
events_enrollments — id, event_id, user_id, status (enum), waitlist_position (nullable), application_data (JSONB), rejection_reason, enrolled_at, confirmed_at, checked_in_at, attended_at, cancelled_at, is_public, timestamps. UNIQUE(event_id, user_id)
events_enrollment_transitions — id, enrollment_id (FK), from_status, to_status, triggered_by, actor_user_id (nullable FK), reason (nullable), created_at
events_check_ins — id, enrollment_id (FK), method (enum), payload (JSONB), event_date (date), checked_in_at, timestamps
events_check_in_codes — id, event_id (FK), code, event_date (date), valid_from, valid_until, max_uses (nullable), uses_count (default 0), timestamps
events_qr_tokens — id, enrollment_id (FK), token (unique), expires_at (nullable), timestamps
Models:
- EventModel, EventEnrollmentPolicy, EventEnrollment, EventEnrollmentTransition, EventCheckIn, EventCheckInCode, EventQrToken
- All with proper relationships, casts (JSONB → array, dates → datetime, enums), and return type hints
Enums:
EventTypeEnum (meetup, workshop, conference)
EnrollmentStatusEnum (pending, confirmed, waitlisted, checked_in, attended, cancelled, rejected, no_show) with canTransitionTo(self $target): bool
EnrollmentMethodEnum (rsvp, rsvp_checkin, application)
CheckInMethodEnum (manual, numeric_code, qr_code)
AttendanceRequirementEnum (all_days, any_day, minimum_days)
Filament Admin:
- Event resource with CRUD (create, list, edit, delete)
- Inline Policy form (1:1 relationship) on event create/edit
- Basic enrollments RelationManager (read-only for now)
Factories:
- EventFactory, EventEnrollmentPolicyFactory, EventEnrollmentFactory, EventCheckInFactory, EventCheckInCodeFactory, EventQrTokenFactory
Key conventions:
declare(strict_types=1) in all files
- Models are
final class
- Foreign keys use
constrained() with appropriate cascades
- Indexes on: status, event_date, event_id+user_id (composite unique), token
casts() method for JSONB and datetime fields
- Mirror DB defaults in model
$attributes
- 1 migration per table (7 migrations total)
- All migrations must have reversible
down() methods
- Run
vendor/bin/pint --dirty --format agent after all PHP changes
Module structure:
- Lives in
app-modules/events/ following internachi/modular conventions
- Namespace:
He4rt\Events\*
- Refer to
app-modules/events/CONTEXT.md for domain glossary and state machine reference
- Refer to
app-modules/events/docs/adr/ for architectural decisions
Acceptance criteria
Blocked by
None — can start immediately
Parent
#237
What to build
Create the foundational schema, models, and Filament Admin resources for the Events Participation module. This is the base layer that all other slices depend on.
Schema (7 tables via individual migrations):
events— id, tenant_id, event_type (enum: meetup/workshop/conference), active, slug, title, description, location, starts_at, ends_at, timestampsevents_enrollment_policies— event_id (unique FK), enrollment_method, check_in_method, capacity (nullable), has_waitlist, attendance_requirement, cancellation_deadline_hours, xp_reward_rsvp, xp_reward_checkin, xp_reward_attendance, application_form_schema (JSONB), timestampsevents_enrollments— id, event_id, user_id, status (enum), waitlist_position (nullable), application_data (JSONB), rejection_reason, enrolled_at, confirmed_at, checked_in_at, attended_at, cancelled_at, is_public, timestamps. UNIQUE(event_id, user_id)events_enrollment_transitions— id, enrollment_id (FK), from_status, to_status, triggered_by, actor_user_id (nullable FK), reason (nullable), created_atevents_check_ins— id, enrollment_id (FK), method (enum), payload (JSONB), event_date (date), checked_in_at, timestampsevents_check_in_codes— id, event_id (FK), code, event_date (date), valid_from, valid_until, max_uses (nullable), uses_count (default 0), timestampsevents_qr_tokens— id, enrollment_id (FK), token (unique), expires_at (nullable), timestampsModels:
Enums:
EventTypeEnum(meetup, workshop, conference)EnrollmentStatusEnum(pending, confirmed, waitlisted, checked_in, attended, cancelled, rejected, no_show) withcanTransitionTo(self $target): boolEnrollmentMethodEnum(rsvp, rsvp_checkin, application)CheckInMethodEnum(manual, numeric_code, qr_code)AttendanceRequirementEnum(all_days, any_day, minimum_days)Filament Admin:
Factories:
Key conventions:
declare(strict_types=1)in all filesfinal classconstrained()with appropriate cascadescasts()method for JSONB and datetime fields$attributesdown()methodsvendor/bin/pint --dirty --format agentafter all PHP changesModule structure:
app-modules/events/followinginternachi/modularconventionsHe4rt\Events\*app-modules/events/CONTEXT.mdfor domain glossary and state machine referenceapp-modules/events/docs/adr/for architectural decisionsAcceptance criteria
EnrollmentStatusEnum::canTransitionTo()correctly validates all allowed transitions per state machine in CONTEXT.mdphp artisan test --compact --filter=Eventsvendor/bin/pint --dirty --format agentBlocked by
None — can start immediately