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