Resolve org-specific activity type labels via provider
epic-activity-type-configuration-business-logic-task-005 — Integrate the OrganizationLabelsProvider into ActivityTypeService so that each ActivityType returned to the UI has its display label resolved from the org's terminology map rather than raw database keys. Ensure label resolution handles missing keys gracefully with a fallback to the raw key value. Wire up the Riverpod providers so the labels provider is injected into the service layer.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 4 - 323 tasks
Can start after Tier 3 completes
Implementation Notes
Add resolvedLabel as a nullable late field or include it in copyWith/fromJson as an optional parameter defaulting to null. The cleanest pattern is to add a withResolvedLabel(String label) method on ActivityType that returns a copyWith-constructed instance with the label set, keeping the domain model pure. In the service, after fetching from cache/repository, resolve labels in a single pass: final labelMap = await labelsProvider.getTerminologyMap(orgId); return types.map((t) => t.withResolvedLabel(labelMap[t.labelKey] ?? t.labelKey)).toList().
The OrganizationLabelsProvider should already be a Riverpod provider — inject it into ActivityTypeService via constructor parameter typed as OrganizationLabelsProvider (or its interface). Ensure this integration does not break existing callers of getActiveTypes — resolvedLabel being populated is additive, not breaking.
Testing Requirements
Unit tests using flutter_test with mocked OrganizationLabelsProvider: (1) known labelKey resolves to org-specific display string, (2) unknown labelKey falls back to raw labelKey without error, (3) empty terminology map results in all types using labelKey as resolvedLabel, (4) provider injection: service calls OrganizationLabelsProvider.getLabel(orgId, labelKey) not a static method, (5) toJson() output does not include resolvedLabel key, (6) fromJson() creates ActivityType with resolvedLabel as null until service resolves it. Verify OrganizationLabelsProvider is called exactly once per getActiveTypes invocation (not once per activity type) using mockito verify call count.
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.