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_gift_aid_eligibility_data::{BenefitLimit, BENEFIT_LIMITS, BENEFIT_LIMITS_HISTORY}; ← this capability’s own data, compiled from data/benefit-limits.json into the same file by fune build
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 BenefitLimit {
for row in BENEFIT_LIMITS {
if row.valid_from <= on_date && row.valid_to.map_or(true, |to| on_date <= to) {
return row;
}
}
if BENEFIT_LIMITS_HISTORY != "full" {
if let Some(earliest) = BENEFIT_LIMITS.iter().map(|r| r.valid_from).min() {
if on_date < earliest {
panic!(
"no Gift Aid benefit limits on {}: this build was installed with history={}, so it only carries rules from {}. Reinstall with history=full for earlier tax years.",
on_date, BENEFIT_LIMITS_HISTORY, earliest
);
}
}
}
panic!("no Gift Aid benefit limits on {}", on_date);
}
fn check_gbp(name: &str, amount: &Money) {
if amount.currency != "GBP" {
panic!("Gift Aid amounts must be in GBP, received {} for {}", amount.currency, name);
}
}
/// Whether a donation qualifies for Gift Aid (ITA 2007 ss416-418). The benefit
/// test is done on the exact limit, scaled by 10000, so 25% or 5% of an odd
/// number of pence is never rounded before the comparison.
///
/// # Panics
/// Panics on a non-GBP amount, a donation that is not positive, a negative
/// benefit, a malformed date, or a date with no limits on file.
pub fn gift_aid_eligibility(
donation: &Money,
benefit_value: &Money,
prior_benefits_this_year: &Money,
has_declaration: bool,
donor_pays_enough_tax: bool,
on_date: &str,
) -> GiftAidEligibility {
check_gbp("donation", donation);
check_gbp("benefitValue", benefit_value);
check_gbp("priorBenefitsThisYear", prior_benefits_this_year);
if donation.minor <= 0 {
panic!("donation must be positive, received {}", donation.minor);
}
if benefit_value.minor < 0 {
panic!("benefitValue must not be negative, received {}", benefit_value.minor);
}
if prior_benefits_this_year.minor < 0 {
panic!("priorBenefitsThisYear must not be negative, received {}", prior_benefits_this_year.minor);
}
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);
// The variable limit, in pence x 10000.
let scaled_limit: i64 = if donation.minor <= rule.small_gift_up_to {
donation.minor * rule.small_gift_basis_points
} else {
match (rule.middle_gift_up_to, rule.middle_gift_fixed) {
(Some(up_to), Some(fixed)) if donation.minor <= up_to => fixed * 10000,
_ => rule.excess_fixed * 10000 + (donation.minor - rule.excess_from) * rule.excess_basis_points,
}
};
let mut reasons: Vec<String> = Vec::new();
if !has_declaration {
reasons.push("no-declaration".to_string());
}
if !donor_pays_enough_tax {
reasons.push("insufficient-tax".to_string());
}
// "exceeds" and "is more than": a benefit exactly at a limit is allowed.
if benefit_value.minor * 10000 > scaled_limit {
reasons.push("benefit-over-limit".to_string());
}
if benefit_value.minor + prior_benefits_this_year.minor > rule.annual_limit {
reasons.push("annual-benefits-over-limit".to_string());
}
GiftAidEligibility {
eligible: reasons.is_empty(),
reasons,
benefit_limit: money(scaled_limit.div_euclid(10000), "GBP"),
annual_benefit_limit: money(rule.annual_limit, "GBP"),
}
}
pub fn gift_aid_eligibility_to_value(e: &GiftAidEligibility) -> Value {
Value::obj(vec![
("eligible", Value::Bool(e.eligible)),
("reasons", Value::Arr(e.reasons.iter().map(|r| Value::str(r)).collect())),
("benefitLimit", money_to_value(&e.benefit_limit)),
("annualBenefitLimit", money_to_value(&e.annual_benefit_limit)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
gift_aid_eligibility_to_value(&gift_aid_eligibility(
&money_from_value(&args[0]),
&money_from_value(&args[1]),
&money_from_value(&args[2]),
args[3].as_bool(),
args[4].as_bool(),
args[5].as_str(),
))
}