cadrumo.application.ledger.source_jurisdiction module

Where a ledger row’s income is sourced, and when the operator must say so.

Two taxpayer conditions make the source jurisdiction an answer nobody may default. A non-resident taxed under IRNR is taxed on Spanish-source income alone, so which jurisdiction a row belongs to decides whether it is in scope at all. An impatriado under the special regime is taxed on Spanish-source income by the same logic, with foreign-source income segregated out of the base. For both, a row whose jurisdiction was assumed rather than stated is a row assigned to a base by accident.

Everyone else has a declared residency, and for them Spain is the ordinary answer – but only once a residency HAS been declared. A profile that has declared none resolves to None, which is the unresolved state rather than a missing one: impatriado_income_ledger segregates such a row with a typed unresolved-jurisdiction issue, on the stated invariant that an unresolved jurisdiction “is NEVER silently coerced to ES”.

That invariant is why this is a decision and not a default. Coercing to ES for an undeclared profile performed exactly the coercion the aggregation exists to refuse, so the None never reached the layer built to handle it and foreign-source income folded into the Spanish base. The stamp is persisted on the transaction, so the mistake outlived the profile later being completed.

The rule lives here rather than at a command boundary because it is tax law about a taxpayer, not a fact about argv: it answers the same way whichever surface asks, and its consumer – the aggregation above – is in this layer too. What a surface still owns is how it ASKS and how it words a refusal, which is why this returns a typed outcome rather than raising a presentation error.

See also

impatriado_income_ledger

The aggregation that segregates an unresolved jurisdiction rather than admitting it to the Spanish base.

SPANISH_SOURCE_JURISDICTION

The jurisdiction a declared resident’s row is sourced in absent any other statement. Named rather than spelled inline so the default and the refusals that withhold it cannot drift apart.

class SourceJurisdictionOutcome(*values)[source]

Bases: StrEnum

What the taxpayer’s conditions say about this row’s source jurisdiction.

Five members and not three, because the two refusals are different questions to put to an operator and the two answers have different provenance. Collapsing either pair would make a surface guess which it was holding.

STATED

The operator stated it; no profile signal is consulted.

DEFAULTED

A declared residency with no special regime; Spain by default.

UNRESOLVED

No residency declared. NOT a missing answer, and never coerced to Spain.

REQUIRED_NON_RESIDENT_IRNR

the operator must state it.

Type:

Non-resident under IRNR

REQUIRED_IMPATRIADO

the operator must state it.

Type:

Impatriado special regime

OUTCOMES_REQUIRING_AN_OPERATOR_STATEMENT

The outcomes that oblige the operator to state a jurisdiction.

Derived by subtraction rather than listed, so it cannot fall behind the enum: everything that neither produces a jurisdiction nor is the unresolved state is, by construction, a question somebody has to answer. A surface wording these refusals reads this rather than rebuilding it, which is also why it exists – learning the set by constructing a resolution per member is not possible, since the ones that carry a value refuse to be built without one.

class SourceJurisdictionResolution(**data)[source]

Bases: BaseModel

One resolution: what the conditions decided, and the value if there is one.

Parameters:

data (Any)

outcome: SourceJurisdictionOutcome
jurisdiction: str | None
property requires_operator_statement: bool

Whether the taxpayer’s conditions oblige the operator to state it.

resolve_source_jurisdiction(operator_value, *, fiscal_residency, irpf_special_regime)[source]

Decide this row’s source jurisdiction from the taxpayer’s conditions.

The order is the rule, not a style. An operator statement wins over every profile signal, because it is the more specific claim and the profile is only ever a default. The two refusals come next and the impatriado refusal is reachable for a taxpayer whose residency is undeclared, so it must be asked before the undeclared path – otherwise an impatriado with an incomplete profile would resolve to UNRESOLVED and never be asked the question the regime requires.

Parameters:
  • operator_value (str | None) – The jurisdiction the operator stated, or None.

  • fiscal_residency (FiscalResidency | None) – The profile’s declared residency, or None when the profile has declared none.

  • irpf_special_regime (IrpfSpecialRegime | None) – The profile’s declared IRPF special regime, if any.

Return type:

SourceJurisdictionResolution

Returns:

The resolution, carrying a jurisdiction only where the conditions produce one.