budget: tweak hiding/grouping of unbudgeted accounts

It now works slightly differently. Eg:

- <unbudgeted>'s subaccounts are hidden by default
- --show-unbudgeted shows all unbudgeted accounts, including subaccounts of budgeted parents
- --show-unbudgeted doesn't affect the grouping under <unbudgeted>

IMHO it's a nice simplification and increase in consistency, while still meeting the original intent.
This commit is contained in:
Simon Michael 2018-04-24 13:42:12 -07:00
parent 8b650d8e4f
commit 568a442307
3 changed files with 59 additions and 100 deletions

View File

@ -57,24 +57,24 @@ accountNameLevel :: AccountName -> Int
accountNameLevel "" = 0 accountNameLevel "" = 0
accountNameLevel a = T.length (T.filter (==acctsepchar) a) + 1 accountNameLevel a = T.length (T.filter (==acctsepchar) a) + 1
-- | A top-level account prefixed to some accounts in budget reports.
-- Defined here so it can be ignored by accountNameDrop.
unbudgetedAccountName :: T.Text
unbudgetedAccountName = "<unbudgeted>"
-- | Remove some number of account name components from the front of the account name. -- | Remove some number of account name components from the front of the account name.
-- If the special "<unbudgeted>" top-level account is present, it is preserved and -- If the special "<unbudgeted>" top-level account is present, it is preserved and
-- dropping affects the rest of the account name. -- dropping affects the rest of the account name.
accountNameDrop :: Int -> AccountName -> AccountName accountNameDrop :: Int -> AccountName -> AccountName
accountNameDrop n a accountNameDrop n a
| a == unbudgetedAccount = a | a == unbudgetedAccountName = a
| unbudgetedAccountAndSep `T.isPrefixOf` a = | unbudgetedAccountAndSep `T.isPrefixOf` a =
case accountNameDrop n $ T.drop (T.length unbudgetedAccountAndSep) a of case accountNameDrop n $ T.drop (T.length unbudgetedAccountAndSep) a of
"" -> unbudgetedAccount "" -> unbudgetedAccountName
a' -> unbudgetedAccountAndSep <> a' a' -> unbudgetedAccountAndSep <> a'
| otherwise = accountNameFromComponents $ drop n $ accountNameComponents a | otherwise = accountNameFromComponents $ drop n $ accountNameComponents a
where where
unbudgetedAccountAndSep = unbudgetedAccount <> acctsep unbudgetedAccountAndSep = unbudgetedAccountName <> acctsep
-- | A top-level account prefixed to some accounts in budget reports.
-- Defined here so it can be ignored by accountNameDrop.
unbudgetedAccount :: T.Text
unbudgetedAccount = "<unbudgeted>"
-- | Sorted unique account names implied by these account names, -- | Sorted unique account names implied by these account names,
-- ie these plus all their parent accounts up to the root. -- ie these plus all their parent accounts up to the root.

View File

@ -16,7 +16,7 @@ import Data.Monoid ((<>))
#endif #endif
import Data.Ord import Data.Ord
import Data.Time.Calendar import Data.Time.Calendar
--import Safe import Safe
import Test.HUnit import Test.HUnit
--import Data.List --import Data.List
--import Data.Maybe --import Data.Maybe
@ -65,24 +65,19 @@ type BudgetReport = PeriodicReport (Maybe Change, Maybe BudgetGoal)
budgetReport :: ReportOpts -> Bool -> Bool -> DateSpan -> Day -> Journal -> BudgetReport budgetReport :: ReportOpts -> Bool -> Bool -> DateSpan -> Day -> Journal -> BudgetReport
budgetReport ropts assrt showunbudgeted reportspan d j = budgetReport ropts assrt showunbudgeted reportspan d j =
let let
budgetj = budgetJournal assrt ropts reportspan j q = queryFromOpts d ropts
budgetedacctsinperiod = budgetj = budgetJournal assrt ropts reportspan j
budgetedaccts =
dbg2 "budgetedacctsinperiod" $ dbg2 "budgetedacctsinperiod" $
accountNamesFromPostings $ accountNamesFromPostings $
concatMap tpostings $ concatMap tpostings $
concatMap (flip runPeriodicTransaction reportspan) $ concatMap (flip runPeriodicTransaction reportspan) $
jperiodictxns j jperiodictxns j
actualj = actualj = budgetRollUp budgetedaccts showunbudgeted j
budgetRollUp budgetedacctsinperiod showunbudgeted
-- (if showunbudgeted then id else budgetRollUp budgetedacctsinperiod True budgetj)
j
q = queryFromOpts d ropts
budgetgoalreport = dbg1 "budgetgoalreport" $ multiBalanceReport ropts q budgetj budgetgoalreport = dbg1 "budgetgoalreport" $ multiBalanceReport ropts q budgetj
actualreport = dbg1 "actualreport" $ multiBalanceReport ropts q actualj actualreport = dbg1 "actualreport" $ multiBalanceReport ropts q actualj
in in
dbg1 "budgetreport" $ dbg1 "budgetreport" $ combineBudgetAndActual budgetgoalreport actualreport
-- (if showunbudgeted then id else hideUnbudgetedAccounts budgetedacctsinperiod) $
combineBudgetAndActual budgetgoalreport actualreport
-- | Use all periodic transactions in the journal to generate -- | Use all periodic transactions in the journal to generate
-- budget transactions in the specified report period. -- budget transactions in the specified report period.
@ -101,19 +96,15 @@ budgetJournal assrt _ropts reportspan j =
] ]
makeBudgetTxn t = txnTieKnot $ t { tdescription = T.pack "Budget transaction" } makeBudgetTxn t = txnTieKnot $ t { tdescription = T.pack "Budget transaction" }
-- variations on hiding unbudgeted accounts: -- | Adjust a journal's account names for budget reporting, in two ways:
-- | Adjust a journal for budget reporting, hiding all or most unbudgeted subaccounts.
-- Specifically,
-- --
-- - account names with no budget goal are rewritten to their closest parent with a budget goal -- 1. accounts with no budget goal anywhere in their ancestry are moved
-- (thereby hiding unbudgeted subaccounts of budgeted accounts, regardless of depth limit). -- under the "unbudgeted" top level account.
-- --
-- - accounts with no budgeted parent are rewritten to "<unbudgeted>:topaccountname" -- 2. subaccounts with no budget goal are merged with their closest parent account
-- (hiding subaccounts of unbudgeted accounts, regardless of depth limit), -- with a budget goal, so that only budgeted accounts are shown.
-- unless --show-unbudgeted is provided. -- This can be disabled by --show-unbudgeted.
-- --
-- This is slightly inconsistent/confusing but probably useful.
budgetRollUp :: [AccountName] -> Bool -> Journal -> Journal budgetRollUp :: [AccountName] -> Bool -> Journal -> Journal
budgetRollUp budgetedaccts showunbudgeted j = j { jtxns = remapTxn <$> jtxns j } budgetRollUp budgetedaccts showunbudgeted j = j { jtxns = remapTxn <$> jtxns j }
where where
@ -122,47 +113,15 @@ budgetRollUp budgetedaccts showunbudgeted j = j { jtxns = remapTxn <$> jtxns j }
mapPostings f t = txnTieKnot $ t { tpostings = f $ tpostings t } mapPostings f t = txnTieKnot $ t { tpostings = f $ tpostings t }
remapPosting p = p { paccount = remapAccount $ paccount p, porigin = Just . fromMaybe p $ porigin p } remapPosting p = p { paccount = remapAccount $ paccount p, porigin = Just . fromMaybe p $ porigin p }
where where
remapAccount origAcctName = remapAccount' origAcctName remapAccount a
| hasbudget = a
| hasbudgetedparent = if showunbudgeted then a else budgetedparent
| otherwise = if showunbudgeted then u <> acctsep <> a else u
where where
remapAccount' a hasbudget = a `elem` budgetedaccts
| a `elem` budgetedaccts = a hasbudgetedparent = not $ T.null budgetedparent
| not (T.null parent) = remapAccount' parent budgetedparent = headDef "" $ filter (`elem` budgetedaccts) $ parentAccountNames a
| showunbudgeted = origAcctName u = unbudgetedAccountName
| otherwise = unbudgetedAccount <> acctsep <> a
where
parent = parentAccountName a
--type PeriodicReportRow a =
-- ( AccountName -- A full account name.
-- , AccountName -- Shortened form of the account name to display in tree mode. Usually the leaf name, possibly with parent accounts prefixed.
-- , Int -- Indent level for displaying this account name in tree mode. 0, 1, 2...
-- , [a] -- The data value for each subperiod.
-- , a -- The total of this row's values.
-- , a -- The average of this row's values.
-- )
-- XXX doesn't work right with depth limit, show-unbudgeted, tree mode
-- | Adjust a budget report, altering the account name for any rows which have no
-- budget goals in any period, so that they are grouped under a special "unbudgeted"
-- prefix, and moving all "unbudgeted" rows to the end.
hideOrRenameUnbudgetedAccounts :: [AccountName] -> BudgetReport -> BudgetReport
hideOrRenameUnbudgetedAccounts budgetedaccts (PeriodicReport (spans, rows, totalrow)) =
PeriodicReport (spans, rs ++ unbudgetedrs, totalrow)
where
(rs, unbudgetedrs) = partition (any (isJust . snd) . fourth6) $ map renameacct rows
renameacct r@(a, a', indent, vals, tot, avg) =
-- if any (isJust . snd) vals
if a `elem` budgetedaccts
then r
else (rename a, mayberename a', indent, vals, tot, avg)
where
rename = ("<unbudgeted>:"<>)
mayberename = id -- XXX
-- | Adjust a budget report, removing any rows which do not correspond to
-- one of the provided budgeted accounts.
hideUnbudgetedAccounts :: [AccountName] -> BudgetReport -> BudgetReport
hideUnbudgetedAccounts budgetedaccts (PeriodicReport (spans, rows, totalrow)) =
PeriodicReport (spans, filter ((`elem` budgetedaccts) . first6) rows, totalrow)
-- | Combine a per-account-and-subperiod report of budget goals, and one -- | Combine a per-account-and-subperiod report of budget goals, and one
-- of actual change amounts, into a budget performance report. -- of actual change amounts, into a budget performance report.

View File

@ -34,28 +34,28 @@
$ hledger bal -D -b 2016-12-01 -e 2016-12-04 -f - --budget $ hledger bal -D -b 2016-12-01 -e 2016-12-04 -f - --budget
Budget performance in 2016/12/01-2016/12/03: Budget performance in 2016/12/01-2016/12/03:
|| 2016/12/01 2016/12/02 2016/12/03 || 2016/12/01 2016/12/02 2016/12/03
=======================++============================================================================== ==================++==============================================================================
<unbudgeted>:expenses || 0 0 $40 <unbudgeted> || 0 0 $40
assets:cash || $-10 [ 40% of $-25] $-14 [ 56% of $-25] $-51 [ 204% of $-25] assets:cash || $-10 [ 40% of $-25] $-14 [ 56% of $-25] $-51 [ 204% of $-25]
expenses:food || $10 [ 100% of $10] $9 [ 90% of $10] $11 [ 110% of $10] expenses:food || $10 [ 100% of $10] $9 [ 90% of $10] $11 [ 110% of $10]
expenses:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15] expenses:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15]
-----------------------++------------------------------------------------------------------------------ ------------------++------------------------------------------------------------------------------
|| 0 [ 0] 0 [ 0] 0 [ 0] || 0 [ 0] 0 [ 0] 0 [ 0]
# 2. --show-unbudgeted # 2. --show-unbudgeted
$ hledger bal -D -b 2016-12-01 -e 2016-12-04 -f - --budget --show-unbudgeted $ hledger bal -D -b 2016-12-01 -e 2016-12-04 -f - --budget --show-unbudgeted
Budget performance in 2016/12/01-2016/12/03: Budget performance in 2016/12/01-2016/12/03:
|| 2016/12/01 2016/12/02 2016/12/03 || 2016/12/01 2016/12/02 2016/12/03
==================++============================================================================== ==============================++==============================================================================
assets:cash || $-10 [ 40% of $-25] $-14 [ 56% of $-25] $-51 [ 204% of $-25] <unbudgeted>:expenses:cab || 0 0 $15
expenses:cab || 0 0 $15 <unbudgeted>:expenses:movies || 0 0 $25
expenses:food || $10 [ 100% of $10] $9 [ 90% of $10] $11 [ 110% of $10] assets:cash || $-10 [ 40% of $-25] $-14 [ 56% of $-25] $-51 [ 204% of $-25]
expenses:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15] expenses:food || $10 [ 100% of $10] $9 [ 90% of $10] $11 [ 110% of $10]
expenses:movies || 0 0 $25 expenses:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15]
------------------++------------------------------------------------------------------------------ ------------------------------++------------------------------------------------------------------------------
|| 0 [ 0] 0 [ 0] 0 [ 0] || 0 [ 0] 0 [ 0] 0 [ 0]
# 3. Test that budget works with mix of commodities # 3. Test that budget works with mix of commodities
< <
@ -95,14 +95,14 @@ Budget performance in 2016/12/01-2016/12/03:
$ hledger bal -D -b 2016-12-01 -e 2016-12-04 -f - --budget $ hledger bal -D -b 2016-12-01 -e 2016-12-04 -f - --budget
Budget performance in 2016/12/01-2016/12/03: Budget performance in 2016/12/01-2016/12/03:
|| 2016/12/01 2016/12/02 2016/12/03 || 2016/12/01 2016/12/02 2016/12/03
=======================++===================================================================================== ==================++=====================================================================================
<unbudgeted>:expenses || 0 0 $40 <unbudgeted> || 0 0 $40
assets:cash || $-15 [ 60% of $-25] $-26 [ 104% of $-25] $-51 [ 204% of $-25] assets:cash || $-15 [ 60% of $-25] $-26 [ 104% of $-25] $-51 [ 204% of $-25]
expenses:food || £10 [ 150% of $10] 20 CAD [ 210% of $10] $11 [ 110% of $10] expenses:food || £10 [ 150% of $10] 20 CAD [ 210% of $10] $11 [ 110% of $10]
expenses:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15] expenses:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15]
-----------------------++------------------------------------------------------------------------------------- ------------------++-------------------------------------------------------------------------------------
|| $-15, £10 [ 0] $-21, 20 CAD [ 0] 0 [ 0] || $-15, £10 [ 0] $-21, 20 CAD [ 0] 0 [ 0]
< <
~ daily ~ daily
@ -182,12 +182,12 @@ Budget performance in 2018/01/01w01:
$ hledger -f- bal --budget -D $ hledger -f- bal --budget -D
Budget performance in 2018/01/01-2018/01/04: Budget performance in 2018/01/01-2018/01/04:
|| 2018/01/01 2018/01/02 2018/01/03 2018/01/04 || 2018/01/01 2018/01/02 2018/01/03 2018/01/04
================++======================================================================================================== ==============++========================================================================================================
<unbudgeted>:b || 1 1 1 1 <unbudgeted> || 1 1 1 1
a || 1 1 [ 100% of 1] 1 [ 100% of 1] 1 a || 1 1 [ 100% of 1] 1 [ 100% of 1] 1
----------------++-------------------------------------------------------------------------------------------------------- --------------++--------------------------------------------------------------------------------------------------------
|| 2 2 [ 200% of 1] 2 [ 200% of 1] 2 || 2 2 [ 200% of 1] 2 [ 200% of 1] 2
# 8. Multiple bounded budgets. # 8. Multiple bounded budgets.
< <