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::dates_days_between::days_between; ← from dates.days-between ^1.0.0 · built alongside by fune
use super::dates_overlap::overlap_days; ← from dates.overlap ^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
/// Split an invoice across accounting periods by days of service.
///
/// money.allocate does the split with each period's days as the weight, so the
/// shares add back to the invoice exactly. A negative amount is split as its
/// positive and negated, so a credit note mirrors its invoice period by period.
///
/// # Panics
/// Panics on malformed dates, a service that ends before it starts, periods
/// that overlap, are out of order or leave days of service uncovered.
pub fn allocate_to_periods(
amount: &Money,
service_start: &str,
service_end: &str,
periods: &[AccountingPeriod],
) -> Vec<PeriodAllocation> {
let service_days = days_between(service_start, service_end) + 1;
if service_days < 1 {
panic!("service ends {}, before it starts {}", service_end, service_start);
}
if periods.is_empty() {
panic!("at least one accounting period is needed");
}
let mut days: Vec<i64> = Vec::with_capacity(periods.len());
for (index, period) in periods.iter().enumerate() {
if days_between(&period.start, &period.end) < 0 {
panic!("period {} ends before it starts", period.label);
}
if index > 0 && days_between(&periods[index - 1].end, &period.start) < 1 {
panic!("period {} overlaps or precedes the period before it", period.label);
}
days.push(overlap_days(service_start, service_end, &period.start, &period.end, true));
}
let covered: i64 = days.iter().sum();
if covered != service_days {
panic!(
"the accounting periods cover {} of the {} days of service",
covered, service_days
);
}
let negative = amount.minor < 0;
let magnitude = money(if negative { -amount.minor } else { amount.minor }, &amount.currency);
let shares = allocate(&magnitude, &days);
periods
.iter()
.enumerate()
.map(|(index, period)| PeriodAllocation {
label: period.label.clone(),
days: days[index],
amount: if negative {
money(-shares[index].minor, &amount.currency)
} else {
shares[index].clone()
},
})
.collect()
}
pub fn accounting_period_from_value(v: &Value) -> AccountingPeriod {
AccountingPeriod {
label: v.get("label").as_str().to_string(),
start: v.get("start").as_str().to_string(),
end: v.get("end").as_str().to_string(),
}
}
pub fn period_allocation_to_value(a: &PeriodAllocation) -> Value {
Value::obj(vec![
("label", Value::str(&a.label)),
("days", Value::Int(a.days)),
("amount", money_to_value(&a.amount)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let periods: Vec<AccountingPeriod> = args[3].as_arr().iter().map(accounting_period_from_value).collect();
Value::Arr(
allocate_to_periods(&money_from_value(&args[0]), args[1].as_str(), args[2].as_str(), &periods)
.iter()
.map(period_allocation_to_value)
.collect(),
)
}