Allow to average over severall months

This commit is contained in:
traxys 2023-11-22 00:20:31 +01:00
parent cc6dc16380
commit 2a74d250a0
3 changed files with 101 additions and 9 deletions

1
Cargo.lock generated
View file

@ -718,6 +718,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"directories",
"either",
"iced",
"iced_aw",
"itertools",

View file

@ -7,6 +7,7 @@ edition = "2021"
[dependencies]
anyhow = "1.0.75"
directories = "5.0.1"
either = "1.9.0"
iced = { version = "0.10.0", features = ["lazy"] }
iced_aw = { version = "0.7.0", default-features = false, features = ["modal", "card", "number_input", "selection_list"] }
itertools = "0.11.0"

View file

@ -2,11 +2,13 @@ use std::{
collections::{BTreeMap, HashMap},
fs::{File, OpenOptions},
io::BufReader,
num::NonZeroU8,
path::PathBuf,
};
use anyhow::anyhow;
use directories::ProjectDirs;
use either::Either;
use iced::{
font, subscription, theme,
widget::{
@ -713,7 +715,11 @@ where
}
}
#[derive(Serialize, Deserialize, Default, Clone)]
fn one() -> NonZeroU8 {
NonZeroU8::MIN
}
#[derive(Serialize, Deserialize, Clone)]
struct Report {
#[serde(default)]
recurring: BTreeMap<String, f64>,
@ -725,6 +731,35 @@ struct Report {
earnings_1: f64,
#[serde(default)]
earnings_2: f64,
#[serde(default = "one")]
average: NonZeroU8,
}
impl Report {
fn spendings(&self) -> f64 {
self.recurring
.values()
.copied()
.chain(
self.variable
.values()
.filter_map(|expr| calc::calc_parser::calc(expr).ok()),
)
.sum()
}
}
impl Default for Report {
fn default() -> Self {
Self {
recurring: Default::default(),
savings: Default::default(),
variable: Default::default(),
earnings_1: Default::default(),
earnings_2: Default::default(),
average: NonZeroU8::MIN,
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)]
@ -833,6 +868,7 @@ enum Message {
SetDate(ReportDate),
Load(ReportDate),
InitFrom(ReportDate),
ChangeAverage(u8),
}
struct Glaurung {
@ -849,11 +885,19 @@ struct EditState {
save_file: PathBuf,
earnings_1: f64,
earnings_2: f64,
average: NonZeroU8,
date: Option<ReportDate>,
}
impl EditState {
fn spendings(&self) -> f64 {
self.recurring
.values()
.chain(self.variable.values().flat_map(|(_, f)| f))
.sum()
}
fn load(&mut self, report: Report) {
self.recurring = report.recurring;
self.savings = report.savings;
@ -867,6 +911,7 @@ impl EditState {
.collect();
self.earnings_1 = report.earnings_1;
self.earnings_2 = report.earnings_2;
self.average = report.average;
}
fn init_from(&mut self, report: Report) {
@ -879,6 +924,7 @@ impl EditState {
.into_keys()
.map(|k| (k, Default::default()))
.collect();
self.average = report.average;
}
fn report(&self) -> Report {
@ -893,6 +939,7 @@ impl EditState {
.collect(),
earnings_1: self.earnings_1,
earnings_2: self.earnings_2,
average: self.average,
}
}
@ -1017,6 +1064,7 @@ impl Application for Glaurung {
earnings_1: Default::default(),
earnings_2: Default::default(),
save_file: config.save_file,
average: one(),
date: None,
},
archive: config.save.archive,
@ -1091,21 +1139,46 @@ impl Application for Glaurung {
Message::DeleteVariable(d) => {
self.edit.variable.remove(&d);
}
Message::ChangeAverage(a) => {
if let Some(a) = NonZeroU8::new(a) {
self.edit.average = a;
}
}
}
Command::none()
}
fn view(&self) -> Element {
let total_spendings = self
.edit
.recurring
.values()
.chain(self.edit.variable.values().flat_map(|(_, f)| f))
.sum::<f64>();
let total_spendings = self.edit.spendings();
let total_earnings = self.edit.earnings_1 + self.edit.earnings_2;
let main_account = total_spendings;
let (historic_spendings, count) = std::iter::once(total_spendings)
.chain(
match self.edit.date {
Some(date) => Either::Left(
self.archive
.iter()
.rev()
.skip_while(move |(d, _)| **d >= date),
),
None => Either::Right(self.archive.iter().rev()),
}
.scan(self.edit.date, |last, (current, report)| match last {
Some(last) if *current + 1 < *last => None,
_ => {
*last = Some(*current);
Some(report)
}
})
.map(|report| report.spendings()),
)
.take(self.edit.average.get() as _)
.fold((0., 0), |(acc, count), spending| {
(acc + spending, count + 1)
});
let main_account = historic_spendings / count as f64;
let main_factor = main_account / total_earnings;
let mut remaining_1 = self.edit.earnings_1;
@ -1233,6 +1306,15 @@ impl Application for Glaurung {
.push(button(text("Init from last month")).on_press(Message::InitFrom(last)));
}
let missing_warning = if count < self.edit.average.get() {
format!(
" (Missing {} months for average)",
self.edit.average.get() - count
)
} else {
"".into()
};
scrollable(
column![
text(date).size(TEXT_H1),
@ -1279,7 +1361,15 @@ impl Application for Glaurung {
.align_items(iced::Alignment::Center),
text(&format!("Total earnings: {total_earnings:.2}",)).size(TEXT_EMPH2),
text("Outputs").size(TEXT_H1),
text(&format!("Main account: {main_account:.2}")).size(TEXT_EMPH2),
text(&format!(
"Main account: {main_account:.2} €{missing_warning}"
))
.size(TEXT_EMPH2),
row![
text("Averaging factor"),
number_input(self.edit.average.get(), u8::MAX, Message::ChangeAverage),
]
.align_items(Alignment::Center),
component(FixedAmounts {
items: &self.edit.savings,
title: "Savings",