1 {-# LANGUAGE DeriveDataTypeable #-}
 
   2 {-# LANGUAGE FlexibleInstances #-}
 
   3 {-# LANGUAGE NamedFieldPuns #-}
 
   4 {-# LANGUAGE OverloadedStrings #-}
 
   5 {-# LANGUAGE TypeFamilies #-}
 
   6 module Hcompta.Format.Ledger where
 
   8 import           Data.Data (Data(..))
 
   9 import           Data.Functor.Compose (Compose(..))
 
  10 import qualified Data.List as Data.List
 
  11 import           Data.Map.Strict (Map)
 
  12 import qualified Data.Map.Strict as Data.Map
 
  13 import           Data.Text (Text)
 
  14 import qualified Data.Time.Clock       as Time
 
  15 import qualified Data.Time.Clock.POSIX as Time
 
  16 import           Data.Typeable (Typeable)
 
  17 import           Text.Parsec.Pos (SourcePos, initialPos)
 
  19 import           Hcompta.Account (Account)
 
  20 import           Hcompta.Amount (Amount)
 
  21 import qualified Hcompta.Amount as Amount
 
  22 import qualified Hcompta.Balance as Balance
 
  23 import           Hcompta.Date (Date)
 
  24 import qualified Hcompta.Date as Date
 
  25 import qualified Hcompta.Filter as Filter
 
  26 import           Hcompta.Lib.Parsec ()
 
  27 import qualified Hcompta.GL as GL
 
  30 type Description = Text
 
  34 -- * The 'Journal' type
 
  38  { journal_file           :: FilePath
 
  39  , journal_includes       :: [Journal]
 
  40  , journal_last_read_time :: Time.UTCTime
 
  41  , journal_transactions   :: Transaction_by_Date
 
  42  , journal_unit_styles    :: Data.Map.Map Amount.Unit Amount.Style
 
  43  } deriving (Data, Eq, Show, Typeable)
 
  49          , journal_includes = []
 
  50          , journal_last_read_time = Time.posixSecondsToUTCTime 0
 
  51          , journal_transactions = Data.Map.empty
 
  52          , journal_unit_styles = Data.Map.empty
 
  55 -- * The 'Transaction' type
 
  59  { transaction_code                      :: Code
 
  60  , transaction_comments_before           :: [Comment]
 
  61  , transaction_comments_after            :: [Comment]
 
  62  , transaction_dates                     :: (Date, [Date])
 
  63  , transaction_description               :: Description
 
  64  , transaction_postings                  :: Posting_by_Account
 
  65  , transaction_virtual_postings          :: Posting_by_Account
 
  66  , transaction_balanced_virtual_postings :: Posting_by_Account
 
  67  , transaction_sourcepos                 :: SourcePos
 
  68  , transaction_status                    :: Status
 
  69  , transaction_tags                      :: Tag_by_Name
 
  70  } deriving (Data, Eq, Show, Typeable)
 
  72 transaction :: Transaction
 
  75          { transaction_code = ""
 
  76          , transaction_comments_before = []
 
  77          , transaction_comments_after = []
 
  78          , transaction_dates = (Date.nil, [])
 
  79          , transaction_description = ""
 
  80          , transaction_postings = Data.Map.empty
 
  81          , transaction_virtual_postings = Data.Map.empty
 
  82          , transaction_balanced_virtual_postings = Data.Map.empty
 
  83          , transaction_sourcepos = initialPos ""
 
  84          , transaction_status = False
 
  85          , transaction_tags = Data.Map.empty
 
  88 instance Filter.Transaction Transaction where
 
  89         type Transaction_Posting Transaction = Posting
 
  90         transaction_date        = fst . transaction_dates
 
  91         transaction_description = transaction_description
 
  92         transaction_postings    = transaction_postings
 
  93         transaction_tags        = transaction_tags
 
  96 instance Filter.GL (GL.GL_Line Transaction) where
 
  97         type GL_Amount (GL.GL_Line Transaction) = Amount
 
  98         register_account         = GL.posting_account  . GL.register_line_posting
 
  99         register_date            = GL.transaction_date . GL.register_line_transaction
 
 100         register_amount_positive = Amount.sum_positive . GL.posting_amount . GL.register_line_posting
 
 101         register_amount_negative = Amount.sum_negative . GL.posting_amount . GL.register_line_posting
 
 102         register_amount_balance  = Amount.sum_balance  . GL.posting_amount . GL.register_line_posting
 
 103         register_sum_positive    = Amount.sum_positive . GL.register_line_sum
 
 104         register_sum_negative    = Amount.sum_negative . GL.register_line_sum
 
 105         register_sum_balance     = Amount.sum_balance  . GL.register_line_sum
 
 108 instance GL.Transaction Transaction where
 
 109         type Transaction_Posting  Transaction = Posting
 
 110         type Transaction_Postings Transaction = Compose (Map Account) []
 
 111         transaction_date     = fst . transaction_dates
 
 112         transaction_postings = Compose . transaction_postings
 
 114 type Transaction_by_Date
 
 115  = Data.Map.Map Date [Transaction]
 
 117 -- | Return a 'Data.Map.Map' associating
 
 118 --   the given 'Transaction's with their respective 'Date'.
 
 119 transaction_by_Date :: [Transaction] -> Transaction_by_Date
 
 120 transaction_by_Date =
 
 121         Data.Map.fromListWith (flip (++)) .
 
 122         Data.List.map (\t -> (fst $ transaction_dates t, [t]))
 
 124 -- * The 'Posting' type
 
 128  { posting_account   :: Account
 
 129  , posting_amounts   :: Map Amount.Unit Amount
 
 130  , posting_comments  :: [Comment]
 
 131  , posting_dates     :: [Date]
 
 132  , posting_sourcepos :: SourcePos
 
 133  , posting_status    :: Bool
 
 134  , posting_tags      :: Tag_by_Name
 
 135  } deriving (Data, Eq, Show, Typeable)
 
 138  =   Posting_Type_Regular
 
 139  |   Posting_Type_Virtual
 
 140  |   Posting_Type_Virtual_Balanced
 
 141  deriving (Data, Eq, Read, Show, Typeable)
 
 143 posting :: Account -> Posting
 
 146          { posting_account = acct
 
 147          , posting_amounts = Data.Map.empty
 
 148          , posting_comments = []
 
 150          , posting_status = False
 
 151          , posting_sourcepos = initialPos ""
 
 152          , posting_tags = Data.Map.empty
 
 156  Balance.Posting Posting where
 
 157         type Posting_Amount Posting = Amount.Sum Amount
 
 158         posting_account = posting_account
 
 159         posting_amounts = Data.Map.map Amount.sum . posting_amounts
 
 160         posting_set_amounts amounts p =
 
 161                 p { posting_amounts=Data.Map.map Amount.sum_balance amounts }
 
 163 instance Filter.Posting Posting where
 
 164         type Posting_Amount Posting = Amount
 
 165         posting_account = posting_account
 
 166         posting_amounts = posting_amounts
 
 168 instance GL.Posting Posting where
 
 169         type Posting_Amount Posting = Amount.Sum (Map Amount.Unit Amount)
 
 170         posting_account = posting_account
 
 171         posting_amount  = Amount.sum . posting_amounts
 
 173 -- ** The 'Posting' mappings
 
 175 type Posting_by_Account
 
 176  = Map Account [Posting]
 
 178 type Posting_by_Amount_and_Account
 
 179  = Map Amount.By_Unit Posting_by_Account
 
 181 type Posting_by_Signs_and_Account
 
 182  = Map Amount.Signs Posting_by_Account
 
 184 -- | Return a Data.'Data.Map.Map' associating the given 'Posting's with their respective 'Account'.
 
 185 posting_by_Account :: [Posting] -> Posting_by_Account
 
 187         Data.Map.fromListWith (flip (++)) .
 
 189          (\p -> (posting_account p, [p]))
 
 191 posting_by_Amount_and_Account :: Posting_by_Account -> Posting_by_Amount_and_Account
 
 192 posting_by_Amount_and_Account =
 
 193         Data.Map.foldlWithKey
 
 198                          (Data.Map.unionWith (++))
 
 200                          (Data.Map.singleton acct [p])))))
 
 203 posting_by_Signs_and_Account :: Posting_by_Account -> Posting_by_Signs_and_Account
 
 204 posting_by_Signs_and_Account =
 
 205         Data.Map.foldlWithKey
 
 210                          (Data.Map.unionWith (++))
 
 211                          (Amount.signs $ posting_amounts p)
 
 212                          (Data.Map.singleton acct [p])))))
 
 217 type Tag = (Tag_Name, Tag_Value)
 
 219 type Tag_Value = Text
 
 221 type Tag_by_Name = Map Tag_Name [Tag_Value]
 
 223 -- | Return a 'Data.Map.Map' associating the 'Value's of the given 'Tag's with their respective 'Name'.
 
 224 tag_by_Name :: [Tag] -> Tag_by_Name
 
 226         Data.Map.fromListWith (flip (++)) .
 
 227         Data.List.map (\(n, v) -> (n, [v]))