Define domain models for admin data layer
epic-admin-portal-foundation-task-001 — Create typed Dart domain models for all admin-scoped entities: OrgAdminScope, AdminUser, AdminActivity, AdminReimbursement, AdminOrganisation, and OrgSubtree. These models form the data contract consumed by all upstream admin services and must include serialization, equality, and copyWith support.
Acceptance Criteria
Technical Requirements
Implementation Notes
Follow the existing domain model pattern in the codebase. Use final fields with a const constructor where possible. For fromJson, use the pattern: field = json['field_name'] as Type — add explicit casts and null checks. For DateTime parsing, use DateTime.parse(json['created_at'] as String) and .toLocal() if the UI displays local times.
For the OrgSubtree.contains() helper, implement as bool contains(String orgId) => subtreeOrganisationIds.contains(orgId) || orgId == rootOrganisationId. The AdminRole and ReimbursementStatus enums should have a fromString(String) factory for JSON deserialization using a switch expression. Avoid using code generation (json_serializable, freezed) unless the project already uses it — adding new build_runner dependencies mid-project adds friction. If the project does use freezed, use it consistently.
Place enums in a separate file lib/domain/admin/admin_enums.dart to keep model files focused.
Testing Requirements
Unit tests only — no widget or integration tests needed for pure domain models. Test file: test/domain/admin/admin_models_test.dart. Required test groups: (1) Serialization — fromJson produces correct field values from a fixture JSON map for each model; toJson produces the correct snake_case map; fromJson(toJson(model)) equals original model; (2) Equality — two models with identical fields are equal; models differing in one field are not equal; (3) CopyWith — copyWith with no arguments returns equal model; copyWith with one argument changes only that field; copyWith supports null for nullable fields; (4) Edge cases — fromJson handles null optional fields without throwing; DateTime fields parse ISO 8601 strings correctly; enum fields throw on invalid values. Use const fixture maps defined at the top of the test file.
Missing RLS policies on one or more tables (e.g., a newly added join table or a Supabase view) could expose cross-org data to org_admin queries, creating a GDPR-reportable data breach.
Mitigation & Contingency
Mitigation: Enumerate all tables and views accessed by admin queries before writing any policy. Create an automated test that attempts a cross-org query for each table from an org_admin JWT and asserts an empty result set.
Contingency: If a gap is discovered post-deployment, immediately disable the affected query surface and deploy a hotfix policy before re-enabling. Log the incident and notify DPO if any cross-org data was returned.
The recursive CTE for NHF's deeply nested org tree (up to 5 levels, 1,400 local chapters) may exceed the 2-second dashboard load target when resolving large subtrees on every request.
Mitigation & Contingency
Mitigation: Benchmark the recursive CTE against a synthetic NHF-scale dataset during development. Introduce a short-TTL server-side cache for subtree resolution results. Index the parent_id column on the organisations table.
Contingency: If CTE performance remains insufficient, materialise the org subtree as a precomputed closure table updated on org structure changes, and switch the RLS guard to query the closure table instead.
Incorrect JWT claim injection in AdminRlsGuard (e.g., wrong claim key name or missing refresh on org switch) could silently apply the wrong org scope, causing org_admin to see a different organisation's data without an explicit error.
Mitigation & Contingency
Mitigation: Write unit tests for the guard that verify the injected claim value against the authenticated user's org_id for every admin route. Add a server-side assertion that the claim matches the user's database record before executing any query.
Contingency: Roll back the guard to a deny-all fallback, invalidate active admin sessions, and re-issue corrected JWTs. Audit query logs to identify any sessions that received incorrect scope.