Expense Types & Requirements
Feature Detail
Description
Defines and enforces per-organization expense types with mutually exclusive selection rules that make invalid combinations technically impossible - for example, preventing simultaneous selection of kilometer reimbursement and a bus ticket for the same journey. Coordinators configure allowed types, reimbursement rates, and exclusion groups in the admin portal; the mobile app fetches and caches this configuration for offline use. The ExpenseTypeSelectorWidget enforces rules via reactive disabling: selecting any type from an exclusion group immediately makes conflicting types unavailable. Server-side validation provides a second enforcement layer independent of client behavior, rejecting submissions with invalid combinations before persistence.
Sources & reasoning
Lines 68 and 113 from two separate sections both explicitly require fixed expense type selection that makes invalid combinations technically impossible. The parent area is Fase 2 (line 148), mapping to v1.0. This feature is foundational to travel-expense-registration and must ship in the same release.
-
docs/source/likeperson.md · line 68Faste valg for utleggstype - ikke fritekst - for å hindre feilkombinasjon (f.eks. både km og bussbillett).
-
docs/source/likeperson.md · line 113Detaljert refusjonsstyring med faste valg som gjør feilkombinasjon teknisk umulig (f.eks. km + bussbillett kan ikke velges samtidig). Automatisk godkjenning under terskel.
Analysis
HLF explicitly identified the inability to enforce mutually exclusive expense types as a root cause of reimbursement errors. When peer mentors can simultaneously select km-based and public transport reimbursement for the same trip, coordinators spend time correcting invalid submissions rather than approving valid ones, increasing per-claim cost and delaying payment. Enforcing valid combinations at the UI layer eliminates this error class before records ever enter the approval queue. The per-organization configuration model supports the multi-tenant architecture: each organization defines its own expense type taxonomy and rates through the admin portal without code changes, allowing organizations with materially different reimbursement policies to coexist on the platform. New organizations onboard by configuring types rather than requiring a deployment, reducing maintenance overhead as the platform scales to additional tenants.
Expense type rules are stored in the expense_types table with a JSON exclusion_groups field encoding sets of mutually exclusive type IDs. The mobile app fetches the active configuration as part of the module bootstrap response and caches it in Drift for offline availability. ExpenseTypeSelectorWidget reads the Riverpod-provided type configuration and applies reactive disabling: any selection updates the enabled state of conflicting types in the same exclusion group immediately. Admin portal management of expense types lives in the admin-expense-approval area. The backend validates submitted expense items against the active type configuration before persisting and returns a structured validation error if an invalid combination is detected, providing defense in depth against client bypass. Reimbursement rate values (e.g., kr-per-km) are stored as type-level configuration and returned with the type list in the bootstrap payload for client-side calculation previews.
Quality Assurance
Peer Mentor (primary) · Coordinator (same flow)Peer Mentor
Quick UAT
- Logg inn som Likeperson. Åpne en aktivitet og trykk på «Legg til utlegg».
- Velg «Kilometergodtgjørelse» fra listen over utleggstyper. Bekreft at kollektivrelaterte typer (f.eks. «Bussbillett») umiddelbart blir nedtonet og ikke kan velges.
- Forsøk å trykke på en nedtonet utleggstype. Bekreft at den forblir utilgjengelig og at en kort forklarende melding vises.
- Fjern valget av «Kilometergodtgjørelse». Bekreft at kollektivtypene igjen blir tilgjengelige for valg.
- Velg «Bussbillett», fyll ut beløp og send inn utlegget. Bekreft at innsendingen godkjennes av serveren og at aktiviteten viser korrekt utleggstype.
Quick UAT — Accessibility
-
Logg inn som Likeperson. Åpne en aktivitet og trykk på «Legg til utlegg».
- Screen reader «Legg til utlegg»-knapp annonseres med rolle 'button'; fokus flyttes til utleggsvelger-skjermen ved åpning.
- Keyboard / focus Knappen nås med Tab; aktiveres med Enter eller Space.
- Touch target «Legg til utlegg»-knapp ≥ 24×24 CSS px.
-
Velg «Kilometergodtgjørelse» fra listen over utleggstyper. Bekreft at kollektivrelaterte typer (f.eks. «Bussbillett») umiddelbart blir nedtonet og ikke kan velges.
- Screen reader Etter valg annonseres «Kilometergodtgjørelse, valgt». Nedtonede typer annonseres med tilstand 'disabled' (f.eks. «Bussbillett, ikke tilgjengelig»).
- Keyboard / focus Piltaster eller Tab navigerer mellom typer; Space velger; deaktiverte elementer hoppes over i tab-rekkefølge.
- Focus visibility Synlig fokusring på valgt type; deaktiverte typer har visuell nedtoning og aria-disabled='true'.
- Contrast Nedtonede typer oppfyller 3:1 kontrast mot bakgrunn; status formidles ikke kun via farge — ikoner eller tekstlabel bekrefter utilgjengelighet.
-
Forsøk å trykke på en nedtonet utleggstype. Bekreft at den forblir utilgjengelig og at en kort forklarende melding vises.
- Screen reader Forklarende melding annonseres via polite live region: f.eks. «Kan ikke kombineres med Kilometergodtgjørelse».
- Focus visibility Fokus forblir på den nedtonede typen; ingen navigasjon til annen skjerm.
- Touch target Nedtonet type-rad ≥ 24×24 CSS px selv i deaktivert tilstand.
- Live region «Kan ikke kombineres med Kilometergodtgjørelse» annonseres som polite live region.
-
Fjern valget av «Kilometergodtgjørelse». Bekreft at kollektivtypene igjen blir tilgjengelige for valg.
- Screen reader «Kilometergodtgjørelse, ikke valgt» annonseres. Reaktivert type annonseres som tilgjengelig.
- Live region Reaktivering av kollektivtyper annonseres som polite live region: f.eks. «Bussbillett er nå tilgjengelig».
- Contrast Reaktiverte typer viser fullt 4.5:1 kontrast; ingen resterende nedtoning-artefakter.
-
Velg «Bussbillett», fyll ut beløp og send inn utlegget. Bekreft at innsendingen godkjennes av serveren og at aktiviteten viser korrekt utleggstype.
- Screen reader «Bussbillett, valgt» annonseres. Etter innsending annonseres bekreftelsesmelding via live region.
- Focus visibility Etter innsending flyttes fokus til bekreftelsesvisning eller tilbake til aktivitetsdetaljside.
- Live region «Utlegg lagret» eller tilsvarende bekreftelse annonseres som polite live region.
- Zoom Skjema og typevalg-liste er fullt brukbar ved 200 % zoom uten horisontal scroll.
Role Boundaries
3 role(s) must NOT access this feature-
Organization Administrator
Organisasjonsadministrator konfigurerer utleggstyper og eksklusjonsgrupper i Admin Web Portal (Expense Approval → Auto-Approval Rules). Rollen har ingen direkte tilgang til utleggsregistreringen i mobilappen — disse skjermene er ikke synlige i admin-portalens navigasjon.
-
Global Administrator
Innlogging på mobilappen omdirigerer til admin-portalen; utleggsregistreringsskjermen er ikke tilgjengelig. Deep-link til utleggsvelger returnerer 403.
-
Prospective Organization Representative
Rollen har ingen brukerkonto i driftsplattformen; innlogging avvises. Mobilappen og utleggsregistreringen er ikke tilgjengelig.
Expected End State
Utlegget er lagret på aktiviteten med én gyldig utleggstype. Ugyldige kombinasjoner (f.eks. km-godtgjørelse + bussbillett) er avvist både i klienten via reaktiv deaktivering og på serversiden ved validering. Utlegget er synlig i aktivitetsdetaljvisningen med korrekt type og beløp.
Components (21)
Shared Components
These components are reused across multiple features
User Stories
No user stories have been generated for this feature yet.