Allow to average over severall months
This commit is contained in:
parent
cc6dc16380
commit
2a74d250a0
3 changed files with 101 additions and 9 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -718,6 +718,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"directories",
|
"directories",
|
||||||
|
"either",
|
||||||
"iced",
|
"iced",
|
||||||
"iced_aw",
|
"iced_aw",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
|
either = "1.9.0"
|
||||||
iced = { version = "0.10.0", features = ["lazy"] }
|
iced = { version = "0.10.0", features = ["lazy"] }
|
||||||
iced_aw = { version = "0.7.0", default-features = false, features = ["modal", "card", "number_input", "selection_list"] }
|
iced_aw = { version = "0.7.0", default-features = false, features = ["modal", "card", "number_input", "selection_list"] }
|
||||||
itertools = "0.11.0"
|
itertools = "0.11.0"
|
||||||
|
|
|
||||||
108
src/main.rs
108
src/main.rs
|
|
@ -2,11 +2,13 @@ use std::{
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
io::BufReader,
|
io::BufReader,
|
||||||
|
num::NonZeroU8,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
|
use either::Either;
|
||||||
use iced::{
|
use iced::{
|
||||||
font, subscription, theme,
|
font, subscription, theme,
|
||||||
widget::{
|
widget::{
|
||||||
|
|
@ -713,7 +715,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
fn one() -> NonZeroU8 {
|
||||||
|
NonZeroU8::MIN
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
struct Report {
|
struct Report {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
recurring: BTreeMap<String, f64>,
|
recurring: BTreeMap<String, f64>,
|
||||||
|
|
@ -725,6 +731,35 @@ struct Report {
|
||||||
earnings_1: f64,
|
earnings_1: f64,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
earnings_2: f64,
|
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)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)]
|
||||||
|
|
@ -833,6 +868,7 @@ enum Message {
|
||||||
SetDate(ReportDate),
|
SetDate(ReportDate),
|
||||||
Load(ReportDate),
|
Load(ReportDate),
|
||||||
InitFrom(ReportDate),
|
InitFrom(ReportDate),
|
||||||
|
ChangeAverage(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Glaurung {
|
struct Glaurung {
|
||||||
|
|
@ -849,11 +885,19 @@ struct EditState {
|
||||||
save_file: PathBuf,
|
save_file: PathBuf,
|
||||||
earnings_1: f64,
|
earnings_1: f64,
|
||||||
earnings_2: f64,
|
earnings_2: f64,
|
||||||
|
average: NonZeroU8,
|
||||||
|
|
||||||
date: Option<ReportDate>,
|
date: Option<ReportDate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditState {
|
impl EditState {
|
||||||
|
fn spendings(&self) -> f64 {
|
||||||
|
self.recurring
|
||||||
|
.values()
|
||||||
|
.chain(self.variable.values().flat_map(|(_, f)| f))
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
fn load(&mut self, report: Report) {
|
fn load(&mut self, report: Report) {
|
||||||
self.recurring = report.recurring;
|
self.recurring = report.recurring;
|
||||||
self.savings = report.savings;
|
self.savings = report.savings;
|
||||||
|
|
@ -867,6 +911,7 @@ impl EditState {
|
||||||
.collect();
|
.collect();
|
||||||
self.earnings_1 = report.earnings_1;
|
self.earnings_1 = report.earnings_1;
|
||||||
self.earnings_2 = report.earnings_2;
|
self.earnings_2 = report.earnings_2;
|
||||||
|
self.average = report.average;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_from(&mut self, report: Report) {
|
fn init_from(&mut self, report: Report) {
|
||||||
|
|
@ -879,6 +924,7 @@ impl EditState {
|
||||||
.into_keys()
|
.into_keys()
|
||||||
.map(|k| (k, Default::default()))
|
.map(|k| (k, Default::default()))
|
||||||
.collect();
|
.collect();
|
||||||
|
self.average = report.average;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report(&self) -> Report {
|
fn report(&self) -> Report {
|
||||||
|
|
@ -893,6 +939,7 @@ impl EditState {
|
||||||
.collect(),
|
.collect(),
|
||||||
earnings_1: self.earnings_1,
|
earnings_1: self.earnings_1,
|
||||||
earnings_2: self.earnings_2,
|
earnings_2: self.earnings_2,
|
||||||
|
average: self.average,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1017,6 +1064,7 @@ impl Application for Glaurung {
|
||||||
earnings_1: Default::default(),
|
earnings_1: Default::default(),
|
||||||
earnings_2: Default::default(),
|
earnings_2: Default::default(),
|
||||||
save_file: config.save_file,
|
save_file: config.save_file,
|
||||||
|
average: one(),
|
||||||
date: None,
|
date: None,
|
||||||
},
|
},
|
||||||
archive: config.save.archive,
|
archive: config.save.archive,
|
||||||
|
|
@ -1091,21 +1139,46 @@ impl Application for Glaurung {
|
||||||
Message::DeleteVariable(d) => {
|
Message::DeleteVariable(d) => {
|
||||||
self.edit.variable.remove(&d);
|
self.edit.variable.remove(&d);
|
||||||
}
|
}
|
||||||
|
Message::ChangeAverage(a) => {
|
||||||
|
if let Some(a) = NonZeroU8::new(a) {
|
||||||
|
self.edit.average = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element {
|
fn view(&self) -> Element {
|
||||||
let total_spendings = self
|
let total_spendings = self.edit.spendings();
|
||||||
.edit
|
|
||||||
.recurring
|
|
||||||
.values()
|
|
||||||
.chain(self.edit.variable.values().flat_map(|(_, f)| f))
|
|
||||||
.sum::<f64>();
|
|
||||||
let total_earnings = self.edit.earnings_1 + self.edit.earnings_2;
|
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 main_factor = main_account / total_earnings;
|
||||||
|
|
||||||
let mut remaining_1 = self.edit.earnings_1;
|
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)));
|
.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(
|
scrollable(
|
||||||
column![
|
column![
|
||||||
text(date).size(TEXT_H1),
|
text(date).size(TEXT_H1),
|
||||||
|
|
@ -1279,7 +1361,15 @@ impl Application for Glaurung {
|
||||||
.align_items(iced::Alignment::Center),
|
.align_items(iced::Alignment::Center),
|
||||||
text(&format!("Total earnings: {total_earnings:.2} €",)).size(TEXT_EMPH2),
|
text(&format!("Total earnings: {total_earnings:.2} €",)).size(TEXT_EMPH2),
|
||||||
text("Outputs").size(TEXT_H1),
|
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 {
|
component(FixedAmounts {
|
||||||
items: &self.edit.savings,
|
items: &self.edit.savings,
|
||||||
title: "Savings",
|
title: "Savings",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue