cadrumo.domain.renta.ledger_expenses module¶
Renta deductible-expense observations derived from ledger facts.
RentaDeductibleExpenseFact is evaluated through
evaluate_renta_deductibility() using CategoryProfile and
RentaDeductibilityContext; eligible
RentaDeductibilityResult values become
RentaDeductibleExpenseObservation records routed through the dated
facts-registry mapping to CasillaId bindings for
Modelo('100').
- class RentaExpenseDirection(*values)[source]¶
Bases:
StrEnumClosed direction values for first-slice Renta expense facts.
- OUTGOING_EXPENSE¶
- REFUND¶
- REVERSAL¶
- class RentaDeductibilityStatus(*values)[source]¶
Bases:
StrEnumWhether a ledger fact can produce a Renta calculation observation.
- ELIGIBLE¶
- INELIGIBLE¶
- class RentaInvoiceEvidenceStatus(*values)[source]¶
Bases:
StrEnumInvoice-evidence state carried by the Renta observation.
- NONE¶
- LINKED¶
- class RentaReconciliationStatus(*values)[source]¶
Bases:
StrEnumTransaction/invoice reconciliation state for duplicate prevention.
- TRANSACTION_ONLY¶
- LINKED_INVOICE¶
- class RentaDeductibilityContext(**data)[source]¶
Bases:
_RentaStrictFrozenModelInputs that affect proportionality but are not ledger facts.
- Parameters:
data (Any)
- profile_year: FilingYear¶
- usage_ratios: dict[SpendingCategory, Decimal]¶
- statutory_cap_days: Decimal | None¶
- statutory_cap_variant_id: str | None¶
- statutory_cap_person_count: int¶
- statutory_cap_variant_person_counts: Mapping[str, int]¶
How many insured persons fall under each annual cap variant, keyed by variant id.
LIRPF art. 30.2.5.a caps the seguro de enfermedad premium at 500 euros per person or 1.500 for a person with discapacidad, so the lawful cap is a SUM over two populations rather than one amount times one count. A single
statutory_cap_person_countcannot express that, and using it alone applies the lower limb to everybody – which understates the allowance for exactly the people the higher limb exists for.
- exclusive_use_confirmed: bool¶
- iva_deduction_ratio: UnitProportion | None¶
Fraction of the fact’s input IVA the activity affords a right to deduct (LIVA arts. 94/104).
None(the default) means the axis has not been evaluated for this fact and preserves the historic base-only behaviour: when an invoice-evidencedtaxable_baseis known, only that net-of-IVA base becomes the IRPF-deductible cost. That is correct for the common case (an activity with full deduction rights recovers its input IVA through Modelo 303, so the IVA is not also an IRPF expense), but it is silently wrong whenever the activity’s right to deduct is less than total: PGC NRV 12.ª treats non-recoverable input IVA as part of the acquisition cost, so the portion the taxpayer cannot deduct for IVA purposes must join the IRPF gasto. Populating this field with the resolved IVA-deduction percentage (1for full deduction,0for a wholly exempt activity with no right to deduct, a fractional value under prorrata general or especial) makes1 - iva_deduction_ratioofiva_amountjoin the deductible base. Seeevaluate_renta_deductibility().Populated by
aggregate_renta_ledger_expenses_from_repositories()for the M100 annual first slice, viaapplication.aggregation.renta_ledger.resolve_iva_deduction_ratio: a whollyEXENTOiva.regimeprofile fact resolves to0outright; otherwise the bucket’sdomain.prorrata_register.ProrrataRegisterwhole-entity entry for the ejercicio contributes its in-force provisional percentage underGENERALorESPECIAL. The M130 quarterly pagos fraccionados gasto path (application.aggregation.renta_gasto_ledger) does not construct aRentaDeductibilityContextat all and is not wired to this axis – a named follow-up, not silently covered here.
- residence_ccaa: CCAA | None¶
Optional residence axis consumed by the governed territorial policy.
- class RentaDeductibleExpenseFact(**data)[source]¶
Bases:
_RentaStrictFrozenModelCanonical ledger fact eligible for Renta deductibility evaluation.
- Parameters:
data (Any)
- transaction_id: TransactionId¶
- invoice_id: str | None¶
- catalogue_id: str¶
- operation_date: date¶
- invoice_issue_date: date | None¶
- posting_date: date | None¶
- payment_date: date | None¶
- gross_amount: Decimal¶
- taxable_base: Decimal | None¶
- iva_amount: Decimal | None¶
- currency: Literal['EUR']¶
- direction: RentaExpenseDirection¶
- category: SpendingCategory¶
- activity_key: str¶
- class RentaDeductibilityResult(**data)[source]¶
Bases:
_RentaStrictFrozenModelDeductibility evaluation result before registry binding resolution.
- Parameters:
data (Any)
- transaction_id: TransactionId¶
- invoice_id: str | None¶
- category: SpendingCategory¶
- category_family: SpendingCategoryFamily¶
- profile_year: FilingYear¶
- proportionality_kind: ProportionalityKind¶
- status: RentaDeductibilityStatus¶
- reason: str¶
- gross_amount: Decimal¶
- deductible_amount: Decimal¶
- non_deductible_amount: Decimal¶
- applied_ratio: UnitProportion | None¶
- statutory_cap_applied: Decimal | None¶
- legal_references: tuple[CategoryCitation, ...]¶
- class RentaDeductibleExpenseObservation(**data)[source]¶
Bases:
_RentaStrictFrozenModelBinding-ready Renta expense observation for the first Modelo 100 slice.
- Parameters:
data (Any)
- observation_id: str¶
- source_kind: Literal[BindingSourceKind.LEDGER_RENTA_GASTOS_ESTIMACION_DIRECTA_AGGREGATION]¶
- modelo: Literal['100']¶
- period: Literal['0A']¶
- tax_year: FilingYear¶
- activity_key: str¶
- target_casilla_id: CasillaId¶
- transaction_id: TransactionId¶
- invoice_id: str | None¶
- catalogue_id: str¶
- operation_date: date¶
- invoice_issue_date: date | None¶
- posting_date: date | None¶
- payment_date: date | None¶
- filing_date: date¶
- gross_amount: Decimal¶
- taxable_base: Decimal | None¶
- iva_amount: Decimal | None¶
- deductible_amount: Decimal¶
- non_deductible_amount: Decimal¶
- currency: Literal['EUR']¶
- direction: RentaExpenseDirection¶
- sign: Literal[-1, 1]¶
- category: SpendingCategory¶
- category_family: SpendingCategoryFamily¶
- profile_year: FilingYear¶
- proportionality_kind: ProportionalityKind¶
- applied_ratio: UnitProportion | None¶
- invoice_evidence_status: RentaInvoiceEvidenceStatus¶
- reconciliation_status: RentaReconciliationStatus¶
- legal_references: tuple[CategoryCitation, ...]¶
- normalize_spending_category(value)[source]¶
Normalize persisted category identifiers to closed enum members.
- Return type:
- Returns:
The
SpendingCategorymember for the given value.- Parameters:
value (SpendingCategory | str)
- resolve_region_category_profiles(profile_year)[source]¶
Return the territorial-regime category-profile overrides for a filing year.
The override layer is provisioned but deliberately empty: the only genuinely region-varying expense-side regimes reach the base through their own dedicated bindings (the Reserva para Inversiones en Canarias, Ley 19/1994 art. 27, is modelled as its own binding, not a
SpendingCategoryprofile), and the Ceuta/Melilla benefit is an art. 68.4 cuota deduction rather than a base-imponible expense rule. NoSpendingCategorytherefore warrants a per-comunidad deductibility variant today, so this resolver returns an empty mapping and every fact falls through to the state year profile. A future territorial-regime enrollment populates this mapping (grounded to its regime law) with no further architectural change.- Return type:
- Returns:
Mapping from
CCAAto a per-SpendingCategoryoverride profile mapping; empty until a territorial regime is enrolled.- Parameters:
profile_year (int)
- select_deductibility_profile(*, state_profile, region_override_profiles, context)[source]¶
Select the applicable category profile, honouring territorial-regime overrides.
region_override_profilesholds the per-CCAAoverrides declared for the fact’s category (empty for the general state-law case). Selection:no override for the category: return
state_profileunchanged (general expense deductibility is state base-imponible law, invariant across comunidades);the category carries an override but
context.residence_ccaa is None: returnNone(D4 fail-closed) so the caller refuses rather than silently choosing a base for an undeclared region;the residence comunidad has an override: return that override profile;
the residence comunidad has no override (a different comunidad owns the regime): return
state_profile(state law applies to this taxpayer’s region).
- Return type:
- Returns:
The selected
CategoryProfile, orNonewhen a region override exists for the category but the residence comunidad is undeclared.- Parameters:
state_profile (CategoryProfile)
region_override_profiles (Mapping[CCAA, CategoryProfile])
context (RentaDeductibilityContext)
- evaluate_renta_deductibility(fact, profile, context)[source]¶
Evaluate one classified ledger fact and return a
RentaDeductibilityResult.deductible_basis(see_deductible_basis_amount()) folds in the non-deductible-for-IVA-purposes share of the fact’s input IVA whencontext.iva_deduction_ratiostates it, so an exempt or prorrata-rationed activity does not lose that real cost.- Return type:
- Parameters:
fact (RentaDeductibleExpenseFact)
profile (CategoryProfile)
context (RentaDeductibilityContext)
- build_renta_deductible_expense_observation(fact, result, *, tax_year)[source]¶
Build a first-slice Modelo 100 observation from an eligible result.
- Return type:
- Returns:
A
RentaDeductibleExpenseObservationready for filing assembly.- Parameters:
fact (RentaDeductibleExpenseFact)
result (RentaDeductibilityResult)
tax_year (int)