critical priority high complexity backend pending backend specialist Tier 2

Acceptance Criteria

A BatchInsertRpcAdapter class is implemented that calls a Supabase Edge Function (or PostgreSQL RPC) with the list of mentor IDs and the ActivityTemplate as input
The RPC/Edge Function executes inserts atomically within a PostgreSQL transaction — either all succeed or per-row failures are captured without rolling back the entire batch
The adapter maps the RPC response to a List<BulkRegistrationEntry> with mentor_id, status (success/failure), and error_reason populated from the database response
Database constraint violations (unique constraint, foreign key violations) per mentor are captured as failure entries with a human-readable error_reason, not surfaced as unhandled exceptions
If the entire RPC call fails (network error, Edge Function crash), BulkRegistrationService marks all pending mentor entries as failure with error_reason 'batch_rpc_failed' and returns a BulkRegistrationResult with overall_status failure
The adapter does not perform individual INSERT statements per mentor — the batch RPC must be a single call to avoid N+1 writes
The RPC/Edge Function enforces organisation-scoped insertion — the coordinator's organisation_id from their JWT is applied to all inserted activity rows
The adapter is covered by an interface (BatchInsertRpcAdapter abstract class) enabling mock injection in unit tests
Retry logic: if the RPC call fails with a 503/timeout, the adapter retries once after a 1-second delay before returning failure

Technical Requirements

frameworks
Flutter
BLoC
apis
Supabase Edge Functions (Deno) — batch activity insert function
Supabase PostgreSQL 15 — RPC function or stored procedure
data models
activity
activity_type
assignment
performance requirements
A batch of 50 mentor inserts must complete within 5 seconds end-to-end
Single RPC call must not exceed 10 seconds before timing out
Client must not block the UI thread during the RPC call — use async/await throughout
security requirements
The batch RPC must validate the coordinator's JWT and extract organisation_id server-side — organisation_id must not be trusted from the client payload
Supabase RLS must be enforced: the coordinator can only insert activities for mentors belonging to their organisation
Service role key is only used inside the Edge Function (Deno server), never in the mobile client
All inserted activity rows must have recorded_by_user_id set to the coordinator's user_id from the JWT

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Design the Supabase Edge Function (Deno) to accept a JSON body: { mentor_ids: string[], activity_template: ActivityTemplate, organization_id: string } where organization_id is overwritten server-side from the JWT claim. The function should loop over mentor_ids and INSERT each activity row, catching per-row errors and returning a response array: [{ mentor_id, success: bool, error?: string }]. Use a PostgreSQL savepoint per row inside the function to allow partial commits without rolling back the whole transaction. On the Flutter side, implement BatchInsertRpcAdapter using Supabase's functions.invoke() method.

Map the response array to BulkRegistrationEntry objects. Use a sealed class for RPC responses (RpcSuccess, RpcPartialSuccess, RpcFailure) to make exhaustive pattern matching possible in the service layer. Keep the retry logic simple: one retry with a fixed 1-second delay, using Future.delayed.

Testing Requirements

Unit tests: mock the BatchInsertRpcAdapter interface and verify BulkRegistrationService correctly maps success/failure entries from the mocked response. Test the retry logic: adapter returns 503 on first call, succeeds on second. Test total RPC failure: verify all entries marked as failure with 'batch_rpc_failed'. Test partial batch result: RPC returns 40 successes and 10 failures — verify BulkRegistrationResult reflects this accurately.

Integration tests (against Supabase test schema): insert a batch of 5 activities via the RPC and verify all rows are created with correct organisation_id and recorded_by_user_id. Test unique constraint violation: attempt to insert a duplicate activity and verify the failure entry captures the error_reason. Coverage target: ≥85% on the adapter mapping and error handling paths.

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.