parent
							
								
									cbf4029b8b
								
							
						
					
					
						commit
						b7413edf22
					
				| @ -22,6 +22,7 @@ module Hledger.Reports.ReportOptions ( | |||||||
|   whichDateFromOpts, |   whichDateFromOpts, | ||||||
|   journalSelectingAmountFromOpts, |   journalSelectingAmountFromOpts, | ||||||
|   intervalFromRawOpts, |   intervalFromRawOpts, | ||||||
|  |   forecastPeriodFromRawOpts, | ||||||
|   queryFromOpts, |   queryFromOpts, | ||||||
|   queryFromOptsOnly, |   queryFromOptsOnly, | ||||||
|   queryOptsFromOpts, |   queryOptsFromOpts, | ||||||
| @ -124,7 +125,7 @@ data ReportOpts = ReportOpts { | |||||||
|       --   sign normalisation, converting normally negative subreports to |       --   sign normalisation, converting normally negative subreports to | ||||||
|       --   normally positive for a more conventional display. |       --   normally positive for a more conventional display. | ||||||
|     ,color_          :: Bool |     ,color_          :: Bool | ||||||
|     ,forecast_       :: Bool |     ,forecast_       :: Maybe DateSpan | ||||||
|     ,transpose_      :: Bool |     ,transpose_      :: Bool | ||||||
|  } deriving (Show, Data, Typeable) |  } deriving (Show, Data, Typeable) | ||||||
| 
 | 
 | ||||||
| @ -192,7 +193,7 @@ rawOptsToReportOpts rawopts = checkReportOpts <$> do | |||||||
|     ,invert_      = boolopt "invert" rawopts' |     ,invert_      = boolopt "invert" rawopts' | ||||||
|     ,pretty_tables_ = boolopt "pretty-tables" rawopts' |     ,pretty_tables_ = boolopt "pretty-tables" rawopts' | ||||||
|     ,color_       = color |     ,color_       = color | ||||||
|     ,forecast_    = boolopt "forecast" rawopts' |     ,forecast_    = forecastPeriodFromRawOpts d rawopts' | ||||||
|     ,transpose_   = boolopt "transpose" rawopts' |     ,transpose_   = boolopt "transpose" rawopts' | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -313,6 +314,18 @@ intervalFromRawOpts = lastDef NoInterval . collectopts intervalfromrawopt | |||||||
|       | n == "yearly"    = Just $ Years 1 |       | n == "yearly"    = Just $ Years 1 | ||||||
|       | otherwise = Nothing |       | otherwise = Nothing | ||||||
| 
 | 
 | ||||||
|  | -- | get period expression from --forecast option | ||||||
|  | forecastPeriodFromRawOpts :: Day -> RawOpts -> Maybe DateSpan | ||||||
|  | forecastPeriodFromRawOpts d opts = | ||||||
|  |   case  | ||||||
|  |     dbg2 "forecastopt" $ maybestringopt "forecast" opts | ||||||
|  |   of | ||||||
|  |     Nothing -> Nothing | ||||||
|  |     Just "" -> Just nulldatespan | ||||||
|  |     Just str -> | ||||||
|  |       either (\e -> usageError $ "could not parse forecast period : "++customErrorBundlePretty e) (Just . snd) $  | ||||||
|  |       parsePeriodExpr d $ stripquotes $ T.pack str | ||||||
|  |      | ||||||
| -- | Extract the interval from the parsed -p/--period expression. | -- | Extract the interval from the parsed -p/--period expression. | ||||||
| -- Return Nothing if an interval is not explicitly defined. | -- Return Nothing if an interval is not explicitly defined. | ||||||
| extractIntervalOrNothing :: (Interval, DateSpan) -> Maybe Interval | extractIntervalOrNothing :: (Interval, DateSpan) -> Maybe Interval | ||||||
|  | |||||||
| @ -1414,7 +1414,7 @@ This can be used to match transactions generated "just now", | |||||||
| rather than generated in the past and saved to the journal. | rather than generated in the past and saved to the journal. | ||||||
| 
 | 
 | ||||||
| Forecast transactions start on the first occurrence, and end on the last occurrence, | Forecast transactions start on the first occurrence, and end on the last occurrence, | ||||||
| of their interval within the forecast period. The forecast period: | of their interval within the forecast period. The default forecast period: | ||||||
| 
 | 
 | ||||||
| - begins on the later of | - begins on the later of | ||||||
|   - the report start date if specified with -b/-p/date: |   - the report start date if specified with -b/-p/date: | ||||||
| @ -1425,8 +1425,9 @@ of their interval within the forecast period. The forecast period: | |||||||
|   or 180 days from today. |   or 180 days from today. | ||||||
| 
 | 
 | ||||||
| where "today" means the current date at report time. | where "today" means the current date at report time. | ||||||
| The "later of" rule ensures that forecast transactions do not overlap normal transactions in time; | The "later of" rule ensures that by default forecast transactions do not overlap normal transactions in time; | ||||||
| they will begin only after normal transactions end. | they will begin only after normal transactions end. If you wish to use your own forecast period, | ||||||
|  | you can provied it via `--forecast=PERIODICEXPR`. | ||||||
| 
 | 
 | ||||||
| Forecasting can be useful for estimating balances into the future, | Forecasting can be useful for estimating balances into the future, | ||||||
| and experimenting with different scenarios. | and experimenting with different scenarios. | ||||||
|  | |||||||
| @ -86,8 +86,8 @@ asInit d reset ui@UIState{ | |||||||
|     q = And [queryFromOpts d ropts, excludeforecastq (forecast_ ropts)] |     q = And [queryFromOpts d ropts, excludeforecastq (forecast_ ropts)] | ||||||
|       where |       where | ||||||
|         -- Except in forecast mode, exclude future/forecast transactions. |         -- Except in forecast mode, exclude future/forecast transactions. | ||||||
|         excludeforecastq True = Any |         excludeforecastq (Just _) = Any | ||||||
|         excludeforecastq False =  -- not:date:tomorrow- not:tag:generated-transaction |         excludeforecastq Nothing  =  -- not:date:tomorrow- not:tag:generated-transaction | ||||||
|           And [ |           And [ | ||||||
|              Not (Date $ DateSpan (Just $ addDays 1 d) Nothing) |              Not (Date $ DateSpan (Just $ addDays 1 d) Nothing) | ||||||
|             ,Not (Tag "generated-transaction" Nothing) |             ,Not (Tag "generated-transaction" Nothing) | ||||||
| @ -218,7 +218,7 @@ asDraw UIState{aopts=_uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}} | |||||||
|               ,("-+", str "depth") |               ,("-+", str "depth") | ||||||
|               ,("T", renderToggle (tree_ ropts) "flat" "tree") |               ,("T", renderToggle (tree_ ropts) "flat" "tree") | ||||||
|               ,("H", renderToggle (not ishistorical) "end-bals" "changes") |               ,("H", renderToggle (not ishistorical) "end-bals" "changes") | ||||||
|               ,("F", renderToggle1 (forecast_ ropts) "forecast") |               ,("F", renderToggle1 (isJust $ forecast_ ropts) "forecast") | ||||||
|               --,("/", "filter") |               --,("/", "filter") | ||||||
|               --,("DEL", "unfilter") |               --,("DEL", "unfilter") | ||||||
|               --,("ESC", "cancel/top") |               --,("ESC", "cancel/top") | ||||||
| @ -339,7 +339,7 @@ asHandle ui0@UIState{ | |||||||
|         VtyEvent (EvKey (KChar 'U') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleUnmarked ui |         VtyEvent (EvKey (KChar 'U') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleUnmarked ui | ||||||
|         VtyEvent (EvKey (KChar 'P') []) -> asCenterAndContinue $ regenerateScreens j d $ togglePending ui |         VtyEvent (EvKey (KChar 'P') []) -> asCenterAndContinue $ regenerateScreens j d $ togglePending ui | ||||||
|         VtyEvent (EvKey (KChar 'C') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleCleared ui |         VtyEvent (EvKey (KChar 'C') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleCleared ui | ||||||
|         VtyEvent (EvKey (KChar 'F') []) -> continue $ regenerateScreens j d $ toggleForecast ui |         VtyEvent (EvKey (KChar 'F') []) -> continue $ regenerateScreens j d $ toggleForecast d ui | ||||||
| 
 | 
 | ||||||
|         VtyEvent (EvKey (KDown)     [MShift]) -> continue $ regenerateScreens j d $ shrinkReportPeriod d ui |         VtyEvent (EvKey (KDown)     [MShift]) -> continue $ regenerateScreens j d $ shrinkReportPeriod d ui | ||||||
|         VtyEvent (EvKey (KUp)       [MShift]) -> continue $ regenerateScreens j d $ growReportPeriod d ui |         VtyEvent (EvKey (KUp)       [MShift]) -> continue $ regenerateScreens j d $ growReportPeriod d ui | ||||||
|  | |||||||
| @ -66,7 +66,7 @@ main = do | |||||||
| 
 | 
 | ||||||
|   -- always include forecasted periodic transactions when loading data; |   -- always include forecasted periodic transactions when loading data; | ||||||
|   -- they will be toggled on and off in the UI. |   -- they will be toggled on and off in the UI. | ||||||
|   let copts' = copts{reportopts_=ropts{forecast_=True}} |   let copts' = copts{reportopts_=ropts{forecast_=Just $ fromMaybe nulldatespan (forecast_ ropts)}} | ||||||
| 
 | 
 | ||||||
|   case True of |   case True of | ||||||
|     _ | "help"            `inRawOpts` rawopts -> putStr (showModeUsage uimode) |     _ | "help"            `inRawOpts` rawopts -> putStr (showModeUsage uimode) | ||||||
|  | |||||||
| @ -72,8 +72,8 @@ rsInit d reset ui@UIState{aopts=_uopts@UIOpts{cliopts_=CliOpts{reportopts_=ropts | |||||||
|     q = And [queryFromOpts d ropts', excludeforecastq (forecast_ ropts)] |     q = And [queryFromOpts d ropts', excludeforecastq (forecast_ ropts)] | ||||||
|       where |       where | ||||||
|         -- Except in forecast mode, exclude future/forecast transactions. |         -- Except in forecast mode, exclude future/forecast transactions. | ||||||
|         excludeforecastq True = Any |         excludeforecastq (Just _) = Any | ||||||
|         excludeforecastq False =  -- not:date:tomorrow- not:tag:generated-transaction |         excludeforecastq Nothing  =  -- not:date:tomorrow- not:tag:generated-transaction | ||||||
|           And [ |           And [ | ||||||
|              Not (Date $ DateSpan (Just $ addDays 1 d) Nothing) |              Not (Date $ DateSpan (Just $ addDays 1 d) Nothing) | ||||||
|             ,Not (Tag "generated-transaction" Nothing) |             ,Not (Tag "generated-transaction" Nothing) | ||||||
| @ -237,7 +237,7 @@ rsDraw UIState{aopts=_uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}} | |||||||
| --              ,("RIGHT", str "transaction") | --              ,("RIGHT", str "transaction") | ||||||
|               ,("T", renderToggle (tree_ ropts) "flat(-subs)" "tree(+subs)") -- rsForceInclusive may override, but use tree_ to ensure a visible toggle effect |               ,("T", renderToggle (tree_ ropts) "flat(-subs)" "tree(+subs)") -- rsForceInclusive may override, but use tree_ to ensure a visible toggle effect | ||||||
|               ,("H", renderToggle (not ishistorical) "historical" "period") |               ,("H", renderToggle (not ishistorical) "historical" "period") | ||||||
|               ,("F", renderToggle1 (forecast_ ropts) "forecast") |               ,("F", renderToggle1 (isJust $ forecast_ ropts) "forecast") | ||||||
| --               ,("a", "add") | --               ,("a", "add") | ||||||
| --               ,("g", "reload") | --               ,("g", "reload") | ||||||
| --               ,("q", "quit") | --               ,("q", "quit") | ||||||
| @ -336,7 +336,7 @@ rsHandle ui@UIState{ | |||||||
|         VtyEvent (EvKey (KChar 'U') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleUnmarked ui |         VtyEvent (EvKey (KChar 'U') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleUnmarked ui | ||||||
|         VtyEvent (EvKey (KChar 'P') []) -> rsCenterAndContinue $ regenerateScreens j d $ togglePending ui |         VtyEvent (EvKey (KChar 'P') []) -> rsCenterAndContinue $ regenerateScreens j d $ togglePending ui | ||||||
|         VtyEvent (EvKey (KChar 'C') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleCleared ui |         VtyEvent (EvKey (KChar 'C') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleCleared ui | ||||||
|         VtyEvent (EvKey (KChar 'F') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleForecast ui |         VtyEvent (EvKey (KChar 'F') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleForecast d ui | ||||||
| 
 | 
 | ||||||
|         VtyEvent (EvKey (KChar '/') []) -> continue $ regenerateScreens j d $ showMinibuffer ui |         VtyEvent (EvKey (KChar '/') []) -> continue $ regenerateScreens j d $ showMinibuffer ui | ||||||
|         VtyEvent (EvKey (KDown)     [MShift]) -> continue $ regenerateScreens j d $ shrinkReportPeriod d ui |         VtyEvent (EvKey (KDown)     [MShift]) -> continue $ regenerateScreens j d $ shrinkReportPeriod d ui | ||||||
|  | |||||||
| @ -164,11 +164,15 @@ toggleHistorical ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts | |||||||
| -- overkill, probably we should just hide/show the periodic | -- overkill, probably we should just hide/show the periodic | ||||||
| -- transactions with a query for their special tag. | -- transactions with a query for their special tag. | ||||||
| -- | -- | ||||||
| toggleForecast :: UIState -> UIState | toggleForecast :: Day -> UIState -> UIState | ||||||
| toggleForecast ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = | toggleForecast d ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = | ||||||
|   ui{aopts=uopts{cliopts_=copts'}} |   ui{aopts=uopts{cliopts_=copts'}} | ||||||
|   where |   where | ||||||
|     copts' = copts{reportopts_=ropts{forecast_=not $ forecast_ ropts}} |     copts' = copts{reportopts_=ropts{forecast_=forecast'}} | ||||||
|  |     forecast' = | ||||||
|  |       case forecast_ ropts of | ||||||
|  |         Just _  -> Nothing | ||||||
|  |         Nothing -> forecastPeriodFromRawOpts d $ rawopts_ copts | ||||||
| 
 | 
 | ||||||
| -- | Toggle between showing all and showing only real (non-virtual) items. | -- | Toggle between showing all and showing only real (non-virtual) items. | ||||||
| toggleReal :: UIState -> UIState | toggleReal :: UIState -> UIState | ||||||
|  | |||||||
| @ -181,8 +181,12 @@ reportflags = [ | |||||||
| 
 | 
 | ||||||
|   -- generated postings/transactions |   -- generated postings/transactions | ||||||
|  ,flagNone ["auto"]          (setboolopt "auto") "apply automated posting rules to modify transactions" |  ,flagNone ["auto"]          (setboolopt "auto") "apply automated posting rules to modify transactions" | ||||||
|  ,flagNone ["forecast"]      (setboolopt "forecast") "generate future transactions from periodic transaction rules, for the next 6 months or till report end date. In hledger-ui, also make ordinary future transactions visible." |  ,flagOpt "" ["forecast"]    (\s opts -> Right $ setopt "forecast" s opts) "PERIODEXP"  | ||||||
| 
 |   (unlines | ||||||
|  |    [ "Generate periodic transactions (from periodic transaction rules). By default these begin after the latest recorded transaction, and end 6 months from today, or at the report end date." | ||||||
|  |    , "Also, in hledger-ui, make future transactions visible." | ||||||
|  |    , "Note that = (and not a space) is required before PERIODEXP if you wish to supply it." | ||||||
|  |    ]) | ||||||
|  ] |  ] | ||||||
| 
 | 
 | ||||||
| -- | Common flags that are accepted but not shown in --help, | -- | Common flags that are accepted but not shown in --help, | ||||||
|  | |||||||
| @ -114,18 +114,22 @@ anonymiseByOpts opts = | |||||||
| -- from today if unspecified. | -- from today if unspecified. | ||||||
| -- | -- | ||||||
| journalAddForecast :: CliOpts -> Journal -> IO Journal | journalAddForecast :: CliOpts -> Journal -> IO Journal | ||||||
| journalAddForecast opts@CliOpts{inputopts_=iopts, reportopts_=ropts} j = do | journalAddForecast CliOpts{inputopts_=iopts, reportopts_=ropts} j = do | ||||||
|   today <- getCurrentDay |   today <- getCurrentDay | ||||||
| 
 | 
 | ||||||
|   -- "They can start no earlier than: the day following the latest normal transaction in the journal (or today if there are none)." |   -- "They can start no earlier than: the day following the latest normal transaction in the journal (or today if there are none)." | ||||||
|   let mjournalend   = dbg2 "journalEndDate" $ journalEndDate False j  -- ignore secondary dates |   let mjournalend   = dbg2 "journalEndDate" $ journalEndDate False j  -- ignore secondary dates | ||||||
|       forecaststart = dbg2 "forecaststart" $ fromMaybe today mjournalend |       forecastbeginDefault = dbg2 "forecastbeginDefault" $ fromMaybe today mjournalend | ||||||
| 
 | 
 | ||||||
|   -- "They end on or before the specified report end date, or 180 days from today if unspecified." |   -- "They end on or before the specified report end date, or 180 days from today if unspecified." | ||||||
|   mspecifiedend <-  snd . dbg2 "specifieddates" <$> specifiedStartEndDates ropts |   mspecifiedend <-  snd . dbg2 "specifieddates" <$> specifiedStartEndDates ropts | ||||||
|   let forecastend = dbg2 "forecastend" $ fromMaybe (addDays 180 today) mspecifiedend |   let forecastendDefault = dbg2 "forecastendDefault" $ fromMaybe (addDays 180 today) mspecifiedend | ||||||
| 
 |        | ||||||
|   let forecastspan = DateSpan (Just forecaststart) (Just forecastend) |   let forecastspan = dbg2 "forecastspan" $ | ||||||
|  |         spanDefaultsFrom | ||||||
|  |           (fromMaybe nulldatespan $ dbg2 "forecastspan flag" $ forecast_ ropts) | ||||||
|  |           (DateSpan (Just forecastbeginDefault) (Just forecastendDefault)) | ||||||
|  |            | ||||||
|       forecasttxns = |       forecasttxns = | ||||||
|         [ txnTieKnot t | pt <- jperiodictxns j |         [ txnTieKnot t | pt <- jperiodictxns j | ||||||
|                        , t <- runPeriodicTransaction pt forecastspan |                        , t <- runPeriodicTransaction pt forecastspan | ||||||
| @ -135,12 +139,12 @@ journalAddForecast opts@CliOpts{inputopts_=iopts, reportopts_=ropts} j = do | |||||||
|       forecasttxns' = (if auto_ iopts then modifyTransactions (jtxnmodifiers j) else id) forecasttxns |       forecasttxns' = (if auto_ iopts then modifyTransactions (jtxnmodifiers j) else id) forecasttxns | ||||||
| 
 | 
 | ||||||
|   return $ |   return $ | ||||||
|     if forecast_ ropts |     case forecast_ ropts of | ||||||
|       then journalBalanceTransactions' opts j{ jtxns = concat [jtxns j, forecasttxns'] } |       Just _  -> journalBalanceTransactions' iopts j{ jtxns = concat [jtxns j, forecasttxns'] } | ||||||
|       else j |       Nothing -> j | ||||||
|   where |   where | ||||||
|     journalBalanceTransactions' opts j = |     journalBalanceTransactions' iopts j = | ||||||
|       let assrt = not . ignore_assertions_ $ inputopts_ opts |       let assrt = not . ignore_assertions_ $ iopts | ||||||
|       in |       in | ||||||
|        either error' id $ journalBalanceTransactions assrt j |        either error' id $ journalBalanceTransactions assrt j | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -156,3 +156,48 @@ Y 2000 | |||||||
| 
 | 
 | ||||||
| >>>2 | >>>2 | ||||||
| >>>=0 | >>>=0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # 8. A balance report with forecast-begin enabling transaction before report end | ||||||
|  | hledger bal -M -b 2016-10 -e 2017-02 -f - --forecast=20160801- | ||||||
|  | <<< | ||||||
|  | 2016/12/31 | ||||||
|  |     expenses:housing  $600 | ||||||
|  |     assets:cash | ||||||
|  | 
 | ||||||
|  | ~ monthly from 2016/1  salary | ||||||
|  |     income  $-1000 | ||||||
|  |     assets:cash | ||||||
|  | >>> | ||||||
|  | Balance changes in 2016-10-01-2017-01-31: | ||||||
|  | 
 | ||||||
|  |                   ||    Oct     Nov     Dec     Jan  | ||||||
|  | ==================++================================ | ||||||
|  |  assets:cash      ||  $1000   $1000    $400   $1000  | ||||||
|  |  expenses:housing ||      0       0    $600       0  | ||||||
|  |  income           || $-1000  $-1000  $-1000  $-1000  | ||||||
|  | ------------------++-------------------------------- | ||||||
|  |                   ||      0       0       0       0  | ||||||
|  | >>>2 | ||||||
|  | >>>=0 | ||||||
|  | 
 | ||||||
|  | # 9. Parse error in malformed forecast period expression | ||||||
|  | hledger bal -M -b 2016-10 -e 2017-02 -f - --forecast=20160801-foobar | ||||||
|  | <<< | ||||||
|  | 2016/12/31 | ||||||
|  |     expenses:housing  $600 | ||||||
|  |     assets:cash | ||||||
|  | 
 | ||||||
|  | ~ monthly from 2016/1  salary | ||||||
|  |     income  $-1000 | ||||||
|  |     assets:cash | ||||||
|  | >>> | ||||||
|  | >>>2 | ||||||
|  | hledger: could not parse forecast period : 1:10: | ||||||
|  |   | | ||||||
|  | 1 | 20160801-foobar | ||||||
|  |   |          ^ | ||||||
|  | unexpected 'f' | ||||||
|  | expecting end of input | ||||||
|  |  (use -h to see usage) | ||||||
|  | >>>=1 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user