Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from typing import List, Optional
from .charity_gift_aid import gift_aid ← from charity.gift-aid ^1.0.0 · built alongside by fune
from .charity_higher_rate_relief_types import HigherRateRelief, TaxRegion
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
from .payroll_income_tax_data import INCOME_TAX_BANDS, INCOME_TAX_BANDS_HISTORY, IncomeTaxBand ← payroll.income-tax’s rule data (^1.0.0) · built alongside by fune
REGIONS = ("rest-of-uk", "scotland", "wales")
def _bands_for(region: str, on_date: str) -> List[IncomeTaxBand]:
rows = [
b for b in INCOME_TAX_BANDS
if b.region == region and b.valid_from <= on_date and (b.valid_to is None or on_date <= b.valid_to)
]
if not rows:
# A history=current build only carries this year's bands; an older year
# would otherwise be answered with the wrong bands.
froms = [b.valid_from for b in INCOME_TAX_BANDS if b.region == region]
if INCOME_TAX_BANDS_HISTORY != "full" and froms:
earliest = min(froms)
if on_date < earliest:
raise ValueError(
"no income tax bands for %s on %s: this build was installed with history=%s, so it only carries rules from %s. "
"Reinstall with history=full for earlier tax years." % (region, on_date, INCOME_TAX_BANDS_HISTORY, earliest)
)
raise ValueError("no income tax bands for %s on %s" % (region, on_date))
return sorted(rows, key=lambda b: b.band)
def _basic_band_index(bands: List[IncomeTaxBand], region: str, on_date: str) -> int:
for i, b in enumerate(bands):
if b.basic_rate:
return i
raise ValueError("no basic rate band for %s on %s" % (region, on_date))
def _scaled_tax(income: int, bands: List[IncomeTaxBand], extend_from: int, extension: int) -> int:
"""Tax on income in pence, times 10000 (pence x basis points), unrounded."""
total = 0
lower = 0
for i, band in enumerate(bands):
if income <= lower:
break
upper: Optional[int] = None if band.up_to is None else band.up_to * 100 + (extension if i >= extend_from else 0)
top = income if upper is None or income < upper else upper
if top > lower:
total += (top - lower) * band.basis_points
if upper is None:
break
lower = upper
return total
def higher_rate_relief(taxable_income: Money, donations: Money, region: TaxRegion, on_date: str) -> HigherRateRelief:
"""The extra relief a Gift Aid donor gets through their own tax: ITA 2007
s414(2)(b) raises the basic rate limit and the higher rate limit (for a
Scottish taxpayer, the Scottish basic rate limit and every limit above it,
but not the starter rate limit) by the grossed up donations.
"""
if taxable_income.currency != "GBP":
raise ValueError("taxableIncome must be in GBP, received %s" % (taxable_income.currency,))
if taxable_income.minor < 0:
raise ValueError("taxableIncome must not be negative, received %d" % (taxable_income.minor,))
if region not in REGIONS:
raise ValueError('unknown tax region "%s": expected rest-of-uk, scotland or wales' % (region,))
grossed = gift_aid(donations, on_date)
bands = _bands_for(region, on_date)
basic = _basic_band_index(bands, region, on_date)
uk_bands = _bands_for("rest-of-uk", on_date)
uk_basic = uk_bands[_basic_band_index(uk_bands, "rest-of-uk", on_date)]
if bands[basic].basis_points != uk_basic.basis_points:
# s414A then adjusts the donor's tax by the rate difference; not modelled.
raise ValueError(
"the %s basic rate differs from the UK basic rate on %s; the section 414A adjustment is not supported" % (region, on_date)
)
before = _scaled_tax(taxable_income.minor, bands, basic, 0)
after = _scaled_tax(taxable_income.minor, bands, basic, grossed.gross.minor)
tax_before = before // 10000
extra = (before - after) // 10000
return HigherRateRelief(
gross_donations=grossed.gross,
basic_rate_relief=grossed.tax_reclaimable,
tax_before=money(tax_before, "GBP"),
tax_after=money(tax_before - extra, "GBP"),
extra_relief=money(extra, "GBP"),
net_cost=money(donations.minor - extra, "GBP"),
)