1use arrayvec::ArrayString;
2use commodity::{Commodity, CommodityTypeID};
3use nanoid::nanoid;
4use rust_decimal::Decimal;
5use std::rc::Rc;
6
7#[cfg(feature = "serde-support")]
8use serde::{Deserialize, Serialize};
9
10const ACCOUNT_ID_LENGTH: usize = 20;
12
13#[cfg_attr(feature = "serde-support", derive(Serialize, Deserialize))]
15#[derive(Copy, Clone, Debug, PartialEq)]
16pub enum AccountStatus {
17 Open,
19 Closed,
21}
22pub type AccountID = ArrayString<[u8; ACCOUNT_ID_LENGTH]>;
24
25pub type AccountCategory = String;
27
28#[cfg_attr(feature = "serde-support", derive(Serialize, Deserialize))]
31#[derive(Debug, Clone)]
32pub struct Account {
33 pub id: AccountID,
35
36 pub name: Option<String>,
38
39 pub commodity_type_id: CommodityTypeID,
41
42 pub category: Option<AccountCategory>,
44}
45
46impl Account {
47 pub fn new_with_id<S: Into<String>>(
51 name: Option<S>,
52 commodity_type_id: CommodityTypeID,
53 category: Option<AccountCategory>,
54 ) -> Account {
55 let id_string: String = nanoid!(ACCOUNT_ID_LENGTH);
56 Self::new(
57 ArrayString::from(id_string.as_ref()).unwrap_or_else(|_| {
58 panic!(
59 "generated id string {0} should fit within ACCOUNT_ID_LENGTH: {1}",
60 id_string, ACCOUNT_ID_LENGTH
61 )
62 }),
63 name,
64 commodity_type_id,
65 category,
66 )
67 }
68
69 pub fn new<S: Into<String>>(
72 id: AccountID,
73 name: Option<S>,
74 commodity_type_id: CommodityTypeID,
75 category: Option<AccountCategory>,
76 ) -> Account {
77 Account {
78 id,
79 name: name.map(|s| s.into()),
80 commodity_type_id,
81 category,
82 }
83 }
84}
85
86impl PartialEq for Account {
87 fn eq(&self, other: &Account) -> bool {
88 self.id == other.id
89 }
90}
91
92#[derive(Debug, Clone, PartialEq)]
94pub struct AccountState {
95 pub account: Rc<Account>,
97
98 pub amount: Commodity,
100
101 pub status: AccountStatus,
103}
104
105impl AccountState {
106 pub fn new(account: Rc<Account>, amount: Commodity, status: AccountStatus) -> AccountState {
108 AccountState {
109 account,
110 amount,
111 status,
112 }
113 }
114
115 pub fn open(&mut self) {
117 self.status = AccountStatus::Open;
118 }
119
120 pub fn close(&mut self) {
122 self.status = AccountStatus::Closed;
123 }
124
125 pub fn eq_approx(&self, other: &AccountState, epsilon: Decimal) -> bool {
126 self.account == other.account
127 && self.status == other.status
128 && self.amount.eq_approx(other.amount, epsilon)
129 }
130}
131
132#[cfg(feature = "serde-support")]
133#[cfg(test)]
134mod serde_tests {
135 use super::Account;
136 use super::AccountID;
137 use commodity::CommodityTypeID;
138 use std::str::FromStr;
139
140 #[test]
141 fn account_serde() {
142 use serde_json;
143
144 let json = r#"{
145 "id": "ABCDEFGHIJKLMNOPQRST",
146 "name": "Test Account",
147 "commodity_type_id": "USD",
148 "category": "Expense"
149}"#;
150
151 let account: Account = serde_json::from_str(json).unwrap();
152
153 let reference_account = Account::new(
154 AccountID::from("ABCDEFGHIJKLMNOPQRST").unwrap(),
155 Some("TestAccount"),
156 CommodityTypeID::from_str("AUD").unwrap(),
157 Some("Expense".to_string()),
158 );
159
160 assert_eq!(reference_account, account);
161 insta::assert_json_snapshot!(account);
162 }
163}