aeat.domain.iva_compensation._carry_forward module¶
Pure regulatory IVA-compensation carry-forward logic for Modelo 303.
This module owns the typed period-state and carry-forward-lot records, the
FIFO projection that turns filed-period states into source-period lots, and
the four-year-window expiry policy. All logic here is pure: it depends only on
decimal, datetime, pydantic, and STRICT_FROZEN_CONFIG
from aeat.core. Repositories, port adapters, and orchestration that wire
these pure pieces to persistence live in the application layer.
- class IvaCompensationExpiryReviewState(*values)[source]¶
Bases:
StrEnumReview state for an IVA compensation carry-forward lot.
- ACTIVE¶
- EXPIRY_REVIEW_DUE¶
- EXPIRED_REVIEW_REQUIRED¶
- class IvaCompensationPeriodState(**data)[source]¶
Bases:
BaseModelLatest known Modelo 303 compensation state for one filed period.
- Parameters:
taxpayer_nif (str)
filing_year (int)
period (Period)
expediente_id (str)
status (str)
presented_at (datetime)
prior_pending_amount (Decimal | None)
applied_amount (Decimal | None)
pending_for_later_amount (Decimal | None)
period_result_amount (Decimal | None)
final_result_amount (Decimal | None)
generated_amount (Decimal)
available_end_amount (Decimal)
source_observation_key (str)
source_artefact_sha256 (str | None)
- taxpayer_nif: str¶
- filing_year: int¶
- period: Period¶
- expediente_id: str¶
- status: str¶
- presented_at: datetime¶
- prior_pending_amount: Decimal | None¶
- applied_amount: Decimal | None¶
- pending_for_later_amount: Decimal | None¶
- period_result_amount: Decimal | None¶
- final_result_amount: Decimal | None¶
- generated_amount: Decimal¶
- available_end_amount: Decimal¶
- source_observation_key: str¶
- source_artefact_sha256: str | None¶
- class IvaCompensationCarryForwardLot(**data)[source]¶
Bases:
BaseModelOne generated IVA compensation balance tracked from its source period.
- Parameters:
- taxpayer_nif: str¶
- source_filing_year: int¶
- source_period: Period¶
- generated_amount: Decimal¶
- applied_amount: Decimal¶
- remaining_amount: Decimal¶
- age_years: int¶
- expiry_review_state: IvaCompensationExpiryReviewState¶
- source_observation_key: str¶
- class IvaCompensationCarryForwardReport(**data)[source]¶
Bases:
BaseModelCarry-forward lot projection from filed Modelo 303 compensation history.
- Parameters:
as_of_year (int)
lots (tuple[IvaCompensationCarryForwardLot, ...])
unallocated_applied_amount (Decimal)
- as_of_year: int¶
- lots: tuple[IvaCompensationCarryForwardLot, ...]¶
- unallocated_applied_amount: Decimal¶
- derive_303_compensation_available(*, posterior, resultado, refunded=False)[source]¶
Modelo 303 end-of-period available compensation carry-forward.
availableequalsiva.compensacion-pendiente-periodos-posteriores(AEAT box 87) plus generated, where generated ismax(0, -iva.resultado)(AEAT box 69). A negative result (a quota a compensar) generates new carry-forward; a positive result generates none.When
refundedisTruethe period’s negative result is requested as devolución (fichero “Tipo de declaración”D) rather than carried forward: the generated credit is excluded from compensación carry, so the GENERATED component is zero andavailable = posterioronly (for a full monthly/annual devolución request the posterior is also zero, so the period carries nothing). The defaultFalsekeeps the standard compensación (C) behaviour, where the negative result generates the carry. Legal basis: RD 1624/1992 art. 30 / Ley 37/1992 art. 116 — a devolución-requested credit is not carried.
- build_iva_compensation_carry_forward_report(states, *, as_of_year)[source]¶
Project filed-period compensation states into source-period lots.
Applications of prior compensation are allocated FIFO across earlier generated lots. Current-period generation is added after any application recorded for that same period, matching Modelo 303’s prior-balance-before-new-generation shape.
Returns an
IvaCompensationCarryForwardReport.- Return type:
- Parameters:
states (tuple[IvaCompensationPeriodState, ...])
as_of_year (int)
- class IvaCompensationYearEndCarryPartition(**data)[source]¶
Bases:
BaseModelModelo 390 year-end carry fields as one FIFO partition.
The two AEAT annual carry-forward boxes partition the year’s pending compensation credit governed by FIFO application netting across the whole ejercicio; they are NOT two independent per-period sums:
last_period_amount(iva.anual.compensacion-ultimo-periodo-97, AEAT box 97, “Resultado de la última autoliquidación. A compensar”) is the saldo the LAST filed period carries forward — the year-generated remaining credit still pending at the end of the last period’s autoliquidación (its disponible, net of any prior-year credit still pending).generated_not_in_last_amount(iva.anual.compensacion-generada-ejercicio-no-97, AEAT box 662, “Cuotas a compensar generadas en el ejercicio, distintas a las incluidas en [97]”) is the remaining of credits GENERATED in the ejercicio that did NOT carry into the last period’s autoliquidación (e.g. a credit refunded in an intervening period, so it left the carry chain) — explicitly the year’s pending credit not included in AEAT box 97.
Applied credits appear in NEITHER box. The AEAT annual identity
[86] = [95] − [97] − [98] − [662]binds the pair: box 97 + box 662 must equal the year’s total pending with no double-count and no drop, sototal_year_remaining_amount == last_period_amount + generated_not_in_last_amountalways holds.- Parameters:
- filing_year: int¶
- last_period_amount: Decimal¶
- generated_not_in_last_amount: Decimal¶
- total_year_remaining_amount: Decimal¶
- derive_iva_compensation_year_end_carry_partition(report, period_states, *, filing_year)[source]¶
Partition the year’s pending compensation credit into the Modelo 390 annual carry ids.
Drives BOTH year-end carry boxes from the single FIFO projection (
report) plus the year’s filed period states, so they partition the year’s pending credit with no double-count and no drop (the AEAT identity).The discriminator between
iva.anual.compensacion-ultimo-periodo-97(AEAT box 97, carried into the last period) andiva.anual.compensacion-generada-ejercicio-no-97(AEAT box 662, generated-but-not-carried) is the last filed period’s available carry-forward saldo (available_end_amount=iva.compensacion-pendiente-periodos-posteriores+ generated):iva.anual.compensacion-ultimo-periodo-97= the year-generated credit carried into the last period — the last filed period’s disponible (available_end_amount) capped at the year’s total remaining (the disponible may also carry prior-YEAR credit, which AEAT box 97 must not double-count, hence the cap).iva.anual.compensacion-generada-ejercicio-no-97= the rest of the year’s remaining credit (generated in the ejercicio but not carried into the last period’s autoliquidación).
In the common always-carry case every pending credit flows forward into the last period, so
iva.anual.compensacion-ultimo-periodo-97collapses to the whole year’s remaining andiva.anual.compensacion-generada-ejercicio-no-97is zero. When a period’s credit does NOT carry into the last period (it left the chain), that remaining lands iniva.anual.compensacion-generada-ejercicio-no-97.Returns an
IvaCompensationYearEndCarryPartition.- Return type:
- Parameters:
report (IvaCompensationCarryForwardReport)
period_states (tuple[IvaCompensationPeriodState, ...])
filing_year (int)
- enforce_iva_compensation_four_year_window(report)[source]¶
Refuse remaining IVA compensation lots beyond the four-year window.
Returns the
IvaCompensationCarryForwardReportunchanged when all lots are within the window.- Return type:
- Parameters:
report (IvaCompensationCarryForwardReport)