Source code for aeat.application.aggregation._retencion_rate_advisory
"""Statutory-rate advisory for administrador/consejero retención observations.Modelo 111 aggregates operator-supplied per-perceptor retención rows: each:class:`~._retenciones.RetencionObservation` carries both the ``taxable_base``and the withheld ``retencion_amount``. For ordinary empleados(:attr:`~core.aggregation.RetencionScheme.WORK_INCOME`) the withholding is apersonalised progressive computation (LIRPF art. 101.1), so no single rate can beasserted. For administradores y miembros de consejos de administración(:attr:`~core.aggregation.RetencionScheme.WORK_INCOME_DIRECTOR`) the lawfixes the rate: LIRPF art. 101.2 (Ley 35/2006, BOE-A-2006-20764), developed byRIRPF art. 80.1.3.º (RD 439/2007), sets a general 35 % that drops to 19 % when thepaying entity's importe neto de la cifra de negocios is below 100.000 euros.The engine does not compute the withheld amount (the operator enters it from theirpayroll), so the fixed rate could previously go unverified: an administrador rowcarrying, say, the ordinary empleado rate would fold into the trabajo block andfile silently. This module surfaces that as a non-blocking:class:`~._source_mesh.CalculationSourceDiagnostic` on the calculate path,grounded in the statutory :class:`~core.aggregation.WorkIncomeRetencionTreatment`descriptor (``no-silent-under-declaration``). Because the engine cannot alwaysknow the paying entity's INCN, a row whose effective rate matches EITHER statutoryfigure (35 % or 19 %) is treated as conforming; only a row consistent with neitherraises the advisory, so a legitimate reduced-rate filing never false-fires."""from__future__importannotationsfromcollections.abcimportIterablefromdecimalimportDecimalfrom...core.aggregationimportRetencionScheme,work_income_retencion_treatmentfrom._retencionesimportRetencionObservationfrom._source_meshimportCalculationSourceDiagnostic#: Diagnostic ``source_kind`` for an administrador/consejero retención whose#: withheld amount matches neither statutory art. 101.2 fixed rate.ADMINISTRADOR_RETENCION_RATE_SOURCE_KIND="administrador_retencion_rate"#: Cent tolerance for the amount comparison: the statutory withholding is a single#: ``base * rate`` product rounded once to cents (money-2), so the maximum honest#: rounding gap between the operator amount and the recomputed expected amount is#: half a cent. A one-cent tolerance accepts that gap while still catching a#: genuinely divergent rate._RATE_MATCH_TOLERANCE_EUR=Decimal("0.01")def_conforms_to_fixed_rate(base:Decimal,amount:Decimal,rate:Decimal)->bool:"""Return whether ``amount`` is ``base * rate`` within the cent tolerance."""expected=base*ratereturnabs(amount-expected)<=_RATE_MATCH_TOLERANCE_EUR
[docs]defadministrador_retencion_rate_advisory_observations(observations:Iterable[RetencionObservation],)->tuple[CalculationSourceDiagnostic,...]:"""Return advisories for administrador rows inconsistent with art. 101.2. A :class:`~._source_mesh.CalculationSourceDiagnostic` (reason ``administrador_retencion_rate_mismatch``) is emitted for each :attr:`~core.aggregation.RetencionScheme.WORK_INCOME_DIRECTOR` observation with a strictly-positive ``taxable_base`` whose withheld ``retencion_amount`` matches neither the general 35 % nor the reduced 19 % fixed rate of LIRPF art. 101.2. Rows on any other scheme (empleados follow the progressive art. 101.1 procedure, so no single rate applies; actividades, premios, capital, and arrendamiento are not art. 101 trabajo), and administrador rows with a non-positive base, are out of scope and never fire. Args: observations: The per-perceptor retención rows feeding the calculation. Returns: A tuple of non-blocking rate-mismatch diagnostics, in input order. """treatment=work_income_retencion_treatment(RetencionScheme.WORK_INCOME_DIRECTOR)iftreatmentisNoneortreatment.fixed_rateisNoneortreatment.fixed_reduced_rateisNone:return()general_rate=treatment.fixed_ratereduced_rate=treatment.fixed_reduced_ratediagnostics:list[CalculationSourceDiagnostic]=[]forobservationinobservations:ifobservation.schemeisnotRetencionScheme.WORK_INCOME_DIRECTOR:continuebase=observation.taxable_baseifbase<=Decimal("0"):continueamount=observation.retencion_amountif_conforms_to_fixed_rate(base,amount,general_rate)or_conforms_to_fixed_rate(base,amount,reduced_rate,):continuediagnostics.append(CalculationSourceDiagnostic(reason="administrador_retencion_rate_mismatch",source_kind=ADMINISTRADOR_RETENCION_RATE_SOURCE_KIND,message=(f"Administrador/consejero retención for perceptor {observation.perceptor_nif!r} "f"(base {base}, withheld {amount}) matches neither the LIRPF art. 101.2 fixed "f"rate of {general_rate} nor the reduced {reduced_rate} for entities with net "f"turnover below 100.000 EUR; confirm the applied withholding rate before filing."),),)returntuple(diagnostics)