cadrumo.domain.iva.saturation module

Grounded saturation primitives for the cadrumo.domain.iva subpackage.

Two reusable primitives that let a caller turn a selected IvaCategory and a transaction gross into the regulated euro substrate (rate, taxable base, IVA amount) without ever guessing a number:

  • resolve_category_rate() maps an IvaCategory to its IvaRateKind and looks the applicable rate up via cadrumo.domain.iva.lookup.lookup_rate(), returning it as a decimal fraction (Decimal("0.21")) wrapped in a typed IvaRateResolution. Domestic general / reduced / super-reduced derive a positive rate; domestic zero and exempt derive 0; every category with no simple derivable positive domestic rate (intra-community, export, reverse-charge, recargo, import, régimen simplificado, no-sujeta, erroneous, unknown) returns a derivable=False resolution carrying an explicit operator-facing reason — never a fabricated rate.

    A domestic tier ALSO refuses while a temporary statute has it carrying two rates at once. Asking a tier for “its” rate is well defined only while the tier has one: RDL 4/2024 art. 1 moved part of the reduced and super-reducido supplies onto a temporary rate and left the rest on the ordinary one, and which of the two applies turns on WHAT was supplied — an axis no bundled AEAT surface carries. Answering with the ordinary rate there would split a gross at the wrong rate, understating the base and overstating the cuota, so the ambiguity is surfaced rather than resolved by guess. rate_kinds_for_declared_rate() is the well-defined inverse for a caller that already holds a rate.

  • split_gross_at_rate() performs the inverse split of a gross at a rate fraction into (taxable_base, iva_amount) quantised with the AEAT-mandated cadrumo.core.money.rounding.round_to_cents() (ROUND_HALF_UP).

The split formula is the canonical inverse of an IVA-inclusive gross: base = round_to_cents(gross / (1 + rate)) and iva = round_to_cents(gross - base). Quantising the base first and deriving the IVA as the remainder guarantees base + iva == gross to the cent regardless of the rounding residual, which is exactly the invariant the cadrumo.domain.transactions.models.Transaction model enforces.

The rate values are grounded in the authored iva-rate-schedule fact (Spain general 21 / reduced 10 / super-reduced 4 / zero 0; LIVA art. 90/91, year-scoped).

class IvaRateResolution(**data)[source]

Bases: BaseModel

Typed outcome of resolving an IvaCategory to a rate fraction.

A resolution is self-documenting: it never silently substitutes a fabricated rate for a category the system cannot ground. When derivable is True the rate carries the applicable rate as a decimal fraction in [0, 1] (e.g. Decimal("0.21"); Decimal("0") for zero-rated and exempt categories). When derivable is False the rate is None and reason states why the operator must complete the field.

Variables:
  • category – The IvaCategory that was resolved.

  • derivable – True when a Spanish domestic rate fraction was derived, False when the category has no simple derivable domestic rate and the operator must complete it.

  • rate – The applicable IVA rate as a decimal fraction in [0, 1] when derivable, else None.

  • rate_kind – The IvaRateKind the category maps to when derivable, else None.

  • reason – An operator-facing explanation present only when the rate is not derivable; the empty string otherwise.

Parameters:

data (Any)

category: IvaCategory
derivable: bool
rate: Decimal | None
rate_kind: IvaRateKind | None
reason: str
resolve_category_rate(category, *, on_date, operation)[source]

Resolve an IvaCategory to its Spanish IVA rate fraction.

Maps category to its IvaRateKind and looks the applicable rate up via cadrumo.domain.iva.lookup.lookup_rate() for registry-declared Spanish member-state token on on_date, returning the percentage as a decimal fraction (IvaRateRecord.pct / 100). Domestic general / reduced / super-reduced derive a positive fraction; domestic zero and exempt derive Decimal("0"). Every category with no simple derivable positive domestic rate returns a derivable=False resolution carrying an operator-facing reason — the system never guesses a rate for those.

Parameters:
  • category (IvaCategory) – The selected IVA category to resolve.

  • on_date (date) – The effective date used to resolve the registry rate.

  • operation (PinnedAuthorityOperation) – Caller-owned pinned authority operation for the complete category and rate lookup.

Return type:

IvaRateResolution

Returns:

A typed IvaRateResolution. derivable is True with a rate fraction for domestic categories; False with a reason for every non-derivable category.

Raises:

IvaRateNotFoundError – If a domestic category maps to a rate kind that the registry has no record for on on_date (a registry gap, not a category-shape problem).

split_gross_at_rate(gross, rate)[source]

Inverse-split an IVA-inclusive gross into (taxable_base, iva_amount).

Computes the IVA-exclusive base and the IVA charged from an IVA-inclusive gross at rate (a decimal fraction, e.g. Decimal("0.21")), quantising with the AEAT-mandated cadrumo.core.money.rounding.round_to_cents() (ROUND_HALF_UP). The base is quantised first and the IVA is taken as the quantised remainder (gross - base), so taxable_base + iva_amount == gross holds to the cent regardless of the rounding residual.

A rate of 0 (zero-rated or exempt) yields the whole gross as the base and a zero IVA amount.

Parameters:
  • gross (Decimal) – The IVA-inclusive gross amount. Expected non-negative (the caller passes abs(amount) for a signed transaction); a signed value is split as given.

  • rate (Decimal) – The IVA rate as a decimal fraction in [0, 1].

Return type:

tuple[Decimal, Decimal]

Returns:

A (taxable_base, iva_amount) tuple, each quantised to euro cents, whose sum equals round_to_cents(gross) to the cent.