generated from OBJNULL/Dockerized-Rust
Compare commits
4 commits
d4b7b500c8
...
a16579802d
Author | SHA1 | Date | |
---|---|---|---|
a16579802d | |||
845df07b86 | |||
72f4dfceb1 | |||
10f6e9e17e |
4 changed files with 194 additions and 9 deletions
|
@ -5,6 +5,8 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
slint = "1.12.1"
|
||||
regex = "1.11.1"
|
||||
chrono = "0.4.41"
|
||||
|
||||
[build-dependencies]
|
||||
slint-build = "1.12.1"
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
// Libraries
|
||||
slint::include_modules!();
|
||||
use super::Backend;
|
||||
use slint::{ModelRc, SharedString, VecModel};
|
||||
use std::io::Result;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::Backend;
|
||||
|
||||
// Macros
|
||||
macro_rules! map_err {
|
||||
($expr:expr) => {
|
||||
|
@ -62,16 +61,51 @@ impl App {
|
|||
pub fn start(&self) -> Result<()> {
|
||||
// Creating an Rc of our App
|
||||
let app = Rc::clone(&self.app);
|
||||
let backend = Rc::clone(&self.backend);
|
||||
|
||||
// Setting the on_submit function
|
||||
self.app.on_on_calculate(move || {
|
||||
// Getting the prices of the stuff
|
||||
let current_membership = app.get_current_membership();
|
||||
let new_membership = app.get_new_membership();
|
||||
let last_billing = app.get_last_billing();
|
||||
|
||||
// DEBUG
|
||||
println!("Current Membership: {}", current_membership);
|
||||
println!("New Membership: {}", new_membership);
|
||||
// Are either of these a "Select One..."
|
||||
if current_membership == "Select One..."
|
||||
|| new_membership == "Select One..."
|
||||
|| last_billing == "NULL"
|
||||
{
|
||||
return SharedString::from(
|
||||
"Please fill out all fields before calculating the Prorate.",
|
||||
);
|
||||
}
|
||||
|
||||
// Making the Backend Calculate This
|
||||
let calculation = backend
|
||||
.calculate(
|
||||
current_membership.into(),
|
||||
new_membership.into(),
|
||||
last_billing.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Is there no cost difference?
|
||||
if calculation.invalid {
|
||||
return SharedString::from("No price difference, don't change anything.");
|
||||
}
|
||||
|
||||
// Returning the Result
|
||||
if calculation.reversed {
|
||||
SharedString::from(format!(
|
||||
"Retract the billing date to {} (-{}).",
|
||||
calculation.new_date, calculation.days_pushed
|
||||
))
|
||||
} else {
|
||||
SharedString::from(format!(
|
||||
"Extend the billing date to {} (+{}).",
|
||||
calculation.new_date, calculation.days_pushed
|
||||
))
|
||||
}
|
||||
});
|
||||
|
||||
// Starting our application
|
||||
|
|
|
@ -1,10 +1,54 @@
|
|||
// Libraries
|
||||
use std::io::Result;
|
||||
use chrono::{self, Datelike, Local, NaiveDate};
|
||||
use regex::Regex;
|
||||
use std::io::{Error, ErrorKind, Result};
|
||||
|
||||
// Functions
|
||||
fn extract_price(input: String) -> Option<f32> {
|
||||
let re = Regex::new(r"\$?(\d+\.\d+)").unwrap();
|
||||
re.captures(&input).and_then(|caps| {
|
||||
caps.get(1).and_then(|m| {
|
||||
let num_str = m.as_str().replace(',', "");
|
||||
num_str.parse::<f32>().ok()
|
||||
})
|
||||
})
|
||||
}
|
||||
fn add_months(date: &NaiveDate, months: i32) -> NaiveDate {
|
||||
// Getting all required dates for our addition
|
||||
let mut year = date.year();
|
||||
let mut month = date.month() as i32 + months;
|
||||
let day = date.day();
|
||||
|
||||
// Adjust year and month to correct values
|
||||
if month > 12 {
|
||||
year += (month - 1) / 12;
|
||||
month = (month - 1) % 12 + 1;
|
||||
} else if month < 1 {
|
||||
year -= (12 - month) / 12 + 1;
|
||||
month = 12 - ((12 - month) % 12);
|
||||
}
|
||||
|
||||
// Trying to build date on target year/month/day - if invalid (e.g. 31st Feb), fallback:
|
||||
// We first try creating date with the original day
|
||||
if let Some(new_date) = NaiveDate::from_ymd_opt(year, month as u32, day) {
|
||||
new_date
|
||||
} else {
|
||||
// If invalid day, set day to last day of previous month by setting day = 0 on next month
|
||||
// Since NaiveDate::from_ymd_opt(year, month + 1, 0) is last day of month
|
||||
NaiveDate::from_ymd_opt(year, month as u32 + 1, 0).expect("Invalid date calculation")
|
||||
}
|
||||
}
|
||||
|
||||
// Structures
|
||||
pub struct Backend {
|
||||
memberships: Vec<String>,
|
||||
}
|
||||
pub struct CalculationResult {
|
||||
pub days_pushed: f32,
|
||||
pub new_date: String,
|
||||
pub reversed: bool,
|
||||
pub invalid: bool,
|
||||
}
|
||||
|
||||
// Implementations
|
||||
impl Backend {
|
||||
|
@ -47,4 +91,101 @@ impl Backend {
|
|||
pub fn get_memberships(&self) -> Vec<String> {
|
||||
self.memberships.clone()
|
||||
}
|
||||
/// Calculates the Prorate with the given parameters
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// # Examples
|
||||
pub fn calculate(
|
||||
&self,
|
||||
current_membership: String,
|
||||
new_membership: String,
|
||||
last_billing: String,
|
||||
) -> Result<CalculationResult> {
|
||||
// Extracting the Prices from our Memberships
|
||||
let current_membership = match extract_price(current_membership) {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"No Price Found (Current).",
|
||||
))
|
||||
}
|
||||
};
|
||||
let new_membership = match extract_price(new_membership) {
|
||||
Some(m) => m,
|
||||
None => return Err(Error::new(ErrorKind::InvalidData, "No Price Found (New).")),
|
||||
};
|
||||
|
||||
// Getting the Dates
|
||||
let last_billing = match NaiveDate::parse_from_str(&last_billing, "%m/%d/%Y") {
|
||||
Ok(date) => date,
|
||||
Err(_) => return Err(Error::new(ErrorKind::InvalidData, "Date Format Incorrect.")),
|
||||
};
|
||||
let current_date = Local::now().date_naive();
|
||||
|
||||
// Getting the amount of Months that have passed
|
||||
let mut months_passed = (current_date.year() - last_billing.year()) * 12
|
||||
+ (current_date.month() as i32 - last_billing.month() as i32);
|
||||
|
||||
// Saftey Check
|
||||
if current_date.day() < last_billing.day() {
|
||||
months_passed -= 1;
|
||||
}
|
||||
|
||||
// Are the amount of months that have passed less than 0?
|
||||
if months_passed < 0 {
|
||||
months_passed = 0;
|
||||
}
|
||||
|
||||
// Getting the Cost Difference
|
||||
let cost_difference = new_membership - current_membership;
|
||||
|
||||
// Stop if there's no price difference
|
||||
if cost_difference == 0.0 {
|
||||
return Ok(CalculationResult {
|
||||
days_pushed: 0.0,
|
||||
new_date: String::new(),
|
||||
reversed: false,
|
||||
invalid: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Getting the billing Start & End
|
||||
let billing_start = add_months(&last_billing, months_passed);
|
||||
let billing_end = add_months(&last_billing, months_passed + 1);
|
||||
|
||||
// Getting the days left in our billing cycle
|
||||
let days_in_cycle = (billing_end - billing_start).num_days() as f32;
|
||||
|
||||
// Getting our daily rate in our plan
|
||||
let current_daily_rate = current_membership / days_in_cycle;
|
||||
|
||||
// Reversing the adjustment logic here
|
||||
let adjustment_days = (-cost_difference / current_daily_rate * 10.0).round() / 10.0;
|
||||
|
||||
// Getting the Billing Dates
|
||||
let billing_date_normal = add_months(&last_billing, months_passed + 1);
|
||||
let adjustment_days_int = adjustment_days.round() as i64;
|
||||
let billing_date_adjusted = if adjustment_days_int >= 0 {
|
||||
billing_date_normal.checked_add_signed(chrono::Duration::days(adjustment_days_int))
|
||||
} else {
|
||||
billing_date_normal.checked_add_signed(chrono::Duration::days(-adjustment_days_int))
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
// Getting the Adjustment Days
|
||||
let adjustment_days_f32 = adjustment_days.abs();
|
||||
|
||||
// Getting the Adjustment Days String
|
||||
let adjusted_date_string = billing_date_adjusted.format("%m/%d/%Y").to_string();
|
||||
|
||||
// Ok!!
|
||||
Ok(CalculationResult {
|
||||
days_pushed: adjustment_days_f32,
|
||||
new_date: adjusted_date_string,
|
||||
reversed: adjustment_days <= 0.0,
|
||||
invalid: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ export component Prorater inherits Window {
|
|||
height: 720px;
|
||||
|
||||
in property <[string]> membership_names: ["Select One..."];
|
||||
out property <string> last_billing: "NULL";
|
||||
out property <string> current_membership;
|
||||
out property <string> new_membership;
|
||||
callback on_calculate();
|
||||
callback on_calculate() -> string;
|
||||
|
||||
VerticalBox {
|
||||
Image {
|
||||
|
@ -72,9 +73,15 @@ export component Prorater inherits Window {
|
|||
Button {
|
||||
text: "Calculate";
|
||||
clicked => {
|
||||
on_calculate();
|
||||
result_text.text = on_calculate();
|
||||
}
|
||||
}
|
||||
|
||||
result_text := Text {
|
||||
text: "";
|
||||
font-size: 1.25rem;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
date_picker := DatePickerPopup {
|
||||
|
@ -84,7 +91,8 @@ export component Prorater inherits Window {
|
|||
|
||||
accepted(date) => {
|
||||
date_picker.close();
|
||||
last-billing-date.text = date.month + "/" + date.day + "/" + date.year;
|
||||
root.last_billing = date.month + "/" + date.day + "/" + date.year;
|
||||
last_billing_date.text = root.last_billing;
|
||||
}
|
||||
|
||||
canceled => {
|
||||
|
|
Loading…
Reference in a new issue