"""Registry lookup for filing window close dates (plazo voluntario).
Provides a profile-free function to ask "when does the plazo voluntario
close for this modelo + filing year + period?" directly from the registry
deadline windows. The result feeds the pre-calculation extemporaneidad
surface in :mod:`cadrumo.application.modelo.work_plazo`. Post-calculation
consumers that know resultado or Modelo 210 tipo-renta context call the sibling
``resolve_filing_window`` entry point; both paths therefore share the same
canonical matcher.
Core types:
:class:`~cadrumo.domain.calculations.registry.schema.ModeloRevision`.
"""
from __future__ import annotations
from datetime import date
from typing import TYPE_CHECKING
from ...core.modelo import Modelo
from ...core.period import Period, PeriodKind
from ...core.result_disposition import ResultDisposition
from ..calculations.registry.errors import RegistrySnapshotError
from ..calculations.registry.irnr_tipo_renta import m210_tipo_renta_code_projection
from .errors import DeadlineValidationError
if TYPE_CHECKING:
from ..calculations.registry.authority import PinnedAuthorityOperation
from ..calculations.registry.deadline_coordinate import DeadlineSemanticCoordinate
from ..calculations.registry.schema import ModeloRevision
from ..calculations.registry.schema_deadlines import DeadlineWindowDefinition
type DeadlineWindowProjection = tuple[str, ModeloRevision, DeadlineWindowDefinition]
[docs]
def resolve_filing_closes_on(
modelo: str,
filing_year: int,
period: Period,
*,
authority: PinnedAuthorityOperation | None = None,
) -> date | None:
"""Return the close date of the plazo voluntario for a modelo+year+period.
Queries the validated registry authority for ``filing_year`` through
:func:`resolve_filing_window` and returns the ``closes_on`` of its unique
unqualified match. This function deliberately accepts no resultado or
Modelo 210 tipo-renta context: it is the pre-calculation convenience, while
post-calculation callers use ``resolve_filing_window`` directly rather than
owning another matcher.
The resolved value belongs to the matching
:class:`~cadrumo.domain.calculations.registry.schema_deadlines.DeadlineWindowDefinition`.
Matching rule: the registry window period must carry the same
filing year and bare registry period token as the supplied ``WorkUnit``
period (e.g. ``"1T"``, ``"0A"``, ``"01"``).
Annual Modelo 100 uses its tax year as the registry key even though
its normal campaign runs in the following calendar year. A close date
for tax year 2024 can therefore fall in 2025. This function never
borrows a following-year or future-year window when an exact match is
absent.
Returns ``None`` when no registry window exactly matches the
combination. The modelo might have no deadline window for that year,
or its period token might not match any window. ``None`` means the
deadline data is unavailable; callers continue without a close date.
A modelo absent from the registry is NOT one of those causes and REFUSES
with :exc:`RegistrySnapshotError` rather than returning ``None``. Production
reaches this through the ``Modelo`` enum, so an unregistered id is a caller
bug or unvalidated external input; returning ``None`` would hand that caller
"no deadline", and an extemporaneidad computed from it would silently drop
the recargo.
Args:
modelo: Agencia Estatal de Administración Tributaria (AEAT) modelo code
(e.g. ``"130"``, ``"303"``).
filing_year: Tax year for which the work unit was created.
period: ``WorkUnit`` bare registry period token (e.g. ``"1T"``,
``"0A"``, ``"01"``).
authority: Optional generation-pinned authority operation.
Returns:
The :class:`~datetime.date` on which the filing window closes,
or ``None`` if not found.
"""
window = resolve_filing_window(modelo, filing_year, period, authority=authority)
return None if window is None else window.closes_on
[docs]
def resolve_filing_window(
modelo: str,
filing_year: int,
period: Period,
*,
resultado: ResultDisposition | None = None,
tipo_renta_code: str | None = None,
authority: PinnedAuthorityOperation | None = None,
) -> DeadlineWindowDefinition | None:
"""Return the registry deadline window for a modelo+year+period, or ``None``.
This is the single matching authority for "which registry deadline window
covers this filing target". Every consumer — the extemporaneidad surface
that needs only
:attr:`~cadrumo.domain.calculations.registry.schema_deadlines.DeadlineWindowDefinition.closes_on`,
and the overview calendar that also
needs ``opens_on`` and ``payment_cutoff_on`` — resolves through here, so a
change to the year/token matching rule or to the no-window behaviour can
never move one surface without the other.
Matching uses the registry's canonical atomic deadline coordinate. The
filing year and bare period token must be exact; a window's absent qualifier
scope is a wildcard, while an authored resultado or tipo-renta scope must
contain the requested canonical value. No following-year or future-year
window is ever borrowed when an exact match is absent.
``None`` means one thing only: the registry loaded, and declares no window
matching this combination. A registry that cannot be read or validated is
NOT an absence of deadline and is not reported as one -- the
:class:`~cadrumo.domain.calculations.registry.errors.RegistryError` propagates.
Reading ``deadline_windows`` costs full modelo validation, because the
authority validates the modelo before projecting its windows. Catching that
failure here and returning ``None`` turned any registry-wide validation
fault into the claim that AEAT declares no filing deadline for this modelo
and period -- a failure laundered into a confident answer about when a
taxpayer must file. The sibling deadline engine wraps the same call and
raises with the failing stage named; this now agrees with it.
Args:
modelo: Agencia Estatal de Administración Tributaria (AEAT) modelo code
(e.g. ``"130"``, ``"303"``).
filing_year: Tax year for which the filing target was created.
period: The typed filing period to match.
resultado: Optional canonical post-calculation result disposition.
tipo_renta_code: Optional official two-digit Modelo 210 tipo-renta code.
authority: Optional generation-pinned authority operation.
Returns:
The matching
:class:`~cadrumo.domain.calculations.registry.schema_deadlines.DeadlineWindowDefinition`,
or ``None`` when the registry declares no window for the combination.
Raises:
RegistryError: The registry could not be read or validated. The caller
is told the deadline is unknown rather than absent.
DeadlineValidationError: More than one window matches the atomic request
coordinate. Ambiguous registry authority is never treated as absence.
"""
selected_authority = authority
if selected_authority is None:
from ..calculations.registry.authority import bundled_indexed_authority
with bundled_indexed_authority().operation() as operation:
return _resolve_filing_window_with_operation(
operation,
modelo=modelo,
filing_year=filing_year,
period=period,
resultado=resultado,
tipo_renta_code=tipo_renta_code,
)
return _resolve_filing_window_with_operation(
selected_authority,
modelo=modelo,
filing_year=filing_year,
period=period,
resultado=resultado,
tipo_renta_code=tipo_renta_code,
)
def _resolve_filing_window_with_operation(
operation: PinnedAuthorityOperation,
*,
modelo: str,
filing_year: int,
period: Period,
resultado: ResultDisposition | None,
tipo_renta_code: str | None,
) -> DeadlineWindowDefinition | None:
"""Resolve one filing window from a single generation-pinned operation."""
if modelo not in operation.modelo_ids():
raise RegistrySnapshotError.for_modelo_not_registered(modelo_id=modelo)
# The closing coordinate of the filing year, which is how a filing year is
# projected onto the date axis the governed catalogue is selected by. It is
# the same coordinate for every modelo, so it is derived before the branch
# rather than inside a Modelo 210 condition, where it would read as a value
# this modelo fixes in code.
filing_year_close = date(filing_year, 12, 31)
if tipo_renta_code is not None and (
modelo != Modelo("210")
or tipo_renta_code
not in m210_tipo_renta_code_projection(
effective_date=filing_year_close,
authority=operation,
)
):
raise DeadlineValidationError(
f"filing window tipo_renta_code {tipo_renta_code!r} is not a canonical official Modelo 210 code",
)
from .engine import indexed_deadline_windows
windows = tuple(item for item in indexed_deadline_windows(operation, filing_year) if item[0] == modelo)
return _resolve_projected_filing_window(
windows,
modelo=modelo,
filing_year=filing_year,
period=period,
resultado=resultado,
tipo_renta_code=tipo_renta_code,
)
def _resolve_projected_filing_window(
windows: tuple[DeadlineWindowProjection, ...],
*,
modelo: str,
filing_year: int,
period: Period,
resultado: ResultDisposition | None,
tipo_renta_code: str | None,
) -> DeadlineWindowDefinition | None:
"""Resolve one already-validated authority projection by semantic coordinate."""
# Registry applicability imports this defining module, so defer the
# registry coordinate import until resolution time to keep that dependency
# cycle out of module initialisation.
from ..calculations.registry.deadline_coordinate import deadline_semantic_coordinate
requested = deadline_semantic_coordinate(modelo, period, resultado, tipo_renta_code)
if requested.filing_year != filing_year:
return None
qualified_m210_event = _is_qualified_m210_event(
modelo=modelo,
period=period,
resultado=resultado,
tipo_renta_code=tipo_renta_code,
)
matches = _matching_projected_windows(
windows,
modelo=modelo,
requested=requested,
filing_year=filing_year,
resultado=resultado,
tipo_renta_code=tipo_renta_code,
qualified_m210_event=qualified_m210_event,
)
return _select_unique_projected_window(matches, requested=requested)
def _is_qualified_m210_event(
*,
modelo: str,
period: Period,
resultado: ResultDisposition | None,
tipo_renta_code: str | None,
) -> bool:
from ..calculations.registry.period_selector_match import selector_period_matches_request
return (
modelo == Modelo("210")
and selector_period_matches_request("EVENT-N", period.registry_token)
and (resultado is not None or tipo_renta_code is not None)
)
def _matching_projected_windows(
windows: tuple[DeadlineWindowProjection, ...],
*,
modelo: str,
requested: DeadlineSemanticCoordinate,
filing_year: int,
resultado: ResultDisposition | None,
tipo_renta_code: str | None,
qualified_m210_event: bool,
) -> tuple[DeadlineWindowDefinition, ...]:
if qualified_m210_event:
return _matching_qualified_m210_event_windows(
windows,
modelo=modelo,
filing_year=filing_year,
resultado=resultado,
tipo_renta_code=tipo_renta_code,
)
return _matching_general_projected_windows(windows, modelo=modelo, requested=requested)
def _matching_qualified_m210_event_windows(
windows: tuple[DeadlineWindowProjection, ...],
*,
modelo: str,
filing_year: int,
resultado: ResultDisposition | None,
tipo_renta_code: str | None,
) -> tuple[DeadlineWindowDefinition, ...]:
return tuple(
window
for projected_modelo, _revision, window in windows
if _matches_qualified_m210_event_window(
projected_modelo,
window,
modelo=modelo,
filing_year=filing_year,
resultado=resultado,
tipo_renta_code=tipo_renta_code,
)
)
def _matches_qualified_m210_event_window(
projected_modelo: str,
window: DeadlineWindowDefinition,
*,
modelo: str,
filing_year: int,
resultado: ResultDisposition | None,
tipo_renta_code: str | None,
) -> bool:
if projected_modelo != modelo:
return False
if window.period.kind is not PeriodKind.ANNUAL:
return False
return any(
(coordinate.filing_year, coordinate.resultado_scope, coordinate.tipo_renta_code)
== (filing_year, resultado, tipo_renta_code)
for coordinate in _window_semantic_coordinates(projected_modelo, window)
)
def _matching_general_projected_windows(
windows: tuple[DeadlineWindowProjection, ...],
*,
modelo: str,
requested: DeadlineSemanticCoordinate,
) -> tuple[DeadlineWindowDefinition, ...]:
return tuple(
window
for projected_modelo, _revision, window in windows
if _matches_general_projected_window(projected_modelo, window, modelo=modelo, requested=requested)
)
def _matches_general_projected_window(
projected_modelo: str,
window: DeadlineWindowDefinition,
*,
modelo: str,
requested: DeadlineSemanticCoordinate,
) -> bool:
if projected_modelo != modelo:
return False
return requested in _window_semantic_coordinates(projected_modelo, window)
def _window_semantic_coordinates(
modelo: str,
window: DeadlineWindowDefinition,
) -> tuple[DeadlineSemanticCoordinate, ...]:
from ..calculations.registry.deadline_coordinate import deadline_window_semantic_coordinates
return deadline_window_semantic_coordinates(modelo, window)
def _select_unique_projected_window(
matches: tuple[DeadlineWindowDefinition, ...],
*,
requested: DeadlineSemanticCoordinate,
) -> DeadlineWindowDefinition | None:
if not matches:
return None
if len(matches) > 1:
raise DeadlineValidationError(
f"filing window resolution is ambiguous for {requested!r}: "
f"matched window ids {[window.id for window in matches]!r}",
)
return next(iter(matches))
__all__ = ["resolve_filing_closes_on", "resolve_filing_window"]