use super::funejson::Value; use super::dates_days_between::days_between; use super::math_round_div::round_div; use super::money_amount::{assert_same_currency, money, money_from_value, money_to_value, Money}; /// The additional or return premium for a change part-way through a policy term. /// /// The difference between the two full-term premiums is pro-rated over the /// days left, with one rounding. Pro-rating the old and new premiums /// separately and subtracting rounds twice and can be a penny out. /// /// # Panics /// Panics on mixed currencies, negative premiums or fee, or dates out of order. pub fn mid_term_adjustment( old_premium: &Money, new_premium: &Money, inception_date: &str, expiry_date: &str, change_date: &str, admin_fee: &Money, mode: &str, ) -> MidTermAdjustment { assert_same_currency(old_premium, new_premium); assert_same_currency(old_premium, admin_fee); if old_premium.minor < 0 || new_premium.minor < 0 { panic!("premiums must not be negative"); } if admin_fee.minor < 0 { panic!("adminFee must not be negative, received {}", admin_fee.minor); } let total_days = days_between(inception_date, expiry_date); if total_days <= 0 { panic!("expiryDate must be after inceptionDate, received {} to {}", inception_date, expiry_date); } let remaining_days = days_between(change_date, expiry_date); if remaining_days < 0 || remaining_days > total_days { panic!("changeDate must be from inceptionDate to expiryDate, received {}", change_date); } let change = round_div((new_premium.minor - old_premium.minor) * remaining_days, total_days, mode); let currency = &old_premium.currency; let kind = if change > 0 { "additional" } else if change < 0 { "return" } else { "none" }; MidTermAdjustment { total_days, remaining_days, premium_change: money(change, currency), admin_fee: admin_fee.clone(), amount_due: money(change + admin_fee.minor, currency), kind: kind.to_string(), } } pub fn mid_term_adjustment_to_value(a: &MidTermAdjustment) -> Value { Value::obj(vec![ ("totalDays", Value::Int(a.total_days)), ("remainingDays", Value::Int(a.remaining_days)), ("premiumChange", money_to_value(&a.premium_change)), ("adminFee", money_to_value(&a.admin_fee)), ("amountDue", money_to_value(&a.amount_due)), ("kind", Value::str(&a.kind)), ]) } pub fn fune_vector(args: &[Value]) -> Value { mid_term_adjustment_to_value(&mid_term_adjustment( &money_from_value(&args[0]), &money_from_value(&args[1]), args[2].as_str(), args[3].as_str(), args[4].as_str(), &money_from_value(&args[5]), args[6].as_str(), )) }