Implement ActivityTypeRepository with org scoping
epic-activity-type-configuration-business-logic-task-002 — Build the ActivityTypeRepository class that queries the Supabase activity_types table using the authenticated org context. Implement fetchActiveByOrg(orgId), fetchAll(orgId), fetchById(orgId, id), create, update, and softDelete methods. Ensure all queries are scoped to the org via RLS-compatible filters and map raw rows to ActivityType domain objects.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Place in lib/features/activity_type/data/repositories/activity_type_repository.dart. Inject SupabaseClient via constructor (or Riverpod provider) — never instantiate Supabase.instance directly inside the repository for testability. The Supabase RLS policy on activity_types enforces org isolation server-side, but always include .eq('organization_id', orgId) in every query for defense-in-depth and explicit intent. Map the DB column is_active (boolean) to ActivityType.isActive and triggers_reimbursement_workflow to triggersReimbursementWorkflow — keep the fromJson mapping in the domain model (task-001), not in the repository.
For softDelete, use .update({'is_active': false}).eq('id', id).eq('organization_id', orgId) — the double-eq prevents cross-org soft-deletes even if RLS is misconfigured. Expose a Riverpod Provider
Testing Requirements
Integration tests using flutter_test with a test Supabase project (or local Supabase via Docker): (1) fetchActiveByOrg returns correct filtered list with only active types for the correct org, (2) fetchActiveByOrg for org with no active types returns empty list (not error), (3) fetchById returns null for non-existent id rather than throwing, (4) create inserts row and returned object has server-generated id and createdAt, (5) update persists changes and returns updated object, (6) softDelete sets is_active=false without removing row, (7) all methods throw RepositoryException (not PostgrestException directly) on DB error. Unit tests with mocked Supabase client covering error path: network failure wraps in RepositoryException.
Metadata flag combination rules differ between organisations (e.g., Blindeforbundet honorarium thresholds, HLF mutual exclusion of km and transit). Encoding these as generic service-level validation may be insufficient, forcing organisation-specific branching inside the service that becomes unmaintainable as new organisations are onboarded.
Mitigation & Contingency
Mitigation: Model flag validation as a pure function that accepts an ActivityTypeMetadata object and an org configuration record, making org-specific rules data-driven rather than hardcoded. Establish the validation contract in the foundation epic so the service just delegates to the validator.
Contingency: Defer complex cross-flag validation to a lightweight edge function that can be updated without a mobile app release, accepting that initial validation in the mobile service layer is permissive and corrected server-side.
Blindeforbundet users rely on VoiceOver and JAWS. If the selection screen is built with non-semantic widgets that fail accessibility audit late in the sprint, a significant rework of the widget tree may be required, blocking the registration wizard integration.
Mitigation & Contingency
Mitigation: Build the selection screen against the project's established accessibility design tokens and semantics wrapper conventions from the start. Run Flutter's semantic tree inspector and a manual VoiceOver pass before marking any widget task complete.
Contingency: Wrap all tappable items in the project's SemanticsWrapperWidget and schedule a dedicated accessibility review session with a screen reader user from Blindeforbundet before the epic is closed.