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::money_amount::{money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
use super::money_apply_rate::apply_rate; ← from money.apply-rate ^1.0.0 · built alongside by fune
use super::retail_oss_vat_data::{OSS_RULES, OSS_RULES_HISTORY}; ← this capability’s own data, compiled from data/oss-rules.json into the same file by fune build
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())
}
// The EU writes Greece as EL in VAT matters; ISO 3166 says GR. Accept both.
fn country(code: &str, name: &str) -> String {
if code.len() != 2 || !code.bytes().all(|b| b.is_ascii_uppercase()) {
panic!("{} must be an ISO 3166-1 alpha-2 code, received \"{}\"", name, code);
}
if code == "EL" {
"GR".to_string()
} else {
code.to_string()
}
}
fn is_member(code: &str) -> bool {
OSS_RULES.iter().any(|r| r.rule == "standard-rate" && r.country == code)
}
fn lookup(rule: &str, place: &str, on_date: &str) -> i64 {
let mut best: Option<(&str, i64)> = None;
for r in OSS_RULES {
if r.rule != rule || r.country != place {
continue;
}
if on_date < r.valid_from {
continue;
}
if let Some(to) = r.valid_to {
if on_date > to {
continue;
}
}
if best.map_or(true, |(from, _)| r.valid_from > from) {
best = Some((r.valid_from, r.value));
}
}
if let Some((_, value)) = best {
return value;
}
// A history=current build carries only the rules still in force; answering
// an older date with today's rate is the failure the dates exist to prevent.
if OSS_RULES_HISTORY != "full" {
let mut earliest: Option<&str> = None;
for r in OSS_RULES {
if r.rule == rule && r.country == place && earliest.map_or(true, |e| r.valid_from < e) {
earliest = Some(r.valid_from);
}
}
if let Some(earliest) = earliest {
if on_date < earliest {
panic!(
"no OSS rule \"{}\" for {} on {}: this build was installed with history={}, so it only carries rules from {}. Reinstall with history=full for older supplies.",
rule, place, on_date, OSS_RULES_HISTORY, earliest
);
}
}
}
panic!("no OSS rule \"{}\" for {} on {}", rule, place, on_date)
}
/// VAT on a business-to-consumer sale to another EU member state: the
/// seller's own rate while its cross-border sales stay within EUR 10,000 (and
/// it has not opted in), the customer's state's rate through the One Stop
/// Shop once they exceed it, and always the customer's for sellers outside
/// the EU.
///
/// # Panics
/// Panics on a malformed date or country code, a customer outside the EU,
/// threshold totals not in EUR, or a date no rule covers.
pub fn oss_vat(
net: &Money,
seller_country: &str,
customer_country: &str,
eu_sales_previous_year: &Money,
eu_sales_this_year: &Money,
opted_in: bool,
on_date: &str,
) -> OssVat {
if !is_iso_date(on_date) {
panic!("onDate must be an ISO date (YYYY-MM-DD), received \"{}\"", on_date);
}
let seller = country(seller_country, "sellerCountry");
let customer = country(customer_country, "customerCountry");
if !is_member(&customer) {
panic!("customer country \"{}\" is not an EU member state", customer_country);
}
for (name, amount) in [
("euSalesPreviousYear", eu_sales_previous_year),
("euSalesThisYear", eu_sales_this_year),
] {
if amount.currency != "EUR" {
panic!("{} must be in EUR, received {}", name, amount.currency);
}
if amount.minor < 0 {
panic!("{} must not be negative, received {}", name, amount.minor);
}
}
let threshold = lookup("distance-selling-threshold", "EU", on_date);
let threshold_exceeded =
eu_sales_previous_year.minor > threshold || eu_sales_this_year.minor > threshold;
let (scheme, tax_country) = if seller == customer {
("domestic", customer)
} else if is_member(&seller) && !threshold_exceeded && !opted_in {
("origin", seller)
} else {
("oss", customer)
};
let basis_points = lookup("standard-rate", &tax_country, on_date);
let vat = apply_rate(net, basis_points, "half-up");
OssVat {
tax_country,
scheme: scheme.to_string(),
basis_points,
threshold_exceeded,
net: net.clone(),
gross: money(net.minor + vat.minor, &net.currency),
vat,
}
}
pub fn oss_vat_to_value(o: &OssVat) -> Value {
Value::obj(vec![
("taxCountry", Value::str(&o.tax_country)),
("scheme", Value::str(&o.scheme)),
("basisPoints", Value::Int(o.basis_points)),
("thresholdExceeded", Value::Bool(o.threshold_exceeded)),
("net", money_to_value(&o.net)),
("vat", money_to_value(&o.vat)),
("gross", money_to_value(&o.gross)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
oss_vat_to_value(&oss_vat(
&money_from_value(&args[0]),
args[1].as_str(),
args[2].as_str(),
&money_from_value(&args[3]),
&money_from_value(&args[4]),
args[5].as_bool(),
args[6].as_str(),
))
}