{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Hledger.Reports.BudgetReport (
BudgetGoal,
BudgetTotal,
BudgetAverage,
BudgetCell,
BudgetReportRow,
BudgetReport,
budgetReport,
budgetReportAsTable,
budgetReportAsText,
budgetReportAsCsv,
combineBudgetAndActual,
tests_BudgetReport
)
where
import Control.Applicative ((<|>))
import Control.Arrow ((***))
import Data.Decimal (roundTo)
import Data.Function (on)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import Data.List (find, partition, transpose, foldl', maximumBy)
import Data.List.Extra (nubSort)
import Data.Maybe (fromMaybe, catMaybes, isJust)
import Data.Map (Map)
import qualified Data.Map as Map
import qualified Data.Set as S
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TB
import Safe (minimumDef)
import qualified Text.Tabular.AsciiWide as Tab
import Hledger.Data
import Hledger.Utils
import Hledger.Reports.ReportOptions
import Hledger.Reports.ReportTypes
import Hledger.Reports.MultiBalanceReport
import Data.Ord (comparing)
import Control.Monad ((>=>))
type BudgetGoal = Change
type BudgetTotal = Total
type BudgetAverage = Average
type BudgetCell = (Maybe Change, Maybe BudgetGoal)
type BudgetReportRow = PeriodicReportRow DisplayName BudgetCell
type BudgetReport = PeriodicReport DisplayName BudgetCell
type BudgetDisplayCell = (WideBuilder, Maybe (WideBuilder, Maybe WideBuilder))
type BudgetDisplayRow = [BudgetDisplayCell]
type BudgetShowMixed = MixedAmount -> [WideBuilder]
type BudgetPercBudget = Change -> BudgetGoal -> [Maybe Percentage]
budgetReport :: ReportSpec -> BalancingOpts -> DateSpan -> Journal -> BudgetReport
budgetReport :: ReportSpec -> BalancingOpts -> DateSpan -> Journal -> BudgetReport
budgetReport ReportSpec
rspec BalancingOpts
bopts DateSpan
reportspan Journal
j = forall a. Show a => String -> a -> a
dbg4 String
"sortedbudgetreport" BudgetReport
budgetreport
where
ropts :: ReportOpts
ropts = (ReportSpec -> ReportOpts
_rsReportOpts ReportSpec
rspec){ accountlistmode_ :: AccountListMode
accountlistmode_ = AccountListMode
ALTree }
showunbudgeted :: Bool
showunbudgeted = ReportOpts -> Bool
empty_ ReportOpts
ropts
budgetedaccts :: Set Text
budgetedaccts =
forall a. Show a => String -> a -> a
dbg3 String
"budgetedacctsinperiod" forall a b. (a -> b) -> a -> b
$
forall a. Ord a => [a] -> Set a
S.fromList forall a b. (a -> b) -> a -> b
$
[Text] -> [Text]
expandAccountNames forall a b. (a -> b) -> a -> b
$
[Posting] -> [Text]
accountNamesFromPostings forall a b. (a -> b) -> a -> b
$
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Transaction -> [Posting]
tpostings forall a b. (a -> b) -> a -> b
$
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\PeriodicTransaction
pt -> Bool -> PeriodicTransaction -> DateSpan -> [Transaction]
runPeriodicTransaction Bool
False PeriodicTransaction
pt DateSpan
reportspan) forall a b. (a -> b) -> a -> b
$
Journal -> [PeriodicTransaction]
jperiodictxns Journal
j
actualj :: Journal
actualj = Set Text -> Bool -> Journal -> Journal
journalWithBudgetAccountNames Set Text
budgetedaccts Bool
showunbudgeted Journal
j
budgetj :: Journal
budgetj = BalancingOpts -> ReportOpts -> DateSpan -> Journal -> Journal
journalAddBudgetGoalTransactions BalancingOpts
bopts ReportOpts
ropts DateSpan
reportspan Journal
j
priceoracle :: PriceOracle
priceoracle = Bool -> Journal -> PriceOracle
journalPriceOracle (ReportOpts -> Bool
infer_prices_ ReportOpts
ropts) Journal
j
budgetgoalreport :: MultiBalanceReport
budgetgoalreport@(PeriodicReport [DateSpan]
_ [PeriodicReportRow DisplayName Change]
budgetgoalitems PeriodicReportRow () Change
budgetgoaltotals) =
forall a. Show a => String -> a -> a
dbg5 String
"budgetgoalreport" forall a b. (a -> b) -> a -> b
$ ReportSpec
-> Journal -> PriceOracle -> Set Text -> MultiBalanceReport
multiBalanceReportWith ReportSpec
rspec{_rsReportOpts :: ReportOpts
_rsReportOpts=ReportOpts
ropts{empty_ :: Bool
empty_=Bool
True}} Journal
budgetj PriceOracle
priceoracle forall a. Monoid a => a
mempty
budgetedacctsseen :: Set Text
budgetedacctsseen = forall a. Ord a => [a] -> Set a
S.fromList forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map forall a. PeriodicReportRow DisplayName a -> Text
prrFullName [PeriodicReportRow DisplayName Change]
budgetgoalitems
actualreport :: MultiBalanceReport
actualreport@(PeriodicReport [DateSpan]
actualspans [PeriodicReportRow DisplayName Change]
_ PeriodicReportRow () Change
_) =
forall a. Show a => String -> a -> a
dbg5 String
"actualreport" forall a b. (a -> b) -> a -> b
$ ReportSpec
-> Journal -> PriceOracle -> Set Text -> MultiBalanceReport
multiBalanceReportWith ReportSpec
rspec{_rsReportOpts :: ReportOpts
_rsReportOpts=ReportOpts
ropts{empty_ :: Bool
empty_=Bool
True}} Journal
actualj PriceOracle
priceoracle Set Text
budgetedacctsseen
budgetgoalreport' :: MultiBalanceReport
budgetgoalreport'
| ReportOpts -> Interval
interval_ ReportOpts
ropts forall a. Eq a => a -> a -> Bool
== Interval
NoInterval = forall a b.
[DateSpan]
-> [PeriodicReportRow a b]
-> PeriodicReportRow () b
-> PeriodicReport a b
PeriodicReport [DateSpan]
actualspans [PeriodicReportRow DisplayName Change]
budgetgoalitems PeriodicReportRow () Change
budgetgoaltotals
| Bool
otherwise = MultiBalanceReport
budgetgoalreport
budgetreport :: BudgetReport
budgetreport = ReportOpts
-> Journal
-> MultiBalanceReport
-> MultiBalanceReport
-> BudgetReport
combineBudgetAndActual ReportOpts
ropts Journal
j MultiBalanceReport
budgetgoalreport' MultiBalanceReport
actualreport
journalAddBudgetGoalTransactions :: BalancingOpts -> ReportOpts -> DateSpan -> Journal -> Journal
journalAddBudgetGoalTransactions :: BalancingOpts -> ReportOpts -> DateSpan -> Journal -> Journal
journalAddBudgetGoalTransactions BalancingOpts
bopts ReportOpts
ropts DateSpan
reportspan Journal
j =
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either forall a. String -> a
error' forall a. a -> a
id forall a b. (a -> b) -> a -> b
$
(Journal -> Either String Journal
journalStyleAmounts forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> BalancingOpts -> Journal -> Either String Journal
journalBalanceTransactions BalancingOpts
bopts) Journal
j{ jtxns :: [Transaction]
jtxns = [Transaction]
budgetts }
where
budgetspan :: DateSpan
budgetspan = forall a. Show a => String -> a -> a
dbg3 String
"budget span" forall a b. (a -> b) -> a -> b
$ Maybe EFDay -> Maybe EFDay -> DateSpan
DateSpan (Day -> EFDay
Exact forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe Day
mbudgetgoalsstartdate) (Day -> EFDay
Exact forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> DateSpan -> Maybe Day
spanEnd DateSpan
reportspan)
where
mbudgetgoalsstartdate :: Maybe Day
mbudgetgoalsstartdate =
case forall a. Ord a => a -> [a] -> a
minimumDef forall a. Maybe a
Nothing forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
filter forall a. Maybe a -> Bool
isJust [Bool -> Journal -> Maybe Day
journalStartDate Bool
False Journal
j, DateSpan -> Maybe Day
spanStart DateSpan
reportspan] of
Maybe Day
Nothing -> forall a. Maybe a
Nothing
Just Day
d -> forall a. a -> Maybe a
Just Day
d'
where
(Interval
intervl, DateSpan
spn) =
case [PeriodicTransaction]
budgetpts of
[] -> (Int -> Interval
Days Int
1, DateSpan
nulldatespan)
[PeriodicTransaction]
pts -> (PeriodicTransaction -> Interval
ptinterval PeriodicTransaction
pt, PeriodicTransaction -> DateSpan
ptspan PeriodicTransaction
pt)
where pt :: PeriodicTransaction
pt = forall (t :: * -> *) a.
Foldable t =>
(a -> a -> Ordering) -> t a -> a
maximumBy (forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing PeriodicTransaction -> Interval
ptinterval) [PeriodicTransaction]
pts
intervalstart :: Day
intervalstart = Interval -> Day -> Day
intervalBoundaryBefore Interval
intervl Day
d
d' :: Day
d' = forall a. Ord a => a -> a -> a
min Day
d forall a b. (a -> b) -> a -> b
$ forall b a. b -> (a -> b) -> Maybe a -> b
maybe Day
intervalstart (forall a. Ord a => a -> a -> a
max Day
intervalstart) forall a b. (a -> b) -> a -> b
$ DateSpan -> Maybe Day
spanStart DateSpan
spn
pat :: Text
pat = forall a. a -> Maybe a -> a
fromMaybe Text
"" forall a b. (a -> b) -> a -> b
$ forall a. Show a => String -> a -> a
dbg3 String
"budget pattern" forall a b. (a -> b) -> a -> b
$ Text -> Text
T.toLower forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ReportOpts -> Maybe Text
budgetpat_ ReportOpts
ropts
budgetpts :: [PeriodicTransaction]
budgetpts = [PeriodicTransaction
pt | PeriodicTransaction
pt <- Journal -> [PeriodicTransaction]
jperiodictxns Journal
j, Text
pat Text -> Text -> Bool
`T.isInfixOf` Text -> Text
T.toLower (PeriodicTransaction -> Text
ptdescription PeriodicTransaction
pt)]
budgetts :: [Transaction]
budgetts =
forall a. Show a => String -> a -> a
dbg5 String
"budget goal txns" forall a b. (a -> b) -> a -> b
$
[Transaction -> Transaction
makeBudgetTxn Transaction
t
| PeriodicTransaction
pt <- [PeriodicTransaction]
budgetpts
, Transaction
t <- Bool -> PeriodicTransaction -> DateSpan -> [Transaction]
runPeriodicTransaction Bool
False PeriodicTransaction
pt DateSpan
budgetspan
]
makeBudgetTxn :: Transaction -> Transaction
makeBudgetTxn Transaction
t = Transaction -> Transaction
txnTieKnot forall a b. (a -> b) -> a -> b
$ Transaction
t { tdescription :: Text
tdescription = String -> Text
T.pack String
"Budget transaction" }
journalWithBudgetAccountNames :: S.Set AccountName -> Bool -> Journal -> Journal
journalWithBudgetAccountNames :: Set Text -> Bool -> Journal -> Journal
journalWithBudgetAccountNames Set Text
budgetedaccts Bool
showunbudgeted Journal
j =
forall a. Show a => (a -> String) -> a -> a
dbg5With ((String
"budget account names: "forall a. [a] -> [a] -> [a]
++)forall b c a. (b -> c) -> (a -> b) -> a -> c
.forall a. Show a => a -> String
pshowforall b c a. (b -> c) -> (a -> b) -> a -> c
.Journal -> [Text]
journalAccountNamesUsed) forall a b. (a -> b) -> a -> b
$
Journal
j { jtxns :: [Transaction]
jtxns = Transaction -> Transaction
remapTxn forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Journal -> [Transaction]
jtxns Journal
j }
where
remapTxn :: Transaction -> Transaction
remapTxn = Transaction -> Transaction
txnTieKnot forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Posting -> Posting) -> Transaction -> Transaction
transactionTransformPostings Posting -> Posting
remapPosting
remapPosting :: Posting -> Posting
remapPosting Posting
p = Posting
p { paccount :: Text
paccount = Text -> Text
remapAccount forall a b. (a -> b) -> a -> b
$ Posting -> Text
paccount Posting
p, poriginal :: Maybe Posting
poriginal = Posting -> Maybe Posting
poriginal Posting
p forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> forall a. a -> Maybe a
Just Posting
p }
remapAccount :: Text -> Text
remapAccount Text
a
| Text
a forall a. Ord a => a -> Set a -> Bool
`S.member` Set Text
budgetedaccts = Text
a
| Just Text
p <- Maybe Text
budgetedparent = if Bool
showunbudgeted then Text
a else Text
p
| Bool
otherwise = if Bool
showunbudgeted then Text
u forall a. Semigroup a => a -> a -> a
<> Text
acctsep forall a. Semigroup a => a -> a -> a
<> Text
a else Text
u
where
budgetedparent :: Maybe Text
budgetedparent = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (forall a. Ord a => a -> Set a -> Bool
`S.member` Set Text
budgetedaccts) forall a b. (a -> b) -> a -> b
$ Text -> [Text]
parentAccountNames Text
a
u :: Text
u = Text
unbudgetedAccountName
combineBudgetAndActual :: ReportOpts -> Journal -> MultiBalanceReport -> MultiBalanceReport -> BudgetReport
combineBudgetAndActual :: ReportOpts
-> Journal
-> MultiBalanceReport
-> MultiBalanceReport
-> BudgetReport
combineBudgetAndActual ReportOpts
ropts Journal
j
(PeriodicReport [DateSpan]
budgetperiods [PeriodicReportRow DisplayName Change]
budgetrows (PeriodicReportRow ()
_ [Change]
budgettots Change
budgetgrandtot Change
budgetgrandavg))
(PeriodicReport [DateSpan]
actualperiods [PeriodicReportRow DisplayName Change]
actualrows (PeriodicReportRow ()
_ [Change]
actualtots Change
actualgrandtot Change
actualgrandavg)) =
forall a b.
[DateSpan]
-> [PeriodicReportRow a b]
-> PeriodicReportRow () b
-> PeriodicReport a b
PeriodicReport [DateSpan]
periods [BudgetReportRow]
sortedrows PeriodicReportRow () (Maybe Change, Maybe Change)
totalrow
where
periods :: [DateSpan]
periods = forall a. Ord a => [a] -> [a]
nubSort forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
filter (forall a. Eq a => a -> a -> Bool
/= DateSpan
nulldatespan) forall a b. (a -> b) -> a -> b
$ [DateSpan]
budgetperiods forall a. [a] -> [a] -> [a]
++ [DateSpan]
actualperiods
rows1 :: [BudgetReportRow]
rows1 =
[ forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow DisplayName
acct [(Maybe Change, Maybe Change)]
amtandgoals (Maybe Change, Maybe Change)
totamtandgoal (Maybe Change, Maybe Change)
avgamtandgoal
| PeriodicReportRow DisplayName
acct [Change]
actualamts Change
actualtot Change
actualavg <- [PeriodicReportRow DisplayName Change]
actualrows
, let mbudgetgoals :: Maybe ([Change], Change, Change)
mbudgetgoals = forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
HM.lookup (DisplayName -> Text
displayFull DisplayName
acct) HashMap Text ([Change], Change, Change)
budgetGoalsByAcct :: Maybe ([BudgetGoal], BudgetTotal, BudgetAverage)
, let budgetmamts :: [Maybe Change]
budgetmamts = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall a. Maybe a
Nothing forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ [DateSpan]
periods) (forall a b. (a -> b) -> [a] -> [b]
map forall a. a -> Maybe a
Just forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall {a} {b} {c}. (a, b, c) -> a
first3) Maybe ([Change], Change, Change)
mbudgetgoals :: [Maybe BudgetGoal]
, let mbudgettot :: Maybe Change
mbudgettot = forall {a} {b} {c}. (a, b, c) -> b
second3 forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe ([Change], Change, Change)
mbudgetgoals :: Maybe BudgetTotal
, let mbudgetavg :: Maybe Change
mbudgetavg = forall {a} {b} {c}. (a, b, c) -> c
third3 forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe ([Change], Change, Change)
mbudgetgoals :: Maybe BudgetAverage
, let acctBudgetByPeriod :: Map DateSpan Change
acctBudgetByPeriod = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [ (DateSpan
p,Change
budgetamt) | (DateSpan
p, Just Change
budgetamt) <- forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
budgetperiods [Maybe Change]
budgetmamts ] :: Map DateSpan BudgetGoal
, let acctActualByPeriod :: Map DateSpan Change
acctActualByPeriod = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [ (DateSpan
p,Change
actualamt) | (DateSpan
p, Just Change
actualamt) <- forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
actualperiods (forall a b. (a -> b) -> [a] -> [b]
map forall a. a -> Maybe a
Just [Change]
actualamts) ] :: Map DateSpan Change
, let amtandgoals :: [(Maybe Change, Maybe Change)]
amtandgoals = [ (forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
acctActualByPeriod, forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
acctBudgetByPeriod) | DateSpan
p <- [DateSpan]
periods ] :: [BudgetCell]
, let totamtandgoal :: (Maybe Change, Maybe Change)
totamtandgoal = (forall a. a -> Maybe a
Just Change
actualtot, Maybe Change
mbudgettot)
, let avgamtandgoal :: (Maybe Change, Maybe Change)
avgamtandgoal = (forall a. a -> Maybe a
Just Change
actualavg, Maybe Change
mbudgetavg)
]
where
HashMap Text ([Change], Change, Change)
budgetGoalsByAcct :: HashMap AccountName ([BudgetGoal], BudgetTotal, BudgetAverage) =
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
HM.fromList [ (DisplayName -> Text
displayFull DisplayName
acct, ([Change]
amts, Change
tot, Change
avg))
| PeriodicReportRow DisplayName
acct [Change]
amts Change
tot Change
avg <- [PeriodicReportRow DisplayName Change]
budgetrows ]
rows2 :: [BudgetReportRow]
rows2 =
[ forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow DisplayName
acct [(Maybe Change, Maybe Change)]
amtandgoals forall {a}. (Maybe a, Maybe Change)
totamtandgoal forall {a}. (Maybe a, Maybe Change)
avgamtandgoal
| PeriodicReportRow DisplayName
acct [Change]
budgetgoals Change
budgettot Change
budgetavg <- [PeriodicReportRow DisplayName Change]
budgetrows
, DisplayName -> Text
displayFull DisplayName
acct forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` forall a b. (a -> b) -> [a] -> [b]
map forall a. PeriodicReportRow DisplayName a -> Text
prrFullName [BudgetReportRow]
rows1
, let acctBudgetByPeriod :: Map DateSpan Change
acctBudgetByPeriod = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList forall a b. (a -> b) -> a -> b
$ forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
budgetperiods [Change]
budgetgoals :: Map DateSpan BudgetGoal
, let amtandgoals :: [(Maybe Change, Maybe Change)]
amtandgoals = [ (forall a. Maybe a
Nothing, forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
acctBudgetByPeriod) | DateSpan
p <- [DateSpan]
periods ] :: [BudgetCell]
, let totamtandgoal :: (Maybe a, Maybe Change)
totamtandgoal = (forall a. Maybe a
Nothing, forall a. a -> Maybe a
Just Change
budgettot)
, let avgamtandgoal :: (Maybe a, Maybe Change)
avgamtandgoal = (forall a. Maybe a
Nothing, forall a. a -> Maybe a
Just Change
budgetavg)
]
[BudgetReportRow]
sortedrows :: [BudgetReportRow] = forall b.
[Text]
-> [PeriodicReportRow DisplayName b]
-> [PeriodicReportRow DisplayName b]
sortRowsLike (forall {b}.
[PeriodicReportRow DisplayName (Maybe Change, b)] -> [Text]
mbrsorted [BudgetReportRow]
unbudgetedrows forall a. [a] -> [a] -> [a]
++ forall {b}.
[PeriodicReportRow DisplayName (Maybe Change, b)] -> [Text]
mbrsorted [BudgetReportRow]
rows') [BudgetReportRow]
rows
where
([BudgetReportRow]
unbudgetedrows, [BudgetReportRow]
rows') = forall a. (a -> Bool) -> [a] -> ([a], [a])
partition ((forall a. Eq a => a -> a -> Bool
==Text
unbudgetedAccountName) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. PeriodicReportRow DisplayName a -> Text
prrFullName) [BudgetReportRow]
rows
mbrsorted :: [PeriodicReportRow DisplayName (Maybe Change, b)] -> [Text]
mbrsorted = forall a b. (a -> b) -> [a] -> [b]
map forall a. PeriodicReportRow DisplayName a -> Text
prrFullName forall b c a. (b -> c) -> (a -> b) -> a -> c
. ReportOpts
-> Journal
-> [PeriodicReportRow DisplayName Change]
-> [PeriodicReportRow DisplayName Change]
sortRows ReportOpts
ropts Journal
j forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a b. (a -> b) -> a -> b
$ forall a. a -> Maybe a -> a
fromMaybe Change
nullmixedamt forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst)
rows :: [BudgetReportRow]
rows = [BudgetReportRow]
rows1 forall a. [a] -> [a] -> [a]
++ [BudgetReportRow]
rows2
totalrow :: PeriodicReportRow () (Maybe Change, Maybe Change)
totalrow = forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow ()
[ (forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
totActualByPeriod, forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
totBudgetByPeriod) | DateSpan
p <- [DateSpan]
periods ]
( forall a. a -> Maybe a
Just Change
actualgrandtot, Change -> Maybe Change
budget Change
budgetgrandtot )
( forall a. a -> Maybe a
Just Change
actualgrandavg, Change -> Maybe Change
budget Change
budgetgrandavg )
where
totBudgetByPeriod :: Map DateSpan Change
totBudgetByPeriod = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList forall a b. (a -> b) -> a -> b
$ forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
budgetperiods [Change]
budgettots :: Map DateSpan BudgetTotal
totActualByPeriod :: Map DateSpan Change
totActualByPeriod = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList forall a b. (a -> b) -> a -> b
$ forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
actualperiods [Change]
actualtots :: Map DateSpan Change
budget :: Change -> Maybe Change
budget Change
b = if Change -> Bool
mixedAmountLooksZero Change
b then forall a. Maybe a
Nothing else forall a. a -> Maybe a
Just Change
b
budgetReportAsText :: ReportOpts -> BudgetReport -> TL.Text
budgetReportAsText :: ReportOpts -> BudgetReport -> Text
budgetReportAsText ropts :: ReportOpts
ropts@ReportOpts{Bool
Int
[Text]
[Status]
Maybe Int
Maybe Text
Maybe NormalSign
Maybe ValuationType
Maybe ConversionOp
Interval
Period
StringFormat
Layout
AccountListMode
BalanceAccumulation
BalanceCalculation
layout_ :: ReportOpts -> Layout
transpose_ :: ReportOpts -> Bool
color_ :: ReportOpts -> Bool
normalbalance_ :: ReportOpts -> Maybe NormalSign
invert_ :: ReportOpts -> Bool
percent_ :: ReportOpts -> Bool
sort_amount_ :: ReportOpts -> Bool
show_costs_ :: ReportOpts -> Bool
summary_only_ :: ReportOpts -> Bool
no_total_ :: ReportOpts -> Bool
row_total_ :: ReportOpts -> Bool
declared_ :: ReportOpts -> Bool
drop_ :: ReportOpts -> Int
balanceaccum_ :: ReportOpts -> BalanceAccumulation
balancecalc_ :: ReportOpts -> BalanceCalculation
txn_dates_ :: ReportOpts -> Bool
related_ :: ReportOpts -> Bool
average_ :: ReportOpts -> Bool
querystring_ :: ReportOpts -> [Text]
pretty_ :: ReportOpts -> Bool
format_ :: ReportOpts -> StringFormat
real_ :: ReportOpts -> Bool
no_elide_ :: ReportOpts -> Bool
date2_ :: ReportOpts -> Bool
depth_ :: ReportOpts -> Maybe Int
value_ :: ReportOpts -> Maybe ValuationType
conversionop_ :: ReportOpts -> Maybe ConversionOp
statuses_ :: ReportOpts -> [Status]
period_ :: ReportOpts -> Period
layout_ :: Layout
transpose_ :: Bool
color_ :: Bool
normalbalance_ :: Maybe NormalSign
invert_ :: Bool
percent_ :: Bool
sort_amount_ :: Bool
show_costs_ :: Bool
summary_only_ :: Bool
no_total_ :: Bool
row_total_ :: Bool
declared_ :: Bool
drop_ :: Int
accountlistmode_ :: AccountListMode
budgetpat_ :: Maybe Text
balanceaccum_ :: BalanceAccumulation
balancecalc_ :: BalanceCalculation
txn_dates_ :: Bool
related_ :: Bool
average_ :: Bool
querystring_ :: [Text]
pretty_ :: Bool
format_ :: StringFormat
real_ :: Bool
no_elide_ :: Bool
empty_ :: Bool
date2_ :: Bool
depth_ :: Maybe Int
infer_prices_ :: Bool
value_ :: Maybe ValuationType
conversionop_ :: Maybe ConversionOp
statuses_ :: [Status]
interval_ :: Interval
period_ :: Period
budgetpat_ :: ReportOpts -> Maybe Text
interval_ :: ReportOpts -> Interval
infer_prices_ :: ReportOpts -> Bool
empty_ :: ReportOpts -> Bool
accountlistmode_ :: ReportOpts -> AccountListMode
..} BudgetReport
budgetr = Builder -> Text
TB.toLazyText forall a b. (a -> b) -> a -> b
$
Text -> Builder
TB.fromText Text
title forall a. Semigroup a => a -> a -> a
<> Text -> Builder
TB.fromText Text
"\n\n" forall a. Semigroup a => a -> a -> a
<>
ReportOpts -> Table Text Text WideBuilder -> Builder
balanceReportTableAsText ReportOpts
ropts (ReportOpts -> BudgetReport -> Table Text Text WideBuilder
budgetReportAsTable ReportOpts
ropts BudgetReport
budgetr)
where
title :: Text
title = Text
"Budget performance in " forall a. Semigroup a => a -> a -> a
<> DateSpan -> Text
showDateSpan (forall a b. PeriodicReport a b -> DateSpan
periodicReportSpan BudgetReport
budgetr)
forall a. Semigroup a => a -> a -> a
<> (case Maybe ConversionOp
conversionop_ of
Just ConversionOp
ToCost -> Text
", converted to cost"
Maybe ConversionOp
_ -> Text
"")
forall a. Semigroup a => a -> a -> a
<> (case Maybe ValuationType
value_ of
Just (AtThen Maybe Text
_mc) -> Text
", valued at posting date"
Just (AtEnd Maybe Text
_mc) -> Text
", valued at period ends"
Just (AtNow Maybe Text
_mc) -> Text
", current value"
Just (AtDate Day
d Maybe Text
_mc) -> Text
", valued at " forall a. Semigroup a => a -> a -> a
<> Day -> Text
showDate Day
d
Maybe ValuationType
Nothing -> Text
"")
forall a. Semigroup a => a -> a -> a
<> Text
":"
budgetReportAsTable :: ReportOpts -> BudgetReport -> Tab.Table Text Text WideBuilder
budgetReportAsTable :: ReportOpts -> BudgetReport -> Table Text Text WideBuilder
budgetReportAsTable
ReportOpts{Bool
Int
[Text]
[Status]
Maybe Int
Maybe Text
Maybe NormalSign
Maybe ValuationType
Maybe ConversionOp
Interval
Period
StringFormat
Layout
AccountListMode
BalanceAccumulation
BalanceCalculation
layout_ :: Layout
transpose_ :: Bool
color_ :: Bool
normalbalance_ :: Maybe NormalSign
invert_ :: Bool
percent_ :: Bool
sort_amount_ :: Bool
show_costs_ :: Bool
summary_only_ :: Bool
no_total_ :: Bool
row_total_ :: Bool
declared_ :: Bool
drop_ :: Int
accountlistmode_ :: AccountListMode
budgetpat_ :: Maybe Text
balanceaccum_ :: BalanceAccumulation
balancecalc_ :: BalanceCalculation
txn_dates_ :: Bool
related_ :: Bool
average_ :: Bool
querystring_ :: [Text]
pretty_ :: Bool
format_ :: StringFormat
real_ :: Bool
no_elide_ :: Bool
empty_ :: Bool
date2_ :: Bool
depth_ :: Maybe Int
infer_prices_ :: Bool
value_ :: Maybe ValuationType
conversionop_ :: Maybe ConversionOp
statuses_ :: [Status]
interval_ :: Interval
period_ :: Period
layout_ :: ReportOpts -> Layout
transpose_ :: ReportOpts -> Bool
color_ :: ReportOpts -> Bool
normalbalance_ :: ReportOpts -> Maybe NormalSign
invert_ :: ReportOpts -> Bool
percent_ :: ReportOpts -> Bool
sort_amount_ :: ReportOpts -> Bool
show_costs_ :: ReportOpts -> Bool
summary_only_ :: ReportOpts -> Bool
no_total_ :: ReportOpts -> Bool
row_total_ :: ReportOpts -> Bool
declared_ :: ReportOpts -> Bool
drop_ :: ReportOpts -> Int
balanceaccum_ :: ReportOpts -> BalanceAccumulation
balancecalc_ :: ReportOpts -> BalanceCalculation
txn_dates_ :: ReportOpts -> Bool
related_ :: ReportOpts -> Bool
average_ :: ReportOpts -> Bool
querystring_ :: ReportOpts -> [Text]
pretty_ :: ReportOpts -> Bool
format_ :: ReportOpts -> StringFormat
real_ :: ReportOpts -> Bool
no_elide_ :: ReportOpts -> Bool
date2_ :: ReportOpts -> Bool
depth_ :: ReportOpts -> Maybe Int
value_ :: ReportOpts -> Maybe ValuationType
conversionop_ :: ReportOpts -> Maybe ConversionOp
statuses_ :: ReportOpts -> [Status]
period_ :: ReportOpts -> Period
budgetpat_ :: ReportOpts -> Maybe Text
interval_ :: ReportOpts -> Interval
infer_prices_ :: ReportOpts -> Bool
empty_ :: ReportOpts -> Bool
accountlistmode_ :: ReportOpts -> AccountListMode
..}
(PeriodicReport [DateSpan]
spans [BudgetReportRow]
items PeriodicReportRow () (Maybe Change, Maybe Change)
tr) =
forall {rh} {a}. Table rh rh a -> Table rh rh a
maybetransposetable forall a b. (a -> b) -> a -> b
$
forall {ch}. Table Text ch WideBuilder -> Table Text ch WideBuilder
addtotalrow forall a b. (a -> b) -> a -> b
$
forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
Tab.Table
(forall h. Properties -> [Header h] -> Header h
Tab.Group Properties
Tab.NoLine forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map forall h. h -> Header h
Tab.Header [Text]
accts)
(forall h. Properties -> [Header h] -> Header h
Tab.Group Properties
Tab.NoLine forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map forall h. h -> Header h
Tab.Header [Text]
colheadings)
[[WideBuilder]]
rows
where
colheadings :: [Text]
colheadings = [Text
"Commodity" | Layout
layout_ forall a. Eq a => a -> a -> Bool
== Layout
LayoutBare]
forall a. [a] -> [a] -> [a]
++ forall a b. (a -> b) -> [a] -> [b]
map (BalanceAccumulation -> [DateSpan] -> DateSpan -> Text
reportPeriodName BalanceAccumulation
balanceaccum_ [DateSpan]
spans) [DateSpan]
spans
forall a. [a] -> [a] -> [a]
++ [Text
" Total" | Bool
row_total_]
forall a. [a] -> [a] -> [a]
++ [Text
"Average" | Bool
average_]
renderacct :: PeriodicReportRow DisplayName a -> Text
renderacct PeriodicReportRow DisplayName a
row = case AccountListMode
accountlistmode_ of
AccountListMode
ALTree -> Int -> Text -> Text
T.replicate ((forall a. PeriodicReportRow DisplayName a -> Int
prrDepth PeriodicReportRow DisplayName a
row forall a. Num a => a -> a -> a
- Int
1)forall a. Num a => a -> a -> a
*Int
2) Text
" " forall a. Semigroup a => a -> a -> a
<> forall a. PeriodicReportRow DisplayName a -> Text
prrDisplayName PeriodicReportRow DisplayName a
row
AccountListMode
ALFlat -> Int -> Text -> Text
accountNameDrop (Int
drop_) forall a b. (a -> b) -> a -> b
$ forall a. PeriodicReportRow DisplayName a -> Text
prrFullName PeriodicReportRow DisplayName a
row
addtotalrow :: Table Text ch WideBuilder -> Table Text ch WideBuilder
addtotalrow
| Bool
no_total_ = forall a. a -> a
id
| Bool
otherwise = let rh :: Header Text
rh = forall h. Properties -> [Header h] -> Header h
Tab.Group Properties
Tab.NoLine forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Int -> a -> [a]
replicate (forall (t :: * -> *) a. Foldable t => t a -> Int
length [[WideBuilder]]
totalrows) forall a b. (a -> b) -> a -> b
$ forall h. h -> Header h
Tab.Header Text
""
ch :: Header [a]
ch = forall h. h -> Header h
Tab.Header []
in (forall a b c. (a -> b -> c) -> b -> a -> c
flip (forall rh ch a ch2.
Properties -> Table rh ch a -> Table rh ch2 a -> Table rh ch a
Tab.concatTables Properties
Tab.SingleLine) forall a b. (a -> b) -> a -> b
$ forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
Tab.Table Header Text
rh forall {a}. Header [a]
ch [[WideBuilder]]
totalrows)
maybetranspose :: [[a]] -> [[a]]
maybetranspose
| Bool
transpose_ = forall a. [[a]] -> [[a]]
transpose
| Bool
otherwise = forall a. a -> a
id
maybetransposetable :: Table rh rh a -> Table rh rh a
maybetransposetable
| Bool
transpose_ = \(Tab.Table Header rh
rh Header rh
ch [[a]]
vals) -> forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
Tab.Table Header rh
ch Header rh
rh (forall a. [[a]] -> [[a]]
transpose [[a]]
vals)
| Bool
otherwise = forall a. a -> a
id
([Text]
accts, [[WideBuilder]]
rows, [[WideBuilder]]
totalrows) = ([Text]
accts', forall {a}. [a] -> [[a]] -> [[a]]
prependcs [WideBuilder]
itemscs ([[BudgetDisplayCell]] -> [[WideBuilder]]
padcells [[BudgetDisplayCell]]
texts), forall {a}. [a] -> [[a]] -> [[a]]
prependcs [WideBuilder]
trcs ([[BudgetDisplayCell]] -> [[WideBuilder]]
padtr [[BudgetDisplayCell]]
trtexts))
where
shownitems :: [[(AccountName, WideBuilder, BudgetDisplayRow)]]
shownitems :: [[(Text, WideBuilder, [BudgetDisplayCell])]]
shownitems = (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\BudgetReportRow
i -> forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\(WideBuilder
cs, [BudgetDisplayCell]
cvals) -> (forall a. PeriodicReportRow DisplayName a -> Text
renderacct BudgetReportRow
i, WideBuilder
cs, [BudgetDisplayCell]
cvals)) forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(Maybe Change, Maybe Change)]
-> [(WideBuilder, [BudgetDisplayCell])]
showrow forall a b. (a -> b) -> a -> b
$ forall {a} {a}. PeriodicReportRow a a -> [a]
rowToBudgetCells BudgetReportRow
i) [BudgetReportRow]
items)
([Text]
accts', [WideBuilder]
itemscs, [[BudgetDisplayCell]]
texts) = forall a b c. [(a, b, c)] -> ([a], [b], [c])
unzip3 forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[(Text, WideBuilder, [BudgetDisplayCell])]]
shownitems
showntr :: [[(WideBuilder, BudgetDisplayRow)]]
showntr :: [[(WideBuilder, [BudgetDisplayCell])]]
showntr = [[(Maybe Change, Maybe Change)]
-> [(WideBuilder, [BudgetDisplayCell])]
showrow forall a b. (a -> b) -> a -> b
$ forall {a} {a}. PeriodicReportRow a a -> [a]
rowToBudgetCells PeriodicReportRow () (Maybe Change, Maybe Change)
tr]
([WideBuilder]
trcs, [[BudgetDisplayCell]]
trtexts) = forall a b. [(a, b)] -> ([a], [b])
unzip forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[(WideBuilder, [BudgetDisplayCell])]]
showntr
trwidths :: [(Int, Int, Int)]
trwidths
| Bool
transpose_ = forall a. Int -> [a] -> [a]
drop (forall (t :: * -> *) a. Foldable t => t a -> Int
length [[BudgetDisplayCell]]
texts) [(Int, Int, Int)]
widths
| Bool
otherwise = [(Int, Int, Int)]
widths
padcells :: [[BudgetDisplayCell]] -> [[WideBuilder]]
padcells = forall a. [[a]] -> [[a]]
maybetranspose forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry (Int, Int, Int) -> BudgetDisplayCell -> WideBuilder
paddisplaycell) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. [a] -> [b] -> [(a, b)]
zip [(Int, Int, Int)]
widths) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [[a]] -> [[a]]
maybetranspose
padtr :: [[BudgetDisplayCell]] -> [[WideBuilder]]
padtr = forall a. [[a]] -> [[a]]
maybetranspose forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry (Int, Int, Int) -> BudgetDisplayCell -> WideBuilder
paddisplaycell) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. [a] -> [b] -> [(a, b)]
zip [(Int, Int, Int)]
trwidths) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [[a]] -> [[a]]
maybetranspose
prependcs :: [a] -> [[a]] -> [[a]]
prependcs [a]
cs
| Layout
layout_ forall a. Eq a => a -> a -> Bool
/= Layout
LayoutBare = forall a. a -> a
id
| Bool
otherwise = forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (:) [a]
cs
rowToBudgetCells :: PeriodicReportRow a a -> [a]
rowToBudgetCells (PeriodicReportRow a
_ [a]
as a
rowtot a
rowavg) = [a]
as
forall a. [a] -> [a] -> [a]
++ [a
rowtot | Bool
row_total_ Bool -> Bool -> Bool
&& Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => t a -> Bool
null [a]
as)]
forall a. [a] -> [a] -> [a]
++ [a
rowavg | Bool
average_ Bool -> Bool -> Bool
&& Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => t a -> Bool
null [a]
as)]
rowfuncs :: [CommoditySymbol] -> (BudgetShowMixed, BudgetPercBudget)
rowfuncs :: [Text] -> (BudgetShowMixed, BudgetPercBudget)
rowfuncs [Text]
cs = case Layout
layout_ of
LayoutWide Maybe Int
width ->
( forall (f :: * -> *) a. Applicative f => a -> f a
pure forall b c a. (b -> c) -> (a -> b) -> a -> c
. AmountDisplayOpts -> Change -> WideBuilder
showMixedAmountB AmountDisplayOpts
oneLine{displayColour :: Bool
displayColour=Bool
color_, displayMaxWidth :: Maybe Int
displayMaxWidth=Maybe Int
width}
, \Change
a -> forall (f :: * -> *) a. Applicative f => a -> f a
pure forall b c a. (b -> c) -> (a -> b) -> a -> c
. Change -> Change -> Maybe Percentage
percentage Change
a)
Layout
_ -> ( AmountDisplayOpts -> BudgetShowMixed
showMixedAmountLinesB AmountDisplayOpts
noPrice{displayOrder :: Maybe [Text]
displayOrder=forall a. a -> Maybe a
Just [Text]
cs, displayMinWidth :: Maybe Int
displayMinWidth=forall a. Maybe a
Nothing, displayColour :: Bool
displayColour=Bool
color_}
, \Change
a Change
b -> forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Change -> Change -> Text -> Maybe Percentage
percentage' Change
a Change
b) [Text]
cs)
showrow :: [BudgetCell] -> [(WideBuilder, BudgetDisplayRow)]
showrow :: [(Maybe Change, Maybe Change)]
-> [(WideBuilder, [BudgetDisplayCell])]
showrow [(Maybe Change, Maybe Change)]
row =
let cs :: [Text]
cs = [(Maybe Change, Maybe Change)] -> [Text]
budgetCellsCommodities [(Maybe Change, Maybe Change)]
row
(BudgetShowMixed
showmixed, BudgetPercBudget
percbudget) = [Text] -> (BudgetShowMixed, BudgetPercBudget)
rowfuncs [Text]
cs
in forall a b. [a] -> [b] -> [(a, b)]
zip (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> WideBuilder
wbFromText [Text]
cs)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [[a]] -> [[a]]
transpose
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (BudgetShowMixed
-> BudgetPercBudget
-> (Maybe Change, Maybe Change)
-> [BudgetDisplayCell]
showcell BudgetShowMixed
showmixed BudgetPercBudget
percbudget)
forall a b. (a -> b) -> a -> b
$ [(Maybe Change, Maybe Change)]
row
budgetCellsCommodities :: [(Maybe Change, Maybe Change)] -> [Text]
budgetCellsCommodities = forall a. Set a -> [a]
S.toList forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' forall a. Ord a => Set a -> Set a -> Set a
S.union forall a. Monoid a => a
mempty forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Maybe Change, Maybe Change) -> Set Text
budgetCellCommodities
budgetCellCommodities :: BudgetCell -> S.Set CommoditySymbol
budgetCellCommodities :: (Maybe Change, Maybe Change) -> Set Text
budgetCellCommodities (Maybe Change
am, Maybe Change
bm) = Maybe Change -> Set Text
f Maybe Change
am forall a. Ord a => Set a -> Set a -> Set a
`S.union` Maybe Change -> Set Text
f Maybe Change
bm
where f :: Maybe Change -> Set Text
f = forall b a. b -> (a -> b) -> Maybe a -> b
maybe forall a. Monoid a => a
mempty Change -> Set Text
maCommodities
cellswidth :: [BudgetCell] -> [[(Int, Int, Int)]]
cellswidth :: [(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]]
cellswidth [(Maybe Change, Maybe Change)]
row =
let cs :: [Text]
cs = [(Maybe Change, Maybe Change)] -> [Text]
budgetCellsCommodities [(Maybe Change, Maybe Change)]
row
(BudgetShowMixed
showmixed, BudgetPercBudget
percbudget) = [Text] -> (BudgetShowMixed, BudgetPercBudget)
rowfuncs [Text]
cs
disp :: (Maybe Change, Maybe Change) -> [BudgetDisplayCell]
disp = BudgetShowMixed
-> BudgetPercBudget
-> (Maybe Change, Maybe Change)
-> [BudgetDisplayCell]
showcell BudgetShowMixed
showmixed BudgetPercBudget
percbudget
budgetpercwidth :: (WideBuilder, Maybe WideBuilder) -> (Int, Int)
budgetpercwidth = WideBuilder -> Int
wbWidth forall (a :: * -> * -> *) b c b' c'.
Arrow a =>
a b c -> a b' c' -> a (b, b') (c, c')
*** forall b a. b -> (a -> b) -> Maybe a -> b
maybe Int
0 WideBuilder -> Int
wbWidth
cellwidth :: BudgetDisplayCell -> (Int, Int, Int)
cellwidth (WideBuilder
am, Maybe (WideBuilder, Maybe WideBuilder)
bm) = let (Int
bw, Int
pw) = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Int
0, Int
0) (WideBuilder, Maybe WideBuilder) -> (Int, Int)
budgetpercwidth Maybe (WideBuilder, Maybe WideBuilder)
bm in (WideBuilder -> Int
wbWidth WideBuilder
am, Int
bw, Int
pw)
in forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap BudgetDisplayCell -> (Int, Int, Int)
cellwidth forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Maybe Change, Maybe Change) -> [BudgetDisplayCell]
disp) [(Maybe Change, Maybe Change)]
row
widths :: [(Int, Int, Int)]
widths = forall a b c. [a] -> [b] -> [c] -> [(a, b, c)]
zip3 [Int]
actualwidths [Int]
budgetwidths [Int]
percentwidths
where
actualwidths :: [Int]
actualwidths = forall a b. (a -> b) -> [a] -> [b]
map (forall a. Integral a => [a] -> a
maximum' forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map forall {a} {b} {c}. (a, b, c) -> a
first3 ) forall a b. (a -> b) -> a -> b
$ [[(Int, Int, Int)]]
cols
budgetwidths :: [Int]
budgetwidths = forall a b. (a -> b) -> [a] -> [b]
map (forall a. Integral a => [a] -> a
maximum' forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map forall {a} {b} {c}. (a, b, c) -> b
second3) forall a b. (a -> b) -> a -> b
$ [[(Int, Int, Int)]]
cols
percentwidths :: [Int]
percentwidths = forall a b. (a -> b) -> [a] -> [b]
map (forall a. Integral a => [a] -> a
maximum' forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map forall {a} {b} {c}. (a, b, c) -> c
third3 ) forall a b. (a -> b) -> a -> b
$ [[(Int, Int, Int)]]
cols
catcolumnwidths :: [[[a]]] -> [[a]]
catcolumnwidths = forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' (forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith forall a. [a] -> [a] -> [a]
(++)) forall a b. (a -> b) -> a -> b
$ forall a. a -> [a]
repeat []
cols :: [[(Int, Int, Int)]]
cols = forall a. [[a]] -> [[a]]
maybetranspose forall a b. (a -> b) -> a -> b
$ forall {a}. [[[a]]] -> [[a]]
catcolumnwidths forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map ([(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]]
cellswidth forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall {a} {a}. PeriodicReportRow a a -> [a]
rowToBudgetCells) [BudgetReportRow]
items forall a. [a] -> [a] -> [a]
++ [[(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]]
cellswidth forall a b. (a -> b) -> a -> b
$ forall {a} {a}. PeriodicReportRow a a -> [a]
rowToBudgetCells PeriodicReportRow () (Maybe Change, Maybe Change)
tr]
showcell :: BudgetShowMixed -> BudgetPercBudget -> BudgetCell -> BudgetDisplayRow
showcell :: BudgetShowMixed
-> BudgetPercBudget
-> (Maybe Change, Maybe Change)
-> [BudgetDisplayCell]
showcell BudgetShowMixed
showmixed BudgetPercBudget
percbudget (Maybe Change
actual, Maybe Change
mbudget) = forall a b. [a] -> [b] -> [(a, b)]
zip (BudgetShowMixed
showmixed Change
actual') [Maybe (WideBuilder, Maybe WideBuilder)]
full
where
actual' :: Change
actual' = forall a. a -> Maybe a -> a
fromMaybe Change
nullmixedamt Maybe Change
actual
budgetAndPerc :: Change -> [(WideBuilder, Maybe WideBuilder)]
budgetAndPerc Change
b =
forall a b. [a] -> [b] -> [(a, b)]
zip (BudgetShowMixed
showmixed Change
b) (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Text -> WideBuilder
wbFromText forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
T.pack forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Show a => a -> String
show forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall i. Integral i => Word8 -> DecimalRaw i -> DecimalRaw i
roundTo Word8
0) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> BudgetPercBudget
percbudget Change
actual' Change
b)
full :: [Maybe (WideBuilder, Maybe WideBuilder)]
full
| Just Change
b <- Maybe Change
mbudget = forall a. a -> Maybe a
Just forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Change -> [(WideBuilder, Maybe WideBuilder)]
budgetAndPerc Change
b
| Bool
otherwise = forall a. a -> [a]
repeat forall a. Maybe a
Nothing
paddisplaycell :: (Int, Int, Int) -> BudgetDisplayCell -> WideBuilder
paddisplaycell :: (Int, Int, Int) -> BudgetDisplayCell -> WideBuilder
paddisplaycell (Int
actualwidth, Int
budgetwidth, Int
percentwidth) (WideBuilder
actual, Maybe (WideBuilder, Maybe WideBuilder)
mbudget) = WideBuilder
full
where
toPadded :: WideBuilder -> Builder
toPadded (WideBuilder Builder
b Int
w) =
(Text -> Builder
TB.fromText forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b c. (a -> b -> c) -> b -> a -> c
flip Int -> Text -> Text
T.replicate Text
" " forall a b. (a -> b) -> a -> b
$ Int
actualwidth forall a. Num a => a -> a -> a
- Int
w) forall a. Semigroup a => a -> a -> a
<> Builder
b
(Int
totalpercentwidth, Int
totalbudgetwidth) =
let totalpercentwidth' :: Int
totalpercentwidth' = if Int
percentwidth forall a. Eq a => a -> a -> Bool
== Int
0 then Int
0 else Int
percentwidth forall a. Num a => a -> a -> a
+ Int
5
in ( Int
totalpercentwidth'
, if Int
budgetwidth forall a. Eq a => a -> a -> Bool
== Int
0 then Int
0 else Int
budgetwidth forall a. Num a => a -> a -> a
+ Int
totalpercentwidth' forall a. Num a => a -> a -> a
+ Int
3
)
budgetb :: (WideBuilder, Maybe WideBuilder) -> Builder
budgetb (WideBuilder
budget, Maybe WideBuilder
perc) =
let perct :: Text
perct = case Maybe WideBuilder
perc of
Maybe WideBuilder
Nothing -> Int -> Text -> Text
T.replicate Int
totalpercentwidth Text
" "
Just WideBuilder
pct -> Int -> Text -> Text
T.replicate (Int
percentwidth forall a. Num a => a -> a -> a
- WideBuilder -> Int
wbWidth WideBuilder
pct) Text
" " forall a. Semigroup a => a -> a -> a
<> WideBuilder -> Text
wbToText WideBuilder
pct forall a. Semigroup a => a -> a -> a
<> Text
"% of "
in Text -> Builder
TB.fromText forall a b. (a -> b) -> a -> b
$ Text
" [" forall a. Semigroup a => a -> a -> a
<> Text
perct forall a. Semigroup a => a -> a -> a
<> Int -> Text -> Text
T.replicate (Int
budgetwidth forall a. Num a => a -> a -> a
- WideBuilder -> Int
wbWidth WideBuilder
budget) Text
" " forall a. Semigroup a => a -> a -> a
<> WideBuilder -> Text
wbToText WideBuilder
budget forall a. Semigroup a => a -> a -> a
<> Text
"]"
emptyBudget :: Builder
emptyBudget = Text -> Builder
TB.fromText forall a b. (a -> b) -> a -> b
$ Int -> Text -> Text
T.replicate Int
totalbudgetwidth Text
" "
full :: WideBuilder
full = forall a b c. (a -> b -> c) -> b -> a -> c
flip Builder -> Int -> WideBuilder
WideBuilder (Int
actualwidth forall a. Num a => a -> a -> a
+ Int
totalbudgetwidth) forall a b. (a -> b) -> a -> b
$
WideBuilder -> Builder
toPadded WideBuilder
actual forall a. Semigroup a => a -> a -> a
<> forall b a. b -> (a -> b) -> Maybe a -> b
maybe Builder
emptyBudget (WideBuilder, Maybe WideBuilder) -> Builder
budgetb Maybe (WideBuilder, Maybe WideBuilder)
mbudget
percentage :: Change -> BudgetGoal -> Maybe Percentage
percentage :: Change -> Change -> Maybe Percentage
percentage Change
actual Change
budget =
case (Change -> [Amount]
costedAmounts Change
actual, Change -> [Amount]
costedAmounts Change
budget) of
([Amount
a], [Amount
b]) | (Amount -> Text
acommodity Amount
a forall a. Eq a => a -> a -> Bool
== Amount -> Text
acommodity Amount
b Bool -> Bool -> Bool
|| Amount -> Bool
amountLooksZero Amount
a) Bool -> Bool -> Bool
&& Bool -> Bool
not (Amount -> Bool
amountLooksZero Amount
b)
-> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Percentage
100 forall a. Num a => a -> a -> a
* Amount -> Percentage
aquantity Amount
a forall a. Fractional a => a -> a -> a
/ Amount -> Percentage
aquantity Amount
b
([Amount], [Amount])
_ ->
forall a. Maybe a
Nothing
where
costedAmounts :: Change -> [Amount]
costedAmounts = case Maybe ConversionOp
conversionop_ of
Just ConversionOp
ToCost -> Change -> [Amount]
amounts forall b c a. (b -> c) -> (a -> b) -> a -> c
. Change -> Change
mixedAmountCost
Maybe ConversionOp
_ -> Change -> [Amount]
amounts
percentage' :: Change -> BudgetGoal -> CommoditySymbol -> Maybe Percentage
percentage' :: Change -> Change -> Text -> Maybe Percentage
percentage' Change
am Change
bm Text
c = case ((,) forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (forall a. Eq a => a -> a -> Bool
(==) Text
c forall b c a. (b -> c) -> (a -> b) -> a -> c
. Amount -> Text
acommodity) forall b c a. (b -> c) -> (a -> b) -> a -> c
. Change -> [Amount]
amounts) Change
am Change
bm of
(Just Amount
a, Just Amount
b) -> Change -> Change -> Maybe Percentage
percentage (Amount -> Change
mixedAmount Amount
a) (Amount -> Change
mixedAmount Amount
b)
(Maybe Amount, Maybe Amount)
_ -> forall a. Maybe a
Nothing
budgetReportAsCsv :: ReportOpts -> BudgetReport -> [[Text]]
budgetReportAsCsv :: ReportOpts -> BudgetReport -> [[Text]]
budgetReportAsCsv
ReportOpts{Bool
Int
[Text]
[Status]
Maybe Int
Maybe Text
Maybe NormalSign
Maybe ValuationType
Maybe ConversionOp
Interval
Period
StringFormat
Layout
AccountListMode
BalanceAccumulation
BalanceCalculation
layout_ :: Layout
transpose_ :: Bool
color_ :: Bool
normalbalance_ :: Maybe NormalSign
invert_ :: Bool
percent_ :: Bool
sort_amount_ :: Bool
show_costs_ :: Bool
summary_only_ :: Bool
no_total_ :: Bool
row_total_ :: Bool
declared_ :: Bool
drop_ :: Int
accountlistmode_ :: AccountListMode
budgetpat_ :: Maybe Text
balanceaccum_ :: BalanceAccumulation
balancecalc_ :: BalanceCalculation
txn_dates_ :: Bool
related_ :: Bool
average_ :: Bool
querystring_ :: [Text]
pretty_ :: Bool
format_ :: StringFormat
real_ :: Bool
no_elide_ :: Bool
empty_ :: Bool
date2_ :: Bool
depth_ :: Maybe Int
infer_prices_ :: Bool
value_ :: Maybe ValuationType
conversionop_ :: Maybe ConversionOp
statuses_ :: [Status]
interval_ :: Interval
period_ :: Period
layout_ :: ReportOpts -> Layout
transpose_ :: ReportOpts -> Bool
color_ :: ReportOpts -> Bool
normalbalance_ :: ReportOpts -> Maybe NormalSign
invert_ :: ReportOpts -> Bool
percent_ :: ReportOpts -> Bool
sort_amount_ :: ReportOpts -> Bool
show_costs_ :: ReportOpts -> Bool
summary_only_ :: ReportOpts -> Bool
no_total_ :: ReportOpts -> Bool
row_total_ :: ReportOpts -> Bool
declared_ :: ReportOpts -> Bool
drop_ :: ReportOpts -> Int
balanceaccum_ :: ReportOpts -> BalanceAccumulation
balancecalc_ :: ReportOpts -> BalanceCalculation
txn_dates_ :: ReportOpts -> Bool
related_ :: ReportOpts -> Bool
average_ :: ReportOpts -> Bool
querystring_ :: ReportOpts -> [Text]
pretty_ :: ReportOpts -> Bool
format_ :: ReportOpts -> StringFormat
real_ :: ReportOpts -> Bool
no_elide_ :: ReportOpts -> Bool
date2_ :: ReportOpts -> Bool
depth_ :: ReportOpts -> Maybe Int
value_ :: ReportOpts -> Maybe ValuationType
conversionop_ :: ReportOpts -> Maybe ConversionOp
statuses_ :: ReportOpts -> [Status]
period_ :: ReportOpts -> Period
budgetpat_ :: ReportOpts -> Maybe Text
interval_ :: ReportOpts -> Interval
infer_prices_ :: ReportOpts -> Bool
empty_ :: ReportOpts -> Bool
accountlistmode_ :: ReportOpts -> AccountListMode
..}
(PeriodicReport [DateSpan]
colspans [BudgetReportRow]
items PeriodicReportRow () (Maybe Change, Maybe Change)
tr)
= (if Bool
transpose_ then forall a. [[a]] -> [[a]]
transpose else forall a. a -> a
id) forall a b. (a -> b) -> a -> b
$
(Text
"Account" forall a. a -> [a] -> [a]
:
[Text
"Commodity" | Layout
layout_ forall a. Eq a => a -> a -> Bool
== Layout
LayoutBare ]
forall a. [a] -> [a] -> [a]
++ forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\DateSpan
spn -> [DateSpan -> Text
showDateSpan DateSpan
spn, Text
"budget"]) [DateSpan]
colspans
forall a. [a] -> [a] -> [a]
++ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[Text
"Total" ,Text
"budget"] | Bool
row_total_]
forall a. [a] -> [a] -> [a]
++ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[Text
"Average",Text
"budget"] | Bool
average_]
) forall a. a -> [a] -> [a]
:
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (forall a.
(PeriodicReportRow a (Maybe Change, Maybe Change) -> Text)
-> PeriodicReportRow a (Maybe Change, Maybe Change) -> [[Text]]
rowAsTexts forall a. PeriodicReportRow DisplayName a -> Text
prrFullName) [BudgetReportRow]
items
forall a. [a] -> [a] -> [a]
++ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [ forall a.
(PeriodicReportRow a (Maybe Change, Maybe Change) -> Text)
-> PeriodicReportRow a (Maybe Change, Maybe Change) -> [[Text]]
rowAsTexts (forall a b. a -> b -> a
const Text
"Total:") PeriodicReportRow () (Maybe Change, Maybe Change)
tr | Bool -> Bool
not Bool
no_total_ ]
where
flattentuples :: [(a, a)] -> [a]
flattentuples [(a, a)]
tups = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[a
a,a
b] | (a
a,a
b) <- [(a, a)]
tups]
showNorm :: Maybe Change -> Text
showNorm = forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"" (WideBuilder -> Text
wbToText forall b c a. (b -> c) -> (a -> b) -> a -> c
. AmountDisplayOpts -> Change -> WideBuilder
showMixedAmountB AmountDisplayOpts
oneLine)
rowAsTexts :: (PeriodicReportRow a BudgetCell -> Text)
-> PeriodicReportRow a BudgetCell
-> [[Text]]
rowAsTexts :: forall a.
(PeriodicReportRow a (Maybe Change, Maybe Change) -> Text)
-> PeriodicReportRow a (Maybe Change, Maybe Change) -> [[Text]]
rowAsTexts PeriodicReportRow a (Maybe Change, Maybe Change) -> Text
render row :: PeriodicReportRow a (Maybe Change, Maybe Change)
row@(PeriodicReportRow a
_ [(Maybe Change, Maybe Change)]
as (Maybe Change
rowtot,Maybe Change
budgettot) (Maybe Change
rowavg, Maybe Change
budgetavg))
| Layout
layout_ forall a. Eq a => a -> a -> Bool
/= Layout
LayoutBare = [PeriodicReportRow a (Maybe Change, Maybe Change) -> Text
render PeriodicReportRow a (Maybe Change, Maybe Change)
row forall a. a -> [a] -> [a]
: forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Maybe Change -> Text
showNorm [Maybe Change]
vals]
| Bool
otherwise =
[[Text]] -> [[Text]]
joinNames forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (:) [Text]
cs
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [[a]] -> [[a]]
transpose
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap WideBuilder -> Text
wbToText forall b c a. (b -> c) -> (a -> b) -> a -> c
. AmountDisplayOpts -> BudgetShowMixed
showMixedAmountLinesB AmountDisplayOpts
oneLine{displayOrder :: Maybe [Text]
displayOrder=forall a. a -> Maybe a
Just [Text]
cs, displayMinWidth :: Maybe Int
displayMinWidth=forall a. Maybe a
Nothing}
forall b c a. (b -> c) -> (a -> b) -> a -> c
.forall a. a -> Maybe a -> a
fromMaybe Change
nullmixedamt)
forall a b. (a -> b) -> a -> b
$ [Maybe Change]
vals
where
cs :: [Text]
cs = forall a. Set a -> [a]
S.toList forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' forall a. Ord a => Set a -> Set a -> Set a
S.union forall a. Monoid a => a
mempty forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Change -> Set Text
maCommodities forall a b. (a -> b) -> a -> b
$ forall a. [Maybe a] -> [a]
catMaybes [Maybe Change]
vals
vals :: [Maybe Change]
vals = forall {a}. [(a, a)] -> [a]
flattentuples [(Maybe Change, Maybe Change)]
as
forall a. [a] -> [a] -> [a]
++ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[Maybe Change
rowtot, Maybe Change
budgettot] | Bool
row_total_]
forall a. [a] -> [a] -> [a]
++ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[Maybe Change
rowavg, Maybe Change
budgetavg] | Bool
average_]
joinNames :: [[Text]] -> [[Text]]
joinNames = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (PeriodicReportRow a (Maybe Change, Maybe Change) -> Text
render PeriodicReportRow a (Maybe Change, Maybe Change)
row forall a. a -> [a] -> [a]
:)
tests_BudgetReport :: TestTree
tests_BudgetReport = String -> [TestTree] -> TestTree
testGroup String
"BudgetReport" [
]