use super::funejson::Value; use super::charity_gasds_data::{GasdsLimit, GASDS_LIMITS, GASDS_LIMITS_HISTORY}; use super::charity_gift_aid::gift_aid; use super::money_amount::{money, money_from_value, money_to_value, Money}; 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 = 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(), )) }