mirror of
https://github.com/simonmichael/hledger.git
synced 2024-11-08 07:09:28 +03:00
budget: declaration and (actual) amount sorting for bal --budget
Account declaration-aware sorting is the default throughout hledger now.
This commit is contained in:
parent
f2b4fca9b0
commit
855bd54d19
@ -35,12 +35,15 @@ import Hledger.Utils
|
||||
--import Hledger.Read (mamountp')
|
||||
import Hledger.Reports.ReportOptions
|
||||
import Hledger.Reports.ReportTypes
|
||||
import Hledger.Reports.BalanceReport (sortAccountItemsLike)
|
||||
import Hledger.Reports.MultiBalanceReports
|
||||
|
||||
|
||||
-- for reference:
|
||||
--
|
||||
--type MultiBalanceReportRow = (AccountName, AccountName, Int, [MixedAmount], MixedAmount, MixedAmount)
|
||||
--type MultiBalanceReportTotals = ([MixedAmount], MixedAmount, MixedAmount) -- (Totals list, sum of totals, average of totals)
|
||||
|
||||
--
|
||||
--type PeriodicReportRow a =
|
||||
-- ( AccountName -- ^ A full account name.
|
||||
-- , [a] -- ^ The data value for each subperiod.
|
||||
@ -53,7 +56,9 @@ type BudgetTotal = Total
|
||||
type BudgetAverage = Average
|
||||
|
||||
-- | A budget report tracks expected and actual changes per account and subperiod.
|
||||
type BudgetReport = PeriodicReport (Maybe Change, Maybe BudgetGoal)
|
||||
type BudgetCell = (Maybe Change, Maybe BudgetGoal)
|
||||
type BudgetReport = PeriodicReport BudgetCell
|
||||
type BudgetReportRow = PeriodicReportRow BudgetCell
|
||||
|
||||
-- | Calculate budget goals from all periodic transactions,
|
||||
-- actual balance changes from the regular transactions,
|
||||
@ -79,8 +84,54 @@ budgetReport ropts assrt showunbudgeted reportspan d j =
|
||||
-- it should be safe to replace it with the latter, so they combine well.
|
||||
| interval_ ropts == NoInterval = MultiBalanceReport (actualspans, budgetgoalitems, budgetgoaltotals)
|
||||
| otherwise = budgetgoalreport
|
||||
budgetreport = combineBudgetAndActual budgetgoalreport' actualreport
|
||||
sortedbudgetreport = sortBudgetReport ropts j budgetreport
|
||||
in
|
||||
dbg1 "budgetreport" $ combineBudgetAndActual budgetgoalreport' actualreport
|
||||
dbg1 "sortedbudgetreport" sortedbudgetreport
|
||||
|
||||
-- | Sort a budget report's rows according to options.
|
||||
sortBudgetReport :: ReportOpts -> Journal -> BudgetReport -> BudgetReport
|
||||
sortBudgetReport ropts j (PeriodicReport (ps, rows, trow)) = PeriodicReport (ps, sortedrows, trow)
|
||||
where
|
||||
sortedrows
|
||||
| sort_amount_ ropts && tree_ ropts = sortTreeBURByActualAmount rows
|
||||
| sort_amount_ ropts = sortFlatBURByActualAmount rows
|
||||
| otherwise = sortByAccountDeclaration rows
|
||||
|
||||
-- Sort a tree-mode budget report's rows by total actual amount at each level.
|
||||
sortTreeBURByActualAmount :: [BudgetReportRow] -> [BudgetReportRow]
|
||||
sortTreeBURByActualAmount rows = sortedrows
|
||||
where
|
||||
anamesandrows = [(first6 r, r) | r <- rows]
|
||||
anames = map fst anamesandrows
|
||||
atotals = [(a,tot) | (a,_,_,_,(tot,_),_) <- rows]
|
||||
accounttree = accountTree "root" anames
|
||||
accounttreewithbals = mapAccounts setibalance accounttree
|
||||
where
|
||||
setibalance a = a{aibalance=
|
||||
fromMaybe 0 $ -- when there's no actual amount, assume 0; will mess up with negative amounts ? TODO
|
||||
fromMaybe (error "sortTreeByAmount 1") $ -- should not happen, but it's ugly; TODO
|
||||
lookup (aname a) atotals
|
||||
}
|
||||
sortedaccounttree = sortAccountTreeByAmount (fromMaybe NormallyPositive $ normalbalance_ ropts) accounttreewithbals
|
||||
sortedanames = map aname $ drop 1 $ flattenAccounts sortedaccounttree
|
||||
sortedrows = sortAccountItemsLike sortedanames anamesandrows
|
||||
|
||||
-- Sort a flat-mode budget report's rows by total actual amount.
|
||||
sortFlatBURByActualAmount :: [BudgetReportRow] -> [BudgetReportRow]
|
||||
sortFlatBURByActualAmount = sortBy (maybeflip $ comparing (fst . fifth6))
|
||||
where
|
||||
maybeflip = if normalbalance_ ropts == Just NormallyNegative then id else flip
|
||||
|
||||
-- Sort the report rows by account declaration order then account name.
|
||||
-- <unbudgeted> remains at the top.
|
||||
sortByAccountDeclaration rows = sortedrows
|
||||
where
|
||||
(unbudgetedrow,rows') = partition ((=="<unbudgeted>").first6) rows
|
||||
anamesandrows = [(first6 r, r) | r <- rows']
|
||||
anames = map fst anamesandrows
|
||||
sortedanames = sortAccountNamesByDeclaration j (tree_ ropts) anames
|
||||
sortedrows = unbudgetedrow ++ sortAccountItemsLike sortedanames anamesandrows
|
||||
|
||||
-- | Use all periodic transactions in the journal to generate
|
||||
-- budget transactions in the specified report period.
|
||||
@ -184,41 +235,6 @@ combineBudgetAndActual
|
||||
rows :: [PeriodicReportRow (Maybe Change, Maybe BudgetGoal)] =
|
||||
sortBy (comparing first6) $ rows1 ++ rows2
|
||||
|
||||
-- -- like MultiBalanceReport
|
||||
-- sortedrows
|
||||
-- | sort_amount_ opts && tree_ opts = sortTreeBURByAmount items
|
||||
-- | sort_amount_ opts = sortFlatBURByAmount items
|
||||
-- | otherwise = sortBURByAccountDeclaration items
|
||||
--
|
||||
-- where
|
||||
-- -- Sort the report rows, representing a tree of accounts, by row total at each level.
|
||||
-- sortTreeMBRByAmount rows = sortedrows
|
||||
-- where
|
||||
-- anamesandrows = [(first6 r, r) | r <- rows]
|
||||
-- anames = map fst anamesandrows
|
||||
-- atotals = [(a,tot) | (a,_,_,_,tot,_) <- rows]
|
||||
-- accounttree = accountTree "root" anames
|
||||
-- accounttreewithbals = mapAccounts setibalance accounttree
|
||||
-- where
|
||||
-- -- should not happen, but it's ugly; TODO
|
||||
-- setibalance a = a{aibalance=fromMaybe (error "sortTreeBURByAmount 1") $ lookup (aname a) atotals}
|
||||
-- sortedaccounttree = sortAccountTreeByAmount (fromMaybe NormallyPositive $ normalbalance_ opts) accounttreewithbals
|
||||
-- sortedanames = map aname $ drop 1 $ flattenAccounts sortedaccounttree
|
||||
-- sortedrows = sortAccountItemsLike sortedanames anamesandrows
|
||||
--
|
||||
-- -- Sort the report rows, representing a flat account list, by row total.
|
||||
-- sortFlatBURByAmount = sortBy (maybeflip $ comparing fifth6)
|
||||
-- where
|
||||
-- maybeflip = if normalbalance_ opts == Just NormallyNegative then id else flip
|
||||
--
|
||||
-- -- Sort the report rows by account declaration order then account name.
|
||||
-- sortBURByAccountDeclaration rows = sortedrows
|
||||
-- where
|
||||
-- anamesandrows = [(first6 r, r) | r <- rows]
|
||||
-- anames = map fst anamesandrows
|
||||
-- sortedanames = sortAccountNamesByDeclaration j (tree_ opts) anames
|
||||
-- sortedrows = sortAccountItemsLike sortedanames anamesandrows
|
||||
|
||||
-- TODO: grand total & average shows 0% when there are no actual amounts, inconsistent with other cells
|
||||
totalrow =
|
||||
( ""
|
||||
|
@ -832,7 +832,8 @@ account assets:bank:checking
|
||||
|
||||
### Account display order
|
||||
|
||||
Account directives have another purpose: they set the display order of accounts in reports.
|
||||
Account directives have another purpose: they set the order in which accounts are displayed,
|
||||
in hledger reports, hledger-ui accounts screen, hledger-web sidebar etc.
|
||||
For example, say you have these top-level accounts:
|
||||
```shell
|
||||
$ accounts -1
|
||||
@ -867,13 +868,14 @@ misc
|
||||
other
|
||||
```
|
||||
|
||||
Ie, declared accounts first, in the order they were declared, followed by undeclared accounts in alphabetic order.
|
||||
Ie, declared accounts first, in the order they were declared, followed by any undeclared accounts in alphabetic order.
|
||||
|
||||
This is supported in most reports organised by account (accounts/balance/bs/bse/cf/is).
|
||||
It is not yet supported in budget reports (balance --budget) or hledger-web's sidebar.
|
||||
|
||||
Note sorting is done at each level of the account tree (within each group of sibling accounts
|
||||
under the same parent).
|
||||
Note that sorting is done at each level of the account tree (within each group of sibling accounts under the same parent).
|
||||
This directive:
|
||||
```journal
|
||||
account other:zoo
|
||||
```
|
||||
would influence the position of `zoo` among `other`'s subaccounts, but not the position of `other` among the top-level accounts.
|
||||
|
||||
### Rewriting accounts
|
||||
|
||||
|
@ -250,7 +250,6 @@ module Hledger.Cli.Commands.Balance (
|
||||
,tests_Balance
|
||||
) where
|
||||
|
||||
import Control.Monad (when)
|
||||
import Data.List
|
||||
import Data.Maybe
|
||||
--import qualified Data.Map as Map
|
||||
@ -313,7 +312,6 @@ balance opts@CliOpts{rawopts_=rawopts,reportopts_=ropts} j = do
|
||||
case (budget, interval) of
|
||||
(True, _) -> do
|
||||
-- single or multicolumn budget report
|
||||
when (sort_amount_ ropts) $ error' "Sorry, --sort-amount is not yet supported with --budget." -- TODO
|
||||
reportspan <- reportSpan j ropts
|
||||
let budgetreport = dbg1 "budgetreport" $ budgetReport ropts assrt showunbudgeted reportspan d j
|
||||
where
|
||||
|
241
tests/budget/sorting.test
Normal file
241
tests/budget/sorting.test
Normal file
@ -0,0 +1,241 @@
|
||||
#* budget report sorting
|
||||
# These tests below aren't very thorough, could use more varied amounts
|
||||
# and pathological cases.
|
||||
|
||||
#** Default sort without account declarations
|
||||
# already tested in budget.test, but for completeness:
|
||||
|
||||
<
|
||||
~ daily from 2016/1/1
|
||||
expenses:food $10
|
||||
expenses:leisure $15
|
||||
assets:cash
|
||||
|
||||
2016/12/01
|
||||
expenses:food $10
|
||||
assets:cash
|
||||
|
||||
2016/12/02
|
||||
expenses:food $9
|
||||
assets:cash
|
||||
|
||||
2016/12/03
|
||||
expenses:food $11
|
||||
assets:cash
|
||||
|
||||
2016/12/02
|
||||
expenses:leisure $5
|
||||
assets:cash
|
||||
|
||||
2016/12/03
|
||||
expenses:movies $25
|
||||
assets:cash
|
||||
|
||||
2016/12/03
|
||||
expenses:cab $15
|
||||
assets:cash
|
||||
|
||||
$ hledger -f- bal --budget -DTN
|
||||
Budget performance in 2016/12/01-2016/12/03:
|
||||
|
||||
|| 2016/12/01 2016/12/02 2016/12/03 Total
|
||||
==================++========================================================================================================
|
||||
<unbudgeted> || 0 0 $40 $40
|
||||
assets:cash || $-10 [ 40% of $-25] $-14 [ 56% of $-25] $-51 [ 204% of $-25] $-75 [ 100% of $-75]
|
||||
expenses:food || $10 [ 100% of $10] $9 [ 90% of $10] $11 [ 110% of $10] $30 [ 100% of $30]
|
||||
expenses:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15] $5 [ 11% of $45]
|
||||
|
||||
#** Default sort with account declarations
|
||||
|
||||
<
|
||||
account expenses
|
||||
account expenses:leisure
|
||||
|
||||
~ daily from 2016/1/1
|
||||
expenses:food $10
|
||||
expenses:leisure $15
|
||||
assets:cash
|
||||
|
||||
2016/12/01
|
||||
expenses:food $10
|
||||
assets:cash
|
||||
|
||||
2016/12/02
|
||||
expenses:food $9
|
||||
assets:cash
|
||||
|
||||
2016/12/03
|
||||
expenses:food $11
|
||||
assets:cash
|
||||
|
||||
2016/12/02
|
||||
expenses:leisure $5
|
||||
assets:cash
|
||||
|
||||
2016/12/03
|
||||
expenses:movies $25
|
||||
assets:cash
|
||||
|
||||
2016/12/03
|
||||
expenses:cab $15
|
||||
assets:cash
|
||||
|
||||
$ hledger -f- bal --budget -DTN
|
||||
Budget performance in 2016/12/01-2016/12/03:
|
||||
|
||||
|| 2016/12/01 2016/12/02 2016/12/03 Total
|
||||
==================++========================================================================================================
|
||||
<unbudgeted> || 0 0 $40 $40
|
||||
expenses:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15] $5 [ 11% of $45]
|
||||
expenses:food || $10 [ 100% of $10] $9 [ 90% of $10] $11 [ 110% of $10] $30 [ 100% of $30]
|
||||
assets:cash || $-10 [ 40% of $-25] $-14 [ 56% of $-25] $-51 [ 204% of $-25] $-75 [ 100% of $-75]
|
||||
|
||||
# # 2. --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:
|
||||
|
||||
# || 2016/12/01 2016/12/02 2016/12/03
|
||||
# ==============================++==============================================================================
|
||||
# <unbudgeted>:expenses:cab || 0 0 $15
|
||||
# <unbudgeted>:expenses:movies || 0 0 $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:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15]
|
||||
# ------------------------------++------------------------------------------------------------------------------
|
||||
# || 0 [ 0] 0 [ 0] 0 [ 0]
|
||||
|
||||
# # 3. Test that budget works with mix of commodities
|
||||
# <
|
||||
# 2016/12/01
|
||||
# expenses:food £10 @@ $15
|
||||
# assets:cash
|
||||
|
||||
# 2016/12/02
|
||||
# expenses:food 10 CAD @ $1
|
||||
# assets:cash
|
||||
|
||||
# 2016/12/02
|
||||
# expenses:food 10 CAD @ $1.1
|
||||
# assets:cash
|
||||
|
||||
# 2016/12/03
|
||||
# expenses:food $11
|
||||
# assets:cash
|
||||
|
||||
# 2016/12/02
|
||||
# expenses:leisure $5
|
||||
# assets:cash
|
||||
|
||||
# 2016/12/03
|
||||
# expenses:movies $25
|
||||
# assets:cash
|
||||
|
||||
# 2016/12/03
|
||||
# expenses:cab $15
|
||||
# assets:cash
|
||||
|
||||
# ~ daily from 2016/1/1
|
||||
# expenses:food $10
|
||||
# expenses:leisure $15
|
||||
# assets:cash
|
||||
|
||||
# $ hledger bal -D -b 2016-12-01 -e 2016-12-04 -f - --budget
|
||||
# Budget performance in 2016/12/01-2016/12/03:
|
||||
|
||||
# || 2016/12/01 2016/12/02 2016/12/03
|
||||
# ==================++=====================================================================================
|
||||
# <unbudgeted> || 0 0 $40
|
||||
# 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:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15]
|
||||
# ------------------++-------------------------------------------------------------------------------------
|
||||
# || $-15, £10 [ 0] $-21, 20 CAD [ 0] 0 [ 0]
|
||||
|
||||
#** Sort by actual amount, flat mode.
|
||||
|
||||
$ hledger -f- bal --budget -DTNS
|
||||
Budget performance in 2016/12/01-2016/12/03:
|
||||
|
||||
|| 2016/12/01 2016/12/02 2016/12/03 Total
|
||||
==================++========================================================================================================
|
||||
<unbudgeted> || 0 0 $40 $40
|
||||
expenses:food || $10 [ 100% of $10] $9 [ 90% of $10] $11 [ 110% of $10] $30 [ 100% of $30]
|
||||
expenses:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15] $5 [ 11% of $45]
|
||||
assets:cash || $-10 [ 40% of $-25] $-14 [ 56% of $-25] $-51 [ 204% of $-25] $-75 [ 100% of $-75]
|
||||
|
||||
#** Sort by actual amount, tree mode.
|
||||
|
||||
$ hledger -f- bal --budget -DTNS --tree
|
||||
Budget performance in 2016/12/01-2016/12/03:
|
||||
|
||||
|| 2016/12/01 2016/12/02 2016/12/03 Total
|
||||
==============++========================================================================================================
|
||||
<unbudgeted> || 0 0 $40 $40
|
||||
expenses || $10 [ 40% of $25] $14 [ 56% of $25] $11 [ 44% of $25] $35 [ 47% of $75]
|
||||
food || $10 [ 100% of $10] $9 [ 90% of $10] $11 [ 110% of $10] $30 [ 100% of $30]
|
||||
leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15] $5 [ 11% of $45]
|
||||
assets || $-10 [ 40% of $-25] $-14 [ 56% of $-25] $-51 [ 204% of $-25] $-75 [ 100% of $-75]
|
||||
cash || $-10 [ 40% of $-25] $-14 [ 56% of $-25] $-51 [ 204% of $-25] $-75 [ 100% of $-75]
|
||||
|
||||
#** other ?
|
||||
# with --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:
|
||||
|
||||
# || 2016/12/01 2016/12/02 2016/12/03
|
||||
# ==============================++==============================================================================
|
||||
# <unbudgeted>:expenses:cab || 0 0 $15
|
||||
# <unbudgeted>:expenses:movies || 0 0 $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:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15]
|
||||
# ------------------------------++------------------------------------------------------------------------------
|
||||
# || 0 [ 0] 0 [ 0] 0 [ 0]
|
||||
|
||||
# with multiple commodities
|
||||
# <
|
||||
# 2016/12/01
|
||||
# expenses:food £10 @@ $15
|
||||
# assets:cash
|
||||
|
||||
# 2016/12/02
|
||||
# expenses:food 10 CAD @ $1
|
||||
# assets:cash
|
||||
|
||||
# 2016/12/02
|
||||
# expenses:food 10 CAD @ $1.1
|
||||
# assets:cash
|
||||
|
||||
# 2016/12/03
|
||||
# expenses:food $11
|
||||
# assets:cash
|
||||
|
||||
# 2016/12/02
|
||||
# expenses:leisure $5
|
||||
# assets:cash
|
||||
|
||||
# 2016/12/03
|
||||
# expenses:movies $25
|
||||
# assets:cash
|
||||
|
||||
# 2016/12/03
|
||||
# expenses:cab $15
|
||||
# assets:cash
|
||||
|
||||
# ~ daily from 2016/1/1
|
||||
# expenses:food $10
|
||||
# expenses:leisure $15
|
||||
# assets:cash
|
||||
|
||||
# $ hledger bal -D -b 2016-12-01 -e 2016-12-04 -f - --budget
|
||||
# Budget performance in 2016/12/01-2016/12/03:
|
||||
|
||||
# || 2016/12/01 2016/12/02 2016/12/03
|
||||
# ==================++=====================================================================================
|
||||
# <unbudgeted> || 0 0 $40
|
||||
# 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:leisure || 0 [ 0% of $15] $5 [ 33% of $15] 0 [ 0% of $15]
|
||||
# ------------------++-------------------------------------------------------------------------------------
|
||||
# || $-15, £10 [ 0] $-21, 20 CAD [ 0] 0 [ 0]
|
||||
|
Loading…
Reference in New Issue
Block a user