{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Hledger.Reports.BudgetReport
where
import Data.Decimal
import Data.List
import Data.Maybe
#if !(MIN_VERSION_base(4,11,0))
import Data.Monoid ((<>))
#endif
import Data.Ord
import Data.Time.Calendar
import Safe
import qualified Data.Map as Map
import Data.Map (Map)
import qualified Data.Text as T
import Text.Printf (printf)
import Text.Tabular as T
import Hledger.Data
import Hledger.Utils
import Hledger.Reports.ReportOptions
import Hledger.Reports.ReportTypes
import Hledger.Reports.BalanceReport (sortAccountItemsLike)
import Hledger.Reports.MultiBalanceReport
type BudgetGoal = Change
type BudgetTotal = Total
type BudgetAverage = Average
type BudgetCell = (Maybe Change, Maybe BudgetGoal)
type BudgetReport = PeriodicReport BudgetCell
type BudgetReportRow = PeriodicReportRow BudgetCell
budgetReport :: ReportOpts -> Bool -> DateSpan -> Day -> Journal -> BudgetReport
budgetReport :: ReportOpts -> Bool -> DateSpan -> Day -> Journal -> BudgetReport
budgetReport ropts' :: ReportOpts
ropts' assrt :: Bool
assrt reportspan :: DateSpan
reportspan d :: Day
d j :: Journal
j =
let
ropts :: ReportOpts
ropts = ReportOpts
ropts' { accountlistmode_ :: AccountListMode
accountlistmode_ = AccountListMode
ALTree }
showunbudgeted :: Bool
showunbudgeted = ReportOpts -> Bool
empty_ ReportOpts
ropts
q :: Query
q = Day -> ReportOpts -> Query
queryFromOpts Day
d ReportOpts
ropts
budgetedaccts :: [AccountName]
budgetedaccts =
String -> [AccountName] -> [AccountName]
forall a. Show a => String -> a -> a
dbg2 "budgetedacctsinperiod" ([AccountName] -> [AccountName]) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> a -> b
$
[AccountName] -> [AccountName]
forall a. Eq a => [a] -> [a]
nub ([AccountName] -> [AccountName]) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> a -> b
$
(AccountName -> [AccountName]) -> [AccountName] -> [AccountName]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap AccountName -> [AccountName]
expandAccountName ([AccountName] -> [AccountName]) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> a -> b
$
[Posting] -> [AccountName]
accountNamesFromPostings ([Posting] -> [AccountName]) -> [Posting] -> [AccountName]
forall a b. (a -> b) -> a -> b
$
(Transaction -> [Posting]) -> [Transaction] -> [Posting]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Transaction -> [Posting]
tpostings ([Transaction] -> [Posting]) -> [Transaction] -> [Posting]
forall a b. (a -> b) -> a -> b
$
(PeriodicTransaction -> [Transaction])
-> [PeriodicTransaction] -> [Transaction]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ((PeriodicTransaction -> DateSpan -> [Transaction])
-> DateSpan -> PeriodicTransaction -> [Transaction]
forall a b c. (a -> b -> c) -> b -> a -> c
flip PeriodicTransaction -> DateSpan -> [Transaction]
runPeriodicTransaction DateSpan
reportspan) ([PeriodicTransaction] -> [Transaction])
-> [PeriodicTransaction] -> [Transaction]
forall a b. (a -> b) -> a -> b
$
Journal -> [PeriodicTransaction]
jperiodictxns Journal
j
actualj :: Journal
actualj = (Journal -> String) -> Journal -> Journal
forall a. Show a => (a -> String) -> a -> a
dbg1With (("actualj"String -> String -> String
forall a. [a] -> [a] -> [a]
++)(String -> String) -> (Journal -> String) -> Journal -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
.[Transaction] -> String
forall a. Show a => a -> String
show([Transaction] -> String)
-> (Journal -> [Transaction]) -> Journal -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Journal -> [Transaction]
jtxns) (Journal -> Journal) -> Journal -> Journal
forall a b. (a -> b) -> a -> b
$ [AccountName] -> Bool -> Journal -> Journal
budgetRollUp [AccountName]
budgetedaccts Bool
showunbudgeted Journal
j
budgetj :: Journal
budgetj = (Journal -> String) -> Journal -> Journal
forall a. Show a => (a -> String) -> a -> a
dbg1With (("budgetj"String -> String -> String
forall a. [a] -> [a] -> [a]
++)(String -> String) -> (Journal -> String) -> Journal -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
.[Transaction] -> String
forall a. Show a => a -> String
show([Transaction] -> String)
-> (Journal -> [Transaction]) -> Journal -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Journal -> [Transaction]
jtxns) (Journal -> Journal) -> Journal -> Journal
forall a b. (a -> b) -> a -> b
$ Bool -> ReportOpts -> DateSpan -> Journal -> Journal
budgetJournal Bool
assrt ReportOpts
ropts DateSpan
reportspan Journal
j
actualreport :: MultiBalanceReport
actualreport@(MultiBalanceReport (actualspans :: [DateSpan]
actualspans, _, _)) = String -> MultiBalanceReport -> MultiBalanceReport
forall a. Show a => String -> a -> a
dbg1 "actualreport" (MultiBalanceReport -> MultiBalanceReport)
-> MultiBalanceReport -> MultiBalanceReport
forall a b. (a -> b) -> a -> b
$ ReportOpts -> Query -> Journal -> MultiBalanceReport
multiBalanceReport ReportOpts
ropts Query
q Journal
actualj
budgetgoalreport :: MultiBalanceReport
budgetgoalreport@(MultiBalanceReport (_, budgetgoalitems :: [MultiBalanceReportRow]
budgetgoalitems, budgetgoaltotals :: MultiBalanceReportTotals
budgetgoaltotals)) = String -> MultiBalanceReport -> MultiBalanceReport
forall a. Show a => String -> a -> a
dbg1 "budgetgoalreport" (MultiBalanceReport -> MultiBalanceReport)
-> MultiBalanceReport -> MultiBalanceReport
forall a b. (a -> b) -> a -> b
$ ReportOpts -> Query -> Journal -> MultiBalanceReport
multiBalanceReport (ReportOpts
ropts{empty_ :: Bool
empty_=Bool
True}) Query
q Journal
budgetj
budgetgoalreport' :: MultiBalanceReport
budgetgoalreport'
| ReportOpts -> Interval
interval_ ReportOpts
ropts Interval -> Interval -> Bool
forall a. Eq a => a -> a -> Bool
== Interval
NoInterval = ([DateSpan], [MultiBalanceReportRow], MultiBalanceReportTotals)
-> MultiBalanceReport
MultiBalanceReport ([DateSpan]
actualspans, [MultiBalanceReportRow]
budgetgoalitems, MultiBalanceReportTotals
budgetgoaltotals)
| Bool
otherwise = MultiBalanceReport
budgetgoalreport
budgetreport :: BudgetReport
budgetreport = MultiBalanceReport -> MultiBalanceReport -> BudgetReport
combineBudgetAndActual MultiBalanceReport
budgetgoalreport' MultiBalanceReport
actualreport
sortedbudgetreport :: BudgetReport
sortedbudgetreport = ReportOpts -> Journal -> BudgetReport -> BudgetReport
sortBudgetReport ReportOpts
ropts Journal
j BudgetReport
budgetreport
in
String -> BudgetReport -> BudgetReport
forall a. Show a => String -> a -> a
dbg1 "sortedbudgetreport" BudgetReport
sortedbudgetreport
sortBudgetReport :: ReportOpts -> Journal -> BudgetReport -> BudgetReport
sortBudgetReport :: ReportOpts -> Journal -> BudgetReport -> BudgetReport
sortBudgetReport ropts :: ReportOpts
ropts j :: Journal
j (PeriodicReport (ps :: [DateSpan]
ps, rows :: [PeriodicReportRow BudgetCell]
rows, trow :: PeriodicReportRow BudgetCell
trow)) = ([DateSpan], [PeriodicReportRow BudgetCell],
PeriodicReportRow BudgetCell)
-> BudgetReport
forall a.
([DateSpan], [PeriodicReportRow a], PeriodicReportRow a)
-> PeriodicReport a
PeriodicReport ([DateSpan]
ps, [PeriodicReportRow BudgetCell]
sortedrows, PeriodicReportRow BudgetCell
trow)
where
sortedrows :: [PeriodicReportRow BudgetCell]
sortedrows
| ReportOpts -> Bool
sort_amount_ ReportOpts
ropts Bool -> Bool -> Bool
&& ReportOpts -> Bool
tree_ ReportOpts
ropts = [PeriodicReportRow BudgetCell] -> [PeriodicReportRow BudgetCell]
sortTreeBURByActualAmount [PeriodicReportRow BudgetCell]
rows
| ReportOpts -> Bool
sort_amount_ ReportOpts
ropts = [PeriodicReportRow BudgetCell] -> [PeriodicReportRow BudgetCell]
sortFlatBURByActualAmount [PeriodicReportRow BudgetCell]
rows
| Bool
otherwise = [PeriodicReportRow BudgetCell] -> [PeriodicReportRow BudgetCell]
forall b c d e f.
[(AccountName, b, c, d, e, f)] -> [(AccountName, b, c, d, e, f)]
sortByAccountDeclaration [PeriodicReportRow BudgetCell]
rows
sortTreeBURByActualAmount :: [BudgetReportRow] -> [BudgetReportRow]
sortTreeBURByActualAmount :: [PeriodicReportRow BudgetCell] -> [PeriodicReportRow BudgetCell]
sortTreeBURByActualAmount rows :: [PeriodicReportRow BudgetCell]
rows = [PeriodicReportRow BudgetCell]
sortedrows
where
anamesandrows :: [(AccountName, PeriodicReportRow BudgetCell)]
anamesandrows = [(PeriodicReportRow BudgetCell -> AccountName
forall a b c d e f. (a, b, c, d, e, f) -> a
first6 PeriodicReportRow BudgetCell
r, PeriodicReportRow BudgetCell
r) | PeriodicReportRow BudgetCell
r <- [PeriodicReportRow BudgetCell]
rows]
anames :: [AccountName]
anames = ((AccountName, PeriodicReportRow BudgetCell) -> AccountName)
-> [(AccountName, PeriodicReportRow BudgetCell)] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map (AccountName, PeriodicReportRow BudgetCell) -> AccountName
forall a b. (a, b) -> a
fst [(AccountName, PeriodicReportRow BudgetCell)]
anamesandrows
atotals :: [(AccountName, Maybe Change)]
atotals = [(AccountName
a,Maybe Change
tot) | (a :: AccountName
a,_,_,_,(tot :: Maybe Change
tot,_),_) <- [PeriodicReportRow BudgetCell]
rows]
accounttree :: Account
accounttree = AccountName -> [AccountName] -> Account
accountTree "root" [AccountName]
anames
accounttreewithbals :: Account
accounttreewithbals = (Account -> Account) -> Account -> Account
mapAccounts Account -> Account
setibalance Account
accounttree
where
setibalance :: Account -> Account
setibalance a :: Account
a = Account
a{aibalance :: Change
aibalance=
Change -> Maybe Change -> Change
forall a. a -> Maybe a -> a
fromMaybe 0 (Maybe Change -> Change) -> Maybe Change -> Change
forall a b. (a -> b) -> a -> b
$
Maybe Change -> Maybe (Maybe Change) -> Maybe Change
forall a. a -> Maybe a -> a
fromMaybe (String -> Maybe Change
forall a. HasCallStack => String -> a
error "sortTreeByAmount 1") (Maybe (Maybe Change) -> Maybe Change)
-> Maybe (Maybe Change) -> Maybe Change
forall a b. (a -> b) -> a -> b
$
AccountName
-> [(AccountName, Maybe Change)] -> Maybe (Maybe Change)
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup (Account -> AccountName
aname Account
a) [(AccountName, Maybe Change)]
atotals
}
sortedaccounttree :: Account
sortedaccounttree = NormalSign -> Account -> Account
sortAccountTreeByAmount (NormalSign -> Maybe NormalSign -> NormalSign
forall a. a -> Maybe a -> a
fromMaybe NormalSign
NormallyPositive (Maybe NormalSign -> NormalSign) -> Maybe NormalSign -> NormalSign
forall a b. (a -> b) -> a -> b
$ ReportOpts -> Maybe NormalSign
normalbalance_ ReportOpts
ropts) Account
accounttreewithbals
sortedanames :: [AccountName]
sortedanames = (Account -> AccountName) -> [Account] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map Account -> AccountName
aname ([Account] -> [AccountName]) -> [Account] -> [AccountName]
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
sortedaccounttree
sortedrows :: [PeriodicReportRow BudgetCell]
sortedrows = [AccountName]
-> [(AccountName, PeriodicReportRow BudgetCell)]
-> [PeriodicReportRow BudgetCell]
forall b. [AccountName] -> [(AccountName, b)] -> [b]
sortAccountItemsLike [AccountName]
sortedanames [(AccountName, PeriodicReportRow BudgetCell)]
anamesandrows
sortFlatBURByActualAmount :: [BudgetReportRow] -> [BudgetReportRow]
sortFlatBURByActualAmount :: [PeriodicReportRow BudgetCell] -> [PeriodicReportRow BudgetCell]
sortFlatBURByActualAmount = (PeriodicReportRow BudgetCell
-> PeriodicReportRow BudgetCell -> Ordering)
-> [PeriodicReportRow BudgetCell] -> [PeriodicReportRow BudgetCell]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy ((PeriodicReportRow BudgetCell
-> PeriodicReportRow BudgetCell -> Ordering)
-> PeriodicReportRow BudgetCell
-> PeriodicReportRow BudgetCell
-> Ordering
forall a c. (a -> a -> c) -> a -> a -> c
maybeflip ((PeriodicReportRow BudgetCell
-> PeriodicReportRow BudgetCell -> Ordering)
-> PeriodicReportRow BudgetCell
-> PeriodicReportRow BudgetCell
-> Ordering)
-> (PeriodicReportRow BudgetCell
-> PeriodicReportRow BudgetCell -> Ordering)
-> PeriodicReportRow BudgetCell
-> PeriodicReportRow BudgetCell
-> Ordering
forall a b. (a -> b) -> a -> b
$ (PeriodicReportRow BudgetCell -> Maybe Change)
-> PeriodicReportRow BudgetCell
-> PeriodicReportRow BudgetCell
-> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing (BudgetCell -> Maybe Change
forall a b. (a, b) -> a
fst (BudgetCell -> Maybe Change)
-> (PeriodicReportRow BudgetCell -> BudgetCell)
-> PeriodicReportRow BudgetCell
-> Maybe Change
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PeriodicReportRow BudgetCell -> BudgetCell
forall a b c d e f. (a, b, c, d, e, f) -> e
fifth6))
where
maybeflip :: (a -> a -> c) -> a -> a -> c
maybeflip = if ReportOpts -> Maybe NormalSign
normalbalance_ ReportOpts
ropts 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
sortByAccountDeclaration :: [(AccountName, b, c, d, e, f)] -> [(AccountName, b, c, d, e, f)]
sortByAccountDeclaration rows :: [(AccountName, b, c, d, e, f)]
rows = [(AccountName, b, c, d, e, f)]
sortedrows
where
(unbudgetedrow :: [(AccountName, b, c, d, e, f)]
unbudgetedrow,rows' :: [(AccountName, b, c, d, e, f)]
rows') = ((AccountName, b, c, d, e, f) -> Bool)
-> [(AccountName, b, c, d, e, f)]
-> ([(AccountName, b, c, d, e, f)], [(AccountName, b, c, d, e, f)])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition ((AccountName -> AccountName -> Bool
forall a. Eq a => a -> a -> Bool
=="<unbudgeted>")(AccountName -> Bool)
-> ((AccountName, b, c, d, e, f) -> AccountName)
-> (AccountName, b, c, d, e, f)
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.(AccountName, b, c, d, e, f) -> AccountName
forall a b c d e f. (a, b, c, d, e, f) -> a
first6) [(AccountName, b, c, d, e, f)]
rows
anamesandrows :: [(AccountName, (AccountName, b, c, d, e, f))]
anamesandrows = [((AccountName, b, c, d, e, f) -> AccountName
forall a b c d e f. (a, b, c, d, e, f) -> a
first6 (AccountName, b, c, d, e, f)
r, (AccountName, b, c, d, e, f)
r) | (AccountName, b, c, d, e, f)
r <- [(AccountName, b, c, d, e, f)]
rows']
anames :: [AccountName]
anames = ((AccountName, (AccountName, b, c, d, e, f)) -> AccountName)
-> [(AccountName, (AccountName, b, c, d, e, f))] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map (AccountName, (AccountName, b, c, d, e, f)) -> AccountName
forall a b. (a, b) -> a
fst [(AccountName, (AccountName, b, c, d, e, f))]
anamesandrows
sortedanames :: [AccountName]
sortedanames = Journal -> Bool -> [AccountName] -> [AccountName]
sortAccountNamesByDeclaration Journal
j (ReportOpts -> Bool
tree_ ReportOpts
ropts) [AccountName]
anames
sortedrows :: [(AccountName, b, c, d, e, f)]
sortedrows = [(AccountName, b, c, d, e, f)]
unbudgetedrow [(AccountName, b, c, d, e, f)]
-> [(AccountName, b, c, d, e, f)] -> [(AccountName, b, c, d, e, f)]
forall a. [a] -> [a] -> [a]
++ [AccountName]
-> [(AccountName, (AccountName, b, c, d, e, f))]
-> [(AccountName, b, c, d, e, f)]
forall b. [AccountName] -> [(AccountName, b)] -> [b]
sortAccountItemsLike [AccountName]
sortedanames [(AccountName, (AccountName, b, c, d, e, f))]
anamesandrows
budgetJournal :: Bool -> ReportOpts -> DateSpan -> Journal -> Journal
budgetJournal :: Bool -> ReportOpts -> DateSpan -> Journal -> Journal
budgetJournal assrt :: Bool
assrt _ropts :: ReportOpts
_ropts reportspan :: DateSpan
reportspan j :: Journal
j =
(String -> Journal)
-> (Journal -> Journal) -> Either String Journal -> Journal
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> Journal
forall a. String -> a
error' Journal -> Journal
forall a. a -> a
id (Either String Journal -> Journal)
-> Either String Journal -> Journal
forall a b. (a -> b) -> a -> b
$ Bool -> Journal -> Either String Journal
journalBalanceTransactions Bool
assrt Journal
j{ jtxns :: [Transaction]
jtxns = [Transaction]
budgetts }
where
budgetspan :: DateSpan
budgetspan = String -> DateSpan -> DateSpan
forall a. Show a => String -> a -> a
dbg2 "budgetspan" (DateSpan -> DateSpan) -> DateSpan -> DateSpan
forall a b. (a -> b) -> a -> b
$ DateSpan
reportspan
budgetts :: [Transaction]
budgetts =
String -> [Transaction] -> [Transaction]
forall a. Show a => String -> a -> a
dbg1 "budgetts" ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
forall a b. (a -> b) -> a -> b
$
[Transaction -> Transaction
makeBudgetTxn Transaction
t
| PeriodicTransaction
pt <- Journal -> [PeriodicTransaction]
jperiodictxns Journal
j
, Transaction
t <- PeriodicTransaction -> DateSpan -> [Transaction]
runPeriodicTransaction PeriodicTransaction
pt DateSpan
budgetspan
]
makeBudgetTxn :: Transaction -> Transaction
makeBudgetTxn t :: Transaction
t = Transaction -> Transaction
txnTieKnot (Transaction -> Transaction) -> Transaction -> Transaction
forall a b. (a -> b) -> a -> b
$ Transaction
t { tdescription :: AccountName
tdescription = String -> AccountName
T.pack "Budget transaction" }
budgetRollUp :: [AccountName] -> Bool -> Journal -> Journal
budgetRollUp :: [AccountName] -> Bool -> Journal -> Journal
budgetRollUp budgetedaccts :: [AccountName]
budgetedaccts showunbudgeted :: Bool
showunbudgeted j :: Journal
j = Journal
j { jtxns :: [Transaction]
jtxns = Transaction -> Transaction
remapTxn (Transaction -> Transaction) -> [Transaction] -> [Transaction]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Journal -> [Transaction]
jtxns Journal
j }
where
remapTxn :: Transaction -> Transaction
remapTxn = ([Posting] -> [Posting]) -> Transaction -> Transaction
mapPostings ((Posting -> Posting) -> [Posting] -> [Posting]
forall a b. (a -> b) -> [a] -> [b]
map Posting -> Posting
remapPosting)
where
mapPostings :: ([Posting] -> [Posting]) -> Transaction -> Transaction
mapPostings f :: [Posting] -> [Posting]
f t :: Transaction
t = Transaction -> Transaction
txnTieKnot (Transaction -> Transaction) -> Transaction -> Transaction
forall a b. (a -> b) -> a -> b
$ Transaction
t { tpostings :: [Posting]
tpostings = [Posting] -> [Posting]
f ([Posting] -> [Posting]) -> [Posting] -> [Posting]
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t }
remapPosting :: Posting -> Posting
remapPosting p :: Posting
p = Posting
p { paccount :: AccountName
paccount = AccountName -> AccountName
remapAccount (AccountName -> AccountName) -> AccountName -> AccountName
forall a b. (a -> b) -> a -> b
$ Posting -> AccountName
paccount Posting
p, poriginal :: Maybe Posting
poriginal = Posting -> Maybe Posting
forall a. a -> Maybe a
Just (Posting -> Maybe Posting)
-> (Maybe Posting -> Posting) -> Maybe Posting -> Maybe Posting
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Posting -> Maybe Posting -> Posting
forall a. a -> Maybe a -> a
fromMaybe Posting
p (Maybe Posting -> Maybe Posting) -> Maybe Posting -> Maybe Posting
forall a b. (a -> b) -> a -> b
$ Posting -> Maybe Posting
poriginal Posting
p }
where
remapAccount :: AccountName -> AccountName
remapAccount a :: AccountName
a
| Bool
hasbudget = AccountName
a
| Bool
hasbudgetedparent = if Bool
showunbudgeted then AccountName
a else AccountName
budgetedparent
| Bool
otherwise = if Bool
showunbudgeted then AccountName
u AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
acctsep AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
a else AccountName
u
where
hasbudget :: Bool
hasbudget = AccountName
a AccountName -> [AccountName] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [AccountName]
budgetedaccts
hasbudgetedparent :: Bool
hasbudgetedparent = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ AccountName -> Bool
T.null AccountName
budgetedparent
budgetedparent :: AccountName
budgetedparent = AccountName -> [AccountName] -> AccountName
forall a. a -> [a] -> a
headDef "" ([AccountName] -> AccountName) -> [AccountName] -> AccountName
forall a b. (a -> b) -> a -> b
$ (AccountName -> Bool) -> [AccountName] -> [AccountName]
forall a. (a -> Bool) -> [a] -> [a]
filter (AccountName -> [AccountName] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [AccountName]
budgetedaccts) ([AccountName] -> [AccountName]) -> [AccountName] -> [AccountName]
forall a b. (a -> b) -> a -> b
$ AccountName -> [AccountName]
parentAccountNames AccountName
a
u :: AccountName
u = AccountName
unbudgetedAccountName
combineBudgetAndActual :: MultiBalanceReport -> MultiBalanceReport -> BudgetReport
combineBudgetAndActual :: MultiBalanceReport -> MultiBalanceReport -> BudgetReport
combineBudgetAndActual
(MultiBalanceReport (budgetperiods :: [DateSpan]
budgetperiods, budgetrows :: [MultiBalanceReportRow]
budgetrows, (budgettots :: [Change]
budgettots, budgetgrandtot :: Change
budgetgrandtot, budgetgrandavg :: Change
budgetgrandavg)))
(MultiBalanceReport (actualperiods :: [DateSpan]
actualperiods, actualrows :: [MultiBalanceReportRow]
actualrows, (actualtots :: [Change]
actualtots, actualgrandtot :: Change
actualgrandtot, actualgrandavg :: Change
actualgrandavg))) =
let
periods :: [DateSpan]
periods = [DateSpan] -> [DateSpan]
forall a. Eq a => [a] -> [a]
nub ([DateSpan] -> [DateSpan]) -> [DateSpan] -> [DateSpan]
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> [DateSpan]
forall a. Ord a => [a] -> [a]
sort ([DateSpan] -> [DateSpan]) -> [DateSpan] -> [DateSpan]
forall a b. (a -> b) -> a -> b
$ (DateSpan -> Bool) -> [DateSpan] -> [DateSpan]
forall a. (a -> Bool) -> [a] -> [a]
filter (DateSpan -> DateSpan -> Bool
forall a. Eq a => a -> a -> Bool
/= DateSpan
nulldatespan) ([DateSpan] -> [DateSpan]) -> [DateSpan] -> [DateSpan]
forall a b. (a -> b) -> a -> b
$ [DateSpan]
budgetperiods [DateSpan] -> [DateSpan] -> [DateSpan]
forall a. [a] -> [a] -> [a]
++ [DateSpan]
actualperiods
rows1 :: [PeriodicReportRow BudgetCell]
rows1 =
[ (AccountName
acct, AccountName
treeacct, Int
treeindent, [BudgetCell]
amtandgoals, BudgetCell
totamtandgoal, BudgetCell
avgamtandgoal)
| (acct :: AccountName
acct, treeacct :: AccountName
treeacct, treeindent :: Int
treeindent, actualamts :: [Change]
actualamts, actualtot :: Change
actualtot, actualavg :: Change
actualavg) <- [MultiBalanceReportRow]
actualrows
, let mbudgetgoals :: Maybe MultiBalanceReportTotals
mbudgetgoals = AccountName
-> Map AccountName MultiBalanceReportTotals
-> Maybe MultiBalanceReportTotals
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup AccountName
acct Map AccountName MultiBalanceReportTotals
budgetGoalsByAcct :: Maybe ([BudgetGoal], BudgetTotal, BudgetAverage)
, let budgetmamts :: [Maybe Change]
budgetmamts = [Maybe Change]
-> (MultiBalanceReportTotals -> [Maybe Change])
-> Maybe MultiBalanceReportTotals
-> [Maybe Change]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Int -> Maybe Change -> [Maybe Change]
forall a. Int -> a -> [a]
replicate ([DateSpan] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [DateSpan]
periods) Maybe Change
forall a. Maybe a
Nothing) ((Change -> Maybe Change) -> [Change] -> [Maybe Change]
forall a b. (a -> b) -> [a] -> [b]
map Change -> Maybe Change
forall a. a -> Maybe a
Just ([Change] -> [Maybe Change])
-> (MultiBalanceReportTotals -> [Change])
-> MultiBalanceReportTotals
-> [Maybe Change]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MultiBalanceReportTotals -> [Change]
forall a b c. (a, b, c) -> a
first3) Maybe MultiBalanceReportTotals
mbudgetgoals :: [Maybe BudgetGoal]
, let mbudgettot :: Maybe Change
mbudgettot = Maybe Change
-> (MultiBalanceReportTotals -> Maybe Change)
-> Maybe MultiBalanceReportTotals
-> Maybe Change
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Maybe Change
forall a. Maybe a
Nothing (Change -> Maybe Change
forall a. a -> Maybe a
Just (Change -> Maybe Change)
-> (MultiBalanceReportTotals -> Change)
-> MultiBalanceReportTotals
-> Maybe Change
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MultiBalanceReportTotals -> Change
forall a b c. (a, b, c) -> b
second3) Maybe MultiBalanceReportTotals
mbudgetgoals :: Maybe BudgetTotal
, let mbudgetavg :: Maybe Change
mbudgetavg = Maybe Change
-> (MultiBalanceReportTotals -> Maybe Change)
-> Maybe MultiBalanceReportTotals
-> Maybe Change
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Maybe Change
forall a. Maybe a
Nothing (Change -> Maybe Change
forall a. a -> Maybe a
Just (Change -> Maybe Change)
-> (MultiBalanceReportTotals -> Change)
-> MultiBalanceReportTotals
-> Maybe Change
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MultiBalanceReportTotals -> Change
forall a b c. (a, b, c) -> c
third3) Maybe MultiBalanceReportTotals
mbudgetgoals :: Maybe BudgetAverage
, let acctBudgetByPeriod :: Map DateSpan Change
acctBudgetByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [ (DateSpan
p,Change
budgetamt) | (p :: DateSpan
p, Just budgetamt :: Change
budgetamt) <- [DateSpan] -> [Maybe Change] -> [(DateSpan, Maybe Change)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
budgetperiods [Maybe Change]
budgetmamts ] :: Map DateSpan BudgetGoal
, let acctActualByPeriod :: Map DateSpan Change
acctActualByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [ (DateSpan
p,Change
actualamt) | (p :: DateSpan
p, Just actualamt :: Change
actualamt) <- [DateSpan] -> [Maybe Change] -> [(DateSpan, Maybe Change)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
actualperiods ((Change -> Maybe Change) -> [Change] -> [Maybe Change]
forall a b. (a -> b) -> [a] -> [b]
map Change -> Maybe Change
forall a. a -> Maybe a
Just [Change]
actualamts) ] :: Map DateSpan Change
, let amtandgoals :: [BudgetCell]
amtandgoals = [ (DateSpan -> Map DateSpan Change -> Maybe Change
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
acctActualByPeriod, DateSpan -> Map DateSpan Change -> Maybe Change
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
acctBudgetByPeriod) | DateSpan
p <- [DateSpan]
periods ] :: [(Maybe Change, Maybe BudgetGoal)]
, let totamtandgoal :: BudgetCell
totamtandgoal = (Change -> Maybe Change
forall a. a -> Maybe a
Just Change
actualtot, Maybe Change
mbudgettot)
, let avgamtandgoal :: BudgetCell
avgamtandgoal = (Change -> Maybe Change
forall a. a -> Maybe a
Just Change
actualavg, Maybe Change
mbudgetavg)
]
where
Map AccountName MultiBalanceReportTotals
budgetGoalsByAcct :: Map AccountName ([BudgetGoal], BudgetTotal, BudgetAverage) =
[(AccountName, MultiBalanceReportTotals)]
-> Map AccountName MultiBalanceReportTotals
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [ (AccountName
acct, ([Change]
amts, Change
tot, Change
avg)) | (acct :: AccountName
acct, _, _, amts :: [Change]
amts, tot :: Change
tot, avg :: Change
avg) <- [MultiBalanceReportRow]
budgetrows ]
rows2 :: [(AccountName, AccountName, Int, [BudgetCell],
(Maybe a, Maybe Change), (Maybe a, Maybe Change))]
rows2 =
[ (AccountName
acct, AccountName
treeacct, Int
treeindent, [BudgetCell]
amtandgoals, (Maybe a, Maybe Change)
forall a. (Maybe a, Maybe Change)
totamtandgoal, (Maybe a, Maybe Change)
forall a. (Maybe a, Maybe Change)
avgamtandgoal)
| (acct :: AccountName
acct, treeacct :: AccountName
treeacct, treeindent :: Int
treeindent, budgetgoals :: [Change]
budgetgoals, budgettot :: Change
budgettot, budgetavg :: Change
budgetavg) <- [MultiBalanceReportRow]
budgetrows
, Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ AccountName
acct AccountName -> [AccountName] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [AccountName]
acctsdone
, let acctBudgetByPeriod :: Map DateSpan Change
acctBudgetByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(DateSpan, Change)] -> Map DateSpan Change)
-> [(DateSpan, Change)] -> Map DateSpan Change
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> [Change] -> [(DateSpan, Change)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
budgetperiods [Change]
budgetgoals :: Map DateSpan BudgetGoal
, let amtandgoals :: [BudgetCell]
amtandgoals = [ (Maybe Change
forall a. Maybe a
Nothing, DateSpan -> Map DateSpan Change -> Maybe Change
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
acctBudgetByPeriod) | DateSpan
p <- [DateSpan]
periods ] :: [(Maybe Change, Maybe BudgetGoal)]
, let totamtandgoal :: (Maybe a, Maybe Change)
totamtandgoal = (Maybe a
forall a. Maybe a
Nothing, Change -> Maybe Change
forall a. a -> Maybe a
Just Change
budgettot)
, let avgamtandgoal :: (Maybe a, Maybe Change)
avgamtandgoal = (Maybe a
forall a. Maybe a
Nothing, Change -> Maybe Change
forall a. a -> Maybe a
Just Change
budgetavg)
]
where
acctsdone :: [AccountName]
acctsdone = (PeriodicReportRow BudgetCell -> AccountName)
-> [PeriodicReportRow BudgetCell] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow BudgetCell -> AccountName
forall a b c d e f. (a, b, c, d, e, f) -> a
first6 [PeriodicReportRow BudgetCell]
rows1
[PeriodicReportRow BudgetCell]
rows :: [PeriodicReportRow (Maybe Change, Maybe BudgetGoal)] =
(PeriodicReportRow BudgetCell
-> PeriodicReportRow BudgetCell -> Ordering)
-> [PeriodicReportRow BudgetCell] -> [PeriodicReportRow BudgetCell]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy ((PeriodicReportRow BudgetCell -> AccountName)
-> PeriodicReportRow BudgetCell
-> PeriodicReportRow BudgetCell
-> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing PeriodicReportRow BudgetCell -> AccountName
forall a b c d e f. (a, b, c, d, e, f) -> a
first6) ([PeriodicReportRow BudgetCell] -> [PeriodicReportRow BudgetCell])
-> [PeriodicReportRow BudgetCell] -> [PeriodicReportRow BudgetCell]
forall a b. (a -> b) -> a -> b
$ [PeriodicReportRow BudgetCell]
rows1 [PeriodicReportRow BudgetCell]
-> [PeriodicReportRow BudgetCell] -> [PeriodicReportRow BudgetCell]
forall a. [a] -> [a] -> [a]
++ [PeriodicReportRow BudgetCell]
forall a a.
[(AccountName, AccountName, Int, [BudgetCell],
(Maybe a, Maybe Change), (Maybe a, Maybe Change))]
rows2
totalrow :: PeriodicReportRow BudgetCell
totalrow =
( ""
, ""
, 0
, [ (DateSpan -> Map DateSpan Change -> Maybe Change
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
totActualByPeriod, DateSpan -> Map DateSpan Change -> Maybe Change
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
totBudgetByPeriod) | DateSpan
p <- [DateSpan]
periods ] :: [(Maybe Total, Maybe BudgetTotal)]
, ( Change -> Maybe Change
forall a. a -> Maybe a
Just Change
actualgrandtot, Change -> Maybe Change
forall a. a -> Maybe a
Just Change
budgetgrandtot ) :: (Maybe Total, Maybe BudgetTotal)
, ( Change -> Maybe Change
forall a. a -> Maybe a
Just Change
actualgrandavg, Change -> Maybe Change
forall a. a -> Maybe a
Just Change
budgetgrandavg ) :: (Maybe Total, Maybe BudgetTotal)
)
where
totBudgetByPeriod :: Map DateSpan Change
totBudgetByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(DateSpan, Change)] -> Map DateSpan Change)
-> [(DateSpan, Change)] -> Map DateSpan Change
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> [Change] -> [(DateSpan, Change)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
budgetperiods [Change]
budgettots :: Map DateSpan BudgetTotal
totActualByPeriod :: Map DateSpan Change
totActualByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(DateSpan, Change)] -> Map DateSpan Change)
-> [(DateSpan, Change)] -> Map DateSpan Change
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> [Change] -> [(DateSpan, Change)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
actualperiods [Change]
actualtots :: Map DateSpan Change
in
([DateSpan], [PeriodicReportRow BudgetCell],
PeriodicReportRow BudgetCell)
-> BudgetReport
forall a.
([DateSpan], [PeriodicReportRow a], PeriodicReportRow a)
-> PeriodicReport a
PeriodicReport
( [DateSpan]
periods
, [PeriodicReportRow BudgetCell]
rows
, PeriodicReportRow BudgetCell
totalrow
)
budgetReportSpan :: BudgetReport -> DateSpan
budgetReportSpan :: BudgetReport -> DateSpan
budgetReportSpan (PeriodicReport ([], _, _)) = Maybe Day -> Maybe Day -> DateSpan
DateSpan Maybe Day
forall a. Maybe a
Nothing Maybe Day
forall a. Maybe a
Nothing
budgetReportSpan (PeriodicReport (spans :: [DateSpan]
spans, _, _)) = Maybe Day -> Maybe Day -> DateSpan
DateSpan (DateSpan -> Maybe Day
spanStart (DateSpan -> Maybe Day) -> DateSpan -> Maybe Day
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> DateSpan
forall a. [a] -> a
head [DateSpan]
spans) (DateSpan -> Maybe Day
spanEnd (DateSpan -> Maybe Day) -> DateSpan -> Maybe Day
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> DateSpan
forall a. [a] -> a
last [DateSpan]
spans)
budgetReportAsText :: ReportOpts -> BudgetReport -> String
budgetReportAsText :: ReportOpts -> BudgetReport -> String
budgetReportAsText ropts :: ReportOpts
ropts@ReportOpts{..} budgetr :: BudgetReport
budgetr@(PeriodicReport ( _, rows :: [PeriodicReportRow BudgetCell]
rows, _)) =
String
title String -> String -> String
forall a. [a] -> [a] -> [a]
++ "\n\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++
ReportOpts
-> (BudgetCell -> String)
-> Table String String BudgetCell
-> String
forall a.
ReportOpts -> (a -> String) -> Table String String a -> String
tableAsText ReportOpts
ropts BudgetCell -> String
showcell (Table String String BudgetCell -> Table String String BudgetCell
forall rh a. Table rh rh a -> Table rh rh a
maybetranspose (Table String String BudgetCell -> Table String String BudgetCell)
-> Table String String BudgetCell -> Table String String BudgetCell
forall a b. (a -> b) -> a -> b
$ ReportOpts -> BudgetReport -> Table String String BudgetCell
budgetReportAsTable ReportOpts
ropts BudgetReport
budgetr)
where
multiperiod :: Bool
multiperiod = Interval
interval_ Interval -> Interval -> Bool
forall a. Eq a => a -> a -> Bool
/= Interval
NoInterval
title :: String
title = String -> String -> String -> String
forall r. PrintfType r => String -> r
printf "Budget performance in %s%s:"
(DateSpan -> String
showDateSpan (DateSpan -> String) -> DateSpan -> String
forall a b. (a -> b) -> a -> b
$ BudgetReport -> DateSpan
budgetReportSpan BudgetReport
budgetr)
(case Maybe ValuationType
value_ of
Just (AtCost _mc :: Maybe AccountName
_mc) -> ", valued at cost"
Just (AtEnd _mc :: Maybe AccountName
_mc) -> ", valued at period ends"
Just (AtNow _mc :: Maybe AccountName
_mc) -> ", current value"
Just (AtDefault _mc :: Maybe AccountName
_mc) | Bool
multiperiod -> ", valued at period ends"
Just (AtDefault _mc :: Maybe AccountName
_mc) -> ", current value"
Just (AtDate d :: Day
d _mc :: Maybe AccountName
_mc) -> ", valued at "String -> String -> String
forall a. [a] -> [a] -> [a]
++Day -> String
showDate Day
d
Nothing -> "")
actualwidth :: Int
actualwidth =
[Int] -> Int
forall a. Integral a => [a] -> a
maximum' [ Int -> (Change -> Int) -> Maybe Change -> Int
forall b a. b -> (a -> b) -> Maybe a -> b
maybe 0 (String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (String -> Int) -> (Change -> String) -> Change -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Change -> String
showMixedAmountOneLineWithoutPrice) Maybe Change
amt
| (_, _, _, amtandgoals :: [BudgetCell]
amtandgoals, _, _) <- [PeriodicReportRow BudgetCell]
rows
, (amt :: Maybe Change
amt, _) <- [BudgetCell]
amtandgoals ]
budgetwidth :: Int
budgetwidth =
[Int] -> Int
forall a. Integral a => [a] -> a
maximum' [ Int -> (Change -> Int) -> Maybe Change -> Int
forall b a. b -> (a -> b) -> Maybe a -> b
maybe 0 (String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (String -> Int) -> (Change -> String) -> Change -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Change -> String
showMixedAmountOneLineWithoutPrice) Maybe Change
goal
| (_, _, _, amtandgoals :: [BudgetCell]
amtandgoals, _, _) <- [PeriodicReportRow BudgetCell]
rows
, (_, goal :: Maybe Change
goal) <- [BudgetCell]
amtandgoals ]
showcell :: (Maybe Change, Maybe BudgetGoal) -> String
showcell :: BudgetCell -> String
showcell (mactual :: Maybe Change
mactual, mbudget :: Maybe Change
mbudget) = String
actualstr String -> String -> String
forall a. [a] -> [a] -> [a]
++ " " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
budgetstr
where
percentwidth :: Int
percentwidth = 4
actual :: Change
actual = Change -> Maybe Change -> Change
forall a. a -> Maybe a -> a
fromMaybe 0 Maybe Change
mactual
actualstr :: String
actualstr = String -> String -> String
forall r. PrintfType r => String -> r
printf ("%"String -> String -> String
forall a. [a] -> [a] -> [a]
++Int -> String
forall a. Show a => a -> String
show Int
actualwidthString -> String -> String
forall a. [a] -> [a] -> [a]
++"s") (Change -> String
showamt Change
actual)
budgetstr :: String
budgetstr = case Maybe Change
mbudget of
Nothing -> Int -> Char -> String
forall a. Int -> a -> [a]
replicate (Int
percentwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ 7 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
budgetwidth) ' '
Just budget :: Change
budget ->
case Change -> Change -> Maybe Percentage
percentage Change
actual Change
budget of
Just pct :: Percentage
pct ->
String -> String -> String -> String
forall r. PrintfType r => String -> r
printf ("[%"String -> String -> String
forall a. [a] -> [a] -> [a]
++Int -> String
forall a. Show a => a -> String
show Int
percentwidthString -> String -> String
forall a. [a] -> [a] -> [a]
++"s%% of %"String -> String -> String
forall a. [a] -> [a] -> [a]
++Int -> String
forall a. Show a => a -> String
show Int
budgetwidthString -> String -> String
forall a. [a] -> [a] -> [a]
++"s]")
(Percentage -> String
forall a. Show a => a -> String
show (Percentage -> String) -> Percentage -> String
forall a b. (a -> b) -> a -> b
$ Word8 -> Percentage -> Percentage
forall i. Integral i => Word8 -> DecimalRaw i -> DecimalRaw i
roundTo 0 Percentage
pct) (Change -> String
showbudgetamt Change
budget)
Nothing ->
String -> String -> String
forall r. PrintfType r => String -> r
printf ("["String -> String -> String
forall a. [a] -> [a] -> [a]
++Int -> Char -> String
forall a. Int -> a -> [a]
replicate (Int
percentwidthInt -> Int -> Int
forall a. Num a => a -> a -> a
+5) ' 'String -> String -> String
forall a. [a] -> [a] -> [a]
++"%"String -> String -> String
forall a. [a] -> [a] -> [a]
++Int -> String
forall a. Show a => a -> String
show Int
budgetwidthString -> String -> String
forall a. [a] -> [a] -> [a]
++"s]")
(Change -> String
showbudgetamt Change
budget)
percentage :: Change -> BudgetGoal -> Maybe Percentage
percentage :: Change -> Change -> Maybe Percentage
percentage actual :: Change
actual budget :: Change
budget =
case (Change -> Change
maybecost (Change -> Change) -> Change -> Change
forall a b. (a -> b) -> a -> b
$ Change -> Change
normaliseMixedAmount Change
actual, Change -> Change
maybecost (Change -> Change) -> Change -> Change
forall a b. (a -> b) -> a -> b
$ Change -> Change
normaliseMixedAmount Change
budget) of
(Mixed [a :: Amount
a], Mixed [b :: Amount
b]) | (Amount -> AccountName
acommodity Amount
a AccountName -> AccountName -> Bool
forall a. Eq a => a -> a -> Bool
== Amount -> AccountName
acommodity Amount
b Bool -> Bool -> Bool
|| Amount -> Bool
isZeroAmount Amount
a) Bool -> Bool -> Bool
&& Bool -> Bool
not (Amount -> Bool
isZeroAmount Amount
b)
-> Percentage -> Maybe Percentage
forall a. a -> Maybe a
Just (Percentage -> Maybe Percentage) -> Percentage -> Maybe Percentage
forall a b. (a -> b) -> a -> b
$ 100 Percentage -> Percentage -> Percentage
forall a. Num a => a -> a -> a
* Amount -> Percentage
aquantity Amount
a Percentage -> Percentage -> Percentage
forall a. Fractional a => a -> a -> a
/ Amount -> Percentage
aquantity Amount
b
_ ->
Maybe Percentage
forall a. Maybe a
Nothing
where
maybecost :: Change -> Change
maybecost = if ReportOpts -> Bool
valuationTypeIsCost ReportOpts
ropts then Change -> Change
costOfMixedAmount else Change -> Change
forall a. a -> a
id
showamt :: MixedAmount -> String
showamt :: Change -> String
showamt | Bool
color_ = Change -> String
cshowMixedAmountOneLineWithoutPrice
| Bool
otherwise = Change -> String
showMixedAmountOneLineWithoutPrice
showbudgetamt :: Change -> String
showbudgetamt = Change -> String
showMixedAmountOneLineWithoutPrice
maybetranspose :: Table rh rh a -> Table rh rh a
maybetranspose | Bool
transpose_ = \(Table rh :: Header rh
rh ch :: Header rh
ch vals :: [[a]]
vals) -> Header rh -> Header rh -> [[a]] -> Table rh rh a
forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
Table Header rh
ch Header rh
rh ([[a]] -> [[a]]
forall a. [[a]] -> [[a]]
transpose [[a]]
vals)
| Bool
otherwise = Table rh rh a -> Table rh rh a
forall a. a -> a
id
budgetReportAsTable :: ReportOpts -> BudgetReport -> Table String String (Maybe MixedAmount, Maybe MixedAmount)
budgetReportAsTable :: ReportOpts -> BudgetReport -> Table String String BudgetCell
budgetReportAsTable
ropts :: ReportOpts
ropts
(PeriodicReport
( periods :: [DateSpan]
periods
, rows :: [PeriodicReportRow BudgetCell]
rows
, (_, _, _, coltots :: [BudgetCell]
coltots, grandtot :: BudgetCell
grandtot, grandavg :: BudgetCell
grandavg)
)) =
Table String String BudgetCell -> Table String String BudgetCell
forall ch. Table String ch BudgetCell -> Table String ch BudgetCell
addtotalrow (Table String String BudgetCell -> Table String String BudgetCell)
-> Table String String BudgetCell -> Table String String BudgetCell
forall a b. (a -> b) -> a -> b
$
Header String
-> Header String
-> [[BudgetCell]]
-> Table String String BudgetCell
forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
Table
(Properties -> [Header String] -> Header String
forall h. Properties -> [Header h] -> Header h
T.Group Properties
NoLine ([Header String] -> Header String)
-> [Header String] -> Header String
forall a b. (a -> b) -> a -> b
$ (String -> Header String) -> [String] -> [Header String]
forall a b. (a -> b) -> [a] -> [b]
map String -> Header String
forall h. h -> Header h
Header [String]
accts)
(Properties -> [Header String] -> Header String
forall h. Properties -> [Header h] -> Header h
T.Group Properties
NoLine ([Header String] -> Header String)
-> [Header String] -> Header String
forall a b. (a -> b) -> a -> b
$ (String -> Header String) -> [String] -> [Header String]
forall a b. (a -> b) -> [a] -> [b]
map String -> Header String
forall h. h -> Header h
Header [String]
colheadings)
((PeriodicReportRow BudgetCell -> [BudgetCell])
-> [PeriodicReportRow BudgetCell] -> [[BudgetCell]]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow BudgetCell -> [BudgetCell]
forall a b c a. (a, b, c, [a], a, a) -> [a]
rowvals [PeriodicReportRow BudgetCell]
rows)
where
colheadings :: [String]
colheadings = (DateSpan -> String) -> [DateSpan] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map DateSpan -> String
showDateSpanMonthAbbrev [DateSpan]
periods
[String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ (if ReportOpts -> Bool
row_total_ ReportOpts
ropts then [" Total"] else [])
[String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ (if ReportOpts -> Bool
average_ ReportOpts
ropts then ["Average"] else [])
accts :: [String]
accts = (PeriodicReportRow BudgetCell -> String)
-> [PeriodicReportRow BudgetCell] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow BudgetCell -> String
forall d e f. (AccountName, AccountName, Int, d, e, f) -> String
renderacct [PeriodicReportRow BudgetCell]
rows
renderacct :: (AccountName, AccountName, Int, d, e, f) -> String
renderacct (a :: AccountName
a,a' :: AccountName
a',i :: Int
i,_,_,_)
| ReportOpts -> Bool
tree_ ReportOpts
ropts = Int -> Char -> String
forall a. Int -> a -> [a]
replicate ((Int
iInt -> Int -> Int
forall a. Num a => a -> a -> a
-1)Int -> Int -> Int
forall a. Num a => a -> a -> a
*2) ' ' String -> String -> String
forall a. [a] -> [a] -> [a]
++ AccountName -> String
T.unpack AccountName
a'
| Bool
otherwise = AccountName -> String
T.unpack (AccountName -> String) -> AccountName -> String
forall a b. (a -> b) -> a -> b
$ ReportOpts -> AccountName -> AccountName
maybeAccountNameDrop ReportOpts
ropts AccountName
a
rowvals :: (a, b, c, [a], a, a) -> [a]
rowvals (_,_,_,as :: [a]
as,rowtot :: a
rowtot,rowavg :: a
rowavg) = [a]
as
[a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ (if ReportOpts -> Bool
row_total_ ReportOpts
ropts then [a
rowtot] else [])
[a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ (if ReportOpts -> Bool
average_ ReportOpts
ropts then [a
rowavg] else [])
addtotalrow :: Table String ch BudgetCell -> Table String ch BudgetCell
addtotalrow | ReportOpts -> Bool
no_total_ ReportOpts
ropts = Table String ch BudgetCell -> Table String ch BudgetCell
forall a. a -> a
id
| Bool
otherwise = (Table String ch BudgetCell
-> SemiTable String BudgetCell -> Table String ch BudgetCell
forall rh ch a. Table rh ch a -> SemiTable rh a -> Table rh ch a
+----+ (String -> [BudgetCell] -> SemiTable String BudgetCell
forall rh a. rh -> [a] -> SemiTable rh a
row "" ([BudgetCell] -> SemiTable String BudgetCell)
-> [BudgetCell] -> SemiTable String BudgetCell
forall a b. (a -> b) -> a -> b
$
[BudgetCell]
coltots
[BudgetCell] -> [BudgetCell] -> [BudgetCell]
forall a. [a] -> [a] -> [a]
++ (if ReportOpts -> Bool
row_total_ ReportOpts
ropts Bool -> Bool -> Bool
&& Bool -> Bool
not ([BudgetCell] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [BudgetCell]
coltots) then [BudgetCell
grandtot] else [])
[BudgetCell] -> [BudgetCell] -> [BudgetCell]
forall a. [a] -> [a] -> [a]
++ (if ReportOpts -> Bool
average_ ReportOpts
ropts Bool -> Bool -> Bool
&& Bool -> Bool
not ([BudgetCell] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [BudgetCell]
coltots) then [BudgetCell
grandavg] else [])
))
maybeAccountNameDrop :: ReportOpts -> AccountName -> AccountName
maybeAccountNameDrop :: ReportOpts -> AccountName -> AccountName
maybeAccountNameDrop opts :: ReportOpts
opts a :: AccountName
a | ReportOpts -> Bool
flat_ ReportOpts
opts = Int -> AccountName -> AccountName
accountNameDrop (ReportOpts -> Int
drop_ ReportOpts
opts) AccountName
a
| Bool
otherwise = AccountName
a
tests_BudgetReport :: TestTree
tests_BudgetReport = String -> [TestTree] -> TestTree
tests "BudgetReport" [
]