mbl 0.1.7

The finantial part based on rust_decimal crate
Documentation
// By KNB, 2025
// Complete mbl crate based on rust_decimal::Decimal type.
// Basic arithmetic operations should be implemented in this crate.
// Most interesting part is how you will store accounts and, that's more
// important, transactions. Here are some ideas: postgres, sqlite, mysql,
// for home accounting json will enough. But even for home accounting critical
// or personal data should be encrypted.

// Should I share some ideas about how to store data in json?
// Should I share some ideas about how to store data in postgres?
// Problems to solve:
// 1. How to assign id to account?
// 2. How to assign id to transaction?
// 3. How to manage users?
// 4. How to protect data?
// 5. How to store data?
// 6. How to manage data?
// 7. How to deploy double entry accounting?
// 8. How to deploy double entry accounting with tags?
// 9. How to sold that software? Or how to get money for it?
// 10. Where to get time for development?

use rust_decimal;
use rust_decimal::prelude::*;

pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

/// Watch out for how you funded, accept only positive values
pub fn fund(howmuch: rust_decimal::Decimal) -> bool {
    howmuch > dec!(0.0)
}

/// Account with balance of rust_decimal type, name and id
pub struct Account {
    pub name: String,
    pub balance: rust_decimal::Decimal,
    pub id: String,
}

/// Transaction with amount, description, account, date, category, tags, from
pub struct Transaction {
    pub txid: String,
    pub amount: rust_decimal::Decimal,
    pub description: String,
    pub account: Account,
    pub date: String,
    pub category: String,
    pub tags: Vec<String>,
    pub from: Account,
    // todo: add to and from
    // todo: add transaction id
    // todo: implement double entry accounting
    // todo: implement fn to_json
    // todo: implement fn from_json
    // todo: implement accounts change
    // todo: implement one to many transactions
    // todo: implement many to many and many to one transactions
}

/// Implement Account functionality
impl Account {
    pub fn new(name: String, balance: rust_decimal::Decimal, id: String) -> Account {
        Account { name, balance, id }
    }

    /// deposit amount to account, amount should be positive
    /// if amount is negative, nothing will happen
    pub fn deposit(&mut self, amount: rust_decimal::Decimal) {
        if amount > dec!(0.0) {
            self.balance += amount;
        }
    }

    /// Withdraw amount from account, balance should be positive after withdraw
    /// if amount is negative, nothing will happen
    /// if amount is positive and balance is less than amount, nothing will happen
    pub fn withdraw(&mut self, amount: rust_decimal::Decimal) {
        if amount > dec!(0.0) && self.balance >= amount{
            self.balance -= amount;
        }
    }
}

/// Implement Transaction functionality
impl Transaction {
    pub fn new(
        txid: String,
        amount: rust_decimal::Decimal,
        description: String,
        account: Account,
        date: String,
        category: String,
        tags: Vec<String>,
        from: Account,
    ) -> Transaction {
        Transaction {
            txid,
            amount,
            description,
            account,
            date,
            category,
            tags,
            from,
        }
    }
    /* pub fn new_from_json(json: String) -> Transaction {
        Transaction {
            txid: "".to_string(),
            amount: dec!(0.0),
            description: "".to_string(),
            account: Account {
                name: "".to_string(),
                balance: dec!(0.0),
                id: "".to_string(),
            },
            date: "".to_string(),
            category: "".to_string(),
            tags: Vec::new(),
            from: Account {
                name: "".to_string(),
                balance: dec!(0.0),
                id: "".to_string(),
            },
        }
    } */
    // solve: how to execute transaction and change accounts
    
    /// Execute transaction, no accounts change actually for while
    pub fn execute(&mut self) {
        if self.amount > dec!(0.0) {
            self.account.deposit(self.amount);
            self.from.withdraw(self.amount);
        }
    }

    pub fn revert(&mut self) {
        if self.amount > dec!(0.0) {
            self.account.withdraw(self.amount);
            self.from.deposit(self.amount);
        }
    }
}
pub struct Mbl {
    pub accounts: Vec<Account>,
    pub transactions: Vec<Transaction>,
    pub tags: Vec<String>,
    pub categories: Vec<String>,
    pub users: Vec<String>,
    pub settings: Vec<String>,
}

impl Mbl {
    pub fn new(
        accounts: Vec<Account>,
        transactions: Vec<Transaction>,
        tags: Vec<String>,
        categories: Vec<String>,
        users: Vec<String>,
        settings: Vec<String>,
    ) -> Self {
        Self {
            accounts,
            transactions,
            tags,
            categories,
            users,
            settings,
        }
    }
    pub fn add_account(&mut self, account: Account) {
        self.accounts.push(account);
    }

    // solve: how to execute transaction and change accounts
    // solve: how to revert transaction and change accounts
    // solve: how to avoid double transaction execution
    // solve: how to avoid double transaction reversion

    pub fn execute(&mut self) {
        for t in self.transactions.iter_mut() {
            if t.amount > dec!(0.0) {
                t.execute();
                //let mut a = self.get_account_by_id(t.account.id.clone());
                //a.balance = t.account.balance;
            }
        }
    }

    pub fn add_transaction(&mut self, transaction: Transaction) {
        if transaction.amount > dec!(0.0) {
            self.transactions.push(transaction);
        }
    }

    pub fn get_account_by_id(&self, id: String) -> Account {
        let mut res: Account = Account {
            name: ("first".to_string()),
            balance: (dec!(0.0)),
            id: ("1".to_string()),
        };
        let mut found = false;
        for account in self.accounts.iter() {
            if account.id == id {
                found = true;
                //println!("{}", account.name);
                //println!("{}", account.balance);
                //println!("{}", account.id);
                res = account.clone();
                //return account.clone();
            }
        }
        if !found {
            panic!("No such account")
        }
        return res;
    }
}

impl Clone for Account {
    fn clone(&self) -> Self {
        Self {
            name: (self.name.clone()),
            balance: (self.balance),
            id: (self.id.clone()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }

    #[test]
    fn test_account() {
        {
            let mut account = Account::new("first".to_string(), dec!(0.0), "1".to_string());
            assert_eq!(account.name, "first");
            assert_eq!(account.balance, dec!(0.0));
            assert_eq!(account.id, "1");
            account.deposit(dec!(10.0));
            assert_eq!(account.balance, dec!(10.0));
            // negative balance not allowed during withdraw
            account.withdraw(dec!(11.0));
            assert_eq!(account.balance, dec!(10.0));
            account.withdraw(dec!(10.0));
            assert_eq!(account.balance, dec!(0.0));
        }
    }
    #[test]
    fn test_transaction() {
        {
            let account = Account::new("first".to_string(), dec!(20.0), "1".to_string());
            let account2 = Account::new("second".to_string(), dec!(20.0), "2".to_string());
            let mut transaction = Transaction::new(
                "1".to_string(),
                dec!(10.0),
                "buy".to_string(),
                account,
                "2025-01-01".to_string(),
                "food".to_string(),
                vec!["food".to_string()],
                account2,
            );
            transaction.execute();
            assert_eq!(transaction.account.balance, dec!(30.0));
        }
    }
}