use std::fmt::Display;
use anyhow::Error;
use chrono::{DateTime, Months, TimeDelta, Utc};
use reqwest::Client;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use crate::app::{Fiat, money::Currency};
use super::transaction::Transaction;
pub trait Price {
async fn get_price(&self, client: &Client) -> anyhow::Result<Decimal>;
}
impl Price for Transactions<Currency> {
async fn get_price(&self, client: &Client) -> anyhow::Result<Decimal> {
match &self.currency {
Currency::Crypto(crypto) => crypto.get_price(client).await,
Currency::Fiat(_) => panic!("You can't hold a fiat currency as a secondary currency!"),
Currency::Metal(metal) => metal.get_price(client).await,
Currency::StockPlus(stock_plus) => stock_plus.get_price(client).await,
}
}
}
pub trait PriceAsTransaction: Price {
async fn get_price_as_transaction(&self) -> anyhow::Result<Transaction>;
}
impl PriceAsTransaction for Transactions<Currency> {
async fn get_price_as_transaction(&self) -> anyhow::Result<Transaction> {
let client = Client::builder()
.user_agent("Mozilla/5.0 (compatible; financial-accounts/0.2-dev; +https://github.com/dcampbell24/financial-accounts)")
.build()?;
let price = self.get_price(&client).await?;
let count = self.count();
match &self.currency {
Currency::Crypto(_) | Currency::Metal(_) | Currency::StockPlus(_) => Ok(Transaction {
amount: dec!(0),
balance: count * price,
date: Utc::now(),
comment: String::new(),
}),
Currency::Fiat(_) => unreachable!("You can't have a fiat price_as_transaction!"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Transactions<T: Clone + Display> {
pub currency: T,
pub txs: Vec<Transaction>,
}
impl<T: Clone + Display> Transactions<T> {
pub const fn new(currency: T) -> Self {
Self {
currency,
txs: Vec::new(),
}
}
pub fn balance(&self) -> Decimal {
self.txs.last().map_or_else(|| dec!(0), |tx| tx.balance)
}
pub fn balance_to_amount(&mut self, mut new_tx: Transaction) -> Transaction {
new_tx.amount = new_tx.balance;
let mut previous_tx = None;
for tx in &mut self.txs {
if tx.date <= new_tx.date {
previous_tx = Some(tx);
}
}
if let Some(tx) = previous_tx {
new_tx.amount = new_tx.balance - tx.balance;
}
for tx in &mut self.txs {
if tx.date > new_tx.date {
tx.amount = tx.balance - new_tx.balance;
break;
}
}
new_tx
}
fn count(&self) -> Decimal {
self.txs.iter().map(|tx| tx.amount).sum()
}
pub fn date_most_recent(&self, date: &DateTime<Utc>) -> anyhow::Result<()> {
for tx in &self.txs {
if &tx.date > date {
return Err(Error::msg("The date is not the most recent!"));
}
}
Ok(())
}
pub fn filter_month(&mut self, filter_date: Option<DateTime<Utc>>) {
if let Some(date) = filter_date {
let mut filtered_tx = Vec::new();
for tx in &self.txs {
if tx.date >= date && tx.date < date.checked_add_months(Months::new(1)).unwrap() {
filtered_tx.push(tx.clone());
}
}
self.txs = filtered_tx;
}
}
pub fn last_week(&self) -> Transactions<T> {
let last_week = Utc::now() - TimeDelta::weeks(1);
let mut txs = Vec::new();
for tx in &self.txs {
if tx.date >= last_week {
txs.push(tx.clone());
}
}
Transactions {
txs,
currency: self.currency.clone(),
}
}
pub fn last_month(&self) -> Transactions<T> {
let last_week = Utc::now() - TimeDelta::days(30);
let mut txs = Vec::new();
for tx in &self.txs {
if tx.date >= last_week {
txs.push(tx.clone());
}
}
Transactions {
txs,
currency: self.currency.clone(),
}
}
pub fn last_year(&self) -> Transactions<T> {
let last_week = Utc::now() - TimeDelta::days(365);
let mut txs = Vec::new();
for tx in &self.txs {
if tx.date >= last_week {
txs.push(tx.clone());
}
}
Transactions {
txs,
currency: self.currency.clone(),
}
}
pub fn max_balance(&self) -> Option<Decimal> {
self.txs.iter().map(|tx| tx.balance).max()
}
pub fn min_balance(&self) -> Option<Decimal> {
self.txs.iter().map(|tx| tx.balance).min()
}
pub fn max_date(&self) -> Option<DateTime<Utc>> {
self.txs.iter().map(|tx| tx.date).max()
}
pub fn min_date(&self) -> Option<DateTime<Utc>> {
self.txs.iter().map(|tx| tx.date).min()
}
pub fn sort(&mut self) {
self.txs.sort_by_key(|tx| tx.date);
}
pub fn total(&self) -> Decimal {
self.txs.iter().map(|d| d.amount).sum()
}
}
impl Transactions<Currency> {
pub const fn has_txs_2nd(&self) -> bool {
match self.currency {
Currency::Crypto(_) | Currency::Metal(_) | Currency::StockPlus(_) => true,
Currency::Fiat(_) => false,
}
}
}
impl Transactions<Fiat> {
pub fn remove_duplicates(&mut self, txs: &Transactions<Fiat>) {
let mut txs_new = Vec::new();
'outer: for tx in &self.txs {
for tx_2nd in &txs.txs {
if tx.date == tx_2nd.date
&& tx.amount == tx_2nd.amount
&& tx.comment == tx_2nd.comment
{
continue 'outer;
}
}
txs_new.push(tx.clone());
}
self.txs = txs_new;
}
}