critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

An abstract class or interface BulkRegistrationService is defined in Dart with a single public method: Future<BulkRegistrationResult> registerBulk(List<String> mentorIds, ActivityTemplate template)
ActivityTemplate model is a Dart class/freezed object containing at minimum: activity_type_id (String), date (DateTime), duration_minutes (int), notes (String?), organization_id (String)
BulkRegistrationResult is a Dart class/freezed object containing: overall_status (BulkStatus enum: success, partial, failure), success_count (int), failure_count (int), entries (List<BulkRegistrationEntry>)
BulkRegistrationEntry contains: mentor_id (String), status (EntryStatus enum: success, failure, skipped_duplicate), error_reason (String?)
All models are immutable (use freezed or const constructors) and support copyWith for state management compatibility
The interface does not reference any Supabase or network implementation details — it is a pure contract
A mock implementation MockBulkRegistrationService is provided alongside the interface for use in widget and unit tests
All model files are covered by dart analyze with zero errors or warnings
The interface file includes a dartdoc comment explaining the expected behaviour of partial batch failures

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
data models
activity
activity_type
performance requirements
Model serialization/deserialization must not allocate on the hot path — use const constructors where possible
security requirements
ActivityTemplate must not carry any auth tokens or user credentials — those are injected at the service implementation layer
mentor_id list must contain only UUIDs — validate format before passing to the service

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use freezed for all data models to get immutability, copyWith, equality, and toString for free — this is consistent with Flutter/BLoC best practices and avoids manual boilerplate. Define the abstract class in lib/services/bulk_registration/bulk_registration_service.dart and place models in lib/services/bulk_registration/models/. The mock implementation should live in test/mocks/ and use a configurable response map (mentorId → EntryStatus) so individual tests can exercise different scenarios. Keep ActivityTemplate fields aligned with the activity data model (activity_type_id, date, duration_minutes) to avoid impedance mismatch at the database layer.

Do not add Supabase-specific fields (e.g., organisation_id should come from the session context at implementation time, not be a required parameter of the template).

Testing Requirements

Unit tests: verify that BulkRegistrationResult correctly aggregates success_count and failure_count from a mixed list of entries. Verify BulkStatus is 'partial' when both successes and failures exist, 'success' when all succeed, 'failure' when all fail. Test that MockBulkRegistrationService returns the expected stubbed result. Test ActivityTemplate and BulkRegistrationEntry copyWith behaviour.

No widget or integration tests required at this stage — this task establishes the contract only. dart analyze must pass with zero issues.

Component
Bulk Registration Service
service high
Epic Risks (3)
medium impact medium prob scope

If the batch insert RPC returns a mix of successes and failures (e.g., 3 of 10 mentors fail due to constraint violations that slipped through application-level duplicate detection), the confirmation screen result state becomes ambiguous. A coordinator who sees '7 of 10 succeeded' may not know whether to manually register the 3 failures, retry, or escalate — leading to either duplicate registrations or silent underreporting.

Mitigation & Contingency

Mitigation: Design the Bulk Registration Service to return a strongly typed BulkRegistrationResult with per-mentor RegistrationOutcome (success | duplicate_detected | constraint_violation | permission_denied). Design the result screen to list each failed mentor with a specific, plain-language reason and a one-tap 'Retry for this mentor' action that pre-fills the activity wizard with the batch template for that individual.

Contingency: If per-mentor retry UI is too complex to deliver within the epic scope, fall back to displaying failed mentors with their error codes and instructing coordinators to use single-proxy mode for the failures. Document this as a known limitation in release notes and create a follow-up ticket for per-mentor retry in the next sprint.

medium impact medium prob dependency

The Proxy Activity Wizard must reuse the existing activity wizard step widgets (type, date, duration, notes) while injecting a proxy attribution banner and a different submission payload builder. If the existing wizard is not designed for composability, the proxy variant may require forking the widget tree, creating two maintenance-diverging codebases that will drift out of sync when the base wizard is updated (e.g., new activity types added, new mandatory fields).

Mitigation & Contingency

Mitigation: Before implementing the Proxy Activity Wizard, audit the existing activity wizard's architecture. If steps are already extracted as independent StatelessWidget/ConsumerWidget classes, compose them directly with a wrapping Column that injects the attribution banner. If they are tightly coupled inside a parent widget, refactor the existing wizard to accept a nullable ProxyContext parameter before starting the proxy variant — this refactor should be a prerequisite task in this epic.

Contingency: If refactoring the base wizard is blocked by unrelated in-flight work on that component, implement the proxy wizard as a full fork but create a shared StepWidgets library file that both the base wizard and proxy wizard import. Schedule a deduplication refactor as a tech-debt ticket in the next planning cycle.

medium impact medium prob technical

The bulk registration flow spans three sequential screens (multi-select → activity form → confirmation → result) with shared mutable state: the selected mentor list, the activity template, the per-mentor duplicate warnings, and the final submission result. Managing this state across screens without a well-designed Bloc risks state leaks, stale duplicate warning data after mentor removal, and confirmation screen inconsistencies if the user navigates back and changes the mentor selection.

Mitigation & Contingency

Mitigation: Define a single BulkRegistrationBloc (or Cubit) with explicit state transitions covering: MentorsSelected → ActivityTemplateCompleted → DuplicatesChecked → ConfirmationReady → Submitting → SubmissionResult. Each backward navigation event (e.g., 'Back' from confirmation to mentor selection) dispatches a ResetToMentorSelection event that clears downstream state. Unit test every state transition with edge cases including empty mentor list, all mentors having duplicates, and network failure during submission.

Contingency: If state management complexity causes persistent bugs in testing, simplify by passing state explicitly through Navigator arguments (immutable snapshots per screen) rather than a shared Bloc. This reduces flexibility but eliminates cross-screen state mutation bugs.