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_data::{BASIC_RATE, BASIC_RATE_HISTORY}; ← this capability’s own data, compiled from data/basic-rate.json into the same file by fune build
use super::math_round_div::round_div; ← from math.round-div ^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 basic_rate(on_date: &str) -> i64 {
for row in BASIC_RATE {
if row.valid_from <= on_date && row.valid_to.map_or(true, |to| on_date <= to) {
return row.basis_points;
}
}
// A history=current build carries only the rate in force; answering an old
// gift with today's rate would be a quiet wrong claim, so say why instead.
if BASIC_RATE_HISTORY != "full" {
if let Some(earliest) = BASIC_RATE.iter().map(|r| r.valid_from).min() {
if on_date < earliest {
panic!(
"no Gift Aid basic rate on {}: this build was installed with history={}, so it only carries rules from {}. Reinstall with history=full for earlier tax years.",
on_date, BASIC_RATE_HISTORY, earliest
);
}
}
}
panic!("no Gift Aid basic rate on {}", on_date);
}
/// The tax a charity reclaims on a Gift Aid donation. The donor is treated as
/// having given the gift after deducting basic rate tax (ITA 2007 s414), so the
/// charity reclaims donation x rate / (100% - rate): 25p per £1 at 20%. It is
/// always the UK basic rate, for Scottish and Welsh taxpayers too.
///
/// # Panics
/// Panics on a non-GBP or negative donation, a malformed date, or a date with
/// no basic rate on file.
pub fn gift_aid(donation: &Money, on_date: &str) -> GiftAid {
if donation.currency != "GBP" {
panic!("Gift Aid is claimed in GBP, received {}", donation.currency);
}
if donation.minor < 0 {
panic!("donation must not be negative, received {}", donation.minor);
}
if !is_iso_date(on_date) {
panic!("onDate must be an ISO date (YYYY-MM-DD), received \"{}\"", on_date);
}
let rate = basic_rate(on_date);
// Rounded down: a claim may never exceed the tax treated as deducted.
let tax = round_div(donation.minor * rate, 10000 - rate, "down");
GiftAid {
donation: money(donation.minor, "GBP"),
tax_reclaimable: money(tax, "GBP"),
gross: money(donation.minor + tax, "GBP"),
basic_rate_basis_points: rate,
}
}
pub fn gift_aid_to_value(g: &GiftAid) -> Value {
Value::obj(vec![
("donation", money_to_value(&g.donation)),
("taxReclaimable", money_to_value(&g.tax_reclaimable)),
("gross", money_to_value(&g.gross)),
("basicRateBasisPoints", Value::Int(g.basic_rate_basis_points)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
if let Value::Float(f) = args[0].get("minor") {
if f.fract() != 0.0 {
panic!("money() takes integer minor units, received {}", f);
}
}
gift_aid_to_value(&gift_aid(&money_from_value(&args[0]), args[1].as_str()))
}