use arrayvec::ArrayString;
use commodity::{Commodity, CommodityTypeID};
use nanoid::nanoid;
use rust_decimal::Decimal;
use std::rc::Rc;
#[cfg(feature = "serde-support")]
use serde::{Deserialize, Serialize};
const ACCOUNT_ID_LENGTH: usize = 20;
#[cfg_attr(feature = "serde-support", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum AccountStatus {
Open,
Closed,
}
pub type AccountID = ArrayString<[u8; ACCOUNT_ID_LENGTH]>;
pub type AccountCategory = String;
#[cfg_attr(feature = "serde-support", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct Account {
pub id: AccountID,
pub name: Option<String>,
pub commodity_type_id: CommodityTypeID,
pub category: Option<AccountCategory>,
}
impl Account {
pub fn new_with_id<S: Into<String>>(
name: Option<S>,
commodity_type_id: CommodityTypeID,
category: Option<AccountCategory>,
) -> Account {
let id_string: String = nanoid!(ACCOUNT_ID_LENGTH);
Self::new(
ArrayString::from(id_string.as_ref()).unwrap_or_else(|_| {
panic!(
"generated id string {0} should fit within ACCOUNT_ID_LENGTH: {1}",
id_string, ACCOUNT_ID_LENGTH
)
}),
name,
commodity_type_id,
category,
)
}
pub fn new<S: Into<String>>(
id: AccountID,
name: Option<S>,
commodity_type_id: CommodityTypeID,
category: Option<AccountCategory>,
) -> Account {
Account {
id,
name: name.map(|s| s.into()),
commodity_type_id,
category,
}
}
}
impl PartialEq for Account {
fn eq(&self, other: &Account) -> bool {
self.id == other.id
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AccountState {
pub account: Rc<Account>,
pub amount: Commodity,
pub status: AccountStatus,
}
impl AccountState {
pub fn new(account: Rc<Account>, amount: Commodity, status: AccountStatus) -> AccountState {
AccountState {
account,
amount,
status,
}
}
pub fn open(&mut self) {
self.status = AccountStatus::Open;
}
pub fn close(&mut self) {
self.status = AccountStatus::Closed;
}
pub fn eq_approx(&self, other: &AccountState, epsilon: Decimal) -> bool {
self.account == other.account
&& self.status == other.status
&& self.amount.eq_approx(other.amount, epsilon)
}
}
#[cfg(feature = "serde-support")]
#[cfg(test)]
mod serde_tests {
use super::Account;
use super::AccountID;
use commodity::CommodityTypeID;
use std::str::FromStr;
#[test]
fn account_serde() {
use serde_json;
let json = r#"{
"id": "ABCDEFGHIJKLMNOPQRST",
"name": "Test Account",
"commodity_type_id": "USD",
"category": "Expense"
}"#;
let account: Account = serde_json::from_str(json).unwrap();
let reference_account = Account::new(
AccountID::from("ABCDEFGHIJKLMNOPQRST").unwrap(),
Some("TestAccount"),
CommodityTypeID::from_str("AUD").unwrap(),
Some("Expense".to_string()),
);
assert_eq!(reference_account, account);
insta::assert_json_snapshot!(account);
}
}