import re from typing import Sequence from .charity_gasds_data import GASDS_LIMITS, GASDS_LIMITS_HISTORY, GasdsLimit from .charity_gasds_types import GasdsClaim from .charity_gift_aid import gift_aid from .money_amount import Money, money 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, )