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::dates_days_between::days_between; ← from dates.days-between ^1.0.0 · built alongside by fune
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_add::add_money; ← from money.add ^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
const ZERO_VAT_SALES: [&str; 5] = ["exempt", "outside-scope", "domestic-reverse-charge", "services-reverse-charge", "ni-eu-goods"];
const ZERO_VAT_PURCHASES: [&str; 2] = ["exempt", "outside-scope"];
const TREATMENTS: [&str; 7] = [
"taxable",
"exempt",
"outside-scope",
"domestic-reverse-charge",
"services-reverse-charge",
"postponed-import-vat",
"ni-eu-goods",
];
/// Drop the pence, towards zero: boxes 6 to 9 are whole pounds.
fn whole_pounds(amount: &Money) -> Money {
money(round_div(amount.minor, 100, "down") * 100, &amount.currency)
}
/// The nine boxes of a UK VAT return for a period.
///
/// Each transaction feeds the boxes its direction and treatment call for
/// (VAT Notice 700/12 sections 3 and 4.6); boxes 6 to 9 drop the pence after
/// totalling.
///
/// # Panics
/// Panics on a period that ends before it starts, an unknown direction or
/// treatment, non-GBP amounts, or VAT on a treatment that carries none.
pub fn vat_return(transactions: &[VatTransaction], period_start: &str, period_end: &str) -> VatReturn {
if days_between(period_start, period_end) < 0 {
panic!("VAT return period ends {}, before it starts {}", period_end, period_start);
}
let mut boxes: Vec<Money> = (0..10).map(|_| money(0, "GBP")).collect();
let mut count = 0;
for (index, t) in transactions.iter().enumerate() {
let n = index + 1;
if t.direction != "sale" && t.direction != "purchase" {
panic!("transaction {}: unknown direction \"{}\"", n, t.direction);
}
if !TREATMENTS.contains(&t.treatment.as_str()) {
panic!("transaction {}: unknown VAT treatment \"{}\"", n, t.treatment);
}
if t.net.currency != "GBP" || t.vat.currency != "GBP" {
panic!("transaction {}: amounts must be in GBP", n);
}
let sale = t.direction == "sale";
if sale && t.treatment == "postponed-import-vat" {
panic!("transaction {}: postponed-import-vat applies to purchases only", n);
}
let zero_vat = if sale {
ZERO_VAT_SALES.contains(&t.treatment.as_str())
} else {
ZERO_VAT_PURCHASES.contains(&t.treatment.as_str())
};
if t.vat.minor != 0 && zero_vat {
panic!("transaction {}: {} {}s must have zero VAT", n, t.treatment, t.direction);
}
if days_between(period_start, &t.date) < 0 || days_between(&t.date, period_end) < 0 {
continue;
}
count += 1;
let mut add = |b: usize, amount: &Money| {
boxes[b] = add_money(&boxes[b], amount);
};
let treatment = t.treatment.as_str();
if sale {
if treatment == "taxable" {
add(1, &t.vat);
}
if treatment != "outside-scope" {
add(6, &t.net);
}
if treatment == "ni-eu-goods" {
add(8, &t.net);
}
} else {
match treatment {
"taxable" => {
add(4, &t.vat);
add(7, &t.net);
}
"exempt" => add(7, &t.net),
"domestic-reverse-charge" | "services-reverse-charge" | "postponed-import-vat" => {
if treatment == "services-reverse-charge" {
add(6, &t.net);
}
add(1, &t.vat);
add(4, &t.vat);
add(7, &t.net);
}
"ni-eu-goods" => {
add(2, &t.vat);
add(4, &t.vat);
add(7, &t.net);
add(9, &t.net);
}
_ => {}
}
}
}
let box3 = add_money(&boxes[1], &boxes[2]);
let difference = box3.minor - boxes[4].minor;
VatReturn {
box1: boxes[1].clone(),
box2: boxes[2].clone(),
box3,
box4: boxes[4].clone(),
box5: money(difference.abs(), "GBP"),
box6: whole_pounds(&boxes[6]),
box7: whole_pounds(&boxes[7]),
box8: whole_pounds(&boxes[8]),
box9: whole_pounds(&boxes[9]),
repayment: difference < 0,
transactions: count,
}
}
pub fn vat_transaction_from_value(v: &Value) -> VatTransaction {
VatTransaction {
date: v.get("date").as_str().to_string(),
direction: v.get("direction").as_str().to_string(),
treatment: v.get("treatment").as_str().to_string(),
net: money_from_value(v.get("net")),
vat: money_from_value(v.get("vat")),
}
}
pub fn vat_return_to_value(r: &VatReturn) -> Value {
Value::obj(vec![
("box1", money_to_value(&r.box1)),
("box2", money_to_value(&r.box2)),
("box3", money_to_value(&r.box3)),
("box4", money_to_value(&r.box4)),
("box5", money_to_value(&r.box5)),
("box6", money_to_value(&r.box6)),
("box7", money_to_value(&r.box7)),
("box8", money_to_value(&r.box8)),
("box9", money_to_value(&r.box9)),
("repayment", Value::Bool(r.repayment)),
("transactions", Value::Int(r.transactions)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let transactions: Vec<VatTransaction> = args[0].as_arr().iter().map(vat_transaction_from_value).collect();
vat_return_to_value(&vat_return(&transactions, args[1].as_str(), args[2].as_str()))
}