use super::funejson::Value; use super::finance_invoice_calculate::{calculate_invoice, invoice_line_from_value, invoice_to_value, Invoice, InvoiceLine}; use super::finance_invoice_line_total::line_total; use super::money_amount::{money, money_from_value}; /// 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 = 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 = args[0].as_arr().iter().map(invoice_line_from_value).collect(); let credits: Vec = 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())) }