    {-# LANGUAGE CPP                 #-}
    {-# LANGUAGE FlexibleInstances   #-}
    {-# LANGUAGE OverloadedStrings   #-}
    {-# LANGUAGE NamedFieldPuns      #-}
    {-# LANGUAGE RankNTypes          #-}
    {-# LANGUAGE RecordWildCards     #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE TupleSections       #-}
    {-|

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

    -}

    module Hledger.Reports.MultiBalanceReport (
      MultiBalanceReport,
      MultiBalanceReportRow,

      multiBalanceReport,
      multiBalanceReportWith,

      compoundBalanceReport,
      compoundBalanceReportWith,

      -- * Helper functions
      makeReportQuery,
      getPostings,
      generateMultiBalanceAccount,
      generatePeriodicReport,
      makePeriodicReportRow,

      -- -- * Tests
      tests_MultiBalanceReport
    )
    where

    #if !MIN_VERSION_base(4,18,0)
    import Control.Applicative (liftA2)
    #endif
    import Control.Monad (guard)
    import Data.Foldable (toList)
    import Data.HashSet qualified as HS
    import Data.List (sortOn)
    import Data.List.NonEmpty (NonEmpty((:|)))
    import Data.Map qualified as M
    import Data.Maybe (fromMaybe, isJust)
    import Data.Ord (Down(..))
    import Data.Semigroup (sconcat)
    import Data.These (these)
    import Data.Time.Calendar (Day(..), fromGregorian)
    import Data.Traversable (mapAccumL)

    import Hledger.Data
    import Hledger.Query
    import Hledger.Utils
    import Hledger.Reports.ReportOptions
    import Hledger.Reports.ReportTypes

    -- | 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

    -- | 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_prices_ (ReportOpts -> Bool) -> ReportOpts -> Bool
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> ReportOpts
    _rsReportOpts ReportSpec
    rspec

    -- | A helper for multiBalanceReport. This one takes some extra arguments,
    -- a 'PriceOracle' to be used for looking up market prices, and a set of
    -- 'AccountName's which should not be elided. 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.
        (DateSpan
    reportspan, Maybe DayPartition
    colspans) = String
    -> (DateSpan, Maybe DayPartition) -> (DateSpan, Maybe DayPartition)
    forall a. Show a => String -> a -> a
    dbg5 String
    "multiBalanceReportWith reportSpan" ((DateSpan, Maybe DayPartition) -> (DateSpan, Maybe DayPartition))
    -> (DateSpan, Maybe DayPartition) -> (DateSpan, Maybe DayPartition)
    forall a b. (a -> b) -> a -> b
    $ Journal -> ReportSpec -> (DateSpan, Maybe DayPartition)
    reportSpan Journal
    j ReportSpec
    rspec'
        rspec :: ReportSpec
    rspec = String -> ReportSpec -> ReportSpec
    forall a. Show a => String -> a -> a
    dbg3 String
    "multiBalanceReportWith rspec" (ReportSpec -> ReportSpec) -> ReportSpec -> ReportSpec
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> DateSpan -> ReportSpec
    makeReportQuery ReportSpec
    rspec' DateSpan
    reportspan
        -- force evaluation order to show price lookup after date spans in debug output (XXX not working)
        -- priceoracle = reportspan `seq` priceoracle0

        -- Get postings
        ps :: [Posting]
    ps = String -> [Posting] -> [Posting]
    forall a. Show a => String -> a -> a
    dbg5 String
    "multiBalanceReportWith ps" ([Posting] -> [Posting]) -> [Posting] -> [Posting]
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> Journal -> PriceOracle -> DateSpan -> [Posting]
    getPostings ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle DateSpan
    reportspan

        -- Process changes into normal, cumulative, or historical amounts, plus value them and mark which are uninteresting
        acct :: Account BalanceData
    acct = String -> Account BalanceData -> Account BalanceData
    forall a. Show a => String -> a -> a
    dbg5 String
    "multiBalanceReportWith acct" (Account BalanceData -> Account BalanceData)
    -> Account BalanceData -> Account BalanceData
    forall a b. (a -> b) -> a -> b
    $ ReportSpec
    -> Journal
    -> PriceOracle
    -> Maybe DayPartition
    -> [Posting]
    -> Account BalanceData
    generateMultiBalanceAccount ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle Maybe DayPartition
    colspans [Posting]
    ps

        -- Generate and postprocess the report, negating balances and taking percentages if needed
        report :: MultiBalanceReport
    report = String -> MultiBalanceReport -> MultiBalanceReport
    forall a. Show a => String -> a -> a
    dbg4 String
    "multiBalanceReportWith report" (MultiBalanceReport -> MultiBalanceReport)
    -> MultiBalanceReport -> MultiBalanceReport
    forall a b. (a -> b) -> a -> b
    $ ReportOpts
    -> Maybe DayPartition -> Account BalanceData -> MultiBalanceReport
    generateMultiBalanceReport (ReportSpec -> ReportOpts
    _rsReportOpts ReportSpec
    rspec) Maybe DayPartition
    colspans Account BalanceData
    acct

    -- | 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 :: forall a.
    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_prices_ (ReportOpts -> Bool) -> ReportOpts -> Bool
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> ReportOpts
    _rsReportOpts ReportSpec
    rspec

    -- | A helper for compoundBalanceReport, similar to multiBalanceReportWith.
    compoundBalanceReportWith :: ReportSpec -> Journal -> PriceOracle
                              -> [CBCSubreportSpec a]
                              -> CompoundPeriodicReport a MixedAmount
    compoundBalanceReportWith :: forall a.
    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.
        (DateSpan
    reportspan, Maybe DayPartition
    colspans) = String
    -> (DateSpan, Maybe DayPartition) -> (DateSpan, Maybe DayPartition)
    forall a. Show a => String -> a -> a
    dbg5 String
    "compoundBalanceReportWith reportSpan" ((DateSpan, Maybe DayPartition) -> (DateSpan, Maybe DayPartition))
    -> (DateSpan, Maybe DayPartition) -> (DateSpan, Maybe DayPartition)
    forall a b. (a -> b) -> a -> b
    $ Journal -> ReportSpec -> (DateSpan, Maybe DayPartition)
    reportSpan Journal
    j ReportSpec
    rspec'
        rspec :: ReportSpec
    rspec = String -> ReportSpec -> ReportSpec
    forall a. Show a => String -> a -> a
    dbg3 String
    "compoundBalanceReportWith rspec" (ReportSpec -> ReportSpec) -> ReportSpec -> ReportSpec
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> DateSpan -> ReportSpec
    makeReportQuery ReportSpec
    rspec' DateSpan
    reportspan

        -- Get postings
        ps :: [Posting]
    ps = String -> [Posting] -> [Posting]
    forall a. Show a => String -> a -> a
    dbg5 String
    "compoundBalanceReportWith ps" ([Posting] -> [Posting]) -> [Posting] -> [Posting]
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> Journal -> PriceOracle -> DateSpan -> [Posting]
    getPostings ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle DateSpan
    reportspan

        subreports :: [(CommoditySymbol, PeriodicReport a MixedAmount, Bool)]
    subreports = (CBCSubreportSpec a
     -> (CommoditySymbol, PeriodicReport a MixedAmount, Bool))
    -> [CBCSubreportSpec a]
    -> [(CommoditySymbol, PeriodicReport a MixedAmount, Bool)]
    forall a b. (a -> b) -> [a] -> [b]
    map CBCSubreportSpec a
    -> (CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    forall {a}.
    CBCSubreportSpec a
    -> (CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    generateSubreport [CBCSubreportSpec a]
    subreportspecs
          where
            generateSubreport :: CBCSubreportSpec a
    -> (CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    generateSubreport CBCSubreportSpec{Bool
    CommoditySymbol
    Query
    ReportOpts -> ReportOpts
    MultiBalanceReport -> PeriodicReport a MixedAmount
    cbcsubreporttitle :: CommoditySymbol
    cbcsubreportquery :: Query
    cbcsubreportoptions :: ReportOpts -> ReportOpts
    cbcsubreporttransform :: MultiBalanceReport -> PeriodicReport a MixedAmount
    cbcsubreportincreasestotal :: Bool
    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 -> Query
    cbcsubreporttitle :: forall a. CBCSubreportSpec a -> CommoditySymbol
    ..} =
                ( CommoditySymbol
    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
    $ ReportOpts
    -> Maybe DayPartition -> Account BalanceData -> MultiBalanceReport
    generateMultiBalanceReport ReportOpts
    ropts Maybe DayPartition
    colspans Account BalanceData
    acct
                , Bool
    cbcsubreportincreasestotal
                )
              where
                ropts :: ReportOpts
    ropts = ReportOpts -> ReportOpts
    cbcsubreportoptions (ReportOpts -> ReportOpts) -> ReportOpts -> ReportOpts
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> ReportOpts
    _rsReportOpts ReportSpec
    rspec
                -- Add a restriction to this subreport to the report query.
                -- XXX in non-thorough way, consider updateReportSpec ?
                rspecsub :: ReportSpec
    rspecsub = ReportSpec
    rspec{_rsReportOpts=ropts, _rsQuery=And [cbcsubreportquery, _rsQuery rspec]}
                -- Match and postings for the subreport
                subreportps :: [Posting]
    subreportps = (Posting -> Bool) -> [Posting] -> [Posting]
    forall a. (a -> Bool) -> [a] -> [a]
    filter ((CommoditySymbol -> Maybe AccountType) -> Query -> Posting -> Bool
    matchesPostingExtra (Journal -> CommoditySymbol -> Maybe AccountType
    journalAccountType Journal
    j) Query
    cbcsubreportquery) [Posting]
    ps
                -- Account representing this subreport
                acct :: Account BalanceData
    acct = ReportSpec
    -> Journal
    -> PriceOracle
    -> Maybe DayPartition
    -> [Posting]
    -> Account BalanceData
    generateMultiBalanceAccount ReportSpec
    rspecsub Journal
    j PriceOracle
    priceoracle Maybe DayPartition
    colspans [Posting]
    subreportps

        -- 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 [(CommoditySymbol, 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
            ((CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    r:[(CommoditySymbol, 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
    $ ((CommoditySymbol, PeriodicReport a MixedAmount, Bool)
     -> PeriodicReportRow () MixedAmount)
    -> NonEmpty (CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    -> NonEmpty (PeriodicReportRow () MixedAmount)
    forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    -> PeriodicReportRow () MixedAmount
    forall {a} {a}.
    (a, PeriodicReport a MixedAmount, Bool)
    -> PeriodicReportRow () MixedAmount
    subreportTotal ((CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    r(CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    -> [(CommoditySymbol, PeriodicReport a MixedAmount, Bool)]
    -> NonEmpty (CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    forall a. a -> [a] -> NonEmpty a
    :|[(CommoditySymbol, PeriodicReport a MixedAmount, Bool)]
    rs)
          where
            subreportTotal :: (a, PeriodicReport a MixedAmount, Bool)
    -> PeriodicReportRow () MixedAmount
    subreportTotal (a
    _, PeriodicReport a MixedAmount
    sr, Bool
    increasestotal) =
                (if Bool
    increasestotal then PeriodicReportRow () MixedAmount
    -> PeriodicReportRow () MixedAmount
    forall a. a -> a
    id else (MixedAmount -> MixedAmount)
    -> PeriodicReportRow () MixedAmount
    -> PeriodicReportRow () MixedAmount
    forall a b.
    (a -> b) -> PeriodicReportRow () a -> PeriodicReportRow () b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap MixedAmount -> MixedAmount
    maNegate) (PeriodicReportRow () MixedAmount
     -> PeriodicReportRow () MixedAmount)
    -> PeriodicReportRow () MixedAmount
    -> PeriodicReportRow () MixedAmount
    forall a b. (a -> b) -> a -> b
    $ PeriodicReport a MixedAmount -> PeriodicReportRow () MixedAmount
    forall a b. PeriodicReport a b -> PeriodicReportRow () b
    prTotals PeriodicReport a MixedAmount
    sr

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

    -- | 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}
      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 = String -> Query -> Query
    forall a. Show a => String -> a -> a
    dbg3 String
    "makeReportQuery reportspandatesq" (Query -> Query) -> Query -> Query
    forall a b. (a -> b) -> a -> b
    $ DateSpan -> Query
    dateqcons DateSpan
    reportspan
        dateless :: Query -> Query
    dateless         = String -> Query -> Query
    forall a. Show a => String -> a -> a
    dbg3 String
    "makeReportQuery 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
    _rsReportOpts ReportSpec
    rspec) then DateSpan -> Query
    Date2 else DateSpan -> Query
    Date

    -- | Gather postings matching the query within the report period.
    getPostings :: ReportSpec -> Journal -> PriceOracle -> DateSpan -> [Posting]
    getPostings :: ReportSpec -> Journal -> PriceOracle -> DateSpan -> [Posting]
    getPostings rspec :: ReportSpec
    rspec@ReportSpec{_rsQuery :: ReportSpec -> Query
    _rsQuery=Query
    query, _rsReportOpts :: ReportSpec -> ReportOpts
    _rsReportOpts=ReportOpts
    ropts} Journal
    j PriceOracle
    priceoracle DateSpan
    reportspan =
        [Posting] -> [Posting]
    setPostingsCount
        ([Posting] -> [Posting])
    -> (Journal -> [Posting]) -> Journal -> [Posting]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Posting -> Bool) -> [Posting] -> [Posting]
    forall a. (a -> Bool) -> [a] -> [a]
    filter Posting -> Bool
    hasAmount  -- omit postings with no definite amount
        ([Posting] -> [Posting])
    -> (Journal -> [Posting]) -> Journal -> [Posting]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Journal -> [Posting]
    journalPostings
        (Journal -> [Posting]) -> Journal -> [Posting]
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> Journal -> PriceOracle -> Journal
    journalValueAndFilterPostingsWith ReportSpec
    rspec' Journal
    j PriceOracle
    priceoracle
      where
        -- If doing --count, set all posting amounts to "1".
        setPostingsCount :: [Posting] -> [Posting]
    setPostingsCount = case ReportOpts -> BalanceCalculation
    balancecalc_ ReportOpts
    ropts of
            BalanceCalculation
    CalcPostingsCount -> (Posting -> Posting) -> [Posting] -> [Posting]
    forall a b. (a -> b) -> [a] -> [b]
    map ((MixedAmount -> MixedAmount) -> Posting -> Posting
    postingTransformAmount (MixedAmount -> MixedAmount -> MixedAmount
    forall a b. a -> b -> a
    const (MixedAmount -> MixedAmount -> MixedAmount)
    -> MixedAmount -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ [Amount] -> MixedAmount
    forall (t :: * -> *). Foldable t => t Amount -> MixedAmount
    mixed [Quantity -> Amount
    num Quantity
    1]))
            BalanceCalculation
    _                 -> [Posting] -> [Posting]
    forall a. a -> a
    id

        rspec' :: ReportSpec
    rspec' = ReportSpec
    rspec{_rsQuery=fullreportq,_rsReportOpts=ropts'}
        -- If we're re-valuing every period, we need to have the unvalued start
        -- balance, so we can do it ourselves later.
        ropts' :: ReportOpts
    ropts' = if Maybe (Maybe CommoditySymbol) -> Bool
    forall a. Maybe a -> Bool
    isJust (ReportOpts -> Maybe (Maybe CommoditySymbol)
    valuationAfterSum ReportOpts
    ropts)
            then ReportOpts
    ropts{period_=dateSpanAsPeriod fullreportspan, value_=Nothing, conversionop_=Just NoConversionOp}  -- If we're valuing after the sum, don't do it now
            else ReportOpts
    ropts{period_=dateSpanAsPeriod fullreportspan}

        -- 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.
        fullreportq :: Query
    fullreportq = String -> Query -> Query
    forall a. Show a => String -> a -> a
    dbg3 String
    "getPostings fullreportq" (Query -> Query) -> Query -> Query
    forall a b. (a -> b) -> a -> b
    $ [Query] -> Query
    And [Query
    datelessq, Query
    fullreportspanq]
        datelessq :: Query
    datelessq   = String -> Query -> Query
    forall a. Show a => String -> a -> a
    dbg3 String
    "getPostings 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
    depthlessq

        -- 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).
        depthlessq :: Query
    depthlessq = String -> Query -> Query
    forall a. Show a => String -> a -> a
    dbg3 String
    "getPostings depthlessq" (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
    queryIsDepth) Query
    query

        fullreportspan :: DateSpan
    fullreportspan  = if ReportOpts -> Bool
    requiresHistorical ReportOpts
    ropts then Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan Maybe EFDay
    forall a. Maybe a
    Nothing (Day -> EFDay
    Exact (Day -> EFDay) -> Maybe Day -> Maybe EFDay
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> DateSpan -> Maybe Day
    spanEnd DateSpan
    reportspan) else DateSpan
    reportspan
        fullreportspanq :: Query
    fullreportspanq = (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
    fullreportspan of
            DateSpan Maybe EFDay
    Nothing Maybe EFDay
    Nothing -> DateSpan
    emptydatespan
            DateSpan
    a -> DateSpan
    a

    -- | Generate the 'Account' for the requested multi-balance report from a list
    -- of 'Posting's.
    generateMultiBalanceAccount :: ReportSpec -> Journal -> PriceOracle -> Maybe DayPartition -> [Posting] -> Account BalanceData
    generateMultiBalanceAccount :: ReportSpec
    -> Journal
    -> PriceOracle
    -> Maybe DayPartition
    -> [Posting]
    -> Account BalanceData
    generateMultiBalanceAccount rspec :: ReportSpec
    rspec@ReportSpec{_rsReportOpts :: ReportSpec -> ReportOpts
    _rsReportOpts=ReportOpts
    ropts} Journal
    j PriceOracle
    priceoracle Maybe DayPartition
    colspans =
        -- Set account declaration info (for sorting purposes)
        (Account BalanceData -> Account BalanceData)
    -> Account BalanceData -> Account BalanceData
    forall a. (Account a -> Account a) -> Account a -> Account a
    mapAccounts (Journal -> Account BalanceData -> Account BalanceData
    forall a. Journal -> Account a -> Account a
    accountSetDeclarationInfo Journal
    j)
        -- Add declared accounts if called with --declared and --empty
        (Account BalanceData -> Account BalanceData)
    -> ([Posting] -> Account BalanceData)
    -> [Posting]
    -> Account BalanceData
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (if (ReportOpts -> Bool
    declared_ ReportOpts
    ropts Bool -> Bool -> Bool
    && ReportOpts -> Bool
    empty_ ReportOpts
    ropts) then ReportSpec -> Journal -> Account BalanceData -> Account BalanceData
    forall a.
    Monoid a =>
    ReportSpec -> Journal -> Account a -> Account a
    addDeclaredAccounts ReportSpec
    rspec Journal
    j else Account BalanceData -> Account BalanceData
    forall a. a -> a
    id)
        -- Negate amounts if applicable
        (Account BalanceData -> Account BalanceData)
    -> ([Posting] -> Account BalanceData)
    -> [Posting]
    -> Account BalanceData
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (if ReportOpts -> Bool
    invert_ ReportOpts
    ropts then (BalanceData -> BalanceData)
    -> Account BalanceData -> Account BalanceData
    forall a b. (a -> b) -> Account a -> Account b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap ((MixedAmount -> MixedAmount) -> BalanceData -> BalanceData
    mapBalanceData MixedAmount -> MixedAmount
    maNegate) else Account BalanceData -> Account BalanceData
    forall a. a -> a
    id)
        -- Mark which accounts are boring and which are interesting
        (Account BalanceData -> Account BalanceData)
    -> ([Posting] -> Account BalanceData)
    -> [Posting]
    -> Account BalanceData
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ReportSpec -> Account BalanceData -> Account BalanceData
    markAccountBoring ReportSpec
    rspec
        -- Process changes into normal, cumulative, or historical amounts, plus value them
        (Account BalanceData -> Account BalanceData)
    -> ([Posting] -> Account BalanceData)
    -> [Posting]
    -> Account BalanceData
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ReportSpec
    -> Journal
    -> PriceOracle
    -> Maybe DayPartition
    -> [Posting]
    -> Account BalanceData
    calculateReportAccount ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle Maybe DayPartition
    colspans
        -- Clip account names
        ([Posting] -> Account BalanceData)
    -> ([Posting] -> [Posting]) -> [Posting] -> Account BalanceData
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Posting -> Posting) -> [Posting] -> [Posting]
    forall a b. (a -> b) -> [a] -> [b]
    map Posting -> Posting
    clipPosting
      where
        -- Clip postings to the requested depth according to the query
        clipPosting :: Posting -> Posting
    clipPosting Posting
    p = Posting
    p{paccount = clipOrEllipsifyAccountName depthSpec $ paccount p}
        depthSpec :: DepthSpec
    depthSpec = String -> DepthSpec -> DepthSpec
    forall a. Show a => String -> a -> a
    dbg3 String
    "generateMultiBalanceAccount depthSpec"
                  (DepthSpec -> DepthSpec)
    -> (Query -> DepthSpec) -> Query -> DepthSpec
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Query -> DepthSpec
    queryDepth (Query -> DepthSpec) -> (Query -> Query) -> Query -> DepthSpec
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Query -> Bool) -> Query -> Query
    filterQuery Query -> Bool
    queryIsDepth (Query -> DepthSpec) -> Query -> DepthSpec
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> Query
    _rsQuery ReportSpec
    rspec

    -- | Add declared accounts to the account tree.
    addDeclaredAccounts :: Monoid a => ReportSpec -> Journal -> Account a -> Account a
    addDeclaredAccounts :: forall a.
    Monoid a =>
    ReportSpec -> Journal -> Account a -> Account a
    addDeclaredAccounts ReportSpec
    rspec Journal
    j Account a
    acct =
        (a -> a) -> (a -> a) -> (a -> a -> a) -> These a a -> a
    forall a c b.
    (a -> c) -> (b -> c) -> (a -> b -> c) -> These a b -> c
    these a -> a
    forall a. a -> a
    id a -> a
    forall a. a -> a
    id a -> a -> a
    forall a b. a -> b -> a
    const (These a a -> a) -> Account (These a a) -> Account a
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Account a -> Account a -> Account (These a a)
    forall a b. Account a -> Account b -> Account (These a b)
    mergeAccounts Account a
    acct Account a
    declaredTree
      where
        declaredTree :: Account a
    declaredTree =
            (Account a -> Account a) -> Account a -> Account a
    forall a. (Account a -> Account a) -> Account a -> Account a
    mapAccounts (\Account a
    a -> Account a
    a{aboring = not $ aname a `HS.member` HS.fromList declaredAccounts}) (Account a -> Account a) -> Account a -> Account a
    forall a b. (a -> b) -> a -> b
    $
              CommoditySymbol -> PeriodData a -> [CommoditySymbol] -> Account a
    forall a.
    CommoditySymbol -> PeriodData a -> [CommoditySymbol] -> Account a
    accountTreeFromBalanceAndNames CommoditySymbol
    "root" (a
    forall a. Monoid a => a
    mempty a -> PeriodData a -> PeriodData a
    forall a b. a -> PeriodData b -> PeriodData a
    forall (f :: * -> *) a b. Functor f => a -> f b -> f a
    <$ Account a -> PeriodData a
    forall a. Account a -> PeriodData a
    adata Account a
    acct) [CommoditySymbol]
    declaredAccounts

        -- With --declared, add the query-matching declared accounts (as dummy postings
        -- so they are processed like the rest).
        declaredAccounts :: [CommoditySymbol]
    declaredAccounts =
          (CommoditySymbol -> CommoditySymbol)
    -> [CommoditySymbol] -> [CommoditySymbol]
    forall a b. (a -> b) -> [a] -> [b]
    map (DepthSpec -> CommoditySymbol -> CommoditySymbol
    clipOrEllipsifyAccountName DepthSpec
    depthSpec) ([CommoditySymbol] -> [CommoditySymbol])
    -> ([CommoditySymbol] -> [CommoditySymbol])
    -> [CommoditySymbol]
    -> [CommoditySymbol]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .
          (CommoditySymbol -> Bool) -> [CommoditySymbol] -> [CommoditySymbol]
    forall a. (a -> Bool) -> [a] -> [a]
    filter ((CommoditySymbol -> Maybe AccountType)
    -> (CommoditySymbol -> [Tag]) -> Query -> CommoditySymbol -> Bool
    matchesAccountExtra (Journal -> CommoditySymbol -> Maybe AccountType
    journalAccountType Journal
    j) (Journal -> CommoditySymbol -> [Tag]
    journalAccountTags Journal
    j) Query
    accttypetagsq) ([CommoditySymbol] -> [CommoditySymbol])
    -> [CommoditySymbol] -> [CommoditySymbol]
    forall a b. (a -> b) -> a -> b
    $
          Journal -> [CommoditySymbol]
    journalAccountNamesDeclared Journal
    j

        accttypetagsq :: Query
    accttypetagsq  = String -> Query -> Query
    forall a. Show a => String -> a -> a
    dbg3 String
    "addDeclaredAccounts accttypetagsq" (Query -> Query) -> (Query -> Query) -> Query -> Query
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .
          (Query -> Bool) -> Query -> Query
    filterQueryOrNotQuery (\Query
    q -> Query -> Bool
    queryIsAcct Query
    q Bool -> Bool -> Bool
    || Query -> Bool
    queryIsType Query
    q Bool -> Bool -> Bool
    || Query -> Bool
    queryIsTag Query
    q) (Query -> Query) -> Query -> Query
    forall a b. (a -> b) -> a -> b
    $
          ReportSpec -> Query
    _rsQuery ReportSpec
    rspec

        depthSpec :: DepthSpec
    depthSpec = Query -> DepthSpec
    queryDepth (Query -> DepthSpec) -> (Query -> Query) -> Query -> DepthSpec
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Query -> Bool) -> Query -> Query
    filterQuery Query -> Bool
    queryIsDepth (Query -> DepthSpec) -> Query -> DepthSpec
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> Query
    _rsQuery ReportSpec
    rspec

    -- | Gather the account balance changes into a regular matrix, then
    -- accumulate and value amounts, as specified by the report options.
    -- Makes sure all report columns have an entry.
    calculateReportAccount :: ReportSpec -> Journal -> PriceOracle -> Maybe DayPartition -> [Posting] -> Account BalanceData
    calculateReportAccount :: ReportSpec
    -> Journal
    -> PriceOracle
    -> Maybe DayPartition
    -> [Posting]
    -> Account BalanceData
    calculateReportAccount ReportSpec
    _ Journal
    _ PriceOracle
    _ Maybe DayPartition
    Nothing [Posting]
    _ =
        CommoditySymbol -> PeriodData BalanceData -> Account BalanceData
    forall a. CommoditySymbol -> PeriodData a -> Account a
    accountFromBalances CommoditySymbol
    "root" (PeriodData BalanceData -> Account BalanceData)
    -> PeriodData BalanceData -> Account BalanceData
    forall a b. (a -> b) -> a -> b
    $ BalanceData -> [(Day, BalanceData)] -> PeriodData BalanceData
    forall a. a -> [(Day, a)] -> PeriodData a
    periodDataFromList BalanceData
    forall a. Monoid a => a
    mempty [(Day
    nulldate, BalanceData
    forall a. Monoid a => a
    mempty)]
    calculateReportAccount rspec :: ReportSpec
    rspec@ReportSpec{_rsReportOpts :: ReportSpec -> ReportOpts
    _rsReportOpts=ReportOpts
    ropts} Journal
    j PriceOracle
    priceoracle (Just DayPartition
    colspans) [Posting]
    ps =
        (PeriodData BalanceData -> PeriodData BalanceData)
    -> Account BalanceData -> Account BalanceData
    forall a. (PeriodData a -> PeriodData a) -> Account a -> Account a
    mapPeriodData PeriodData BalanceData -> PeriodData BalanceData
    rowbals Account BalanceData
    changesAcct
      where
        -- The valued row amounts to be displayed: per-period changes,
        -- zero-based cumulative totals, or
        -- starting-balance-based historical balances.
        rowbals :: PeriodData BalanceData -> PeriodData BalanceData
        rowbals :: PeriodData BalanceData -> PeriodData BalanceData
    rowbals PeriodData BalanceData
    unvaluedChanges = case ReportOpts -> BalanceAccumulation
    balanceaccum_ ReportOpts
    ropts of
            BalanceAccumulation
    PerPeriod  -> PeriodData BalanceData
    changes
            BalanceAccumulation
    Cumulative -> PeriodData BalanceData
    cumulative
            BalanceAccumulation
    Historical -> PeriodData BalanceData
    historical
          where
            -- changes to report on: usually just the valued changes themselves, but use the
            -- differences in the valued historical amount for CalcValueChange and CalcGain.
            changes :: PeriodData BalanceData
    changes = case ReportOpts -> BalanceCalculation
    balancecalc_ ReportOpts
    ropts of
                BalanceCalculation
    CalcChange        -> PeriodData BalanceData -> PeriodData BalanceData
    avalue PeriodData BalanceData
    unvaluedChanges
                BalanceCalculation
    CalcBudget        -> PeriodData BalanceData -> PeriodData BalanceData
    avalue PeriodData BalanceData
    unvaluedChanges
                BalanceCalculation
    CalcValueChange   -> PeriodData BalanceData -> PeriodData BalanceData
    forall (t :: * -> *).
    Traversable t =>
    t BalanceData -> t BalanceData
    periodChanges PeriodData BalanceData
    historical
                BalanceCalculation
    CalcGain          -> PeriodData BalanceData -> PeriodData BalanceData
    forall (t :: * -> *).
    Traversable t =>
    t BalanceData -> t BalanceData
    periodChanges PeriodData BalanceData
    historical
                BalanceCalculation
    CalcPostingsCount -> PeriodData BalanceData -> PeriodData BalanceData
    avalue PeriodData BalanceData
    unvaluedChanges
            -- the historical balance is the valued cumulative sum of all unvalued changes
            historical :: PeriodData BalanceData
    historical = PeriodData BalanceData -> PeriodData BalanceData
    avalue (PeriodData BalanceData -> PeriodData BalanceData)
    -> PeriodData BalanceData -> PeriodData BalanceData
    forall a b. (a -> b) -> a -> b
    $ PeriodData BalanceData -> PeriodData BalanceData
    forall (t :: * -> *).
    Traversable t =>
    t BalanceData -> t BalanceData
    cumulativeSum PeriodData BalanceData
    unvaluedChanges
            -- since this is a cumulative sum of valued amounts, it should not be valued again
            cumulative :: PeriodData BalanceData
    cumulative = PeriodData BalanceData -> PeriodData BalanceData
    forall (t :: * -> *).
    Traversable t =>
    t BalanceData -> t BalanceData
    cumulativeSum PeriodData BalanceData
    changes{pdpre = mempty}
            avalue :: PeriodData BalanceData -> PeriodData BalanceData
    avalue = ReportOpts
    -> Journal
    -> PriceOracle
    -> DayPartition
    -> PeriodData BalanceData
    -> PeriodData BalanceData
    periodDataValuation ReportOpts
    ropts Journal
    j PriceOracle
    priceoracle DayPartition
    colspans

        changesAcct :: Account BalanceData
    changesAcct = (Account BalanceData -> String)
    -> Account BalanceData -> Account BalanceData
    forall a. (a -> String) -> a -> a
    dbg5With (\Account BalanceData
    x -> String
    "calculateReportAccount changesAcct\n" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ Account BalanceData -> String
    forall a. Show a => Account a -> String
    showAccounts Account BalanceData
    x) (Account BalanceData -> Account BalanceData)
    -> (Account BalanceData -> Account BalanceData)
    -> Account BalanceData
    -> Account BalanceData
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .
            (PeriodData BalanceData -> PeriodData BalanceData)
    -> Account BalanceData -> Account BalanceData
    forall a. (PeriodData a -> PeriodData a) -> Account a -> Account a
    mapPeriodData (BalanceData
    -> PeriodData Day
    -> PeriodData BalanceData
    -> PeriodData BalanceData
    forall a b. a -> PeriodData b -> PeriodData a -> PeriodData a
    padPeriodData BalanceData
    forall a. Monoid a => a
    mempty (DayPartition -> PeriodData Day
    dayPartitionToPeriodData DayPartition
    colspans)) (Account BalanceData -> Account BalanceData)
    -> Account BalanceData -> Account BalanceData
    forall a b. (a -> b) -> a -> b
    $
            (Posting -> Maybe Day) -> [Posting] -> Account BalanceData
    accountFromPostings Posting -> Maybe Day
    getIntervalStartDate [Posting]
    ps

        getIntervalStartDate :: Posting -> Maybe Day
    getIntervalStartDate Posting
    p = (Maybe Day, Day) -> Maybe Day
    forall a b. (a, b) -> a
    fst ((Maybe Day, Day) -> Maybe Day) -> (Maybe Day, Day) -> Maybe Day
    forall a b. (a -> b) -> a -> b
    $ Day -> DayPartition -> (Maybe Day, Day)
    dayPartitionFind (Posting -> Day
    getPostingDate Posting
    p) DayPartition
    colspans
        getPostingDate :: Posting -> Day
    getPostingDate = WhichDate -> Posting -> Day
    postingDateOrDate2 (ReportOpts -> WhichDate
    whichDate (ReportSpec -> ReportOpts
    _rsReportOpts ReportSpec
    rspec))

    -- | The valuation function to use for the chosen report options.
    periodDataValuation :: ReportOpts -> Journal -> PriceOracle -> DayPartition
                        -> PeriodData BalanceData -> PeriodData BalanceData
    periodDataValuation :: ReportOpts
    -> Journal
    -> PriceOracle
    -> DayPartition
    -> PeriodData BalanceData
    -> PeriodData BalanceData
    periodDataValuation ReportOpts
    ropts Journal
    j PriceOracle
    priceoracle DayPartition
    colspans =
        (Day -> BalanceData -> BalanceData)
    -> PeriodData Day
    -> PeriodData BalanceData
    -> PeriodData BalanceData
    forall a b c.
    (a -> b -> c) -> PeriodData a -> PeriodData b -> PeriodData c
    opPeriodData Day -> BalanceData -> BalanceData
    valueBalanceData (DayPartition -> PeriodData Day
    dayPartitionToPeriodData DayPartition
    colspans)
      where
        valueBalanceData :: Day -> BalanceData -> BalanceData
        valueBalanceData :: Day -> BalanceData -> BalanceData
    valueBalanceData Day
    d = (MixedAmount -> MixedAmount) -> BalanceData -> BalanceData
    mapBalanceData (Day -> MixedAmount -> MixedAmount
    valueMixedAmount Day
    d)

        valueMixedAmount :: Day -> MixedAmount -> MixedAmount
        valueMixedAmount :: Day -> MixedAmount -> MixedAmount
    valueMixedAmount = ReportOpts
    -> Journal -> PriceOracle -> Day -> MixedAmount -> MixedAmount
    mixedAmountApplyValuationAfterSumFromOptsWith ReportOpts
    ropts Journal
    j PriceOracle
    priceoracle

    -- | Mark which nodes of an 'Account' are boring, and so should be omitted from reports.
    markAccountBoring :: ReportSpec -> Account BalanceData -> Account BalanceData
    markAccountBoring :: ReportSpec -> Account BalanceData -> Account BalanceData
    markAccountBoring ReportSpec{_rsQuery :: ReportSpec -> Query
    _rsQuery=Query
    query,_rsReportOpts :: ReportSpec -> ReportOpts
    _rsReportOpts=ReportOpts
    ropts}
        -- If depth 0, all accounts except the top-level account are boring
        | Bool
    qdepthIsZero = Bool -> Account BalanceData -> Account BalanceData
    forall {a}. Bool -> Account a -> Account a
    markBoring Bool
    False (Account BalanceData -> Account BalanceData)
    -> (Account BalanceData -> Account BalanceData)
    -> Account BalanceData
    -> Account BalanceData
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Account BalanceData -> Account BalanceData)
    -> Account BalanceData -> Account BalanceData
    forall a. (Account a -> Account a) -> Account a -> Account a
    mapAccounts (Bool -> Account BalanceData -> Account BalanceData
    forall {a}. Bool -> Account a -> Account a
    markBoring Bool
    True)
        -- Otherwise the top level account is boring, and subaccounts are boring if
        -- they are both boring in and of themselves and are boring parents
        | Bool
    otherwise    = Bool -> Account BalanceData -> Account BalanceData
    forall {a}. Bool -> Account a -> Account a
    markBoring Bool
    True (Account BalanceData -> Account BalanceData)
    -> (Account BalanceData -> Account BalanceData)
    -> Account BalanceData
    -> Account BalanceData
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Account BalanceData -> Account BalanceData)
    -> Account BalanceData -> Account BalanceData
    forall a. (Account a -> Account a) -> Account a -> Account a
    mapAccounts ((Account BalanceData -> Bool)
    -> Account BalanceData -> Account BalanceData
    forall {a}. (Account a -> Bool) -> Account a -> Account a
    markBoringBy ((Bool -> Bool -> Bool)
    -> (Account BalanceData -> Bool)
    -> (Account BalanceData -> Bool)
    -> Account BalanceData
    -> Bool
    forall a b c.
    (a -> b -> c)
    -> (Account BalanceData -> a)
    -> (Account BalanceData -> b)
    -> Account BalanceData
    -> c
    forall (f :: * -> *) a b c.
    Applicative f =>
    (a -> b -> c) -> f a -> f b -> f c
    liftA2 Bool -> Bool -> Bool
    (&&) Account BalanceData -> Bool
    isBoring Account BalanceData -> Bool
    forall a. Account a -> Bool
    isBoringParent))
      where
        -- Accounts boring on their own
        isBoring :: Account BalanceData -> Bool
        isBoring :: Account BalanceData -> Bool
    isBoring Account BalanceData
    acct = Bool
    tooDeep Bool -> Bool -> Bool
    || Bool
    allZeros
          where
            tooDeep :: Bool
    tooDeep = Int
    d Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    > Int
    qdepth                                       -- Throw out anything too deep
            allZeros :: Bool
    allZeros = (BalanceData -> MixedAmount) -> Map Day BalanceData -> Bool
    forall {t :: * -> *} {a}.
    Foldable t =>
    (a -> MixedAmount) -> t a -> Bool
    isZeroRow BalanceData -> MixedAmount
    balance Map Day BalanceData
    amts Bool -> Bool -> Bool
    && Bool -> Bool
    not Bool
    keepEmptyAccount  -- Throw away everything with a zero balance in the row, unless..
            keepEmptyAccount :: Bool
    keepEmptyAccount = ReportOpts -> Bool
    empty_ ReportOpts
    ropts Bool -> Bool -> Bool
    && Account BalanceData -> Bool
    keepWhenEmpty Account BalanceData
    acct      -- We are keeping empty rows and this row meets the criteria

            amts :: Map Day BalanceData
    amts = PeriodData BalanceData -> Map Day BalanceData
    forall a. PeriodData a -> Map Day a
    pdperiods (PeriodData BalanceData -> Map Day BalanceData)
    -> PeriodData BalanceData -> Map Day BalanceData
    forall a b. (a -> b) -> a -> b
    $ Account BalanceData -> PeriodData BalanceData
    forall a. Account a -> PeriodData a
    adata Account BalanceData
    acct
            d :: Int
    d = CommoditySymbol -> Int
    accountNameLevel (CommoditySymbol -> Int) -> CommoditySymbol -> Int
    forall a b. (a -> b) -> a -> b
    $ Account BalanceData -> CommoditySymbol
    forall a. Account a -> CommoditySymbol
    aname Account BalanceData
    acct

            qdepth :: Int
    qdepth = Int -> Maybe Int -> Int
    forall a. a -> Maybe a -> a
    fromMaybe Int
    forall a. Bounded a => a
    maxBound (Maybe Int -> Int)
    -> (CommoditySymbol -> Maybe Int) -> CommoditySymbol -> Int
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . DepthSpec -> CommoditySymbol -> Maybe Int
    getAccountNameClippedDepth DepthSpec
    depthspec (CommoditySymbol -> Int) -> CommoditySymbol -> Int
    forall a b. (a -> b) -> a -> b
    $ Account BalanceData -> CommoditySymbol
    forall a. Account a -> CommoditySymbol
    aname Account BalanceData
    acct
            balance :: BalanceData -> MixedAmount
    balance = MixedAmount -> MixedAmount
    maybeStripPrices (MixedAmount -> MixedAmount)
    -> (BalanceData -> MixedAmount) -> BalanceData -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . case ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts of
                AccountListMode
    ALTree | Int
    d Int -> Int -> Bool
    forall a. Eq a => a -> a -> Bool
    == Int
    qdepth -> BalanceData -> MixedAmount
    bdincludingsubs
                AccountListMode
    _                    -> BalanceData -> MixedAmount
    bdexcludingsubs

        -- Accounts which don't have enough interesting subaccounts
        isBoringParent :: Account a -> Bool
        isBoringParent :: forall a. Account a -> Bool
    isBoringParent Account a
    acct = case ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts of
            AccountListMode
    ALTree -> Bool
    notEnoughSubs Bool -> Bool -> Bool
    || Bool
    droppedAccount
            AccountListMode
    ALFlat -> Bool
    True
          where
            notEnoughSubs :: Bool
    notEnoughSubs = [Account a] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [Account a]
    interestingSubs Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    < Int
    minimumSubs
            droppedAccount :: Bool
    droppedAccount = CommoditySymbol -> Int
    accountNameLevel (Account a -> CommoditySymbol
    forall a. Account a -> CommoditySymbol
    aname Account a
    acct) Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    <= ReportOpts -> Int
    drop_ ReportOpts
    ropts
            interestingSubs :: [Account a]
    interestingSubs = (Account a -> Bool) -> [Account a] -> [Account a]
    forall a. (a -> Bool) -> [a] -> [a]
    filter ((Account a -> Bool) -> Account a -> Bool
    forall a. (Account a -> Bool) -> Account a -> Bool
    anyAccounts (Bool -> Bool
    not (Bool -> Bool) -> (Account a -> Bool) -> Account a -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Account a -> Bool
    forall a. Account a -> Bool
    aboring)) ([Account a] -> [Account a]) -> [Account a] -> [Account a]
    forall a b. (a -> b) -> a -> b
    $ Account a -> [Account a]
    forall a. Account a -> [Account a]
    asubs Account a
    acct
            minimumSubs :: Int
    minimumSubs = 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)
        keepWhenEmpty :: Account BalanceData -> Bool
    keepWhenEmpty = case ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts of
            AccountListMode
    ALFlat -> (BalanceData -> Bool) -> Map Day BalanceData -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    any ((Int
    0Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    <) (Int -> Bool) -> (BalanceData -> Int) -> BalanceData -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . BalanceData -> Int
    bdnumpostings) (Map Day BalanceData -> Bool)
    -> (Account BalanceData -> Map Day BalanceData)
    -> Account BalanceData
    -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . PeriodData BalanceData -> Map Day BalanceData
    forall a. PeriodData a -> Map Day a
    pdperiods (PeriodData BalanceData -> Map Day BalanceData)
    -> (Account BalanceData -> PeriodData BalanceData)
    -> Account BalanceData
    -> Map Day BalanceData
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Account BalanceData -> PeriodData BalanceData
    forall a. Account a -> PeriodData a
    adata  -- Keep all accounts that have postings in flat mode
            AccountListMode
    ALTree -> [Account BalanceData] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null ([Account BalanceData] -> Bool)
    -> (Account BalanceData -> [Account BalanceData])
    -> Account BalanceData
    -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Account BalanceData -> [Account BalanceData]
    forall a. Account a -> [Account a]
    asubs                                    -- Keep only empty leaves in tree mode
        maybeStripPrices :: MixedAmount -> MixedAmount
    maybeStripPrices = if ReportOpts -> Maybe ConversionOp
    conversionop_ ReportOpts
    ropts Maybe ConversionOp -> Maybe ConversionOp -> Bool
    forall a. Eq a => a -> a -> Bool
    == ConversionOp -> Maybe ConversionOp
    forall a. a -> Maybe a
    Just ConversionOp
    NoConversionOp then MixedAmount -> MixedAmount
    forall a. a -> a
    id else MixedAmount -> MixedAmount
    mixedAmountStripCosts

        qdepthIsZero :: Bool
    qdepthIsZero = DepthSpec
    depthspec DepthSpec -> DepthSpec -> Bool
    forall a. Eq a => a -> a -> Bool
    == Maybe Int -> [(Regexp, Int)] -> DepthSpec
    DepthSpec (Int -> Maybe Int
    forall a. a -> Maybe a
    Just Int
    0) []
        depthspec :: DepthSpec
    depthspec = Query -> DepthSpec
    queryDepth Query
    query

        markBoring :: Bool -> Account a -> Account a
    markBoring   Bool
    v Account a
    a = Account a
    a{aboring = v}
        markBoringBy :: (Account a -> Bool) -> Account a -> Account a
    markBoringBy Account a -> Bool
    f Account a
    a = Account a
    a{aboring = f a}

    -- | Build a report row.
    --
    -- Calculate the column totals. These are always the sum of column amounts.
    generateMultiBalanceReport :: ReportOpts -> Maybe DayPartition -> Account BalanceData -> MultiBalanceReport
    generateMultiBalanceReport :: ReportOpts
    -> Maybe DayPartition -> Account BalanceData -> MultiBalanceReport
    generateMultiBalanceReport ReportOpts
    ropts Maybe DayPartition
    colspans =
        ReportOpts -> MultiBalanceReport -> MultiBalanceReport
    reportPercent ReportOpts
    ropts (MultiBalanceReport -> MultiBalanceReport)
    -> (Account BalanceData -> MultiBalanceReport)
    -> Account BalanceData
    -> MultiBalanceReport
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (forall a.
     ReportOpts
     -> (BalanceData -> MixedAmount)
     -> a
     -> Account BalanceData
     -> PeriodicReportRow a MixedAmount)
    -> (BalanceData -> MixedAmount)
    -> (MixedAmount -> MixedAmount)
    -> ReportOpts
    -> Maybe DayPartition
    -> Account BalanceData
    -> MultiBalanceReport
    forall c b.
    Show c =>
    (forall a.
     ReportOpts
     -> (BalanceData -> MixedAmount)
     -> a
     -> Account b
     -> PeriodicReportRow a c)
    -> (b -> MixedAmount)
    -> (c -> MixedAmount)
    -> ReportOpts
    -> Maybe DayPartition
    -> Account b
    -> PeriodicReport DisplayName c
    generatePeriodicReport ReportOpts
    -> (BalanceData -> MixedAmount)
    -> a
    -> Account BalanceData
    -> PeriodicReportRow a MixedAmount
    forall a.
    ReportOpts
    -> (BalanceData -> MixedAmount)
    -> a
    -> Account BalanceData
    -> PeriodicReportRow a MixedAmount
    makeMultiBalanceReportRow BalanceData -> MixedAmount
    bdincludingsubs MixedAmount -> MixedAmount
    forall a. a -> a
    id ReportOpts
    ropts Maybe DayPartition
    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.
    generatePeriodicReport :: Show c =>
        (forall a. ReportOpts -> (BalanceData -> MixedAmount) -> a -> Account b -> PeriodicReportRow a c)
        -> (b -> MixedAmount) -> (c -> MixedAmount)
        -> ReportOpts -> Maybe DayPartition -> Account b -> PeriodicReport DisplayName c
    generatePeriodicReport :: forall c b.
    Show c =>
    (forall a.
     ReportOpts
     -> (BalanceData -> MixedAmount)
     -> a
     -> Account b
     -> PeriodicReportRow a c)
    -> (b -> MixedAmount)
    -> (c -> MixedAmount)
    -> ReportOpts
    -> Maybe DayPartition
    -> Account b
    -> PeriodicReport DisplayName c
    generatePeriodicReport forall a.
    ReportOpts
    -> (BalanceData -> MixedAmount)
    -> a
    -> Account b
    -> PeriodicReportRow a c
    makeRow b -> MixedAmount
    treeAmt c -> MixedAmount
    flatAmt ReportOpts
    ropts Maybe DayPartition
    colspans Account b
    acct =
        [DateSpan]
    -> [PeriodicReportRow DisplayName c]
    -> PeriodicReportRow () c
    -> PeriodicReport DisplayName c
    forall a b.
    [DateSpan]
    -> [PeriodicReportRow a b]
    -> PeriodicReportRow () b
    -> PeriodicReport a b
    PeriodicReport (Maybe DayPartition -> [DateSpan]
    maybeDayPartitionToDateSpans Maybe DayPartition
    colspans) (Account b -> [PeriodicReportRow DisplayName c]
    buildAndSort Account b
    acct) PeriodicReportRow () c
    totalsrow
      where
        -- Build report rows and sort them
        buildAndSort :: Account b -> [PeriodicReportRow DisplayName c]
    buildAndSort = String
    -> [PeriodicReportRow DisplayName c]
    -> [PeriodicReportRow DisplayName c]
    forall a. Show a => String -> a -> a
    dbg5 String
    "generatePeriodicReport buildAndSort" ([PeriodicReportRow DisplayName c]
     -> [PeriodicReportRow DisplayName c])
    -> (Account b -> [PeriodicReportRow DisplayName c])
    -> Account b
    -> [PeriodicReportRow DisplayName c]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . case ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts of
            AccountListMode
    ALTree | ReportOpts -> Bool
    sort_amount_ ReportOpts
    ropts -> Account b -> [PeriodicReportRow DisplayName c]
    buildRows (Account b -> [PeriodicReportRow DisplayName c])
    -> (Account b -> Account b)
    -> Account b
    -> [PeriodicReportRow DisplayName c]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Account b -> Account b
    sortTreeByAmount
            AccountListMode
    ALFlat | ReportOpts -> Bool
    sort_amount_ ReportOpts
    ropts -> [PeriodicReportRow DisplayName c]
    -> [PeriodicReportRow DisplayName c]
    sortFlatByAmount ([PeriodicReportRow DisplayName c]
     -> [PeriodicReportRow DisplayName c])
    -> (Account b -> [PeriodicReportRow DisplayName c])
    -> Account b
    -> [PeriodicReportRow DisplayName c]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Account b -> [PeriodicReportRow DisplayName c]
    buildRows
            AccountListMode
    _                           -> Account b -> [PeriodicReportRow DisplayName c]
    buildRows (Account b -> [PeriodicReportRow DisplayName c])
    -> (Account b -> Account b)
    -> Account b
    -> [PeriodicReportRow DisplayName c]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Account b -> Account b
    forall a. Account a -> Account a
    sortAccountTreeByDeclaration

        buildRows :: Account b -> [PeriodicReportRow DisplayName c]
    buildRows = (ReportOpts
     -> (BalanceData -> MixedAmount)
     -> DisplayName
     -> Account b
     -> PeriodicReportRow DisplayName c)
    -> ReportOpts -> Account b -> [PeriodicReportRow DisplayName c]
    forall b c.
    (ReportOpts
     -> (BalanceData -> MixedAmount)
     -> DisplayName
     -> Account b
     -> PeriodicReportRow DisplayName c)
    -> ReportOpts -> Account b -> [PeriodicReportRow DisplayName c]
    buildReportRows ReportOpts
    -> (BalanceData -> MixedAmount)
    -> DisplayName
    -> Account b
    -> PeriodicReportRow DisplayName c
    forall a.
    ReportOpts
    -> (BalanceData -> MixedAmount)
    -> a
    -> Account b
    -> PeriodicReportRow a c
    makeRow ReportOpts
    ropts

        -- Calculate column totals from the inclusive balances of the root account
        totalsrow :: PeriodicReportRow () c
    totalsrow = String -> PeriodicReportRow () c -> PeriodicReportRow () c
    forall a. Show a => String -> a -> a
    dbg5 String
    "generatePeriodicReport totalsrow" (PeriodicReportRow () c -> PeriodicReportRow () c)
    -> PeriodicReportRow () c -> PeriodicReportRow () c
    forall a b. (a -> b) -> a -> b
    $ ReportOpts
    -> (BalanceData -> MixedAmount)
    -> ()
    -> Account b
    -> PeriodicReportRow () c
    forall a.
    ReportOpts
    -> (BalanceData -> MixedAmount)
    -> a
    -> Account b
    -> PeriodicReportRow a c
    makeRow ReportOpts
    ropts BalanceData -> MixedAmount
    bdincludingsubs () Account b
    acct

        sortTreeByAmount :: Account b -> Account b
    sortTreeByAmount = case 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 of
            NormalSign
    NormallyPositive -> (Account b -> (Down MixedAmount, CommoditySymbol))
    -> Account b -> Account b
    forall b a. Ord b => (Account a -> b) -> Account a -> Account a
    sortAccountTreeOn (\Account b
    r -> (MixedAmount -> Down MixedAmount
    forall a. a -> Down a
    Down (MixedAmount -> Down MixedAmount)
    -> MixedAmount -> Down MixedAmount
    forall a b. (a -> b) -> a -> b
    $ Account b -> MixedAmount
    amt Account b
    r, Account b -> CommoditySymbol
    forall a. Account a -> CommoditySymbol
    aname Account b
    r))
            NormalSign
    NormallyNegative -> (Account b -> (MixedAmount, CommoditySymbol))
    -> Account b -> Account b
    forall b a. Ord b => (Account a -> b) -> Account a -> Account a
    sortAccountTreeOn (\Account b
    r -> (Account b -> MixedAmount
    amt Account b
    r, Account b -> CommoditySymbol
    forall a. Account a -> CommoditySymbol
    aname Account b
    r))
          where
            amt :: Account b -> MixedAmount
    amt = MixedAmount -> MixedAmount
    mixedAmountStripCosts (MixedAmount -> MixedAmount)
    -> (Account b -> MixedAmount) -> Account b -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Map Day MixedAmount -> MixedAmount
    forall {a}. Map a MixedAmount -> MixedAmount
    sortKey (Map Day MixedAmount -> MixedAmount)
    -> (Account b -> Map Day MixedAmount) -> Account b -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (b -> MixedAmount) -> Map Day b -> Map Day MixedAmount
    forall a b. (a -> b) -> Map Day a -> Map Day b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap b -> MixedAmount
    treeAmt (Map Day b -> Map Day MixedAmount)
    -> (Account b -> Map Day b) -> Account b -> Map Day MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . PeriodData b -> Map Day b
    forall a. PeriodData a -> Map Day a
    pdperiods (PeriodData b -> Map Day b)
    -> (Account b -> PeriodData b) -> Account b -> Map Day b
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Account b -> PeriodData b
    forall a. Account a -> PeriodData a
    adata
            sortKey :: Map a MixedAmount -> MixedAmount
    sortKey = case ReportOpts -> BalanceAccumulation
    balanceaccum_ ReportOpts
    ropts of
              BalanceAccumulation
    PerPeriod -> Map a MixedAmount -> MixedAmount
    forall (t :: * -> *). Foldable t => t MixedAmount -> MixedAmount
    maSum
              BalanceAccumulation
    _         -> MixedAmount
    -> ((a, MixedAmount) -> MixedAmount)
    -> Maybe (a, MixedAmount)
    -> MixedAmount
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe MixedAmount
    nullmixedamt (a, MixedAmount) -> MixedAmount
    forall a b. (a, b) -> b
    snd (Maybe (a, MixedAmount) -> MixedAmount)
    -> (Map a MixedAmount -> Maybe (a, MixedAmount))
    -> Map a MixedAmount
    -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Map a MixedAmount -> Maybe (a, MixedAmount)
    forall k a. Map k a -> Maybe (k, a)
    M.lookupMax

        sortFlatByAmount :: [PeriodicReportRow DisplayName c]
    -> [PeriodicReportRow DisplayName c]
    sortFlatByAmount = case 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 of
            NormalSign
    NormallyPositive -> (PeriodicReportRow DisplayName c
     -> (Down MixedAmount, CommoditySymbol))
    -> [PeriodicReportRow DisplayName c]
    -> [PeriodicReportRow DisplayName c]
    forall b a. Ord b => (a -> b) -> [a] -> [a]
    sortOn (\PeriodicReportRow DisplayName c
    r -> (MixedAmount -> Down MixedAmount
    forall a. a -> Down a
    Down (MixedAmount -> Down MixedAmount)
    -> MixedAmount -> Down MixedAmount
    forall a b. (a -> b) -> a -> b
    $ PeriodicReportRow DisplayName c -> MixedAmount
    forall {a}. PeriodicReportRow a c -> MixedAmount
    amt PeriodicReportRow DisplayName c
    r, PeriodicReportRow DisplayName c -> CommoditySymbol
    forall a. PeriodicReportRow DisplayName a -> CommoditySymbol
    prrFullName PeriodicReportRow DisplayName c
    r))
            NormalSign
    NormallyNegative -> (PeriodicReportRow DisplayName c -> (MixedAmount, CommoditySymbol))
    -> [PeriodicReportRow DisplayName c]
    -> [PeriodicReportRow DisplayName c]
    forall b a. Ord b => (a -> b) -> [a] -> [a]
    sortOn (\PeriodicReportRow DisplayName c
    r -> (PeriodicReportRow DisplayName c -> MixedAmount
    forall {a}. PeriodicReportRow a c -> MixedAmount
    amt PeriodicReportRow DisplayName c
    r, PeriodicReportRow DisplayName c -> CommoditySymbol
    forall a. PeriodicReportRow DisplayName a -> CommoditySymbol
    prrFullName PeriodicReportRow DisplayName c
    r))
          where amt :: PeriodicReportRow a c -> MixedAmount
    amt = MixedAmount -> MixedAmount
    mixedAmountStripCosts (MixedAmount -> MixedAmount)
    -> (PeriodicReportRow a c -> MixedAmount)
    -> PeriodicReportRow a c
    -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . c -> MixedAmount
    flatAmt (c -> MixedAmount)
    -> (PeriodicReportRow a c -> c)
    -> PeriodicReportRow a c
    -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . PeriodicReportRow a c -> c
    forall a b. PeriodicReportRow a b -> b
    prrTotal

    -- | Build the report rows.
    -- One row per account, with account name info, row amounts, row total and row average.
    -- Rows are sorted according to the order in the 'Account' tree.
    buildReportRows :: forall b c.
                    (ReportOpts -> (BalanceData -> MixedAmount) -> DisplayName -> Account b -> PeriodicReportRow DisplayName c)
                    -> ReportOpts -> Account b -> [PeriodicReportRow DisplayName c]
    buildReportRows :: forall b c.
    (ReportOpts
     -> (BalanceData -> MixedAmount)
     -> DisplayName
     -> Account b
     -> PeriodicReportRow DisplayName c)
    -> ReportOpts -> Account b -> [PeriodicReportRow DisplayName c]
    buildReportRows ReportOpts
    -> (BalanceData -> MixedAmount)
    -> DisplayName
    -> Account b
    -> PeriodicReportRow DisplayName c
    makeRow ReportOpts
    ropts = Bool
    -> Int -> Int -> Account b -> [PeriodicReportRow DisplayName c]
    mkRows Bool
    True (-ReportOpts -> Int
    drop_ ReportOpts
    ropts) Int
    0
      where
        -- Build the row for an account at a given depth with some number of boring parents
        mkRows :: Bool -> Int -> Int -> Account b -> [PeriodicReportRow DisplayName c]
        mkRows :: Bool
    -> Int -> Int -> Account b -> [PeriodicReportRow DisplayName c]
    mkRows Bool
    isRoot Int
    d Int
    boringParents Account b
    acct
            -- Account is boring and has no interesting children at any depth, so we stop
            | Account b -> Bool
    forall a. Account a -> Bool
    allBoring Account b
    acct                 = []
            -- Account is a boring root account, and should be bypassed entirely
            | Account b -> Bool
    forall a. Account a -> Bool
    aboring Account b
    acct Bool -> Bool -> Bool
    && Bool
    isRoot         = Int -> Int -> [PeriodicReportRow DisplayName c]
    buildSubrows Int
    d Int
    0
            -- Account is boring and has been dropped, so should be skipped and move up the hierarchy
            | Account b -> Bool
    forall a. Account a -> Bool
    aboring Account b
    acct Bool -> Bool -> Bool
    && Int
    d Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    < Int
    0          = Int -> Int -> [PeriodicReportRow DisplayName c]
    buildSubrows (Int
    d Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    1) Int
    0
            -- Account is boring, and we can omit boring parents, so we should omit but keep track
            | Account b -> Bool
    forall a. Account a -> Bool
    aboring Account b
    acct Bool -> Bool -> Bool
    && Bool
    canOmitParents = Int -> Int -> [PeriodicReportRow DisplayName c]
    buildSubrows Int
    d (Int
    boringParents Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    1)
            -- Account is not boring or otherwise should be displayed.
            | Bool
    otherwise = ReportOpts
    -> (BalanceData -> MixedAmount)
    -> DisplayName
    -> Account b
    -> PeriodicReportRow DisplayName c
    makeRow ReportOpts
    ropts BalanceData -> MixedAmount
    balance DisplayName
    displayname Account b
    acct PeriodicReportRow DisplayName c
    -> [PeriodicReportRow DisplayName c]
    -> [PeriodicReportRow DisplayName c]
    forall a. a -> [a] -> [a]
    : Int -> Int -> [PeriodicReportRow DisplayName c]
    buildSubrows (Int
    d Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    1) Int
    0
          where
            displayname :: DisplayName
    displayname = Int -> Int -> CommoditySymbol -> DisplayName
    displayedName Int
    d Int
    boringParents (CommoditySymbol -> DisplayName) -> CommoditySymbol -> DisplayName
    forall a b. (a -> b) -> a -> b
    $ Account b -> CommoditySymbol
    forall a. Account a -> CommoditySymbol
    aname Account b
    acct
            buildSubrows :: Int -> Int -> [PeriodicReportRow DisplayName c]
    buildSubrows Int
    i Int
    b = (Account b -> [PeriodicReportRow DisplayName c])
    -> [Account b] -> [PeriodicReportRow DisplayName c]
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap (Bool
    -> Int -> Int -> Account b -> [PeriodicReportRow DisplayName c]
    mkRows Bool
    False Int
    i Int
    b) ([Account b] -> [PeriodicReportRow DisplayName c])
    -> [Account b] -> [PeriodicReportRow DisplayName c]
    forall a b. (a -> b) -> a -> b
    $ Account b -> [Account b]
    forall a. Account a -> [Account a]
    asubs Account b
    acct

        canOmitParents :: Bool
    canOmitParents = ReportOpts -> Bool
    flat_ ReportOpts
    ropts Bool -> Bool -> Bool
    || Bool -> Bool
    not (ReportOpts -> Bool
    no_elide_ ReportOpts
    ropts)
        allBoring :: Account a -> Bool
    allBoring Account a
    a = Account a -> Bool
    forall a. Account a -> Bool
    aboring Account a
    a Bool -> Bool -> Bool
    && (Account a -> Bool) -> [Account a] -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    all Account a -> Bool
    allBoring (Account a -> [Account a]
    forall a. Account a -> [Account a]
    asubs Account a
    a)
        balance :: BalanceData -> MixedAmount
    balance = case ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts of
            AccountListMode
    ALTree -> BalanceData -> MixedAmount
    bdincludingsubs
            AccountListMode
    ALFlat -> BalanceData -> MixedAmount
    bdexcludingsubs

        displayedName :: Int -> Int -> CommoditySymbol -> DisplayName
    displayedName Int
    d Int
    boringParents CommoditySymbol
    name
            | Int
    d Int -> Int -> Bool
    forall a. Eq a => a -> a -> Bool
    == Int
    0 Bool -> Bool -> Bool
    && CommoditySymbol
    name CommoditySymbol -> CommoditySymbol -> Bool
    forall a. Eq a => a -> a -> Bool
    == CommoditySymbol
    "root" = CommoditySymbol -> CommoditySymbol -> Int -> DisplayName
    DisplayName CommoditySymbol
    "..." CommoditySymbol
    "..." Int
    0
            | Bool
    otherwise = case ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts of
                AccountListMode
    ALTree -> CommoditySymbol -> CommoditySymbol -> Int -> DisplayName
    DisplayName CommoditySymbol
    name CommoditySymbol
    leaf (Int -> DisplayName) -> Int -> DisplayName
    forall a b. (a -> b) -> a -> b
    $ Int -> Int -> Int
    forall a. Ord a => a -> a -> a
    max Int
    0 Int
    d
                AccountListMode
    ALFlat -> CommoditySymbol -> CommoditySymbol -> Int -> DisplayName
    DisplayName CommoditySymbol
    name CommoditySymbol
    droppedName Int
    0
          where
            leaf :: CommoditySymbol
    leaf = [CommoditySymbol] -> CommoditySymbol
    accountNameFromComponents
                   ([CommoditySymbol] -> CommoditySymbol)
    -> ([CommoditySymbol] -> [CommoditySymbol])
    -> [CommoditySymbol]
    -> CommoditySymbol
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [CommoditySymbol] -> [CommoditySymbol]
    forall a. [a] -> [a]
    reverse ([CommoditySymbol] -> [CommoditySymbol])
    -> ([CommoditySymbol] -> [CommoditySymbol])
    -> [CommoditySymbol]
    -> [CommoditySymbol]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Int -> [CommoditySymbol] -> [CommoditySymbol]
    forall a. Int -> [a] -> [a]
    take (Int
    boringParents Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    1) ([CommoditySymbol] -> [CommoditySymbol])
    -> ([CommoditySymbol] -> [CommoditySymbol])
    -> [CommoditySymbol]
    -> [CommoditySymbol]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [CommoditySymbol] -> [CommoditySymbol]
    forall a. [a] -> [a]
    reverse
                   ([CommoditySymbol] -> CommoditySymbol)
    -> [CommoditySymbol] -> CommoditySymbol
    forall a b. (a -> b) -> a -> b
    $ CommoditySymbol -> [CommoditySymbol]
    accountNameComponents CommoditySymbol
    droppedName
            droppedName :: CommoditySymbol
    droppedName = Int -> CommoditySymbol -> CommoditySymbol
    accountNameDrop (ReportOpts -> Int
    drop_ ReportOpts
    ropts) CommoditySymbol
    name

    -- | Build a report row.
    --
    -- Calculate the column totals. These are always the sum of column amounts.
    makeMultiBalanceReportRow :: ReportOpts -> (BalanceData -> MixedAmount)
                              -> a -> Account BalanceData -> PeriodicReportRow a MixedAmount
    makeMultiBalanceReportRow :: forall a.
    ReportOpts
    -> (BalanceData -> MixedAmount)
    -> a
    -> Account BalanceData
    -> PeriodicReportRow a MixedAmount
    makeMultiBalanceReportRow = MixedAmount
    -> (Map Day MixedAmount -> (MixedAmount, MixedAmount))
    -> ReportOpts
    -> (BalanceData -> MixedAmount)
    -> a
    -> Account BalanceData
    -> PeriodicReportRow a MixedAmount
    forall c b a.
    c
    -> (Map Day c -> (c, c))
    -> ReportOpts
    -> (b -> c)
    -> a
    -> Account b
    -> PeriodicReportRow a c
    makePeriodicReportRow MixedAmount
    nullmixedamt Map Day MixedAmount -> (MixedAmount, MixedAmount)
    forall (f :: * -> *).
    Foldable f =>
    f MixedAmount -> (MixedAmount, MixedAmount)
    sumAndAverageMixedAmounts

    -- | Build a report row.
    --
    -- Calculate the column totals. These are always the sum of column amounts.
    makePeriodicReportRow :: c -> (M.Map Day c -> (c, c))
                          -> ReportOpts -> (b -> c)
                          -> a -> Account b -> PeriodicReportRow a c
    makePeriodicReportRow :: forall c b a.
    c
    -> (Map Day c -> (c, c))
    -> ReportOpts
    -> (b -> c)
    -> a
    -> Account b
    -> PeriodicReportRow a c
    makePeriodicReportRow c
    nullEntry Map Day c -> (c, c)
    totalAndAverage ReportOpts
    ropts b -> c
    balance a
    name Account b
    acct =
        a -> [c] -> c -> c -> PeriodicReportRow a c
    forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
    PeriodicReportRow a
    name (Map Day c -> [c]
    forall a. Map Day a -> [a]
    forall (t :: * -> *) a. Foldable t => t a -> [a]
    toList Map Day c
    rowbals) c
    rowtotal c
    avg
      where
        rowbals :: Map Day c
    rowbals = (b -> c) -> Map Day b -> Map Day c
    forall a b. (a -> b) -> Map Day a -> Map Day b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap b -> c
    balance (Map Day b -> Map Day c)
    -> (PeriodData b -> Map Day b) -> PeriodData b -> Map Day c
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . PeriodData b -> Map Day b
    forall a. PeriodData a -> Map Day a
    pdperiods (PeriodData b -> Map Day c) -> PeriodData b -> Map Day c
    forall a b. (a -> b) -> a -> b
    $ Account b -> PeriodData b
    forall a. Account a -> PeriodData a
    adata Account b
    acct
        (c
    total, c
    avg) = Map Day c -> (c, c)
    totalAndAverage Map Day c
    rowbals
        -- Total for a cumulative/historical report is always the last column.
        rowtotal :: c
    rowtotal = case ReportOpts -> BalanceAccumulation
    balanceaccum_ ReportOpts
    ropts of
            BalanceAccumulation
    PerPeriod -> c
    total
            BalanceAccumulation
    _         -> c -> ((Day, c) -> c) -> Maybe (Day, c) -> c
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe c
    nullEntry (Day, c) -> c
    forall a b. (a, b) -> b
    snd (Maybe (Day, c) -> c) -> Maybe (Day, c) -> c
    forall a b. (a -> b) -> a -> b
    $ Map Day c -> Maybe (Day, c)
    forall k a. Map k a -> Maybe (k, a)
    M.lookupMax Map Day c
    rowbals

    -- | 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)

    -- | 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 (String -> MixedAmount
    forall a. String -> a
    error' String
    errmsg) (Maybe MixedAmount -> MixedAmount)
    -> Maybe MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ do  -- PARTIAL:
        a' <- MixedAmount -> Maybe Amount
    unifyMixedAmount MixedAmount
    a
        b' <- unifyMixedAmount b
        guard $ amountIsZero a' || amountIsZero b' || acommodity a' == acommodity b'
        return $ mixed [per $ if aquantity b' == 0 then 0 else aquantity a' / abs (aquantity b') * 100]
      where errmsg :: String
    errmsg = String
    "Cannot calculate percentages if accounts have different commodities (Hint: Try --cost, -V or similar flags.)"

    -- | Calculate a cumulative sum from a list of period changes.
    cumulativeSum :: Traversable t => t BalanceData -> t BalanceData
    cumulativeSum :: forall (t :: * -> *).
    Traversable t =>
    t BalanceData -> t BalanceData
    cumulativeSum = (BalanceData, t BalanceData) -> t BalanceData
    forall a b. (a, b) -> b
    snd ((BalanceData, t BalanceData) -> t BalanceData)
    -> (t BalanceData -> (BalanceData, t BalanceData))
    -> t BalanceData
    -> t BalanceData
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (BalanceData -> BalanceData -> (BalanceData, BalanceData))
    -> BalanceData -> t BalanceData -> (BalanceData, t BalanceData)
    forall (t :: * -> *) s a b.
    Traversable t =>
    (s -> a -> (s, b)) -> s -> t a -> (s, t b)
    mapAccumL (\BalanceData
    prev BalanceData
    new -> let z :: BalanceData
    z = BalanceData
    prev BalanceData -> BalanceData -> BalanceData
    forall a. Semigroup a => a -> a -> a
    <> BalanceData
    new in (BalanceData
    z, BalanceData
    z)) BalanceData
    forall a. Monoid a => a
    mempty

    -- | Extract period changes from a cumulative list.
    periodChanges :: Traversable t => t BalanceData -> t BalanceData
    periodChanges :: forall (t :: * -> *).
    Traversable t =>
    t BalanceData -> t BalanceData
    periodChanges = (BalanceData, t BalanceData) -> t BalanceData
    forall a b. (a, b) -> b
    snd ((BalanceData, t BalanceData) -> t BalanceData)
    -> (t BalanceData -> (BalanceData, t BalanceData))
    -> t BalanceData
    -> t BalanceData
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (BalanceData -> BalanceData -> (BalanceData, BalanceData))
    -> BalanceData -> t BalanceData -> (BalanceData, t BalanceData)
    forall (t :: * -> *) s a b.
    Traversable t =>
    (s -> a -> (s, b)) -> s -> t a -> (s, t b)
    mapAccumL (\BalanceData
    prev BalanceData
    new -> (BalanceData
    new, (MixedAmount -> MixedAmount -> MixedAmount)
    -> BalanceData -> BalanceData -> BalanceData
    opBalanceData MixedAmount -> MixedAmount -> MixedAmount
    maMinus BalanceData
    new BalanceData
    prev)) BalanceData
    forall a. Monoid a => a
    mempty

    -- tests

    tests_MultiBalanceReport :: TestTree
    tests_MultiBalanceReport = String -> [TestTree] -> TestTree
    testGroup String
    "MultiBalanceReport" [

      let
        amt0 :: Amount
    amt0 = Amount {acommodity :: CommoditySymbol
    acommodity=CommoditySymbol
    "$", aquantity :: Quantity
    aquantity=Quantity
    0, acost :: Maybe AmountCost
    acost=Maybe AmountCost
    forall a. Maybe a
    Nothing, acostbasis :: Maybe CostBasis
    acostbasis=Maybe CostBasis
    forall a. Maybe a
    Nothing,
          astyle :: AmountStyle
    astyle=AmountStyle {ascommodityside :: Side
    ascommodityside = Side
    L, ascommodityspaced :: Bool
    ascommodityspaced = Bool
    False, asdigitgroups :: Maybe DigitGroupStyle
    asdigitgroups = Maybe DigitGroupStyle
    forall a. Maybe a
    Nothing,
          asdecimalmark :: Maybe Char
    asdecimalmark = Char -> Maybe Char
    forall a. a -> Maybe a
    Just Char
    '.', asprecision :: AmountPrecision
    asprecision = Word8 -> AmountPrecision
    Precision Word8
    2, asrounding :: Rounding
    asrounding = Rounding
    NoRounding}}
        (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=And [queryFromFlags $ _rsReportOpts rspec, _rsQuery 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
    -> (CommoditySymbol, CommoditySymbol, Int, [String], String,
        String)
    showw (PeriodicReportRow DisplayName
    a [MixedAmount]
    lAmt MixedAmount
    amt MixedAmount
    amt')
                  = (DisplayName -> CommoditySymbol
    displayFull DisplayName
    a, DisplayName -> CommoditySymbol
    displayName DisplayName
    a, DisplayName -> Int
    displayIndent DisplayName
    a, (MixedAmount -> String) -> [MixedAmount] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map MixedAmount -> String
    showMixedAmountDebug [MixedAmount]
    lAmt, MixedAmount -> String
    showMixedAmountDebug MixedAmount
    amt, MixedAmount -> String
    showMixedAmountDebug MixedAmount
    amt')
          ((PeriodicReportRow DisplayName MixedAmount
     -> (CommoditySymbol, CommoditySymbol, Int, [String], String,
         String))
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [(CommoditySymbol, CommoditySymbol, Int, [String], String,
         String)]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName MixedAmount
    -> (CommoditySymbol, CommoditySymbol, Int, [String], String,
        String)
    showw [PeriodicReportRow DisplayName MixedAmount]
    aitems) [(CommoditySymbol, CommoditySymbol, Int, [String], String, String)]
    -> [(CommoditySymbol, CommoditySymbol, Int, [String], String,
         String)]
    -> IO ()
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> IO ()
    @?= ((PeriodicReportRow DisplayName MixedAmount
     -> (CommoditySymbol, CommoditySymbol, Int, [String], String,
         String))
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [(CommoditySymbol, CommoditySymbol, Int, [String], String,
         String)]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName MixedAmount
    -> (CommoditySymbol, CommoditySymbol, Int, [String], String,
        String)
    showw [PeriodicReportRow DisplayName MixedAmount]
    eitems)
          MixedAmount -> String
    showMixedAmountDebug (PeriodicReportRow () MixedAmount -> MixedAmount
    forall a b. PeriodicReportRow a b -> b
    prrTotal PeriodicReportRow () MixedAmount
    atotal) String -> String -> IO ()
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> IO ()
    @?= MixedAmount -> String
    showMixedAmountDebug MixedAmount
    etotal -- we only check the sum of the totals
      in
       String -> [TestTree] -> TestTree
    testGroup String
    "multiBalanceReport" [
          String -> IO () -> TestTree
    testCase String
    "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` ([], MixedAmount
    nullmixedamt)

         ,String -> IO () -> TestTree
    testCase String
    "with -H on a populated period"  (IO () -> TestTree) -> IO () -> TestTree
    forall a b. (a -> b) -> a -> b
    $
          (ReportSpec
    defreportspec{_rsReportOpts=defreportopts{period_= PeriodBetween (fromGregorian 2008 1 1) (fromGregorian 2008 1 2), balanceaccum_=Historical}}, 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 (CommoditySymbol -> DisplayName
    flatDisplayName CommoditySymbol
    "assets:bank:checking") [Amount -> MixedAmount
    mixedAmount (Amount -> MixedAmount) -> Amount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd Quantity
    1]    (Amount -> MixedAmount
    mixedAmount (Amount -> MixedAmount) -> Amount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd Quantity
    1)    (Amount -> MixedAmount
    mixedAmount Amount
    amt0{aquantity=1})
            , DisplayName
    -> [MixedAmount]
    -> MixedAmount
    -> MixedAmount
    -> PeriodicReportRow DisplayName MixedAmount
    forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
    PeriodicReportRow (CommoditySymbol -> DisplayName
    flatDisplayName CommoditySymbol
    "income:salary")        [Amount -> MixedAmount
    mixedAmount (Amount -> MixedAmount) -> Amount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd (-Quantity
    1)] (Amount -> MixedAmount
    mixedAmount (Amount -> MixedAmount) -> Amount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd (-Quantity
    1)) (Amount -> MixedAmount
    mixedAmount Amount
    amt0{aquantity=(-1)})
            ],
            Amount -> MixedAmount
    mixedAmount (Amount -> MixedAmount) -> Amount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd Quantity
    0)

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

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