lib: Create mixedAmountApplyValuationAfterSumFromOptsWith for doing any valuation needed after summing amounts.
This commit is contained in:
		
							parent
							
								
									6fb3dfdbb2
								
							
						
					
					
						commit
						940b2c6ab9
					
				| @ -42,13 +42,13 @@ import Data.HashMap.Strict (HashMap) | |||||||
| import qualified Data.HashMap.Strict as HM | import qualified Data.HashMap.Strict as HM | ||||||
| import Data.Map (Map) | import Data.Map (Map) | ||||||
| import qualified Data.Map as M | import qualified Data.Map as M | ||||||
| import Data.Maybe (fromMaybe, mapMaybe) | import Data.Maybe (fromMaybe, isJust, mapMaybe) | ||||||
| import Data.Ord (Down(..)) | import Data.Ord (Down(..)) | ||||||
| #if !(MIN_VERSION_base(4,11,0)) | #if !(MIN_VERSION_base(4,11,0)) | ||||||
| import Data.Semigroup ((<>)) | import Data.Semigroup ((<>)) | ||||||
| #endif | #endif | ||||||
| import Data.Semigroup (sconcat) | import Data.Semigroup (sconcat) | ||||||
| import Data.Time.Calendar (Day, addDays, fromGregorian) | import Data.Time.Calendar (Day, fromGregorian) | ||||||
| import Safe (lastDef, minimumMay) | import Safe (lastDef, minimumMay) | ||||||
| 
 | 
 | ||||||
| import Hledger.Data | import Hledger.Data | ||||||
| @ -115,7 +115,7 @@ multiBalanceReportWith rspec' j priceoracle = report | |||||||
|     rspec      = dbg3 "reportopts" $ makeReportQuery rspec' reportspan |     rspec      = dbg3 "reportopts" $ makeReportQuery rspec' reportspan | ||||||
| 
 | 
 | ||||||
|     -- Group postings into their columns. |     -- Group postings into their columns. | ||||||
|     colps    = dbg5 "colps"  $ getPostingsByColumn rspec j reportspan |     colps = dbg5 "colps" $ getPostingsByColumn rspec j priceoracle reportspan | ||||||
| 
 | 
 | ||||||
|     -- The matched accounts with a starting balance. All of these should appear |     -- The matched accounts with a starting balance. All of these should appear | ||||||
|     -- in the report, even if they have no postings during the report period. |     -- in the report, even if they have no postings during the report period. | ||||||
| @ -143,7 +143,7 @@ compoundBalanceReportWith rspec' j priceoracle subreportspecs = cbr | |||||||
|     rspec      = dbg3 "reportopts" $ makeReportQuery rspec' reportspan |     rspec      = dbg3 "reportopts" $ makeReportQuery rspec' reportspan | ||||||
| 
 | 
 | ||||||
|     -- Group postings into their columns. |     -- Group postings into their columns. | ||||||
|     colps    = dbg5 "colps"  $ getPostingsByColumn rspec j reportspan |     colps = dbg5 "colps" $ getPostingsByColumn rspec j priceoracle reportspan | ||||||
| 
 | 
 | ||||||
|     -- The matched accounts with a starting balance. All of these should appear |     -- The matched accounts with a starting balance. All of these should appear | ||||||
|     -- in the report, even if they have no postings during the report period. |     -- in the report, even if they have no postings during the report period. | ||||||
| @ -191,7 +191,7 @@ startingBalances rspec@ReportSpec{rsQuery=query,rsOpts=ropts} j priceoracle repo | |||||||
|     fmap (M.findWithDefault nullacct precedingspan) acctmap |     fmap (M.findWithDefault nullacct precedingspan) acctmap | ||||||
|   where |   where | ||||||
|     acctmap = calculateReportMatrix rspec' j priceoracle mempty |     acctmap = calculateReportMatrix rspec' j priceoracle mempty | ||||||
|             . M.singleton precedingspan . map fst $ getPostings rspec' j |             . M.singleton precedingspan . map fst $ getPostings rspec' j priceoracle | ||||||
| 
 | 
 | ||||||
|     rspec' = rspec{rsQuery=startbalq,rsOpts=ropts'} |     rspec' = rspec{rsQuery=startbalq,rsOpts=ropts'} | ||||||
|     -- If we're re-valuing every period, we need to have the unvalued start |     -- If we're re-valuing every period, we need to have the unvalued start | ||||||
| @ -229,11 +229,11 @@ makeReportQuery rspec reportspan | |||||||
|     dateqcons        = if date2_ (rsOpts rspec) then Date2 else Date |     dateqcons        = if date2_ (rsOpts rspec) then Date2 else Date | ||||||
| 
 | 
 | ||||||
| -- | Group postings, grouped by their column | -- | Group postings, grouped by their column | ||||||
| getPostingsByColumn :: ReportSpec -> Journal -> DateSpan -> Map DateSpan [Posting] | getPostingsByColumn :: ReportSpec -> Journal -> PriceOracle -> DateSpan -> Map DateSpan [Posting] | ||||||
| getPostingsByColumn rspec j reportspan = columns | getPostingsByColumn rspec j priceoracle reportspan = columns | ||||||
|   where |   where | ||||||
|     -- Postings matching the query within the report period. |     -- Postings matching the query within the report period. | ||||||
|     ps :: [(Posting, Day)] = dbg5 "getPostingsByColumn" $ getPostings rspec j |     ps :: [(Posting, Day)] = dbg5 "getPostingsByColumn" $ getPostings rspec j priceoracle | ||||||
| 
 | 
 | ||||||
|     -- The date spans to be included as report columns. |     -- The date spans to be included as report columns. | ||||||
|     colspans = dbg3 "colspans" $ splitSpan (interval_ $ rsOpts rspec) reportspan |     colspans = dbg3 "colspans" $ splitSpan (interval_ $ rsOpts rspec) reportspan | ||||||
| @ -244,13 +244,13 @@ getPostingsByColumn rspec j reportspan = columns | |||||||
|     columns = foldr addPosting emptyMap ps |     columns = foldr addPosting emptyMap ps | ||||||
| 
 | 
 | ||||||
| -- | Gather postings matching the query within the report period. | -- | Gather postings matching the query within the report period. | ||||||
| getPostings :: ReportSpec -> Journal -> [(Posting, Day)] | getPostings :: ReportSpec -> Journal -> PriceOracle -> [(Posting, Day)] | ||||||
| getPostings ReportSpec{rsQuery=query,rsOpts=ropts} = | getPostings rspec@ReportSpec{rsQuery=query,rsOpts=ropts} j priceoracle = | ||||||
|     map (\p -> (p, date p)) . |     map (\p -> (p, date p)) . | ||||||
|     journalPostings . |     journalPostings . | ||||||
|     filterJournalAmounts symq .      -- remove amount parts excluded by cur: |     filterJournalAmounts symq .      -- remove amount parts excluded by cur: | ||||||
|     filterJournalPostings reportq .  -- remove postings not matched by (adjusted) query |     filterJournalPostings reportq $  -- remove postings not matched by (adjusted) query | ||||||
|     journalSelectingAmountFromOpts ropts |     valuedJournal | ||||||
|   where |   where | ||||||
|     symq = dbg3 "symq" . filterQuery queryIsSym $ dbg3 "requested q" query |     symq = dbg3 "symq" . filterQuery queryIsSym $ dbg3 "requested q" query | ||||||
|     -- The user's query with no depth limit, and expanded to the report span |     -- The user's query with no depth limit, and expanded to the report span | ||||||
| @ -258,6 +258,8 @@ getPostings ReportSpec{rsQuery=query,rsOpts=ropts} = | |||||||
|     -- handles the hledger-ui+future txns case above). |     -- handles the hledger-ui+future txns case above). | ||||||
|     reportq = dbg3 "reportq" $ depthless query |     reportq = dbg3 "reportq" $ depthless query | ||||||
|     depthless = dbg3 "depthless" . filterQuery (not . queryIsDepth) |     depthless = dbg3 "depthless" . filterQuery (not . queryIsDepth) | ||||||
|  |     valuedJournal | isJust (valuationAfterSum ropts) = j | ||||||
|  |                   | otherwise = journalApplyValuationFromOptsWith rspec j priceoracle | ||||||
| 
 | 
 | ||||||
|     date = case whichDateFromOpts ropts of |     date = case whichDateFromOpts ropts of | ||||||
|         PrimaryDate   -> postingDate |         PrimaryDate   -> postingDate | ||||||
| @ -296,7 +298,7 @@ calculateReportMatrix rspec@ReportSpec{rsOpts=ropts} j priceoracle startbals col | |||||||
|     -- starting-balance-based historical balances. |     -- starting-balance-based historical balances. | ||||||
|     rowbals name changes = dbg5 "rowbals" $ case balancetype_ ropts of |     rowbals name changes = dbg5 "rowbals" $ case balancetype_ ropts of | ||||||
|         PeriodChange      -> changeamts |         PeriodChange      -> changeamts | ||||||
|         CumulativeChange  -> cumulativeSum avalue nullacct changeamts |         CumulativeChange  -> cumulative | ||||||
|         HistoricalBalance -> historical |         HistoricalBalance -> historical | ||||||
|       where |       where | ||||||
|         -- changes to report on: usually just the changes itself, but use the |         -- changes to report on: usually just the changes itself, but use the | ||||||
| @ -305,6 +307,7 @@ calculateReportMatrix rspec@ReportSpec{rsOpts=ropts} j priceoracle startbals col | |||||||
|             ChangeReport      -> M.mapWithKey avalue changes |             ChangeReport      -> M.mapWithKey avalue changes | ||||||
|             BudgetReport      -> M.mapWithKey avalue changes |             BudgetReport      -> M.mapWithKey avalue changes | ||||||
|             ValueChangeReport -> periodChanges valuedStart historical |             ValueChangeReport -> periodChanges valuedStart historical | ||||||
|  |         cumulative = cumulativeSum avalue nullacct changeamts | ||||||
|         historical = cumulativeSum avalue startingBalance changes |         historical = cumulativeSum avalue startingBalance changes | ||||||
|         startingBalance = HM.lookupDefault nullacct name startbals |         startingBalance = HM.lookupDefault nullacct name startbals | ||||||
|         valuedStart = avalue (DateSpan Nothing historicalDate) startingBalance |         valuedStart = avalue (DateSpan Nothing historicalDate) startingBalance | ||||||
| @ -313,10 +316,10 @@ calculateReportMatrix rspec@ReportSpec{rsOpts=ropts} j priceoracle startbals col | |||||||
|     -- pad with zeros |     -- pad with zeros | ||||||
|     allchanges     = ((<>zeros) <$> acctchanges) <> (zeros <$ startbals) |     allchanges     = ((<>zeros) <$> acctchanges) <> (zeros <$ startbals) | ||||||
|     acctchanges    = dbg5 "acctchanges" . addElided $ transposeMap colacctchanges |     acctchanges    = dbg5 "acctchanges" . addElided $ transposeMap colacctchanges | ||||||
|     colacctchanges = dbg5 "colacctchanges" $ fmap (acctChangesFromPostings rspec) valuedps |     colacctchanges = dbg5 "colacctchanges" $ fmap (acctChangesFromPostings rspec) colps | ||||||
|     valuedps = M.mapWithKey (\colspan -> map (pvalue colspan)) colps |  | ||||||
| 
 | 
 | ||||||
|     (pvalue, avalue) = postingAndAccountValuations rspec j priceoracle |     avalue = acctApplyBoth . mixedAmountApplyValuationAfterSumFromOptsWith ropts j priceoracle | ||||||
|  |     acctApplyBoth f a = a{aibalance = f $ aibalance a, aebalance = f $ aebalance a} | ||||||
|     addElided = if queryDepth (rsQuery rspec) == Just 0 then HM.insert "..." zeros else id |     addElided = if queryDepth (rsQuery rspec) == Just 0 then HM.insert "..." zeros else id | ||||||
|     historicalDate = minimumMay $ mapMaybe spanStart colspans |     historicalDate = minimumMay $ mapMaybe spanStart colspans | ||||||
|     zeros = M.fromList [(span, nullacct) | span <- colspans] |     zeros = M.fromList [(span, nullacct) | span <- colspans] | ||||||
| @ -554,28 +557,6 @@ cumulativeSum :: (DateSpan -> Account -> Account) -> Account -> Map DateSpan Acc | |||||||
| cumulativeSum value start = snd . M.mapAccumWithKey accumValued start | cumulativeSum value start = snd . M.mapAccumWithKey accumValued start | ||||||
|   where accumValued startAmt date newAmt = let s = sumAcct startAmt newAmt in (s, value date s) |   where accumValued startAmt date newAmt = let s = sumAcct startAmt newAmt in (s, value date s) | ||||||
| 
 | 
 | ||||||
| -- | Calculate the Posting and Account valuation functions required by this MultiBalanceReport. |  | ||||||
| postingAndAccountValuations :: ReportSpec -> Journal -> PriceOracle |  | ||||||
|                             -> (DateSpan -> Posting -> Posting, DateSpan -> Account -> Account) |  | ||||||
| postingAndAccountValuations ReportSpec{rsToday=today, rsOpts=ropts} j priceoracle = case value_ ropts of |  | ||||||
|     -- If we're doing no valuation, just return the identity functions. |  | ||||||
|     Nothing          -> (const id, const id) |  | ||||||
|     -- If we're doing AtEnd valuation, we may need to value the same posting at different dates |  | ||||||
|     -- (for example, when preparing a ValueChange report). So we should do valuation on the Accounts. |  | ||||||
|     Just v@(AtEnd _) -> (const id, avalue v) |  | ||||||
|     -- Otherwise, all valuation should be done on the Postings. |  | ||||||
|     Just v           -> (pvalue v, const id) |  | ||||||
|   where |  | ||||||
|     -- For a Posting: convert to cost, apply valuation, then strip prices if we don't need them (See issue #1507). |  | ||||||
|     pvalue v span = postingApplyValuation priceoracle styles (end span) today v |  | ||||||
|     -- For an Account: Apply valuation to both the inclusive and exclusive balances. |  | ||||||
|     avalue v span a = a{aibalance = value (aibalance a), aebalance = value (aebalance a)} |  | ||||||
|       where value = mixedAmountApplyValuation priceoracle styles (end span) today (error "multiBalanceReport: did not expect amount valuation to be called ") v  -- PARTIAL: should not happen |  | ||||||
| 
 |  | ||||||
|     end = maybe (error "multiBalanceReport: expected all spans to have an end date")  -- PARTIAL: should not happen |  | ||||||
|             (addDays (-1)) . spanEnd |  | ||||||
|     styles = journalCommodityStyles j |  | ||||||
| 
 |  | ||||||
| -- tests | -- tests | ||||||
| 
 | 
 | ||||||
| tests_MultiBalanceReport = tests "MultiBalanceReport" [ | tests_MultiBalanceReport = tests "MultiBalanceReport" [ | ||||||
|  | |||||||
| @ -31,6 +31,8 @@ module Hledger.Reports.ReportOptions ( | |||||||
|   journalSelectingAmountFromOpts, |   journalSelectingAmountFromOpts, | ||||||
|   journalApplyValuationFromOpts, |   journalApplyValuationFromOpts, | ||||||
|   journalApplyValuationFromOptsWith, |   journalApplyValuationFromOptsWith, | ||||||
|  |   mixedAmountApplyValuationAfterSumFromOptsWith, | ||||||
|  |   valuationAfterSum, | ||||||
|   intervalFromRawOpts, |   intervalFromRawOpts, | ||||||
|   forecastPeriodFromRawOpts, |   forecastPeriodFromRawOpts, | ||||||
|   queryFromFlags, |   queryFromFlags, | ||||||
| @ -528,18 +530,34 @@ journalApplyValuationFromOptsWith rspec@ReportSpec{rsOpts=ropts} j priceoracle = | |||||||
|     historical = DateSpan Nothing $ spanStart =<< headMay spans |     historical = DateSpan Nothing $ spanStart =<< headMay spans | ||||||
|     spans = splitSpan (interval_ ropts) $ reportSpanBothDates j rspec |     spans = splitSpan (interval_ ropts) $ reportSpanBothDates j rspec | ||||||
|     styles = journalCommodityStyles j |     styles = journalCommodityStyles j | ||||||
|     err = error' "journalApplyValuationFromOpts: expected a non-empty journal" |     err = error "journalApplyValuationFromOpts: expected all spans to have an end date" | ||||||
| 
 | 
 | ||||||
| -- | Whether we need to perform valuation after summing amounts, as in a | -- | Calculate the Account valuation functions required for valuing after summing amounts. | ||||||
| -- historical report with --value=end. | -- Used in MultiBalanceReport to value historical reports and the like. | ||||||
| valuationAfterSum :: ReportOpts -> Bool | mixedAmountApplyValuationAfterSumFromOptsWith :: ReportOpts -> Journal -> PriceOracle -> (DateSpan -> MixedAmount -> MixedAmount) | ||||||
|  | mixedAmountApplyValuationAfterSumFromOptsWith ropts j priceoracle = case valuationAfterSum ropts of | ||||||
|  |     Just mc -> \span -> valuation mc span . maybeStripPrices . costing | ||||||
|  |     Nothing -> const id | ||||||
|  |   where | ||||||
|  |     valuation mc span = mixedAmountValueAtDate priceoracle styles mc (maybe err (addDays (-1)) $ spanEnd span) | ||||||
|  |       where err = error "mixedAmountApplyValuationAfterSumFromOptsWith: expected all spans to have an end date" | ||||||
|  |     maybeStripPrices = if show_costs_ ropts then id else mixedAmountStripPrices | ||||||
|  |     costing = case cost_ ropts of | ||||||
|  |         Cost   -> styleMixedAmount styles . mixedAmountCost | ||||||
|  |         NoCost -> id | ||||||
|  |     styles = journalCommodityStyles j | ||||||
|  | 
 | ||||||
|  | -- | If we are performing valuation after summing amounts, return Just the | ||||||
|  | -- commodity symbols we're converting to, otherwise return Nothing. | ||||||
|  | -- Used for example with historical reports with --value=end. | ||||||
|  | valuationAfterSum :: ReportOpts -> Maybe (Maybe CommoditySymbol) | ||||||
| valuationAfterSum ropts = case value_ ropts of | valuationAfterSum ropts = case value_ ropts of | ||||||
|     Just (AtEnd _) -> case (reporttype_ ropts, balancetype_ ropts) of |     Just (AtEnd mc) -> case (reporttype_ ropts, balancetype_ ropts) of | ||||||
|         (ValueChangeReport, _) -> True |         (ValueChangeReport, _) -> Just mc | ||||||
|         (_, HistoricalBalance) -> True |         (_, HistoricalBalance) -> Just mc | ||||||
|         (_, CumulativeChange)  -> True |         (_, CumulativeChange)  -> Just mc | ||||||
|         _                      -> False |         _                      -> Nothing | ||||||
|     _ -> False |     _ -> Nothing | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| -- | Convert report options to a query, ignoring any non-flag command line arguments. | -- | Convert report options to a query, ignoring any non-flag command line arguments. | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user