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 = dbg2 "forecastspan" $
|
||||||
|
spanDefaultsFrom
|
||||||
|
(fromMaybe nulldatespan $ dbg2 "forecastspan flag" $ forecast_ ropts)
|
||||||
|
(DateSpan (Just forecastbeginDefault) (Just forecastendDefault))
|
||||||
|
|
||||||
let forecastspan = DateSpan (Just forecaststart) (Just forecastend)
|
|
||||||
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