Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { giftAid } from "./charity_gift_aid.ts"; ← from charity.gift-aid ^1.0.0 · built alongside by fune
import { type Money, money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { type GasdsClaim } from "./charity_gasds_types.ts";
import { type GasdsLimit, GASDS_LIMITS, GASDS_LIMITS_HISTORY } from "./charity_gasds_data.ts"; ← this capability’s own data, compiled from data/gasds-limits.json into the same file by fune build
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
function limitsOn(onDate: string): GasdsLimit {
const row = GASDS_LIMITS.find((r) => r.validFrom <= onDate && (r.validTo === null || onDate <= r.validTo));
if (row !== undefined) return row;
if (GASDS_LIMITS_HISTORY !== "full" && GASDS_LIMITS.length > 0) {
const earliest = GASDS_LIMITS.map((r) => r.validFrom).reduce((a, b) => (b < a ? b : a));
if (onDate < earliest) {
throw new RangeError(
`no GASDS limits on ${onDate}: this build was installed with history=${GASDS_LIMITS_HISTORY}, so it only carries rules from ${earliest}. Reinstall with history=full for earlier tax years.`
);
}
}
throw new RangeError(`no GASDS limits on ${onDate}`);
}
function checkAmount(name: string, amount: Money): void {
if (amount.currency !== "GBP") throw new RangeError(`GASDS amounts must be in GBP, received ${amount.currency} for ${name}`);
if (amount.minor < 0) throw new RangeError(`${name} must not be negative, received ${amount.minor}`);
}
/**
* 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).
*/
export function gasds(
smallDonations: Money,
giftAidDonations: Money,
communityBuildingDonations: readonly Money[],
onDate: string
): GasdsClaim {
checkAmount("smallDonations", smallDonations);
checkAmount("giftAidDonations", giftAidDonations);
communityBuildingDonations.forEach((d) => checkAmount("communityBuildingDonations", d));
if (!ISO_DATE.test(onDate)) throw new RangeError(`onDate must be an ISO date (YYYY-MM-DD), received "${onDate}"`);
const rule = limitsOn(onDate);
if (communityBuildingDonations.length > 0 && !rule.communityBuildings) {
throw new RangeError(`community building claims before 6 April 2017 are not supported, received a claim for ${onDate}`);
}
const inBuildings = communityBuildingDonations.reduce((sum, d) => sum + d.minor, 0);
if (inBuildings > smallDonations.minor) {
throw new RangeError(
`communityBuildingDonations total ${inBuildings}, more than smallDonations of ${smallDonations.minor}`
);
}
let specified = rule.specifiedAmount;
let usedCommunityBuildings = false;
// s6 applies only to a charity active in more than one community building.
if (communityBuildingDonations.length >= 2) {
const buildings = communityBuildingDonations.reduce((sum, d) => sum + Math.min(d.minor, rule.specifiedAmount), 0);
if (buildings > specified) {
specified = buildings;
usedCommunityBuildings = true;
}
}
const maximum = Math.min(10 * giftAidDonations.minor, specified);
const eligible = Math.min(smallDonations.minor, maximum);
return {
eligibleDonations: money(eligible, "GBP"),
topUp: giftAid(money(eligible, "GBP"), onDate).taxReclaimable,
maximumDonations: money(maximum, "GBP"),
specifiedAmount: money(specified, "GBP"),
smallDonationLimit: money(rule.smallDonationLimit, "GBP"),
usedCommunityBuildings,
};
}