aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2019-08-17 02:36:43 -0400
committerJesse Luehrs <doy@tozt.net>2019-08-17 02:36:43 -0400
commite801bc38e6ef8746ea61dd2bc7fce59e3204049f (patch)
tree4183b294737ad7b54a160c7c42dd27b29a2813b6
parenta494b85f2ab08c98f57c59883c07bb92e2afd193 (diff)
downloadynab-api-e801bc38e6ef8746ea61dd2bc7fce59e3204049f.tar.gz
ynab-api-e801bc38e6ef8746ea61dd2bc7fce59e3204049f.zip
add ability to actually update transactions
-rw-r--r--src/main.rs3
-rw-r--r--src/views/txn_table.rs44
-rw-r--r--src/ynab/budget.rs71
-rw-r--r--src/ynab/client.rs20
-rw-r--r--src/ynab/transaction.rs91
5 files changed, 179 insertions, 50 deletions
diff --git a/src/main.rs b/src/main.rs
index fbb078b..8ee7bc3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,7 +13,7 @@ fn main() {
.read_to_string(&mut key)
.unwrap();
let client = ynab::Client::new(&key.trim());
- let budget = client.default_budget();
+ let budget = client.into_default_budget();
checks::run_checks(&budget);
@@ -32,6 +32,7 @@ fn main() {
layout.add_child(views::txn_tables(&budget));
+ app.set_user_data(budget);
app.add_fullscreen_layer(layout);
app.run();
}
diff --git a/src/views/txn_table.rs b/src/views/txn_table.rs
index 8cc927c..007b4e2 100644
--- a/src/views/txn_table.rs
+++ b/src/views/txn_table.rs
@@ -52,7 +52,9 @@ impl cursive_table_view::TableViewItem<TxnColumn>
}
}
TxnColumn::Date => self.date.clone(),
- TxnColumn::Payee => self.payee.clone(),
+ TxnColumn::Payee => {
+ self.payee.clone().unwrap_or_else(|| "".to_string())
+ }
TxnColumn::Amount => crate::ynab::format_amount(self.amount),
TxnColumn::TotalAmount => {
if self.amount == self.total_amount {
@@ -114,33 +116,47 @@ fn txn_table(
})
.default_column(TxnColumn::Date)
.on_submit(move |s, _, _| {
- let total_outflow = s
- .call_on_id("outflows_table", |v: &mut TxnTableView| -> i64 {
+ let outflows: Vec<_> = s
+ .call_on_id("outflows_table", |v: &mut TxnTableView| {
v.borrow_items()
.iter()
.filter(|t| t.selected)
- .map(|t| t.amount)
- .sum()
+ .cloned()
+ .collect()
})
.unwrap();
- let total_inflow = s
- .call_on_id("inflows_table", |v: &mut TxnTableView| -> i64 {
+ let inflows: Vec<_> = s
+ .call_on_id("inflows_table", |v: &mut TxnTableView| {
v.borrow_items()
.iter()
.filter(|t| t.selected)
- .map(|t| t.amount)
- .sum()
+ .cloned()
+ .collect()
})
.unwrap();
+ let total_outflow: i64 = outflows.iter().map(|t| t.amount).sum();
+ let total_inflow: i64 = inflows.iter().map(|t| t.amount).sum();
let total_amount = total_outflow + total_inflow;
if total_amount == 0 {
- s.add_layer(cursive::views::Dialog::info(
- "success, sum is zero!",
- ))
+ let budget: &mut crate::ynab::Budget = s.user_data().unwrap();
+ let txns: Vec<_> =
+ outflows.iter().chain(inflows.iter()).collect();
+ let err = budget.reconcile_transactions(&txns);
+ if let Some(err) = err {
+ s.add_layer(cursive::views::Dialog::info(format!(
+ "Error: {}",
+ err
+ )))
+ } else {
+ s.add_layer(cursive::views::Dialog::info(format!(
+ "Successfully updated {} transactions",
+ txns.len()
+ )))
+ }
} else {
s.add_layer(cursive::views::Dialog::info(format!(
- "failed, sum is {}",
- total_amount
+ "Selected amount is {}, must be 0",
+ crate::ynab::format_amount(total_amount)
)))
}
})
diff --git a/src/ynab/budget.rs b/src/ynab/budget.rs
index 5d2741b..dfaaf81 100644
--- a/src/ynab/budget.rs
+++ b/src/ynab/budget.rs
@@ -1,12 +1,17 @@
pub struct Budget {
+ client: super::client::Client,
budget: ynab_api::models::BudgetDetail,
reimbursables: Vec<super::transaction::Transaction>,
}
impl Budget {
- pub fn new(budget: ynab_api::models::BudgetDetail) -> Self {
+ pub fn new(
+ client: super::client::Client,
+ budget: ynab_api::models::BudgetDetail,
+ ) -> Self {
let reimbursables = Self::get_reimbursables(&budget);
Self {
+ client,
budget,
reimbursables,
}
@@ -24,6 +29,24 @@ impl Budget {
&self.reimbursables
}
+ pub fn reconcile_transactions(
+ &self,
+ txns: &[&super::transaction::Transaction],
+ ) -> Option<String> {
+ let mut to_update =
+ ynab_api::models::UpdateTransactionsWrapper::new();
+ to_update.transactions = Some(
+ txns.iter()
+ .map(|t| {
+ let mut ut = t.to_update_transaction();
+ ut.flag_color = Some("green".to_string());
+ ut
+ })
+ .collect(),
+ );
+ self.client.update_transactions(&self.budget.id, to_update)
+ }
+
fn get_reimbursables(
budget: &ynab_api::models::BudgetDetail,
) -> Vec<super::transaction::Transaction> {
@@ -61,23 +84,14 @@ impl Budget {
let payee = t
.payee_id
.iter()
- .flat_map(|payee_id| payee_map.get(payee_id).cloned())
+ .map(|payee_id| payee_map.get(payee_id).cloned())
.next()
- .unwrap_or_else(|| "".to_string());
- let reimbursed = if let Some(color) = &t.flag_color {
- color == "green"
- } else {
- false
- };
+ .unwrap_or(None);
- reimbursables.push(super::transaction::Transaction {
- date: t.date.clone(),
- payee,
- amount: t.amount,
- total_amount: t.amount,
- reimbursed,
- selected: false,
- })
+ let mut txn =
+ super::transaction::Transaction::from_transaction(t);
+ txn.payee = payee;
+ reimbursables.push(txn);
}
}
let transaction_map = transaction_map;
@@ -96,31 +110,24 @@ impl Budget {
let payee = st
.payee_id
.iter()
- .flat_map(|payee_id| payee_map.get(payee_id).cloned())
+ .map(|payee_id| payee_map.get(payee_id).cloned())
.next()
.unwrap_or_else(|| {
t.payee_id
.iter()
- .flat_map(|payee_id| {
+ .map(|payee_id| {
payee_map.get(payee_id).cloned()
})
.next()
- .unwrap_or_else(|| "".to_string())
+ .unwrap_or(None)
});
- let reimbursed = if let Some(color) = &t.flag_color {
- color == "green"
- } else {
- false
- };
- reimbursables.push(super::transaction::Transaction {
- date: t.date.clone(),
- payee,
- amount: st.amount,
- total_amount: t.amount,
- reimbursed,
- selected: false,
- })
+ let mut txn =
+ super::transaction::Transaction::from_sub_transaction(
+ t, st,
+ );
+ txn.payee = payee;
+ reimbursables.push(txn);
}
}
} else {
diff --git a/src/ynab/client.rs b/src/ynab/client.rs
index 852481a..9467750 100644
--- a/src/ynab/client.rs
+++ b/src/ynab/client.rs
@@ -15,7 +15,7 @@ impl Client {
}
}
- pub fn default_budget(&self) -> super::budget::Budget {
+ pub fn into_default_budget(self) -> super::budget::Budget {
let budgets =
self.api.budgets_api().get_budgets().unwrap().data.budgets;
let budget = budgets.iter().next().unwrap();
@@ -26,6 +26,22 @@ impl Client {
.unwrap()
.data
.budget;
- super::budget::Budget::new(full_budget)
+ super::budget::Budget::new(self, full_budget)
+ }
+
+ pub fn update_transactions(
+ &self,
+ budget_id: &str,
+ transactions: ynab_api::models::UpdateTransactionsWrapper,
+ ) -> Option<String> {
+ let res = self
+ .api
+ .transactions_api()
+ .update_transactions(budget_id, transactions);
+ if let Err(e) = res {
+ Some(format!("{:?}", e))
+ } else {
+ None
+ }
}
}
diff --git a/src/ynab/transaction.rs b/src/ynab/transaction.rs
index 326ee83..38e5d54 100644
--- a/src/ynab/transaction.rs
+++ b/src/ynab/transaction.rs
@@ -1,9 +1,98 @@
#[derive(Clone, Debug)]
pub struct Transaction {
+ pub id: String,
pub date: String,
- pub payee: String,
pub amount: i64,
+ pub memo: Option<String>,
+ pub cleared: String,
+ pub approved: bool,
+ pub flag_color: Option<String>,
+ pub account_id: String,
+ pub payee_id: Option<String>,
+ pub category_id: Option<String>,
+ pub import_id: Option<String>,
+
+ pub payee: Option<String>,
pub total_amount: i64,
pub reimbursed: bool,
pub selected: bool,
}
+
+impl Transaction {
+ pub fn from_transaction(
+ t: &ynab_api::models::TransactionSummary,
+ ) -> Self {
+ let reimbursed = if let Some(color) = &t.flag_color {
+ color == "green"
+ } else {
+ false
+ };
+ Self {
+ id: t.id.clone(),
+ date: t.date.clone(),
+ amount: t.amount,
+ memo: t.memo.clone(),
+ cleared: t.cleared.clone(),
+ approved: t.approved,
+ flag_color: t.flag_color.clone(),
+ account_id: t.account_id.clone(),
+ payee_id: t.payee_id.clone(),
+ category_id: t.category_id.clone(),
+ import_id: t.import_id.clone(),
+
+ payee: None,
+ total_amount: t.amount,
+ reimbursed,
+ selected: false,
+ }
+ }
+
+ pub fn from_sub_transaction(
+ t: &ynab_api::models::TransactionSummary,
+ st: &ynab_api::models::SubTransaction,
+ ) -> Self {
+ let reimbursed = if let Some(color) = &t.flag_color {
+ color == "green"
+ } else {
+ false
+ };
+ Self {
+ id: t.id.clone(),
+ date: t.date.clone(),
+ amount: st.amount,
+ memo: t.memo.clone(),
+ cleared: t.cleared.clone(),
+ approved: t.approved,
+ flag_color: t.flag_color.clone(),
+ account_id: t.account_id.clone(),
+ payee_id: t.payee_id.clone(),
+ category_id: t.category_id.clone(),
+ import_id: t.import_id.clone(),
+
+ payee: None,
+ total_amount: t.amount,
+ reimbursed,
+ selected: false,
+ }
+ }
+
+ pub fn to_update_transaction(
+ &self,
+ ) -> ynab_api::models::UpdateTransaction {
+ let mut ut = ynab_api::models::UpdateTransaction::new(
+ self.account_id.clone(),
+ self.date.clone(),
+ self.amount,
+ );
+ ut.id = Some(self.id.clone());
+ ut.payee_id = self.payee_id.clone();
+ ut.category_id = self.category_id.clone();
+ ut.memo = self.memo.clone();
+ ut.cleared = Some(self.cleared.clone());
+ ut.approved = Some(self.approved);
+ ut.flag_color = self.flag_color.clone();
+ ut.import_id = self.import_id.clone();
+
+ ut
+ }
+}