Design organization_units database schema
epic-organizational-hierarchy-management-foundation-task-001 — Design and implement the recursive adjacency-list database schema for the organization_units table in Supabase. Include columns for id, parent_id (self-referential FK), name, unit_type (national/region/chapter), org_id, is_active, deleted_at (soft-delete), and created_at. Add cascade constraints and indexes for tree traversal performance.
Acceptance Criteria
Technical Requirements
Implementation Notes
Use an adjacency list (not a nested set or closure table) as specified β it is the simplest model for a hierarchy that changes infrequently and needs efficient parentβchildren traversal. The recursive CTE view is the right pattern for NHF's 3-level hierarchy (national β region β chapter). For circular reference prevention, a PostgreSQL trigger (BEFORE INSERT OR UPDATE) that walks the ancestor chain is the most reliable approach β the trigger calls a recursive function checking if the new parent_id is a descendant of the row being inserted. Keep the trigger simple: it only needs to walk up the tree (following parent_id) and abort if it ever reaches the current row's id.
NHF has up to 1,400 chapters β the hierarchy is wide, not deep (max 3 levels), so recursive traversal is cheap. Ensure the migration filename follows the existing project convention (timestamp prefix).
Testing Requirements
Write SQL-level tests (pg_tap or plain SQL assertions in a test migration): (1) insert a national β region β chapter chain and verify the CTE view returns correct depth values (0, 1, 2), (2) attempt to delete a region node that has chapter children and verify ON DELETE RESTRICT raises an error, (3) insert two chapters with the same name under the same parent and verify the unique constraint rejects the second insert, (4) insert a chapter with deleted_at set and verify the unique constraint does NOT block a new active chapter with the same name under the same parent, (5) verify that setting parent_id = id (self-reference) is rejected by the circular reference check. Also write a Dart integration test that calls OrganizationUnitRepository.getChildren(parentId) and verifies the returned list matches the seeded data.
Recursive CTE queries for large hierarchies (1,400+ nodes) may exceed Supabase query timeouts or produce unacceptably slow responses, degrading tree load time beyond the 1-second target.
Mitigation & Contingency
Mitigation: Implement Supabase RPC functions for subtree fetches rather than client-side recursive calls. Use materialized path or closure table as a supplemental index for depth-first traversal. Benchmark with realistic NHF data volumes during development.
Contingency: Fall back to a pre-computed flat unit list stored in the hierarchy cache with client-side tree reconstruction, trading freshness for speed. Add a background refresh job to keep the cache warm.
Concurrent writes from multiple admin sessions could cause cache staleness, leading to stale tree views and incorrect ancestor path computations that corrupt aggregation results.
Mitigation & Contingency
Mitigation: Use optimistic versioning on cache entries with a short TTL (5 minutes) as a safety net. Subscribe to Supabase Realtime on the organization_units table to push invalidation events to all connected clients.
Contingency: Provide a manual 'Refresh Hierarchy' action in the admin portal that forces a full cache bust, and display a staleness warning banner when the cache age exceeds the TTL.
Persisting the flat unit list to local storage may expose organization structure data if the device is compromised or the storage is not properly encrypted, violating data protection requirements.
Mitigation & Contingency
Mitigation: Use flutter_secure_storage (AES-256 backed by Keychain/Keystore) for the local unit list cache rather than SharedPreferences. Include only unit IDs, names, and types β no member PII.
Contingency: Disable local-storage persistence entirely and rely on in-memory cache only. Accept the trade-off of no offline hierarchy access for the security guarantee.