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