Functional Weave
Code in Rust

construction.day-rate-quote@1.0.0

impl/rust.rs

3,353 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::finance_markup_price::markup_price;  ← from finance.markup-price ^1.0.0 · built alongside by fune
use super::finance_tax_add_vat::add_vat;  ← from finance.tax.add-vat ^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 MAX_SAFE: i128 = 9_007_199_254_740_991;

/// A quote from labour days, a day rate, marked-up materials and VAT.
///
/// The markup goes on the materials only: the day rate already is the price of
/// labour. Labour is rounded once, to the penny, from days x rate; VAT is one
/// rounding on the net total, as on the invoice that follows.
///
/// # Panics
/// Panics on negative days or rates, mixed currencies, an unknown VAT
/// category, or a product beyond 2^53 - 1.
pub fn day_rate_quote(
    labour_days_hundredths: i64,
    day_rate: &Money,
    materials_cost: &Money,
    materials_markup_basis_points: i64,
    vat_category: Option<&str>,
    on_date: &str,
) -> DayRateQuote {
    if labour_days_hundredths < 0 {
        panic!(
            "labourDaysHundredths must be a whole number of hundredths, not negative, received {}",
            labour_days_hundredths
        );
    }
    if day_rate.minor < 0 {
        panic!("dayRate must not be negative, received {}", day_rate.minor);
    }
    let product = day_rate.minor as i128 * labour_days_hundredths as i128;
    // i64 could go further, but TypeScript cannot, and all three must agree.
    if product > MAX_SAFE {
        panic!("the labour calculation exceeds 2^53 - 1");
    }
    let labour = money(round_div(product as i64, 100, "half-up"), &day_rate.currency);
    let materials = markup_price(materials_cost, materials_markup_basis_points, "markup", "half-up");
    let net = add_money(&labour, &materials);
    match vat_category {
        None => DayRateQuote {
            vat: money(0, &net.currency),
            gross: net.clone(),
            labour,
            materials,
            net,
            vat_basis_points: 0,
        },
        Some(category) => {
            let vat = add_vat(&net, "GB", category, on_date);
            DayRateQuote {
                labour,
                materials,
                net,
                vat: vat.tax,
                gross: vat.gross,
                vat_basis_points: vat.basis_points,
            }
        }
    }
}

pub fn day_rate_quote_to_value(q: &DayRateQuote) -> Value {
    Value::obj(vec![
        ("labour", money_to_value(&q.labour)),
        ("materials", money_to_value(&q.materials)),
        ("net", money_to_value(&q.net)),
        ("vat", money_to_value(&q.vat)),
        ("gross", money_to_value(&q.gross)),
        ("vatBasisPoints", Value::Int(q.vat_basis_points)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    if let Value::Float(f) = &args[0] {
        panic!(
            "labourDaysHundredths must be a whole number of hundredths, not negative, received {}",
            f
        );
    }
    if let Value::Float(f) = &args[3] {
        panic!("basisPoints must be an integer, received {}", f);
    }
    let category = if args[4].is_null() { None } else { Some(args[4].as_str()) };
    day_rate_quote_to_value(&day_rate_quote(
        args[0].as_i64(),
        &money_from_value(&args[1]),
        &money_from_value(&args[2]),
        args[3].as_i64(),
        category,
        args[5].as_str(),
    ))
}