fix: budget: Make sure boring parents of unbudgeted accounts are not

elided if they have a budget. (#1800)

This only affects calls with --budget and -E, but not with --no-elide.
This commit is contained in:
Stephen Morgan 2022-01-06 19:18:04 +01:00 committed by Simon Michael
parent d9b0184720
commit 8cd9e81c34
3 changed files with 61 additions and 30 deletions

View File

@ -82,10 +82,12 @@ budgetReport rspec bopts reportspan j = dbg4 "sortedbudgetreport" budgetreport
jperiodictxns j jperiodictxns j
actualj = journalWithBudgetAccountNames budgetedaccts showunbudgeted j actualj = journalWithBudgetAccountNames budgetedaccts showunbudgeted j
budgetj = journalAddBudgetGoalTransactions bopts ropts reportspan j budgetj = journalAddBudgetGoalTransactions bopts ropts reportspan j
actualreport@(PeriodicReport actualspans _ _) = priceoracle = journalPriceOracle (infer_prices_ ropts) j
dbg5 "actualreport" $ multiBalanceReport rspec{_rsReportOpts=ropts{empty_=True}} actualj
budgetgoalreport@(PeriodicReport _ budgetgoalitems budgetgoaltotals) = budgetgoalreport@(PeriodicReport _ budgetgoalitems budgetgoaltotals) =
dbg5 "budgetgoalreport" $ multiBalanceReport rspec{_rsReportOpts=ropts{empty_=True}} budgetj dbg5 "budgetgoalreport" $ multiBalanceReportWith rspec{_rsReportOpts=ropts{empty_=True}} budgetj priceoracle mempty
budgetedacctsseen = S.fromList $ map prrFullName budgetgoalitems
actualreport@(PeriodicReport actualspans _ _) =
dbg5 "actualreport" $ multiBalanceReportWith rspec{_rsReportOpts=ropts{empty_=True}} actualj priceoracle budgetedacctsseen
budgetgoalreport' budgetgoalreport'
-- If no interval is specified: -- If no interval is specified:
-- budgetgoalreport's span might be shorter actualreport's due to periodic txns; -- budgetgoalreport's span might be shorter actualreport's due to periodic txns;

View File

@ -47,6 +47,8 @@ import qualified Data.Map as M
import Data.Maybe (fromMaybe, isJust, mapMaybe) import Data.Maybe (fromMaybe, isJust, mapMaybe)
import Data.Ord (Down(..)) import Data.Ord (Down(..))
import Data.Semigroup (sconcat) import Data.Semigroup (sconcat)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Time.Calendar (fromGregorian) import Data.Time.Calendar (fromGregorian)
import Safe (lastDef, minimumMay) import Safe (lastDef, minimumMay)
@ -102,16 +104,16 @@ type ClippedAccountName = AccountName
-- by the balance command (in multiperiod mode) and (via compoundBalanceReport) -- by the balance command (in multiperiod mode) and (via compoundBalanceReport)
-- by the bs/cf/is commands. -- by the bs/cf/is commands.
multiBalanceReport :: ReportSpec -> Journal -> MultiBalanceReport multiBalanceReport :: ReportSpec -> Journal -> MultiBalanceReport
multiBalanceReport rspec j = multiBalanceReportWith rspec j (journalPriceOracle infer j) multiBalanceReport rspec j = multiBalanceReportWith rspec j (journalPriceOracle infer j) mempty
where infer = infer_prices_ $ _rsReportOpts rspec where infer = infer_prices_ $ _rsReportOpts rspec
-- | A helper for multiBalanceReport. This one takes an extra argument, -- | A helper for multiBalanceReport. This one takes some extra arguments,
-- a PriceOracle to be used for looking up market prices. Commands which -- a 'PriceOracle' to be used for looking up market prices, and a set of
-- run multiple reports (bs etc.) can generate the price oracle just -- 'AccountName's which should not be elided. Commands which run multiple
-- once for efficiency, passing it to each report by calling this -- reports (bs etc.) can generate the price oracle just once for efficiency,
-- function directly. -- passing it to each report by calling this function directly.
multiBalanceReportWith :: ReportSpec -> Journal -> PriceOracle -> MultiBalanceReport multiBalanceReportWith :: ReportSpec -> Journal -> PriceOracle -> Set AccountName -> MultiBalanceReport
multiBalanceReportWith rspec' j priceoracle = report multiBalanceReportWith rspec' j priceoracle unelidableaccts = report
where where
-- Queries, report/column dates. -- Queries, report/column dates.
reportspan = dbg3 "reportspan" $ reportSpan j rspec' reportspan = dbg3 "reportspan" $ reportSpan j rspec'
@ -127,7 +129,7 @@ multiBalanceReportWith rspec' j priceoracle = report
-- Generate and postprocess the report, negating balances and taking percentages if needed -- Generate and postprocess the report, negating balances and taking percentages if needed
report = dbg4 "multiBalanceReportWith" $ report = dbg4 "multiBalanceReportWith" $
generateMultiBalanceReport rspec j priceoracle colps startbals generateMultiBalanceReport rspec j priceoracle unelidableaccts colps startbals
-- | Generate a compound balance report from a list of CBCSubreportSpec. This -- | Generate a compound balance report from a list of CBCSubreportSpec. This
-- shares postings between the subreports. -- shares postings between the subreports.
@ -159,7 +161,7 @@ compoundBalanceReportWith rspec' j priceoracle subreportspecs = cbr
( cbcsubreporttitle ( cbcsubreporttitle
-- Postprocess the report, negating balances and taking percentages if needed -- Postprocess the report, negating balances and taking percentages if needed
, cbcsubreporttransform $ , cbcsubreporttransform $
generateMultiBalanceReport rspecsub j priceoracle colps' startbals' generateMultiBalanceReport rspecsub j priceoracle mempty colps' startbals'
, cbcsubreportincreasestotal , cbcsubreportincreasestotal
) )
where where
@ -343,17 +345,17 @@ calculateReportMatrix rspec@ReportSpec{_rsReportOpts=ropts} j priceoracle startb
-- | Lay out a set of postings grouped by date span into a regular matrix with rows -- | Lay out a set of postings grouped by date span into a regular matrix with rows
-- given by AccountName and columns by DateSpan, then generate a MultiBalanceReport -- given by AccountName and columns by DateSpan, then generate a MultiBalanceReport
-- from the columns. -- from the columns.
generateMultiBalanceReport :: ReportSpec -> Journal -> PriceOracle generateMultiBalanceReport :: ReportSpec -> Journal -> PriceOracle -> Set AccountName
-> [(DateSpan, [Posting])] -> HashMap AccountName Account -> [(DateSpan, [Posting])] -> HashMap AccountName Account
-> MultiBalanceReport -> MultiBalanceReport
generateMultiBalanceReport rspec@ReportSpec{_rsReportOpts=ropts} j priceoracle colps startbals = generateMultiBalanceReport rspec@ReportSpec{_rsReportOpts=ropts} j priceoracle unelidableaccts colps startbals =
report report
where where
-- Process changes into normal, cumulative, or historical amounts, plus value them -- Process changes into normal, cumulative, or historical amounts, plus value them
matrix = calculateReportMatrix rspec j priceoracle startbals colps matrix = calculateReportMatrix rspec j priceoracle startbals colps
-- All account names that will be displayed, possibly depth-clipped. -- All account names that will be displayed, possibly depth-clipped.
displaynames = dbg5 "displaynames" $ displayedAccounts rspec matrix displaynames = dbg5 "displaynames" $ displayedAccounts rspec unelidableaccts matrix
-- All the rows of the report. -- All the rows of the report.
rows = dbg5 "rows" . (if invert_ ropts then map (fmap maNegate) else id) -- Negate amounts if applicable rows = dbg5 "rows" . (if invert_ ropts then map (fmap maNegate) else id) -- Negate amounts if applicable
@ -394,9 +396,11 @@ buildReportRows ropts displaynames =
-- | Calculate accounts which are to be displayed in the report, as well as -- | Calculate accounts which are to be displayed in the report, as well as
-- their name and depth -- their name and depth
displayedAccounts :: ReportSpec -> HashMap AccountName (Map DateSpan Account) displayedAccounts :: ReportSpec
-> Set AccountName
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName DisplayName -> HashMap AccountName DisplayName
displayedAccounts ReportSpec{_rsQuery=query,_rsReportOpts=ropts} valuedaccts displayedAccounts ReportSpec{_rsQuery=query,_rsReportOpts=ropts} unelidableaccts valuedaccts
| depth == 0 = HM.singleton "..." $ DisplayName "..." "..." 1 | depth == 0 = HM.singleton "..." $ DisplayName "..." "..." 1
| otherwise = HM.mapWithKey (\a _ -> displayedName a) displayedAccts | otherwise = HM.mapWithKey (\a _ -> displayedName a) displayedAccts
where where
@ -421,7 +425,8 @@ displayedAccounts ReportSpec{_rsQuery=query,_rsReportOpts=ropts} valuedaccts
-- Accounts interesting for their own sake -- Accounts interesting for their own sake
isInteresting name amts = isInteresting name amts =
d <= depth -- Throw out anything too deep d <= depth -- Throw out anything too deep
&& ( (empty_ ropts && keepWhenEmpty amts) -- Keep empty accounts when called with --empty && ( name `Set.member` unelidableaccts -- Unelidable accounts should be kept unless too deep
||(empty_ ropts && keepWhenEmpty amts) -- Keep empty accounts when called with --empty
|| not (isZeroRow balance amts) -- Keep everything with a non-zero balance in the row || not (isZeroRow balance amts) -- Keep everything with a non-zero balance in the row
) )
where where

View File

@ -409,7 +409,31 @@ Budget performance in 2019-01-01..2019-01-03:
-------------------++--------------------------- -------------------++---------------------------
|| 0 [ 0] || 0 [ 0]
# 20. Subaccounts + nested budgets # 20. Also should work when there are no postings directly in budgeted parents (#1800)
$ hledger -f- bal -e 2019-01-02 --budget -E
Budget performance in 2019-01-01:
|| 2019-01-01
===============================++===========================
expenses:personal || $10.00 [1% of $1,000.00]
expenses:personal:electronics || $10.00
liabilities || $-10.00 [1% of $-1000.00]
-------------------------------++---------------------------
|| 0 [ 0]
# 21. Also should work when there are no postings directly in budgeted parents with --tree (#1800)
$ hledger -f- bal -e 2019-01-02 --budget --tree -E
Budget performance in 2019-01-01:
|| 2019-01-01
===================++===========================
expenses:personal || $10.00 [1% of $1,000.00]
electronics || $10.00
liabilities || $-10.00 [1% of $-1000.00]
-------------------++---------------------------
|| 0 [ 0]
# 22. Subaccounts + nested budgets
< <
~ monthly from 2019/01 ~ monthly from 2019/01
expenses:personal $1,000.00 expenses:personal $1,000.00
@ -439,7 +463,7 @@ Budget performance in 2019-01-01..2019-01-03:
-------------------------------++---------------------------- -------------------------------++----------------------------
|| 0 [ 0] || 0 [ 0]
# 21. # 23.
$ hledger -f- bal --budget -E $ hledger -f- bal --budget -E
Budget performance in 2019-01-01..2019-01-03: Budget performance in 2019-01-01..2019-01-03:
@ -452,7 +476,7 @@ Budget performance in 2019-01-01..2019-01-03:
----------------------------------------++---------------------------- ----------------------------------------++----------------------------
|| 0 [ 0] || 0 [ 0]
# 22. # 24.
$ hledger -f- bal --budget --tree $ hledger -f- bal --budget --tree
Budget performance in 2019-01-01..2019-01-03: Budget performance in 2019-01-01..2019-01-03:
@ -464,7 +488,7 @@ Budget performance in 2019-01-01..2019-01-03:
-------------------++---------------------------- -------------------++----------------------------
|| 0 [ 0] || 0 [ 0]
# 23. # 25.
$ hledger -f- bal --budget --tree -E $ hledger -f- bal --budget --tree -E
Budget performance in 2019-01-01..2019-01-03: Budget performance in 2019-01-01..2019-01-03:
@ -477,7 +501,7 @@ Budget performance in 2019-01-01..2019-01-03:
-------------------++---------------------------- -------------------++----------------------------
|| 0 [ 0] || 0 [ 0]
## 24. Zero budget == no budget # 26. Zero budget == no budget
< <
~ monthly from 2019-01 ~ monthly from 2019-01
expenses:bills $100 ; bills has a $100 budget of its own, separate from subaccounts expenses:bills $100 ; bills has a $100 budget of its own, separate from subaccounts
@ -515,7 +539,7 @@ Budget performance in 2019-01-01..2019-01-02:
------------------++------------------------ ------------------++------------------------
|| 0 [ 0] || 0 [ 0]
# 25. -E shows d and e # 27. -E shows d and e
$ hledger bal -f- --budget -E $ hledger bal -f- --budget -E
Budget performance in 2019-01-01..2019-01-02: Budget performance in 2019-01-01..2019-01-02:
@ -532,7 +556,7 @@ Budget performance in 2019-01-01..2019-01-02:
------------------++------------------------ ------------------++------------------------
|| 0 [ 0] || 0 [ 0]
# 26. The totals row shows correct totals. # 28. The totals row shows correct totals.
# -T/--total and -A/--average adds those columns. # -T/--total and -A/--average adds those columns.
$ hledger bal -f- --budget -TA not:income $ hledger bal -f- --budget -TA not:income
Budget performance in 2019-01-01..2019-01-02: Budget performance in 2019-01-01..2019-01-02:
@ -547,7 +571,7 @@ Budget performance in 2019-01-01..2019-01-02:
------------------++-------------------------------------------------------------- ------------------++--------------------------------------------------------------
|| $80 [22% of $370] $80 [22% of $370] $80 [22% of $370] || $80 [22% of $370] $80 [22% of $370] $80 [22% of $370]
# 27. CSV output works. # 29. CSV output works.
$ hledger bal -f- --budget -TA not:income -O csv $ hledger bal -f- --budget -TA not:income -O csv
"Account","2019-01-01..2019-01-02","budget","Total","budget","Average","budget" "Account","2019-01-01..2019-01-02","budget","Total","budget","Average","budget"
"expenses:bills","$80","$370","$80","$370","$80","$370" "expenses:bills","$80","$370","$80","$370","$80","$370"
@ -557,7 +581,7 @@ $ hledger bal -f- --budget -TA not:income -O csv
"expenses:bills:f","$10","0","$10","0","$10","0" "expenses:bills:f","$10","0","$10","0","$10","0"
"Total:","$80","$370","$80","$370","$80","$370" "Total:","$80","$370","$80","$370","$80","$370"
# 28. You would expect this to show a budget goal in jan, feb, mar. # 30. You would expect this to show a budget goal in jan, feb, mar.
# But by the usual report date logic, which picks the oldest and newest # But by the usual report date logic, which picks the oldest and newest
# transaction date (1/15 and 3/15) as start and end date by default, # transaction date (1/15 and 3/15) as start and end date by default,
# and since "monthly" generates transactions on the 1st, # and since "monthly" generates transactions on the 1st,
@ -585,7 +609,7 @@ Budget performance in 2020Q1:
---------------++----------------------------------------------------------- ---------------++-----------------------------------------------------------
|| 0 [ 0% of $500] 0 [0% of $500] 0 [ 0% of $500] || 0 [ 0% of $500] 0 [0% of $500] 0 [ 0% of $500]
# 29. Specifying the report period works around it. # 31. Specifying the report period works around it.
$ hledger -f- bal --budget -M date:2020q1 $ hledger -f- bal --budget -M date:2020q1
Budget performance in 2020Q1: Budget performance in 2020Q1:
@ -596,7 +620,7 @@ Budget performance in 2020Q1:
---------------++----------------------------------------------------------- ---------------++-----------------------------------------------------------
|| 0 [ 0% of $500] 0 [0% of $500] 0 [ 0% of $500] || 0 [ 0% of $500] 0 [0% of $500] 0 [ 0% of $500]
# 30. Select from multiple named budgets. # 32. Select from multiple named budgets.
< <
~ weekly weekly budget ~ weekly weekly budget
(aaa) 1 (aaa) 1