Service Layer medium complexity mobile
2
Dependencies
2
Dependents
1
Entities
0
Integrations

Description

Riverpod StateNotifier or BLoC that owns the current expense type selection set and enforces mutual exclusion rules on every toggle event. Computes which types are disabled after each change and exposes the updated selection state and disabled set to the UI layer.

Feature: Expense Type Selection with Mutual Exclusion

expense-selection-bloc

Summaries

The Expense Selection BLoC is the business rules engine that enforces correct expense claiming behavior across the entire reimbursement submission flow. It ensures that peer mentors cannot accidentally submit incompatible expense combinations — such as claiming both personal vehicle mileage and public transit for the same journey — which would create compliance issues and require manual correction by finance teams. By centralizing these mutual exclusion rules in a single, tested component, the organization guarantees consistent policy enforcement regardless of how the UI evolves. This reduces claims processing errors, protects reimbursement program integrity, and lowers the administrative cost of claim review and correction.

This is a medium-complexity state management component with two critical dependencies: the Mutual Exclusion Rule Engine (which defines compatibility logic) and the Expense Calculation Service (which computes reimbursement amounts). Both must be available before end-to-end integration testing can proceed, though the BLoC itself can be unit-tested with mocked dependencies. This component is a shared dependency for the Expense Type Picker Widget and the Expense Calculation Preview — sequencing its delivery early unblocks both UI components. The stream-based interface requires careful testing of rapid successive toggle events to ensure no race conditions or state inconsistencies.

Allocate time for property-based testing of the mutual exclusion matrix across all four expense type combinations.

Implement as a Riverpod StateNotifier or a flutter_bloc Cubit/BLoC. The state object carries two sets: selectedTypes and disabledTypes. On toggleExpenseType(type): if type is in disabledTypes, ignore the event; if type is in selectedTypes, remove it and recompute disabled; otherwise add it and recompute disabled. Recomputation delegates to MutualExclusionRuleEngine.computeDisabled(selectedTypes) → Set.

After every valid toggle, invoke ExpenseCalculationService.calculate(selectedTypes) asynchronously and emit an intermediate loading state, then a final state with the updated calculation result. clearSelection() resets both sets to empty and clears the calculation. Expose getSelectedTypes() and getDisabledTypes() as synchronous getters on the current state for use by widgets that don't need stream subscription. Ensure the StateNotifier is scoped to the expense submission screen's ProviderScope so it is automatically disposed when the screen is popped.

Responsibilities

  • Maintain the current set of selected expense types
  • Invoke compatibility matrix on each toggle to compute disabled types
  • Expose loading, valid, and error states
  • Trigger reimbursement recalculation on selection change

Interfaces

toggleExpenseType(ExpenseType type)
clearSelection()
getSelectedTypes() → Set<ExpenseType>
getDisabledTypes() → Set<ExpenseType>
Stream<ExpenseSelectionState> get stream
ExpenseSelectionState get state

Relationships

Dependencies (2)

Components this component depends on

Dependents (2)

Components that depend on this component

Related Data Entities (1)

Data entities managed by this component

API Contract

View full contract →
REST /api/v1/expense-selections 5 endpoints
GET /api/v1/expense-selections List all expense selection sessions
GET /api/v1/expense-selections/:id Get current selection state for a session
POST /api/v1/expense-selections Create a new expense selection session
PUT /api/v1/expense-selections/:id Toggle an expense type within a selection session
DELETE /api/v1/expense-selections/:id Clear and delete a selection session