{-|

Balance report, used by the balance command.

-}

{-# LANGUAGE FlexibleInstances   #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Hledger.Reports.BalanceReport (
  BalanceReport,
  BalanceReportItem,
  balanceReport,
  flatShowsExclusiveBalance,

  -- * Tests
  tests_BalanceReport
)
where

import Data.Time.Calendar

import Hledger.Data
import Hledger.Query
import Hledger.Utils
import Hledger.Reports.MultiBalanceReport (multiBalanceReport)
import Hledger.Reports.ReportOptions
import Hledger.Reports.ReportTypes


-- | A simple balance report. It has:
--
-- 1. a list of items, one per account, each containing:
--
--   * the full account name
--
--   * the Ledger-style elided short account name
--     (the leaf account name, prefixed by any boring parents immediately above);
--     or with --flat, the full account name again
--
--   * the number of indentation steps for rendering a Ledger-style account tree,
--     taking into account elided boring parents, --no-elide and --flat
--
--   * an amount
--
-- 2. the total of all amounts
--
type BalanceReport = ([BalanceReportItem], MixedAmount)
type BalanceReportItem = (AccountName, AccountName, Int, MixedAmount)

-- | When true (the default), this makes balance --flat reports and their implementation clearer.
-- Single/multi-col balance reports currently aren't all correct if this is false.
flatShowsExclusiveBalance :: Bool
flatShowsExclusiveBalance    = Bool
True

-- | Enabling this makes balance --flat --empty also show parent accounts without postings,
-- in addition to those with postings and a zero balance. Disabling it shows only the latter.
-- No longer supported, but leave this here for a bit.
-- flatShowsPostinglessAccounts = True

-- | Generate a simple balance report, containing the matched accounts and
-- their balances (change of balance) during the specified period.
-- If the normalbalance_ option is set, it adjusts the sorting and sign of
-- amounts (see ReportOpts and CompoundBalanceCommand).
balanceReport :: ReportSpec -> Journal -> BalanceReport
balanceReport :: ReportSpec -> Journal -> BalanceReport
balanceReport ReportSpec
rspec Journal
j = ([(AccountName, AccountName, Int, MixedAmount)]
rows, MixedAmount
total)
  where
    report :: MultiBalanceReport
report = ReportSpec -> Journal -> MultiBalanceReport
multiBalanceReport ReportSpec
rspec Journal
j
    rows :: [(AccountName, AccountName, Int, MixedAmount)]
rows = [( forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName PeriodicReportRow DisplayName MixedAmount
row
            , forall a. PeriodicReportRow DisplayName a -> AccountName
prrDisplayName PeriodicReportRow DisplayName MixedAmount
row
            , forall a. PeriodicReportRow DisplayName a -> Int
prrDepth PeriodicReportRow DisplayName MixedAmount
row forall a. Num a => a -> a -> a
- Int
1  -- BalanceReport uses 0-based account depths
            , forall a b. PeriodicReportRow a b -> b
prrTotal PeriodicReportRow DisplayName MixedAmount
row
            ) | PeriodicReportRow DisplayName MixedAmount
row <- forall a b. PeriodicReport a b -> [PeriodicReportRow a b]
prRows MultiBalanceReport
report]
    total :: MixedAmount
total = forall a b. PeriodicReportRow a b -> b
prrTotal forall a b. (a -> b) -> a -> b
$ forall a b. PeriodicReport a b -> PeriodicReportRow () b
prTotals MultiBalanceReport
report


-- tests

Right Journal
samplejournal2 =
  BalancingOpts -> Journal -> Either String Journal
journalBalanceTransactions BalancingOpts
defbalancingopts
    Journal
nulljournal{
      jtxns :: [Transaction]
jtxns = [
        Transaction -> Transaction
txnTieKnot Transaction{
          tindex :: Integer
tindex=Integer
0,
          tsourcepos :: (SourcePos, SourcePos)
tsourcepos=(SourcePos, SourcePos)
nullsourcepos,
          tdate :: Day
tdate=Integer -> Int -> Int -> Day
fromGregorian Integer
2008 Int
01 Int
01,
          tdate2 :: Maybe Day
tdate2=forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2009 Int
01 Int
01,
          tstatus :: Status
tstatus=Status
Unmarked,
          tcode :: AccountName
tcode=AccountName
"",
          tdescription :: AccountName
tdescription=AccountName
"income",
          tcomment :: AccountName
tcomment=AccountName
"",
          ttags :: [Tag]
ttags=[],
          tpostings :: [Posting]
tpostings=
            [Posting
posting {paccount :: AccountName
paccount=AccountName
"assets:bank:checking", pamount :: MixedAmount
pamount=Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1)}
            ,Posting
posting {paccount :: AccountName
paccount=AccountName
"income:salary", pamount :: MixedAmount
pamount=MixedAmount
missingmixedamt}
            ],
          tprecedingcomment :: AccountName
tprecedingcomment=AccountName
""
        }
      ]
    }

tests_BalanceReport :: TestTree
tests_BalanceReport = String -> [TestTree] -> TestTree
testGroup String
"BalanceReport" [

  let
    (ReportSpec
rspec,Journal
journal) gives :: (ReportSpec, Journal) -> BalanceReport -> IO ()
`gives` BalanceReport
r = do
      let opts' :: ReportSpec
opts' = ReportSpec
rspec{_rsQuery :: Query
_rsQuery=[Query] -> Query
And [ReportOpts -> Query
queryFromFlags forall a b. (a -> b) -> a -> b
$ ReportSpec -> ReportOpts
_rsReportOpts ReportSpec
rspec, ReportSpec -> Query
_rsQuery ReportSpec
rspec]}
          ([(AccountName, AccountName, Int, MixedAmount)]
eitems, MixedAmount
etotal) = BalanceReport
r
          ([(AccountName, AccountName, Int, MixedAmount)]
aitems, MixedAmount
atotal) = ReportSpec -> Journal -> BalanceReport
balanceReport ReportSpec
opts' Journal
journal
          showw :: (a, b, c, MixedAmount) -> (a, b, c, String)
showw (a
acct,b
acct',c
indent,MixedAmount
amt) = (a
acct, b
acct', c
indent, MixedAmount -> String
showMixedAmountDebug MixedAmount
amt)
      (forall a b. (a -> b) -> [a] -> [b]
map forall {a} {b} {c}. (a, b, c, MixedAmount) -> (a, b, c, String)
showw [(AccountName, AccountName, Int, MixedAmount)]
aitems) forall a. (Eq a, Show a, HasCallStack) => a -> a -> IO ()
@?= (forall a b. (a -> b) -> [a] -> [b]
map forall {a} {b} {c}. (a, b, c, MixedAmount) -> (a, b, c, String)
showw [(AccountName, AccountName, Int, MixedAmount)]
eitems)
      (MixedAmount -> String
showMixedAmountDebug MixedAmount
atotal) forall a. (Eq a, Show a, HasCallStack) => a -> a -> IO ()
@?= (MixedAmount -> String
showMixedAmountDebug MixedAmount
etotal)
  in
    String -> [TestTree] -> TestTree
testGroup String
"balanceReport" [

     String -> IO () -> TestTree
testCase String
"no args, null journal" forall a b. (a -> b) -> a -> b
$
     (ReportSpec
defreportspec, Journal
nulljournal) (ReportSpec, Journal) -> BalanceReport -> IO ()
`gives` ([], MixedAmount
nullmixedamt)

    ,String -> IO () -> TestTree
testCase String
"no args, sample journal" forall a b. (a -> b) -> a -> b
$
     (ReportSpec
defreportspec, Journal
samplejournal) (ReportSpec, Journal) -> BalanceReport -> IO ()
`gives`
      ([
        (AccountName
"assets:bank:checking",AccountName
"assets:bank:checking",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"assets:bank:saving",AccountName
"assets:bank:saving",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"assets:cash",AccountName
"assets:cash",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
2)))
       ,(AccountName
"expenses:food",AccountName
"expenses:food",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"expenses:supplies",AccountName
"expenses:supplies",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"income:gifts",AccountName
"income:gifts",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1)))
       ,(AccountName
"income:salary",AccountName
"income:salary",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1)))
       ],
       Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
0))

    ,String -> IO () -> TestTree
testCase String
"with --tree" forall a b. (a -> b) -> a -> b
$
     (ReportSpec
defreportspec{_rsReportOpts :: ReportOpts
_rsReportOpts=ReportOpts
defreportopts{accountlistmode_ :: AccountListMode
accountlistmode_=AccountListMode
ALTree}}, Journal
samplejournal) (ReportSpec, Journal) -> BalanceReport -> IO ()
`gives`
      ([
        (AccountName
"assets",AccountName
"assets",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
0))
       ,(AccountName
"assets:bank",AccountName
"bank",Int
1, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
2))
       ,(AccountName
"assets:bank:checking",AccountName
"checking",Int
2, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"assets:bank:saving",AccountName
"saving",Int
2, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"assets:cash",AccountName
"cash",Int
1, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
2)))
       ,(AccountName
"expenses",AccountName
"expenses",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
2))
       ,(AccountName
"expenses:food",AccountName
"food",Int
1, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"expenses:supplies",AccountName
"supplies",Int
1, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"income",AccountName
"income",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
2)))
       ,(AccountName
"income:gifts",AccountName
"gifts",Int
1, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1)))
       ,(AccountName
"income:salary",AccountName
"salary",Int
1, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1)))
       ],
       Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
0))

    ,String -> IO () -> TestTree
testCase String
"with --depth=N" forall a b. (a -> b) -> a -> b
$
     (ReportSpec
defreportspec{_rsReportOpts :: ReportOpts
_rsReportOpts=ReportOpts
defreportopts{depth_ :: Maybe Int
depth_=forall a. a -> Maybe a
Just Int
1}}, Journal
samplejournal) (ReportSpec, Journal) -> BalanceReport -> IO ()
`gives`
      ([
       (AccountName
"expenses",    AccountName
"expenses",     Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
2))
       ,(AccountName
"income",      AccountName
"income",      Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
2)))
       ],
       Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
0))

    ,String -> IO () -> TestTree
testCase String
"with depth:N" forall a b. (a -> b) -> a -> b
$
     (ReportSpec
defreportspec{_rsQuery :: Query
_rsQuery=Int -> Query
Depth Int
1}, Journal
samplejournal) (ReportSpec, Journal) -> BalanceReport -> IO ()
`gives`
      ([
       (AccountName
"expenses",    AccountName
"expenses",     Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
2))
       ,(AccountName
"income",      AccountName
"income",      Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
2)))
       ],
       Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
0))

    ,String -> IO () -> TestTree
testCase String
"with date:" forall a b. (a -> b) -> a -> b
$
     (ReportSpec
defreportspec{_rsQuery :: Query
_rsQuery=DateSpan -> Query
Date forall a b. (a -> b) -> a -> b
$ Maybe EFDay -> Maybe EFDay -> DateSpan
DateSpan (forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Day -> EFDay
Exact forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2009 Int
01 Int
01) (forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Day -> EFDay
Exact forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2010 Int
01 Int
01)}, Journal
samplejournal2) (ReportSpec, Journal) -> BalanceReport -> IO ()
`gives`
      ([], MixedAmount
nullmixedamt)

    ,String -> IO () -> TestTree
testCase String
"with date2:" forall a b. (a -> b) -> a -> b
$
     (ReportSpec
defreportspec{_rsQuery :: Query
_rsQuery=DateSpan -> Query
Date2 forall a b. (a -> b) -> a -> b
$ Maybe EFDay -> Maybe EFDay -> DateSpan
DateSpan (forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Day -> EFDay
Exact forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2009 Int
01 Int
01) (forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Day -> EFDay
Exact forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2010 Int
01 Int
01)}, Journal
samplejournal2) (ReportSpec, Journal) -> BalanceReport -> IO ()
`gives`
      ([
        (AccountName
"assets:bank:checking",AccountName
"assets:bank:checking",Int
0,Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"income:salary",AccountName
"income:salary",Int
0,Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1)))
       ],
       Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
0))

    ,String -> IO () -> TestTree
testCase String
"with desc:" forall a b. (a -> b) -> a -> b
$
     (ReportSpec
defreportspec{_rsQuery :: Query
_rsQuery=Regexp -> Query
Desc forall a b. (a -> b) -> a -> b
$ AccountName -> Regexp
toRegexCI' AccountName
"income"}, Journal
samplejournal) (ReportSpec, Journal) -> BalanceReport -> IO ()
`gives`
      ([
        (AccountName
"assets:bank:checking",AccountName
"assets:bank:checking",Int
0,Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"income:salary",AccountName
"income:salary",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1)))
       ],
       Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
0))

    ,String -> IO () -> TestTree
testCase String
"with not:desc:" forall a b. (a -> b) -> a -> b
$
     (ReportSpec
defreportspec{_rsQuery :: Query
_rsQuery=Query -> Query
Not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Regexp -> Query
Desc forall a b. (a -> b) -> a -> b
$ AccountName -> Regexp
toRegexCI' AccountName
"income"}, Journal
samplejournal) (ReportSpec, Journal) -> BalanceReport -> IO ()
`gives`
      ([
        (AccountName
"assets:bank:saving",AccountName
"assets:bank:saving",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"assets:cash",AccountName
"assets:cash",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
2)))
       ,(AccountName
"expenses:food",AccountName
"expenses:food",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"expenses:supplies",AccountName
"expenses:supplies",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
       ,(AccountName
"income:gifts",AccountName
"income:gifts",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1)))
       ],
       Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
0))

    ,String -> IO () -> TestTree
testCase String
"with period on a populated period" forall a b. (a -> b) -> a -> b
$
      (ReportSpec
defreportspec{_rsReportOpts :: ReportOpts
_rsReportOpts=ReportOpts
defreportopts{period_ :: Period
period_= Day -> Day -> Period
PeriodBetween (Integer -> Int -> Int -> Day
fromGregorian Integer
2008 Int
1 Int
1) (Integer -> Int -> Int -> Day
fromGregorian Integer
2008 Int
1 Int
2)}}, Journal
samplejournal) (ReportSpec, Journal) -> BalanceReport -> IO ()
`gives`
       (
        [
         (AccountName
"assets:bank:checking",AccountName
"assets:bank:checking",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1))
        ,(AccountName
"income:salary",AccountName
"income:salary",Int
0, Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1)))
        ],
        Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
0))

     ,String -> IO () -> TestTree
testCase String
"with period on an unpopulated period" forall a b. (a -> b) -> a -> b
$
      (ReportSpec
defreportspec{_rsReportOpts :: ReportOpts
_rsReportOpts=ReportOpts
defreportopts{period_ :: Period
period_= Day -> Day -> Period
PeriodBetween (Integer -> Int -> Int -> Day
fromGregorian Integer
2008 Int
1 Int
2) (Integer -> Int -> Int -> Day
fromGregorian Integer
2008 Int
1 Int
3)}}, Journal
samplejournal) (ReportSpec, Journal) -> BalanceReport -> IO ()
`gives`
       ([], MixedAmount
nullmixedamt)



  {-
      ,testCase "accounts report with account pattern o" ~:
       defreportopts{patterns_=["o"]} `gives`
       ["                  $1  expenses:food"
       ,"                 $-2  income"
       ,"                 $-1    gifts"
       ,"                 $-1    salary"
       ,"--------------------"
       ,"                 $-1"
       ]

      ,testCase "accounts report with account pattern o and --depth 1" ~:
       defreportopts{patterns_=["o"],depth_=Just 1} `gives`
       ["                  $1  expenses"
       ,"                 $-2  income"
       ,"--------------------"
       ,"                 $-1"
       ]

      ,testCase "accounts report with account pattern a" ~:
       defreportopts{patterns_=["a"]} `gives`
       ["                 $-1  assets"
       ,"                  $1    bank:saving"
       ,"                 $-2    cash"
       ,"                 $-1  income:salary"
       ,"                  $1  liabilities:debts"
       ,"--------------------"
       ,"                 $-1"
       ]

      ,testCase "accounts report with account pattern e" ~:
       defreportopts{patterns_=["e"]} `gives`
       ["                 $-1  assets"
       ,"                  $1    bank:saving"
       ,"                 $-2    cash"
       ,"                  $2  expenses"
       ,"                  $1    food"
       ,"                  $1    supplies"
       ,"                 $-2  income"
       ,"                 $-1    gifts"
       ,"                 $-1    salary"
       ,"                  $1  liabilities:debts"
       ,"--------------------"
       ,"                   0"
       ]

      ,testCase "accounts report with unmatched parent of two matched subaccounts" ~:
       defreportopts{patterns_=["cash","saving"]} `gives`
       ["                 $-1  assets"
       ,"                  $1    bank:saving"
       ,"                 $-2    cash"
       ,"--------------------"
       ,"                 $-1"
       ]

      ,testCase "accounts report with multi-part account name" ~:
       defreportopts{patterns_=["expenses:food"]} `gives`
       ["                  $1  expenses:food"
       ,"--------------------"
       ,"                  $1"
       ]

      ,testCase "accounts report with negative account pattern" ~:
       defreportopts{patterns_=["not:assets"]} `gives`
       ["                  $2  expenses"
       ,"                  $1    food"
       ,"                  $1    supplies"
       ,"                 $-2  income"
       ,"                 $-1    gifts"
       ,"                 $-1    salary"
       ,"                  $1  liabilities:debts"
       ,"--------------------"
       ,"                  $1"
       ]

      ,testCase "accounts report negative account pattern always matches full name" ~:
       defreportopts{patterns_=["not:e"]} `gives`
       ["--------------------"
       ,"                   0"
       ]

      ,testCase "accounts report negative patterns affect totals" ~:
       defreportopts{patterns_=["expenses","not:food"]} `gives`
       ["                  $1  expenses:supplies"
       ,"--------------------"
       ,"                  $1"
       ]

      ,testCase "accounts report with -E shows zero-balance accounts" ~:
       defreportopts{patterns_=["assets"],empty_=True} `gives`
       ["                 $-1  assets"
       ,"                  $1    bank"
       ,"                   0      checking"
       ,"                  $1      saving"
       ,"                 $-2    cash"
       ,"--------------------"
       ,"                 $-1"
       ]

      ,testCase "accounts report with cost basis" $
         j <- (readJournal def Nothing $ unlines
                [""
                ,"2008/1/1 test           "
                ,"  a:b          10h @ $50"
                ,"  c:d                   "
                ]) >>= either error' return
         let j' = journalCanonicaliseAmounts $ journalToCost ToCost j -- enable cost basis adjustment
         balanceReportAsText defreportopts (balanceReport defreportopts Any j') `is`
           ["                $500  a:b"
           ,"               $-500  c:d"
           ,"--------------------"
           ,"                   0"
           ]
  -}
     ]

 ]