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::insurance_ipt_data::{IPT_RATES, IPT_RATES_HISTORY}; ← this capability’s own data, compiled from data/ipt-rates.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(value: &str) -> bool {
let bytes = value.as_bytes();
bytes.len() == 10
&& bytes[4] == b'-'
&& bytes[7] == b'-'
&& bytes
.iter()
.enumerate()
.all(|(i, b)| i == 4 || i == 7 || b.is_ascii_digit())
}
/// Add UK Insurance Premium Tax to a net premium, or take it out of a gross
/// one, at the rate in force on the date the insurer receives the premium.
///
/// Only one figure is ever rounded - the tax when adding, the net when taking
/// out - and the other is the difference, so net + tax is always the gross.
///
/// # Panics
/// Panics on an unknown band, a malformed date, or a date no rate covers.
pub fn insurance_premium_tax(amount: &Money, includes_tax: bool, band: &str, on_date: &str, mode: &str) -> IptBreakdown {
if band != "standard" && band != "higher" {
panic!("unknown IPT band \"{}\": use standard or higher", band);
}
if !is_iso_date(on_date) {
panic!("onDate must be an ISO date (YYYY-MM-DD), received \"{}\"", on_date);
}
let mut found: Option<(i64, &'static str)> = None;
for r in IPT_RATES {
if r.band != band || on_date < r.valid_from {
continue;
}
if let Some(to) = r.valid_to {
if on_date > to {
continue;
}
}
found = Some((r.basis_points, r.valid_from));
}
let (bp, valid_from) = match found {
Some(f) => f,
None => {
// A history=current build keeps only the rates in force; answering
// an older date with today's rate would be exactly the wrong answer.
if IPT_RATES_HISTORY != "full" {
let mut earliest: Option<&'static str> = None;
for r in IPT_RATES {
if r.band == band && (earliest.is_none() || r.valid_from < earliest.unwrap()) {
earliest = Some(r.valid_from);
}
}
if let Some(earliest) = earliest {
if on_date < earliest {
panic!(
"no IPT rule for {} on {}: this build was installed with history={}, so it only carries rates from {}. Reinstall with history=full for older premiums.",
band, on_date, IPT_RATES_HISTORY, earliest
);
}
}
}
panic!("no IPT rule for {} on {}", band, on_date)
}
};
let (net, tax, gross) = if includes_tax {
let net = money(round_div(amount.minor * 10000, 10000 + bp, mode), &amount.currency);
let tax = money(amount.minor - net.minor, &amount.currency);
(net, tax, amount.clone())
} else {
let tax = money(round_div(amount.minor * bp, 10000, mode), &amount.currency);
let gross = money(amount.minor + tax.minor, &amount.currency);
(amount.clone(), tax, gross)
};
IptBreakdown {
net,
tax,
gross,
basis_points: bp,
band: band.to_string(),
valid_from: valid_from.to_string(),
}
}
pub fn ipt_breakdown_to_value(b: &IptBreakdown) -> Value {
Value::obj(vec![
("net", money_to_value(&b.net)),
("tax", money_to_value(&b.tax)),
("gross", money_to_value(&b.gross)),
("basisPoints", Value::Int(b.basis_points)),
("band", Value::str(&b.band)),
("validFrom", Value::str(&b.valid_from)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
ipt_breakdown_to_value(&insurance_premium_tax(
&money_from_value(&args[0]),
args[1].as_bool(),
args[2].as_str(),
args[3].as_str(),
args[4].as_str(),
))
}