cadrumo.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 cadrumo.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:
data (Any)
- taxpayer_nif: SubjectTaxId | None¶
- filing_year: FilingYear¶
- period: Period¶
- registry_snapshot_ref: RegistrySnapshotRef¶
- provenance: IvaCompensationStateProvenance¶
- expediente_id: AeatExpedienteId | None¶
- status: str | None¶
- presented_at: UtcInstant¶
- 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: ContentDigest | None¶
- class IvaCompensationCarryForwardLot(**data)[source]¶
Bases:
BaseModelOne generated IVA compensation balance tracked from its source period.
- Parameters:
data (Any)
- taxpayer_nif: SubjectTaxId | None¶
- source_filing_year: FilingYear¶
- source_period: Period¶
- generated_amount: Decimal¶
- applied_amount: Decimal¶
- remaining_amount: Decimal¶
- age_years: NonNegativeInt¶
- expiry_review_state: IvaCompensationExpiryReviewState¶
- source_observation_key: str¶
- class IvaCompensationCarryForwardReport(**data)[source]¶
Bases:
BaseModelCarry-forward lot projection from filed Modelo 303 compensation history.
- Parameters:
data (Any)
- as_of_year: FilingYear¶
- 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:
data (Any)
- filing_year: FilingYear¶
- 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)
- iva_compensation_period_sort_key(period)[source]¶
Order one typed IVA filing period within its filing year.
Quarterly and monthly Modelo 303 rows remain in their established ordinal order. The annual
0Arow is deliberately ordered after every periodic row; its generic calendar span begins in January, so date ordering would incorrectly put it before the year’s filings. Unsupported period families follow the recognised IVA filing forms without being interpreted as a periodic IVA declaration.