feat: registration form fields, section preferences, tag sync & schema updates

Implement EAV system for dynamic event-specific registration fields
with organisation-level templates, person section preferences with
priority ranking, and TagSyncService for deferred tag_picker sync.

New tables: registration_field_templates, registration_form_fields,
person_field_values, person_section_preferences.
New columns: persons.remarks, events.registration_show_section_preferences,
events.registration_show_availability.

58 tests, 126 assertions — all 432 tests pass (zero regressions).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 22:10:16 +02:00
parent fcff3b0344
commit f6e3568011
51 changed files with 3774 additions and 1 deletions

View File

@@ -15,8 +15,12 @@ use App\Http\Controllers\Api\V1\MeController;
use App\Http\Controllers\Api\V1\MemberController;
use App\Http\Controllers\Api\V1\OrganisationController;
use App\Http\Controllers\Api\V1\PersonController;
use App\Http\Controllers\Api\V1\PersonFieldValueController;
use App\Http\Controllers\Api\V1\PersonIdentityMatchController;
use App\Http\Controllers\Api\V1\PersonSectionPreferenceController;
use App\Http\Controllers\Api\V1\PersonTagController;
use App\Http\Controllers\Api\V1\RegistrationFieldTemplateController;
use App\Http\Controllers\Api\V1\RegistrationFormFieldController;
use App\Http\Controllers\Api\V1\ShiftAssignmentController;
use App\Http\Controllers\Api\V1\ShiftController;
use App\Http\Controllers\Api\V1\TimeSlotController;
@@ -104,6 +108,10 @@ Route::middleware('auth:sanctum')->group(function () {
->except(['show']);
Route::get('person-tag-categories', [PersonTagController::class, 'categories']);
// Registration field templates (organisation settings)
Route::apiResource('registration-field-templates', RegistrationFieldTemplateController::class)
->except(['show']);
// User tag assignments
Route::get('users/{user}/tags', [UserOrganisationTagController::class, 'index']);
Route::post('users/{user}/tags', [UserOrganisationTagController::class, 'store']);
@@ -160,6 +168,21 @@ Route::middleware('auth:sanctum')->group(function () {
// Volunteer availabilities
Route::get('persons/{person}/availabilities', [VolunteerAvailabilityController::class, 'index']);
Route::post('persons/{person}/availabilities/sync', [VolunteerAvailabilityController::class, 'sync']);
// Person field values
Route::get('persons/{person}/field-values', [PersonFieldValueController::class, 'index']);
Route::put('persons/{person}/field-values', [PersonFieldValueController::class, 'upsert']);
// Person section preferences
Route::get('persons/{person}/section-preferences', [PersonSectionPreferenceController::class, 'index']);
Route::put('persons/{person}/section-preferences', [PersonSectionPreferenceController::class, 'replace']);
// Registration form fields (event settings)
Route::apiResource('registration-fields', RegistrationFormFieldController::class)
->except(['show']);
Route::post('registration-fields/reorder', [RegistrationFormFieldController::class, 'reorder']);
Route::post('registration-fields/from-template', [RegistrationFormFieldController::class, 'fromTemplate']);
Route::post('registration-fields/import-from-event', [RegistrationFormFieldController::class, 'importFromEvent']);
Route::apiResource('crowd-lists', CrowdListController::class)
->except(['show']);
Route::get('crowd-lists/{crowdList}/persons', [CrowdListController::class, 'persons']);