diff --git a/bin/hledger-budget.hs b/bin/hledger-budget.hs index cf599787a..6fe4251db 100755 --- a/bin/hledger-budget.hs +++ b/bin/hledger-budget.hs @@ -13,7 +13,6 @@ import Data.List import Data.String.Here import System.Console.CmdArgs import Hledger.Cli -import Hledger.Data.AutoTransaction -- hledger-budget REPORT-COMMAND [--no-offset] [--no-buckets] [OPTIONS...] diff --git a/hledger-lib/Hledger/Data.hs b/hledger-lib/Hledger/Data.hs index 4f3e83696..cfd4bef73 100644 --- a/hledger-lib/Hledger/Data.hs +++ b/hledger-lib/Hledger/Data.hs @@ -22,6 +22,7 @@ module Hledger.Data ( module Hledger.Data.StringFormat, module Hledger.Data.Timeclock, module Hledger.Data.Transaction, + module Hledger.Data.AutoTransaction, module Hledger.Data.Types, tests_Hledger_Data ) @@ -42,6 +43,7 @@ import Hledger.Data.RawOptions import Hledger.Data.StringFormat import Hledger.Data.Timeclock import Hledger.Data.Transaction +import Hledger.Data.AutoTransaction import Hledger.Data.Types tests_Hledger_Data :: Test diff --git a/hledger-lib/Hledger/Reports/ReportOptions.hs b/hledger-lib/Hledger/Reports/ReportOptions.hs index cd02e35c3..e76c2e497 100644 --- a/hledger-lib/Hledger/Reports/ReportOptions.hs +++ b/hledger-lib/Hledger/Reports/ReportOptions.hs @@ -104,6 +104,7 @@ data ReportOpts = ReportOpts { -- eg in the income section of an income statement, this helps --sort-amount know -- how to sort negative numbers. ,color_ :: Bool + ,forecast_ :: Bool } deriving (Show, Data, Typeable) instance Default ReportOpts where def = defreportopts @@ -134,6 +135,7 @@ defreportopts = ReportOpts def def def + def rawOptsToReportOpts :: RawOpts -> IO ReportOpts rawOptsToReportOpts rawopts = checkReportOpts <$> do @@ -164,6 +166,7 @@ rawOptsToReportOpts rawopts = checkReportOpts <$> do ,sort_amount_ = boolopt "sort-amount" rawopts' ,pretty_tables_ = boolopt "pretty-tables" rawopts' ,color_ = color + ,forecast_ = boolopt "forecast" rawopts' } -- | Do extra validation of raw option values, raising an error if there's a problem. diff --git a/hledger/Hledger/Cli/CliOptions.hs b/hledger/Hledger/Cli/CliOptions.hs index 35c6537c3..11292f55f 100644 --- a/hledger/Hledger/Cli/CliOptions.hs +++ b/hledger/Hledger/Cli/CliOptions.hs @@ -155,6 +155,7 @@ reportflags = [ ,flagNone ["empty","E"] (setboolopt "empty") "show items with zero amount, normally hidden" ,flagNone ["cost","B"] (setboolopt "cost") "convert amounts to their cost at transaction time (using the transaction price, if any)" ,flagNone ["value","V"] (setboolopt "value") "convert amounts to their market value on the report end date (using the most recent applicable market price, if any)" + ,flagNone ["forecast"] (\opts -> setboolopt "forecast" opts) "generate forecast transactions" ] -- | Common output-related flags: --output-file, --output-format... diff --git a/hledger/Hledger/Cli/Utils.hs b/hledger/Hledger/Cli/Utils.hs index 1934b3f71..813a6d38b 100644 --- a/hledger/Hledger/Cli/Utils.hs +++ b/hledger/Hledger/Cli/Utils.hs @@ -31,7 +31,7 @@ import Data.List import Data.Maybe import qualified Data.Text as T import qualified Data.Text.IO as T -import Data.Time (Day) +import Data.Time (Day, addDays) import Data.Word import Numeric import Safe (readMay) @@ -70,6 +70,7 @@ withJournalDo opts cmd = do . anonymiseByOpts opts . journalApplyAliases (aliasesFromOpts opts) <=< journalApplyValue (reportopts_ opts) + <=< journalAddForecast opts either error' f ej -- | Apply the pivot transformation on a journal, if option is present. @@ -117,6 +118,29 @@ journalApplyValue ropts j = do = id return $ convert j +-- | Run PeriodicTransactions from journal from today or journal end to requested end day. +-- Add generated transactions to the journal +journalAddForecast :: CliOpts -> Journal -> IO Journal +journalAddForecast opts j = do + today <- getCurrentDay + -- Create forecast starting from end of journal + 1 day, and until the end of requested reporting period + -- If end is not provided, do 180 days of forecast. + -- Note that jdatespan already returns last day + 1 + let startDate = fromMaybe today $ spanEnd (jdatespan j) + endDate = fromMaybe (addDays 180 today) $ periodEnd (period_ ropts) + dates = DateSpan (Just startDate) (Just endDate) + withForecast = [makeForecast t | pt <- jperiodictxns j, t <- runPeriodicTransaction pt dates, spanContainsDate dates (tdate t) ] ++ (jtxns j) + makeForecast t = txnTieKnot $ t { tdescription = T.pack "Forecast transaction" } + ropts = reportopts_ opts + if forecast_ ropts + then return $ journalBalanceTransactions' opts j { jtxns = withForecast } + else return j + where + journalBalanceTransactions' opts j = + let assrt = not . ignore_assertions_ $ inputopts_ opts + in + either error' id $ journalBalanceTransactions assrt j + -- | Write some output to stdout or to a file selected by --output-file. -- If the file exists it will be overwritten. writeOutput :: CliOpts -> String -> IO () diff --git a/tests/budget/forecast.test b/tests/budget/forecast.test new file mode 100644 index 000000000..0e9a1904f --- /dev/null +++ b/tests/budget/forecast.test @@ -0,0 +1,102 @@ +# Test --forecast switch +hledger bal -M -b 2016-11 -e 2017-02 -f - --forecast +<<< +2016/12/31 + expenses:housing $600 + assets:cash + +~ monthly from 2016/1 + income $-1000 + expenses:food $20 + expenses:leisure $15 + expenses:grocery $30 + assets:cash +>>> +Balance changes in 2016/12/01-2017/01/31: + + || 2016/12 2017/01 +==================++================== + assets:cash || $-600 $935 + expenses:food || 0 $20 + expenses:grocery || 0 $30 + expenses:housing || $600 0 + expenses:leisure || 0 $15 + income || 0 $-1000 +------------------++------------------ + || 0 0 + +>>>2 +>>>=0 + + +hledger print -b 2016-11 -e 2017-02 -f - --forecast +<<< +2016/12/31 + expenses:housing $600 + assets:cash + +~ monthly from 2016/1 + income $-1000 + expenses:food $20 + expenses:leisure $15 + expenses:grocery $30 + assets:cash +>>> +2016/12/31 + expenses:housing $600 + assets:cash + +2017/01/01 Forecast transaction + income $-1000 + expenses:food $20 + expenses:leisure $15 + expenses:grocery $30 + assets:cash + +>>>2 +>>>=0 + + +hledger register -b 2016-11 -e 2017-02 -f - --forecast +<<< +2016/12/31 + expenses:housing $600 + assets:cash + +~ monthly from 2016/1 + income $-1000 + expenses:food $20 + expenses:leisure $15 + expenses:grocery $30 + assets:cash +>>> +2016/12/31 expenses:housing $600 $600 + assets:cash $-600 0 +2017/01/01 Forecast transact.. income $-1000 $-1000 + expenses:food $20 $-980 + expenses:leisure $15 $-965 + expenses:grocery $30 $-935 + assets:cash $935 0 +>>>2 +>>>=0 + +# Check that --forecast generates transactions only after last transaction in journal +hledger register -b 2015-12 -e 2017-02 -f - assets:cash --forecast +<<< +2016/01/01 + expenses:fun $10 ; more fireworks + assets:cash + +2016/12/02 + expenses:housing $600 + assets:cash + +~ yearly from 2016 + income $-10000 ; bonus + assets:cash +>>> +2016/01/01 assets:cash $-10 $-10 +2016/12/02 assets:cash $-600 $-610 +2017/01/01 Forecast transact.. assets:cash $10000 $9390 +>>>2 +>>>=0