critical priority medium complexity database pending database specialist Tier 1

Acceptance Criteria

A `UnitAssignmentRepository` abstract class and a `SupabaseUnitAssignmentRepository` implementation are created
`assignUserToUnit(userId, unitId, isPrimary)` inserts a row into the junction table and returns the created assignment
`removeUserFromUnit(userId, unitId)` deletes the assignment row and returns a success/failure result
`updatePrimaryUnit(userId, unitId, isPrimary)` updates the `is_primary` flag; setting a new primary automatically unsets the previous primary for that user
`getUnitsForUser(userId)` returns all organizational units a user is assigned to, ordered by `is_primary DESC` then unit name
`getUsersForUnit(unitId)` returns all users assigned to a given unit with their role and `is_primary` flag
Attempting to create a duplicate assignment (same userId + unitId) returns a domain-level `DuplicateAssignmentException` rather than leaking a Supabase/PostgreSQL error
All repository operations are scoped by the caller's access scope — a coordinator cannot assign users to units outside their chapter
Repository methods use typed `Result<T, E>` or equivalent error-handling pattern — no uncaught exceptions propagate to callers
Unit tests with a mock Supabase client cover all CRUD operations and the duplicate constraint path
Repository integration with the hierarchy cache: `getUnitsForUser` checks the cache for pre-fetched unit metadata before making additional Supabase calls

Technical Requirements

frameworks
Flutter
Supabase Dart SDK
BLoC or Riverpod (for dependency injection)
flutter_test + mocktail (for unit tests)
apis
Supabase REST API — table: `user_unit_assignments`
Supabase RPC for atomic primary-unit swap
data models
UnitAssignment
OrganizationalUnit
UserProfile
UserRole
AccessScope
performance requirements
`getUnitsForUser` completes in under 200ms for users with up to 5 assigned units (NHF max)
`getUsersForUnit` completes in under 500ms for units with up to 100 assigned users
Bulk assignment operations (coordinator assigning multiple users) use batched Supabase inserts, not N individual calls
security requirements
All queries must apply Row-Level Security (RLS) via Supabase — repository must never bypass RLS with service-role key on the client
Scope enforcement: repository validates that the target unit is within the caller's AccessScope before executing write operations
User IDs and unit IDs must be validated as non-empty UUIDs before any Supabase call

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Model the junction table as `user_unit_assignments(id UUID, user_id UUID, unit_id UUID, is_primary BOOL, assigned_at TIMESTAMPTZ)` with a unique constraint on `(user_id, unit_id)`. The primary-unit swap (unset old primary, set new primary) must be atomic — implement this as a Supabase RPC function (PostgreSQL function) rather than two sequential REST calls to avoid race conditions. Map the Supabase `PostgrestException` with code `23505` (unique violation) to `DuplicateAssignmentException` in the repository. Use a `Result` type (or the `dartz` package's `Either`) so callers handle errors explicitly.

Inject the `HierarchyCacheRepository` into this repository via constructor so that after a write operation, the relevant cache entries can be invalidated without tight coupling.

Testing Requirements

Write unit tests using mocktail to mock the Supabase client. Test each CRUD method: (1) happy path with valid inputs; (2) duplicate assignment returns DuplicateAssignmentException; (3) out-of-scope assignment returns ScopeViolationException; (4) Supabase network error is wrapped in a domain-level error type; (5) primary-unit swap atomically unsets the old primary. Write one integration test against a Supabase test project (or local Supabase via Docker) that verifies the full create→read→update→delete lifecycle. Use setUp/tearDown to clean test data.

Verify that getUnitsForUser returns results in the correct order (primary first).

Component
Unit Assignment Service
service medium
Epic Risks (3)
high impact medium prob technical

Recursive aggregation queries across four hierarchy levels (national → region → local) with 1,400 leaf nodes may be too slow for real-time dashboard requests, exceeding the 200ms target and causing spinner timeouts.

Mitigation & Contingency

Mitigation: Implement aggregation as a Supabase RPC using a single recursive CTE rather than multiple round-trip queries. Pre-compute aggregations nightly via a scheduled Edge Function and cache results. For real-time needs, aggregate only the immediate subtree on demand.

Contingency: Surface a 'Refreshing...' indicator and serve stale cached aggregations immediately. Queue an async recalculation and push updated data via Supabase Realtime when ready, avoiding blocking the admin dashboard.

medium impact medium prob scope

The 5-chapter limit and primary-assignment constraint are NHF-specific. Applying these rules globally may break HLF and Blindeforbundet configurations where different limits apply, requiring per-organization configuration that was not initially scoped.

Mitigation & Contingency

Mitigation: Make the maximum assignment count a configurable value stored in the organization's feature-flag or settings table rather than a hardcoded constant. Design the assignment service to read this limit at runtime per organization.

Contingency: Default the limit to a high value (e.g., 100) for organizations other than NHF, effectively making it non-restrictive, while keeping the enforcement logic intact for when per-org configuration is fully implemented.

medium impact low prob technical

The searchable parent dropdown in HierarchyNodeEditor must search across up to 1,400 units efficiently. Client-side filtering of the full hierarchy may be slow; server-side search adds complexity and latency.

Mitigation & Contingency

Mitigation: Use the in-memory hierarchy cache as the search corpus — since the cache already holds the flat unit list, client-side filtering with a debounced input is sufficient and avoids extra Supabase calls. Pre-build a search index on cache load.

Contingency: Cap the dropdown to showing the 50 most recently accessed units by default, with a 'search all' option that triggers a server-side full-text query. This keeps the common case fast while supporting edge cases.