2008-10-10 07:32:12 +04:00
|
|
|
{-|
|
|
|
|
|
|
|
|
A ledger-compatible @balance@ command. Here's how it should work:
|
|
|
|
|
|
|
|
A sample account tree (as in the sample.ledger file):
|
|
|
|
|
|
|
|
@
|
|
|
|
assets
|
|
|
|
cash
|
|
|
|
checking
|
|
|
|
saving
|
|
|
|
expenses
|
|
|
|
food
|
|
|
|
supplies
|
|
|
|
income
|
|
|
|
gifts
|
|
|
|
salary
|
|
|
|
liabilities
|
|
|
|
debts
|
|
|
|
@
|
|
|
|
|
|
|
|
The balance command shows top-level accounts by default:
|
|
|
|
|
|
|
|
@
|
|
|
|
\> ledger balance
|
|
|
|
$-1 assets
|
|
|
|
$2 expenses
|
|
|
|
$-2 income
|
|
|
|
$1 liabilities
|
|
|
|
@
|
|
|
|
|
|
|
|
With -s (--showsubs), also show the subaccounts:
|
|
|
|
|
|
|
|
@
|
|
|
|
$-1 assets
|
|
|
|
$-2 cash
|
|
|
|
$1 saving
|
|
|
|
$2 expenses
|
|
|
|
$1 food
|
|
|
|
$1 supplies
|
|
|
|
$-2 income
|
|
|
|
$-1 gifts
|
|
|
|
$-1 salary
|
|
|
|
$1 liabilities:debts
|
|
|
|
@
|
|
|
|
|
|
|
|
- @checking@ is not shown because it has a zero balance and no interesting
|
|
|
|
subaccounts.
|
|
|
|
|
2008-10-12 10:23:55 +04:00
|
|
|
- @liabilities@ is displayed only as a prefix because it has the same balance
|
|
|
|
as its single subaccount.
|
2008-10-10 07:32:12 +04:00
|
|
|
|
|
|
|
With an account pattern, show only the accounts with matching names:
|
|
|
|
|
|
|
|
@
|
|
|
|
\> ledger balance o
|
|
|
|
$1 expenses:food
|
|
|
|
$-2 income
|
|
|
|
--------------------
|
|
|
|
$-1
|
|
|
|
@
|
|
|
|
|
|
|
|
- The o matched @food@ and @income@, so they are shown.
|
|
|
|
|
|
|
|
- Parents of matched accounts are also shown for context (@expenses@).
|
|
|
|
|
|
|
|
- This time the grand total is also shown, because it is not zero.
|
|
|
|
|
|
|
|
Again, -s adds the subaccounts:
|
|
|
|
|
|
|
|
@
|
|
|
|
\> ledger -s balance o
|
|
|
|
$1 expenses:food
|
|
|
|
$-2 income
|
|
|
|
$-1 gifts
|
|
|
|
$-1 salary
|
|
|
|
--------------------
|
|
|
|
$-1
|
|
|
|
@
|
|
|
|
|
|
|
|
- @food@ has no subaccounts. @income@ has two, so they are shown.
|
|
|
|
|
|
|
|
- We do not add the subaccounts of parents included for context (@expenses@).
|
|
|
|
|
2008-10-12 10:23:55 +04:00
|
|
|
Some notes for the implementation:
|
2008-10-10 07:32:12 +04:00
|
|
|
|
2008-10-12 10:23:55 +04:00
|
|
|
- a simple balance report shows top-level accounts
|
2008-10-10 07:32:12 +04:00
|
|
|
|
2008-10-12 10:23:55 +04:00
|
|
|
- with an account pattern, it shows accounts whose leafname matches, plus their parents
|
2008-10-10 07:32:12 +04:00
|
|
|
|
2008-10-12 10:23:55 +04:00
|
|
|
- with the showsubs option, it also shows all subaccounts of the above
|
2008-10-10 07:32:12 +04:00
|
|
|
|
2008-10-12 10:23:55 +04:00
|
|
|
- zero-balance leaf accounts are removed
|
2008-10-10 07:32:12 +04:00
|
|
|
|
2008-10-12 10:23:55 +04:00
|
|
|
- the resulting account tree is displayed with each account's aggregated
|
2008-10-12 10:45:54 +04:00
|
|
|
balance, with boring parents prefixed to the next line
|
|
|
|
|
|
|
|
- a boring parent has the same balance as its child and is not explicitly
|
|
|
|
matched by the display options.
|
2008-10-10 07:32:12 +04:00
|
|
|
|
2008-10-12 10:23:55 +04:00
|
|
|
- the sum of the balances shown is displayed at the end, if it is non-zero
|
2008-10-11 08:23:49 +04:00
|
|
|
|
2008-10-10 07:32:12 +04:00
|
|
|
-}
|
|
|
|
|
|
|
|
module BalanceCommand
|
|
|
|
where
|
|
|
|
import Ledger.Utils
|
|
|
|
import Ledger.Types
|
|
|
|
import Ledger.Amount
|
|
|
|
import Ledger.AccountName
|
|
|
|
import Ledger.Ledger
|
|
|
|
import Options
|
2008-10-10 14:05:12 +04:00
|
|
|
import Utils
|
2008-10-10 07:32:12 +04:00
|
|
|
|
|
|
|
|
|
|
|
-- | Print a balance report.
|
2008-10-12 13:17:21 +04:00
|
|
|
balance :: [Opt] -> [String] -> Ledger -> IO ()
|
|
|
|
balance opts args l = putStr $ showBalanceReport opts args l
|
2008-10-10 14:05:12 +04:00
|
|
|
|
2008-10-12 10:23:55 +04:00
|
|
|
-- | Generate balance report output for a ledger, based on options.
|
2008-10-12 13:17:21 +04:00
|
|
|
showBalanceReport :: [Opt] -> [String] -> Ledger -> String
|
|
|
|
showBalanceReport opts args l = acctsstr ++ totalstr
|
2008-10-10 07:32:12 +04:00
|
|
|
where
|
2008-10-12 10:23:55 +04:00
|
|
|
acctsstr = concatMap (showAccountTreeWithBalances acctnamestoshow) $ subs treetoshow
|
2008-10-17 07:14:23 +04:00
|
|
|
totalstr = if isZeroMixedAmount total
|
2008-10-12 10:23:55 +04:00
|
|
|
then ""
|
2008-10-17 07:14:23 +04:00
|
|
|
else printf "--------------------\n%20s\n" $ showMixedAmount total
|
2008-10-12 10:23:55 +04:00
|
|
|
showingsubs = ShowSubs `elem` opts
|
2008-10-11 08:23:49 +04:00
|
|
|
pats@(apats,dpats) = parseAccountDescriptionArgs args
|
2008-10-12 10:23:55 +04:00
|
|
|
maxdepth = if null args && not showingsubs then 1 else 9999
|
|
|
|
acctstoshow = balancereportaccts showingsubs apats l
|
|
|
|
acctnamestoshow = map aname acctstoshow
|
2008-10-11 08:23:49 +04:00
|
|
|
treetoshow = pruneZeroBalanceLeaves $ pruneUnmatchedAccounts $ treeprune maxdepth $ ledgerAccountTree 9999 l
|
2008-10-18 14:38:01 +04:00
|
|
|
total = sum $ map abalance $ nonredundantaccts
|
2008-10-11 08:23:49 +04:00
|
|
|
nonredundantaccts = filter (not . hasparentshowing) acctstoshow
|
2008-10-12 10:23:55 +04:00
|
|
|
hasparentshowing a = (parentAccountName $ aname a) `elem` acctnamestoshow
|
|
|
|
|
|
|
|
-- select accounts for which we should show balances, based on the options
|
|
|
|
balancereportaccts :: Bool -> [String] -> Ledger -> [Account]
|
|
|
|
balancereportaccts False [] l = topAccounts l
|
2008-10-15 10:32:42 +04:00
|
|
|
balancereportaccts False pats l = accountsMatching pats l
|
2008-10-12 10:23:55 +04:00
|
|
|
balancereportaccts True pats l = addsubaccts l $ balancereportaccts False pats l
|
|
|
|
|
|
|
|
-- add (in tree order) any missing subacccounts to a list of accounts
|
|
|
|
addsubaccts :: Ledger -> [Account] -> [Account]
|
|
|
|
addsubaccts l as = concatMap addsubs as where addsubs = maybe [] flatten . ledgerAccountTreeAt l
|
2008-10-11 08:23:49 +04:00
|
|
|
|
|
|
|
-- remove any accounts from the tree which are not one of the acctstoshow,
|
2008-10-12 10:23:55 +04:00
|
|
|
-- or one of their parents, or one of their subaccounts when doing --showsubs
|
2008-10-11 08:23:49 +04:00
|
|
|
pruneUnmatchedAccounts :: Tree Account -> Tree Account
|
|
|
|
pruneUnmatchedAccounts = treefilter matched
|
|
|
|
where
|
|
|
|
matched (Account name _ _)
|
2008-10-12 10:23:55 +04:00
|
|
|
| name `elem` acctnamestoshow = True
|
|
|
|
| any (name `isAccountNamePrefixOf`) acctnamestoshow = True
|
|
|
|
| showingsubs && any (`isAccountNamePrefixOf` name) acctnamestoshow = True
|
2008-10-11 08:23:49 +04:00
|
|
|
| otherwise = False
|
|
|
|
|
2008-10-12 10:23:55 +04:00
|
|
|
-- remove zero-balance leaf accounts (recursively)
|
2008-10-11 08:23:49 +04:00
|
|
|
pruneZeroBalanceLeaves :: Tree Account -> Tree Account
|
2008-10-17 07:14:23 +04:00
|
|
|
pruneZeroBalanceLeaves = treefilter (not . isZeroMixedAmount . abalance)
|
2008-10-10 15:55:12 +04:00
|
|
|
|
2008-10-12 10:23:55 +04:00
|
|
|
-- | Show a tree of accounts with balances, for the balance report,
|
|
|
|
-- eliding boring parent accounts. Requires a list of the account names we
|
|
|
|
-- are interested in to help with that.
|
|
|
|
showAccountTreeWithBalances :: [AccountName] -> Tree Account -> String
|
2008-10-18 06:43:13 +04:00
|
|
|
showAccountTreeWithBalances matchednames =
|
|
|
|
showAccountTreeWithBalances' matchednames 0 ""
|
2008-10-10 07:32:12 +04:00
|
|
|
where
|
2008-10-12 10:23:55 +04:00
|
|
|
showAccountTreeWithBalances' :: [AccountName] -> Int -> String -> Tree Account -> String
|
2008-10-18 06:43:13 +04:00
|
|
|
showAccountTreeWithBalances' matchednames indent prefix (Node (Account fullname _ bal) subs)
|
|
|
|
| not isboringparent = this ++ subswithindent
|
|
|
|
| otherwise = subswithprefix
|
2008-10-11 08:23:49 +04:00
|
|
|
where
|
2008-10-18 06:43:13 +04:00
|
|
|
subswithindent = showsubs (indent+1) ""
|
|
|
|
subswithprefix = showsubs indent (prefix++leafname++":")
|
|
|
|
showsubs i p = concatMap (showAccountTreeWithBalances' matchednames i p) subs
|
|
|
|
this = showbal ++ spaces ++ prefix ++ leafname ++ "\n"
|
2008-10-17 07:14:23 +04:00
|
|
|
showbal = printf "%20s" $ showMixedAmount bal
|
2008-10-18 06:43:13 +04:00
|
|
|
spaces = " " ++ replicate (indent * 2) ' '
|
2008-10-11 08:23:49 +04:00
|
|
|
leafname = accountLeafName fullname
|
2008-10-18 06:43:13 +04:00
|
|
|
isboringparent = numsubs >= 1 && (bal == subbal || not matched)
|
2008-10-12 10:23:55 +04:00
|
|
|
numsubs = length subs
|
|
|
|
subbal = abalance $ root $ head subs
|
2008-10-18 06:43:13 +04:00
|
|
|
matched = fullname `elem` matchednames
|