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 = [
|
||||
"anyhow",
|
||||
"directories",
|
||||
"either",
|
||||
"iced",
|
||||
"iced_aw",
|
||||
"itertools",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
108
src/main.rs
108
src/main.rs
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue