1 {-# LANGUAGE DeriveDataTypeable #-} 2 {-| 3 4 Most data types are defined here to avoid import cycles. 5 Here is an overview of the hledger data model: 6 7 > Journal -- a journal is read from one or more data files. It contains.. 8 > [Transaction] -- journal transactions (aka entries), which have date, status, code, description and.. 9 > [Posting] -- multiple account postings, which have account name and amount 10 > [HistoricalPrice] -- historical commodity prices 11 > 12 > Ledger -- a ledger is derived from a journal, by applying a filter specification and doing some further processing. It contains.. 13 > Journal -- a filtered copy of the original journal, containing only the transactions and postings we are interested in 14 > Tree AccountName -- all accounts named by the journal's transactions, as a hierarchy 15 > Map AccountName Account -- the postings, and resulting balances, in each account 16 17 For more detailed documentation on each type, see the corresponding modules. 18 19 Evolution of transaction\/entry\/posting terminology: 20 21 - ledger 2: entries contain transactions 22 23 - hledger 0.4: Entrys contain RawTransactions (which are flattened to Transactions) 24 25 - ledger 3: transactions contain postings 26 27 - hledger 0.5: LedgerTransactions contain Postings (which are flattened to Transactions) 28 29 - hledger 0.8: Transactions contain Postings (referencing Transactions..) 30 31 -} 32 33 module Hledger.Data.Types 34 where 35 import Control.Monad.Error (ErrorT) 36 import Data.Time.Calendar 37 import Data.Time.LocalTime 38 import Data.Tree 39 import Data.Typeable 40 import qualified Data.Map as Map 41 import System.Time (ClockTime) 42 43 44 type SmartDate = (String,String,String) 45 46 data WhichDate = ActualDate | EffectiveDate deriving (Eq,Show) 47 48 data DateSpan = DateSpan (Maybe Day) (Maybe Day) deriving (Eq,Show,Ord) 49 50 data Interval = NoInterval 51 | Days Int | Weeks Int | Months Int | Quarters Int | Years Int 52 | DayOfMonth Int | DayOfWeek Int 53 -- WeekOfYear Int | MonthOfYear Int | QuarterOfYear Int 54 deriving (Eq,Show,Ord) 55 56 type AccountName = String 57 58 data Side = L | R deriving (Eq,Show,Read,Ord) 59 60 data Commodity = Commodity { 61 symbol :: String, -- ^ the commodity's symbol 62 -- display preferences for amounts of this commodity 63 side :: Side, -- ^ should the symbol appear on the left or the right 64 spaced :: Bool, -- ^ should there be a space between symbol and quantity 65 precision :: Int, -- ^ number of decimal places to display 66 -- XXX these three might be better belonging to Journal 67 decimalpoint :: Char, -- ^ character to use as decimal point 68 separator :: Char, -- ^ character to use for separating digit groups (eg thousands) 69 separatorpositions :: [Int] -- ^ positions of separators, counting leftward from decimal point 70 } deriving (Eq,Ord,Show,Read) 71 72 -- | An amount's price in another commodity may be written as \@ unit 73 -- price or \@\@ total price. Note although a MixedAmount is used, it 74 -- should be in a single commodity, also the amount should be positive; 75 -- these are not enforced currently. 76 data Price = UnitPrice MixedAmount | TotalPrice MixedAmount 77 deriving (Eq,Ord) 78 79 data Amount = Amount { 80 commodity :: Commodity, 81 quantity :: Double, 82 price :: Maybe Price -- ^ the price for this amount at posting time 83 } deriving (Eq,Ord) 84 85 newtype MixedAmount = Mixed [Amount] deriving (Eq,Ord) 86 87 data PostingType = RegularPosting | VirtualPosting | BalancedVirtualPosting 88 deriving (Eq,Show) 89 90 data Posting = Posting { 91 pstatus :: Bool, 92 paccount :: AccountName, 93 pamount :: MixedAmount, 94 pcomment :: String, 95 ptype :: PostingType, 96 pmetadata :: [(String,String)], 97 ptransaction :: Maybe Transaction -- ^ this posting's parent transaction (co-recursive types). 98 -- Tying this knot gets tedious, Maybe makes it easier/optional. 99 } 100 101 -- The equality test for postings ignores the parent transaction's 102 -- identity, to avoid infinite loops. 103 instance Eq Posting where 104 (==) (Posting a1 b1 c1 d1 e1 f1 _) (Posting a2 b2 c2 d2 e2 f2 _) = a1==a2 && b1==b2 && c1==c2 && d1==d2 && e1==e2 && f1==f2 105 106 data Transaction = Transaction { 107 tdate :: Day, 108 teffectivedate :: Maybe Day, 109 tstatus :: Bool, -- XXX tcleared ? 110 tcode :: String, 111 tdescription :: String, 112 tcomment :: String, 113 tmetadata :: [(String,String)], 114 tpostings :: [Posting], -- ^ this transaction's postings (co-recursive types). 115 tpreceding_comment_lines :: String 116 } deriving (Eq) 117 118 data ModifierTransaction = ModifierTransaction { 119 mtvalueexpr :: String, 120 mtpostings :: [Posting] 121 } deriving (Eq) 122 123 data PeriodicTransaction = PeriodicTransaction { 124 ptperiodicexpr :: String, 125 ptpostings :: [Posting] 126 } deriving (Eq) 127 128 data TimeLogCode = SetBalance | SetRequiredHours | In | Out | FinalOut deriving (Eq,Ord) 129 130 data TimeLogEntry = TimeLogEntry { 131 tlcode :: TimeLogCode, 132 tldatetime :: LocalTime, 133 tlcomment :: String 134 } deriving (Eq,Ord) 135 136 data HistoricalPrice = HistoricalPrice { 137 hdate :: Day, 138 hsymbol :: String, 139 hamount :: MixedAmount 140 } deriving (Eq) -- & Show (in Amount.hs) 141 142 type Year = Integer 143 144 -- | A journal "context" is some data which can change in the course of 145 -- parsing a journal. An example is the default year, which changes when a 146 -- Y directive is encountered. At the end of parsing, the final context 147 -- is saved for later use by eg the add command. 148 data JournalContext = Ctx { 149 ctxYear :: !(Maybe Year) -- ^ the default year most recently specified with Y 150 , ctxCommodity :: !(Maybe Commodity) -- ^ the default commodity most recently specified with D 151 , ctxAccount :: ![AccountName] -- ^ the current stack of parent accounts/account name components 152 -- specified with "account" directive(s). Concatenated, these 153 -- are the account prefix prepended to parsed account names. 154 , ctxAliases :: ![(AccountName,AccountName)] -- ^ the current list of account name aliases in effect 155 } deriving (Read, Show, Eq) 156 157 data Journal = Journal { 158 jmodifiertxns :: [ModifierTransaction], 159 jperiodictxns :: [PeriodicTransaction], 160 jtxns :: [Transaction], 161 open_timelog_entries :: [TimeLogEntry], 162 historical_prices :: [HistoricalPrice], 163 final_comment_lines :: String, -- ^ any trailing comments from the journal file 164 jContext :: JournalContext, -- ^ the context (parse state) at the end of parsing 165 files :: [(FilePath, String)], -- ^ the file path and raw text of the main and 166 -- any included journal files. The main file is 167 -- first followed by any included files in the 168 -- order encountered. 169 filereadtime :: ClockTime -- ^ when this journal was last read from its file(s) 170 } deriving (Eq, Typeable) 171 172 -- | A JournalUpdate is some transformation of a Journal. It can do I/O or 173 -- raise an error. 174 type JournalUpdate = ErrorT String IO (Journal -> Journal) 175 176 -- | A hledger journal reader is a triple of format name, format-detecting 177 -- predicate, and a parser to Journal. 178 data Reader = Reader {rFormat :: String 179 ,rDetector :: FilePath -> String -> Bool 180 ,rParser :: FilePath -> String -> ErrorT String IO Journal 181 } 182 183 data Ledger = Ledger { 184 journal :: Journal, 185 accountnametree :: Tree AccountName, 186 accountmap :: Map.Map AccountName Account 187 } 188 189 data Account = Account { 190 aname :: AccountName, 191 apostings :: [Posting], -- ^ postings in this account 192 abalance :: MixedAmount -- ^ sum of postings in this account and subaccounts 193 } 194 195 -- | A generic, pure specification of how to filter (or search) transactions and postings. 196 data FilterSpec = FilterSpec { 197 datespan :: DateSpan -- ^ only include if in this date span 198 ,cleared :: Maybe Bool -- ^ only include if cleared\/uncleared\/don't care 199 ,real :: Bool -- ^ only include if real\/don't care 200 ,empty :: Bool -- ^ include if empty (ie amount is zero) 201 ,acctpats :: [String] -- ^ only include if matching these account patterns 202 ,descpats :: [String] -- ^ only include if matching these description patterns 203 ,depth :: Maybe Int 204 } deriving (Show) 205