{-# LANGUAGE FlexibleInstances, RecordWildCards, ScopedTypeVariables, OverloadedStrings #-}
module Hledger.Reports.BalanceReport (
BalanceReport,
BalanceReportItem,
balanceReport,
flatShowsExclusiveBalance,
tests_BalanceReport
)
where
import Data.Time.Calendar
import Hledger.Data
import Hledger.Read (mamountp')
import Hledger.Query
import Hledger.Utils
import Hledger.Reports.MultiBalanceReport (multiBalanceReportWith)
import Hledger.Reports.ReportOptions
import Hledger.Reports.ReportTypes
type BalanceReport = ([BalanceReportItem], MixedAmount)
type BalanceReportItem = (AccountName, AccountName, Int, MixedAmount)
flatShowsExclusiveBalance :: Bool
flatShowsExclusiveBalance = Bool
True
balanceReport :: ReportOpts -> Query -> Journal -> BalanceReport
balanceReport :: ReportOpts -> Query -> Journal -> BalanceReport
balanceReport ropts :: ReportOpts
ropts q :: Query
q j :: Journal
j = ([(AccountName, AccountName, Int, MixedAmount)]
rows, MixedAmount
total)
where
report :: MultiBalanceReport
report = ReportOpts -> Query -> Journal -> PriceOracle -> MultiBalanceReport
multiBalanceReportWith ReportOpts
ropts Query
q Journal
j (Bool -> Journal -> PriceOracle
journalPriceOracle (ReportOpts -> Bool
infer_value_ ReportOpts
ropts) Journal
j)
rows :: [(AccountName, AccountName, Int, MixedAmount)]
rows = [( PeriodicReportRow DisplayName MixedAmount -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName PeriodicReportRow DisplayName MixedAmount
row
, PeriodicReportRow DisplayName MixedAmount -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrDisplayName PeriodicReportRow DisplayName MixedAmount
row
, PeriodicReportRow DisplayName MixedAmount -> Int
forall a. PeriodicReportRow DisplayName a -> Int
prrDepth PeriodicReportRow DisplayName MixedAmount
row Int -> Int -> Int
forall a. Num a => a -> a -> a
- 1
, PeriodicReportRow DisplayName MixedAmount -> MixedAmount
forall a b. PeriodicReportRow a b -> b
prrTotal PeriodicReportRow DisplayName MixedAmount
row
) | PeriodicReportRow DisplayName MixedAmount
row <- MultiBalanceReport -> [PeriodicReportRow DisplayName MixedAmount]
forall a b. PeriodicReport a b -> [PeriodicReportRow a b]
prRows MultiBalanceReport
report]
total :: MixedAmount
total = PeriodicReportRow () MixedAmount -> MixedAmount
forall a b. PeriodicReportRow a b -> b
prrTotal (PeriodicReportRow () MixedAmount -> MixedAmount)
-> PeriodicReportRow () MixedAmount -> MixedAmount
forall a b. (a -> b) -> a -> b
$ MultiBalanceReport -> PeriodicReportRow () MixedAmount
forall a b. PeriodicReport a b -> PeriodicReportRow () b
prTotals MultiBalanceReport
report
Right samplejournal2 :: Journal
samplejournal2 =
Bool -> Journal -> Either String Journal
journalBalanceTransactions Bool
False
Journal
nulljournal{
jtxns :: [Transaction]
jtxns = [
Transaction -> Transaction
txnTieKnot Transaction :: Integer
-> AccountName
-> GenericSourcePos
-> Day
-> Maybe Day
-> Status
-> AccountName
-> AccountName
-> AccountName
-> [Tag]
-> [Posting]
-> Transaction
Transaction{
tindex :: Integer
tindex=0,
tsourcepos :: GenericSourcePos
tsourcepos=GenericSourcePos
nullsourcepos,
tdate :: Day
tdate=Integer -> Int -> Int -> Day
fromGregorian 2008 01 01,
tdate2 :: Maybe Day
tdate2=Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian 2009 01 01,
tstatus :: Status
tstatus=Status
Unmarked,
tcode :: AccountName
tcode="",
tdescription :: AccountName
tdescription="income",
tcomment :: AccountName
tcomment="",
ttags :: [Tag]
ttags=[],
tpostings :: [Posting]
tpostings=
[Posting
posting {paccount :: AccountName
paccount="assets:bank:checking", pamount :: MixedAmount
pamount=[Amount] -> MixedAmount
Mixed [DecimalRaw Integer -> Amount
usd 1]}
,Posting
posting {paccount :: AccountName
paccount="income:salary", pamount :: MixedAmount
pamount=MixedAmount
missingmixedamt}
],
tprecedingcomment :: AccountName
tprecedingcomment=""
}
]
}
tests_BalanceReport :: TestTree
tests_BalanceReport = String -> [TestTree] -> TestTree
tests "BalanceReport" [
let
(opts :: ReportOpts
opts,journal :: Journal
journal) gives :: (ReportOpts, Journal) -> BalanceReport -> IO ()
`gives` r :: BalanceReport
r = do
let (eitems :: [(AccountName, AccountName, Int, MixedAmount)]
eitems, etotal :: MixedAmount
etotal) = BalanceReport
r
(aitems :: [(AccountName, AccountName, Int, MixedAmount)]
aitems, atotal :: MixedAmount
atotal) = ReportOpts -> Query -> Journal -> BalanceReport
balanceReport ReportOpts
opts (Day -> ReportOpts -> Query
queryFromOpts Day
nulldate ReportOpts
opts) Journal
journal
showw :: (a, b, c, MixedAmount) -> (a, b, c, String)
showw (acct :: a
acct,acct' :: b
acct',indent :: c
indent,amt :: MixedAmount
amt) = (a
acct, b
acct', c
indent, MixedAmount -> String
showMixedAmountDebug MixedAmount
amt)
(((AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, String))
-> [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, String)]
forall a b. (a -> b) -> [a] -> [b]
map (AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, String)
forall a b c. (a, b, c, MixedAmount) -> (a, b, c, String)
showw [(AccountName, AccountName, Int, MixedAmount)]
aitems) [(AccountName, AccountName, Int, String)]
-> [(AccountName, AccountName, Int, String)] -> IO ()
forall a. (Eq a, Show a, HasCallStack) => a -> a -> IO ()
@?= (((AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, String))
-> [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, String)]
forall a b. (a -> b) -> [a] -> [b]
map (AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, String)
forall a b c. (a, b, c, MixedAmount) -> (a, b, c, String)
showw [(AccountName, AccountName, Int, MixedAmount)]
eitems)
(MixedAmount -> String
showMixedAmountDebug MixedAmount
atotal) String -> String -> IO ()
forall a. (Eq a, Show a, HasCallStack) => a -> a -> IO ()
@?= (MixedAmount -> String
showMixedAmountDebug MixedAmount
etotal)
in
String -> [TestTree] -> TestTree
tests "balanceReport" [
String -> IO () -> TestTree
test "no args, null journal" (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
(ReportOpts
defreportopts, Journal
nulljournal) (ReportOpts, Journal) -> BalanceReport -> IO ()
`gives` ([], 0)
,String -> IO () -> TestTree
test "no args, sample journal" (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
(ReportOpts
defreportopts, Journal
samplejournal) (ReportOpts, Journal) -> BalanceReport -> IO ()
`gives`
([
("assets:bank:checking","assets:bank:checking",0, String -> MixedAmount
mamountp' "$1.00")
,("assets:bank:saving","assets:bank:saving",0, String -> MixedAmount
mamountp' "$1.00")
,("assets:cash","assets:cash",0, String -> MixedAmount
mamountp' "$-2.00")
,("expenses:food","expenses:food",0, String -> MixedAmount
mamountp' "$1.00")
,("expenses:supplies","expenses:supplies",0, String -> MixedAmount
mamountp' "$1.00")
,("income:gifts","income:gifts",0, String -> MixedAmount
mamountp' "$-1.00")
,("income:salary","income:salary",0, String -> MixedAmount
mamountp' "$-1.00")
],
[Amount] -> MixedAmount
Mixed [DecimalRaw Integer -> Amount
usd 0])
,String -> IO () -> TestTree
test "with --tree" (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
(ReportOpts
defreportopts{accountlistmode_ :: AccountListMode
accountlistmode_=AccountListMode
ALTree}, Journal
samplejournal) (ReportOpts, Journal) -> BalanceReport -> IO ()
`gives`
([
("assets","assets",0, String -> MixedAmount
mamountp' "$0.00")
,("assets:bank","bank",1, String -> MixedAmount
mamountp' "$2.00")
,("assets:bank:checking","checking",2, String -> MixedAmount
mamountp' "$1.00")
,("assets:bank:saving","saving",2, String -> MixedAmount
mamountp' "$1.00")
,("assets:cash","cash",1, String -> MixedAmount
mamountp' "$-2.00")
,("expenses","expenses",0, String -> MixedAmount
mamountp' "$2.00")
,("expenses:food","food",1, String -> MixedAmount
mamountp' "$1.00")
,("expenses:supplies","supplies",1, String -> MixedAmount
mamountp' "$1.00")
,("income","income",0, String -> MixedAmount
mamountp' "$-2.00")
,("income:gifts","gifts",1, String -> MixedAmount
mamountp' "$-1.00")
,("income:salary","salary",1, String -> MixedAmount
mamountp' "$-1.00")
],
[Amount] -> MixedAmount
Mixed [DecimalRaw Integer -> Amount
usd 0])
,String -> IO () -> TestTree
test "with --depth=N" (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
(ReportOpts
defreportopts{depth_ :: Maybe Int
depth_=Int -> Maybe Int
forall a. a -> Maybe a
Just 1}, Journal
samplejournal) (ReportOpts, Journal) -> BalanceReport -> IO ()
`gives`
([
("expenses", "expenses", 0, String -> MixedAmount
mamountp' "$2.00")
,("income", "income", 0, String -> MixedAmount
mamountp' "$-2.00")
],
[Amount] -> MixedAmount
Mixed [DecimalRaw Integer -> Amount
usd 0])
,String -> IO () -> TestTree
test "with depth:N" (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
(ReportOpts
defreportopts{query_ :: String
query_="depth:1"}, Journal
samplejournal) (ReportOpts, Journal) -> BalanceReport -> IO ()
`gives`
([
("expenses", "expenses", 0, String -> MixedAmount
mamountp' "$2.00")
,("income", "income", 0, String -> MixedAmount
mamountp' "$-2.00")
],
[Amount] -> MixedAmount
Mixed [DecimalRaw Integer -> Amount
usd 0])
,String -> IO () -> TestTree
test "with date:" (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
(ReportOpts
defreportopts{query_ :: String
query_="date:'in 2009'"}, Journal
samplejournal2) (ReportOpts, Journal) -> BalanceReport -> IO ()
`gives`
([], 0)
,String -> IO () -> TestTree
test "with date2:" (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
(ReportOpts
defreportopts{query_ :: String
query_="date2:'in 2009'"}, Journal
samplejournal2) (ReportOpts, Journal) -> BalanceReport -> IO ()
`gives`
([
("assets:bank:checking","assets:bank:checking",0,String -> MixedAmount
mamountp' "$1.00")
,("income:salary","income:salary",0,String -> MixedAmount
mamountp' "$-1.00")
],
[Amount] -> MixedAmount
Mixed [DecimalRaw Integer -> Amount
usd 0])
,String -> IO () -> TestTree
test "with desc:" (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
(ReportOpts
defreportopts{query_ :: String
query_="desc:income"}, Journal
samplejournal) (ReportOpts, Journal) -> BalanceReport -> IO ()
`gives`
([
("assets:bank:checking","assets:bank:checking",0,String -> MixedAmount
mamountp' "$1.00")
,("income:salary","income:salary",0, String -> MixedAmount
mamountp' "$-1.00")
],
[Amount] -> MixedAmount
Mixed [DecimalRaw Integer -> Amount
usd 0])
,String -> IO () -> TestTree
test "with not:desc:" (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
(ReportOpts
defreportopts{query_ :: String
query_="not:desc:income"}, Journal
samplejournal) (ReportOpts, Journal) -> BalanceReport -> IO ()
`gives`
([
("assets:bank:saving","assets:bank:saving",0, String -> MixedAmount
mamountp' "$1.00")
,("assets:cash","assets:cash",0, String -> MixedAmount
mamountp' "$-2.00")
,("expenses:food","expenses:food",0, String -> MixedAmount
mamountp' "$1.00")
,("expenses:supplies","expenses:supplies",0, String -> MixedAmount
mamountp' "$1.00")
,("income:gifts","income:gifts",0, String -> MixedAmount
mamountp' "$-1.00")
],
[Amount] -> MixedAmount
Mixed [DecimalRaw Integer -> Amount
usd 0])
,String -> IO () -> TestTree
test "with period on a populated period" (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
(ReportOpts
defreportopts{period_ :: Period
period_= Day -> Day -> Period
PeriodBetween (Integer -> Int -> Int -> Day
fromGregorian 2008 1 1) (Integer -> Int -> Int -> Day
fromGregorian 2008 1 2)}, Journal
samplejournal) (ReportOpts, Journal) -> BalanceReport -> IO ()
`gives`
(
[
("assets:bank:checking","assets:bank:checking",0, String -> MixedAmount
mamountp' "$1.00")
,("income:salary","income:salary",0, String -> MixedAmount
mamountp' "$-1.00")
],
[Amount] -> MixedAmount
Mixed [DecimalRaw Integer -> Amount
usd 0])
,String -> IO () -> TestTree
test "with period on an unpopulated period" (IO () -> TestTree) -> IO () -> TestTree
forall a b. (a -> b) -> a -> b
$
(ReportOpts
defreportopts{period_ :: Period
period_= Day -> Day -> Period
PeriodBetween (Integer -> Int -> Int -> Day
fromGregorian 2008 1 2) (Integer -> Int -> Int -> Day
fromGregorian 2008 1 3)}, Journal
samplejournal) (ReportOpts, Journal) -> BalanceReport -> IO ()
`gives`
([], 0)
]
]