high priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

BulkRegistrationService.registerBulk returns a BulkRegistrationResult where success_count equals the number of entries with status success
failure_count equals the number of entries with status failure; skipped_duplicate entries are counted separately and not included in failure_count
overall_status is BulkStatus.success when all non-skipped entries succeed, BulkStatus.failure when all non-skipped entries fail, and BulkStatus.partial when there is a mix
Each BulkRegistrationEntry in the result list has a non-null error_reason when status is failure, describing the cause (e.g., 'duplicate_check_failed', 'batch_rpc_failed', 'db_constraint_violation: unique_activity_per_day')
Successful entries (status success) have a null error_reason
The result entries list preserves the same order as the input mentorIds list for predictable UI rendering
If mentorIds input is empty, the service returns a BulkRegistrationResult with success_count 0, failure_count 0, overall_status success, and an empty entries list
The aggregation logic is a pure function (no side effects) and is unit-testable in isolation
BulkRegistrationResult can be serialized to JSON for logging/audit purposes without including PII beyond mentor UUIDs

Technical Requirements

frameworks
Flutter
data models
activity
performance requirements
Aggregation of 200 entries must complete in under 10ms — this is pure in-memory logic
No database calls in the aggregation step — it operates only on already-collected results
security requirements
error_reason strings must not include raw database error messages containing table/column names — sanitize to human-readable descriptions before storing in the result
BulkRegistrationResult must not log or expose personal data in error_reason fields — use mentor UUIDs only

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Implement aggregation as a static method or a standalone pure function (e.g., BulkResultAggregator.aggregate(List entries)) to keep it isolated from network/database concerns. Calculate counts using entries.where((e) => e.status == EntryStatus.success).length — avoid manual loops for readability. Determine BulkStatus by checking if successCount == 0 (all failure), failureCount == 0 (all success), otherwise partial. The aggregator should be called at the end of registerBulk after all phases (duplicate check + RPC write) have completed and all entries have been assigned a status.

Pre-populate all entries in a mutable map keyed by mentor_id at the start of registerBulk, then update statuses as each phase runs — this ensures no mentor is accidentally omitted from the final result even if a step throws unexpectedly.

Testing Requirements

Unit tests: test all four aggregation scenarios — all success, all failure, all skipped, mixed partial. Verify success_count, failure_count, and overall_status for each scenario. Test empty input list. Test that entries list order matches input mentorIds order.

Test that error_reason is non-null for all failure entries and null for all success entries. Test edge case: 1 mentor in list, succeeds → overall_status is success not partial. Test BulkRegistrationResult JSON serialization produces expected structure. Coverage target: 100% on the aggregation logic function — it is pure and fully testable.

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.