From 696e3098c87f5cef0b51a3e4ba050b4e3eb2ce49 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Fri, 26 Apr 2019 11:46:19 -0700 Subject: [PATCH] reg: support --value-at in register reports; cleanups, tests --- hledger-lib/Hledger/Reports/EntriesReport.hs | 17 ++- .../Hledger/Reports/MultiBalanceReports.hs | 14 +- hledger-lib/Hledger/Reports/PostingsReport.hs | 56 ++++++-- hledger-lib/Hledger/Reports/ReportOptions.hs | 1 + hledger/hledger_options.m4.md | 7 +- tests/journal/market-prices.test | 132 ++++++++++++++---- 6 files changed, 164 insertions(+), 63 deletions(-) diff --git a/hledger-lib/Hledger/Reports/EntriesReport.hs b/hledger-lib/Hledger/Reports/EntriesReport.hs index 9eb3b681d..39cccada5 100644 --- a/hledger-lib/Hledger/Reports/EntriesReport.hs +++ b/hledger-lib/Hledger/Reports/EntriesReport.hs @@ -44,15 +44,14 @@ entriesReport opts q j = -- | Convert all the posting amounts in an EntriesReport to their -- default valuation commodities. This means using the Journal's most -- recent applicable market prices before the valuation date. --- The valuation date is set with --value-date and can be: --- a custom date; --- the posting date; --- the last day in the report period, or in the journal if no period --- (or the posting date, if journal is empty - shouldn't happen); --- or today's date (gives an error if today_ is not set in ReportOpts). +-- The valuation date is set with --value-at and can be: +-- each posting's date, +-- the last day in the report period (or in the journal if no period, +-- or the posting dates if journal is empty - shouldn't happen), +-- or today's date (gives an error if today_ is not set in ReportOpts), +-- or a specified date. erValue :: ReportOpts -> Journal -> EntriesReport -> EntriesReport -erValue ropts@ReportOpts{..} j ts = - map txnvalue ts +erValue ropts@ReportOpts{..} j ts = map txnvalue ts where txnvalue t@Transaction{..} = t{tpostings=map postingvalue tpostings} postingvalue p@Posting{..} = p{pamount=mixedAmountValue prices d pamount} @@ -78,7 +77,7 @@ erValue ropts@ReportOpts{..} j ts = AtPeriod -> fromMaybe (postingDate p) mperiodorjournallastday AtNow -> case today_ of Just d -> d - Nothing -> error' "ReportOpts today_ is unset so could not satisfy --value-at=now" + Nothing -> error' "erValue: ReportOpts today_ is unset so could not satisfy --value-at=now" AtDate d -> d tests_EntriesReport = tests "EntriesReport" [ diff --git a/hledger-lib/Hledger/Reports/MultiBalanceReports.hs b/hledger-lib/Hledger/Reports/MultiBalanceReports.hs index f77675980..6d28d5518 100644 --- a/hledger-lib/Hledger/Reports/MultiBalanceReports.hs +++ b/hledger-lib/Hledger/Reports/MultiBalanceReports.hs @@ -275,11 +275,11 @@ multiBalanceReportSpan (MultiBalanceReport (colspans, _, _)) = DateSpan (spanSta -- | Convert all the posting amounts in a MultiBalanceReport to their -- default valuation commodities. This means using the Journal's most -- recent applicable market prices before the valuation date. --- The valuation date is set with --value-date and can be: --- the posting date, --- the last day in the report subperiod, --- today's date (gives an error if today_ is not set in ReportOpts), --- or a custom date. +-- The valuation date is set with --value-at and can be: +-- each posting's date, +-- or the last day in the report subperiod, +-- or today's date (gives an error if today_ is not set in ReportOpts), +-- or a specified date. mbrValue :: ReportOpts -> Journal -> MultiBalanceReport -> MultiBalanceReport mbrValue ReportOpts{..} Journal{..} (MultiBalanceReport (spans, rows, (coltotals, rowtotaltotal, rowavgtotal))) = MultiBalanceReport ( @@ -299,11 +299,11 @@ mbrValue ReportOpts{..} Journal{..} (MultiBalanceReport (spans, rows, (coltotals -- & reversed for quick lookup of the latest price. prices = reverse $ sortOn mpdate jmarketprices d = case value_at_ of - AtTransaction -> error' "sorry, --value-at=transaction is not yet supported with multicolumn balance reports" -- XXX + AtTransaction -> error' "sorry, --value-at=transaction is not yet supported with balance reports" -- XXX AtPeriod -> periodend AtNow -> case today_ of Just d -> d - Nothing -> error' "ReportOpts today_ is unset so could not satisfy --value-at=now" + Nothing -> error' "mbrValue: ReportOpts today_ is unset so could not satisfy --value-at=now" AtDate d -> d -- | Generates a simple non-columnar BalanceReport, but using multiBalanceReport, diff --git a/hledger-lib/Hledger/Reports/PostingsReport.hs b/hledger-lib/Hledger/Reports/PostingsReport.hs index 95182c2ae..ab8dab9c6 100644 --- a/hledger-lib/Hledger/Reports/PostingsReport.hs +++ b/hledger-lib/Hledger/Reports/PostingsReport.hs @@ -1,10 +1,16 @@ -{-# LANGUAGE RecordWildCards, DeriveDataTypeable, FlexibleInstances, TupleSections, OverloadedStrings #-} {-| Postings report, used by the register command. -} +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} + module Hledger.Reports.PostingsReport ( PostingsReport, PostingsReportItem, @@ -228,22 +234,44 @@ negatePostingAmount p = p { pamount = negate $ pamount p } -- | Convert all the posting amounts in a PostingsReport to their -- default valuation commodities. This means using the Journal's most -- recent applicable market prices before the valuation date. --- The valuation date is the specified report end date if any, --- otherwise the current date, otherwise the journal's end date. +-- The valuation date is set with --value-at and can be: +-- each posting's date, +-- the last day in the report period (or in the journal if no period, +-- or the posting dates if journal is empty - shouldn't happen), +-- or today's date (gives an error if today_ is not set in ReportOpts), +-- or a specified date. prValue :: ReportOpts -> Journal -> PostingsReport -> PostingsReport -prValue ropts j r = - let mvaluationdate = periodEnd (period_ ropts) <|> today_ ropts <|> journalEndDate False j - in case mvaluationdate of - Nothing -> r - Just d -> r' +prValue ropts@ReportOpts{..} j@Journal{..} (totallabel, items) = (totallabel, items') + where + items' = [ (md, md2, desc, p{pamount=val $ pamount p}, val tot) + | (md, md2, desc, p, tot) <- items + , let val = mixedAmountValue prices (valuationdate $ postingDate p) + ] + valuationdate pdate = + case value_at_ of + AtTransaction | interval_ /= NoInterval -> error' "sorry, --value-at=transaction is not yet supported with periodic register reports" -- XXX + AtPeriod | interval_ /= NoInterval -> error' "sorry, --value-at=transaction is not yet supported with periodic register reports" -- XXX + AtTransaction -> pdate + AtPeriod -> fromMaybe pdate mperiodorjournallastday + AtNow -> case today_ of + Just d -> d + Nothing -> error' "prValue: ReportOpts today_ is unset so could not satisfy --value-at=now" + AtDate d -> d where - -- prices are in parse order - sort into date then parse order, - -- & reversed for quick lookup of the latest price. - prices = reverse $ sortOn mpdate $ jmarketprices j - (label,items) = r - r' = (label, [(md,md2,desc,valueposting p, mixedAmountValue prices d amt) | (md,md2,desc,p,amt) <- items]) - valueposting p@Posting{..} = p{pamount=mixedAmountValue prices d pamount} + mperiodorjournallastday = mperiodlastday <|> journalEndDate False j + -- Get the last day of the report period. + -- Will be Nothing if no report period is specified, or also + -- if ReportOpts does not have today_ set, since we need that + -- to get the report period robustly. + mperiodlastday :: Maybe Day = do + t <- today_ + let q = queryFromOpts t ropts + qend <- queryEndDate False q + return $ addDays (-1) qend + -- prices are in parse order - sort into date then parse order, + -- & reversed for quick lookup of the latest price. + prices = reverse $ sortOn mpdate jmarketprices -- tests diff --git a/hledger-lib/Hledger/Reports/ReportOptions.hs b/hledger-lib/Hledger/Reports/ReportOptions.hs index 4c554bd19..e482f5ce3 100644 --- a/hledger-lib/Hledger/Reports/ReportOptions.hs +++ b/hledger-lib/Hledger/Reports/ReportOptions.hs @@ -94,6 +94,7 @@ data ReportOpts = ReportOpts { today_ :: Maybe Day -- ^ The current date. A late addition to ReportOpts. -- Optional, but when set it may affect some reports: -- Reports use it when picking a -V valuation date. + -- This is not great, adds indeterminacy. ,period_ :: Period ,interval_ :: Interval ,statuses_ :: [Status] -- ^ Zero, one, or two statuses to be matched diff --git a/hledger/hledger_options.m4.md b/hledger/hledger_options.m4.md index 289e8a9a9..b05ce5d36 100644 --- a/hledger/hledger_options.m4.md +++ b/hledger/hledger_options.m4.md @@ -502,10 +502,12 @@ The precise effect of the keywords is command-specific, but here is their genera - `--value-at=transaction` (or `t`) : Use the prices as of each transaction date (more precisely, each [posting date](/journal.html#posting-dates)). +: (Currently not supported with: balance commands, periodic register reports.) - `--value-at=period` (or `p`) : Use the prices as of the last day of the report period (or each subperiod). : When no report period is specified, this will be the journal's last transaction date. +: (Currently not supported with: periodic register reports.) - `--value-at=now` (or `n`) : Use the prices as of today's date when the report is generated. This is the default. @@ -514,11 +516,6 @@ The precise effect of the keywords is command-specific, but here is their genera : Use the prices as of the given date (8 digits with `-` or `/` or `.` separators). : Eg `--value-at=2019-04-25`. -Currently `--value-at` affects only some commands -([print](/hledger.html#print), -[multicolumn balance reports](/hledger.html#balance)), -and some of the keywords may not be supported by certain commands. - Here are some examples to show its effect: ```journal diff --git a/tests/journal/market-prices.test b/tests/journal/market-prices.test index 0b1b4f4cc..2627f1511 100644 --- a/tests/journal/market-prices.test +++ b/tests/journal/market-prices.test @@ -28,8 +28,24 @@ $ hledger -f- bal -N -V $135.00 expenses:foreign -# 3. Market prices in the future (later than today's date) are always ignored. #453, #683 -# XXX not working right now +# 3. The location of price directives does not matter. +# If multiple directives have the same date, the last parsed is used. +< +3000/01/01 + (a) $100 + +P 2000/1/1 $ €1.35 + +3000/03/03 + (b) $100 + +P 2000/1/1 $ €1.30 + +$ hledger -f- bal -N -V a + €130.00 a + + +# 4. Market prices in the future (later than today's date) are ignored by default. #453, #683 < P 2000/1/1 $ €1.20 P 3000/1/1 $ €1.30 @@ -41,37 +57,16 @@ $ hledger -f- bal -N -V €120.00 a -# 4. The market prices in effect at the report end date are used. -# The location of price directives does not matter. -# If multiple directives have the same date, the last parsed is used. -< -P 3000/3/1 $ €1.40 - -3000/01/01 - (a) $100 - -3000/03/03 - (b) $100 - -P 2000/1/1 $ €1.20 -P 3000/1/1 $ €1.35 -P 3000/1/1 $ €1.30 - -$ hledger -f- bal -N -V a -e 3000/2 - €130.00 a - - -# 5. Again, this time there are prices later than the journal data and -# an explicit report end date brings them into play. +# 5. Market prices in the future are still ignored by default even +# with an explicit future report end date, unlike hledger 1.14-. < P 3000/1/1 $ €1.10 -P 3000/2/1 $ €1.30 3000/01/01 (a) $100 -$ hledger -f- bal -N -V a -e 3000/2 - €130.00 a +$ hledger -f- bal -N -V -e 3000/2 + $100 a # 6. Market prices interact with D directives and with amount style canonicalisation. #131 @@ -210,7 +205,7 @@ Balance changes in 2000q1: # 15. multicolumn balance report valued at transaction is not supported $ hledger -f- bal -M --value-at=transaction ->2 /--value-at=transaction is not yet supported with multicolumn balance reports/ +>2 /not yet supported with balance reports/ >=1 # 16. multicolumn balance report valued at period end @@ -243,3 +238,84 @@ Balance changes in 2000q1: ---++--------------- || 1 B 1 B 1 B +# 19. single column balance report with default value +$ hledger -f- bal -V + 12 B a +-------------------- + 12 B + +# 20. single column balance report valued at transaction is not supported +$ hledger -f- bal --value-at=transaction +>2 /not yet supported with balance reports/ +>=1 + +# 21. single column balance report valued at period end +$ hledger -f- bal --value-at=period + 9 B a +-------------------- + 9 B + +# 22. single column balance report valued at today +$ hledger -f- bal --value-at=now + 12 B a +-------------------- + 12 B + +# 23. single column balance report valued at other date +$ hledger -f- bal --value-at=2000-01-15 + 3 B a +-------------------- + 3 B + +# 24. register report with default value +$ hledger -f- reg -V +2000/01/01 (a) 4 B 4 B +2000/02/01 (a) 4 B 8 B +2000/03/01 (a) 4 B 12 B + +# 25. register report valued at transaction +$ hledger -f- reg --value-at=transaction +2000/01/01 (a) 1 B 1 B +2000/02/01 (a) 2 B 4 B +2000/03/01 (a) 3 B 9 B + +# 26. register report valued at period end +$ hledger -f- reg --value-at=period +2000/01/01 (a) 3 B 3 B +2000/02/01 (a) 3 B 6 B +2000/03/01 (a) 3 B 9 B + +# 27. register report valued at today +$ hledger -f- reg --value-at=now +2000/01/01 (a) 4 B 4 B +2000/02/01 (a) 4 B 8 B +2000/03/01 (a) 4 B 12 B + +# 28. register report valued at other date +$ hledger -f- reg --value-at=2000-01-15 +2000/01/01 (a) 1 B 1 B +2000/02/01 (a) 1 B 2 B +2000/03/01 (a) 1 B 3 B + +# 29. periodic register report with default value +$ hledger -f- reg -V -Q +2000q1 a 12 B 12 B + +# 30. periodic register report valued at transaction +$ hledger -f- reg --value-at=transaction -Q +>2 /not yet supported with periodic register reports/ +>=1 + +# 31. periodic register report valued at period end +$ hledger -f- reg --value-at=period -Q +>2 /not yet supported with periodic register reports/ +>=1 + +# 32. periodic register report valued at today +$ hledger -f- reg --value-at=now -Q +2000q1 a 12 B 12 B + +# 33. periodic register report valued at other date +$ hledger -f- reg --value-at=2000-01-15 -Q +2000q1 a 3 B 3 B +