This upgrades Account to enable it to store a multiperiod balance, with a separate balance for each date period. This enables it do the hard work in MultiBalanceReport. Some new types are created to enable convenient operation of accounts. - `BalanceData` is a type which stores an exclusive balance, inclusive balance, and number of postings. This was previously directly stored in Account, but is now factored into a separate data type. - `PeriodData` is a container which stores date-indexed data, as well as pre-period data. In post cases, this represents the report spans, along with the historical data. - Account becomes polymorphic, allowing customisation of the type of data it stores. This will usually be `BalanceData`, but in `BudgetReport` it can use `These BalanceData BalanceData` to store both actuals and budgets in the same structure. The data structure changes to contain a `PeriodData`, allowing multiperiod accounts. Some minor changes are made to behaviour for consistency: - --declared treats parent accounts consistently. - --flat --empty ensures that implied accounts with no postings are not displayed, but accounts with zero balance and actual postings are.
113 lines
3.9 KiB
Haskell
113 lines
3.9 KiB
Haskell
{-|
|
|
|
|
A 'Ledger' is derived from a 'Journal' by applying a filter specification
|
|
to select 'Transaction's and 'Posting's of interest. It contains the
|
|
filtered journal and knows the resulting chart of accounts, account
|
|
balances, and postings in each account.
|
|
|
|
-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
module Hledger.Data.Ledger (
|
|
nullledger
|
|
,ledgerFromJournal
|
|
,ledgerAccountNames
|
|
,ledgerAccount
|
|
,ledgerRootAccount
|
|
,ledgerTopAccounts
|
|
,ledgerLeafAccounts
|
|
,ledgerPostings
|
|
,ledgerDateSpan
|
|
,ledgerCommodities
|
|
,tests_Ledger
|
|
)
|
|
where
|
|
|
|
import qualified Data.Map as M
|
|
import Safe (headDef)
|
|
import Text.Printf
|
|
|
|
import Test.Tasty (testGroup)
|
|
import Test.Tasty.HUnit ((@?=), testCase)
|
|
import Hledger.Data.Types
|
|
import Hledger.Data.Account
|
|
import Hledger.Data.Dates (nulldate)
|
|
import Hledger.Data.Journal
|
|
import Hledger.Query
|
|
|
|
|
|
instance Show Ledger where
|
|
show l = printf "Ledger with %d transactions, %d accounts\n" --"%s"
|
|
(length (jtxns $ ljournal l) +
|
|
length (jtxnmodifiers $ ljournal l) +
|
|
length (jperiodictxns $ ljournal l))
|
|
(length $ ledgerAccountNames l)
|
|
-- (showtree $ ledgerAccountNameTree l)
|
|
|
|
nullledger :: Ledger
|
|
nullledger = Ledger {
|
|
ljournal = nulljournal,
|
|
laccounts = []
|
|
}
|
|
|
|
-- | Filter a journal's transactions with the given query, then build
|
|
-- a "Ledger", containing the journal plus the tree of all its
|
|
-- accounts with their subaccount-inclusive and subaccount-exclusive
|
|
-- balances. If the query includes a depth limit, the ledger's journal
|
|
-- will be depth limited, but the ledger's account tree will not.
|
|
ledgerFromJournal :: Query -> Journal -> Ledger
|
|
ledgerFromJournal q j = nullledger{ljournal=j'', laccounts=as}
|
|
where
|
|
(q',depthq) = (filterQuery (not . queryIsDepth) q, filterQuery queryIsDepth q)
|
|
j' = filterJournalAmounts (filterQuery queryIsSym q) $ -- remove amount parts which the query's sym: terms would exclude
|
|
filterJournalPostings q' j
|
|
-- Ledger does not use date-separated balances, so dates are left empty
|
|
as = accountsFromPostings (const $ Just nulldate) $ journalPostings j'
|
|
j'' = filterJournalPostings depthq j'
|
|
|
|
-- | List a ledger's account names.
|
|
ledgerAccountNames :: Ledger -> [AccountName]
|
|
ledgerAccountNames = drop 1 . map aname . laccounts
|
|
|
|
-- | Get the named account from a ledger.
|
|
ledgerAccount :: Ledger -> AccountName -> Maybe (Account BalanceData)
|
|
ledgerAccount l a = lookupAccount a $ laccounts l
|
|
|
|
-- | Get this ledger's root account, which is a dummy "root" account
|
|
-- above all others. This should always be first in the account list,
|
|
-- if somehow not this returns a null account.
|
|
ledgerRootAccount :: Ledger -> Account BalanceData
|
|
ledgerRootAccount = headDef nullacct . laccounts
|
|
|
|
-- | List a ledger's top-level accounts (the ones below the root), in tree order.
|
|
ledgerTopAccounts :: Ledger -> [Account BalanceData]
|
|
ledgerTopAccounts = asubs . headDef nullacct . laccounts
|
|
|
|
-- | List a ledger's bottom-level (subaccount-less) accounts, in tree order.
|
|
ledgerLeafAccounts :: Ledger -> [Account BalanceData]
|
|
ledgerLeafAccounts = filter (null.asubs) . laccounts
|
|
|
|
-- | List a ledger's postings, in the order parsed.
|
|
ledgerPostings :: Ledger -> [Posting]
|
|
ledgerPostings = journalPostings . ljournal
|
|
|
|
-- | The (fully specified) date span containing all the ledger's (filtered) transactions,
|
|
-- or DateSpan Nothing Nothing if there are none.
|
|
ledgerDateSpan :: Ledger -> DateSpan
|
|
ledgerDateSpan = journalDateSpanBothDates . ljournal
|
|
|
|
-- | All commodities used in this ledger.
|
|
ledgerCommodities :: Ledger -> [CommoditySymbol]
|
|
ledgerCommodities = M.keys . jinferredcommoditystyles . ljournal
|
|
|
|
-- tests
|
|
|
|
tests_Ledger =
|
|
testGroup "Ledger" [
|
|
testCase "ledgerFromJournal" $ do
|
|
length (ledgerPostings $ ledgerFromJournal Any nulljournal) @?= 0
|
|
length (ledgerPostings $ ledgerFromJournal Any samplejournal) @?= 13
|
|
length (ledgerPostings $ ledgerFromJournal (Depth 2) samplejournal) @?= 7
|
|
]
|