{-# LANGUAGE CPP                 #-}
{-# LANGUAGE FlexibleInstances   #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE RecordWildCards     #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-|

Multi-column balance reports, used by the balance command.

-}

module Hledger.Reports.MultiBalanceReport (
  MultiBalanceReport,
  MultiBalanceReportRow,

  multiBalanceReport,
  multiBalanceReportWith,

  compoundBalanceReport,
  compoundBalanceReportWith,

  sortRows,
  sortRowsLike,

  -- * Helper functions
  calculateReportSpan,
  makeReportQuery,
  getPostingsByColumn,
  getPostings,
  calculateColSpans,
  startingBalances,
  generateMultiBalanceReport,

  -- -- * Tests
  tests_MultiBalanceReport
)
where

import Control.Monad (guard)
import Data.Foldable (toList)
import Data.List (sortOn, transpose)
import Data.List.NonEmpty (NonEmpty(..))
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import Data.Map (Map)
import qualified Data.Map as M
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Ord (Down(..))
#if !(MIN_VERSION_base(4,11,0))
import Data.Semigroup ((<>))
#endif
import Data.Semigroup (sconcat)
import Data.Time.Calendar (Day, addDays, fromGregorian)
import Safe (headMay, lastDef, lastMay, minimumMay)

import Hledger.Data
import Hledger.Query
import Hledger.Utils hiding (dbg3,dbg4,dbg5)
import qualified Hledger.Utils
import Hledger.Read (mamountp')
import Hledger.Reports.ReportOptions
import Hledger.Reports.ReportTypes


-- add a prefix to this function's debug output
dbg3 :: [Char] -> a -> a
dbg3 [Char]
s = let p :: [Char]
p = [Char]
"multiBalanceReport" in [Char] -> a -> a
forall a. Show a => [Char] -> a -> a
Hledger.Utils.dbg3 ([Char]
p[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++[Char]
" "[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++[Char]
s)
dbg4 :: [Char] -> a -> a
dbg4 [Char]
s = let p :: [Char]
p = [Char]
"multiBalanceReport" in [Char] -> a -> a
forall a. Show a => [Char] -> a -> a
Hledger.Utils.dbg4 ([Char]
p[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++[Char]
" "[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++[Char]
s)
dbg5 :: [Char] -> a -> a
dbg5 [Char]
s = let p :: [Char]
p = [Char]
"multiBalanceReport" in [Char] -> a -> a
forall a. Show a => [Char] -> a -> a
Hledger.Utils.dbg5 ([Char]
p[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++[Char]
" "[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++[Char]
s)


-- | A multi balance report is a kind of periodic report, where the amounts
-- correspond to balance changes or ending balances in a given period. It has:
--
-- 1. a list of each column's period (date span)
--
-- 2. a list of rows, each containing:
--
--   * the full account name, display name, and display depth
--
--   * A list of amounts, one for each column.
--
--   * the total of the row's amounts for a periodic report
--
--   * the average of the row's amounts
--
-- 3. the column totals, and the overall grand total (or zero for
-- cumulative/historical reports) and grand average.

type MultiBalanceReport    = PeriodicReport    DisplayName MixedAmount
type MultiBalanceReportRow = PeriodicReportRow DisplayName MixedAmount

-- type alias just to remind us which AccountNames might be depth-clipped, below.
type ClippedAccountName = AccountName


-- | Generate a multicolumn balance report for the matched accounts,
-- showing the change of balance, accumulated balance, or historical balance
-- in each of the specified periods. If the normalbalance_ option is set, it
-- adjusts the sorting and sign of amounts (see ReportOpts and
-- CompoundBalanceCommand). hledger's most powerful and useful report, used
-- by the balance command (in multiperiod mode) and (via compoundBalanceReport)
-- by the bs/cf/is commands.
multiBalanceReport :: ReportSpec -> Journal -> MultiBalanceReport
multiBalanceReport :: ReportSpec -> Journal -> MultiBalanceReport
multiBalanceReport ReportSpec
rspec Journal
j = ReportSpec -> Journal -> PriceOracle -> MultiBalanceReport
multiBalanceReportWith ReportSpec
rspec Journal
j (Bool -> Journal -> PriceOracle
journalPriceOracle Bool
infer Journal
j)
  where infer :: Bool
infer = ReportOpts -> Bool
infer_value_ (ReportOpts -> Bool) -> ReportOpts -> Bool
forall a b. (a -> b) -> a -> b
$ ReportSpec -> ReportOpts
rsOpts ReportSpec
rspec

-- | A helper for multiBalanceReport. This one takes an extra argument,
-- a PriceOracle to be used for looking up market prices. Commands which
-- run multiple reports (bs etc.) can generate the price oracle just
-- once for efficiency, passing it to each report by calling this
-- function directly.
multiBalanceReportWith :: ReportSpec -> Journal -> PriceOracle -> MultiBalanceReport
multiBalanceReportWith :: ReportSpec -> Journal -> PriceOracle -> MultiBalanceReport
multiBalanceReportWith ReportSpec
rspec' Journal
j PriceOracle
priceoracle = MultiBalanceReport
report
  where
    -- Queries, report/column dates.
    reportspan :: DateSpan
reportspan = [Char] -> DateSpan -> DateSpan
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"reportspan" (DateSpan -> DateSpan) -> DateSpan -> DateSpan
forall a b. (a -> b) -> a -> b
$ ReportSpec -> Journal -> DateSpan
calculateReportSpan ReportSpec
rspec' Journal
j
    rspec :: ReportSpec
rspec      = [Char] -> ReportSpec -> ReportSpec
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"reportopts" (ReportSpec -> ReportSpec) -> ReportSpec -> ReportSpec
forall a b. (a -> b) -> a -> b
$ ReportSpec -> DateSpan -> ReportSpec
makeReportQuery ReportSpec
rspec' DateSpan
reportspan
    valuation :: Day -> MixedAmount -> MixedAmount
valuation  = ReportSpec
-> Journal -> PriceOracle -> Day -> MixedAmount -> MixedAmount
makeValuation ReportSpec
rspec' Journal
j PriceOracle
priceoracle  -- Must use rspec' instead of rspec,
                                                     -- so the reportspan isn't used for valuation

    -- Group postings into their columns.
    colps :: Map DateSpan [Posting]
colps    = [Char] -> Map DateSpan [Posting] -> Map DateSpan [Posting]
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"colps"  (Map DateSpan [Posting] -> Map DateSpan [Posting])
-> Map DateSpan [Posting] -> Map DateSpan [Posting]
forall a b. (a -> b) -> a -> b
$ ReportSpec -> Journal -> DateSpan -> Map DateSpan [Posting]
getPostingsByColumn ReportSpec
rspec Journal
j DateSpan
reportspan
    colspans :: [DateSpan]
colspans = [Char] -> [DateSpan] -> [DateSpan]
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"colspans" ([DateSpan] -> [DateSpan]) -> [DateSpan] -> [DateSpan]
forall a b. (a -> b) -> a -> b
$ Map DateSpan [Posting] -> [DateSpan]
forall k a. Map k a -> [k]
M.keys Map DateSpan [Posting]
colps

    -- 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.
    startbals :: HashMap AccountName Account
startbals = [Char]
-> HashMap AccountName Account -> HashMap AccountName Account
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"startbals" (HashMap AccountName Account -> HashMap AccountName Account)
-> HashMap AccountName Account -> HashMap AccountName Account
forall a b. (a -> b) -> a -> b
$ ReportSpec -> Journal -> DateSpan -> HashMap AccountName Account
startingBalances ReportSpec
rspec Journal
j DateSpan
reportspan

    -- Generate and postprocess the report, negating balances and taking percentages if needed
    report :: MultiBalanceReport
report = [Char] -> MultiBalanceReport -> MultiBalanceReport
forall a. Show a => [Char] -> a -> a
dbg4 [Char]
"multiBalanceReportWith" (MultiBalanceReport -> MultiBalanceReport)
-> MultiBalanceReport -> MultiBalanceReport
forall a b. (a -> b) -> a -> b
$
      ReportSpec
-> Journal
-> (Day -> MixedAmount -> MixedAmount)
-> [DateSpan]
-> Map DateSpan [Posting]
-> HashMap AccountName Account
-> MultiBalanceReport
generateMultiBalanceReport ReportSpec
rspec Journal
j Day -> MixedAmount -> MixedAmount
valuation [DateSpan]
colspans Map DateSpan [Posting]
colps HashMap AccountName Account
startbals

-- | Generate a compound balance report from a list of CBCSubreportSpec. This
-- shares postings between the subreports.
compoundBalanceReport :: ReportSpec -> Journal -> [CBCSubreportSpec a]
                      -> CompoundPeriodicReport a MixedAmount
compoundBalanceReport :: ReportSpec
-> Journal
-> [CBCSubreportSpec a]
-> CompoundPeriodicReport a MixedAmount
compoundBalanceReport ReportSpec
rspec Journal
j = ReportSpec
-> Journal
-> PriceOracle
-> [CBCSubreportSpec a]
-> CompoundPeriodicReport a MixedAmount
forall a.
ReportSpec
-> Journal
-> PriceOracle
-> [CBCSubreportSpec a]
-> CompoundPeriodicReport a MixedAmount
compoundBalanceReportWith ReportSpec
rspec Journal
j (Bool -> Journal -> PriceOracle
journalPriceOracle Bool
infer Journal
j)
  where infer :: Bool
infer = ReportOpts -> Bool
infer_value_ (ReportOpts -> Bool) -> ReportOpts -> Bool
forall a b. (a -> b) -> a -> b
$ ReportSpec -> ReportOpts
rsOpts ReportSpec
rspec

-- | A helper for compoundBalanceReport, similar to multiBalanceReportWith.
compoundBalanceReportWith :: ReportSpec -> Journal -> PriceOracle
                          -> [CBCSubreportSpec a]
                          -> CompoundPeriodicReport a MixedAmount
compoundBalanceReportWith :: ReportSpec
-> Journal
-> PriceOracle
-> [CBCSubreportSpec a]
-> CompoundPeriodicReport a MixedAmount
compoundBalanceReportWith ReportSpec
rspec' Journal
j PriceOracle
priceoracle [CBCSubreportSpec a]
subreportspecs = CompoundPeriodicReport a MixedAmount
cbr
  where
    -- Queries, report/column dates.
    reportspan :: DateSpan
reportspan = [Char] -> DateSpan -> DateSpan
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"reportspan" (DateSpan -> DateSpan) -> DateSpan -> DateSpan
forall a b. (a -> b) -> a -> b
$ ReportSpec -> Journal -> DateSpan
calculateReportSpan ReportSpec
rspec' Journal
j
    rspec :: ReportSpec
rspec      = [Char] -> ReportSpec -> ReportSpec
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"reportopts" (ReportSpec -> ReportSpec) -> ReportSpec -> ReportSpec
forall a b. (a -> b) -> a -> b
$ ReportSpec -> DateSpan -> ReportSpec
makeReportQuery ReportSpec
rspec' DateSpan
reportspan
    valuation :: Day -> MixedAmount -> MixedAmount
valuation  = ReportSpec
-> Journal -> PriceOracle -> Day -> MixedAmount -> MixedAmount
makeValuation ReportSpec
rspec' Journal
j PriceOracle
priceoracle  -- Must use ropts' instead of ropts,
                                                     -- so the reportspan isn't used for valuation

    -- Group postings into their columns.
    colps :: Map DateSpan [Posting]
colps    = [Char] -> Map DateSpan [Posting] -> Map DateSpan [Posting]
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"colps"  (Map DateSpan [Posting] -> Map DateSpan [Posting])
-> Map DateSpan [Posting] -> Map DateSpan [Posting]
forall a b. (a -> b) -> a -> b
$ ReportSpec -> Journal -> DateSpan -> Map DateSpan [Posting]
getPostingsByColumn ReportSpec
rspec{rsOpts :: ReportOpts
rsOpts=(ReportSpec -> ReportOpts
rsOpts ReportSpec
rspec){empty_ :: Bool
empty_=Bool
True}} Journal
j DateSpan
reportspan
    colspans :: [DateSpan]
colspans = [Char] -> [DateSpan] -> [DateSpan]
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"colspans" ([DateSpan] -> [DateSpan]) -> [DateSpan] -> [DateSpan]
forall a b. (a -> b) -> a -> b
$ Map DateSpan [Posting] -> [DateSpan]
forall k a. Map k a -> [k]
M.keys Map DateSpan [Posting]
colps

    -- 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.
    startbals :: HashMap AccountName Account
startbals = [Char]
-> HashMap AccountName Account -> HashMap AccountName Account
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"startbals" (HashMap AccountName Account -> HashMap AccountName Account)
-> HashMap AccountName Account -> HashMap AccountName Account
forall a b. (a -> b) -> a -> b
$ ReportSpec -> Journal -> DateSpan -> HashMap AccountName Account
startingBalances ReportSpec
rspec Journal
j DateSpan
reportspan

    subreports :: [([Char], PeriodicReport a MixedAmount, Bool)]
subreports = (CBCSubreportSpec a
 -> ([Char], PeriodicReport a MixedAmount, Bool))
-> [CBCSubreportSpec a]
-> [([Char], PeriodicReport a MixedAmount, Bool)]
forall a b. (a -> b) -> [a] -> [b]
map CBCSubreportSpec a -> ([Char], PeriodicReport a MixedAmount, Bool)
forall a.
CBCSubreportSpec a -> ([Char], PeriodicReport a MixedAmount, Bool)
generateSubreport [CBCSubreportSpec a]
subreportspecs
      where
        generateSubreport :: CBCSubreportSpec a -> ([Char], PeriodicReport a MixedAmount, Bool)
generateSubreport CBCSubreportSpec{Bool
[Char]
Journal -> Query
ReportOpts -> ReportOpts
MultiBalanceReport -> PeriodicReport a MixedAmount
cbcsubreportincreasestotal :: forall a. CBCSubreportSpec a -> Bool
cbcsubreporttransform :: forall a.
CBCSubreportSpec a
-> MultiBalanceReport -> PeriodicReport a MixedAmount
cbcsubreportoptions :: forall a. CBCSubreportSpec a -> ReportOpts -> ReportOpts
cbcsubreportquery :: forall a. CBCSubreportSpec a -> Journal -> Query
cbcsubreporttitle :: forall a. CBCSubreportSpec a -> [Char]
cbcsubreportincreasestotal :: Bool
cbcsubreporttransform :: MultiBalanceReport -> PeriodicReport a MixedAmount
cbcsubreportoptions :: ReportOpts -> ReportOpts
cbcsubreportquery :: Journal -> Query
cbcsubreporttitle :: [Char]
..} =
            ( [Char]
cbcsubreporttitle
            -- Postprocess the report, negating balances and taking percentages if needed
            , MultiBalanceReport -> PeriodicReport a MixedAmount
cbcsubreporttransform (MultiBalanceReport -> PeriodicReport a MixedAmount)
-> MultiBalanceReport -> PeriodicReport a MixedAmount
forall a b. (a -> b) -> a -> b
$
                ReportSpec
-> Journal
-> (Day -> MixedAmount -> MixedAmount)
-> [DateSpan]
-> Map DateSpan [Posting]
-> HashMap AccountName Account
-> MultiBalanceReport
generateMultiBalanceReport ReportSpec
rspec{rsOpts :: ReportOpts
rsOpts=ReportOpts
ropts} Journal
j Day -> MixedAmount -> MixedAmount
valuation [DateSpan]
colspans Map DateSpan [Posting]
colps' HashMap AccountName Account
startbals'
            , Bool
cbcsubreportincreasestotal
            )
          where
            -- Filter the column postings according to each subreport
            colps' :: Map DateSpan [Posting]
colps'     = (Posting -> Bool) -> [Posting] -> [Posting]
forall a. (a -> Bool) -> [a] -> [a]
filter (Query -> Posting -> Bool
matchesPosting Query
q) ([Posting] -> [Posting])
-> Map DateSpan [Posting] -> Map DateSpan [Posting]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Map DateSpan [Posting]
colps
            startbals' :: HashMap AccountName Account
startbals' = (AccountName -> Account -> Bool)
-> HashMap AccountName Account -> HashMap AccountName Account
forall k v. (k -> v -> Bool) -> HashMap k v -> HashMap k v
HM.filterWithKey (\AccountName
k Account
_ -> Query -> AccountName -> Bool
matchesAccount Query
q AccountName
k) HashMap AccountName Account
startbals
            ropts :: ReportOpts
ropts      = ReportOpts -> ReportOpts
cbcsubreportoptions (ReportOpts -> ReportOpts) -> ReportOpts -> ReportOpts
forall a b. (a -> b) -> a -> b
$ ReportSpec -> ReportOpts
rsOpts ReportSpec
rspec
            q :: Query
q          = Journal -> Query
cbcsubreportquery Journal
j

    -- Sum the subreport totals by column. Handle these cases:
    -- - no subreports
    -- - empty subreports, having no subtotals (#588)
    -- - subreports with a shorter subtotals row than the others
    overalltotals :: PeriodicReportRow () MixedAmount
overalltotals = case [([Char], PeriodicReport a MixedAmount, Bool)]
subreports of
        []     -> ()
-> [MixedAmount]
-> MixedAmount
-> MixedAmount
-> PeriodicReportRow () MixedAmount
forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow () [] MixedAmount
nullmixedamt MixedAmount
nullmixedamt
        (([Char], PeriodicReport a MixedAmount, Bool)
r:[([Char], PeriodicReport a MixedAmount, Bool)]
rs) -> NonEmpty (PeriodicReportRow () MixedAmount)
-> PeriodicReportRow () MixedAmount
forall a. Semigroup a => NonEmpty a -> a
sconcat (NonEmpty (PeriodicReportRow () MixedAmount)
 -> PeriodicReportRow () MixedAmount)
-> NonEmpty (PeriodicReportRow () MixedAmount)
-> PeriodicReportRow () MixedAmount
forall a b. (a -> b) -> a -> b
$ (([Char], PeriodicReport a MixedAmount, Bool)
 -> PeriodicReportRow () MixedAmount)
-> NonEmpty ([Char], PeriodicReport a MixedAmount, Bool)
-> NonEmpty (PeriodicReportRow () MixedAmount)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ([Char], PeriodicReport a MixedAmount, Bool)
-> PeriodicReportRow () MixedAmount
forall b a a.
Num b =>
(a, PeriodicReport a b, Bool) -> PeriodicReportRow () b
subreportTotal (([Char], PeriodicReport a MixedAmount, Bool)
r([Char], PeriodicReport a MixedAmount, Bool)
-> [([Char], PeriodicReport a MixedAmount, Bool)]
-> NonEmpty ([Char], PeriodicReport a MixedAmount, Bool)
forall a. a -> [a] -> NonEmpty a
:|[([Char], PeriodicReport a MixedAmount, Bool)]
rs)
      where
        subreportTotal :: (a, PeriodicReport a b, Bool) -> PeriodicReportRow () b
subreportTotal (a
_, PeriodicReport a b
sr, Bool
increasestotal) =
            (if Bool
increasestotal then PeriodicReportRow () b -> PeriodicReportRow () b
forall a. a -> a
id else (b -> b) -> PeriodicReportRow () b -> PeriodicReportRow () b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap b -> b
forall a. Num a => a -> a
negate) (PeriodicReportRow () b -> PeriodicReportRow () b)
-> PeriodicReportRow () b -> PeriodicReportRow () b
forall a b. (a -> b) -> a -> b
$ PeriodicReport a b -> PeriodicReportRow () b
forall a b. PeriodicReport a b -> PeriodicReportRow () b
prTotals PeriodicReport a b
sr

    cbr :: CompoundPeriodicReport a MixedAmount
cbr = [Char]
-> [DateSpan]
-> [([Char], PeriodicReport a MixedAmount, Bool)]
-> PeriodicReportRow () MixedAmount
-> CompoundPeriodicReport a MixedAmount
forall a b.
[Char]
-> [DateSpan]
-> [([Char], PeriodicReport a b, Bool)]
-> PeriodicReportRow () b
-> CompoundPeriodicReport a b
CompoundPeriodicReport [Char]
"" [DateSpan]
colspans [([Char], PeriodicReport a MixedAmount, Bool)]
subreports PeriodicReportRow () MixedAmount
overalltotals


-- | Calculate starting balances, if needed for -H
--
-- Balances at report start date, from all earlier postings which otherwise match the query.
-- These balances are unvalued.
-- TODO: Do we want to check whether to bother calculating these? isHistorical
-- and startDate is not nothing, otherwise mempty? This currently gives a
-- failure with some totals which are supposed to be 0 being blank.
startingBalances :: ReportSpec -> Journal -> DateSpan -> HashMap AccountName Account
startingBalances :: ReportSpec -> Journal -> DateSpan -> HashMap AccountName Account
startingBalances rspec :: ReportSpec
rspec@ReportSpec{rsQuery :: ReportSpec -> Query
rsQuery=Query
query,rsOpts :: ReportSpec -> ReportOpts
rsOpts=ReportOpts
ropts} Journal
j DateSpan
reportspan =
    ReportSpec -> [Posting] -> HashMap AccountName Account
acctChangesFromPostings ReportSpec
rspec' ([Posting] -> HashMap AccountName Account)
-> ([(Posting, Day)] -> [Posting])
-> [(Posting, Day)]
-> HashMap AccountName Account
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Posting, Day) -> Posting) -> [(Posting, Day)] -> [Posting]
forall a b. (a -> b) -> [a] -> [b]
map (Posting, Day) -> Posting
forall a b. (a, b) -> a
fst ([(Posting, Day)] -> HashMap AccountName Account)
-> [(Posting, Day)] -> HashMap AccountName Account
forall a b. (a -> b) -> a -> b
$ ReportSpec -> Journal -> [(Posting, Day)]
getPostings ReportSpec
rspec' Journal
j
  where
    rspec' :: ReportSpec
rspec' = ReportSpec
rspec{rsQuery :: Query
rsQuery=Query
startbalq,rsOpts :: ReportOpts
rsOpts=ReportOpts
ropts'}
    ropts' :: ReportOpts
ropts' = case ReportOpts -> AccountListMode
accountlistmode_ ReportOpts
ropts of
        AccountListMode
ALTree -> ReportOpts
ropts{period_ :: Period
period_=Period
precedingperiod, no_elide_ :: Bool
no_elide_=Bool
True}
        AccountListMode
ALFlat -> ReportOpts
ropts{period_ :: Period
period_=Period
precedingperiod}

    -- q projected back before the report start date.
    -- When there's no report start date, in case there are future txns (the hledger-ui case above),
    -- we use emptydatespan to make sure they aren't counted as starting balance.
    startbalq :: Query
startbalq = [Char] -> Query -> Query
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"startbalq" (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
And [Query
datelessq, Query
precedingspanq]
    datelessq :: Query
datelessq = [Char] -> Query -> Query
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"datelessq" (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ (Query -> Bool) -> Query -> Query
filterQuery (Bool -> Bool
not (Bool -> Bool) -> (Query -> Bool) -> Query -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Query -> Bool
queryIsDateOrDate2) Query
query

    precedingperiod :: Period
precedingperiod = DateSpan -> Period
dateSpanAsPeriod (DateSpan -> Period) -> (Period -> DateSpan) -> Period -> Period
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DateSpan -> DateSpan -> DateSpan
spanIntersect DateSpan
precedingspan (DateSpan -> DateSpan)
-> (Period -> DateSpan) -> Period -> DateSpan
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
                         Period -> DateSpan
periodAsDateSpan (Period -> Period) -> Period -> Period
forall a b. (a -> b) -> a -> b
$ ReportOpts -> Period
period_ ReportOpts
ropts
    precedingspan :: DateSpan
precedingspan = Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing (Maybe Day -> DateSpan) -> Maybe Day -> DateSpan
forall a b. (a -> b) -> a -> b
$ DateSpan -> Maybe Day
spanStart DateSpan
reportspan
    precedingspanq :: Query
precedingspanq = (if ReportOpts -> Bool
date2_ ReportOpts
ropts then DateSpan -> Query
Date2 else DateSpan -> Query
Date) (DateSpan -> Query) -> DateSpan -> Query
forall a b. (a -> b) -> a -> b
$ case DateSpan
precedingspan of
        DateSpan Maybe Day
Nothing Maybe Day
Nothing -> DateSpan
emptydatespan
        DateSpan
a -> DateSpan
a

-- | Calculate the span of the report to be generated.
calculateReportSpan :: ReportSpec -> Journal -> DateSpan
calculateReportSpan :: ReportSpec -> Journal -> DateSpan
calculateReportSpan ReportSpec{rsQuery :: ReportSpec -> Query
rsQuery=Query
query,rsOpts :: ReportSpec -> ReportOpts
rsOpts=ReportOpts
ropts} Journal
j = DateSpan
reportspan
  where
    -- The date span specified by -b/-e/-p options and query args if any.
    requestedspan :: DateSpan
requestedspan  = [Char] -> DateSpan -> DateSpan
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"requestedspan" (DateSpan -> DateSpan) -> DateSpan -> DateSpan
forall a b. (a -> b) -> a -> b
$ Bool -> Query -> DateSpan
queryDateSpan (ReportOpts -> Bool
date2_ ReportOpts
ropts) Query
query
    -- If the requested span is open-ended, close it using the journal's end dates.
    -- This can still be the null (open) span if the journal is empty.
    requestedspan' :: DateSpan
requestedspan' = [Char] -> DateSpan -> DateSpan
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"requestedspan'" (DateSpan -> DateSpan) -> DateSpan -> DateSpan
forall a b. (a -> b) -> a -> b
$
        DateSpan
requestedspan DateSpan -> DateSpan -> DateSpan
`spanDefaultsFrom` Bool -> Journal -> DateSpan
journalDateSpan (ReportOpts -> Bool
date2_ ReportOpts
ropts) Journal
j
    -- The list of interval spans enclosing the requested span.
    -- This list can be empty if the journal was empty,
    -- or if hledger-ui has added its special date:-tomorrow to the query
    -- and all txns are in the future.
    intervalspans :: [DateSpan]
intervalspans  = [Char] -> [DateSpan] -> [DateSpan]
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"intervalspans" ([DateSpan] -> [DateSpan]) -> [DateSpan] -> [DateSpan]
forall a b. (a -> b) -> a -> b
$ Interval -> DateSpan -> [DateSpan]
splitSpan (ReportOpts -> Interval
interval_ ReportOpts
ropts) DateSpan
requestedspan'
    -- The requested span enlarged to enclose a whole number of intervals.
    -- This can be the null span if there were no intervals.
    reportspan :: DateSpan
reportspan = Maybe Day -> Maybe Day -> DateSpan
DateSpan (DateSpan -> Maybe Day
spanStart (DateSpan -> Maybe Day) -> Maybe DateSpan -> Maybe Day
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< [DateSpan] -> Maybe DateSpan
forall a. [a] -> Maybe a
headMay [DateSpan]
intervalspans)
                          (DateSpan -> Maybe Day
spanEnd (DateSpan -> Maybe Day) -> Maybe DateSpan -> Maybe Day
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< [DateSpan] -> Maybe DateSpan
forall a. [a] -> Maybe a
lastMay [DateSpan]
intervalspans)

-- | Remove any date queries and insert queries from the report span.
-- The user's query expanded to the report span
-- if there is one (otherwise any date queries are left as-is, which
-- handles the hledger-ui+future txns case above).
makeReportQuery :: ReportSpec -> DateSpan -> ReportSpec
makeReportQuery :: ReportSpec -> DateSpan -> ReportSpec
makeReportQuery ReportSpec
rspec DateSpan
reportspan
    | DateSpan
reportspan DateSpan -> DateSpan -> Bool
forall a. Eq a => a -> a -> Bool
== DateSpan
nulldatespan = ReportSpec
rspec
    | Bool
otherwise = ReportSpec
rspec{rsQuery :: Query
rsQuery=Query
query}
  where
    query :: Query
query            = Query -> Query
simplifyQuery (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Query] -> Query
And [Query -> Query
dateless (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ ReportSpec -> Query
rsQuery ReportSpec
rspec, Query
reportspandatesq]
    reportspandatesq :: Query
reportspandatesq = [Char] -> Query -> Query
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"reportspandatesq" (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ DateSpan -> Query
dateqcons DateSpan
reportspan
    dateless :: Query -> Query
dateless         = [Char] -> Query -> Query
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"dateless" (Query -> Query) -> (Query -> Query) -> Query -> Query
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Query -> Bool) -> Query -> Query
filterQuery (Bool -> Bool
not (Bool -> Bool) -> (Query -> Bool) -> Query -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Query -> Bool
queryIsDateOrDate2)
    dateqcons :: DateSpan -> Query
dateqcons        = if ReportOpts -> Bool
date2_ (ReportSpec -> ReportOpts
rsOpts ReportSpec
rspec) then DateSpan -> Query
Date2 else DateSpan -> Query
Date

-- | Make a valuation function for valuating MixedAmounts and a given Day
makeValuation :: ReportSpec -> Journal -> PriceOracle -> (Day -> MixedAmount -> MixedAmount)
makeValuation :: ReportSpec
-> Journal -> PriceOracle -> Day -> MixedAmount -> MixedAmount
makeValuation ReportSpec
rspec Journal
j PriceOracle
priceoracle Day
day = case ReportOpts -> Maybe ValuationType
value_ (ReportSpec -> ReportOpts
rsOpts ReportSpec
rspec) of
    Maybe ValuationType
Nothing -> MixedAmount -> MixedAmount
forall a. a -> a
id
    Just ValuationType
v  -> PriceOracle
-> Map AccountName AmountStyle
-> Day
-> Maybe Day
-> Day
-> Bool
-> ValuationType
-> MixedAmount
-> MixedAmount
mixedAmountApplyValuation PriceOracle
priceoracle Map AccountName AmountStyle
styles Day
day Maybe Day
mreportlast (ReportSpec -> Day
rsToday ReportSpec
rspec) Bool
multiperiod ValuationType
v
  where
    -- Some things needed if doing valuation.
    styles :: Map AccountName AmountStyle
styles = Journal -> Map AccountName AmountStyle
journalCommodityStyles Journal
j
    mreportlast :: Maybe Day
mreportlast = ReportSpec -> Maybe Day
reportPeriodLastDay ReportSpec
rspec
    multiperiod :: Bool
multiperiod = ReportOpts -> Interval
interval_ (ReportSpec -> ReportOpts
rsOpts ReportSpec
rspec) Interval -> Interval -> Bool
forall a. Eq a => a -> a -> Bool
/= Interval
NoInterval

-- | Group postings, grouped by their column
getPostingsByColumn :: ReportSpec -> Journal -> DateSpan -> Map DateSpan [Posting]
getPostingsByColumn :: ReportSpec -> Journal -> DateSpan -> Map DateSpan [Posting]
getPostingsByColumn ReportSpec
rspec Journal
j DateSpan
reportspan = Map DateSpan [Posting]
columns
  where
    -- Postings matching the query within the report period.
    [(Posting, Day)]
ps :: [(Posting, Day)] = [Char] -> [(Posting, Day)] -> [(Posting, Day)]
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"getPostingsByColumn" ([(Posting, Day)] -> [(Posting, Day)])
-> [(Posting, Day)] -> [(Posting, Day)]
forall a b. (a -> b) -> a -> b
$ ReportSpec -> Journal -> [(Posting, Day)]
getPostings ReportSpec
rspec Journal
j
    days :: [Day]
days = ((Posting, Day) -> Day) -> [(Posting, Day)] -> [Day]
forall a b. (a -> b) -> [a] -> [b]
map (Posting, Day) -> Day
forall a b. (a, b) -> b
snd [(Posting, Day)]
ps

    -- The date spans to be included as report columns.
    colspans :: [DateSpan]
colspans = ReportOpts -> DateSpan -> [Day] -> [DateSpan]
calculateColSpans (ReportSpec -> ReportOpts
rsOpts ReportSpec
rspec) DateSpan
reportspan [Day]
days
    addPosting :: (a, Day) -> Map DateSpan [a] -> Map DateSpan [a]
addPosting (a
p, Day
d) = (Map DateSpan [a] -> Map DateSpan [a])
-> (DateSpan -> Map DateSpan [a] -> Map DateSpan [a])
-> Maybe DateSpan
-> Map DateSpan [a]
-> Map DateSpan [a]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Map DateSpan [a] -> Map DateSpan [a]
forall a. a -> a
id (([a] -> [a]) -> DateSpan -> Map DateSpan [a] -> Map DateSpan [a]
forall k a. Ord k => (a -> a) -> k -> Map k a -> Map k a
M.adjust (a
pa -> [a] -> [a]
forall a. a -> [a] -> [a]
:)) (Maybe DateSpan -> Map DateSpan [a] -> Map DateSpan [a])
-> Maybe DateSpan -> Map DateSpan [a] -> Map DateSpan [a]
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> Day -> Maybe DateSpan
latestSpanContaining [DateSpan]
colspans Day
d
    emptyMap :: Map DateSpan [a]
emptyMap = [(DateSpan, [a])] -> Map DateSpan [a]
forall k a. Ord k => [(k, a)] -> Map k a
M.fromList ([(DateSpan, [a])] -> Map DateSpan [a])
-> ([[a]] -> [(DateSpan, [a])]) -> [[a]] -> Map DateSpan [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [DateSpan] -> [[a]] -> [(DateSpan, [a])]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
colspans ([[a]] -> Map DateSpan [a]) -> [[a]] -> Map DateSpan [a]
forall a b. (a -> b) -> a -> b
$ [a] -> [[a]]
forall a. a -> [a]
repeat []

    -- Group postings into their columns
    columns :: Map DateSpan [Posting]
columns = ((Posting, Day)
 -> Map DateSpan [Posting] -> Map DateSpan [Posting])
-> Map DateSpan [Posting]
-> [(Posting, Day)]
-> Map DateSpan [Posting]
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (Posting, Day) -> Map DateSpan [Posting] -> Map DateSpan [Posting]
forall a. (a, Day) -> Map DateSpan [a] -> Map DateSpan [a]
addPosting Map DateSpan [Posting]
forall a. Map DateSpan [a]
emptyMap [(Posting, Day)]
ps

-- | Gather postings matching the query within the report period.
getPostings :: ReportSpec -> Journal -> [(Posting, Day)]
getPostings :: ReportSpec -> Journal -> [(Posting, Day)]
getPostings ReportSpec{rsQuery :: ReportSpec -> Query
rsQuery=Query
query,rsOpts :: ReportSpec -> ReportOpts
rsOpts=ReportOpts
ropts} =
    (Posting -> (Posting, Day)) -> [Posting] -> [(Posting, Day)]
forall a b. (a -> b) -> [a] -> [b]
map (\Posting
p -> (Posting
p, Posting -> Day
date Posting
p)) ([Posting] -> [(Posting, Day)])
-> (Journal -> [Posting]) -> Journal -> [(Posting, Day)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
    Journal -> [Posting]
journalPostings (Journal -> [Posting])
-> (Journal -> Journal) -> Journal -> [Posting]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
    Query -> Journal -> Journal
filterJournalAmounts Query
symq (Journal -> Journal) -> (Journal -> Journal) -> Journal -> Journal
forall b c a. (b -> c) -> (a -> b) -> a -> c
.    -- remove amount parts excluded by cur:
    Query -> Journal -> Journal
filterJournalPostings Query
reportq  -- remove postings not matched by (adjusted) query
  where
    symq :: Query
symq = [Char] -> Query -> Query
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"symq" (Query -> Query) -> (Query -> Query) -> Query -> Query
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Query -> Bool) -> Query -> Query
filterQuery Query -> Bool
queryIsSym (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ [Char] -> Query -> Query
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"requested q" Query
query
    -- The user's query with no depth limit, and expanded to the report span
    -- if there is one (otherwise any date queries are left as-is, which
    -- handles the hledger-ui+future txns case above).
    reportq :: Query
reportq = [Char] -> Query -> Query
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"reportq" (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ Query -> Query
depthless Query
query
    depthless :: Query -> Query
depthless = [Char] -> Query -> Query
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"depthless" (Query -> Query) -> (Query -> Query) -> Query -> Query
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Query -> Bool) -> Query -> Query
filterQuery (Bool -> Bool
not (Bool -> Bool) -> (Query -> Bool) -> Query -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Query -> Bool
queryIsDepth)

    date :: Posting -> Day
date = case ReportOpts -> WhichDate
whichDateFromOpts ReportOpts
ropts of
        WhichDate
PrimaryDate   -> Posting -> Day
postingDate
        WhichDate
SecondaryDate -> Posting -> Day
postingDate2

-- | Calculate the DateSpans to be used for the columns of the report.
calculateColSpans :: ReportOpts -> DateSpan -> [Day] -> [DateSpan]
calculateColSpans :: ReportOpts -> DateSpan -> [Day] -> [DateSpan]
calculateColSpans ReportOpts
ropts DateSpan
reportspan [Day]
days =
    Interval -> DateSpan -> [DateSpan]
splitSpan (ReportOpts -> Interval
interval_ ReportOpts
ropts) DateSpan
displayspan
  where
    displayspan :: DateSpan
displayspan
      | ReportOpts -> Bool
empty_ ReportOpts
ropts = [Char] -> DateSpan -> DateSpan
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"displayspan (-E)" DateSpan
reportspan                        -- all the requested intervals
      | Bool
otherwise = [Char] -> DateSpan -> DateSpan
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"displayspan" (DateSpan -> DateSpan) -> DateSpan -> DateSpan
forall a b. (a -> b) -> a -> b
$ DateSpan
reportspan DateSpan -> DateSpan -> DateSpan
`spanIntersect` DateSpan
matchedspan  -- exclude leading/trailing empty intervals
    matchedspan :: DateSpan
matchedspan = [Char] -> DateSpan -> DateSpan
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"matchedspan" (DateSpan -> DateSpan) -> DateSpan -> DateSpan
forall a b. (a -> b) -> a -> b
$ [Day] -> DateSpan
daysSpan [Day]
days


-- | Gather the account balance changes into a regular matrix
-- including the accounts from all columns.
calculateAccountChanges :: ReportSpec -> [DateSpan] -> Map DateSpan [Posting]
                        -> HashMap ClippedAccountName (Map DateSpan Account)
calculateAccountChanges :: ReportSpec
-> [DateSpan]
-> Map DateSpan [Posting]
-> HashMap AccountName (Map DateSpan Account)
calculateAccountChanges ReportSpec
rspec [DateSpan]
colspans Map DateSpan [Posting]
colps
    | Query -> Maybe Int
queryDepth (ReportSpec -> Query
rsQuery ReportSpec
rspec) Maybe Int -> Maybe Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int -> Maybe Int
forall a. a -> Maybe a
Just Int
0 = HashMap AccountName (Map DateSpan Account)
acctchanges HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
forall a. Semigroup a => a -> a -> a
<> HashMap AccountName (Map DateSpan Account)
elided
    | Bool
otherwise = HashMap AccountName (Map DateSpan Account)
acctchanges
  where
    -- Transpose to get each account's balance changes across all columns.
    acctchanges :: HashMap AccountName (Map DateSpan Account)
acctchanges = Map DateSpan (HashMap AccountName Account)
-> HashMap AccountName (Map DateSpan Account)
forall a.
Map DateSpan (HashMap AccountName a)
-> HashMap AccountName (Map DateSpan a)
transposeMap Map DateSpan (HashMap AccountName Account)
colacctchanges

    Map DateSpan (HashMap AccountName Account)
colacctchanges :: Map DateSpan (HashMap ClippedAccountName Account) =
      [Char]
-> Map DateSpan (HashMap AccountName Account)
-> Map DateSpan (HashMap AccountName Account)
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"colacctchanges" (Map DateSpan (HashMap AccountName Account)
 -> Map DateSpan (HashMap AccountName Account))
-> Map DateSpan (HashMap AccountName Account)
-> Map DateSpan (HashMap AccountName Account)
forall a b. (a -> b) -> a -> b
$ ([Posting] -> HashMap AccountName Account)
-> Map DateSpan [Posting]
-> Map DateSpan (HashMap AccountName Account)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (ReportSpec -> [Posting] -> HashMap AccountName Account
acctChangesFromPostings ReportSpec
rspec) Map DateSpan [Posting]
colps

    elided :: HashMap AccountName (Map DateSpan Account)
elided = AccountName
-> Map DateSpan Account
-> HashMap AccountName (Map DateSpan Account)
forall k v. Hashable k => k -> v -> HashMap k v
HM.singleton AccountName
"..." (Map DateSpan Account
 -> HashMap AccountName (Map DateSpan Account))
-> Map DateSpan Account
-> HashMap AccountName (Map DateSpan Account)
forall a b. (a -> b) -> a -> b
$ [(DateSpan, Account)] -> Map DateSpan Account
forall k a. Ord k => [(k, a)] -> Map k a
M.fromList [(DateSpan
span, Account
nullacct) | DateSpan
span <- [DateSpan]
colspans]

-- | Given a set of postings, eg for a single report column, gather
-- the accounts that have postings and calculate the change amount for
-- each. Accounts and amounts will be depth-clipped appropriately if
-- a depth limit is in effect.
acctChangesFromPostings :: ReportSpec -> [Posting] -> HashMap ClippedAccountName Account
acctChangesFromPostings :: ReportSpec -> [Posting] -> HashMap AccountName Account
acctChangesFromPostings ReportSpec{rsQuery :: ReportSpec -> Query
rsQuery=Query
query,rsOpts :: ReportSpec -> ReportOpts
rsOpts=ReportOpts
ropts} [Posting]
ps =
    [(AccountName, Account)] -> HashMap AccountName Account
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
HM.fromList [(Account -> AccountName
aname Account
a, Account
a) | Account
a <- [Account]
as]
  where
    as :: [Account]
as = [Account] -> [Account]
filterAccounts ([Account] -> [Account])
-> ([Account] -> [Account]) -> [Account] -> [Account]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> [Account] -> [Account]
forall a. Int -> [a] -> [a]
drop Int
1 ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$ [Posting] -> [Account]
accountsFromPostings [Posting]
ps
    filterAccounts :: [Account] -> [Account]
filterAccounts = case ReportOpts -> AccountListMode
accountlistmode_ ReportOpts
ropts of
        AccountListMode
ALTree -> (Account -> Bool) -> [Account] -> [Account]
forall a. (a -> Bool) -> [a] -> [a]
filter ((Query
depthq Query -> AccountName -> Bool
`matchesAccount`) (AccountName -> Bool)
-> (Account -> AccountName) -> Account -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Account -> AccountName
aname)      -- exclude deeper balances
        AccountListMode
ALFlat -> Maybe Int -> [Account] -> [Account]
clipAccountsAndAggregate (Query -> Maybe Int
queryDepth Query
depthq) ([Account] -> [Account])
-> ([Account] -> [Account]) -> [Account] -> [Account]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.  -- aggregate deeper balances at the depth limit.
                      (Account -> Bool) -> [Account] -> [Account]
forall a. (a -> Bool) -> [a] -> [a]
filter ((Int
0Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<) (Int -> Bool) -> (Account -> Int) -> Account -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Account -> Int
anumpostings)
    depthq :: Query
depthq = [Char] -> Query -> Query
forall a. Show a => [Char] -> a -> a
dbg3 [Char]
"depthq" (Query -> Query) -> Query -> Query
forall a b. (a -> b) -> a -> b
$ (Query -> Bool) -> Query -> Query
filterQuery Query -> Bool
queryIsDepth Query
query

-- | Accumulate and value amounts, as specified by the report options.
--
-- Makes sure all report columns have an entry.
accumValueAmounts :: ReportOpts -> (Day -> MixedAmount -> MixedAmount) -> [DateSpan]
                  -> HashMap ClippedAccountName Account
                  -> HashMap ClippedAccountName (Map DateSpan Account)
                  -> HashMap ClippedAccountName (Map DateSpan Account)
accumValueAmounts :: ReportOpts
-> (Day -> MixedAmount -> MixedAmount)
-> [DateSpan]
-> HashMap AccountName Account
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
accumValueAmounts ReportOpts
ropts Day -> MixedAmount -> MixedAmount
valuation [DateSpan]
colspans HashMap AccountName Account
startbals HashMap AccountName (Map DateSpan Account)
acctchanges =  -- PARTIAL:
    -- Ensure all columns have entries, including those with starting balances
    (AccountName -> Map DateSpan Account -> Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
forall k v1 v2. (k -> v1 -> v2) -> HashMap k v1 -> HashMap k v2
HM.mapWithKey AccountName -> Map DateSpan Account -> Map DateSpan Account
rowbals (HashMap AccountName (Map DateSpan Account)
 -> HashMap AccountName (Map DateSpan Account))
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
forall a b. (a -> b) -> a -> b
$ ((Map DateSpan Account
-> Map DateSpan Account -> Map DateSpan Account
forall a. Semigroup a => a -> a -> a
<>Map DateSpan Account
zeros) (Map DateSpan Account -> Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> HashMap AccountName (Map DateSpan Account)
acctchanges) HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
forall a. Semigroup a => a -> a -> a
<> (Map DateSpan Account
zeros Map DateSpan Account
-> HashMap AccountName Account
-> HashMap AccountName (Map DateSpan Account)
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ HashMap AccountName Account
startbals)
  where
    -- The valued row amounts to be displayed: per-period changes,
    -- zero-based cumulative totals, or
    -- starting-balance-based historical balances.
    rowbals :: AccountName -> Map DateSpan Account -> Map DateSpan Account
rowbals AccountName
name Map DateSpan Account
changes = [Char] -> Map DateSpan Account -> Map DateSpan Account
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"rowbals" (Map DateSpan Account -> Map DateSpan Account)
-> Map DateSpan Account -> Map DateSpan Account
forall a b. (a -> b) -> a -> b
$ case ReportOpts -> BalanceType
balancetype_ ReportOpts
ropts of
        BalanceType
PeriodChange      -> Map DateSpan Account
changeamts
        BalanceType
CumulativeChange  -> Map DateSpan Account
cumulative
        BalanceType
HistoricalBalance -> Map DateSpan Account
historical
      where
        historical :: Map DateSpan Account
historical = Account -> Map DateSpan Account
cumulativeSum Account
startingBalance
        cumulative :: Map DateSpan Account
cumulative | Bool
fixedValuationDate = Account -> Map DateSpan Account
cumulativeSum Account
nullacct
                   | Bool
otherwise          = (Account -> Account)
-> Map DateSpan Account -> Map DateSpan Account
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Account -> Account -> Account
`subtractAcct` Account
valuedStart) Map DateSpan Account
historical
        changeamts :: Map DateSpan Account
changeamts | Bool
fixedValuationDate = (DateSpan -> Account -> Account)
-> Map DateSpan Account -> Map DateSpan Account
forall k a b. (k -> a -> b) -> Map k a -> Map k b
M.mapWithKey DateSpan -> Account -> Account
valueAcct Map DateSpan Account
changes
                   | Bool
otherwise          = [(DateSpan, Account)] -> Map DateSpan Account
forall k a. [(k, a)] -> Map k a
M.fromDistinctAscList ([(DateSpan, Account)] -> Map DateSpan Account)
-> ([Account] -> [(DateSpan, Account)])
-> [Account]
-> Map DateSpan Account
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [DateSpan] -> [Account] -> [(DateSpan, Account)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
dates ([Account] -> Map DateSpan Account)
-> [Account] -> Map DateSpan Account
forall a b. (a -> b) -> a -> b
$
                                            (Account -> Account -> Account)
-> [Account] -> [Account] -> [Account]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith Account -> Account -> Account
subtractAcct [Account]
histamts (Account
valuedStartAccount -> [Account] -> [Account]
forall a. a -> [a] -> [a]
:[Account]
histamts)
          where ([DateSpan]
dates, [Account]
histamts) = [(DateSpan, Account)] -> ([DateSpan], [Account])
forall a b. [(a, b)] -> ([a], [b])
unzip ([(DateSpan, Account)] -> ([DateSpan], [Account]))
-> [(DateSpan, Account)] -> ([DateSpan], [Account])
forall a b. (a -> b) -> a -> b
$ Map DateSpan Account -> [(DateSpan, Account)]
forall k a. Map k a -> [(k, a)]
M.toAscList Map DateSpan Account
historical

        cumulativeSum :: Account -> Map DateSpan Account
cumulativeSum Account
start = (Account, Map DateSpan Account) -> Map DateSpan Account
forall a b. (a, b) -> b
snd ((Account, Map DateSpan Account) -> Map DateSpan Account)
-> (Account, Map DateSpan Account) -> Map DateSpan Account
forall a b. (a -> b) -> a -> b
$ (Account -> DateSpan -> Account -> (Account, Account))
-> Account
-> Map DateSpan Account
-> (Account, Map DateSpan Account)
forall a k b c.
(a -> k -> b -> (a, c)) -> a -> Map k b -> (a, Map k c)
M.mapAccumWithKey Account -> DateSpan -> Account -> (Account, Account)
accumValued Account
start Map DateSpan Account
changes
          where accumValued :: Account -> DateSpan -> Account -> (Account, Account)
accumValued Account
startAmt DateSpan
date Account
newAmt = (Account
s, DateSpan -> Account -> Account
valueAcct DateSpan
date Account
s)
                  where s :: Account
s = Account -> Account -> Account
sumAcct Account
startAmt Account
newAmt

        -- Whether the market price is measured at the same date for all report
        -- periods, and we can therefore use the simpler calculations for
        -- cumulative and change reports.
        fixedValuationDate :: Bool
fixedValuationDate = case ReportOpts -> Maybe ValuationType
value_ ReportOpts
ropts of
            Just (AtCost (Just AccountName
_)) -> Bool
singleperiod
            Just (AtEnd  Maybe AccountName
_)        -> Bool
singleperiod
            Just (AtDefault Maybe AccountName
_)     -> Bool
singleperiod
            Maybe ValuationType
_                      -> Bool
True
          where singleperiod :: Bool
singleperiod = ReportOpts -> Interval
interval_ ReportOpts
ropts Interval -> Interval -> Bool
forall a. Eq a => a -> a -> Bool
== Interval
NoInterval

        startingBalance :: Account
startingBalance = Account -> AccountName -> HashMap AccountName Account -> Account
forall k v. (Eq k, Hashable k) => v -> k -> HashMap k v -> v
HM.lookupDefault Account
nullacct AccountName
name HashMap AccountName Account
startbals
        valuedStart :: Account
valuedStart = DateSpan -> Account -> Account
valueAcct (Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
historicalDate) Account
startingBalance

    -- Add the values of two accounts. Should be right-biased, since it's used
    -- in scanl, so other properties (such as anumpostings) stay in the right place
    sumAcct :: Account -> Account -> Account
sumAcct Account{aibalance :: Account -> MixedAmount
aibalance=MixedAmount
i1,aebalance :: Account -> MixedAmount
aebalance=MixedAmount
e1} a :: Account
a@Account{aibalance :: Account -> MixedAmount
aibalance=MixedAmount
i2,aebalance :: Account -> MixedAmount
aebalance=MixedAmount
e2} =
        Account
a{aibalance :: MixedAmount
aibalance = MixedAmount
i1 MixedAmount -> MixedAmount -> MixedAmount
forall a. Num a => a -> a -> a
+ MixedAmount
i2, aebalance :: MixedAmount
aebalance = MixedAmount
e1 MixedAmount -> MixedAmount -> MixedAmount
forall a. Num a => a -> a -> a
+ MixedAmount
e2}

    -- Subtract the values in one account from another. Should be left-biased.
    subtractAcct :: Account -> Account -> Account
subtractAcct a :: Account
a@Account{aibalance :: Account -> MixedAmount
aibalance=MixedAmount
i1,aebalance :: Account -> MixedAmount
aebalance=MixedAmount
e1} Account{aibalance :: Account -> MixedAmount
aibalance=MixedAmount
i2,aebalance :: Account -> MixedAmount
aebalance=MixedAmount
e2} =
        Account
a{aibalance :: MixedAmount
aibalance = MixedAmount
i1 MixedAmount -> MixedAmount -> MixedAmount
forall a. Num a => a -> a -> a
- MixedAmount
i2, aebalance :: MixedAmount
aebalance = MixedAmount
e1 MixedAmount -> MixedAmount -> MixedAmount
forall a. Num a => a -> a -> a
- MixedAmount
e2}

    -- We may be converting amounts to value, per hledger_options.m4.md "Effect of --value on reports".
    valueAcct :: DateSpan -> Account -> Account
valueAcct (DateSpan Maybe Day
_ (Just Day
end)) Account
acct =
        Account
acct{aibalance :: MixedAmount
aibalance = MixedAmount -> MixedAmount
value (Account -> MixedAmount
aibalance Account
acct), aebalance :: MixedAmount
aebalance = MixedAmount -> MixedAmount
value (Account -> MixedAmount
aebalance Account
acct)}
      where value :: MixedAmount -> MixedAmount
value = Day -> MixedAmount -> MixedAmount
valuation (Integer -> Day -> Day
addDays (-Integer
1) Day
end)
    valueAcct DateSpan
_ Account
_ = [Char] -> Account
forall a. HasCallStack => [Char] -> a
error [Char]
"multiBalanceReport: expected all spans to have an end date"  -- XXX should not happen

    zeros :: Map DateSpan Account
zeros = [(DateSpan, Account)] -> Map DateSpan Account
forall k a. Ord k => [(k, a)] -> Map k a
M.fromList [(DateSpan
span, Account
nullacct) | DateSpan
span <- [DateSpan]
colspans]
    historicalDate :: Maybe Day
historicalDate = [Day] -> Maybe Day
forall a. Ord a => [a] -> Maybe a
minimumMay ([Day] -> Maybe Day) -> [Day] -> Maybe Day
forall a b. (a -> b) -> a -> b
$ (DateSpan -> Maybe Day) -> [DateSpan] -> [Day]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe DateSpan -> Maybe Day
spanStart [DateSpan]
colspans


-- | Lay out a set of postings grouped by date span into a regular matrix with rows
-- given by AccountName and columns by DateSpan, then generate a MultiBalanceReport
-- from the columns.
generateMultiBalanceReport :: ReportSpec -> Journal -> (Day -> MixedAmount -> MixedAmount) -> [DateSpan]
                           -> Map DateSpan [Posting] -> HashMap AccountName Account
                           -> MultiBalanceReport
generateMultiBalanceReport :: ReportSpec
-> Journal
-> (Day -> MixedAmount -> MixedAmount)
-> [DateSpan]
-> Map DateSpan [Posting]
-> HashMap AccountName Account
-> MultiBalanceReport
generateMultiBalanceReport rspec :: ReportSpec
rspec@ReportSpec{rsOpts :: ReportSpec -> ReportOpts
rsOpts=ReportOpts
ropts} Journal
j Day -> MixedAmount -> MixedAmount
valuation [DateSpan]
colspans Map DateSpan [Posting]
colps HashMap AccountName Account
startbals =
    MultiBalanceReport
report
  where
    -- Each account's balance changes across all columns.
    acctchanges :: HashMap AccountName (Map DateSpan Account)
acctchanges = [Char]
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"acctchanges" (HashMap AccountName (Map DateSpan Account)
 -> HashMap AccountName (Map DateSpan Account))
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
forall a b. (a -> b) -> a -> b
$ ReportSpec
-> [DateSpan]
-> Map DateSpan [Posting]
-> HashMap AccountName (Map DateSpan Account)
calculateAccountChanges ReportSpec
rspec [DateSpan]
colspans Map DateSpan [Posting]
colps

    -- Process changes into normal, cumulative, or historical amounts, plus value them
    accumvalued :: HashMap AccountName (Map DateSpan Account)
accumvalued = ReportOpts
-> (Day -> MixedAmount -> MixedAmount)
-> [DateSpan]
-> HashMap AccountName Account
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
accumValueAmounts ReportOpts
ropts Day -> MixedAmount -> MixedAmount
valuation [DateSpan]
colspans HashMap AccountName Account
startbals HashMap AccountName (Map DateSpan Account)
acctchanges

    -- All account names that will be displayed, possibly depth-clipped.
    displaynames :: HashMap AccountName DisplayName
displaynames = [Char]
-> HashMap AccountName DisplayName
-> HashMap AccountName DisplayName
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"displaynames" (HashMap AccountName DisplayName
 -> HashMap AccountName DisplayName)
-> HashMap AccountName DisplayName
-> HashMap AccountName DisplayName
forall a b. (a -> b) -> a -> b
$ ReportSpec
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName DisplayName
displayedAccounts ReportSpec
rspec HashMap AccountName (Map DateSpan Account)
accumvalued

    -- All the rows of the report.
    rows :: [PeriodicReportRow DisplayName MixedAmount]
rows = [Char]
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"rows"
             ([PeriodicReportRow DisplayName MixedAmount]
 -> [PeriodicReportRow DisplayName MixedAmount])
-> ([PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount])
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (if ReportOpts -> Bool
invert_ ReportOpts
ropts then (PeriodicReportRow DisplayName MixedAmount
 -> PeriodicReportRow DisplayName MixedAmount)
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
forall a b. (a -> b) -> [a] -> [b]
map ((MixedAmount -> MixedAmount)
-> PeriodicReportRow DisplayName MixedAmount
-> PeriodicReportRow DisplayName MixedAmount
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap MixedAmount -> MixedAmount
forall a. Num a => a -> a
negate) else [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
forall a. a -> a
id)  -- Negate amounts if applicable
             ([PeriodicReportRow DisplayName MixedAmount]
 -> [PeriodicReportRow DisplayName MixedAmount])
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
forall a b. (a -> b) -> a -> b
$ ReportOpts
-> HashMap AccountName DisplayName
-> HashMap AccountName (Map DateSpan Account)
-> [PeriodicReportRow DisplayName MixedAmount]
buildReportRows ReportOpts
ropts HashMap AccountName DisplayName
displaynames HashMap AccountName (Map DateSpan Account)
accumvalued

    -- Calculate column totals
    totalsrow :: PeriodicReportRow () MixedAmount
totalsrow = [Char]
-> PeriodicReportRow () MixedAmount
-> PeriodicReportRow () MixedAmount
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"totalsrow" (PeriodicReportRow () MixedAmount
 -> PeriodicReportRow () MixedAmount)
-> PeriodicReportRow () MixedAmount
-> PeriodicReportRow () MixedAmount
forall a b. (a -> b) -> a -> b
$ ReportOpts
-> [PeriodicReportRow DisplayName MixedAmount]
-> PeriodicReportRow () MixedAmount
calculateTotalsRow ReportOpts
ropts [PeriodicReportRow DisplayName MixedAmount]
rows

    -- Sorted report rows.
    sortedrows :: [PeriodicReportRow DisplayName MixedAmount]
sortedrows = [Char]
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"sortedrows" ([PeriodicReportRow DisplayName MixedAmount]
 -> [PeriodicReportRow DisplayName MixedAmount])
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
forall a b. (a -> b) -> a -> b
$ ReportOpts
-> Journal
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
sortRows ReportOpts
ropts Journal
j [PeriodicReportRow DisplayName MixedAmount]
rows

    -- Take percentages if needed
    report :: MultiBalanceReport
report = ReportOpts -> MultiBalanceReport -> MultiBalanceReport
reportPercent ReportOpts
ropts (MultiBalanceReport -> MultiBalanceReport)
-> MultiBalanceReport -> MultiBalanceReport
forall a b. (a -> b) -> a -> b
$ [DateSpan]
-> [PeriodicReportRow DisplayName MixedAmount]
-> PeriodicReportRow () MixedAmount
-> MultiBalanceReport
forall a b.
[DateSpan]
-> [PeriodicReportRow a b]
-> PeriodicReportRow () b
-> PeriodicReport a b
PeriodicReport [DateSpan]
colspans [PeriodicReportRow DisplayName MixedAmount]
sortedrows PeriodicReportRow () MixedAmount
totalsrow

-- | Build the report rows.
--
-- One row per account, with account name info, row amounts, row total and row average.
buildReportRows :: ReportOpts
                -> HashMap AccountName DisplayName
                -> HashMap AccountName (Map DateSpan Account)
                -> [MultiBalanceReportRow]
buildReportRows :: ReportOpts
-> HashMap AccountName DisplayName
-> HashMap AccountName (Map DateSpan Account)
-> [PeriodicReportRow DisplayName MixedAmount]
buildReportRows ReportOpts
ropts HashMap AccountName DisplayName
displaynames = HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
-> [PeriodicReportRow DisplayName MixedAmount]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList (HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
 -> [PeriodicReportRow DisplayName MixedAmount])
-> (HashMap AccountName (Map DateSpan Account)
    -> HashMap AccountName (PeriodicReportRow DisplayName MixedAmount))
-> HashMap AccountName (Map DateSpan Account)
-> [PeriodicReportRow DisplayName MixedAmount]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (AccountName
 -> Map DateSpan Account
 -> Maybe (PeriodicReportRow DisplayName MixedAmount))
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
forall k v1 v2.
(k -> v1 -> Maybe v2) -> HashMap k v1 -> HashMap k v2
HM.mapMaybeWithKey AccountName
-> Map DateSpan Account
-> Maybe (PeriodicReportRow DisplayName MixedAmount)
forall (t :: * -> *).
Foldable t =>
AccountName
-> t Account -> Maybe (PeriodicReportRow DisplayName MixedAmount)
mkRow
  where
    mkRow :: AccountName
-> t Account -> Maybe (PeriodicReportRow DisplayName MixedAmount)
mkRow AccountName
name t Account
accts = do
        DisplayName
displayname <- AccountName -> HashMap AccountName DisplayName -> Maybe DisplayName
forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
HM.lookup AccountName
name HashMap AccountName DisplayName
displaynames
        PeriodicReportRow DisplayName MixedAmount
-> Maybe (PeriodicReportRow DisplayName MixedAmount)
forall (m :: * -> *) a. Monad m => a -> m a
return (PeriodicReportRow DisplayName MixedAmount
 -> Maybe (PeriodicReportRow DisplayName MixedAmount))
-> PeriodicReportRow DisplayName MixedAmount
-> Maybe (PeriodicReportRow DisplayName MixedAmount)
forall a b. (a -> b) -> a -> b
$ DisplayName
-> [MixedAmount]
-> MixedAmount
-> MixedAmount
-> PeriodicReportRow DisplayName MixedAmount
forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow DisplayName
displayname [MixedAmount]
rowbals MixedAmount
rowtot MixedAmount
rowavg
      where
        rowbals :: [MixedAmount]
rowbals = (Account -> MixedAmount) -> [Account] -> [MixedAmount]
forall a b. (a -> b) -> [a] -> [b]
map Account -> MixedAmount
balance ([Account] -> [MixedAmount]) -> [Account] -> [MixedAmount]
forall a b. (a -> b) -> a -> b
$ t Account -> [Account]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList t Account
accts
        -- The total and average for the row.
        -- These are always simply the sum/average of the displayed row amounts.
        -- Total for a cumulative/historical report is always the last column.
        rowtot :: MixedAmount
rowtot = case ReportOpts -> BalanceType
balancetype_ ReportOpts
ropts of
            BalanceType
PeriodChange -> [MixedAmount] -> MixedAmount
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [MixedAmount]
rowbals
            BalanceType
_            -> MixedAmount -> [MixedAmount] -> MixedAmount
forall a. a -> [a] -> a
lastDef MixedAmount
0 [MixedAmount]
rowbals
        rowavg :: MixedAmount
rowavg = [MixedAmount] -> MixedAmount
averageMixedAmounts [MixedAmount]
rowbals
    balance :: Account -> MixedAmount
balance = case ReportOpts -> AccountListMode
accountlistmode_ ReportOpts
ropts of AccountListMode
ALTree -> Account -> MixedAmount
aibalance; AccountListMode
ALFlat -> Account -> MixedAmount
aebalance

-- | Calculate accounts which are to be displayed in the report, as well as
-- their name and depth
displayedAccounts :: ReportSpec -> HashMap AccountName (Map DateSpan Account)
                  -> HashMap AccountName DisplayName
displayedAccounts :: ReportSpec
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName DisplayName
displayedAccounts ReportSpec{rsQuery :: ReportSpec -> Query
rsQuery=Query
query,rsOpts :: ReportSpec -> ReportOpts
rsOpts=ReportOpts
ropts} HashMap AccountName (Map DateSpan Account)
valuedaccts
    | Int
depth Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 = AccountName -> DisplayName -> HashMap AccountName DisplayName
forall k v. Hashable k => k -> v -> HashMap k v
HM.singleton AccountName
"..." (DisplayName -> HashMap AccountName DisplayName)
-> DisplayName -> HashMap AccountName DisplayName
forall a b. (a -> b) -> a -> b
$ AccountName -> AccountName -> Int -> DisplayName
DisplayName AccountName
"..." AccountName
"..." Int
1
    | Bool
otherwise  = (AccountName -> Map DateSpan Account -> DisplayName)
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName DisplayName
forall k v1 v2. (k -> v1 -> v2) -> HashMap k v1 -> HashMap k v2
HM.mapWithKey (\AccountName
a Map DateSpan Account
_ -> AccountName -> DisplayName
displayedName AccountName
a) HashMap AccountName (Map DateSpan Account)
displayedAccts
  where
    -- Accounts which are to be displayed
    displayedAccts :: HashMap AccountName (Map DateSpan Account)
displayedAccts = (if Int
depth Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 then HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
forall a. a -> a
id else (AccountName -> Map DateSpan Account -> Bool)
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
forall k v. (k -> v -> Bool) -> HashMap k v -> HashMap k v
HM.filterWithKey AccountName -> Map DateSpan Account -> Bool
forall (t :: * -> *).
Foldable t =>
AccountName -> t Account -> Bool
keep) HashMap AccountName (Map DateSpan Account)
valuedaccts
      where
        keep :: AccountName -> t Account -> Bool
keep AccountName
name t Account
amts = AccountName -> t Account -> Bool
forall (t :: * -> *).
Foldable t =>
AccountName -> t Account -> Bool
isInteresting AccountName
name t Account
amts Bool -> Bool -> Bool
|| AccountName
name AccountName -> HashMap AccountName Int -> Bool
forall k a. (Eq k, Hashable k) => k -> HashMap k a -> Bool
`HM.member` HashMap AccountName Int
interestingParents

    displayedName :: AccountName -> DisplayName
displayedName AccountName
name = case ReportOpts -> AccountListMode
accountlistmode_ ReportOpts
ropts of
        AccountListMode
ALTree -> AccountName -> AccountName -> Int -> DisplayName
DisplayName AccountName
name AccountName
leaf (Int -> DisplayName) -> (Int -> Int) -> Int -> DisplayName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 (Int -> DisplayName) -> Int -> DisplayName
forall a b. (a -> b) -> a -> b
$ Int
level Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
boringParents
        AccountListMode
ALFlat -> AccountName -> AccountName -> Int -> DisplayName
DisplayName AccountName
name AccountName
droppedName Int
1
      where
        droppedName :: AccountName
droppedName = Int -> AccountName -> AccountName
accountNameDrop (ReportOpts -> Int
drop_ ReportOpts
ropts) AccountName
name
        leaf :: AccountName
leaf = [AccountName] -> AccountName
accountNameFromComponents ([AccountName] -> AccountName)
-> ([AccountName] -> [AccountName]) -> [AccountName] -> AccountName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [AccountName] -> [AccountName]
forall a. [a] -> [a]
reverse ([AccountName] -> [AccountName])
-> ([AccountName] -> [AccountName])
-> [AccountName]
-> [AccountName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (AccountName -> AccountName) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map AccountName -> AccountName
accountLeafName ([AccountName] -> AccountName) -> [AccountName] -> AccountName
forall a b. (a -> b) -> a -> b
$
            AccountName
droppedName AccountName -> [AccountName] -> [AccountName]
forall a. a -> [a] -> [a]
: (AccountName -> Bool) -> [AccountName] -> [AccountName]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile AccountName -> Bool
notDisplayed [AccountName]
parents

        level :: Int
level = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ AccountName -> Int
accountNameLevel AccountName
name Int -> Int -> Int
forall a. Num a => a -> a -> a
- ReportOpts -> Int
drop_ ReportOpts
ropts
        parents :: [AccountName]
parents = Int -> [AccountName] -> [AccountName]
forall a. Int -> [a] -> [a]
take (Int
level Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) ([AccountName] -> [AccountName]) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> a -> b
$ AccountName -> [AccountName]
parentAccountNames AccountName
name
        boringParents :: Int
boringParents = if ReportOpts -> Bool
no_elide_ ReportOpts
ropts then Int
0 else [AccountName] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([AccountName] -> Int) -> [AccountName] -> Int
forall a b. (a -> b) -> a -> b
$ (AccountName -> Bool) -> [AccountName] -> [AccountName]
forall a. (a -> Bool) -> [a] -> [a]
filter AccountName -> Bool
notDisplayed [AccountName]
parents
        notDisplayed :: AccountName -> Bool
notDisplayed = Bool -> Bool
not (Bool -> Bool) -> (AccountName -> Bool) -> AccountName -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (AccountName -> HashMap AccountName (Map DateSpan Account) -> Bool
forall k a. (Eq k, Hashable k) => k -> HashMap k a -> Bool
`HM.member` HashMap AccountName (Map DateSpan Account)
displayedAccts)

    -- Accounts interesting for their own sake
    isInteresting :: AccountName -> t Account -> Bool
isInteresting AccountName
name t Account
amts =
        Int
d Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
depth                                     -- Throw out anything too deep
        Bool -> Bool -> Bool
&& ((ReportOpts -> Bool
empty_ ReportOpts
ropts Bool -> Bool -> Bool
&& (Account -> Bool) -> t Account -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all ([Account] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null ([Account] -> Bool) -> (Account -> [Account]) -> Account -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Account -> [Account]
asubs) t Account
amts)  -- Keep all leaves when using empty_
           Bool -> Bool -> Bool
|| Bool -> Bool
not ((Account -> MixedAmount) -> t Account -> Bool
forall (t :: * -> *) a.
Foldable t =>
(a -> MixedAmount) -> t a -> Bool
isZeroRow Account -> MixedAmount
balance t Account
amts))            -- Throw out anything with zero balance
      where
        d :: Int
d = AccountName -> Int
accountNameLevel AccountName
name
        balance :: Account -> MixedAmount
balance | AccountListMode
ALTree <- ReportOpts -> AccountListMode
accountlistmode_ ReportOpts
ropts, Int
d Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
depth = Account -> MixedAmount
aibalance
                | Bool
otherwise = Account -> MixedAmount
aebalance

    -- Accounts interesting because they are a fork for interesting subaccounts
    interestingParents :: HashMap AccountName Int
interestingParents = [Char] -> HashMap AccountName Int -> HashMap AccountName Int
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"interestingParents" (HashMap AccountName Int -> HashMap AccountName Int)
-> HashMap AccountName Int -> HashMap AccountName Int
forall a b. (a -> b) -> a -> b
$ case ReportOpts -> AccountListMode
accountlistmode_ ReportOpts
ropts of
        AccountListMode
ALTree -> (AccountName -> Int -> Bool)
-> HashMap AccountName Int -> HashMap AccountName Int
forall k v. (k -> v -> Bool) -> HashMap k v -> HashMap k v
HM.filterWithKey AccountName -> Int -> Bool
hasEnoughSubs HashMap AccountName Int
numSubs
        AccountListMode
ALFlat -> HashMap AccountName Int
forall a. Monoid a => a
mempty
      where
        hasEnoughSubs :: AccountName -> Int -> Bool
hasEnoughSubs AccountName
name Int
nsubs = Int
nsubs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
minSubs Bool -> Bool -> Bool
&& AccountName -> Int
accountNameLevel AccountName
name Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> ReportOpts -> Int
drop_ ReportOpts
ropts
        minSubs :: Int
minSubs = if ReportOpts -> Bool
no_elide_ ReportOpts
ropts then Int
1 else Int
2

    isZeroRow :: (a -> MixedAmount) -> t a -> Bool
isZeroRow a -> MixedAmount
balance = (a -> Bool) -> t a -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (MixedAmount -> Bool
mixedAmountLooksZero (MixedAmount -> Bool) -> (a -> MixedAmount) -> a -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> MixedAmount
balance)
    depth :: Int
depth = Int -> Maybe Int -> Int
forall a. a -> Maybe a -> a
fromMaybe Int
forall a. Bounded a => a
maxBound (Maybe Int -> Int) -> Maybe Int -> Int
forall a b. (a -> b) -> a -> b
$  Query -> Maybe Int
queryDepth Query
query
    numSubs :: HashMap AccountName Int
numSubs = [AccountName] -> HashMap AccountName Int
subaccountTallies ([AccountName] -> HashMap AccountName Int)
-> (HashMap AccountName (Map DateSpan Account) -> [AccountName])
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HashMap AccountName (Map DateSpan Account) -> [AccountName]
forall k v. HashMap k v -> [k]
HM.keys (HashMap AccountName (Map DateSpan Account)
 -> HashMap AccountName Int)
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName Int
forall a b. (a -> b) -> a -> b
$ (AccountName -> Map DateSpan Account -> Bool)
-> HashMap AccountName (Map DateSpan Account)
-> HashMap AccountName (Map DateSpan Account)
forall k v. (k -> v -> Bool) -> HashMap k v -> HashMap k v
HM.filterWithKey AccountName -> Map DateSpan Account -> Bool
forall (t :: * -> *).
Foldable t =>
AccountName -> t Account -> Bool
isInteresting HashMap AccountName (Map DateSpan Account)
valuedaccts

-- | Sort the rows by amount or by account declaration order.
sortRows :: ReportOpts -> Journal -> [MultiBalanceReportRow] -> [MultiBalanceReportRow]
sortRows :: ReportOpts
-> Journal
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
sortRows ReportOpts
ropts Journal
j
    | ReportOpts -> Bool
sort_amount_ ReportOpts
ropts, AccountListMode
ALTree <- ReportOpts -> AccountListMode
accountlistmode_ ReportOpts
ropts = [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
sortTreeMBRByAmount
    | ReportOpts -> Bool
sort_amount_ ReportOpts
ropts, AccountListMode
ALFlat <- ReportOpts -> AccountListMode
accountlistmode_ ReportOpts
ropts = [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
sortFlatMBRByAmount
    | Bool
otherwise                                            = [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
sortMBRByAccountDeclaration
  where
    -- Sort the report rows, representing a tree of accounts, by row total at each level.
    -- Similar to sortMBRByAccountDeclaration/sortAccountNamesByDeclaration.
    sortTreeMBRByAmount :: [MultiBalanceReportRow] -> [MultiBalanceReportRow]
    sortTreeMBRByAmount :: [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
sortTreeMBRByAmount [PeriodicReportRow DisplayName MixedAmount]
rows = (AccountName -> Maybe (PeriodicReportRow DisplayName MixedAmount))
-> [AccountName] -> [PeriodicReportRow DisplayName MixedAmount]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (AccountName
-> HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
-> Maybe (PeriodicReportRow DisplayName MixedAmount)
forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
`HM.lookup` HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
rowMap) [AccountName]
sortedanames
      where
        accounttree :: Account
accounttree = AccountName -> [AccountName] -> Account
accountTree AccountName
"root" ([AccountName] -> Account) -> [AccountName] -> Account
forall a b. (a -> b) -> a -> b
$ (PeriodicReportRow DisplayName MixedAmount -> AccountName)
-> [PeriodicReportRow DisplayName MixedAmount] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow DisplayName MixedAmount -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName [PeriodicReportRow DisplayName MixedAmount]
rows
        rowMap :: HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
rowMap = [(AccountName, PeriodicReportRow DisplayName MixedAmount)]
-> HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
HM.fromList ([(AccountName, PeriodicReportRow DisplayName MixedAmount)]
 -> HashMap AccountName (PeriodicReportRow DisplayName MixedAmount))
-> [(AccountName, PeriodicReportRow DisplayName MixedAmount)]
-> HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
forall a b. (a -> b) -> a -> b
$ (PeriodicReportRow DisplayName MixedAmount
 -> (AccountName, PeriodicReportRow DisplayName MixedAmount))
-> [PeriodicReportRow DisplayName MixedAmount]
-> [(AccountName, PeriodicReportRow DisplayName MixedAmount)]
forall a b. (a -> b) -> [a] -> [b]
map (\PeriodicReportRow DisplayName MixedAmount
row -> (PeriodicReportRow DisplayName MixedAmount -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName PeriodicReportRow DisplayName MixedAmount
row, PeriodicReportRow DisplayName MixedAmount
row)) [PeriodicReportRow DisplayName MixedAmount]
rows
        -- Set the inclusive balance of an account from the rows, or sum the
        -- subaccounts if it's not present
        accounttreewithbals :: Account
accounttreewithbals = (Account -> Account) -> Account -> Account
mapAccounts Account -> Account
setibalance Account
accounttree
        setibalance :: Account -> Account
setibalance Account
a = Account
a{aibalance :: MixedAmount
aibalance = MixedAmount
-> (PeriodicReportRow DisplayName MixedAmount -> MixedAmount)
-> Maybe (PeriodicReportRow DisplayName MixedAmount)
-> MixedAmount
forall b a. b -> (a -> b) -> Maybe a -> b
maybe ([MixedAmount] -> MixedAmount
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ([MixedAmount] -> MixedAmount)
-> ([Account] -> [MixedAmount]) -> [Account] -> MixedAmount
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Account -> MixedAmount) -> [Account] -> [MixedAmount]
forall a b. (a -> b) -> [a] -> [b]
map Account -> MixedAmount
aibalance ([Account] -> MixedAmount) -> [Account] -> MixedAmount
forall a b. (a -> b) -> a -> b
$ Account -> [Account]
asubs Account
a) PeriodicReportRow DisplayName MixedAmount -> MixedAmount
forall a b. PeriodicReportRow a b -> b
prrTotal (Maybe (PeriodicReportRow DisplayName MixedAmount) -> MixedAmount)
-> Maybe (PeriodicReportRow DisplayName MixedAmount) -> MixedAmount
forall a b. (a -> b) -> a -> b
$
                                          AccountName
-> HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
-> Maybe (PeriodicReportRow DisplayName MixedAmount)
forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
HM.lookup (Account -> AccountName
aname Account
a) HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
rowMap}
        sortedaccounttree :: Account
sortedaccounttree = NormalSign -> Account -> Account
sortAccountTreeByAmount (NormalSign -> Maybe NormalSign -> NormalSign
forall a. a -> Maybe a -> a
fromMaybe NormalSign
NormallyPositive (Maybe NormalSign -> NormalSign) -> Maybe NormalSign -> NormalSign
forall a b. (a -> b) -> a -> b
$ ReportOpts -> Maybe NormalSign
normalbalance_ ReportOpts
ropts) Account
accounttreewithbals
        sortedanames :: [AccountName]
sortedanames = (Account -> AccountName) -> [Account] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map Account -> AccountName
aname ([Account] -> [AccountName]) -> [Account] -> [AccountName]
forall a b. (a -> b) -> a -> b
$ Int -> [Account] -> [Account]
forall a. Int -> [a] -> [a]
drop Int
1 ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$ Account -> [Account]
flattenAccounts Account
sortedaccounttree

    -- Sort the report rows, representing a flat account list, by row total.
    sortFlatMBRByAmount :: [MultiBalanceReportRow] -> [MultiBalanceReportRow]
    sortFlatMBRByAmount :: [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
sortFlatMBRByAmount = case ReportOpts -> Maybe NormalSign
normalbalance_ ReportOpts
ropts of
        Just NormalSign
NormallyNegative -> (PeriodicReportRow DisplayName MixedAmount -> MixedAmount)
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn PeriodicReportRow DisplayName MixedAmount -> MixedAmount
forall a. PeriodicReportRow a MixedAmount -> MixedAmount
amt
        Maybe NormalSign
_                     -> (PeriodicReportRow DisplayName MixedAmount -> Down MixedAmount)
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (MixedAmount -> Down MixedAmount
forall a. a -> Down a
Down (MixedAmount -> Down MixedAmount)
-> (PeriodicReportRow DisplayName MixedAmount -> MixedAmount)
-> PeriodicReportRow DisplayName MixedAmount
-> Down MixedAmount
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PeriodicReportRow DisplayName MixedAmount -> MixedAmount
forall a. PeriodicReportRow a MixedAmount -> MixedAmount
amt)
      where amt :: PeriodicReportRow a MixedAmount -> MixedAmount
amt = MixedAmount -> MixedAmount
normaliseMixedAmountSquashPricesForDisplay (MixedAmount -> MixedAmount)
-> (PeriodicReportRow a MixedAmount -> MixedAmount)
-> PeriodicReportRow a MixedAmount
-> MixedAmount
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PeriodicReportRow a MixedAmount -> MixedAmount
forall a b. PeriodicReportRow a b -> b
prrTotal

    -- Sort the report rows by account declaration order then account name.
    sortMBRByAccountDeclaration :: [MultiBalanceReportRow] -> [MultiBalanceReportRow]
    sortMBRByAccountDeclaration :: [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
sortMBRByAccountDeclaration [PeriodicReportRow DisplayName MixedAmount]
rows = [AccountName]
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
forall b.
[AccountName]
-> [PeriodicReportRow DisplayName b]
-> [PeriodicReportRow DisplayName b]
sortRowsLike [AccountName]
sortedanames [PeriodicReportRow DisplayName MixedAmount]
rows
      where
        sortedanames :: [AccountName]
sortedanames = Journal -> Bool -> [AccountName] -> [AccountName]
sortAccountNamesByDeclaration Journal
j (ReportOpts -> Bool
tree_ ReportOpts
ropts) ([AccountName] -> [AccountName]) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> a -> b
$ (PeriodicReportRow DisplayName MixedAmount -> AccountName)
-> [PeriodicReportRow DisplayName MixedAmount] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow DisplayName MixedAmount -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName [PeriodicReportRow DisplayName MixedAmount]
rows

-- | Build the report totals row.
--
-- Calculate the column totals. These are always the sum of column amounts.
calculateTotalsRow :: ReportOpts -> [MultiBalanceReportRow] -> PeriodicReportRow () MixedAmount
calculateTotalsRow :: ReportOpts
-> [PeriodicReportRow DisplayName MixedAmount]
-> PeriodicReportRow () MixedAmount
calculateTotalsRow ReportOpts
ropts [PeriodicReportRow DisplayName MixedAmount]
rows =
    ()
-> [MixedAmount]
-> MixedAmount
-> MixedAmount
-> PeriodicReportRow () MixedAmount
forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow () [MixedAmount]
coltotals MixedAmount
grandtotal MixedAmount
grandaverage
  where
    isTopRow :: PeriodicReportRow DisplayName a -> Bool
isTopRow PeriodicReportRow DisplayName a
row = ReportOpts -> Bool
flat_ ReportOpts
ropts Bool -> Bool -> Bool
|| Bool -> Bool
not ((AccountName -> Bool) -> [AccountName] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (AccountName
-> HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
-> Bool
forall k a. (Eq k, Hashable k) => k -> HashMap k a -> Bool
`HM.member` HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
rowMap) [AccountName]
parents)
      where parents :: [AccountName]
parents = [AccountName] -> [AccountName]
forall a. [a] -> [a]
init ([AccountName] -> [AccountName])
-> (AccountName -> [AccountName]) -> AccountName -> [AccountName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AccountName -> [AccountName]
expandAccountName (AccountName -> [AccountName]) -> AccountName -> [AccountName]
forall a b. (a -> b) -> a -> b
$ PeriodicReportRow DisplayName a -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName PeriodicReportRow DisplayName a
row
    rowMap :: HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
rowMap = [(AccountName, PeriodicReportRow DisplayName MixedAmount)]
-> HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
HM.fromList ([(AccountName, PeriodicReportRow DisplayName MixedAmount)]
 -> HashMap AccountName (PeriodicReportRow DisplayName MixedAmount))
-> [(AccountName, PeriodicReportRow DisplayName MixedAmount)]
-> HashMap AccountName (PeriodicReportRow DisplayName MixedAmount)
forall a b. (a -> b) -> a -> b
$ (PeriodicReportRow DisplayName MixedAmount
 -> (AccountName, PeriodicReportRow DisplayName MixedAmount))
-> [PeriodicReportRow DisplayName MixedAmount]
-> [(AccountName, PeriodicReportRow DisplayName MixedAmount)]
forall a b. (a -> b) -> [a] -> [b]
map (\PeriodicReportRow DisplayName MixedAmount
row -> (PeriodicReportRow DisplayName MixedAmount -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName PeriodicReportRow DisplayName MixedAmount
row, PeriodicReportRow DisplayName MixedAmount
row)) [PeriodicReportRow DisplayName MixedAmount]
rows

    colamts :: [[MixedAmount]]
colamts = [[MixedAmount]] -> [[MixedAmount]]
forall a. [[a]] -> [[a]]
transpose ([[MixedAmount]] -> [[MixedAmount]])
-> ([PeriodicReportRow DisplayName MixedAmount] -> [[MixedAmount]])
-> [PeriodicReportRow DisplayName MixedAmount]
-> [[MixedAmount]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (PeriodicReportRow DisplayName MixedAmount -> [MixedAmount])
-> [PeriodicReportRow DisplayName MixedAmount] -> [[MixedAmount]]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow DisplayName MixedAmount -> [MixedAmount]
forall a b. PeriodicReportRow a b -> [b]
prrAmounts ([PeriodicReportRow DisplayName MixedAmount] -> [[MixedAmount]])
-> [PeriodicReportRow DisplayName MixedAmount] -> [[MixedAmount]]
forall a b. (a -> b) -> a -> b
$ (PeriodicReportRow DisplayName MixedAmount -> Bool)
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
forall a. (a -> Bool) -> [a] -> [a]
filter PeriodicReportRow DisplayName MixedAmount -> Bool
forall a. PeriodicReportRow DisplayName a -> Bool
isTopRow [PeriodicReportRow DisplayName MixedAmount]
rows

    [MixedAmount]
coltotals :: [MixedAmount] = [Char] -> [MixedAmount] -> [MixedAmount]
forall a. Show a => [Char] -> a -> a
dbg5 [Char]
"coltotals" ([MixedAmount] -> [MixedAmount]) -> [MixedAmount] -> [MixedAmount]
forall a b. (a -> b) -> a -> b
$ ([MixedAmount] -> MixedAmount) -> [[MixedAmount]] -> [MixedAmount]
forall a b. (a -> b) -> [a] -> [b]
map [MixedAmount] -> MixedAmount
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [[MixedAmount]]
colamts

    -- Calculate the grand total and average. These are always the sum/average
    -- of the column totals.
    -- Total for a cumulative/historical report is always the last column.
    grandtotal :: MixedAmount
grandtotal = case ReportOpts -> BalanceType
balancetype_ ReportOpts
ropts of
        BalanceType
PeriodChange -> [MixedAmount] -> MixedAmount
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [MixedAmount]
coltotals
        BalanceType
_            -> MixedAmount -> [MixedAmount] -> MixedAmount
forall a. a -> [a] -> a
lastDef MixedAmount
0 [MixedAmount]
coltotals
    grandaverage :: MixedAmount
grandaverage = [MixedAmount] -> MixedAmount
averageMixedAmounts [MixedAmount]
coltotals

-- | Map the report rows to percentages if needed
reportPercent :: ReportOpts -> MultiBalanceReport -> MultiBalanceReport
reportPercent :: ReportOpts -> MultiBalanceReport -> MultiBalanceReport
reportPercent ReportOpts
ropts report :: MultiBalanceReport
report@(PeriodicReport [DateSpan]
spans [PeriodicReportRow DisplayName MixedAmount]
rows PeriodicReportRow () MixedAmount
totalrow)
  | ReportOpts -> Bool
percent_ ReportOpts
ropts = [DateSpan]
-> [PeriodicReportRow DisplayName MixedAmount]
-> PeriodicReportRow () MixedAmount
-> MultiBalanceReport
forall a b.
[DateSpan]
-> [PeriodicReportRow a b]
-> PeriodicReportRow () b
-> PeriodicReport a b
PeriodicReport [DateSpan]
spans ((PeriodicReportRow DisplayName MixedAmount
 -> PeriodicReportRow DisplayName MixedAmount)
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow DisplayName MixedAmount
-> PeriodicReportRow DisplayName MixedAmount
forall a.
PeriodicReportRow a MixedAmount -> PeriodicReportRow a MixedAmount
percentRow [PeriodicReportRow DisplayName MixedAmount]
rows) (PeriodicReportRow () MixedAmount
-> PeriodicReportRow () MixedAmount
forall a.
PeriodicReportRow a MixedAmount -> PeriodicReportRow a MixedAmount
percentRow PeriodicReportRow () MixedAmount
totalrow)
  | Bool
otherwise      = MultiBalanceReport
report
  where
    percentRow :: PeriodicReportRow a MixedAmount -> PeriodicReportRow a MixedAmount
percentRow (PeriodicReportRow a
name [MixedAmount]
rowvals MixedAmount
rowtotal MixedAmount
rowavg) =
      a
-> [MixedAmount]
-> MixedAmount
-> MixedAmount
-> PeriodicReportRow a MixedAmount
forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow a
name
        ((MixedAmount -> MixedAmount -> MixedAmount)
-> [MixedAmount] -> [MixedAmount] -> [MixedAmount]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith MixedAmount -> MixedAmount -> MixedAmount
perdivide [MixedAmount]
rowvals ([MixedAmount] -> [MixedAmount]) -> [MixedAmount] -> [MixedAmount]
forall a b. (a -> b) -> a -> b
$ PeriodicReportRow () MixedAmount -> [MixedAmount]
forall a b. PeriodicReportRow a b -> [b]
prrAmounts PeriodicReportRow () MixedAmount
totalrow)
        (MixedAmount -> MixedAmount -> MixedAmount
perdivide MixedAmount
rowtotal (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
forall a b. (a -> b) -> a -> b
$ PeriodicReportRow () MixedAmount -> MixedAmount
forall a b. PeriodicReportRow a b -> b
prrTotal PeriodicReportRow () MixedAmount
totalrow)
        (MixedAmount -> MixedAmount -> MixedAmount
perdivide MixedAmount
rowavg (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
forall a b. (a -> b) -> a -> b
$ PeriodicReportRow () MixedAmount -> MixedAmount
forall a b. PeriodicReportRow a b -> b
prrAverage PeriodicReportRow () MixedAmount
totalrow)


-- | Transpose a Map of HashMaps to a HashMap of Maps.
--
-- Makes sure that all DateSpans are present in all rows.
transposeMap :: Map DateSpan (HashMap AccountName a)
             -> HashMap AccountName (Map DateSpan a)
transposeMap :: Map DateSpan (HashMap AccountName a)
-> HashMap AccountName (Map DateSpan a)
transposeMap Map DateSpan (HashMap AccountName a)
xs = (DateSpan
 -> HashMap AccountName a
 -> HashMap AccountName (Map DateSpan a)
 -> HashMap AccountName (Map DateSpan a))
-> HashMap AccountName (Map DateSpan a)
-> Map DateSpan (HashMap AccountName a)
-> HashMap AccountName (Map DateSpan a)
forall k a b. (k -> a -> b -> b) -> b -> Map k a -> b
M.foldrWithKey DateSpan
-> HashMap AccountName a
-> HashMap AccountName (Map DateSpan a)
-> HashMap AccountName (Map DateSpan a)
forall k k a.
(Hashable k, Ord k, Eq k) =>
k -> HashMap k a -> HashMap k (Map k a) -> HashMap k (Map k a)
addSpan HashMap AccountName (Map DateSpan a)
forall a. Monoid a => a
mempty Map DateSpan (HashMap AccountName a)
xs
  where
    addSpan :: k -> HashMap k a -> HashMap k (Map k a) -> HashMap k (Map k a)
addSpan k
span HashMap k a
acctmap HashMap k (Map k a)
seen = (k -> a -> HashMap k (Map k a) -> HashMap k (Map k a))
-> HashMap k (Map k a) -> HashMap k a -> HashMap k (Map k a)
forall k v a. (k -> v -> a -> a) -> a -> HashMap k v -> a
HM.foldrWithKey (k -> k -> a -> HashMap k (Map k a) -> HashMap k (Map k a)
forall k k a.
(Eq k, Hashable k, Ord k) =>
k -> k -> a -> HashMap k (Map k a) -> HashMap k (Map k a)
addAcctSpan k
span) HashMap k (Map k a)
seen HashMap k a
acctmap

    addAcctSpan :: k -> k -> a -> HashMap k (Map k a) -> HashMap k (Map k a)
addAcctSpan k
span k
acct a
a = (Maybe (Map k a) -> Maybe (Map k a))
-> k -> HashMap k (Map k a) -> HashMap k (Map k a)
forall k v.
(Eq k, Hashable k) =>
(Maybe v -> Maybe v) -> k -> HashMap k v -> HashMap k v
HM.alter Maybe (Map k a) -> Maybe (Map k a)
f k
acct
      where f :: Maybe (Map k a) -> Maybe (Map k a)
f = Map k a -> Maybe (Map k a)
forall a. a -> Maybe a
Just (Map k a -> Maybe (Map k a))
-> (Maybe (Map k a) -> Map k a)
-> Maybe (Map k a)
-> Maybe (Map k a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. k -> a -> Map k a -> Map k a
forall k a. Ord k => k -> a -> Map k a -> Map k a
M.insert k
span a
a (Map k a -> Map k a)
-> (Maybe (Map k a) -> Map k a) -> Maybe (Map k a) -> Map k a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Map k a -> Maybe (Map k a) -> Map k a
forall a. a -> Maybe a -> a
fromMaybe Map k a
forall a. Monoid a => a
mempty

-- | A sorting helper: sort a list of things (eg report rows) keyed by account name
-- to match the provided ordering of those same account names.
sortRowsLike :: [AccountName] -> [PeriodicReportRow DisplayName b] -> [PeriodicReportRow DisplayName b]
sortRowsLike :: [AccountName]
-> [PeriodicReportRow DisplayName b]
-> [PeriodicReportRow DisplayName b]
sortRowsLike [AccountName]
sortedas [PeriodicReportRow DisplayName b]
rows = (AccountName -> Maybe (PeriodicReportRow DisplayName b))
-> [AccountName] -> [PeriodicReportRow DisplayName b]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (AccountName
-> HashMap AccountName (PeriodicReportRow DisplayName b)
-> Maybe (PeriodicReportRow DisplayName b)
forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
`HM.lookup` HashMap AccountName (PeriodicReportRow DisplayName b)
rowMap) [AccountName]
sortedas
  where rowMap :: HashMap AccountName (PeriodicReportRow DisplayName b)
rowMap = [(AccountName, PeriodicReportRow DisplayName b)]
-> HashMap AccountName (PeriodicReportRow DisplayName b)
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
HM.fromList ([(AccountName, PeriodicReportRow DisplayName b)]
 -> HashMap AccountName (PeriodicReportRow DisplayName b))
-> [(AccountName, PeriodicReportRow DisplayName b)]
-> HashMap AccountName (PeriodicReportRow DisplayName b)
forall a b. (a -> b) -> a -> b
$ (PeriodicReportRow DisplayName b
 -> (AccountName, PeriodicReportRow DisplayName b))
-> [PeriodicReportRow DisplayName b]
-> [(AccountName, PeriodicReportRow DisplayName b)]
forall a b. (a -> b) -> [a] -> [b]
map (\PeriodicReportRow DisplayName b
row -> (PeriodicReportRow DisplayName b -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName PeriodicReportRow DisplayName b
row, PeriodicReportRow DisplayName b
row)) [PeriodicReportRow DisplayName b]
rows

-- | Given a list of account names, find all forking parent accounts, i.e.
-- those which fork between different branches
subaccountTallies :: [AccountName] -> HashMap AccountName Int
subaccountTallies :: [AccountName] -> HashMap AccountName Int
subaccountTallies = (AccountName -> HashMap AccountName Int -> HashMap AccountName Int)
-> HashMap AccountName Int
-> [AccountName]
-> HashMap AccountName Int
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr AccountName -> HashMap AccountName Int -> HashMap AccountName Int
forall v.
Num v =>
AccountName -> HashMap AccountName v -> HashMap AccountName v
incrementParent HashMap AccountName Int
forall a. Monoid a => a
mempty ([AccountName] -> HashMap AccountName Int)
-> ([AccountName] -> [AccountName])
-> [AccountName]
-> HashMap AccountName Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [AccountName] -> [AccountName]
expandAccountNames
  where
    incrementParent :: AccountName -> HashMap AccountName v -> HashMap AccountName v
incrementParent AccountName
a = (v -> v -> v)
-> AccountName
-> v
-> HashMap AccountName v
-> HashMap AccountName v
forall k v.
(Eq k, Hashable k) =>
(v -> v -> v) -> k -> v -> HashMap k v -> HashMap k v
HM.insertWith v -> v -> v
forall a. Num a => a -> a -> a
(+) (AccountName -> AccountName
parentAccountName AccountName
a) v
1

-- | A helper: what percentage is the second mixed amount of the first ?
-- Keeps the sign of the first amount.
-- Uses unifyMixedAmount to unify each argument and then divides them.
-- Both amounts should be in the same, single commodity.
-- This can call error if the arguments are not right.
perdivide :: MixedAmount -> MixedAmount -> MixedAmount
perdivide :: MixedAmount -> MixedAmount -> MixedAmount
perdivide MixedAmount
a MixedAmount
b = MixedAmount -> Maybe MixedAmount -> MixedAmount
forall a. a -> Maybe a -> a
fromMaybe ([Char] -> MixedAmount
forall a. [Char] -> a
error' [Char]
errmsg) (Maybe MixedAmount -> MixedAmount)
-> Maybe MixedAmount -> MixedAmount
forall a b. (a -> b) -> a -> b
$ do  -- PARTIAL:
    Amount
a' <- MixedAmount -> Maybe Amount
unifyMixedAmount MixedAmount
a
    Amount
b' <- MixedAmount -> Maybe Amount
unifyMixedAmount MixedAmount
b
    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Amount -> Bool
amountIsZero Amount
a' Bool -> Bool -> Bool
|| Amount -> Bool
amountIsZero Amount
b' Bool -> Bool -> Bool
|| Amount -> AccountName
acommodity Amount
a' AccountName -> AccountName -> Bool
forall a. Eq a => a -> a -> Bool
== Amount -> AccountName
acommodity Amount
b'
    MixedAmount -> Maybe MixedAmount
forall (m :: * -> *) a. Monad m => a -> m a
return (MixedAmount -> Maybe MixedAmount)
-> MixedAmount -> Maybe MixedAmount
forall a b. (a -> b) -> a -> b
$ [Amount] -> MixedAmount
mixed [Quantity -> Amount
per (Quantity -> Amount) -> Quantity -> Amount
forall a b. (a -> b) -> a -> b
$ if Amount -> Quantity
aquantity Amount
b' Quantity -> Quantity -> Bool
forall a. Eq a => a -> a -> Bool
== Quantity
0 then Quantity
0 else Amount -> Quantity
aquantity Amount
a' Quantity -> Quantity -> Quantity
forall a. Fractional a => a -> a -> a
/ Quantity -> Quantity
forall a. Num a => a -> a
abs (Amount -> Quantity
aquantity Amount
b') Quantity -> Quantity -> Quantity
forall a. Num a => a -> a -> a
* Quantity
100]
  where errmsg :: [Char]
errmsg = [Char]
"Cannot calculate percentages if accounts have different commodities (Hint: Try --cost, -V or similar flags.)"

-- tests

tests_MultiBalanceReport :: TestTree
tests_MultiBalanceReport = [Char] -> [TestTree] -> TestTree
tests [Char]
"MultiBalanceReport" [

  let
    amt0 :: Amount
amt0 = Amount :: AccountName
-> Quantity -> Bool -> AmountStyle -> Maybe AmountPrice -> Amount
Amount {acommodity :: AccountName
acommodity=AccountName
"$", aquantity :: Quantity
aquantity=Quantity
0, aprice :: Maybe AmountPrice
aprice=Maybe AmountPrice
forall a. Maybe a
Nothing, astyle :: AmountStyle
astyle=AmountStyle :: Side
-> Bool
-> AmountPrecision
-> Maybe Char
-> Maybe DigitGroupStyle
-> AmountStyle
AmountStyle {ascommodityside :: Side
ascommodityside = Side
L, ascommodityspaced :: Bool
ascommodityspaced = Bool
False, asprecision :: AmountPrecision
asprecision = Word8 -> AmountPrecision
Precision Word8
2, asdecimalpoint :: Maybe Char
asdecimalpoint = Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'.', asdigitgroups :: Maybe DigitGroupStyle
asdigitgroups = Maybe DigitGroupStyle
forall a. Maybe a
Nothing}, aismultiplier :: Bool
aismultiplier=Bool
False}
    (ReportSpec
rspec,Journal
journal) gives :: (ReportSpec, Journal)
-> ([PeriodicReportRow DisplayName MixedAmount], MixedAmount)
-> IO ()
`gives` ([PeriodicReportRow DisplayName MixedAmount], MixedAmount)
r = do
      let rspec' :: ReportSpec
rspec' = ReportSpec
rspec{rsQuery :: Query
rsQuery=[Query] -> Query
And [ReportOpts -> Query
queryFromFlags (ReportOpts -> Query) -> ReportOpts -> Query
forall a b. (a -> b) -> a -> b
$ ReportSpec -> ReportOpts
rsOpts ReportSpec
rspec, ReportSpec -> Query
rsQuery ReportSpec
rspec]}
          ([PeriodicReportRow DisplayName MixedAmount]
eitems, MixedAmount
etotal) = ([PeriodicReportRow DisplayName MixedAmount], MixedAmount)
r
          (PeriodicReport [DateSpan]
_ [PeriodicReportRow DisplayName MixedAmount]
aitems PeriodicReportRow () MixedAmount
atotal) = ReportSpec -> Journal -> MultiBalanceReport
multiBalanceReport ReportSpec
rspec' Journal
journal
          showw :: PeriodicReportRow DisplayName MixedAmount
-> (AccountName, AccountName, Int, [[Char]], [Char], [Char])
showw (PeriodicReportRow DisplayName
a [MixedAmount]
lAmt MixedAmount
amt MixedAmount
amt')
              = (DisplayName -> AccountName
displayFull DisplayName
a, DisplayName -> AccountName
displayName DisplayName
a, DisplayName -> Int
displayDepth DisplayName
a, (MixedAmount -> [Char]) -> [MixedAmount] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map MixedAmount -> [Char]
showMixedAmountDebug [MixedAmount]
lAmt, MixedAmount -> [Char]
showMixedAmountDebug MixedAmount
amt, MixedAmount -> [Char]
showMixedAmountDebug MixedAmount
amt')
      ((PeriodicReportRow DisplayName MixedAmount
 -> (AccountName, AccountName, Int, [[Char]], [Char], [Char]))
-> [PeriodicReportRow DisplayName MixedAmount]
-> [(AccountName, AccountName, Int, [[Char]], [Char], [Char])]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow DisplayName MixedAmount
-> (AccountName, AccountName, Int, [[Char]], [Char], [Char])
showw [PeriodicReportRow DisplayName MixedAmount]
aitems) [(AccountName, AccountName, Int, [[Char]], [Char], [Char])]
-> [(AccountName, AccountName, Int, [[Char]], [Char], [Char])]
-> IO ()
forall a. (Eq a, Show a, HasCallStack) => a -> a -> IO ()
@?= ((PeriodicReportRow DisplayName MixedAmount
 -> (AccountName, AccountName, Int, [[Char]], [Char], [Char]))
-> [PeriodicReportRow DisplayName MixedAmount]
-> [(AccountName, AccountName, Int, [[Char]], [Char], [Char])]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow DisplayName MixedAmount
-> (AccountName, AccountName, Int, [[Char]], [Char], [Char])
showw [PeriodicReportRow DisplayName MixedAmount]
eitems)
      MixedAmount -> [Char]
showMixedAmountDebug (PeriodicReportRow () MixedAmount -> MixedAmount
forall a b. PeriodicReportRow a b -> b
prrTotal PeriodicReportRow () MixedAmount
atotal) [Char] -> [Char] -> IO ()
forall a. (Eq a, Show a, HasCallStack) => a -> a -> IO ()
@?= MixedAmount -> [Char]
showMixedAmountDebug MixedAmount
etotal -- we only check the sum of the totals
  in
   [Char] -> [TestTree] -> TestTree
tests [Char]
"multiBalanceReport" [
      [Char] -> IO () -> TestTree
test [Char]
"null journal"  (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
      (ReportSpec
defreportspec, Journal
nulljournal) (ReportSpec, Journal)
-> ([PeriodicReportRow DisplayName MixedAmount], MixedAmount)
-> IO ()
`gives` ([], [Amount] -> MixedAmount
Mixed [Amount
nullamt])

     ,[Char] -> IO () -> TestTree
test [Char]
"with -H on a populated period"  (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
      (ReportSpec
defreportspec{rsOpts :: ReportOpts
rsOpts=ReportOpts
defreportopts{period_ :: Period
period_= Day -> Day -> Period
PeriodBetween (Integer -> Int -> Int -> Day
fromGregorian Integer
2008 Int
1 Int
1) (Integer -> Int -> Int -> Day
fromGregorian Integer
2008 Int
1 Int
2), balancetype_ :: BalanceType
balancetype_=BalanceType
HistoricalBalance}}, Journal
samplejournal) (ReportSpec, Journal)
-> ([PeriodicReportRow DisplayName MixedAmount], MixedAmount)
-> IO ()
`gives`
       (
        [ DisplayName
-> [MixedAmount]
-> MixedAmount
-> MixedAmount
-> PeriodicReportRow DisplayName MixedAmount
forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow (AccountName -> DisplayName
flatDisplayName AccountName
"assets:bank:checking") [[Char] -> MixedAmount
mamountp' [Char]
"$1.00"]  ([Char] -> MixedAmount
mamountp' [Char]
"$1.00")  ([Amount] -> MixedAmount
Mixed [Amount
amt0 {aquantity :: Quantity
aquantity=Quantity
1}])
        , DisplayName
-> [MixedAmount]
-> MixedAmount
-> MixedAmount
-> PeriodicReportRow DisplayName MixedAmount
forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow (AccountName -> DisplayName
flatDisplayName AccountName
"income:salary")        [[Char] -> MixedAmount
mamountp' [Char]
"$-1.00"] ([Char] -> MixedAmount
mamountp' [Char]
"$-1.00") ([Amount] -> MixedAmount
Mixed [Amount
amt0 {aquantity :: Quantity
aquantity=(-Quantity
1)}])
        ],
        [Char] -> MixedAmount
mamountp' [Char]
"$0.00")

     -- ,test "a valid history on an empty period"  $
     --  (defreportopts{period_= PeriodBetween (fromGregorian 2008 1 2) (fromGregorian 2008 1 3), balancetype_=HistoricalBalance}, samplejournal) `gives`
     --   (
     --    [
     --     ("assets:bank:checking","checking",3, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amt0 {aquantity=1}])
     --    ,("income:salary","salary",2, [mamountp' "$-1.00"], mamountp' "$-1.00",Mixed [amt0 {aquantity=(-1)}])
     --    ],
     --    Mixed [usd0])

     -- ,test "a valid history on an empty period (more complex)"  $
     --  (defreportopts{period_= PeriodBetween (fromGregorian 2009 1 1) (fromGregorian 2009 1 2), balancetype_=HistoricalBalance}, samplejournal) `gives`
     --   (
     --    [
     --    ("assets:bank:checking","checking",3, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amt0 {aquantity=1}])
     --    ,("assets:bank:saving","saving",3, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amt0 {aquantity=1}])
     --    ,("assets:cash","cash",2, [mamountp' "$-2.00"], mamountp' "$-2.00",Mixed [amt0 {aquantity=(-2)}])
     --    ,("expenses:food","food",2, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amt0 {aquantity=(1)}])
     --    ,("expenses:supplies","supplies",2, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amt0 {aquantity=(1)}])
     --    ,("income:gifts","gifts",2, [mamountp' "$-1.00"], mamountp' "$-1.00",Mixed [amt0 {aquantity=(-1)}])
     --    ,("income:salary","salary",2, [mamountp' "$-1.00"], mamountp' "$-1.00",Mixed [amt0 {aquantity=(-1)}])
     --    ],
     --    Mixed [usd0])
    ]
 ]