1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! data contains fundamental types used in Ledger data.
//! Note the structure is quite similar to repl module,
//! however, repl is for textual representation while
//! data is more for understanding.

use std::cmp::Ordering;

use rust_decimal::Decimal;

/// Represents a transaction where the money transfered across the accounts.
#[derive(Debug, PartialEq, Eq)]
pub struct Transaction {
    /// Date when the transaction issued.
    pub date: chrono::NaiveDate,
    /// Date when the transaction got effective, optional.
    pub effective_date: Option<chrono::NaiveDate>,
    /// Indiacates clearing state of the entire transaction.
    pub clear_state: ClearState,
    /// Transaction code (not necessarily unique).
    pub code: Option<String>,
    /// Label of the transaction, often the opposite party of the transaction.
    pub payee: String,
    /// Postings of the transaction, could be empty.
    pub posts: Vec<Posting>,
}

impl Transaction {
    /// Constructs minimal transaction.
    pub fn new(date: chrono::NaiveDate, payee: String) -> Transaction {
        Transaction {
            date,
            effective_date: None,
            clear_state: ClearState::Uncleared,
            code: None,
            payee,
            posts: Vec::new(),
        }
    }
}

/// Represents a clearing state, often combined with the ambiguity.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum ClearState {
    /// No specific meaning.
    #[default]
    Uncleared,
    /// Useful to declare that the transaction / post is confirmed.
    Cleared,
    /// Useful to declare that the transaction / post is still pending.
    Pending,
}

/// Posting in a transaction to represent a particular account amount increase / decrease.
#[derive(Debug, PartialEq, Eq)]
pub struct Posting {
    /// Account of the post target.
    pub account: String,
    /// Posting specific ClearState.
    pub clear_state: ClearState,
    /// Amount of the posting.
    pub amount: Option<ExchangedAmount>,
    /// Balance after the transaction of the specified account.
    pub balance: Option<Amount>,
    /// Overwrites the payee.
    pub payee: Option<String>,
}

/// Cost of the posting.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ExchangedAmount {
    /// Amount that posting account was increased by.
    pub amount: Amount,
    /// Exchange rate information to balance with other postings.
    pub exchange: Option<Exchange>,
}

/// Commodity exchange information.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Exchange {
    /// Represents the amount equals to the `ExchangedAmount.amount`.
    Total(Amount),
    /// Represents te amount equals to 1 `ExchangedAmount.amount.commodity`.
    Rate(Amount),
}

/// Amount of posting, balance, ...
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Amount {
    /// Numerical value.
    pub value: Decimal,
    /// Commodity aka currency.
    pub commodity: String,
}

impl Amount {
    /// Returns `true` if the amount is zero.
    pub fn is_zero(&self) -> bool {
        self.value.is_zero()
    }

    /// Returns `true` if the amount is positive.
    pub fn is_sign_positive(&self) -> bool {
        self.value.is_sign_positive()
    }

    /// Returns `true` if the amount is negative.
    pub fn is_sign_negative(&self) -> bool {
        self.value.is_sign_negative()
    }
}

/// # Examples
///
/// ```
/// # use rust_decimal_macros::dec;
/// let x = okane_core::datamodel::Amount{
///     value: dec!(-5),
///     commodity: "JPY".to_string(),
/// };
/// let y = -x.clone();
/// assert_eq!(x.value, dec!(-5));
/// assert_eq!(x.commodity, "JPY");
/// assert_eq!(y.value, dec!(5));
/// assert_eq!(y.commodity, "JPY");
/// ```
impl std::ops::Neg for Amount {
    type Output = Amount;
    fn neg(self) -> Amount {
        Amount {
            value: -self.value,
            commodity: self.commodity,
        }
    }
}

impl PartialOrd for Amount {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        if self.commodity != other.commodity {
            None
        } else {
            Some(self.value.cmp(&other.value))
        }
    }
}