budget: refactor; show budget goals even with no or zero actual
This makes budget reports more intuitive. It is a temporary hack which can misorder columns in some cases (if actual and budget activity occur in a different range of columns). We should redo this in a more principled way.
This commit is contained in:
parent
2b34ff1184
commit
2a87ea56ff
@ -251,7 +251,7 @@ module Hledger.Cli.Commands.Balance (
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Decimal
|
import Data.Decimal
|
||||||
import Data.List (intercalate, nub)
|
import Data.List
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
@ -329,7 +329,7 @@ balance opts@CliOpts{rawopts_=rawopts,reportopts_=ropts} j = do
|
|||||||
|
|
||||||
_ | boolopt "budget" rawopts -> do
|
_ | boolopt "budget" rawopts -> do
|
||||||
-- multi column budget report
|
-- multi column budget report
|
||||||
reportspan <- dbg1 "reportspan" <$> reportSpan j ropts
|
reportspan <- reportSpan j ropts
|
||||||
let budget = budgetJournal opts reportspan j
|
let budget = budgetJournal opts reportspan j
|
||||||
j' = budgetRollUp opts budget j
|
j' = budgetRollUp opts budget j
|
||||||
report = dbg1 "report" $ multiBalanceReport ropts (queryFromOpts d ropts) j'
|
report = dbg1 "report" $ multiBalanceReport ropts (queryFromOpts d ropts) j'
|
||||||
@ -655,7 +655,7 @@ type ActualAmountsReport = MultiBalanceReport
|
|||||||
type BudgetAmountsReport = MultiBalanceReport
|
type BudgetAmountsReport = MultiBalanceReport
|
||||||
type ActualAmountsTable = Table String String MixedAmount
|
type ActualAmountsTable = Table String String MixedAmount
|
||||||
type BudgetAmountsTable = Table String String MixedAmount
|
type BudgetAmountsTable = Table String String MixedAmount
|
||||||
type ActualAndBudgetAmountsTable = Table String String (MixedAmount, Maybe MixedAmount)
|
type ActualAndBudgetAmountsTable = Table String String (Maybe MixedAmount, Maybe MixedAmount)
|
||||||
type Percentage = Decimal
|
type Percentage = Decimal
|
||||||
|
|
||||||
-- | Given two multi-column balance reports, the first representing a budget
|
-- | Given two multi-column balance reports, the first representing a budget
|
||||||
@ -674,28 +674,29 @@ multiBalanceReportWithBudgetAsText opts budgetr actualr =
|
|||||||
HistoricalBalance -> "Ending balances (historical)"
|
HistoricalBalance -> "Ending balances (historical)"
|
||||||
|
|
||||||
actualandbudgetamts :: ActualAndBudgetAmountsTable
|
actualandbudgetamts :: ActualAndBudgetAmountsTable
|
||||||
actualandbudgetamts = combine (balanceReportAsTable opts actualr) (balanceReportAsTable opts budgetr)
|
actualandbudgetamts = combineTables (balanceReportAsTable opts actualr) (balanceReportAsTable opts budgetr)
|
||||||
|
|
||||||
showcell :: (ActualAmount, Maybe BudgetAmount) -> String
|
showcell :: (Maybe ActualAmount, Maybe BudgetAmount) -> String
|
||||||
showcell (actual, mbudget) =
|
showcell (mactual, mbudget) = actualstr ++ " " ++ budgetstr
|
||||||
case (actual, mbudget) of
|
|
||||||
(actual, Nothing) ->
|
|
||||||
printf ("%"++show actualwidth++"s " ++ replicate (percentwidth + 7 + budgetwidth) ' ') (showamt actual)
|
|
||||||
(actual, Just budget) ->
|
|
||||||
case percentage actual budget of
|
|
||||||
Just pct ->
|
|
||||||
printf ("%"++show actualwidth++"s [%"++show percentwidth++"s%% of %"++show budgetwidth++"s]")
|
|
||||||
(showamt actual) (show $ roundTo 0 pct) (showamt budget)
|
|
||||||
Nothing ->
|
|
||||||
printf ("%"++show actualwidth++"s ["++replicate (percentwidth+5) ' '++"%"++show budgetwidth++"s]")
|
|
||||||
(showamt actual) (showamt budget)
|
|
||||||
where
|
where
|
||||||
actualwidth = 7
|
actualwidth = 7
|
||||||
percentwidth = 4
|
percentwidth = 4
|
||||||
budgetwidth = 5
|
budgetwidth = 5
|
||||||
|
actualstr = printf ("%"++show actualwidth++"s") (maybe "" showamt mactual)
|
||||||
|
budgetstr = case (mactual, mbudget) of
|
||||||
|
(_, Nothing) -> replicate (percentwidth + 7 + budgetwidth) ' '
|
||||||
|
(mactual, Just budget) ->
|
||||||
|
case percentage mactual budget of
|
||||||
|
Just pct ->
|
||||||
|
printf ("[%"++show percentwidth++"s%% of %"++show budgetwidth++"s]")
|
||||||
|
(show $ roundTo 0 pct) (showamt budget)
|
||||||
|
Nothing ->
|
||||||
|
printf ("["++replicate (percentwidth+5) ' '++"%"++show budgetwidth++"s]")
|
||||||
|
(showamt budget)
|
||||||
|
|
||||||
percentage :: ActualAmount -> BudgetAmount -> Maybe Percentage
|
percentage :: Maybe ActualAmount -> BudgetAmount -> Maybe Percentage
|
||||||
percentage actual budget =
|
percentage Nothing _ = Nothing
|
||||||
|
percentage (Just actual) budget =
|
||||||
-- percentage of budget consumed is always computed in the cost basis
|
-- percentage of budget consumed is always computed in the cost basis
|
||||||
case (toCost actual, toCost budget) of
|
case (toCost actual, toCost budget) of
|
||||||
(Mixed [a1], Mixed [a2])
|
(Mixed [a1], Mixed [a2])
|
||||||
@ -711,28 +712,37 @@ multiBalanceReportWithBudgetAsText opts budgetr actualr =
|
|||||||
| otherwise = showMixedAmountOneLineWithoutPrice
|
| otherwise = showMixedAmountOneLineWithoutPrice
|
||||||
|
|
||||||
-- Combine a table of actual amounts and a table of budgeted amounts into
|
-- Combine a table of actual amounts and a table of budgeted amounts into
|
||||||
-- a single table of (actualamount, Maybe budgetamount) tuples.
|
-- a single table of (Maybe actualamount, Maybe budgetamount) tuples.
|
||||||
-- The budget table's row/column titles should be a subset of the actual table's.
|
-- The actual and budget table need not have the same account rows or date columns.
|
||||||
-- (This is satisfied by the construction of the budget report and the
|
-- Every row and column from either table will appear in the combined table.
|
||||||
-- process of rolling up account names.)
|
-- TODO better to combine the reports, not these tables which are just rendering helpers
|
||||||
combine :: ActualAmountsTable -> BudgetAmountsTable -> ActualAndBudgetAmountsTable
|
combineTables :: ActualAmountsTable -> BudgetAmountsTable -> ActualAndBudgetAmountsTable
|
||||||
combine (Table l t d) (Table l' t' d') = Table l t combinedRows
|
combineTables (Table aaccthdrs adatehdrs arows) (Table baccthdrs bdatehdrs brows) =
|
||||||
|
addtotalrow $ Table caccthdrs cdatehdrs crows
|
||||||
where
|
where
|
||||||
-- For all accounts that are present in the budget, zip actual amounts with budget amounts
|
[aaccts, adates, baccts, bdates] = map headerContents [aaccthdrs, adatehdrs, baccthdrs, bdatehdrs]
|
||||||
combinedRows = [ combineRow row budgetRow
|
-- combined account names
|
||||||
| (acct, row) <- zip (headerContents l) d
|
-- TODO Can't sort these or things will fall apart.
|
||||||
, let budgetRow =
|
caccts = dbg2 "caccts" $ init $ (dbg2 "aaccts" $ filter (not . null) aaccts) `union` (dbg2 "baccts" baccts)
|
||||||
if acct == "" then [] -- "" is totals row
|
caccthdrs = T.Group NoLine $ map Header $ caccts
|
||||||
else fromMaybe [] $ Map.lookup acct budgetAccts
|
-- Actual column dates and budget column dates could be different.
|
||||||
]
|
-- TODO Can't easily combine these preserving correct order, will go wrong on monthly reports probably.
|
||||||
-- Budget could cover smaller interval of time than the whole journal.
|
cdates = dbg2 "cdates" $ sort $ (dbg2 "adates" adates) `union` (dbg2 "bdates" bdates)
|
||||||
-- Headers for budget row will always be a sublist of headers of row
|
cdatehdrs = T.Group NoLine $ map Header cdates
|
||||||
combineRow r br =
|
-- corresponding rows of combined actual and/or budget amounts
|
||||||
let reportRow = zip (headerContents t) r
|
crows = [ combineRow (actualRow a) (budgetRow a) | a <- caccts ]
|
||||||
budgetRow = Map.fromList $ zip (headerContents t') br
|
-- totals row
|
||||||
findBudgetVal hdr = Map.lookup hdr budgetRow
|
addtotalrow | no_total_ opts = id
|
||||||
in map (\(hdr, val) -> (val, findBudgetVal hdr)) reportRow
|
| otherwise = (+----+ (row "" $ combineRow (actualRow "") (budgetRow "")))
|
||||||
budgetAccts = Map.fromList $ zip (headerContents l') d'
|
-- helpers
|
||||||
|
combineRow arow brow =
|
||||||
|
dbg1 "row" $ [(actualAmt d, budgetAmt d) | d <- cdates]
|
||||||
|
where
|
||||||
|
actualAmt date = Map.lookup date $ Map.fromList $ zip adates arow
|
||||||
|
budgetAmt date = Map.lookup date $ Map.fromList $ zip bdates brow
|
||||||
|
|
||||||
|
actualRow acct = fromMaybe [] $ Map.lookup acct $ Map.fromList $ zip aaccts arows
|
||||||
|
budgetRow acct = fromMaybe [] $ Map.lookup acct $ Map.fromList $ zip baccts brows
|
||||||
|
|
||||||
-- | Given a table representing a multi-column balance report (for example,
|
-- | Given a table representing a multi-column balance report (for example,
|
||||||
-- made using 'balanceReportAsTable'), render it in a format suitable for
|
-- made using 'balanceReportAsTable'), render it in a format suitable for
|
||||||
|
|||||||
@ -41,7 +41,7 @@ Balance changes in 2016/12/01-2016/12/03:
|
|||||||
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% of 0] 0 [ 0% of 0] 0 [ 0% of 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
|
||||||
@ -55,7 +55,7 @@ Balance changes in 2016/12/01-2016/12/03:
|
|||||||
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]
|
||||||
expenses:movies || 0 0 $25
|
expenses:movies || 0 0 $25
|
||||||
------------------++------------------------------------------------------------------------------
|
------------------++------------------------------------------------------------------------------
|
||||||
|| 0 0 0
|
|| 0 [ 0% of 0] 0 [ 0% of 0] 0 [ 0% of 0]
|
||||||
|
|
||||||
# 3. Test that budget works with mix of commodities
|
# 3. Test that budget works with mix of commodities
|
||||||
<
|
<
|
||||||
@ -102,7 +102,8 @@ Balance changes in 2016/12/01-2016/12/03:
|
|||||||
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 $-21.0, 20 CAD 0
|
|| $-15, £10 [ 0% of 0] $-21.0, 20 CAD [ 0% of 0] 0 [ 0% of 0]
|
||||||
|
# TODO zero totals ^
|
||||||
|
|
||||||
<
|
<
|
||||||
~ daily
|
~ daily
|
||||||
@ -144,7 +145,8 @@ Balance changes in 2018/01/01-2018/01/03:
|
|||||||
b || 1 0 1
|
b || 1 0 1
|
||||||
c || 1 0 1
|
c || 1 0 1
|
||||||
---++------------------------------------------------------------------------------
|
---++------------------------------------------------------------------------------
|
||||||
|| 3 0 3
|
|| 3 [ 30% of 10] 0 [ 0% of 10] 3 [ 30% of 10]
|
||||||
|
# TODO misleading totals ? ^
|
||||||
|
|
||||||
# 6. And with -W it selects the weekly budget, defined by all weekly periodic transactions.
|
# 6. And with -W it selects the weekly budget, defined by all weekly periodic transactions.
|
||||||
$ hledger -f- bal --budget -W
|
$ hledger -f- bal --budget -W
|
||||||
@ -156,7 +158,7 @@ Balance changes in 2018/01/01w01:
|
|||||||
b || 2 [ 2% of 100]
|
b || 2 [ 2% of 100]
|
||||||
c || 2 [ 0% of 1000]
|
c || 2 [ 0% of 1000]
|
||||||
---++--------------------------
|
---++--------------------------
|
||||||
|| 6
|
|| 6 [ 1% of 1100]
|
||||||
|
|
||||||
# 7. A bounded two day budget. The end date is exclusive as usual.
|
# 7. A bounded two day budget. The end date is exclusive as usual.
|
||||||
<
|
<
|
||||||
@ -187,7 +189,7 @@ Balance changes in 2018/01/01-2018/01/04:
|
|||||||
<unbudgeted>:b || 1 1 1 1
|
<unbudgeted>:b || 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 2 2
|
|| 2 2 [ 200% of 1] 2 [ 200% of 1] 2
|
||||||
|
|
||||||
# 8. Multiple bounded budgets.
|
# 8. Multiple bounded budgets.
|
||||||
<
|
<
|
||||||
@ -216,7 +218,7 @@ Balance changes in 2018/01/01-2018/01/04:
|
|||||||
===++========================================================================================================
|
===++========================================================================================================
|
||||||
a || 1 [ 100% of 1] 1 [ 100% of 1] 1 [ 10% of 10] 1 [ 10% of 10]
|
a || 1 [ 100% of 1] 1 [ 100% of 1] 1 [ 10% of 10] 1 [ 10% of 10]
|
||||||
---++--------------------------------------------------------------------------------------------------------
|
---++--------------------------------------------------------------------------------------------------------
|
||||||
|| 1 1 1 1
|
|| 1 [ 100% of 1] 1 [ 100% of 1] 1 [ 10% of 10] 1 [ 10% of 10]
|
||||||
|
|
||||||
# 9. A "from A to B" budget should not be included in a report beginning on B.
|
# 9. A "from A to B" budget should not be included in a report beginning on B.
|
||||||
$ hledger -f- bal --budget -D -b 2018/1/3
|
$ hledger -f- bal --budget -D -b 2018/1/3
|
||||||
@ -226,5 +228,37 @@ Balance changes in 2018/01/03-2018/01/04:
|
|||||||
===++====================================================
|
===++====================================================
|
||||||
a || 1 [ 10% of 10] 1 [ 10% of 10]
|
a || 1 [ 10% of 10] 1 [ 10% of 10]
|
||||||
---++----------------------------------------------------
|
---++----------------------------------------------------
|
||||||
|| 1 1
|
|| 1 [ 10% of 10] 1 [ 10% of 10]
|
||||||
|
|
||||||
|
<
|
||||||
|
~ daily
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2018/1/2
|
||||||
|
(a) 2
|
||||||
|
|
||||||
|
2018/1/2
|
||||||
|
(a) -2
|
||||||
|
|
||||||
|
# 10. accounts with non-zero budget should be shown by default
|
||||||
|
# even if there are no actual transactions in the period,
|
||||||
|
# or if the actual amount is zero.
|
||||||
|
# $ hledger -f- bal --budget -D date:2018/1/1-2018/1/3
|
||||||
|
# Balance changes in 2018/01/01-2018/01/02:
|
||||||
|
|
||||||
|
# || 2018/01/01 2018/01/02
|
||||||
|
# ===++====================================================
|
||||||
|
# a || [ 1] [ 1]
|
||||||
|
# ---++----------------------------------------------------
|
||||||
|
# || [ 1] [ 1]
|
||||||
|
|
||||||
|
# 11. With -E, zeroes are shown
|
||||||
|
$ hledger -f- bal --budget -D date:2018/1/1-2018/1/3 -E
|
||||||
|
Balance changes in 2018/01/01-2018/01/02:
|
||||||
|
|
||||||
|
|| 2018/01/01 2018/01/02
|
||||||
|
===++====================================================
|
||||||
|
a || 0 [ 0% of 1] 0 [ 0% of 1]
|
||||||
|
---++----------------------------------------------------
|
||||||
|
|| 0 [ 0% of 1] 0 [ 0% of 1]
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user