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_invoice_line_total::line_total; ← from finance.invoice.line-total ^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::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
use super::money_sum::sum_money; ← from money.sum ^1.0.0 · built alongside by fune
/// Calculate a complete invoice.
///
/// Note what this function does not contain: no multiplication, no
/// percentages, no rounding, no rate table. Each of those is a capability that
/// is already pinned by its own vectors. What is left here is the part that is
/// genuinely about invoices - the order of operations, and how tax is grouped.
///
/// VAT is calculated per line and then grouped by rate, which keeps each
/// line's tax reconcilable against that line.
///
/// # Panics
/// Panics if `lines` is empty or the lines are not all in one currency.
pub fn calculate_invoice(lines: &[InvoiceLine], jurisdiction: &str, invoice_date: &str) -> Invoice {
if lines.is_empty() {
panic!("an invoice needs at least one line");
}
let currency = lines[0].unit_price.currency.clone();
let mut calculated: Vec<CalculatedLine> = Vec::with_capacity(lines.len());
for line in lines {
let net = line_total(&line.unit_price, line.quantity, line.discount_basis_points);
let vat = add_vat(&net, jurisdiction, &line.tax_category, invoice_date);
calculated.push(CalculatedLine {
description: line.description.clone(),
net: vat.net,
tax: vat.tax,
gross: vat.gross,
basis_points: vat.basis_points,
});
}
let nets: Vec<Money> = calculated.iter().map(|l| l.net.clone()).collect();
let taxes: Vec<Money> = calculated.iter().map(|l| l.tax.clone()).collect();
let subtotal = sum_money(&nets, ¤cy);
let tax_total = sum_money(&taxes, ¤cy);
// Grouped by rate and ordered by rate, so the summary block on a printed
// invoice is stable between runs and between languages.
let mut tax_breakdown: Vec<TaxGroup> = Vec::new();
for line in &calculated {
match tax_breakdown
.iter_mut()
.find(|group| group.basis_points == line.basis_points)
{
Some(group) => {
group.taxable = add_money(&group.taxable, &line.net);
group.tax = add_money(&group.tax, &line.tax);
}
None => tax_breakdown.push(TaxGroup {
basis_points: line.basis_points,
taxable: line.net.clone(),
tax: line.tax.clone(),
}),
}
}
tax_breakdown.sort_by_key(|group| group.basis_points);
Invoice {
currency,
lines: calculated,
total: add_money(&subtotal, &tax_total),
subtotal,
tax_total,
tax_breakdown,
}
}
pub fn invoice_line_from_value(v: &Value) -> InvoiceLine {
InvoiceLine {
description: v.get("description").as_str().to_string(),
unit_price: money_from_value(v.get("unitPrice")),
quantity: v.get("quantity").as_i64(),
discount_basis_points: v.get("discountBasisPoints").as_i64(),
tax_category: v.get("taxCategory").as_str().to_string(),
}
}
pub fn invoice_to_value(invoice: &Invoice) -> Value {
Value::obj(vec![
("currency", Value::str(&invoice.currency)),
(
"lines",
Value::Arr(
invoice
.lines
.iter()
.map(|line| {
Value::obj(vec![
("description", Value::str(&line.description)),
("net", money_to_value(&line.net)),
("tax", money_to_value(&line.tax)),
("gross", money_to_value(&line.gross)),
("basisPoints", Value::Int(line.basis_points)),
])
})
.collect(),
),
),
("subtotal", money_to_value(&invoice.subtotal)),
("taxTotal", money_to_value(&invoice.tax_total)),
("total", money_to_value(&invoice.total)),
(
"taxBreakdown",
Value::Arr(
invoice
.tax_breakdown
.iter()
.map(|group| {
Value::obj(vec![
("basisPoints", Value::Int(group.basis_points)),
("taxable", money_to_value(&group.taxable)),
("tax", money_to_value(&group.tax)),
])
})
.collect(),
),
),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let lines: Vec<InvoiceLine> = args[0].as_arr().iter().map(invoice_line_from_value).collect();
invoice_to_value(&calculate_invoice(
&lines,
args[1].as_str(),
args[2].as_str(),
))
}