{-# LANGUAGE FlexibleInstances, RecordWildCards, ScopedTypeVariables, OverloadedStrings #-}
module Hledger.Reports.BalanceReport (
BalanceReport,
BalanceReportItem,
balanceReport,
flatShowsExclusiveBalance,
sortAccountItemsLike,
unifyMixedAmount,
perdivide,
tests_BalanceReport
)
where
import Data.List
import Data.Ord
import Data.Maybe
import Data.Time.Calendar
import Hledger.Data
import Hledger.Read (mamountp')
import Hledger.Query
import Hledger.Utils
import Hledger.Reports.ReportOptions
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@ReportOpts{..} q :: Query
q j :: Journal
j =
(if Bool
invert_ then BalanceReport -> BalanceReport
brNegate else BalanceReport -> BalanceReport
forall a. a -> a
id) (BalanceReport -> BalanceReport) -> BalanceReport -> BalanceReport
forall a b. (a -> b) -> a -> b
$
([(AccountName, AccountName, Int, MixedAmount)]
mappedsorteditems, MixedAmount
mappedtotal)
where
dbg :: String -> a -> a
dbg s :: String
s = let p :: String
p = "balanceReport" in String -> a -> a
forall a. Show a => String -> a -> a
Hledger.Utils.dbg4 (String
pString -> String -> String
forall a. [a] -> [a] -> [a]
++" "String -> String -> String
forall a. [a] -> [a] -> [a]
++String
s)
dbg' :: String -> a -> a
dbg' s :: String
s = let p :: String
p = "balanceReport" in String -> a -> a
forall a. Show a => String -> a -> a
Hledger.Utils.dbg5 (String
pString -> String -> String
forall a. [a] -> [a] -> [a]
++" "String -> String -> String
forall a. [a] -> [a] -> [a]
++String
s)
accttree :: Account
accttree = Ledger -> Account
ledgerRootAccount (Ledger -> Account) -> Ledger -> Account
forall a b. (a -> b) -> a -> b
$ Query -> Journal -> Ledger
ledgerFromJournal Query
q (Journal -> Ledger) -> Journal -> Ledger
forall a b. (a -> b) -> a -> b
$ ReportOpts -> Journal -> Journal
journalSelectingAmountFromOpts ReportOpts
ropts Journal
j
valuedaccttree :: Account
valuedaccttree = (Account -> Account) -> Account -> Account
mapAccounts Account -> Account
avalue Account
accttree
where
avalue :: Account -> Account
avalue a :: Account
a@Account{..} = Account
a{aebalance :: MixedAmount
aebalance=MixedAmount -> MixedAmount
maybevalue MixedAmount
aebalance, aibalance :: MixedAmount
aibalance=MixedAmount -> MixedAmount
maybevalue MixedAmount
aibalance}
where
maybevalue :: MixedAmount -> MixedAmount
maybevalue = (MixedAmount -> MixedAmount)
-> (ValuationType -> MixedAmount -> MixedAmount)
-> Maybe ValuationType
-> MixedAmount
-> MixedAmount
forall b a. b -> (a -> b) -> Maybe a -> b
maybe MixedAmount -> MixedAmount
forall a. a -> a
id ValuationType -> MixedAmount -> MixedAmount
applyvaluation Maybe ValuationType
value_
where
applyvaluation :: ValuationType -> MixedAmount -> MixedAmount
applyvaluation = PriceOracle
-> Map AccountName AmountStyle
-> Day
-> Maybe Day
-> Day
-> Bool
-> ValuationType
-> MixedAmount
-> MixedAmount
mixedAmountApplyValuation PriceOracle
priceoracle Map AccountName AmountStyle
styles Day
periodlast Maybe Day
mreportlast Day
today Bool
multiperiod
where
priceoracle :: PriceOracle
priceoracle = Bool -> Journal -> PriceOracle
journalPriceOracle Bool
infer_value_ Journal
j
styles :: Map AccountName AmountStyle
styles = Journal -> Map AccountName 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' "balanceReport: expected a non-empty journal") (Maybe Day -> Day) -> Maybe Day -> Day
forall a b. (a -> b) -> a -> b
$
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' "balanceReport: could not pick a valuation date, ReportOpts today_ is unset") Maybe Day
today_
multiperiod :: Bool
multiperiod = Interval
interval_ Interval -> Interval -> Bool
forall a. Eq a => a -> a -> Bool
/= Interval
NoInterval
[Account]
displayaccts :: [Account]
| Query -> Int
queryDepth Query
q Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 0 =
String -> [Account] -> [Account]
forall a. Show a => String -> a -> a
dbg' "displayaccts" ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$
Int -> [Account] -> [Account]
forall a. Int -> [a] -> [a]
take 1 ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$ Int -> [Account] -> [Account]
clipAccountsAndAggregate (Query -> Int
queryDepth Query
q) ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$ Account -> [Account]
flattenAccounts Account
valuedaccttree
| ReportOpts -> Bool
flat_ ReportOpts
ropts = String -> [Account] -> [Account]
forall a. Show a => String -> a -> a
dbg' "displayaccts" ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$
[Account] -> [Account]
filterzeros ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$
[Account] -> [Account]
filterempty ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$
Int -> [Account] -> [Account]
forall a. Int -> [a] -> [a]
drop 1 ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$ Int -> [Account] -> [Account]
clipAccountsAndAggregate (Query -> Int
queryDepth Query
q) ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$ Account -> [Account]
flattenAccounts Account
valuedaccttree
| Bool
otherwise = String -> [Account] -> [Account]
forall a. Show a => String -> a -> a
dbg' "displayaccts" ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$
(Account -> Bool) -> [Account] -> [Account]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not(Bool -> Bool) -> (Account -> Bool) -> Account -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Account -> Bool
aboring) ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$
Int -> [Account] -> [Account]
forall a. Int -> [a] -> [a]
drop 1 ([Account] -> [Account]) -> [Account] -> [Account]
forall a b. (a -> b) -> a -> b
$ Account -> [Account]
flattenAccounts (Account -> [Account]) -> Account -> [Account]
forall a b. (a -> b) -> a -> b
$
Account -> Account
markboring (Account -> Account) -> Account -> Account
forall a b. (a -> b) -> a -> b
$
Account -> Account
prunezeros (Account -> Account) -> Account -> Account
forall a b. (a -> b) -> a -> b
$
NormalSign -> Account -> Account
sortAccountTreeByAmount (NormalSign -> Maybe NormalSign -> NormalSign
forall a. a -> Maybe a -> a
fromMaybe NormalSign
NormallyPositive Maybe NormalSign
normalbalance_) (Account -> Account) -> Account -> Account
forall a b. (a -> b) -> a -> b
$
Int -> Account -> Account
clipAccounts (Query -> Int
queryDepth Query
q) Account
valuedaccttree
where
balance :: Account -> MixedAmount
balance = if ReportOpts -> Bool
flat_ ReportOpts
ropts then Account -> MixedAmount
aebalance else Account -> MixedAmount
aibalance
filterzeros :: [Account] -> [Account]
filterzeros = if Bool
empty_ then [Account] -> [Account]
forall a. a -> a
id else (Account -> Bool) -> [Account] -> [Account]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Account -> Bool) -> Account -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MixedAmount -> Bool
mixedAmountLooksZero (MixedAmount -> Bool)
-> (Account -> MixedAmount) -> Account -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Account -> MixedAmount
balance)
filterempty :: [Account] -> [Account]
filterempty = (Account -> Bool) -> [Account] -> [Account]
forall a. (a -> Bool) -> [a] -> [a]
filter (\a :: Account
a -> Account -> Int
anumpostings Account
a Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0 Bool -> Bool -> Bool
|| Bool -> Bool
not (MixedAmount -> Bool
mixedAmountLooksZero (Account -> MixedAmount
balance Account
a)))
prunezeros :: Account -> Account
prunezeros = if Bool
empty_ then Account -> Account
forall a. a -> a
id else Account -> Maybe Account -> Account
forall a. a -> Maybe a -> a
fromMaybe Account
nullacct (Maybe Account -> Account)
-> (Account -> Maybe Account) -> Account -> Account
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Account -> Bool) -> Account -> Maybe Account
pruneAccounts (MixedAmount -> Bool
mixedAmountLooksZero (MixedAmount -> Bool)
-> (Account -> MixedAmount) -> Account -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Account -> MixedAmount
balance)
markboring :: Account -> Account
markboring = if Bool
no_elide_ then Account -> Account
forall a. a -> a
id else Account -> Account
markBoringParentAccounts
items :: [(AccountName, AccountName, Int, MixedAmount)]
items = String
-> [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)]
forall a. Show a => String -> a -> a
dbg "items" ([(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)])
-> [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)]
forall a b. (a -> b) -> a -> b
$ (Account -> (AccountName, AccountName, Int, MixedAmount))
-> [Account] -> [(AccountName, AccountName, Int, MixedAmount)]
forall a b. (a -> b) -> [a] -> [b]
map (ReportOpts
-> Query -> Account -> (AccountName, AccountName, Int, MixedAmount)
balanceReportItem ReportOpts
ropts Query
q) [Account]
displayaccts
sorteditems :: [(AccountName, AccountName, Int, MixedAmount)]
sorteditems
| Bool
sort_amount_ Bool -> Bool -> Bool
&& ReportOpts -> Bool
tree_ ReportOpts
ropts = [(AccountName, AccountName, Int, MixedAmount)]
items
| Bool
sort_amount_ = [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)]
sortFlatBRByAmount [(AccountName, AccountName, Int, MixedAmount)]
items
| Bool
otherwise = [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)]
sortBRByAccountDeclaration [(AccountName, AccountName, Int, MixedAmount)]
items
where
sortFlatBRByAmount :: [BalanceReportItem] -> [BalanceReportItem]
sortFlatBRByAmount :: [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)]
sortFlatBRByAmount = ((AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount) -> Ordering)
-> [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy (((AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount) -> Ordering)
-> (AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount)
-> Ordering
forall a c. (a -> a -> c) -> a -> a -> c
maybeflip (((AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount) -> Ordering)
-> (AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount)
-> Ordering)
-> ((AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount) -> Ordering)
-> (AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount)
-> Ordering
forall a b. (a -> b) -> a -> b
$ ((AccountName, AccountName, Int, MixedAmount) -> MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount)
-> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing (MixedAmount -> MixedAmount
normaliseMixedAmountSquashPricesForDisplay (MixedAmount -> MixedAmount)
-> ((AccountName, AccountName, Int, MixedAmount) -> MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount)
-> MixedAmount
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (AccountName, AccountName, Int, MixedAmount) -> MixedAmount
forall a b c d. (a, b, c, d) -> d
fourth4))
where
maybeflip :: (a -> a -> c) -> a -> a -> c
maybeflip = if Maybe NormalSign
normalbalance_ Maybe NormalSign -> Maybe NormalSign -> Bool
forall a. Eq a => a -> a -> Bool
== NormalSign -> Maybe NormalSign
forall a. a -> Maybe a
Just NormalSign
NormallyNegative then (a -> a -> c) -> a -> a -> c
forall a. a -> a
id else (a -> a -> c) -> a -> a -> c
forall a b c. (a -> b -> c) -> b -> a -> c
flip
sortBRByAccountDeclaration :: [BalanceReportItem] -> [BalanceReportItem]
sortBRByAccountDeclaration :: [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)]
sortBRByAccountDeclaration rows :: [(AccountName, AccountName, Int, MixedAmount)]
rows = [(AccountName, AccountName, Int, MixedAmount)]
sortedrows
where
anamesandrows :: [(AccountName, (AccountName, AccountName, Int, MixedAmount))]
anamesandrows = [((AccountName, AccountName, Int, MixedAmount) -> AccountName
forall a b c d. (a, b, c, d) -> a
first4 (AccountName, AccountName, Int, MixedAmount)
r, (AccountName, AccountName, Int, MixedAmount)
r) | (AccountName, AccountName, Int, MixedAmount)
r <- [(AccountName, AccountName, Int, MixedAmount)]
rows]
anames :: [AccountName]
anames = ((AccountName, (AccountName, AccountName, Int, MixedAmount))
-> AccountName)
-> [(AccountName, (AccountName, AccountName, Int, MixedAmount))]
-> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map (AccountName, (AccountName, AccountName, Int, MixedAmount))
-> AccountName
forall a b. (a, b) -> a
fst [(AccountName, (AccountName, AccountName, Int, MixedAmount))]
anamesandrows
sortedanames :: [AccountName]
sortedanames = Journal -> Bool -> [AccountName] -> [AccountName]
sortAccountNamesByDeclaration Journal
j (ReportOpts -> Bool
tree_ ReportOpts
ropts) [AccountName]
anames
sortedrows :: [(AccountName, AccountName, Int, MixedAmount)]
sortedrows = [AccountName]
-> [(AccountName, (AccountName, AccountName, Int, MixedAmount))]
-> [(AccountName, AccountName, Int, MixedAmount)]
forall b. [AccountName] -> [(AccountName, b)] -> [b]
sortAccountItemsLike [AccountName]
sortedanames [(AccountName, (AccountName, AccountName, Int, MixedAmount))]
anamesandrows
total :: MixedAmount
total | Bool -> Bool
not (ReportOpts -> Bool
flat_ ReportOpts
ropts) = String -> MixedAmount -> MixedAmount
forall a. Show a => String -> a -> a
dbg "total" (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
amt | (_,_,indent :: Int
indent,amt :: MixedAmount
amt) <- [(AccountName, AccountName, Int, MixedAmount)]
items, Int
indent Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 0]
| Bool
otherwise = String -> MixedAmount -> MixedAmount
forall a. Show a => String -> a -> a
dbg "total" (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
forall a b. (a -> b) -> a -> b
$
if Bool
flatShowsExclusiveBalance
then [MixedAmount] -> MixedAmount
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ([MixedAmount] -> MixedAmount) -> [MixedAmount] -> MixedAmount
forall a b. (a -> b) -> a -> b
$ ((AccountName, AccountName, Int, MixedAmount) -> MixedAmount)
-> [(AccountName, AccountName, Int, MixedAmount)] -> [MixedAmount]
forall a b. (a -> b) -> [a] -> [b]
map (AccountName, AccountName, Int, MixedAmount) -> MixedAmount
forall a b c d. (a, b, c, d) -> d
fourth4 [(AccountName, AccountName, Int, MixedAmount)]
items
else [MixedAmount] -> MixedAmount
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ([MixedAmount] -> MixedAmount) -> [MixedAmount] -> MixedAmount
forall a b. (a -> b) -> a -> b
$ (Account -> MixedAmount) -> [Account] -> [MixedAmount]
forall a b. (a -> b) -> [a] -> [b]
map Account -> MixedAmount
aebalance ([Account] -> [MixedAmount]) -> [Account] -> [MixedAmount]
forall a b. (a -> b) -> a -> b
$ Int -> [Account] -> [Account]
clipAccountsAndAggregate 1 [Account]
displayaccts
mappedtotal :: MixedAmount
mappedtotal | Bool
percent_ = String -> MixedAmount -> MixedAmount
forall a. Show a => String -> a -> a
dbg "mappedtotal" (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
forall a b. (a -> b) -> a -> b
$ MixedAmount
total MixedAmount -> MixedAmount -> MixedAmount
`perdivide` MixedAmount
total
| Bool
otherwise = MixedAmount
total
mappedsorteditems :: [(AccountName, AccountName, Int, MixedAmount)]
mappedsorteditems | Bool
percent_ =
String
-> [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)]
forall a. Show a => String -> a -> a
dbg "mappedsorteditems" ([(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)])
-> [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)]
forall a b. (a -> b) -> a -> b
$
((AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount))
-> [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)]
forall a b. (a -> b) -> [a] -> [b]
map (\(fname :: AccountName
fname, sname :: AccountName
sname, indent :: Int
indent, amount :: MixedAmount
amount) -> (AccountName
fname, AccountName
sname, Int
indent, MixedAmount
amount MixedAmount -> MixedAmount -> MixedAmount
`perdivide` MixedAmount
total)) [(AccountName, AccountName, Int, MixedAmount)]
sorteditems
| Bool
otherwise = [(AccountName, AccountName, Int, MixedAmount)]
sorteditems
sortAccountItemsLike :: [AccountName] -> [(AccountName, b)] -> [b]
sortAccountItemsLike :: [AccountName] -> [(AccountName, b)] -> [b]
sortAccountItemsLike sortedas :: [AccountName]
sortedas items :: [(AccountName, b)]
items =
(AccountName -> [b]) -> [AccountName] -> [b]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\a :: AccountName
a -> [b] -> (b -> [b]) -> Maybe b -> [b]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (b -> [b] -> [b]
forall a. a -> [a] -> [a]
:[]) (Maybe b -> [b]) -> Maybe b -> [b]
forall a b. (a -> b) -> a -> b
$ AccountName -> [(AccountName, b)] -> Maybe b
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup AccountName
a [(AccountName, b)]
items) [AccountName]
sortedas
markBoringParentAccounts :: Account -> Account
markBoringParentAccounts :: Account -> Account
markBoringParentAccounts = Account -> Account
tieAccountParents (Account -> Account) -> (Account -> Account) -> Account -> Account
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Account -> Account) -> Account -> Account
mapAccounts Account -> Account
mark
where
mark :: Account -> Account
mark a :: Account
a | [Account] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (Account -> [Account]
asubs Account
a) Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 1 Bool -> Bool -> Bool
&& MixedAmount -> Bool
mixedAmountLooksZero (Account -> MixedAmount
aebalance Account
a) = Account
a{aboring :: Bool
aboring=Bool
True}
| Bool
otherwise = Account
a
balanceReportItem :: ReportOpts -> Query -> Account -> BalanceReportItem
balanceReportItem :: ReportOpts
-> Query -> Account -> (AccountName, AccountName, Int, MixedAmount)
balanceReportItem opts :: ReportOpts
opts q :: Query
q a :: Account
a
| ReportOpts -> Bool
flat_ ReportOpts
opts = (AccountName
name, AccountName
name, 0, (if Bool
flatShowsExclusiveBalance then Account -> MixedAmount
aebalance else Account -> MixedAmount
aibalance) Account
a)
| Bool
otherwise = (AccountName
name, AccountName
elidedname, Int
indent, Account -> MixedAmount
aibalance Account
a)
where
name :: AccountName
name | Query -> Int
queryDepth Query
q Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0 = Account -> AccountName
aname Account
a
| Bool
otherwise = "..."
elidedname :: AccountName
elidedname = [AccountName] -> AccountName
accountNameFromComponents ([AccountName]
adjacentboringparentnames [AccountName] -> [AccountName] -> [AccountName]
forall a. [a] -> [a] -> [a]
++ [AccountName -> AccountName
accountLeafName AccountName
name])
adjacentboringparentnames :: [AccountName]
adjacentboringparentnames = [AccountName] -> [AccountName]
forall a. [a] -> [a]
reverse ([AccountName] -> [AccountName]) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> a -> b
$ (Account -> AccountName) -> [Account] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map (AccountName -> AccountName
accountLeafName(AccountName -> AccountName)
-> (Account -> AccountName) -> Account -> AccountName
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Account -> AccountName
aname) ([Account] -> [AccountName]) -> [Account] -> [AccountName]
forall a b. (a -> b) -> a -> b
$ (Account -> Bool) -> [Account] -> [Account]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile Account -> Bool
aboring [Account]
parents
indent :: Int
indent = [Account] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Account] -> Int) -> [Account] -> Int
forall a b. (a -> b) -> a -> b
$ (Account -> Bool) -> [Account] -> [Account]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not(Bool -> Bool) -> (Account -> Bool) -> Account -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Account -> Bool
aboring) [Account]
parents
parents :: [Account]
parents = case Account -> [Account]
parentAccounts Account
a of [] -> []
as :: [Account]
as -> [Account] -> [Account]
forall a. [a] -> [a]
init [Account]
as
brNegate :: BalanceReport -> BalanceReport
brNegate :: BalanceReport -> BalanceReport
brNegate (is :: [(AccountName, AccountName, Int, MixedAmount)]
is, tot :: MixedAmount
tot) = (((AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount))
-> [(AccountName, AccountName, Int, MixedAmount)]
-> [(AccountName, AccountName, Int, MixedAmount)]
forall a b. (a -> b) -> [a] -> [b]
map (AccountName, AccountName, Int, MixedAmount)
-> (AccountName, AccountName, Int, MixedAmount)
forall d a b c. Num d => (a, b, c, d) -> (a, b, c, d)
brItemNegate [(AccountName, AccountName, Int, MixedAmount)]
is, -MixedAmount
tot)
where
brItemNegate :: (a, b, c, d) -> (a, b, c, d)
brItemNegate (a :: a
a, a' :: b
a', d :: c
d, amt :: d
amt) = (a
a, b
a', c
d, -d
amt)
unifyMixedAmount :: MixedAmount -> Amount
unifyMixedAmount :: MixedAmount -> Amount
unifyMixedAmount mixedAmount :: MixedAmount
mixedAmount = (Amount -> Amount -> Amount) -> Amount -> [Amount] -> Amount
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl Amount -> Amount -> Amount
combine (Quantity -> Amount
num 0) (MixedAmount -> [Amount]
amounts MixedAmount
mixedAmount)
where
combine :: Amount -> Amount -> Amount
combine amount :: Amount
amount result :: Amount
result =
if Amount -> Bool
amountIsZero Amount
amount
then Amount
result
else if Amount -> Bool
amountIsZero Amount
result
then Amount
amount
else if Amount -> AccountName
acommodity Amount
amount AccountName -> AccountName -> Bool
forall a. Eq a => a -> a -> Bool
== Amount -> AccountName
acommodity Amount
result
then Amount
amount Amount -> Amount -> Amount
forall a. Num a => a -> a -> a
+ Amount
result
else String -> Amount
forall a. String -> a
error' "Cannot calculate percentages for accounts with multiple commodities. (Hint: Try --cost, -V or similar flags.)"
perdivide :: MixedAmount -> MixedAmount -> MixedAmount
perdivide :: MixedAmount -> MixedAmount -> MixedAmount
perdivide a :: MixedAmount
a b :: MixedAmount
b =
let a' :: Amount
a' = MixedAmount -> Amount
unifyMixedAmount MixedAmount
a
b' :: Amount
b' = MixedAmount -> Amount
unifyMixedAmount MixedAmount
b
in if Amount -> Bool
amountIsZero Amount
a' Bool -> Bool -> Bool
|| Amount -> Bool
amountIsZero Amount
b' Bool -> Bool -> Bool
|| Amount -> AccountName
acommodity Amount
a' AccountName -> AccountName -> Bool
forall a. Eq a => a -> a -> Bool
== Amount -> AccountName
acommodity Amount
b'
then [Amount] -> MixedAmount
mixed [Quantity -> Amount
per (Quantity -> Amount) -> Quantity -> Amount
forall a b. (a -> b) -> a -> b
$ if Amount -> Quantity
aquantity Amount
b' Quantity -> Quantity -> Bool
forall a. Eq a => a -> a -> Bool
== 0 then 0 else (Amount -> Quantity
aquantity Amount
a' Quantity -> Quantity -> Quantity
forall a. Fractional a => a -> a -> a
/ Quantity -> Quantity
forall a. Num a => a -> a
abs (Amount -> Quantity
aquantity Amount
b') Quantity -> Quantity -> Quantity
forall a. Num a => a -> a -> a
* 100)]
else String -> MixedAmount
forall a. String -> a
error' "Cannot calculate percentages if accounts have different commodities. (Hint: Try --cost, -V or similar flags.)"
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=String -> Day
parsedate "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
$ String -> Day
parsedate "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 [Quantity -> 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)]
eitems) [(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)]
aitems)
(MixedAmount -> String
showMixedAmountDebug MixedAmount
etotal) String -> String -> IO ()
forall a. (Eq a, Show a, HasCallStack) => a -> a -> IO ()
@?= (MixedAmount -> String
showMixedAmountDebug MixedAmount
atotal)
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` ([], [Amount] -> MixedAmount
Mixed [Amount
nullamt])
,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","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 [Quantity -> 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 [Quantity -> 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 [Quantity -> 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`
([],
[Amount] -> MixedAmount
Mixed [Amount
nullamt])
,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 [Quantity -> 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 [Quantity -> 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","assets",0, String -> MixedAmount
mamountp' "$-1.00")
,("assets:bank:saving","bank:saving",1, 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:gifts","income:gifts",0, String -> MixedAmount
mamountp' "$-1.00")
],
[Amount] -> MixedAmount
Mixed [Quantity -> 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 [Quantity -> 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`
([],[Amount] -> MixedAmount
Mixed [Amount
nullamt])
]
]