Create Supabase expense type catalogue schema
epic-travel-expense-registration-foundation-task-001 — Design and migrate the Supabase database schema for the expense type catalogue, including tables for expense types, exclusive group definitions, and org-specific catalogue overrides. Define RLS policies to enforce organisation-level isolation so HLF and Blindeforbundet see only their own catalogue entries.
Acceptance Criteria
Technical Requirements
Implementation Notes
Design the exclusive group concept as a many-to-many join table rather than a foreign key on expense_types so that future requirements can place a type in multiple groups if needed. Do not add a 'display_order' column yet — sort alphabetically or by created_at until UX requires explicit ordering. The requires_receipt_above_nok column stores the threshold as a nullable integer (null means no receipt ever required for this type) — this avoids a separate boolean and keeps the receipt logic in one place. For the RLS policy, use: CREATE POLICY select_own_org ON expense_types FOR SELECT TO authenticated USING ((auth.jwt()->>'organization_id')::uuid = organization_id).
Keep the seed data in a separate seed migration file (suffixed _seed) so it can be skipped in production if catalogue is managed via admin portal.
Testing Requirements
Write a migration test script (bash or SQL) that: (1) applies the migration to a test database, (2) inserts two organizations with different expense type catalogues using the service role, (3) authenticates as a user from org A and verifies SELECT returns only org A's expense types, (4) attempts SELECT as org B user and verifies org A's rows are not returned, (5) attempts INSERT as an authenticated (non-service-role) user and verifies it is blocked by RLS. This script should be runnable via supabase db test or a custom npm test:db script. Additionally, verify the mutual exclusion seed data: query expense_type_exclusive_group_members for HLF's 'transport-mode' group and confirm both 'km-godtgjørelse' and 'kollektiv' are present.
Row-level security policies for expense claims must correctly scope data to organisation, role (peer mentor sees own claims only, coordinator sees org-wide queue), and claim status. Incorrect RLS can expose claims cross-organisation or prevent coordinators from accessing the attestation queue.
Mitigation & Contingency
Mitigation: Define RLS policies in code-reviewed migration files. Write integration tests that attempt cross-org reads with different JWT roles and assert access denial. Review with a second engineer before merging migrations.
Contingency: If RLS is misconfigured post-deployment, disable the affected policy temporarily and apply a hotfix migration within the same release window. No claim data is exposed publicly due to Supabase project-level auth requirement.
The auto-approval Edge Function is triggered server-side on expense insert. Cold-start latency or Edge Function failures can block the submission response and degrade UX, especially on mobile networks.
Mitigation & Contingency
Mitigation: Implement the auto-approval Edge Function client with a timeout and graceful fallback: if no result is received within 5 seconds, treat the claim as 'pending' and poll for the status update via Supabase Realtime. Keep the Edge Function warm with a periodic ping.
Contingency: If Edge Function reliability is unacceptable, move auto-approval evaluation to a database trigger or Postgres function as an interim measure, accepting that threshold configuration changes require a migration rather than a settings update.
The expense type catalogue and threshold configuration are cached locally for offline use. If an organisation updates their catalogue exclusion rules or thresholds while a peer mentor is offline, the local cache may allow submissions that violate the new policy.
Mitigation & Contingency
Mitigation: Cache entries include a TTL (24 hours). On connectivity restore, refresh cache before allowing new submissions. Server-side validation in the Edge Function and save functions provides a second enforcement layer.
Contingency: If a stale-cache submission passes client validation but fails server validation, surface a clear error message explaining that the expense type rules have been updated and prompt the user to review their selection with the refreshed catalogue.