Define ActivityRegistrationPayload and validation rules
epic-quick-activity-registration-business-logic-task-004 — Create the ActivityRegistrationPayload value object containing all required fields: activity type ID, date, duration in minutes, optional notes, organisation ID, and peer mentor user ID. Define the validation rules as a pure function or validator class: activity type must be non-null, date must not be in the future by more than one day, duration must be between 1 and 480 minutes.
Acceptance Criteria
Technical Requirements
Implementation Notes
Model ActivityRegistrationPayload as a plain Dart class with final fields. Do NOT use freezed unless the rest of the codebase already uses it — match existing conventions. For ValidationError, a sealed class (Dart 3) is preferred over an enum because it allows attaching contextual data (e.g. the field name, the actual value) without runtime casting.
The validator should be a stateless class with a single validate() method — no dependencies, no side effects — making it trivially testable. The 'not more than 1 day in future' rule should use DateTime.now() injected via a clock parameter in tests to avoid flakiness. Use a default clock in production. Keep this class in lib/features/activity_registration/domain/models/ following clean architecture conventions.
Testing Requirements
Unit tests only. Use flutter_test. Create ActivityRegistrationPayloadTest and ActivityRegistrationValidatorTest. Cover: (1) all valid permutations for boundary values on date and duration, (2) each validation rule in isolation to confirm correct ValidationError variant returned, (3) multi-error accumulation — a payload with both invalid date and invalid duration returns both errors, (4) equality — two payloads with identical fields are equal, (5) notes field: null is valid, empty string is valid, 1000 chars is valid, 1001 chars is invalid.
Target 100% branch coverage on the validator.
The wizard Cubit manages multiple concurrent state slices (current step, each field value, submission status, error state). As the number of wizard steps grows, the state class can become unwieldy, making it difficult to reason about transitions, leading to subtle bugs where advancing a step resets a previously filled field.
Mitigation & Contingency
Mitigation: Use an immutable state model (copyWith pattern) with a separate sealed class per wizard step state. Keep the Cubit's emit calls minimal and always derive the next state from the current state to prevent accidental field resets. Document the state machine transitions explicitly in code comments.
Contingency: If state complexity becomes unmanageable, split into a parent WizardCubit (owns step navigation and submission) and per-step child Cubits (own individual field state), coordinating via a shared repository layer.
Organisation-specific compensation eligibility rules (e.g., activity type + duration thresholds) are business logic that may change independently of the app release cycle. Hardcoding these rules in ActivityRegistrationService means rule changes require a new app deployment, causing delays and potential financial errors if the deployed version uses outdated rules.
Mitigation & Contingency
Mitigation: Model compensation rules as configuration fetched from Supabase (stored per organisation), cached locally. ActivityRegistrationService reads from cache with a fallback to hardcoded defaults for offline scenarios. Design the rule schema to be extensible without code changes.
Contingency: If dynamic rules are not ready for initial release, ship with hardcoded rules and a feature flag that enables the remote-config path. Document the rule structure clearly so coordinators can trigger a rule update via a Supabase dashboard entry rather than a code deployment.
The last-used activity type stored in RegistrationPreferencesStore may become invalid if the organisation administrator deactivates that activity type between sessions. The Cubit would pre-populate a deleted type, and either the UI would show a missing item or submission would fail with a foreign-key constraint error.
Mitigation & Contingency
Mitigation: In RegistrationDefaultsManager, validate the retrieved last-used activity type ID against the current list of active types fetched from the activity type repository. If the stored ID is not in the active list, fall back to the first active type alphabetically.
Contingency: If validation cannot be performed offline, surface a non-blocking warning in the activity type step ('Your previously used activity type is no longer available') and require the user to make a new selection before advancing.