cadrumo.domain.currency.service module

Currency normalization service: convert foreign amounts to EUR.

Provides ExchangeRateProvider — the protocol an exchange-rate backend must implement — and CurrencyNormalizationService, which applies a provider-supplied rate to produce a NormalizedAmount (from ._models). When no provider is configured or no rate is available the service returns a NormalizedAmount with status = CurrencyNormalizationStatus.MISSING_RATE so callers can surface a human-readable warning rather than silently propagating a zero amount into a filing (modelo = an AEAT tax form).

class ExchangeRateProvider(*args, **kwargs)[source]

Bases: Protocol

Protocol for fetching exchange rates.

property rate_source_id: str

Stable identifier for the rate authority this provider speaks for.

Stamped onto every converted record so a stored euro figure names the series it came from. Required rather than optional: a provider that cannot say who it is produces conversions nothing can later audit, and a default here would put that anonymity one attribute away.

get_eur_rate(currency, rate_date)[source]

Get the exchange rate to EUR for a given currency and date.

Returns the rate such that original_amount * rate = eur_amount. Returns None if no rate is available.

Return type:

Decimal | None

Parameters:
class CurrencyNormalizationService(rate_provider=None)[source]

Bases: object

Service to normalize foreign currencies to EUR.

Parameters:

rate_provider (ExchangeRateProvider | None)

normalize(amount, rate_date)[source]

Normalize an amount to EUR using the rate for the given date.

Return type:

NormalizedAmount

Returns:

A NormalizedAmount with the EUR-equivalent and conversion metadata.

Parameters:
resolve_fx_conversion_stamp(*, currency, on_date, rate_provider)[source]

Return the euro-conversion stamp for a foreign-currency record, or None.

The single authority for the stamping policy: which date the rate is taken at, when a record is left unstamped, and what an unresolvable rate means. The rate SOURCE was already centralised behind ExchangeRateProvider; this centralises the decision made around it, which had been hand-declared at two application call sites.

Conversion happens at the record’s own operation date — the issue or invoice date — because that is the date Spanish law binds the official rate to (Ley 46/1998 art. 36), and because converting once at ingest keeps a stored euro figure stable rather than drifting with every later read.

A euro record is left unstamped: there is nothing to convert, and a 1-valued stamp would assert a conversion that never happened. A foreign record whose rate cannot be resolved is ALSO left unstamped rather than defaulted, and that is the load-bearing half of this policy. Without a stamp the record reports no euro value and is held back from projection, which an operator can see and correct; a fabricated or assumed rate would instead flow silently into a filed euro amount, and nothing downstream could tell it from a real one.

Parameters:
  • currency (str) – The record’s currency. Compared case-insensitively against the euro token, so a caller that has not yet normalised it is safe.

  • on_date (date) – The operation date to take the rate at.

  • rate_provider (ExchangeRateProvider) – The rate source. Required rather than defaulted: the default ECB provider is an outbound adapter, and this layer must not reach for it.

Return type:

FxConversionStamp | None

Returns:

The FxConversionStamp to apply, or None when the record must be left unstamped — euro, or no resolvable rate. The stamp names the provider’s rate authority, so the record says not only what rate was applied and when, but on whose published series.