Functional Weave
Code in Rust

finance.credit-note.calculate@1.0.0

impl/rust.rs

3,814 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_invoice_calculate::{calculate_invoice, invoice_line_from_value, invoice_to_value, Invoice, InvoiceLine};  ← from finance.invoice.calculate ^1.0.0 · built alongside by fune
use super::finance_invoice_line_total::line_total;  ← from finance.invoice.line-total ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value};  ← from money.amount ^1.0.0 · built alongside by fune

/// A credit note against an invoice, with VAT at the original tax point.
///
/// Every credit becomes a negative invoice line and the whole note is
/// calculated by finance.invoice.calculate at the original date, so the VAT
/// rates, rounding and grouping are the invoice's own.
///
/// # Panics
/// Panics on an empty or malformed credit, a line out of range, or credits
/// exceeding what a line was invoiced for.
pub fn calculate_credit_note(
    original_lines: &[InvoiceLine],
    credits: &[CreditRequest],
    jurisdiction: &str,
    original_tax_point: &str,
) -> Invoice {
    if credits.is_empty() {
        panic!("a credit note needs at least one line");
    }
    let mut credited = vec![0i64; original_lines.len()];
    let mut lines: Vec<InvoiceLine> = Vec::with_capacity(credits.len());
    for credit in credits {
        let n = credit.line;
        if n < 1 || n as usize > original_lines.len() {
            panic!("credit refers to line {}, but the invoice has {} lines", n, original_lines.len());
        }
        let original = &original_lines[n as usize - 1];
        if original.quantity <= 0 {
            panic!("line {} of the invoice is not a sale that can be credited", n);
        }
        let line = match (&credit.quantity, &credit.net) {
            (Some(quantity), None) => {
                if *quantity <= 0 {
                    panic!("credit quantity for line {} must be greater than zero", n);
                }
                InvoiceLine {
                    quantity: -quantity,
                    ..original.clone()
                }
            }
            (None, Some(net)) => {
                if net.currency != original.unit_price.currency {
                    panic!("credit for line {} must be in {}", n, original.unit_price.currency);
                }
                if net.minor <= 0 {
                    panic!("credit amount for line {} must be greater than zero", n);
                }
                InvoiceLine {
                    description: original.description.clone(),
                    unit_price: money(-net.minor, &net.currency),
                    quantity: 1,
                    discount_basis_points: 0,
                    tax_category: original.tax_category.clone(),
                }
            }
            _ => panic!("credit for line {} needs exactly one of quantity and net", n),
        };
        let slot = n as usize - 1;
        credited[slot] -= line_total(&line.unit_price, line.quantity, line.discount_basis_points).minor;
        let invoiced = line_total(&original.unit_price, original.quantity, original.discount_basis_points).minor;
        if credited[slot] > invoiced {
            panic!("credits against line {} exceed what it was invoiced for", n);
        }
        lines.push(line);
    }
    calculate_invoice(&lines, jurisdiction, original_tax_point)
}

pub fn credit_request_from_value(v: &Value) -> CreditRequest {
    CreditRequest {
        line: v.get("line").as_i64(),
        quantity: if v.get("quantity").is_null() { None } else { Some(v.get("quantity").as_i64()) },
        net: if v.get("net").is_null() { None } else { Some(money_from_value(v.get("net"))) },
    }
}

pub fn fune_vector(args: &[Value]) -> Value {
    let originals: Vec<InvoiceLine> = args[0].as_arr().iter().map(invoice_line_from_value).collect();
    let credits: Vec<CreditRequest> = args[1].as_arr().iter().map(credit_request_from_value).collect();
    invoice_to_value(&calculate_credit_note(&originals, &credits, args[2].as_str(), args[3].as_str()))
}