Functional Weave
Code in Python

finance.tax.vat-flat-rate@1.0.1

impl/rust.rs

3,493 bytes · the Rust implementation · view raw

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_add_days::add_days;  ← from dates.add-days ^1.0.0 · built alongside by fune
use super::dates_add_months::add_months;  ← from dates.add-months ^1.0.0 · built alongside by fune
use super::finance_tax_vat_flat_rate_data::{FLAT_RATES, FLAT_RATES_HISTORY};  ← this capability’s own data, compiled from data/flat-rates.json into the same file by fune build
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

const LIMITED_COST: &str = "limited-cost-business";

fn rate_on(sector: &str, on_date: &str) -> i64 {
    let rows: Vec<_> = FLAT_RATES.iter().filter(|r| r.sector == sector).collect();
    for row in &rows {
        if on_date >= row.valid_from && row.valid_to.map_or(true, |to| on_date <= to) {
            return row.basis_points;
        }
    }
    if FLAT_RATES_HISTORY != "full" {
        if let Some(earliest) = rows.iter().map(|r| r.valid_from).min() {
            if on_date < earliest {
                panic!(
                    "no flat rate for {} on {}: this build was installed with history={}, so it only carries rates from {}. Reinstall with history=full for older periods.",
                    sector, on_date, FLAT_RATES_HISTORY, earliest
                );
            }
        }
    }
    panic!("no flat rate for {} on {}", sector, on_date);
}

/// VAT due under the Flat Rate Scheme on a slice of VAT-inclusive turnover.
///
/// The rate is the one in force on the supply date; the first-year reduction
/// runs to the day before the first anniversary of registration.
///
/// # Panics
/// Panics on non-GBP turnover, an unknown sector, a date without a rate, or a
/// supply before the registration date.
pub fn flat_rate_vat(
    turnover: &Money,
    sector: &str,
    limited_cost_business: bool,
    registration_date: Option<&str>,
    supply_date: &str,
) -> FlatRateVat {
    if turnover.currency != "GBP" {
        panic!("the flat rate scheme is for GBP turnover, received {}", turnover.currency);
    }
    add_days(supply_date, 0);
    if sector == LIMITED_COST || !FLAT_RATES.iter().any(|r| r.sector == sector) {
        panic!("unknown flat rate sector \"{}\"", sector);
    }
    let base = rate_on(if limited_cost_business { LIMITED_COST } else { sector }, supply_date);

    let mut reduced = false;
    if let Some(registered) = registration_date {
        if supply_date < add_days(registered, 0).as_str() {
            panic!(
                "supply date {} is before the VAT registration date {}",
                supply_date, registered
            );
        }
        reduced = supply_date < add_months(registered, 12).as_str();
    }
    let basis_points = if reduced { (base - 100).max(0) } else { base };
    FlatRateVat {
        sector: sector.to_string(),
        limited_cost_business,
        first_year_reduction: reduced,
        basis_points,
        vat_due: money(apply_rate(turnover, basis_points, "half-up").minor, "GBP"),
    }
}

pub fn flat_rate_vat_to_value(v: &FlatRateVat) -> Value {
    Value::obj(vec![
        ("sector", Value::str(&v.sector)),
        ("limitedCostBusiness", Value::Bool(v.limited_cost_business)),
        ("firstYearReduction", Value::Bool(v.first_year_reduction)),
        ("basisPoints", Value::Int(v.basis_points)),
        ("vatDue", money_to_value(&v.vat_due)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let registration = if args[3].is_null() { None } else { Some(args[3].as_str()) };
    flat_rate_vat_to_value(&flat_rate_vat(
        &money_from_value(&args[0]),
        args[1].as_str(),
        args[2].as_bool(),
        registration,
        args[4].as_str(),
    ))
}