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_tax_add_vat::{add_vat, vat_breakdown_to_value, VatBreakdown}; ← from finance.tax.add-vat ^1.0.0 · built alongside by fune
use super::finance_tax_remove_vat::remove_vat; ← from finance.tax.remove-vat ^1.0.0 · built alongside by fune
use super::money_allocate::allocate; ← from money.allocate ^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::money_sum::sum_money; ← from money.sum ^1.0.0 · built alongside by fune
use super::retail_promotion_apply::{
applied_promotion_to_value, apply_promotions, promotion_from_value, PromoLine, Promotion,
};
/// The checkout: promotions, then the basket discount shared across the
/// lines, then VAT per line on what the line really costs, then delivery.
///
/// No arithmetic lives here; the order of operations is the whole function.
///
/// # Panics
/// Panics on mixed currencies, negative discounts or delivery, a discount
/// bigger than the basket, or anything its dependencies refuse.
#[allow(clippy::too_many_arguments)]
pub fn basket_total(
lines: &[BasketLine],
promotions: &[Promotion],
basket_discount: &Money,
delivery: &Money,
delivery_tax_category: &str,
prices_include_vat: bool,
jurisdiction: &str,
on_date: &str,
) -> Basket {
let promo_lines: Vec<PromoLine> = lines
.iter()
.map(|l| PromoLine {
sku: l.sku.clone(),
unit_price: l.unit_price.clone(),
quantity: l.quantity,
})
.collect();
let priced = apply_promotions(&promo_lines, promotions);
let currency = priced.subtotal.currency.clone();
for (name, amount) in [("basketDiscount", basket_discount), ("delivery", delivery)] {
if amount.currency != currency {
panic!("currency mismatch: {} and {}", currency, amount.currency);
}
if amount.minor < 0 {
panic!("{} must not be negative, received {}", name, amount.minor);
}
}
if basket_discount.minor > priced.total.minor {
panic!(
"basketDiscount {} is more than the basket's {} after promotions",
basket_discount.minor, priced.total.minor
);
}
let after_promotions: Vec<i64> = priced.lines.iter().map(|l| l.net.minor).collect();
let shares: Vec<Money> = if basket_discount.minor == 0 {
after_promotions.iter().map(|_| money(0, ¤cy)).collect()
} else {
allocate(basket_discount, &after_promotions)
};
let vat = |amount: &Money, category: &str| -> VatBreakdown {
if prices_include_vat {
remove_vat(amount, jurisdiction, category, on_date)
} else {
add_vat(amount, jurisdiction, category, on_date)
}
};
let mut rates: Vec<i64> = Vec::new();
let mut totals: Vec<BasketLineTotal> = Vec::new();
for (i, line) in lines.iter().enumerate() {
let v = vat(&money(after_promotions[i] - shares[i].minor, ¤cy), &line.tax_category);
rates.push(v.basis_points);
totals.push(BasketLineTotal {
sku: line.sku.clone(),
quantity: line.quantity,
gross: priced.lines[i].gross.clone(),
promotion_discount: priced.lines[i].discount.clone(),
basket_discount: shares[i].clone(),
net: v.net,
tax: v.tax,
total: v.gross,
});
}
let delivery_vat = vat(delivery, delivery_tax_category);
// Grouped by rate, lowest first, so the receipt's VAT summary is stable.
let mut groups: Vec<(i64, i64, i64)> = Vec::new();
let mut add = |bp: i64, net: i64, tax: i64| match groups.iter_mut().find(|g| g.0 == bp) {
Some(g) => {
g.1 += net;
g.2 += tax;
}
None => groups.push((bp, net, tax)),
};
for (i, t) in totals.iter().enumerate() {
add(rates[i], t.net.minor, t.tax.minor);
}
if delivery.minor != 0 {
add(delivery_vat.basis_points, delivery_vat.net.minor, delivery_vat.tax.minor);
}
groups.sort_by_key(|g| g.0);
let vat_breakdown: Vec<BasketVatGroup> = groups
.iter()
.map(|&(bp, n, t)| BasketVatGroup {
basis_points: bp,
net: money(n, ¤cy),
tax: money(t, ¤cy),
})
.collect();
let mut nets: Vec<Money> = totals.iter().map(|t| t.net.clone()).collect();
nets.push(delivery_vat.net.clone());
let mut taxes: Vec<Money> = totals.iter().map(|t| t.tax.clone()).collect();
taxes.push(delivery_vat.tax.clone());
let mut grosses: Vec<Money> = totals.iter().map(|t| t.total.clone()).collect();
grosses.push(delivery_vat.gross.clone());
Basket {
lines: totals,
promotions: priced.applied,
delivery: delivery_vat,
vat_breakdown,
subtotal: priced.subtotal,
promotion_discount: priced.discount,
basket_discount: basket_discount.clone(),
net: sum_money(&nets, ¤cy),
tax: sum_money(&taxes, ¤cy),
total: sum_money(&grosses, ¤cy),
}
}
pub fn basket_line_from_value(v: &Value) -> BasketLine {
BasketLine {
sku: v.get("sku").as_str().to_string(),
unit_price: money_from_value(v.get("unitPrice")),
quantity: v.get("quantity").as_i64(),
tax_category: v.get("taxCategory").as_str().to_string(),
}
}
pub fn basket_to_value(b: &Basket) -> Value {
Value::obj(vec![
(
"lines",
Value::Arr(
b.lines
.iter()
.map(|l| {
Value::obj(vec![
("sku", Value::str(&l.sku)),
("quantity", Value::Int(l.quantity)),
("gross", money_to_value(&l.gross)),
("promotionDiscount", money_to_value(&l.promotion_discount)),
("basketDiscount", money_to_value(&l.basket_discount)),
("net", money_to_value(&l.net)),
("tax", money_to_value(&l.tax)),
("total", money_to_value(&l.total)),
])
})
.collect(),
),
),
("promotions", Value::Arr(b.promotions.iter().map(applied_promotion_to_value).collect())),
("delivery", vat_breakdown_to_value(&b.delivery)),
(
"vatBreakdown",
Value::Arr(
b.vat_breakdown
.iter()
.map(|g| {
Value::obj(vec![
("basisPoints", Value::Int(g.basis_points)),
("net", money_to_value(&g.net)),
("tax", money_to_value(&g.tax)),
])
})
.collect(),
),
),
("subtotal", money_to_value(&b.subtotal)),
("promotionDiscount", money_to_value(&b.promotion_discount)),
("basketDiscount", money_to_value(&b.basket_discount)),
("net", money_to_value(&b.net)),
("tax", money_to_value(&b.tax)),
("total", money_to_value(&b.total)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let lines: Vec<BasketLine> = args[0].as_arr().iter().map(basket_line_from_value).collect();
let promotions: Vec<Promotion> = args[1].as_arr().iter().map(promotion_from_value).collect();
basket_to_value(&basket_total(
&lines,
&promotions,
&money_from_value(&args[2]),
&money_from_value(&args[3]),
args[4].as_str(),
args[5].as_bool(),
args[6].as_str(),
args[7].as_str(),
))
}