Functional Weave
Code in TypeScript

retail.sales-tax-us@1.1.1

impl/rust.rs

3,882 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::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
use super::retail_sales_tax_us_data::{STATE_RATES, STATE_RATES_HISTORY};  ← this capability’s own data, compiled from data/state-rates.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())
}

fn state_rate(state: &str, on_date: &str) -> i64 {
    if !STATE_RATES.iter().any(|r| r.state == state) {
        panic!("unknown US state \"{}\"", state);
    }
    let mut best: Option<(&str, i64)> = None;
    for r in STATE_RATES {
        if r.state != state || 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.rate_per_million));
        }
    }
    if let Some((_, rate)) = best {
        return rate;
    }
    if STATE_RATES_HISTORY != "full" {
        let mut earliest: Option<&str> = None;
        for r in STATE_RATES {
            if r.state == state && 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 sales tax rate for {} on {}: this build was installed with history={}, so it only carries rates from {}. Reinstall with history=full for older sales.",
                    state, on_date, STATE_RATES_HISTORY, earliest
                );
            }
        }
    }
    panic!("no sales tax rate for {} on {}", state, on_date)
}

/// US sales tax: the state's base rate on the date of sale plus the caller's
/// local rate, applied once and rounded half up to the cent.
///
/// # Panics
/// Panics on a malformed date, a non-USD amount, an unknown state, a local
/// rate outside 0..1000000, or a date no rate covers.
pub fn sales_tax_us(amount: &Money, state: &str, local_rate_per_million: i64, on_date: &str) -> UsSalesTax {
    if !is_iso_date(on_date) {
        panic!("onDate must be an ISO date (YYYY-MM-DD), received \"{}\"", on_date);
    }
    if amount.currency != "USD" {
        panic!("US sales tax is charged in USD, received {}", amount.currency);
    }
    if !(0..1000000).contains(&local_rate_per_million) {
        panic!(
            "localRatePerMillion must be from 0 to 999999, received {}",
            local_rate_per_million
        );
    }
    let state_rate_per_million = state_rate(state, on_date);
    let combined = state_rate_per_million + local_rate_per_million;
    let tax = money(round_div(amount.minor * combined, 1000000, "half-up"), "USD");
    UsSalesTax {
        state: state.to_string(),
        state_rate_per_million,
        local_rate_per_million,
        combined_rate_per_million: combined,
        taxable: amount.clone(),
        total: money(amount.minor + tax.minor, "USD"),
        tax,
    }
}

pub fn us_sales_tax_to_value(t: &UsSalesTax) -> Value {
    Value::obj(vec![
        ("state", Value::str(&t.state)),
        ("stateRatePerMillion", Value::Int(t.state_rate_per_million)),
        ("localRatePerMillion", Value::Int(t.local_rate_per_million)),
        ("combinedRatePerMillion", Value::Int(t.combined_rate_per_million)),
        ("taxable", money_to_value(&t.taxable)),
        ("tax", money_to_value(&t.tax)),
        ("total", money_to_value(&t.total)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    us_sales_tax_to_value(&sales_tax_us(
        &money_from_value(&args[0]),
        args[1].as_str(),
        args[2].as_i64(),
        args[3].as_str(),
    ))
}