Service Layer low complexity Shared Component mobilebackend
0
Dependencies
1
Dependents
3
Entities
0
Integrations

Description

Evaluates whether a receipt attachment is required or optional based on the org-configurable monetary threshold (default 100 kr for HLF). Reads the threshold value from organization configuration and exposes a reactive stream so the form UI updates instantly when the claim amount changes.

Feature: Receipt Capture and Attachment

receipt-threshold-validator

Summaries

The Receipt Threshold Validator enforces the organization's expense policy automatically, reducing reliance on employees knowing the rules. For HLF, the default 100 kr threshold ensures that receipts are only required when they materially impact the audit trail, reducing unnecessary friction for small purchases while maintaining compliance controls where they matter. Because the threshold is organization-configurable, the same system can serve multiple organizations with different policies without code changes — a key capability for scaling the product to new customers. This component is shared across mobile and backend contexts, ensuring policy is enforced consistently regardless of submission channel.

This shared component runs in both mobile and backend execution contexts, which adds coordination overhead — logic must be consistent across platforms or centralized. Its role as a dependency for the Receipt Attachment Indicator means it sits on the critical path for that widget's integration. The reactive stream interface (`getThresholdStatusStream`) requires testing under amount-change scenarios, including boundary values at exactly the threshold amount. Organization configuration loading must be tested with multiple org IDs to confirm isolation.

Since this component has no external dependencies, it can be developed and unit-tested independently early in the sprint, de-risking downstream components. Low complexity but high integration breadth.

The Receipt Threshold Validator is a shared service with no external dependencies, making it a clean, easily testable unit. Its core logic evaluates `claimAmount >= threshold` against a per-org config value loaded via `getThresholdForOrg(orgId)`. The reactive interface `getThresholdStatusStream(Stream, orgId)` should be implemented using standard reactive primitives (e.g., `switchMap` or `combineLatest` in RxDart) so the UI receives threshold status updates synchronously as the amount field changes. The `validateSubmission` method doubles as a backend guard — ensure both mobile and backend implementations use the same threshold logic, ideally via shared business logic or a well-tested shared module.

Cache org configuration to avoid repeated reads on each amount change. Boundary condition: treat `claimAmount == threshold` as required.

Responsibilities

  • Load org-specific receipt threshold from configuration
  • Determine if receipt is required given a claim amount
  • Expose reactive threshold status stream for UI binding
  • Support multiple organizations with different threshold values

Interfaces

isReceiptRequired(double claimAmount, String orgId)
getThresholdForOrg(String orgId)
getThresholdStatusStream(Stream<double> amountStream, String orgId)
validateSubmission(double claimAmount, bool hasAttachment, String orgId)

Relationships

Dependents (1)

Components that depend on this component

API Contract

View full contract →
REST /api/v1/receipt-thresholds 7 endpoints
GET /api/v1/receipt-thresholds List receipt thresholds for all organisations
GET /api/v1/receipt-thresholds/:orgId Get receipt threshold configuration for a specific organisation
POST /api/v1/receipt-thresholds Create a receipt threshold configuration for an organisation
PUT /api/v1/receipt-thresholds/:orgId Update receipt threshold for an organisation
DELETE /api/v1/receipt-thresholds/:orgId Delete receipt threshold configuration for an organisation
GET /api/v1/receipt-thresholds/:orgId/required Check whether a receipt is required for a given claim amount
+1 more