imp: bal: budget goals now respect -H (#1879)

This commit is contained in:
Simon Michael 2022-07-09 09:59:17 +01:00
parent 3e60e784f3
commit 42bcbad2fd
4 changed files with 72 additions and 12 deletions

View File

@ -76,7 +76,7 @@ module Hledger.Data.Dates (
daysInSpan, daysInSpan,
tests_Dates tests_Dates
) , intervalStartBefore)
where where
import qualified Control.Monad.Fail as Fail (MonadFail, fail) import qualified Control.Monad.Fail as Fail (MonadFail, fail)
@ -548,6 +548,13 @@ prevyear = startofyear . addGregorianYearsClip (-1)
nextyear = startofyear . addGregorianYearsClip 1 nextyear = startofyear . addGregorianYearsClip 1
startofyear day = fromGregorian y 1 1 where (y,_,_) = toGregorian day startofyear day = fromGregorian y 1 1 where (y,_,_) = toGregorian day
-- Get the natural start for the given interval that falls on or before the given day.
intervalStartBefore :: Interval -> Day -> Day
intervalStartBefore int d =
case splitSpan int (DateSpan (Just d) (Just $ addDays 1 d)) of
(DateSpan (Just start) _:_) -> start
_ -> d
-- | For given date d find year-long interval that starts on given -- | For given date d find year-long interval that starts on given
-- MM/DD of year and covers it. -- MM/DD of year and covers it.
-- The given MM and DD should be basically valid (1-12 & 1-31), -- The given MM and DD should be basically valid (1-12 & 1-31),

View File

@ -73,7 +73,9 @@ instance Show PeriodicTransaction where
--nullperiodictransaction is defined in Types.hs --nullperiodictransaction is defined in Types.hs
-- | Generate transactions from 'PeriodicTransaction' within a 'DateSpan' -- | Generate transactions from 'PeriodicTransaction' within a 'DateSpan'.
-- This should be a closed span with both start and end dates specified;
-- an open ended span will generate no transactions.
-- --
-- Note that new transactions require 'txnTieKnot' post-processing. -- Note that new transactions require 'txnTieKnot' post-processing.
-- The new transactions will have three tags added: -- The new transactions will have three tags added:

View File

@ -28,7 +28,7 @@ import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM import qualified Data.HashMap.Strict as HM
import Data.List (find, partition, transpose, foldl') import Data.List (find, partition, transpose, foldl')
import Data.List.Extra (nubSort) import Data.List.Extra (nubSort)
import Data.Maybe (fromMaybe, catMaybes) import Data.Maybe (fromMaybe, catMaybes, isJust)
import Data.Map (Map) import Data.Map (Map)
import qualified Data.Map as Map import qualified Data.Map as Map
import qualified Data.Set as S import qualified Data.Set as S
@ -36,6 +36,7 @@ import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TB import qualified Data.Text.Lazy.Builder as TB
import Safe (maximumDef, minimumDef)
--import System.Console.CmdArgs.Explicit as C --import System.Console.CmdArgs.Explicit as C
--import Lucid as L --import Lucid as L
import qualified Text.Tabular.AsciiWide as Tab import qualified Text.Tabular.AsciiWide as Tab
@ -96,27 +97,47 @@ budgetReport rspec bopts reportspan j = dbg4 "sortedbudgetreport" budgetreport
| otherwise = budgetgoalreport | otherwise = budgetgoalreport
budgetreport = combineBudgetAndActual ropts j budgetgoalreport' actualreport budgetreport = combineBudgetAndActual ropts j budgetgoalreport' actualreport
-- | Use all periodic transactions in the journal to generate -- | Use all (or all matched by --budget's argument) periodic transactions in the journal
-- budget goal transactions in the specified date span. -- to generate budget goal transactions in the specified date span (and before, to support
-- Budget goal transactions are similar to forecast transactions except -- --historical. The precise start date is the natural start date of the largest interval
-- their purpose and effect is to define balance change goals, per account and period, -- of the active periodic transaction rules that is on or before the earlier of journal start date,
-- for BudgetReport. -- report start date.)
-- Budget goal transactions are similar to forecast transactions except their purpose
-- and effect is to define balance change goals, per account and period, for BudgetReport.
--
journalAddBudgetGoalTransactions :: BalancingOpts -> ReportOpts -> DateSpan -> Journal -> Journal journalAddBudgetGoalTransactions :: BalancingOpts -> ReportOpts -> DateSpan -> Journal -> Journal
journalAddBudgetGoalTransactions bopts ropts reportspan j = journalAddBudgetGoalTransactions bopts ropts reportspan j =
either error' id $ journalBalanceTransactions bopts j{ jtxns = budgetts } -- PARTIAL: either error' id $ journalBalanceTransactions bopts j{ jtxns = budgetts } -- PARTIAL:
where where
budgetspan = dbg3 "budget span" $ reportspan budgetspan = dbg3 "budget span" $ DateSpan mbudgetgoalsstartdate (spanEnd reportspan)
pat = fromMaybe "" $ dbg3 "budget pattern" $ T.toLower <$> budgetpat_ ropts where
mbudgetgoalsstartdate =
-- We want to also generate budget goal txns before the report start date, in case -H is used.
-- What should the actual starting date for goal txns be ? This gets a little tricky: consider a
-- journal with a "~ monthly" periodic transaction rule, where the first transaction is on 1/5.
-- Users will certainly expect a budget goal for january, but "~ monthly" generates transactions
-- on the first of month, and starting from 1/5 would exclude 1/1.
-- Hopefully the following procedure will produce intuitive behaviour in general:
-- from the earlier of the journal start date and the report start date,
-- move backward to the nearest natural start date of the largest period seen among the
-- active periodic transactions (so here: monthly, 1/5 -> 1/1).
case minimumDef Nothing $ filter isJust [journalStartDate False j, spanStart reportspan] of
Nothing -> Nothing
Just d -> Just $ intervalStartBefore biggestinterval d
where
biggestinterval = maximumDef (Days 1) $ map ptinterval budgetpts
-- select periodic transactions matching a pattern -- select periodic transactions matching a pattern
-- (the argument of the (final) --budget option). -- (the argument of the (final) --budget option).
-- XXX two limitations/wishes, requiring more extensive type changes: -- XXX two limitations/wishes, requiring more extensive type changes:
-- - give an error if pat is non-null and matches no periodic txns -- - give an error if pat is non-null and matches no periodic txns
-- - allow a regexp or a full hledger query, not just a substring -- - allow a regexp or a full hledger query, not just a substring
pat = fromMaybe "" $ dbg3 "budget pattern" $ T.toLower <$> budgetpat_ ropts
budgetpts = [pt | pt <- jperiodictxns j, pat `T.isInfixOf` T.toLower (ptdescription pt)]
budgetts = budgetts =
dbg5 "budget goal txns" $ dbg5 "budget goal txns" $
[makeBudgetTxn t [makeBudgetTxn t
| pt <- jperiodictxns j | pt <- budgetpts
, pat `T.isInfixOf` T.toLower (ptdescription pt)
, t <- runPeriodicTransaction pt budgetspan , t <- runPeriodicTransaction pt budgetspan
] ]
makeBudgetTxn t = txnTieKnot $ t { tdescription = T.pack "Budget transaction" } makeBudgetTxn t = txnTieKnot $ t { tdescription = T.pack "Budget transaction" }

View File

@ -635,3 +635,33 @@ $ hledger -f- bal --budget=weekly -p 2021-01
$ hledger -f- bal --budget=monthly -p 2021-01 $ hledger -f- bal --budget=monthly -p 2021-01
> !/aaa/ > !/aaa/
>= >=
# 33. Cumulative budget report.
<
~ monthly
(a) 10
2022-01-05
(a) 10
2022-02-03
(a) 5
$ hledger -f- bal --budget -M --cumulative
Budget performance in 2022-01-01..2022-02-28:
|| 2022-01-31 2022-02-28
===++=================================
a || 10 [100% of 10] 15 [75% of 20]
---++---------------------------------
|| 10 [100% of 10] 15 [75% of 20]
# 34. Historical budget report.
$ hledger -f- bal --budget -M --historical -b 2022-02-01
Budget performance in 2022-02:
|| 2022-02-28
===++================
a || 15 [75% of 20]
---++----------------
|| 15 [75% of 20]