Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::charity_gasds_data::{GasdsLimit, GASDS_LIMITS, GASDS_LIMITS_HISTORY}; ← this capability’s own data, compiled from data/gasds-limits.json into the same file by fune build
use super::charity_gift_aid::gift_aid; ← from charity.gift-aid ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
fn is_iso_date(s: &str) -> bool {
let b = s.as_bytes();
b.len() == 10
&& b.iter().enumerate().all(|(i, c)| if i == 4 || i == 7 { *c == b'-' } else { c.is_ascii_digit() })
}
fn limits_on(on_date: &str) -> &'static GasdsLimit {
for row in GASDS_LIMITS {
if row.valid_from <= on_date && row.valid_to.map_or(true, |to| on_date <= to) {
return row;
}
}
if GASDS_LIMITS_HISTORY != "full" {
if let Some(earliest) = GASDS_LIMITS.iter().map(|r| r.valid_from).min() {
if on_date < earliest {
panic!(
"no GASDS limits on {}: this build was installed with history={}, so it only carries rules from {}. Reinstall with history=full for earlier tax years.",
on_date, GASDS_LIMITS_HISTORY, earliest
);
}
}
}
panic!("no GASDS limits on {}", on_date);
}
fn check_amount(name: &str, amount: &Money) {
if amount.currency != "GBP" {
panic!("GASDS amounts must be in GBP, received {} for {}", amount.currency, name);
}
if amount.minor < 0 {
panic!("{} must not be negative, received {}", name, 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).
///
/// # Panics
/// Panics on a non-GBP or negative amount, a malformed date, a date with no
/// limits on file, community buildings before 2017-18, or community building
/// donations that add up to more than the small donations.
pub fn gasds(
small_donations: &Money,
gift_aid_donations: &Money,
community_building_donations: &[Money],
on_date: &str,
) -> GasdsClaim {
check_amount("smallDonations", small_donations);
check_amount("giftAidDonations", gift_aid_donations);
for d in community_building_donations {
check_amount("communityBuildingDonations", d);
}
if !is_iso_date(on_date) {
panic!("onDate must be an ISO date (YYYY-MM-DD), received \"{}\"", on_date);
}
let rule = limits_on(on_date);
if !community_building_donations.is_empty() && !rule.community_buildings {
panic!("community building claims before 6 April 2017 are not supported, received a claim for {}", on_date);
}
let in_buildings: i64 = community_building_donations.iter().map(|d| d.minor).sum();
if in_buildings > small_donations.minor {
panic!(
"communityBuildingDonations total {}, more than smallDonations of {}",
in_buildings, small_donations.minor
);
}
let mut specified = rule.specified_amount;
let mut used_community_buildings = false;
// s6 applies only to a charity active in more than one community building.
if community_building_donations.len() >= 2 {
let buildings: i64 = community_building_donations
.iter()
.map(|d| d.minor.min(rule.specified_amount))
.sum();
if buildings > specified {
specified = buildings;
used_community_buildings = true;
}
}
let maximum = (10 * gift_aid_donations.minor).min(specified);
let eligible = small_donations.minor.min(maximum);
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,
}
}
pub fn gasds_claim_to_value(c: &GasdsClaim) -> Value {
Value::obj(vec![
("eligibleDonations", money_to_value(&c.eligible_donations)),
("topUp", money_to_value(&c.top_up)),
("maximumDonations", money_to_value(&c.maximum_donations)),
("specifiedAmount", money_to_value(&c.specified_amount)),
("smallDonationLimit", money_to_value(&c.small_donation_limit)),
("usedCommunityBuildings", Value::Bool(c.used_community_buildings)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let buildings: Vec<Money> = args[2].as_arr().iter().map(money_from_value).collect();
gasds_claim_to_value(&gasds(
&money_from_value(&args[0]),
&money_from_value(&args[1]),
&buildings,
args[3].as_str(),
))
}