mirror of
https://github.com/simonmichael/hledger.git
synced 2024-11-08 07:09:28 +03:00
lib: multiBalanceReport: Remove old balanceReport code, update some tests.
This commit is contained in:
parent
edb28d51c5
commit
e079c8b808
@ -11,18 +11,12 @@ module Hledger.Reports.BalanceReport (
|
||||
BalanceReportItem,
|
||||
balanceReport,
|
||||
flatShowsExclusiveBalance,
|
||||
sortAccountItemsLike,
|
||||
unifyMixedAmount,
|
||||
perdivide,
|
||||
|
||||
-- * Tests
|
||||
tests_BalanceReport
|
||||
)
|
||||
where
|
||||
|
||||
import Data.List
|
||||
import Data.Ord
|
||||
import Data.Maybe
|
||||
import Data.Time.Calendar
|
||||
|
||||
import Hledger.Data
|
||||
@ -30,6 +24,7 @@ import Hledger.Read (mamountp')
|
||||
import Hledger.Query
|
||||
import Hledger.Utils
|
||||
import Hledger.Reports.ReportOptions
|
||||
import Hledger.Reports.MultiBalanceReport (balanceReportFromMultiBalanceReport)
|
||||
|
||||
|
||||
-- | A simple balance report. It has:
|
||||
@ -66,166 +61,8 @@ flatShowsExclusiveBalance = True
|
||||
-- This is like PeriodChangeReport with a single column (but more mature,
|
||||
-- eg this can do hierarchical display).
|
||||
balanceReport :: ReportOpts -> Query -> Journal -> BalanceReport
|
||||
balanceReport ropts@ReportOpts{..} q j =
|
||||
(if invert_ then brNegate else id) $
|
||||
(mappedsorteditems, mappedtotal)
|
||||
where
|
||||
-- dbg = const id -- exclude from debug output
|
||||
dbg s = let p = "balanceReport" in Hledger.Utils.dbg4 (p++" "++s) -- add prefix in debug output
|
||||
dbg' s = let p = "balanceReport" in Hledger.Utils.dbg5 (p++" "++s) -- add prefix in debug output
|
||||
balanceReport = balanceReportFromMultiBalanceReport
|
||||
|
||||
-- Get all the summed accounts & balances, according to the query, as an account tree.
|
||||
-- If doing cost valuation, amounts will be converted to cost first.
|
||||
accttree = ledgerRootAccount $ ledgerFromJournal q $ journalSelectingAmountFromOpts ropts j
|
||||
|
||||
-- For other kinds of valuation, convert the summed amounts to value,
|
||||
-- per hledger_options.m4.md "Effect of --value on reports".
|
||||
valuedaccttree = mapAccounts avalue accttree
|
||||
where
|
||||
avalue a@Account{..} = a{aebalance=maybevalue aebalance, aibalance=maybevalue aibalance}
|
||||
where
|
||||
maybevalue = maybe id applyvaluation value_
|
||||
where
|
||||
applyvaluation = mixedAmountApplyValuation priceoracle styles periodlast mreportlast today multiperiod
|
||||
where
|
||||
priceoracle = journalPriceOracle infer_value_ j
|
||||
styles = journalCommodityStyles j
|
||||
periodlast = fromMaybe
|
||||
(error' "balanceReport: expected a non-empty journal") $ -- XXX shouldn't happen
|
||||
reportPeriodOrJournalLastDay ropts j
|
||||
mreportlast = reportPeriodLastDay ropts
|
||||
today = fromMaybe (error' "balanceReport: could not pick a valuation date, ReportOpts today_ is unset") today_
|
||||
multiperiod = interval_ /= NoInterval
|
||||
|
||||
-- Modify this tree for display - depth limit, boring parents, zeroes - and convert to a list.
|
||||
displayaccts :: [Account]
|
||||
| queryDepth q == 0 =
|
||||
dbg' "displayaccts" $
|
||||
take 1 $ clipAccountsAndAggregate (queryDepth q) $ flattenAccounts valuedaccttree
|
||||
| flat_ ropts = dbg' "displayaccts" $
|
||||
filterzeros $
|
||||
filterempty $
|
||||
drop 1 $ clipAccountsAndAggregate (queryDepth q) $ flattenAccounts valuedaccttree
|
||||
| otherwise = dbg' "displayaccts" $
|
||||
filter (not.aboring) $
|
||||
drop 1 $ flattenAccounts $
|
||||
markboring $
|
||||
prunezeros $
|
||||
sortAccountTreeByAmount (fromMaybe NormallyPositive normalbalance_) $
|
||||
clipAccounts (queryDepth q) valuedaccttree
|
||||
where
|
||||
balance = if flat_ ropts then aebalance else aibalance
|
||||
filterzeros = if empty_ then id else filter (not . mixedAmountLooksZero . balance)
|
||||
filterempty = filter (\a -> anumpostings a > 0 || not (mixedAmountLooksZero (balance a)))
|
||||
prunezeros = if empty_ then id else fromMaybe nullacct . pruneAccounts (mixedAmountLooksZero . balance)
|
||||
markboring = if no_elide_ then id else markBoringParentAccounts
|
||||
|
||||
-- Make a report row for each account.
|
||||
items = dbg "items" $ map (balanceReportItem ropts q) displayaccts
|
||||
|
||||
-- Sort report rows (except sorting by amount in tree mode, which was done above).
|
||||
sorteditems
|
||||
| sort_amount_ && tree_ ropts = items
|
||||
| sort_amount_ = sortFlatBRByAmount items
|
||||
| otherwise = sortBRByAccountDeclaration items
|
||||
where
|
||||
-- Sort the report rows, representing a flat account list, by row total.
|
||||
sortFlatBRByAmount :: [BalanceReportItem] -> [BalanceReportItem]
|
||||
sortFlatBRByAmount = sortBy (maybeflip $ comparing (normaliseMixedAmountSquashPricesForDisplay . fourth4))
|
||||
where
|
||||
maybeflip = if normalbalance_ == Just NormallyNegative then id else flip
|
||||
-- Sort the report rows by account declaration order then account name.
|
||||
sortBRByAccountDeclaration :: [BalanceReportItem] -> [BalanceReportItem]
|
||||
sortBRByAccountDeclaration rows = sortedrows
|
||||
where
|
||||
anamesandrows = [(first4 r, r) | r <- rows]
|
||||
anames = map fst anamesandrows
|
||||
sortedanames = sortAccountNamesByDeclaration j (tree_ ropts) anames
|
||||
sortedrows = sortAccountItemsLike sortedanames anamesandrows
|
||||
|
||||
-- Calculate the grand total.
|
||||
total | not (flat_ ropts) = dbg "total" $ sum [amt | (_,_,indent,amt) <- items, indent == 0]
|
||||
| otherwise = dbg "total" $
|
||||
if flatShowsExclusiveBalance
|
||||
then sum $ map fourth4 items
|
||||
else sum $ map aebalance $ clipAccountsAndAggregate 1 displayaccts
|
||||
|
||||
-- Calculate percentages if needed.
|
||||
mappedtotal | percent_ = dbg "mappedtotal" $ total `perdivide` total
|
||||
| otherwise = total
|
||||
mappedsorteditems | percent_ =
|
||||
dbg "mappedsorteditems" $
|
||||
map (\(fname, sname, indent, amount) -> (fname, sname, indent, amount `perdivide` total)) sorteditems
|
||||
| otherwise = sorteditems
|
||||
|
||||
-- | A sorting helper: sort a list of things (eg report rows) keyed by account name
|
||||
-- to match the provided ordering of those same account names.
|
||||
sortAccountItemsLike :: [AccountName] -> [(AccountName, b)] -> [b]
|
||||
sortAccountItemsLike sortedas items =
|
||||
concatMap (\a -> maybe [] (:[]) $ lookup a items) sortedas
|
||||
|
||||
-- | In an account tree with zero-balance leaves removed, mark the
|
||||
-- elidable parent accounts (those with one subaccount and no balance
|
||||
-- of their own).
|
||||
markBoringParentAccounts :: Account -> Account
|
||||
markBoringParentAccounts = tieAccountParents . mapAccounts mark
|
||||
where
|
||||
mark a | length (asubs a) == 1 && mixedAmountLooksZero (aebalance a) = a{aboring=True}
|
||||
| otherwise = a
|
||||
|
||||
balanceReportItem :: ReportOpts -> Query -> Account -> BalanceReportItem
|
||||
balanceReportItem opts q a
|
||||
| flat_ opts = (name, name, 0, (if flatShowsExclusiveBalance then aebalance else aibalance) a)
|
||||
| otherwise = (name, elidedname, indent, aibalance a)
|
||||
where
|
||||
name | queryDepth q > 0 = aname a
|
||||
| otherwise = "..."
|
||||
elidedname = accountNameFromComponents (adjacentboringparentnames ++ [accountLeafName name])
|
||||
adjacentboringparentnames = reverse $ map (accountLeafName.aname) $ takeWhile aboring parents
|
||||
indent = length $ filter (not.aboring) parents
|
||||
-- parents exclude the tree's root node
|
||||
parents = case parentAccounts a of [] -> []
|
||||
as -> init as
|
||||
|
||||
-- -- the above using the newer multi balance report code:
|
||||
-- balanceReport' opts q j = (items, total)
|
||||
-- where
|
||||
-- MultiBalanceReport (_,mbrrows,mbrtotals) = PeriodChangeReport opts q j
|
||||
-- items = [(a,a',n, headDef 0 bs) | ((a,a',n), bs) <- mbrrows]
|
||||
-- total = headDef 0 mbrtotals
|
||||
|
||||
-- | Flip the sign of all amounts in a BalanceReport.
|
||||
brNegate :: BalanceReport -> BalanceReport
|
||||
brNegate (is, tot) = (map brItemNegate is, -tot)
|
||||
where
|
||||
brItemNegate (a, a', d, amt) = (a, a', d, -amt)
|
||||
|
||||
-- | Helper to unify a MixedAmount to a single commodity value.
|
||||
-- Like normaliseMixedAmount, this consolidates amounts of the same commodity
|
||||
-- and discards zero amounts; but this one insists on simplifying to
|
||||
-- a single commodity, and will throw a program-terminating error if
|
||||
-- this is not possible.
|
||||
unifyMixedAmount :: MixedAmount -> Amount
|
||||
unifyMixedAmount mixedAmount = foldl combine (num 0) (amounts mixedAmount)
|
||||
where
|
||||
combine amount result =
|
||||
if amountIsZero amount
|
||||
then result
|
||||
else if amountIsZero result
|
||||
then amount
|
||||
else if acommodity amount == acommodity result
|
||||
then amount + result
|
||||
else error' "Cannot calculate percentages for accounts with multiple commodities. (Hint: Try --cost, -V or similar flags.)"
|
||||
|
||||
-- | Helper to calculate the percentage from two mixed. Keeps the sign of the first argument.
|
||||
-- Uses unifyMixedAmount to unify each argument and then divides them.
|
||||
perdivide :: MixedAmount -> MixedAmount -> MixedAmount
|
||||
perdivide a b =
|
||||
let a' = unifyMixedAmount a
|
||||
b' = unifyMixedAmount b
|
||||
in if amountIsZero a' || amountIsZero b' || acommodity a' == acommodity b'
|
||||
then mixed [per $ if aquantity b' == 0 then 0 else (aquantity a' / abs (aquantity b') * 100)]
|
||||
else error' "Cannot calculate percentages if accounts have different commodities. (Hint: Try --cost, -V or similar flags.)"
|
||||
|
||||
-- tests
|
||||
|
||||
@ -259,13 +96,13 @@ tests_BalanceReport = tests "BalanceReport" [
|
||||
let (eitems, etotal) = r
|
||||
(aitems, atotal) = balanceReport opts (queryFromOpts nulldate opts) journal
|
||||
showw (acct,acct',indent,amt) = (acct, acct', indent, showMixedAmountDebug amt)
|
||||
(map showw eitems) @?= (map showw aitems)
|
||||
(map showw aitems) @?= (map showw eitems)
|
||||
(showMixedAmountDebug etotal) @?= (showMixedAmountDebug atotal)
|
||||
in
|
||||
tests "balanceReport" [
|
||||
|
||||
test "no args, null journal" $
|
||||
(defreportopts, nulljournal) `gives` ([], Mixed [nullamt])
|
||||
(defreportopts, nulljournal) `gives` ([], Mixed [])
|
||||
|
||||
,test "no args, sample journal" $
|
||||
(defreportopts, samplejournal) `gives`
|
||||
@ -303,7 +140,7 @@ tests_BalanceReport = tests "BalanceReport" [
|
||||
,test "with date:" $
|
||||
(defreportopts{query_="date:'in 2009'"}, samplejournal2) `gives`
|
||||
([],
|
||||
Mixed [nullamt])
|
||||
Mixed [])
|
||||
|
||||
,test "with date2:" $
|
||||
(defreportopts{query_="date2:'in 2009'"}, samplejournal2) `gives`
|
||||
@ -345,7 +182,7 @@ tests_BalanceReport = tests "BalanceReport" [
|
||||
|
||||
,test "with period on an unpopulated period" $
|
||||
(defreportopts{period_= PeriodBetween (fromGregorian 2008 1 2) (fromGregorian 2008 1 3)}, samplejournal) `gives`
|
||||
([],Mixed [nullamt])
|
||||
([],Mixed [])
|
||||
|
||||
|
||||
|
||||
|
@ -37,7 +37,6 @@ import Hledger.Utils
|
||||
--import Hledger.Read (mamountp')
|
||||
import Hledger.Reports.ReportOptions
|
||||
import Hledger.Reports.ReportTypes
|
||||
import Hledger.Reports.BalanceReport (sortAccountItemsLike)
|
||||
import Hledger.Reports.MultiBalanceReport
|
||||
|
||||
|
||||
|
@ -18,6 +18,8 @@ module Hledger.Reports.MultiBalanceReport (
|
||||
balanceReportFromMultiBalanceReport,
|
||||
tableAsText,
|
||||
|
||||
sortAccountItemsLike,
|
||||
|
||||
-- -- * Tests
|
||||
tests_MultiBalanceReport
|
||||
)
|
||||
@ -480,12 +482,11 @@ balanceReportFromMultiBalanceReport ropts q j = (rows', total)
|
||||
PeriodicReport _ rows (PeriodicReportRow _ totals _ _) =
|
||||
multiBalanceReportWith ropts' q j (journalPriceOracle (infer_value_ ropts) j)
|
||||
rows' = [( displayFull a
|
||||
, leafName a
|
||||
, displayName a
|
||||
, if tree_ ropts' then displayDepth a - 1 else 0 -- BalanceReport uses 0-based account depths
|
||||
, headDef nullmixedamt amts -- 0 columns is illegal, should not happen, return zeroes if it does
|
||||
) | PeriodicReportRow a amts _ _ <- rows]
|
||||
total = headDef nullmixedamt totals
|
||||
leafName = if flat_ ropts' then displayFull else displayName -- BalanceReport expects full account name here with --flat
|
||||
ropts' = setDefaultAccountListMode ALTree ropts
|
||||
|
||||
|
||||
|
@ -355,7 +355,7 @@ balance opts@CliOpts{rawopts_=rawopts,reportopts_=ropts@ReportOpts{..}} j = do
|
||||
balanceReportAsCsv :: ReportOpts -> BalanceReport -> CSV
|
||||
balanceReportAsCsv opts (items, total) =
|
||||
["account","balance"] :
|
||||
[[T.unpack (maybeAccountNameDrop opts a), showMixedAmountOneLineWithoutPrice b] | (a, _, _, b) <- items]
|
||||
[[T.unpack a, showMixedAmountOneLineWithoutPrice b] | (a, _, _, b) <- items]
|
||||
++
|
||||
if no_total_ opts
|
||||
then []
|
||||
@ -404,7 +404,7 @@ This implementation turned out to be a bit convoluted but implements the followi
|
||||
balanceReportItemAsText :: ReportOpts -> StringFormat -> BalanceReportItem -> [String]
|
||||
balanceReportItemAsText opts fmt (_, accountName, depth, amt) =
|
||||
renderBalanceReportItem opts fmt (
|
||||
maybeAccountNameDrop opts accountName,
|
||||
accountName,
|
||||
depth,
|
||||
normaliseMixedAmountSquashPricesForDisplay amt
|
||||
)
|
||||
|
@ -87,12 +87,14 @@ Balance changes in 2015:
|
||||
$ hledger -f - bal -Y --tree
|
||||
Balance changes in 2015:
|
||||
|
||||
|| 2015
|
||||
===========++======
|
||||
3 || 1
|
||||
5 || 1
|
||||
-----------++------
|
||||
||
|
||||
|| 2015
|
||||
=========++======
|
||||
1:2 || 0
|
||||
3 || 1
|
||||
4 || 0
|
||||
5 || 1
|
||||
---------++------
|
||||
|| 0
|
||||
|
||||
# 6. TODO: after 5, test account code sorting
|
||||
# account 1:2:3 100
|
||||
|
@ -32,7 +32,7 @@ Balance changes in 2018:
|
||||
>=
|
||||
|
||||
# 2. Tree mode. Missing parent accounts are added (b).
|
||||
$ hledger -f- bal -NY --tree
|
||||
$ hledger -f- bal -NY --tree --no-elide
|
||||
Balance changes in 2018:
|
||||
|
||||
|| 2018
|
||||
@ -90,7 +90,7 @@ Balance changes in 2018:
|
||||
|
||||
# 4. With account directives, tree mode.
|
||||
# Missing parent accounts are added (b).
|
||||
$ hledger -f- bal -NY --tree
|
||||
$ hledger -f- bal -NY --tree --no-elide
|
||||
Balance changes in 2018:
|
||||
|
||||
|| 2018
|
||||
@ -141,7 +141,7 @@ Balance changes in 2018:
|
||||
2018/1/1
|
||||
(a:k) 1
|
||||
|
||||
$ hledger -f- bal -NY --sort-amount --tree
|
||||
$ hledger -f- bal -NY --sort-amount --tree --no-elide
|
||||
Balance changes in 2018:
|
||||
|
||||
|| 2018
|
||||
|
Loading…
Reference in New Issue
Block a user