Functional Weave
Code in Python

charity.gasds@1.0.2

impl/python.py

3,627 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import re
from typing import Sequence

from .charity_gasds_data import GASDS_LIMITS, GASDS_LIMITS_HISTORY, GasdsLimit  ← this capability’s own data, compiled from data/gasds-limits.json into the same file by fune build
from .charity_gasds_types import GasdsClaim
from .charity_gift_aid import gift_aid  ← from charity.gift-aid ^1.0.0 · built alongside by fune
from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune

ISO_DATE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}")


def _limits_on(on_date: str) -> GasdsLimit:
    for row in GASDS_LIMITS:
        if row.valid_from <= on_date and (row.valid_to is None or on_date <= row.valid_to):
            return row
    if GASDS_LIMITS_HISTORY != "full" and len(GASDS_LIMITS) > 0:
        earliest = min(row.valid_from for row in GASDS_LIMITS)
        if on_date < earliest:
            raise ValueError(
                "no GASDS limits on %s: this build was installed with history=%s, so it only carries rules from %s. "
                "Reinstall with history=full for earlier tax years." % (on_date, GASDS_LIMITS_HISTORY, earliest)
            )
    raise ValueError("no GASDS limits on %s" % (on_date,))


def _check_amount(name: str, amount: Money) -> None:
    if amount.currency != "GBP":
        raise ValueError("GASDS amounts must be in GBP, received %s for %s" % (amount.currency, name))
    if amount.minor < 0:
        raise ValueError("%s must not be negative, received %d" % (name, amount.minor))


def gasds(
    small_donations: Money,
    gift_aid_donations: Money,
    community_building_donations: Sequence[Money],
    on_date: str,
) -> GasdsClaim:
    """The GASDS top-up for one tax year (Small Charitable Donations Act 2012
    s1): paid on small donations up to the lower of 10 times the Gift Aid
    donations claimed in the same year (the matching rule) and the specified
    amount, which for a charity active in more than one community building may
    be the sum of each building's local donations, up to £8,000 each (s6).
    """
    _check_amount("smallDonations", small_donations)
    _check_amount("giftAidDonations", gift_aid_donations)
    for d in community_building_donations:
        _check_amount("communityBuildingDonations", d)
    if not isinstance(on_date, str) or not ISO_DATE.fullmatch(on_date):
        raise ValueError('onDate must be an ISO date (YYYY-MM-DD), received "%s"' % (on_date,))
    rule = _limits_on(on_date)
    if len(community_building_donations) > 0 and not rule.community_buildings:
        raise ValueError("community building claims before 6 April 2017 are not supported, received a claim for %s" % (on_date,))
    in_buildings = sum(d.minor for d in community_building_donations)
    if in_buildings > small_donations.minor:
        raise ValueError(
            "communityBuildingDonations total %d, more than smallDonations of %d" % (in_buildings, small_donations.minor)
        )

    specified = rule.specified_amount
    used_community_buildings = False
    # s6 applies only to a charity active in more than one community building.
    if len(community_building_donations) >= 2:
        buildings = sum(min(d.minor, rule.specified_amount) for d in community_building_donations)
        if buildings > specified:
            specified = buildings
            used_community_buildings = True
    maximum = min(10 * gift_aid_donations.minor, specified)
    eligible = min(small_donations.minor, maximum)
    return GasdsClaim(
        eligible_donations=money(eligible, "GBP"),
        top_up=gift_aid(money(eligible, "GBP"), on_date).tax_reclaimable,
        maximum_donations=money(maximum, "GBP"),
        specified_amount=money(specified, "GBP"),
        small_donation_limit=money(rule.small_donation_limit, "GBP"),
        used_community_buildings=used_community_buildings,
    )