high priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

DriverHonorariumService is injectable via Riverpod and has no direct Supabase imports — it depends on HonorariumRepository and OrgConfigRepository abstractions
submitHonorarium(expenseId, activityId, amount, declarationAcknowledged) persists the record via the repository and returns the created HonorariumRecord
getRateGuidance(orgId) returns the OrgHonorariumConfig for the given org, including: rate_band definitions with min/max amounts, the assignment count thresholds (e.g. 3rd and 15th), and the current driver's assignment count for the current period
validateAmount(amount, rateBand, orgId) returns a ValidationResult with isValid, the applicable rate band, and a human-readable guidance message; it returns invalid with a descriptive error if amount falls outside the configured band range
acknowledgeDeclaration(honorariumId) sets declaration_acknowledged_at to the current UTC time and returns the updated record; it returns an error if declaration was already acknowledged
isFeatureEnabled(orgId) returns true only when the org has the 'driver_honorarium' feature flag enabled in their org config; all other service methods call this guard and return a no-op result (typed as HonorariumFeatureDisabled) when false
The service correctly escalates the rate band when the driver's assignment count crosses the configured threshold (e.g. from 'standard' to 'elevated' at 3rd assignment)
All service methods are pure business logic — no direct database calls; the repository layer (from task-001) handles persistence
Unit tests cover: valid amount in band, amount below minimum, amount above maximum, band escalation at threshold boundary (2nd vs. 3rd assignment), feature flag disabled returns no-op, declaration already acknowledged returns typed error
Service is registered as a Riverpod provider and can be overridden in tests with a mock repository

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
OrgConfigRepository (reads org feature flags and rate config from Supabase)
HonorariumRepository (from task-001)
data models
HonorariumRecord
OrgHonorariumConfig (rate bands, thresholds)
OrgFeatureFlags
performance requirements
getRateGuidance must not make a network call on every invocation — cache the org config for the session or until explicitly invalidated
validateAmount is synchronous (no I/O) — O(1) complexity
Assignment count for band escalation is fetched once per session and cached in the Riverpod provider state
security requirements
isFeatureEnabled must be called as the first guard in every public method — not a post-hoc check
orgId must be sourced from the authenticated session (Supabase JWT), never from a method parameter that could be spoofed by a UI caller
declaration_acknowledged_at is set server-side (via repository) — the service must not pass a timestamp from the client
Rate band configuration is read-only from the service layer; only coordinators can modify it via the admin portal

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Place at `lib/features/expense/domain/services/driver_honorarium_service.dart`. Use the `Result` pattern (or a sealed class `HonorariumResult`) for all return types — avoid throwing exceptions in business logic; reserve exceptions for truly unexpected errors. The feature flag check pattern: `if (!await isFeatureEnabled(orgId)) return HonorariumResult.featureDisabled();`. Model `OrgHonorariumConfig` as an immutable Dart class with `const` constructor — it is a value object fetched once and cached.

For band escalation, implement a `resolveRateBand(int assignmentCount, OrgHonorariumConfig config)` pure function — testable in isolation. The Blindeforbundet-specific rate bands are: 'standard' (assignments 1–14) and 'elevated' (assignments 15+), with a 'bonus' trigger at assignment 3 for the Kontorhonorar rule — confirm exact thresholds with the product team before coding. Expose the Riverpod provider as `driverHonorariumServiceProvider` in the providers barrel file.

Testing Requirements

Unit tests only (no integration tests in this task — covered by task-011 and a dedicated org-extensions integration test). Use Riverpod's ProviderContainer with overrides to inject mock repositories. Test matrix: feature disabled → all methods return HonorariumFeatureDisabled; feature enabled, valid params → happy path; feature enabled, amount out of band → ValidationResult.invalid with message; band escalation at exact threshold (n-1, n, n+1 assignments); acknowledgeDeclaration called twice → second call returns AlreadyAcknowledgedError. Use flutter_test.

Aim for 100% branch coverage of DriverHonorariumService. No golden tests needed.

Component
Driver Honorarium Service
service medium
Epic Risks (3)
high impact high prob integration

The Dynamics portal (HLF) and Xledger (Blindeforbundet) APIs have organisation-managed API contracts that may change without notice. Field mapping requirements, authentication flows, and export formats are not fully documented and may only be clarified during integration testing.

Mitigation & Contingency

Mitigation: Engage HLF and Blindeforbundet technical contacts early to obtain API documentation, sandbox credentials, and example payloads before implementation starts. Design the accounting integration client as a thin adapter layer with organisation-specific mappers so that field mapping changes require only mapper updates, not core client changes.

Contingency: If API documentation is unavailable or the API is unstable during Phase 3, implement a CSV/JSON file export as an interim deliverable. Coordinators can manually upload the file to their respective accounting systems until the live API integration is completed.

high impact medium prob scope

The confidentiality declaration for Blindeforbundet drivers may have specific legal requirements around content, format, wording, and record-keeping that are not yet specified. Implementing the wrong declaration flow could expose Blindeforbundet to compliance risk.

Mitigation & Contingency

Mitigation: Treat the declaration content and acknowledgement flow as a Blindeforbundet-controlled configuration, not hardcoded text. Implement the declaration as a templated document fetched from Supabase and reviewed by Blindeforbundet before any production deployment. Obtain written sign-off on the declaration text and acknowledgement mechanism before the epic is considered complete.

Contingency: If legal requirements cannot be confirmed in time for the sprint, deliver the driver honorarium form without the confidentiality declaration and gate the entire driver feature behind its feature flag. The declaration can be added in a follow-up sprint once requirements are confirmed, without blocking other feature delivery.

high impact medium prob integration

If the accounting export can be triggered multiple times for the same approved claims batch, duplicate records may be created in Dynamics or Xledger, causing accounting reconciliation problems that are difficult to reverse.

Mitigation & Contingency

Mitigation: Implement idempotent export runs: each export batch is assigned a unique run ID stored in the database. The accounting integration client checks for an existing successful export run for the same claim IDs before submitting. Approved claims that have been exported are marked with exported_at timestamp to prevent re-export.

Contingency: If duplicate exports occur despite idempotency checks (e.g. network failure after API success but before local confirmation), provide coordinators with an export history panel showing run IDs and timestamps. Implement a reconciliation endpoint that can query the accounting system for existing records before re-submitting flagged claims.