    {-# LANGUAGE OverloadedStrings, RecordWildCards, FlexibleInstances #-}
    {-|

    An account-centric transactions report.

    -}

    module Hledger.Reports.AccountTransactionsReport (
      AccountTransactionsReport,
      AccountTransactionsReportItem,
      accountTransactionsReport,
      accountTransactionsReportItems,
      transactionRegisterDate,
      tests_AccountTransactionsReport
    )
    where

    import Data.List
    import Data.Ord
    import Data.Maybe
    import qualified Data.Text as T
    import Data.Time.Calendar

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

    -- | An account transactions report represents transactions affecting
    -- a particular account (or possibly several accounts, but we don't
    -- use that). It is used eg by hledger-ui's and hledger-web's register
    -- view, and hledger's aregister report, where we want to show one row
    -- per transaction, in the context of the current account. Report
    -- items consist of:
    --
    -- - the transaction, unmodified
    --
    -- - the transaction as seen in the context of the current account and query,
    --   which means:
    --
    --   - the transaction date is set to the "transaction context date",
    --     which can be different from the transaction's general date:
    --     if postings to the current account (and matched by the report query)
    --     have their own dates, it's the earliest of these dates.
    --
    --   - the transaction's postings are filtered, excluding any which are not
    --     matched by the report query
    --
    -- - a text description of the other account(s) posted to/from
    --
    -- - a flag indicating whether there's more than one other account involved
    --
    -- - the total increase/decrease to the current account
    --
    -- - the report transactions' running total after this transaction;
    --   or if historical balance is requested (-H), the historical running total.
    --   The historical running total includes transactions from before the
    --   report start date if one is specified, filtered by the report query.
    --   The historical running total may or may not be the account's historical
    --   running balance, depending on the report query.
    --
    -- Items are sorted by transaction register date (the earliest date the transaction
    -- posts to the current account), most recent first.
    -- Reporting intervals are currently ignored.
    --
    type AccountTransactionsReport =
      (String                          -- label for the balance column, eg "balance" or "total"
      ,[AccountTransactionsReportItem] -- line items, one per transaction
      )

    type AccountTransactionsReportItem =
      (
       Transaction -- the transaction, unmodified
      ,Transaction -- the transaction, as seen from the current account
      ,Bool        -- is this a split (more than one posting to other accounts) ?
      ,String      -- a display string describing the other account(s), if any
      ,MixedAmount -- the amount posted to the current account(s) (or total amount posted)
      ,MixedAmount -- the register's running total or the current account(s)'s historical balance, after this transaction
      )

    totallabel :: String
    totallabel   = "Period Total"
    balancelabel :: String
    balancelabel = "Historical Total"

    accountTransactionsReport :: ReportOpts -> Journal -> Query -> Query -> AccountTransactionsReport
    accountTransactionsReport :: ReportOpts
    -> Journal -> Query -> Query -> AccountTransactionsReport
    accountTransactionsReport ropts :: ReportOpts
    ropts j :: Journal
    j reportq :: Query
    reportq thisacctq :: Query
    thisacctq = (String
    label, [AccountTransactionsReportItem]
    items)
      where
        -- a depth limit should not affect the account transactions report
        -- seems unnecessary for some reason XXX
        reportq' :: Query
    reportq' = -- filterQuery (not . queryIsDepth)
                   Query
    reportq

        -- get all transactions
        ts1 :: [Transaction]
    ts1 =
          -- ptraceAtWith 5 (("ts1:\n"++).pshowTransactions) $
          Journal -> [Transaction]
    jtxns Journal
    j

        -- apply any cur:SYM filters in reportq'
        symq :: Query
    symq  = (Query -> Bool) -> Query -> Query
    filterQuery Query -> Bool
    queryIsSym Query
    reportq'
        ts2 :: [Transaction]
    ts2 =
          Int -> ([Transaction] -> String) -> [Transaction] -> [Transaction]
    forall a. Show a => Int -> (a -> String) -> a -> a
    ptraceAtWith 5 (("ts2:\n"String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++)(String -> String)
    -> ([Transaction] -> String) -> [Transaction] -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .[Transaction] -> String
    pshowTransactions) ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $
          (if Query -> Bool
    queryIsNull Query
    symq then [Transaction] -> [Transaction]
    forall a. a -> a
    id else (Transaction -> Transaction) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> [a] -> [b]
    map (Query -> Transaction -> Transaction
    filterTransactionAmounts Query
    symq)) [Transaction]
    ts1

        -- keep just the transactions affecting this account (via possibly realness or status-filtered postings)
        realq :: Query
    realq = (Query -> Bool) -> Query -> Query
    filterQuery Query -> Bool
    queryIsReal Query
    reportq'
        statusq :: Query
    statusq = (Query -> Bool) -> Query -> Query
    filterQuery Query -> Bool
    queryIsStatus Query
    reportq'
        ts3 :: [Transaction]
    ts3 =
          Int -> String -> [Transaction] -> [Transaction]
    forall a. Int -> String -> a -> a
    traceAt 3 ("thisacctq: "String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++Query -> String
    forall a. Show a => a -> String
    show Query
    thisacctq) ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $
          Int -> ([Transaction] -> String) -> [Transaction] -> [Transaction]
    forall a. Show a => Int -> (a -> String) -> a -> a
    ptraceAtWith 5 (("ts3:\n"String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++)(String -> String)
    -> ([Transaction] -> String) -> [Transaction] -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .[Transaction] -> String
    pshowTransactions) ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $
          (Transaction -> Bool) -> [Transaction] -> [Transaction]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Query -> Transaction -> Bool
    matchesTransaction Query
    thisacctq (Transaction -> Bool)
    -> (Transaction -> Transaction) -> Transaction -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Query -> Transaction -> Transaction
    filterTransactionPostings ([Query] -> Query
    And [Query
    realq, Query
    statusq])) [Transaction]
    ts2

        -- maybe convert these transactions to cost or value
        -- PARTIAL:
        prices :: PriceOracle
    prices = Bool -> Journal -> PriceOracle
    journalPriceOracle (ReportOpts -> Bool
    infer_value_ ReportOpts
    ropts) Journal
    j
        styles :: Map CommoditySymbol AmountStyle
    styles = Journal -> Map CommoditySymbol AmountStyle
    journalCommodityStyles Journal
    j
        periodlast :: Day
    periodlast =
          Day -> Maybe Day -> Day
    forall a. a -> Maybe a -> a
    fromMaybe (String -> Day
    forall a. String -> a
    error' "journalApplyValuation: expected a non-empty journal") (Maybe Day -> Day) -> Maybe Day -> Day
    forall a b. (a -> b) -> a -> b
    $ -- XXX shouldn't happen
          ReportOpts -> Journal -> Maybe Day
    reportPeriodOrJournalLastDay ReportOpts
    ropts Journal
    j
        mreportlast :: Maybe Day
    mreportlast = ReportOpts -> Maybe Day
    reportPeriodLastDay ReportOpts
    ropts
        today :: Day
    today = Day -> Maybe Day -> Day
    forall a. a -> Maybe a -> a
    fromMaybe (String -> Day
    forall a. String -> a
    error' "journalApplyValuation: could not pick a valuation date, ReportOpts today_ is unset") (Maybe Day -> Day) -> Maybe Day -> Day
    forall a b. (a -> b) -> a -> b
    $ ReportOpts -> Maybe Day
    today_ ReportOpts
    ropts -- XXX shouldn't happen
        multiperiod :: Bool
    multiperiod = ReportOpts -> Interval
    interval_ ReportOpts
    ropts Interval -> Interval -> Bool
    forall a. Eq a => a -> a -> Bool
    /= Interval
    NoInterval
        tval :: Transaction -> Transaction
    tval = case ReportOpts -> Maybe ValuationType
    value_ ReportOpts
    ropts of
                 Just v :: ValuationType
    v  -> \t :: Transaction
    t -> PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Day
    -> Maybe Day
    -> Day
    -> Bool
    -> Transaction
    -> ValuationType
    -> Transaction
    transactionApplyValuation PriceOracle
    prices Map CommoditySymbol AmountStyle
    styles Day
    periodlast Maybe Day
    mreportlast Day
    today Bool
    multiperiod Transaction
    t ValuationType
    v
                 Nothing -> Transaction -> Transaction
    forall a. a -> a
    id
        ts4 :: [Transaction]
    ts4 =
          Int -> ([Transaction] -> String) -> [Transaction] -> [Transaction]
    forall a. Show a => Int -> (a -> String) -> a -> a
    ptraceAtWith 5 (("ts4:\n"String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++)(String -> String)
    -> ([Transaction] -> String) -> [Transaction] -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .[Transaction] -> String
    pshowTransactions) ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $
          (Transaction -> Transaction) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> [a] -> [b]
    map Transaction -> Transaction
    tval [Transaction]
    ts3

        -- sort by the transaction's register date, for accurate starting balance
        -- these are not yet filtered by tdate, we want to search them all for priorps
        ts5 :: [Transaction]
    ts5 =
          Int -> ([Transaction] -> String) -> [Transaction] -> [Transaction]
    forall a. Show a => Int -> (a -> String) -> a -> a
    ptraceAtWith 5 (("ts5:\n"String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++)(String -> String)
    -> ([Transaction] -> String) -> [Transaction] -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .[Transaction] -> String
    pshowTransactions) ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $
          (Transaction -> Transaction -> Ordering)
    -> [Transaction] -> [Transaction]
    forall a. (a -> a -> Ordering) -> [a] -> [a]
    sortBy ((Transaction -> Day) -> Transaction -> Transaction -> Ordering
    forall a b. Ord a => (b -> a) -> b -> b -> Ordering
    comparing (Query -> Query -> Transaction -> Day
    transactionRegisterDate Query
    reportq' Query
    thisacctq)) [Transaction]
    ts4

        (startbal :: MixedAmount
    startbal,label :: String
    label)
          | ReportOpts -> BalanceType
    balancetype_ ReportOpts
    ropts BalanceType -> BalanceType -> Bool
    forall a. Eq a => a -> a -> Bool
    == BalanceType
    HistoricalBalance = ([Posting] -> MixedAmount
    sumPostings [Posting]
    priorps, String
    balancelabel)
          | Bool
    otherwise                               = (MixedAmount
    nullmixedamt,        String
    totallabel)
          where
            priorps :: [Posting]
    priorps = String -> [Posting] -> [Posting]
    forall a. Show a => String -> a -> a
    dbg5 "priorps" ([Posting] -> [Posting]) -> [Posting] -> [Posting]
    forall a b. (a -> b) -> a -> b
    $
                      (Posting -> Bool) -> [Posting] -> [Posting]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Query -> Posting -> Bool
    matchesPosting
                              (String -> Query -> Query
    forall a. Show a => String -> a -> a
    dbg5 "priorq" (Query -> Query) -> Query -> Query
    forall a b. (a -> b) -> a -> b
    $
                               [Query] -> Query
    And [Query
    thisacctq, Query
    tostartdateq, Query
    datelessreportq]))
                             ([Posting] -> [Posting]) -> [Posting] -> [Posting]
    forall a b. (a -> b) -> a -> b
    $ [Transaction] -> [Posting]
    transactionsPostings [Transaction]
    ts5
            tostartdateq :: Query
    tostartdateq =
              case Maybe Day
    mstartdate of
                Just _  -> DateSpan -> Query
    Date (Maybe Day -> Maybe Day -> DateSpan
    DateSpan Maybe Day
    forall a. Maybe a
    Nothing Maybe Day
    mstartdate)
                Nothing -> Query
    None  -- no start date specified, there are no prior postings
            mstartdate :: Maybe Day
    mstartdate = Bool -> Query -> Maybe Day
    queryStartDate (ReportOpts -> Bool
    date2_ ReportOpts
    ropts) Query
    reportq'
            datelessreportq :: Query
    datelessreportq = (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
    reportq'

        -- accountTransactionsReportItem will keep transactions of any date which have any posting inside the report period.
        -- Should we also require that transaction date is inside the report period ?
        -- Should we be filtering by reportq here to apply other query terms (?)
        -- Make it an option for now.
        filtertxns :: Bool
    filtertxns = ReportOpts -> Bool
    txn_dates_ ReportOpts
    ropts

        items :: [AccountTransactionsReportItem]
    items = [AccountTransactionsReportItem] -> [AccountTransactionsReportItem]
    forall a. [a] -> [a]
    reverse ([AccountTransactionsReportItem]
     -> [AccountTransactionsReportItem])
    -> [AccountTransactionsReportItem]
    -> [AccountTransactionsReportItem]
    forall a b. (a -> b) -> a -> b
    $
                Query
    -> Query
    -> MixedAmount
    -> (MixedAmount -> MixedAmount)
    -> [Transaction]
    -> [AccountTransactionsReportItem]
    accountTransactionsReportItems Query
    reportq' Query
    thisacctq MixedAmount
    startbal MixedAmount -> MixedAmount
    forall a. Num a => a -> a
    negate ([Transaction] -> [AccountTransactionsReportItem])
    -> [Transaction] -> [AccountTransactionsReportItem]
    forall a b. (a -> b) -> a -> b
    $
                (if Bool
    filtertxns then (Transaction -> Bool) -> [Transaction] -> [Transaction]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Query
    reportq' Query -> Transaction -> Bool
    `matchesTransaction`) else [Transaction] -> [Transaction]
    forall a. a -> a
    id) ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $
                [Transaction]
    ts5

    pshowTransactions :: [Transaction] -> String
    pshowTransactions :: [Transaction] -> String
    pshowTransactions = [String] -> String
    forall a. Show a => a -> String
    pshow ([String] -> String)
    -> ([Transaction] -> [String]) -> [Transaction] -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Transaction -> String) -> [Transaction] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map (\t :: Transaction
    t -> [String] -> String
    unwords [Day -> String
    forall a. Show a => a -> String
    show (Day -> String) -> Day -> String
    forall a b. (a -> b) -> a -> b
    $ Transaction -> Day
    tdate Transaction
    t, CommoditySymbol -> String
    T.unpack (CommoditySymbol -> String) -> CommoditySymbol -> String
    forall a b. (a -> b) -> a -> b
    $ Transaction -> CommoditySymbol
    tdescription Transaction
    t])

    -- | Generate transactions report items from a list of transactions,
    -- using the provided user-specified report query, a query specifying
    -- which account to use as the focus, a starting balance, a sign-setting
    -- function and a balance-summing function. Or with a None current account
    -- query, this can also be used for the transactionsReport.
    accountTransactionsReportItems :: Query -> Query -> MixedAmount -> (MixedAmount -> MixedAmount) -> [Transaction] -> [AccountTransactionsReportItem]
    accountTransactionsReportItems :: Query
    -> Query
    -> MixedAmount
    -> (MixedAmount -> MixedAmount)
    -> [Transaction]
    -> [AccountTransactionsReportItem]
    accountTransactionsReportItems reportq :: Query
    reportq thisacctq :: Query
    thisacctq bal :: MixedAmount
    bal signfn :: MixedAmount -> MixedAmount
    signfn =
        [Maybe AccountTransactionsReportItem]
    -> [AccountTransactionsReportItem]
    forall a. [Maybe a] -> [a]
    catMaybes ([Maybe AccountTransactionsReportItem]
     -> [AccountTransactionsReportItem])
    -> ([Transaction] -> [Maybe AccountTransactionsReportItem])
    -> [Transaction]
    -> [AccountTransactionsReportItem]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (MixedAmount, [Maybe AccountTransactionsReportItem])
    -> [Maybe AccountTransactionsReportItem]
    forall a b. (a, b) -> b
    snd ((MixedAmount, [Maybe AccountTransactionsReportItem])
     -> [Maybe AccountTransactionsReportItem])
    -> ([Transaction]
        -> (MixedAmount, [Maybe AccountTransactionsReportItem]))
    -> [Transaction]
    -> [Maybe AccountTransactionsReportItem]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .
        (MixedAmount
     -> Transaction
     -> (MixedAmount, Maybe AccountTransactionsReportItem))
    -> MixedAmount
    -> [Transaction]
    -> (MixedAmount, [Maybe AccountTransactionsReportItem])
    forall (t :: * -> *) a b c.
    Traversable t =>
    (a -> b -> (a, c)) -> a -> t b -> (a, t c)
    mapAccumL (Query
    -> Query
    -> (MixedAmount -> MixedAmount)
    -> MixedAmount
    -> Transaction
    -> (MixedAmount, Maybe AccountTransactionsReportItem)
    accountTransactionsReportItem Query
    reportq Query
    thisacctq MixedAmount -> MixedAmount
    signfn) MixedAmount
    bal

    accountTransactionsReportItem :: Query -> Query -> (MixedAmount -> MixedAmount) -> MixedAmount -> Transaction -> (MixedAmount, Maybe AccountTransactionsReportItem)
    accountTransactionsReportItem :: Query
    -> Query
    -> (MixedAmount -> MixedAmount)
    -> MixedAmount
    -> Transaction
    -> (MixedAmount, Maybe AccountTransactionsReportItem)
    accountTransactionsReportItem reportq :: Query
    reportq thisacctq :: Query
    thisacctq signfn :: MixedAmount -> MixedAmount
    signfn bal :: MixedAmount
    bal torig :: Transaction
    torig = (MixedAmount, Maybe AccountTransactionsReportItem)
    balItem
        -- 201403: This is used for both accountTransactionsReport and transactionsReport, which makes it a bit overcomplicated
        -- 201407: I've lost my grip on this, let's just hope for the best
        -- 201606: we now calculate change and balance from filtered postings, check this still works well for all callers XXX
        where
          tfiltered :: Transaction
    tfiltered@Transaction{tpostings :: Transaction -> [Posting]
    tpostings=[Posting]
    reportps} = Query -> Transaction -> Transaction
    filterTransactionPostings Query
    reportq Transaction
    torig
          tacct :: Transaction
    tacct = Transaction
    tfiltered{tdate :: Day
    tdate=Query -> Query -> Transaction -> Day
    transactionRegisterDate Query
    reportq Query
    thisacctq Transaction
    tfiltered}
          balItem :: (MixedAmount, Maybe AccountTransactionsReportItem)
    balItem = case [Posting]
    reportps of
               [] -> (MixedAmount
    bal, Maybe AccountTransactionsReportItem
    forall a. Maybe a
    Nothing)  -- no matched postings in this transaction, skip it
               _  -> (MixedAmount
    b, AccountTransactionsReportItem
    -> Maybe AccountTransactionsReportItem
    forall a. a -> Maybe a
    Just (Transaction
    torig, Transaction
    tacct, Int
    numotheraccts Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    > 1, String
    otheracctstr, MixedAmount
    a, MixedAmount
    b))
                     where
                      (thisacctps :: [Posting]
    thisacctps, otheracctps :: [Posting]
    otheracctps) = (Posting -> Bool) -> [Posting] -> ([Posting], [Posting])
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    partition (Query -> Posting -> Bool
    matchesPosting Query
    thisacctq) [Posting]
    reportps
                      numotheraccts :: Int
    numotheraccts = [CommoditySymbol] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length ([CommoditySymbol] -> Int) -> [CommoditySymbol] -> Int
    forall a b. (a -> b) -> a -> b
    $ [CommoditySymbol] -> [CommoditySymbol]
    forall a. Eq a => [a] -> [a]
    nub ([CommoditySymbol] -> [CommoditySymbol])
    -> [CommoditySymbol] -> [CommoditySymbol]
    forall a b. (a -> b) -> a -> b
    $ (Posting -> CommoditySymbol) -> [Posting] -> [CommoditySymbol]
    forall a b. (a -> b) -> [a] -> [b]
    map Posting -> CommoditySymbol
    paccount [Posting]
    otheracctps
                      otheracctstr :: String
    otheracctstr | Query
    thisacctq Query -> Query -> Bool
    forall a. Eq a => a -> a -> Bool
    == Query
    None  = [Posting] -> String
    summarisePostingAccounts [Posting]
    reportps     -- no current account ? summarise all matched postings
                                   | Int
    numotheraccts Int -> Int -> Bool
    forall a. Eq a => a -> a -> Bool
    == 0 = [Posting] -> String
    summarisePostingAccounts [Posting]
    thisacctps   -- only postings to current account ? summarise those
                                   | Bool
    otherwise          = [Posting] -> String
    summarisePostingAccounts [Posting]
    otheracctps  -- summarise matched postings to other account(s)
                      a :: MixedAmount
    a = MixedAmount -> MixedAmount
    signfn (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> MixedAmount
    forall a. Num a => a -> a
    negate (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ [MixedAmount] -> MixedAmount
    forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
    sum ([MixedAmount] -> MixedAmount) -> [MixedAmount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Posting -> MixedAmount) -> [Posting] -> [MixedAmount]
    forall a b. (a -> b) -> [a] -> [b]
    map Posting -> MixedAmount
    pamount [Posting]
    thisacctps
                      b :: MixedAmount
    b = MixedAmount
    bal MixedAmount -> MixedAmount -> MixedAmount
    forall a. Num a => a -> a -> a
    + MixedAmount
    a

    -- | What is the transaction's date in the context of a particular account
    -- (specified with a query) and report query, as in an account register ?
    -- It's normally the transaction's general date, but if any posting(s)
    -- matched by the report query and affecting the matched account(s) have
    -- their own earlier dates, it's the earliest of these dates.
    -- Secondary transaction/posting dates are ignored.
    transactionRegisterDate :: Query -> Query -> Transaction -> Day
    transactionRegisterDate :: Query -> Query -> Transaction -> Day
    transactionRegisterDate reportq :: Query
    reportq thisacctq :: Query
    thisacctq t :: Transaction
    t
      | [Posting] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [Posting]
    thisacctps = Transaction -> Day
    tdate Transaction
    t
      | Bool
    otherwise       = [Day] -> Day
    forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
    minimum ([Day] -> Day) -> [Day] -> Day
    forall a b. (a -> b) -> a -> b
    $ (Posting -> Day) -> [Posting] -> [Day]
    forall a b. (a -> b) -> [a] -> [b]
    map Posting -> Day
    postingDate [Posting]
    thisacctps
      where
        reportps :: [Posting]
    reportps   = Transaction -> [Posting]
    tpostings (Transaction -> [Posting]) -> Transaction -> [Posting]
    forall a b. (a -> b) -> a -> b
    $ Query -> Transaction -> Transaction
    filterTransactionPostings Query
    reportq Transaction
    t
        thisacctps :: [Posting]
    thisacctps = (Posting -> Bool) -> [Posting] -> [Posting]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Query -> Posting -> Bool
    matchesPosting Query
    thisacctq) [Posting]
    reportps

    -- -- | Generate a short readable summary of some postings, like
    -- -- "from (negatives) to (positives)".
    -- summarisePostings :: [Posting] -> String
    -- summarisePostings ps =
    --     case (summarisePostingAccounts froms, summarisePostingAccounts tos) of
    --        ("",t) -> "to "++t
    --        (f,"") -> "from "++f
    --        (f,t)  -> "from "++f++" to "++t
    --     where
    --       (froms,tos) = partition (fromMaybe False . isNegativeMixedAmount . pamount) ps

    -- | Generate a simplified summary of some postings' accounts.
    -- To reduce noise, if there are both real and virtual postings, show only the real ones.
    summarisePostingAccounts :: [Posting] -> String
    summarisePostingAccounts :: [Posting] -> String
    summarisePostingAccounts ps :: [Posting]
    ps =
      (String -> [String] -> String
    forall a. [a] -> [[a]] -> [a]
    intercalate ", " ([String] -> String)
    -> ([Posting] -> [String]) -> [Posting] -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (CommoditySymbol -> String) -> [CommoditySymbol] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map (CommoditySymbol -> String
    T.unpack (CommoditySymbol -> String)
    -> (CommoditySymbol -> CommoditySymbol)
    -> CommoditySymbol
    -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . CommoditySymbol -> CommoditySymbol
    accountSummarisedName) ([CommoditySymbol] -> [String])
    -> ([Posting] -> [CommoditySymbol]) -> [Posting] -> [String]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [CommoditySymbol] -> [CommoditySymbol]
    forall a. Eq a => [a] -> [a]
    nub ([CommoditySymbol] -> [CommoditySymbol])
    -> ([Posting] -> [CommoditySymbol])
    -> [Posting]
    -> [CommoditySymbol]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Posting -> CommoditySymbol) -> [Posting] -> [CommoditySymbol]
    forall a b. (a -> b) -> [a] -> [b]
    map Posting -> CommoditySymbol
    paccount) [Posting]
    displayps -- XXX pack
      where
        realps :: [Posting]
    realps = (Posting -> Bool) -> [Posting] -> [Posting]
    forall a. (a -> Bool) -> [a] -> [a]
    filter Posting -> Bool
    isReal [Posting]
    ps
        displayps :: [Posting]
    displayps | [Posting] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [Posting]
    realps = [Posting]
    ps
                  | Bool
    otherwise   = [Posting]
    realps

    -- tests

    tests_AccountTransactionsReport :: TestTree
    tests_AccountTransactionsReport = String -> [TestTree] -> TestTree
    tests "AccountTransactionsReport" [
     ]