{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE RecordWildCards     #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Hledger.Reports.BudgetReport (
  BudgetGoal,
  BudgetTotal,
  BudgetAverage,
  BudgetCell,
  BudgetReportRow,
  BudgetReport,
  budgetReport,
  budgetReportAsTable,
  budgetReportAsText,
  budgetReportAsCsv,
  -- * Helpers
  combineBudgetAndActual,
  -- * Tests
  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')
import Data.List.Extra (nubSort)
import Data.Maybe (fromMaybe, catMaybes)
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 System.Console.CmdArgs.Explicit as C
--import Lucid as L
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


type BudgetGoal    = Change
type BudgetTotal   = Total
type BudgetAverage = Average

-- | A budget report tracks expected and actual changes per account and subperiod.
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]

-- | Calculate per-account, per-period budget (balance change) goals
-- from all periodic transactions, calculate actual balance changes
-- from the regular transactions, and compare these to get a 'BudgetReport'.
-- Unbudgeted accounts may be hidden or renamed (see journalWithBudgetAccountNames).
budgetReport :: ReportSpec -> BalancingOpts -> DateSpan -> Journal -> BudgetReport
budgetReport :: ReportSpec -> BalancingOpts -> DateSpan -> Journal -> BudgetReport
budgetReport ReportSpec
rspec BalancingOpts
bopts DateSpan
reportspan Journal
j = String -> BudgetReport -> BudgetReport
forall a. Show a => String -> a -> a
dbg4 String
"sortedbudgetreport" BudgetReport
budgetreport
  where
    -- Budget report demands ALTree mode to ensure subaccounts and subaccount budgets are properly handled
    -- and that reports with and without --empty make sense when compared side by side
    ropts :: ReportOpts
ropts = (ReportSpec -> ReportOpts
_rsReportOpts ReportSpec
rspec){ accountlistmode_ :: AccountListMode
accountlistmode_ = AccountListMode
ALTree }
    showunbudgeted :: Bool
showunbudgeted = ReportOpts -> Bool
empty_ ReportOpts
ropts
    budgetedaccts :: Set AccountName
budgetedaccts =
      String -> Set AccountName -> Set AccountName
forall a. Show a => String -> a -> a
dbg3 String
"budgetedacctsinperiod" (Set AccountName -> Set AccountName)
-> Set AccountName -> Set AccountName
forall a b. (a -> b) -> a -> b
$
      [AccountName] -> Set AccountName
forall a. Ord a => [a] -> Set a
S.fromList ([AccountName] -> Set AccountName)
-> [AccountName] -> Set AccountName
forall a b. (a -> b) -> a -> b
$
      [AccountName] -> [AccountName]
expandAccountNames ([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]
`runPeriodicTransaction` DateSpan
reportspan) ([PeriodicTransaction] -> [Transaction])
-> [PeriodicTransaction] -> [Transaction]
forall a b. (a -> b) -> a -> b
$
      Journal -> [PeriodicTransaction]
jperiodictxns Journal
j
    actualj :: Journal
actualj = Set AccountName -> Bool -> Journal -> Journal
journalWithBudgetAccountNames Set AccountName
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 :: PeriodicReport DisplayName MixedAmount
budgetgoalreport@(PeriodicReport [DateSpan]
_ [PeriodicReportRow DisplayName MixedAmount]
budgetgoalitems PeriodicReportRow () MixedAmount
budgetgoaltotals) =
        String
-> PeriodicReport DisplayName MixedAmount
-> PeriodicReport DisplayName MixedAmount
forall a. Show a => String -> a -> a
dbg5 String
"budgetgoalreport" (PeriodicReport DisplayName MixedAmount
 -> PeriodicReport DisplayName MixedAmount)
-> PeriodicReport DisplayName MixedAmount
-> PeriodicReport DisplayName MixedAmount
forall a b. (a -> b) -> a -> b
$ ReportSpec
-> Journal
-> PriceOracle
-> Set AccountName
-> PeriodicReport DisplayName MixedAmount
multiBalanceReportWith ReportSpec
rspec{_rsReportOpts :: ReportOpts
_rsReportOpts=ReportOpts
ropts{empty_ :: Bool
empty_=Bool
True}} Journal
budgetj PriceOracle
priceoracle Set AccountName
forall a. Monoid a => a
mempty
    budgetedacctsseen :: Set AccountName
budgetedacctsseen = [AccountName] -> Set AccountName
forall a. Ord a => [a] -> Set a
S.fromList ([AccountName] -> Set AccountName)
-> [AccountName] -> Set AccountName
forall a b. (a -> b) -> a -> b
$ (PeriodicReportRow DisplayName MixedAmount -> AccountName)
-> [PeriodicReportRow DisplayName MixedAmount] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow DisplayName MixedAmount -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName [PeriodicReportRow DisplayName MixedAmount]
budgetgoalitems
    actualreport :: PeriodicReport DisplayName MixedAmount
actualreport@(PeriodicReport [DateSpan]
actualspans [PeriodicReportRow DisplayName MixedAmount]
_ PeriodicReportRow () MixedAmount
_) =
        String
-> PeriodicReport DisplayName MixedAmount
-> PeriodicReport DisplayName MixedAmount
forall a. Show a => String -> a -> a
dbg5 String
"actualreport"     (PeriodicReport DisplayName MixedAmount
 -> PeriodicReport DisplayName MixedAmount)
-> PeriodicReport DisplayName MixedAmount
-> PeriodicReport DisplayName MixedAmount
forall a b. (a -> b) -> a -> b
$ ReportSpec
-> Journal
-> PriceOracle
-> Set AccountName
-> PeriodicReport DisplayName MixedAmount
multiBalanceReportWith ReportSpec
rspec{_rsReportOpts :: ReportOpts
_rsReportOpts=ReportOpts
ropts{empty_ :: Bool
empty_=Bool
True}} Journal
actualj PriceOracle
priceoracle Set AccountName
budgetedacctsseen
    budgetgoalreport' :: PeriodicReport DisplayName MixedAmount
budgetgoalreport'
      -- If no interval is specified:
      -- budgetgoalreport's span might be shorter actualreport's due to periodic txns;
      -- it should be safe to replace it with the latter, so they combine well.
      | ReportOpts -> Interval
interval_ ReportOpts
ropts Interval -> Interval -> Bool
forall a. Eq a => a -> a -> Bool
== Interval
NoInterval = [DateSpan]
-> [PeriodicReportRow DisplayName MixedAmount]
-> PeriodicReportRow () MixedAmount
-> PeriodicReport DisplayName MixedAmount
forall a b.
[DateSpan]
-> [PeriodicReportRow a b]
-> PeriodicReportRow () b
-> PeriodicReport a b
PeriodicReport [DateSpan]
actualspans [PeriodicReportRow DisplayName MixedAmount]
budgetgoalitems PeriodicReportRow () MixedAmount
budgetgoaltotals
      | Bool
otherwise = PeriodicReport DisplayName MixedAmount
budgetgoalreport
    budgetreport :: BudgetReport
budgetreport = ReportOpts
-> Journal
-> PeriodicReport DisplayName MixedAmount
-> PeriodicReport DisplayName MixedAmount
-> BudgetReport
combineBudgetAndActual ReportOpts
ropts Journal
j PeriodicReport DisplayName MixedAmount
budgetgoalreport' PeriodicReport DisplayName MixedAmount
actualreport

-- | Use all periodic transactions in the journal to generate
-- budget goal transactions in the specified date span.
-- Budget goal transactions are similar to forecast transactions except
-- their purpose and effect is to define balance change goals, per account and period,
-- for BudgetReport.
journalAddBudgetGoalTransactions :: BalancingOpts -> ReportOpts -> DateSpan -> Journal -> Journal
journalAddBudgetGoalTransactions :: BalancingOpts -> ReportOpts -> DateSpan -> Journal -> Journal
journalAddBudgetGoalTransactions BalancingOpts
bopts ReportOpts
ropts DateSpan
reportspan 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
$ BalancingOpts -> Journal -> Either String Journal
journalBalanceTransactions BalancingOpts
bopts Journal
j{ jtxns :: [Transaction]
jtxns = [Transaction]
budgetts }  -- PARTIAL:
  where
    budgetspan :: DateSpan
budgetspan = String -> DateSpan -> DateSpan
forall a. Show a => String -> a -> a
dbg3 String
"budget span" (DateSpan -> DateSpan) -> DateSpan -> DateSpan
forall a b. (a -> b) -> a -> b
$ DateSpan
reportspan
    pat :: AccountName
pat = AccountName -> Maybe AccountName -> AccountName
forall a. a -> Maybe a -> a
fromMaybe AccountName
"" (Maybe AccountName -> AccountName)
-> Maybe AccountName -> AccountName
forall a b. (a -> b) -> a -> b
$ String -> Maybe AccountName -> Maybe AccountName
forall a. Show a => String -> a -> a
dbg3 String
"budget pattern" (Maybe AccountName -> Maybe AccountName)
-> Maybe AccountName -> Maybe AccountName
forall a b. (a -> b) -> a -> b
$ AccountName -> AccountName
T.toLower (AccountName -> AccountName)
-> Maybe AccountName -> Maybe AccountName
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ReportOpts -> Maybe AccountName
budgetpat_ ReportOpts
ropts
    -- select periodic transactions matching a pattern
    -- (the argument of the (final) --budget option).
    -- XXX two limitations/wishes, requiring more extensive type changes:
    -- - give an error if pat is non-null and matches no periodic txns
    -- - allow a regexp or a full hledger query, not just a substring
    budgetts :: [Transaction]
budgetts =
      String -> [Transaction] -> [Transaction]
forall a. Show a => String -> a -> a
dbg5 String
"budget goal txns" ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
forall a b. (a -> b) -> a -> b
$
      [Transaction -> Transaction
makeBudgetTxn Transaction
t
      | PeriodicTransaction
pt <- Journal -> [PeriodicTransaction]
jperiodictxns Journal
j
      , AccountName
pat AccountName -> AccountName -> Bool
`T.isInfixOf` AccountName -> AccountName
T.toLower (PeriodicTransaction -> AccountName
ptdescription PeriodicTransaction
pt)
      , Transaction
t <- PeriodicTransaction -> DateSpan -> [Transaction]
runPeriodicTransaction PeriodicTransaction
pt DateSpan
budgetspan
      ]
    makeBudgetTxn :: Transaction -> Transaction
makeBudgetTxn 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 String
"Budget transaction" }

-- | Adjust a journal's account names for budget reporting, in two ways:
--
-- 1. accounts with no budget goal anywhere in their ancestry are moved
--    under the "unbudgeted" top level account.
--
-- 2. subaccounts with no budget goal are merged with their closest parent account
--    with a budget goal, so that only budgeted accounts are shown.
--    This can be disabled by -E/--empty.
--
journalWithBudgetAccountNames :: S.Set AccountName -> Bool -> Journal -> Journal
journalWithBudgetAccountNames :: Set AccountName -> Bool -> Journal -> Journal
journalWithBudgetAccountNames Set AccountName
budgetedaccts Bool
showunbudgeted Journal
j =
  (Journal -> String) -> Journal -> Journal
forall a. Show a => (a -> String) -> a -> a
dbg5With ((String
"budget account names: "String -> String -> String
forall a. [a] -> [a] -> [a]
++)(String -> String) -> (Journal -> String) -> Journal -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
.[AccountName] -> String
forall a. Show a => a -> String
pshow([AccountName] -> String)
-> (Journal -> [AccountName]) -> Journal -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Journal -> [AccountName]
journalAccountNamesUsed) (Journal -> Journal) -> Journal -> Journal
forall a b. (a -> b) -> a -> b
$
  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 = Transaction -> Transaction
txnTieKnot (Transaction -> Transaction)
-> (Transaction -> Transaction) -> Transaction -> Transaction
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 :: 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
poriginal Posting
p Maybe Posting -> Maybe Posting -> Maybe Posting
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Posting -> Maybe Posting
forall a. a -> Maybe a
Just Posting
p }
    remapAccount :: AccountName -> AccountName
remapAccount AccountName
a
      | AccountName
a AccountName -> Set AccountName -> Bool
forall a. Ord a => a -> Set a -> Bool
`S.member` Set AccountName
budgetedaccts = AccountName
a
      | Just AccountName
p <- Maybe AccountName
budgetedparent   = if Bool
showunbudgeted then AccountName
a else AccountName
p
      | 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
        budgetedparent :: Maybe AccountName
budgetedparent = (AccountName -> Bool) -> [AccountName] -> Maybe AccountName
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (AccountName -> Set AccountName -> Bool
forall a. Ord a => a -> Set a -> Bool
`S.member` Set AccountName
budgetedaccts) ([AccountName] -> Maybe AccountName)
-> [AccountName] -> Maybe AccountName
forall a b. (a -> b) -> a -> b
$ AccountName -> [AccountName]
parentAccountNames AccountName
a
        u :: AccountName
u = AccountName
unbudgetedAccountName

-- | Combine a per-account-and-subperiod report of budget goals, and one
-- of actual change amounts, into a budget performance report.
-- The two reports should have the same report interval, but need not
-- have exactly the same account rows or date columns.
-- (Cells in the combined budget report can be missing a budget goal,
-- an actual amount, or both.) The combined report will include:
--
-- - consecutive subperiods at the same interval as the two reports,
--   spanning the period of both reports
--
-- - all accounts mentioned in either report, sorted by account code or
--   account name or amount as appropriate.
--
combineBudgetAndActual :: ReportOpts -> Journal -> MultiBalanceReport -> MultiBalanceReport -> BudgetReport
combineBudgetAndActual :: ReportOpts
-> Journal
-> PeriodicReport DisplayName MixedAmount
-> PeriodicReport DisplayName MixedAmount
-> BudgetReport
combineBudgetAndActual ReportOpts
ropts Journal
j
      (PeriodicReport [DateSpan]
budgetperiods [PeriodicReportRow DisplayName MixedAmount]
budgetrows (PeriodicReportRow ()
_ [MixedAmount]
budgettots MixedAmount
budgetgrandtot MixedAmount
budgetgrandavg))
      (PeriodicReport [DateSpan]
actualperiods [PeriodicReportRow DisplayName MixedAmount]
actualrows (PeriodicReportRow ()
_ [MixedAmount]
actualtots MixedAmount
actualgrandtot MixedAmount
actualgrandavg)) =
    [DateSpan]
-> [PeriodicReportRow DisplayName BudgetCell]
-> PeriodicReportRow () BudgetCell
-> BudgetReport
forall a b.
[DateSpan]
-> [PeriodicReportRow a b]
-> PeriodicReportRow () b
-> PeriodicReport a b
PeriodicReport [DateSpan]
periods [PeriodicReportRow DisplayName BudgetCell]
sortedrows PeriodicReportRow () BudgetCell
totalrow
  where
    periods :: [DateSpan]
periods = [DateSpan] -> [DateSpan]
forall a. Ord a => [a] -> [a]
nubSort ([DateSpan] -> [DateSpan])
-> ([DateSpan] -> [DateSpan]) -> [DateSpan] -> [DateSpan]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (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

    -- first, combine any corresponding budget goals with actual changes
    rows1 :: [PeriodicReportRow DisplayName BudgetCell]
rows1 =
      [ DisplayName
-> [BudgetCell]
-> BudgetCell
-> BudgetCell
-> PeriodicReportRow DisplayName BudgetCell
forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow DisplayName
acct [BudgetCell]
amtandgoals BudgetCell
totamtandgoal BudgetCell
avgamtandgoal
      | PeriodicReportRow DisplayName
acct [MixedAmount]
actualamts MixedAmount
actualtot MixedAmount
actualavg <- [PeriodicReportRow DisplayName MixedAmount]
actualrows
      , let mbudgetgoals :: Maybe ([MixedAmount], MixedAmount, MixedAmount)
mbudgetgoals       = AccountName
-> HashMap AccountName ([MixedAmount], MixedAmount, MixedAmount)
-> Maybe ([MixedAmount], MixedAmount, MixedAmount)
forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
HM.lookup (DisplayName -> AccountName
displayFull DisplayName
acct) HashMap AccountName ([MixedAmount], MixedAmount, MixedAmount)
budgetGoalsByAcct :: Maybe ([BudgetGoal], BudgetTotal, BudgetAverage)
      , let budgetmamts :: [Maybe MixedAmount]
budgetmamts        = [Maybe MixedAmount]
-> (([MixedAmount], MixedAmount, MixedAmount)
    -> [Maybe MixedAmount])
-> Maybe ([MixedAmount], MixedAmount, MixedAmount)
-> [Maybe MixedAmount]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Maybe MixedAmount
forall a. Maybe a
Nothing Maybe MixedAmount -> [DateSpan] -> [Maybe MixedAmount]
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ [DateSpan]
periods) ((MixedAmount -> Maybe MixedAmount)
-> [MixedAmount] -> [Maybe MixedAmount]
forall a b. (a -> b) -> [a] -> [b]
map MixedAmount -> Maybe MixedAmount
forall a. a -> Maybe a
Just ([MixedAmount] -> [Maybe MixedAmount])
-> (([MixedAmount], MixedAmount, MixedAmount) -> [MixedAmount])
-> ([MixedAmount], MixedAmount, MixedAmount)
-> [Maybe MixedAmount]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([MixedAmount], MixedAmount, MixedAmount) -> [MixedAmount]
forall a b c. (a, b, c) -> a
first3) Maybe ([MixedAmount], MixedAmount, MixedAmount)
mbudgetgoals :: [Maybe BudgetGoal]
      , let mbudgettot :: Maybe MixedAmount
mbudgettot         = ([MixedAmount], MixedAmount, MixedAmount) -> MixedAmount
forall a b c. (a, b, c) -> b
second3 (([MixedAmount], MixedAmount, MixedAmount) -> MixedAmount)
-> Maybe ([MixedAmount], MixedAmount, MixedAmount)
-> Maybe MixedAmount
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe ([MixedAmount], MixedAmount, MixedAmount)
mbudgetgoals :: Maybe BudgetTotal
      , let mbudgetavg :: Maybe MixedAmount
mbudgetavg         = ([MixedAmount], MixedAmount, MixedAmount) -> MixedAmount
forall a b c. (a, b, c) -> c
third3 (([MixedAmount], MixedAmount, MixedAmount) -> MixedAmount)
-> Maybe ([MixedAmount], MixedAmount, MixedAmount)
-> Maybe MixedAmount
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe ([MixedAmount], MixedAmount, MixedAmount)
mbudgetgoals  :: Maybe BudgetAverage
      , let acctBudgetByPeriod :: Map DateSpan MixedAmount
acctBudgetByPeriod = [(DateSpan, MixedAmount)] -> Map DateSpan MixedAmount
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [ (DateSpan
p,MixedAmount
budgetamt) | (DateSpan
p, Just MixedAmount
budgetamt) <- [DateSpan]
-> [Maybe MixedAmount] -> [(DateSpan, Maybe MixedAmount)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
budgetperiods [Maybe MixedAmount]
budgetmamts ] :: Map DateSpan BudgetGoal
      , let acctActualByPeriod :: Map DateSpan MixedAmount
acctActualByPeriod = [(DateSpan, MixedAmount)] -> Map DateSpan MixedAmount
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [ (DateSpan
p,MixedAmount
actualamt) | (DateSpan
p, Just MixedAmount
actualamt) <- [DateSpan]
-> [Maybe MixedAmount] -> [(DateSpan, Maybe MixedAmount)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
actualperiods ((MixedAmount -> Maybe MixedAmount)
-> [MixedAmount] -> [Maybe MixedAmount]
forall a b. (a -> b) -> [a] -> [b]
map MixedAmount -> Maybe MixedAmount
forall a. a -> Maybe a
Just [MixedAmount]
actualamts) ] :: Map DateSpan Change
      , let amtandgoals :: [BudgetCell]
amtandgoals        = [ (DateSpan -> Map DateSpan MixedAmount -> Maybe MixedAmount
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan MixedAmount
acctActualByPeriod, DateSpan -> Map DateSpan MixedAmount -> Maybe MixedAmount
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan MixedAmount
acctBudgetByPeriod) | DateSpan
p <- [DateSpan]
periods ] :: [BudgetCell]
      , let totamtandgoal :: BudgetCell
totamtandgoal      = (MixedAmount -> Maybe MixedAmount
forall a. a -> Maybe a
Just MixedAmount
actualtot, Maybe MixedAmount
mbudgettot)
      , let avgamtandgoal :: BudgetCell
avgamtandgoal      = (MixedAmount -> Maybe MixedAmount
forall a. a -> Maybe a
Just MixedAmount
actualavg, Maybe MixedAmount
mbudgetavg)
      ]
      where
        HashMap AccountName ([MixedAmount], MixedAmount, MixedAmount)
budgetGoalsByAcct :: HashMap AccountName ([BudgetGoal], BudgetTotal, BudgetAverage) =
          [(AccountName, ([MixedAmount], MixedAmount, MixedAmount))]
-> HashMap AccountName ([MixedAmount], MixedAmount, MixedAmount)
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
HM.fromList [ (DisplayName -> AccountName
displayFull DisplayName
acct, ([MixedAmount]
amts, MixedAmount
tot, MixedAmount
avg))
                         | PeriodicReportRow DisplayName
acct [MixedAmount]
amts MixedAmount
tot MixedAmount
avg <- [PeriodicReportRow DisplayName MixedAmount]
budgetrows ]

    -- next, make rows for budget goals with no actual changes
    rows2 :: [PeriodicReportRow DisplayName BudgetCell]
rows2 =
      [ DisplayName
-> [BudgetCell]
-> BudgetCell
-> BudgetCell
-> PeriodicReportRow DisplayName BudgetCell
forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow DisplayName
acct [BudgetCell]
amtandgoals BudgetCell
forall a. (Maybe a, Maybe MixedAmount)
totamtandgoal BudgetCell
forall a. (Maybe a, Maybe MixedAmount)
avgamtandgoal
      | PeriodicReportRow DisplayName
acct [MixedAmount]
budgetgoals MixedAmount
budgettot MixedAmount
budgetavg <- [PeriodicReportRow DisplayName MixedAmount]
budgetrows
      , DisplayName -> AccountName
displayFull DisplayName
acct AccountName -> [AccountName] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` (PeriodicReportRow DisplayName BudgetCell -> AccountName)
-> [PeriodicReportRow DisplayName BudgetCell] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow DisplayName BudgetCell -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName [PeriodicReportRow DisplayName BudgetCell]
rows1
      , let acctBudgetByPeriod :: Map DateSpan MixedAmount
acctBudgetByPeriod = [(DateSpan, MixedAmount)] -> Map DateSpan MixedAmount
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(DateSpan, MixedAmount)] -> Map DateSpan MixedAmount)
-> [(DateSpan, MixedAmount)] -> Map DateSpan MixedAmount
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> [MixedAmount] -> [(DateSpan, MixedAmount)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
budgetperiods [MixedAmount]
budgetgoals :: Map DateSpan BudgetGoal
      , let amtandgoals :: [BudgetCell]
amtandgoals        = [ (Maybe MixedAmount
forall a. Maybe a
Nothing, DateSpan -> Map DateSpan MixedAmount -> Maybe MixedAmount
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan MixedAmount
acctBudgetByPeriod) | DateSpan
p <- [DateSpan]
periods ] :: [BudgetCell]
      , let totamtandgoal :: (Maybe a, Maybe MixedAmount)
totamtandgoal      = (Maybe a
forall a. Maybe a
Nothing, MixedAmount -> Maybe MixedAmount
forall a. a -> Maybe a
Just MixedAmount
budgettot)
      , let avgamtandgoal :: (Maybe a, Maybe MixedAmount)
avgamtandgoal      = (Maybe a
forall a. Maybe a
Nothing, MixedAmount -> Maybe MixedAmount
forall a. a -> Maybe a
Just MixedAmount
budgetavg)
      ]

    -- combine and re-sort rows
    -- TODO: add --sort-budget to sort by budget goal amount
    [PeriodicReportRow DisplayName BudgetCell]
sortedrows :: [BudgetReportRow] = [AccountName]
-> [PeriodicReportRow DisplayName BudgetCell]
-> [PeriodicReportRow DisplayName BudgetCell]
forall b.
[AccountName]
-> [PeriodicReportRow DisplayName b]
-> [PeriodicReportRow DisplayName b]
sortRowsLike ([PeriodicReportRow DisplayName BudgetCell] -> [AccountName]
forall b.
[PeriodicReportRow DisplayName (Maybe MixedAmount, b)]
-> [AccountName]
mbrsorted [PeriodicReportRow DisplayName BudgetCell]
unbudgetedrows [AccountName] -> [AccountName] -> [AccountName]
forall a. [a] -> [a] -> [a]
++ [PeriodicReportRow DisplayName BudgetCell] -> [AccountName]
forall b.
[PeriodicReportRow DisplayName (Maybe MixedAmount, b)]
-> [AccountName]
mbrsorted [PeriodicReportRow DisplayName BudgetCell]
rows') [PeriodicReportRow DisplayName BudgetCell]
rows
      where
        ([PeriodicReportRow DisplayName BudgetCell]
unbudgetedrows, [PeriodicReportRow DisplayName BudgetCell]
rows') = (PeriodicReportRow DisplayName BudgetCell -> Bool)
-> [PeriodicReportRow DisplayName BudgetCell]
-> ([PeriodicReportRow DisplayName BudgetCell],
    [PeriodicReportRow DisplayName BudgetCell])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition ((AccountName -> AccountName -> Bool
forall a. Eq a => a -> a -> Bool
==AccountName
unbudgetedAccountName) (AccountName -> Bool)
-> (PeriodicReportRow DisplayName BudgetCell -> AccountName)
-> PeriodicReportRow DisplayName BudgetCell
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PeriodicReportRow DisplayName BudgetCell -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName) [PeriodicReportRow DisplayName BudgetCell]
rows
        mbrsorted :: [PeriodicReportRow DisplayName (Maybe MixedAmount, b)]
-> [AccountName]
mbrsorted = (PeriodicReportRow DisplayName MixedAmount -> AccountName)
-> [PeriodicReportRow DisplayName MixedAmount] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow DisplayName MixedAmount -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName ([PeriodicReportRow DisplayName MixedAmount] -> [AccountName])
-> ([PeriodicReportRow DisplayName (Maybe MixedAmount, b)]
    -> [PeriodicReportRow DisplayName MixedAmount])
-> [PeriodicReportRow DisplayName (Maybe MixedAmount, b)]
-> [AccountName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ReportOpts
-> Journal
-> [PeriodicReportRow DisplayName MixedAmount]
-> [PeriodicReportRow DisplayName MixedAmount]
sortRows ReportOpts
ropts Journal
j ([PeriodicReportRow DisplayName MixedAmount]
 -> [PeriodicReportRow DisplayName MixedAmount])
-> ([PeriodicReportRow DisplayName (Maybe MixedAmount, b)]
    -> [PeriodicReportRow DisplayName MixedAmount])
-> [PeriodicReportRow DisplayName (Maybe MixedAmount, b)]
-> [PeriodicReportRow DisplayName MixedAmount]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (PeriodicReportRow DisplayName (Maybe MixedAmount, b)
 -> PeriodicReportRow DisplayName MixedAmount)
-> [PeriodicReportRow DisplayName (Maybe MixedAmount, b)]
-> [PeriodicReportRow DisplayName MixedAmount]
forall a b. (a -> b) -> [a] -> [b]
map (((Maybe MixedAmount, b) -> MixedAmount)
-> PeriodicReportRow DisplayName (Maybe MixedAmount, b)
-> PeriodicReportRow DisplayName MixedAmount
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (((Maybe MixedAmount, b) -> MixedAmount)
 -> PeriodicReportRow DisplayName (Maybe MixedAmount, b)
 -> PeriodicReportRow DisplayName MixedAmount)
-> ((Maybe MixedAmount, b) -> MixedAmount)
-> PeriodicReportRow DisplayName (Maybe MixedAmount, b)
-> PeriodicReportRow DisplayName MixedAmount
forall a b. (a -> b) -> a -> b
$ MixedAmount -> Maybe MixedAmount -> MixedAmount
forall a. a -> Maybe a -> a
fromMaybe MixedAmount
nullmixedamt (Maybe MixedAmount -> MixedAmount)
-> ((Maybe MixedAmount, b) -> Maybe MixedAmount)
-> (Maybe MixedAmount, b)
-> MixedAmount
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Maybe MixedAmount, b) -> Maybe MixedAmount
forall a b. (a, b) -> a
fst)
        rows :: [PeriodicReportRow DisplayName BudgetCell]
rows = [PeriodicReportRow DisplayName BudgetCell]
rows1 [PeriodicReportRow DisplayName BudgetCell]
-> [PeriodicReportRow DisplayName BudgetCell]
-> [PeriodicReportRow DisplayName BudgetCell]
forall a. [a] -> [a] -> [a]
++ [PeriodicReportRow DisplayName BudgetCell]
rows2

    totalrow :: PeriodicReportRow () BudgetCell
totalrow = ()
-> [BudgetCell]
-> BudgetCell
-> BudgetCell
-> PeriodicReportRow () BudgetCell
forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow ()
        [ (DateSpan -> Map DateSpan MixedAmount -> Maybe MixedAmount
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan MixedAmount
totActualByPeriod, DateSpan -> Map DateSpan MixedAmount -> Maybe MixedAmount
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan MixedAmount
totBudgetByPeriod) | DateSpan
p <- [DateSpan]
periods ]
        ( MixedAmount -> Maybe MixedAmount
forall a. a -> Maybe a
Just MixedAmount
actualgrandtot, MixedAmount -> Maybe MixedAmount
budget MixedAmount
budgetgrandtot )
        ( MixedAmount -> Maybe MixedAmount
forall a. a -> Maybe a
Just MixedAmount
actualgrandavg, MixedAmount -> Maybe MixedAmount
budget MixedAmount
budgetgrandavg )
      where
        totBudgetByPeriod :: Map DateSpan MixedAmount
totBudgetByPeriod = [(DateSpan, MixedAmount)] -> Map DateSpan MixedAmount
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(DateSpan, MixedAmount)] -> Map DateSpan MixedAmount)
-> [(DateSpan, MixedAmount)] -> Map DateSpan MixedAmount
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> [MixedAmount] -> [(DateSpan, MixedAmount)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
budgetperiods [MixedAmount]
budgettots :: Map DateSpan BudgetTotal
        totActualByPeriod :: Map DateSpan MixedAmount
totActualByPeriod = [(DateSpan, MixedAmount)] -> Map DateSpan MixedAmount
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(DateSpan, MixedAmount)] -> Map DateSpan MixedAmount)
-> [(DateSpan, MixedAmount)] -> Map DateSpan MixedAmount
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> [MixedAmount] -> [(DateSpan, MixedAmount)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
actualperiods [MixedAmount]
actualtots :: Map DateSpan Change
        budget :: MixedAmount -> Maybe MixedAmount
budget MixedAmount
b = if MixedAmount -> Bool
mixedAmountLooksZero MixedAmount
b then Maybe MixedAmount
forall a. Maybe a
Nothing else MixedAmount -> Maybe MixedAmount
forall a. a -> Maybe a
Just MixedAmount
b

-- | Render a budget report as plain text suitable for console output.
budgetReportAsText :: ReportOpts -> BudgetReport -> TL.Text
budgetReportAsText :: ReportOpts -> BudgetReport -> Text
budgetReportAsText ropts :: ReportOpts
ropts@ReportOpts{Bool
Int
[AccountName]
[Status]
Maybe Int
Maybe AccountName
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
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 -> [AccountName]
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
no_total_ :: Bool
row_total_ :: Bool
declared_ :: Bool
drop_ :: Int
accountlistmode_ :: AccountListMode
budgetpat_ :: Maybe AccountName
balanceaccum_ :: BalanceAccumulation
balancecalc_ :: BalanceCalculation
txn_dates_ :: Bool
related_ :: Bool
average_ :: Bool
querystring_ :: [AccountName]
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 AccountName
interval_ :: ReportOpts -> Interval
infer_prices_ :: ReportOpts -> Bool
empty_ :: ReportOpts -> Bool
accountlistmode_ :: ReportOpts -> AccountListMode
..} BudgetReport
budgetr = Builder -> Text
TB.toLazyText (Builder -> Text) -> Builder -> Text
forall a b. (a -> b) -> a -> b
$
    AccountName -> Builder
TB.fromText AccountName
title Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> AccountName -> Builder
TB.fromText AccountName
"\n\n" Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<>
      ReportOpts -> Table AccountName AccountName WideBuilder -> Builder
balanceReportTableAsText ReportOpts
ropts (ReportOpts
-> BudgetReport -> Table AccountName AccountName WideBuilder
budgetReportAsTable ReportOpts
ropts BudgetReport
budgetr)
  where
    title :: AccountName
title = AccountName
"Budget performance in " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> DateSpan -> AccountName
showDateSpan (BudgetReport -> DateSpan
forall a b. PeriodicReport a b -> DateSpan
periodicReportSpan BudgetReport
budgetr)
           AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> (case Maybe ConversionOp
conversionop_ of
                 Just ConversionOp
ToCost -> AccountName
", converted to cost"
                 Maybe ConversionOp
_           -> AccountName
"")
           AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> (case Maybe ValuationType
value_ of
                 Just (AtThen Maybe AccountName
_mc)   -> AccountName
", valued at posting date"
                 Just (AtEnd Maybe AccountName
_mc)    -> AccountName
", valued at period ends"
                 Just (AtNow Maybe AccountName
_mc)    -> AccountName
", current value"
                 Just (AtDate Day
d Maybe AccountName
_mc) -> AccountName
", valued at " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> Day -> AccountName
showDate Day
d
                 Maybe ValuationType
Nothing             -> AccountName
"")
           AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
":"

-- | Build a 'Table' from a multi-column balance report.
budgetReportAsTable :: ReportOpts -> BudgetReport -> Tab.Table Text Text WideBuilder
budgetReportAsTable :: ReportOpts
-> BudgetReport -> Table AccountName AccountName WideBuilder
budgetReportAsTable
  ReportOpts{Bool
Int
[AccountName]
[Status]
Maybe Int
Maybe AccountName
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
no_total_ :: Bool
row_total_ :: Bool
declared_ :: Bool
drop_ :: Int
accountlistmode_ :: AccountListMode
budgetpat_ :: Maybe AccountName
balanceaccum_ :: BalanceAccumulation
balancecalc_ :: BalanceCalculation
txn_dates_ :: Bool
related_ :: Bool
average_ :: Bool
querystring_ :: [AccountName]
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
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 -> [AccountName]
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 AccountName
interval_ :: ReportOpts -> Interval
infer_prices_ :: ReportOpts -> Bool
empty_ :: ReportOpts -> Bool
accountlistmode_ :: ReportOpts -> AccountListMode
..}
  (PeriodicReport [DateSpan]
spans [PeriodicReportRow DisplayName BudgetCell]
items PeriodicReportRow () BudgetCell
tr) =
    Table AccountName AccountName WideBuilder
-> Table AccountName AccountName WideBuilder
forall rh a. Table rh rh a -> Table rh rh a
maybetransposetable (Table AccountName AccountName WideBuilder
 -> Table AccountName AccountName WideBuilder)
-> Table AccountName AccountName WideBuilder
-> Table AccountName AccountName WideBuilder
forall a b. (a -> b) -> a -> b
$
    Table AccountName AccountName WideBuilder
-> Table AccountName AccountName WideBuilder
forall ch.
Table AccountName ch WideBuilder
-> Table AccountName ch WideBuilder
addtotalrow (Table AccountName AccountName WideBuilder
 -> Table AccountName AccountName WideBuilder)
-> Table AccountName AccountName WideBuilder
-> Table AccountName AccountName WideBuilder
forall a b. (a -> b) -> a -> b
$
    Header AccountName
-> Header AccountName
-> [[WideBuilder]]
-> Table AccountName AccountName WideBuilder
forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
Tab.Table
      (Properties -> [Header AccountName] -> Header AccountName
forall h. Properties -> [Header h] -> Header h
Tab.Group Properties
Tab.NoLine ([Header AccountName] -> Header AccountName)
-> [Header AccountName] -> Header AccountName
forall a b. (a -> b) -> a -> b
$ (AccountName -> Header AccountName)
-> [AccountName] -> [Header AccountName]
forall a b. (a -> b) -> [a] -> [b]
map AccountName -> Header AccountName
forall h. h -> Header h
Tab.Header [AccountName]
accts)
      (Properties -> [Header AccountName] -> Header AccountName
forall h. Properties -> [Header h] -> Header h
Tab.Group Properties
Tab.NoLine ([Header AccountName] -> Header AccountName)
-> [Header AccountName] -> Header AccountName
forall a b. (a -> b) -> a -> b
$ (AccountName -> Header AccountName)
-> [AccountName] -> [Header AccountName]
forall a b. (a -> b) -> [a] -> [b]
map AccountName -> Header AccountName
forall h. h -> Header h
Tab.Header [AccountName]
colheadings)
      [[WideBuilder]]
rows
  where
    colheadings :: [AccountName]
colheadings = [AccountName
"Commodity" | Layout
layout_ Layout -> Layout -> Bool
forall a. Eq a => a -> a -> Bool
== Layout
LayoutBare]
                  [AccountName] -> [AccountName] -> [AccountName]
forall a. [a] -> [a] -> [a]
++ (DateSpan -> AccountName) -> [DateSpan] -> [AccountName]
forall a b. (a -> b) -> [a] -> [b]
map (BalanceAccumulation -> [DateSpan] -> DateSpan -> AccountName
reportPeriodName BalanceAccumulation
balanceaccum_ [DateSpan]
spans) [DateSpan]
spans
                  [AccountName] -> [AccountName] -> [AccountName]
forall a. [a] -> [a] -> [a]
++ [AccountName
"  Total" | Bool
row_total_]
                  [AccountName] -> [AccountName] -> [AccountName]
forall a. [a] -> [a] -> [a]
++ [AccountName
"Average" | Bool
average_]

    -- FIXME. Have to check explicitly for which to render here, since
    -- budgetReport sets accountlistmode to ALTree. Find a principled way to do
    -- this.
    renderacct :: PeriodicReportRow DisplayName a -> AccountName
renderacct PeriodicReportRow DisplayName a
row = case AccountListMode
accountlistmode_ of
        AccountListMode
ALTree -> Int -> AccountName -> AccountName
T.replicate ((PeriodicReportRow DisplayName a -> Int
forall a. PeriodicReportRow DisplayName a -> Int
prrDepth PeriodicReportRow DisplayName a
row Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1)Int -> Int -> Int
forall a. Num a => a -> a -> a
*Int
2) AccountName
" " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> PeriodicReportRow DisplayName a -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrDisplayName PeriodicReportRow DisplayName a
row
        AccountListMode
ALFlat -> Int -> AccountName -> AccountName
accountNameDrop (Int
drop_) (AccountName -> AccountName) -> AccountName -> AccountName
forall a b. (a -> b) -> a -> b
$ PeriodicReportRow DisplayName a -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName PeriodicReportRow DisplayName a
row

    addtotalrow :: Table AccountName ch WideBuilder
-> Table AccountName ch WideBuilder
addtotalrow
      | Bool
no_total_ = Table AccountName ch WideBuilder
-> Table AccountName ch WideBuilder
forall a. a -> a
id
      | Bool
otherwise = let rh :: Header AccountName
rh = Properties -> [Header AccountName] -> Header AccountName
forall h. Properties -> [Header h] -> Header h
Tab.Group Properties
Tab.NoLine ([Header AccountName] -> Header AccountName)
-> (Header AccountName -> [Header AccountName])
-> Header AccountName
-> Header AccountName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Header AccountName -> [Header AccountName]
forall a. Int -> a -> [a]
replicate ([[WideBuilder]] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [[WideBuilder]]
totalrows) (Header AccountName -> Header AccountName)
-> Header AccountName -> Header AccountName
forall a b. (a -> b) -> a -> b
$ AccountName -> Header AccountName
forall h. h -> Header h
Tab.Header AccountName
""
                        ch :: Header [a]
ch = [a] -> Header [a]
forall h. h -> Header h
Tab.Header [] -- ignored
                     in ((Table AccountName ch WideBuilder
 -> Table AccountName [Any] WideBuilder
 -> Table AccountName ch WideBuilder)
-> Table AccountName [Any] WideBuilder
-> Table AccountName ch WideBuilder
-> Table AccountName ch WideBuilder
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Properties
-> Table AccountName ch WideBuilder
-> Table AccountName [Any] WideBuilder
-> Table AccountName ch WideBuilder
forall rh ch a ch2.
Properties -> Table rh ch a -> Table rh ch2 a -> Table rh ch a
Tab.concatTables Properties
Tab.SingleLine) (Table AccountName [Any] WideBuilder
 -> Table AccountName ch WideBuilder
 -> Table AccountName ch WideBuilder)
-> Table AccountName [Any] WideBuilder
-> Table AccountName ch WideBuilder
-> Table AccountName ch WideBuilder
forall a b. (a -> b) -> a -> b
$ Header AccountName
-> Header [Any]
-> [[WideBuilder]]
-> Table AccountName [Any] WideBuilder
forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
Tab.Table Header AccountName
rh Header [Any]
forall a. Header [a]
ch [[WideBuilder]]
totalrows)

    maybetranspose :: [[a]] -> [[a]]
maybetranspose
      | Bool
transpose_ = [[a]] -> [[a]]
forall a. [[a]] -> [[a]]
transpose
      | Bool
otherwise  = [[a]] -> [[a]]
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) -> Header rh -> Header rh -> [[a]] -> Table rh rh a
forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
Tab.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

    ([AccountName]
accts, [[WideBuilder]]
rows, [[WideBuilder]]
totalrows) = ([AccountName]
accts, [WideBuilder] -> [[WideBuilder]] -> [[WideBuilder]]
forall a. [a] -> [[a]] -> [[a]]
prependcs [WideBuilder]
itemscs ([[BudgetDisplayCell]] -> [[WideBuilder]]
padcells [[BudgetDisplayCell]]
texts), [WideBuilder] -> [[WideBuilder]] -> [[WideBuilder]]
forall a. [a] -> [[a]] -> [[a]]
prependcs [WideBuilder]
trcs ([[BudgetDisplayCell]] -> [[WideBuilder]]
padtr [[BudgetDisplayCell]]
trtexts))
      where
        shownitems :: [[(AccountName, WideBuilder, BudgetDisplayRow)]]
        shownitems :: [[(AccountName, WideBuilder, [BudgetDisplayCell])]]
shownitems = ((PeriodicReportRow DisplayName BudgetCell
 -> [(AccountName, WideBuilder, [BudgetDisplayCell])])
-> [PeriodicReportRow DisplayName BudgetCell]
-> [[(AccountName, WideBuilder, [BudgetDisplayCell])]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\PeriodicReportRow DisplayName BudgetCell
i -> ((WideBuilder, [BudgetDisplayCell])
 -> (AccountName, WideBuilder, [BudgetDisplayCell]))
-> [(WideBuilder, [BudgetDisplayCell])]
-> [(AccountName, WideBuilder, [BudgetDisplayCell])]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\(WideBuilder
cs, [BudgetDisplayCell]
cvals) -> (PeriodicReportRow DisplayName BudgetCell -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
renderacct PeriodicReportRow DisplayName BudgetCell
i, WideBuilder
cs, [BudgetDisplayCell]
cvals)) ([(WideBuilder, [BudgetDisplayCell])]
 -> [(AccountName, WideBuilder, [BudgetDisplayCell])])
-> ([BudgetCell] -> [(WideBuilder, [BudgetDisplayCell])])
-> [BudgetCell]
-> [(AccountName, WideBuilder, [BudgetDisplayCell])]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [BudgetCell] -> [(WideBuilder, [BudgetDisplayCell])]
showrow ([BudgetCell] -> [(AccountName, WideBuilder, [BudgetDisplayCell])])
-> [BudgetCell]
-> [(AccountName, WideBuilder, [BudgetDisplayCell])]
forall a b. (a -> b) -> a -> b
$ PeriodicReportRow DisplayName BudgetCell -> [BudgetCell]
forall a a. PeriodicReportRow a a -> [a]
rowToBudgetCells PeriodicReportRow DisplayName BudgetCell
i) [PeriodicReportRow DisplayName BudgetCell]
items)
        ([AccountName]
accts, [WideBuilder]
itemscs, [[BudgetDisplayCell]]
texts) = [(AccountName, WideBuilder, [BudgetDisplayCell])]
-> ([AccountName], [WideBuilder], [[BudgetDisplayCell]])
forall a b c. [(a, b, c)] -> ([a], [b], [c])
unzip3 ([(AccountName, WideBuilder, [BudgetDisplayCell])]
 -> ([AccountName], [WideBuilder], [[BudgetDisplayCell]]))
-> [(AccountName, WideBuilder, [BudgetDisplayCell])]
-> ([AccountName], [WideBuilder], [[BudgetDisplayCell]])
forall a b. (a -> b) -> a -> b
$ [[(AccountName, WideBuilder, [BudgetDisplayCell])]]
-> [(AccountName, WideBuilder, [BudgetDisplayCell])]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[(AccountName, WideBuilder, [BudgetDisplayCell])]]
shownitems

        showntr    :: [[(WideBuilder, BudgetDisplayRow)]]
        showntr :: [[(WideBuilder, [BudgetDisplayCell])]]
showntr    = [[BudgetCell] -> [(WideBuilder, [BudgetDisplayCell])]
showrow ([BudgetCell] -> [(WideBuilder, [BudgetDisplayCell])])
-> [BudgetCell] -> [(WideBuilder, [BudgetDisplayCell])]
forall a b. (a -> b) -> a -> b
$ PeriodicReportRow () BudgetCell -> [BudgetCell]
forall a a. PeriodicReportRow a a -> [a]
rowToBudgetCells PeriodicReportRow () BudgetCell
tr]
        ([WideBuilder]
trcs, [[BudgetDisplayCell]]
trtexts)         = [(WideBuilder, [BudgetDisplayCell])]
-> ([WideBuilder], [[BudgetDisplayCell]])
forall a b. [(a, b)] -> ([a], [b])
unzip  ([(WideBuilder, [BudgetDisplayCell])]
 -> ([WideBuilder], [[BudgetDisplayCell]]))
-> [(WideBuilder, [BudgetDisplayCell])]
-> ([WideBuilder], [[BudgetDisplayCell]])
forall a b. (a -> b) -> a -> b
$ [[(WideBuilder, [BudgetDisplayCell])]]
-> [(WideBuilder, [BudgetDisplayCell])]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[(WideBuilder, [BudgetDisplayCell])]]
showntr
        trwidths :: [(Int, Int, Int)]
trwidths
          | Bool
transpose_ = Int -> [(Int, Int, Int)] -> [(Int, Int, Int)]
forall a. Int -> [a] -> [a]
drop ([[BudgetDisplayCell]] -> Int
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 = [[WideBuilder]] -> [[WideBuilder]]
forall a. [[a]] -> [[a]]
maybetranspose ([[WideBuilder]] -> [[WideBuilder]])
-> ([[BudgetDisplayCell]] -> [[WideBuilder]])
-> [[BudgetDisplayCell]]
-> [[WideBuilder]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([BudgetDisplayCell] -> [WideBuilder])
-> [[BudgetDisplayCell]] -> [[WideBuilder]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((((Int, Int, Int), BudgetDisplayCell) -> WideBuilder)
-> [((Int, Int, Int), BudgetDisplayCell)] -> [WideBuilder]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (((Int, Int, Int) -> BudgetDisplayCell -> WideBuilder)
-> ((Int, Int, Int), BudgetDisplayCell) -> WideBuilder
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry (Int, Int, Int) -> BudgetDisplayCell -> WideBuilder
paddisplaycell) ([((Int, Int, Int), BudgetDisplayCell)] -> [WideBuilder])
-> ([BudgetDisplayCell] -> [((Int, Int, Int), BudgetDisplayCell)])
-> [BudgetDisplayCell]
-> [WideBuilder]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(Int, Int, Int)]
-> [BudgetDisplayCell] -> [((Int, Int, Int), BudgetDisplayCell)]
forall a b. [a] -> [b] -> [(a, b)]
zip [(Int, Int, Int)]
widths)   ([[BudgetDisplayCell]] -> [[WideBuilder]])
-> ([[BudgetDisplayCell]] -> [[BudgetDisplayCell]])
-> [[BudgetDisplayCell]]
-> [[WideBuilder]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[BudgetDisplayCell]] -> [[BudgetDisplayCell]]
forall a. [[a]] -> [[a]]
maybetranspose
        padtr :: [[BudgetDisplayCell]] -> [[WideBuilder]]
padtr    = [[WideBuilder]] -> [[WideBuilder]]
forall a. [[a]] -> [[a]]
maybetranspose ([[WideBuilder]] -> [[WideBuilder]])
-> ([[BudgetDisplayCell]] -> [[WideBuilder]])
-> [[BudgetDisplayCell]]
-> [[WideBuilder]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([BudgetDisplayCell] -> [WideBuilder])
-> [[BudgetDisplayCell]] -> [[WideBuilder]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((((Int, Int, Int), BudgetDisplayCell) -> WideBuilder)
-> [((Int, Int, Int), BudgetDisplayCell)] -> [WideBuilder]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (((Int, Int, Int) -> BudgetDisplayCell -> WideBuilder)
-> ((Int, Int, Int), BudgetDisplayCell) -> WideBuilder
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry (Int, Int, Int) -> BudgetDisplayCell -> WideBuilder
paddisplaycell) ([((Int, Int, Int), BudgetDisplayCell)] -> [WideBuilder])
-> ([BudgetDisplayCell] -> [((Int, Int, Int), BudgetDisplayCell)])
-> [BudgetDisplayCell]
-> [WideBuilder]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(Int, Int, Int)]
-> [BudgetDisplayCell] -> [((Int, Int, Int), BudgetDisplayCell)]
forall a b. [a] -> [b] -> [(a, b)]
zip [(Int, Int, Int)]
trwidths) ([[BudgetDisplayCell]] -> [[WideBuilder]])
-> ([[BudgetDisplayCell]] -> [[BudgetDisplayCell]])
-> [[BudgetDisplayCell]]
-> [[WideBuilder]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[BudgetDisplayCell]] -> [[BudgetDisplayCell]]
forall a. [[a]] -> [[a]]
maybetranspose

        -- commodities are shown with the amounts without `layout_ == LayoutBare`
        prependcs :: [a] -> [[a]] -> [[a]]
prependcs [a]
cs
          | Layout
layout_ Layout -> Layout -> Bool
forall a. Eq a => a -> a -> Bool
/= Layout
LayoutBare = [[a]] -> [[a]]
forall a. a -> a
id
          | Bool
otherwise = (a -> [a] -> [a]) -> [a] -> [[a]] -> [[a]]
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
        [a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ [a
rowtot | Bool
row_total_ Bool -> Bool -> Bool
&& Bool -> Bool
not ([a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [a]
as)]
        [a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ [a
rowavg | Bool
average_   Bool -> Bool -> Bool
&& Bool -> Bool
not ([a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [a]
as)]

    -- functions for displaying budget cells depending on `commodity-layout_` option
    rowfuncs :: [CommoditySymbol] -> (BudgetShowMixed, BudgetPercBudget)
    rowfuncs :: [AccountName] -> (BudgetShowMixed, BudgetPercBudget)
rowfuncs [AccountName]
cs = case Layout
layout_ of
      LayoutWide Maybe Int
width ->
           ( WideBuilder -> [WideBuilder]
forall (f :: * -> *) a. Applicative f => a -> f a
pure (WideBuilder -> [WideBuilder])
-> (MixedAmount -> WideBuilder) -> BudgetShowMixed
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AmountDisplayOpts -> MixedAmount -> WideBuilder
showMixedAmountB AmountDisplayOpts
oneLine{displayColour :: Bool
displayColour=Bool
color_, displayMaxWidth :: Maybe Int
displayMaxWidth=Maybe Int
width}
           , \MixedAmount
a -> Maybe Percentage -> [Maybe Percentage]
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Percentage -> [Maybe Percentage])
-> (MixedAmount -> Maybe Percentage)
-> MixedAmount
-> [Maybe Percentage]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MixedAmount -> MixedAmount -> Maybe Percentage
percentage MixedAmount
a)
      Layout
_ -> ( AmountDisplayOpts -> BudgetShowMixed
showMixedAmountLinesB AmountDisplayOpts
noPrice{displayOrder :: Maybe [AccountName]
displayOrder=[AccountName] -> Maybe [AccountName]
forall a. a -> Maybe a
Just [AccountName]
cs, displayMinWidth :: Maybe Int
displayMinWidth=Maybe Int
forall a. Maybe a
Nothing, displayColour :: Bool
displayColour=Bool
color_}
           , \MixedAmount
a MixedAmount
b -> (AccountName -> Maybe Percentage)
-> [AccountName] -> [Maybe Percentage]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (MixedAmount -> MixedAmount -> AccountName -> Maybe Percentage
percentage' MixedAmount
a MixedAmount
b) [AccountName]
cs)

    showrow :: [BudgetCell] -> [(WideBuilder, BudgetDisplayRow)]
    showrow :: [BudgetCell] -> [(WideBuilder, [BudgetDisplayCell])]
showrow [BudgetCell]
row =
      let cs :: [AccountName]
cs = [BudgetCell] -> [AccountName]
budgetCellsCommodities [BudgetCell]
row
          (BudgetShowMixed
showmixed, BudgetPercBudget
percbudget) = [AccountName] -> (BudgetShowMixed, BudgetPercBudget)
rowfuncs [AccountName]
cs
       in   [WideBuilder]
-> [[BudgetDisplayCell]] -> [(WideBuilder, [BudgetDisplayCell])]
forall a b. [a] -> [b] -> [(a, b)]
zip ((AccountName -> WideBuilder) -> [AccountName] -> [WideBuilder]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap AccountName -> WideBuilder
wbFromText [AccountName]
cs)
          ([[BudgetDisplayCell]] -> [(WideBuilder, [BudgetDisplayCell])])
-> ([BudgetCell] -> [[BudgetDisplayCell]])
-> [BudgetCell]
-> [(WideBuilder, [BudgetDisplayCell])]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[BudgetDisplayCell]] -> [[BudgetDisplayCell]]
forall a. [[a]] -> [[a]]
transpose
          ([[BudgetDisplayCell]] -> [[BudgetDisplayCell]])
-> ([BudgetCell] -> [[BudgetDisplayCell]])
-> [BudgetCell]
-> [[BudgetDisplayCell]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (BudgetCell -> [BudgetDisplayCell])
-> [BudgetCell] -> [[BudgetDisplayCell]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (BudgetShowMixed
-> BudgetPercBudget -> BudgetCell -> [BudgetDisplayCell]
showcell BudgetShowMixed
showmixed BudgetPercBudget
percbudget)
          ([BudgetCell] -> [(WideBuilder, [BudgetDisplayCell])])
-> [BudgetCell] -> [(WideBuilder, [BudgetDisplayCell])]
forall a b. (a -> b) -> a -> b
$ [BudgetCell]
row

    budgetCellsCommodities :: [BudgetCell] -> [AccountName]
budgetCellsCommodities = Set AccountName -> [AccountName]
forall a. Set a -> [a]
S.toList (Set AccountName -> [AccountName])
-> ([BudgetCell] -> Set AccountName)
-> [BudgetCell]
-> [AccountName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Set AccountName -> Set AccountName -> Set AccountName)
-> Set AccountName -> [Set AccountName] -> Set AccountName
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Set AccountName -> Set AccountName -> Set AccountName
forall a. Ord a => Set a -> Set a -> Set a
S.union Set AccountName
forall a. Monoid a => a
mempty ([Set AccountName] -> Set AccountName)
-> ([BudgetCell] -> [Set AccountName])
-> [BudgetCell]
-> Set AccountName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (BudgetCell -> Set AccountName)
-> [BudgetCell] -> [Set AccountName]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap BudgetCell -> Set AccountName
budgetCellCommodities
    budgetCellCommodities :: BudgetCell -> S.Set CommoditySymbol
    budgetCellCommodities :: BudgetCell -> Set AccountName
budgetCellCommodities (Maybe MixedAmount
am, Maybe MixedAmount
bm) = Maybe MixedAmount -> Set AccountName
f Maybe MixedAmount
am Set AccountName -> Set AccountName -> Set AccountName
forall a. Ord a => Set a -> Set a -> Set a
`S.union` Maybe MixedAmount -> Set AccountName
f Maybe MixedAmount
bm
      where f :: Maybe MixedAmount -> Set AccountName
f = Set AccountName
-> (MixedAmount -> Set AccountName)
-> Maybe MixedAmount
-> Set AccountName
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Set AccountName
forall a. Monoid a => a
mempty MixedAmount -> Set AccountName
maCommodities

    cellswidth :: [BudgetCell] -> [[(Int, Int, Int)]]
    cellswidth :: [BudgetCell] -> [[(Int, Int, Int)]]
cellswidth [BudgetCell]
row =
      let cs :: [AccountName]
cs = [BudgetCell] -> [AccountName]
budgetCellsCommodities [BudgetCell]
row
          (BudgetShowMixed
showmixed, BudgetPercBudget
percbudget) = [AccountName] -> (BudgetShowMixed, BudgetPercBudget)
rowfuncs [AccountName]
cs
          disp :: BudgetCell -> [BudgetDisplayCell]
disp = BudgetShowMixed
-> BudgetPercBudget -> BudgetCell -> [BudgetDisplayCell]
showcell BudgetShowMixed
showmixed BudgetPercBudget
percbudget
          budgetpercwidth :: (WideBuilder, Maybe WideBuilder) -> (Int, Int)
budgetpercwidth = WideBuilder -> Int
wbWidth (WideBuilder -> Int)
-> (Maybe WideBuilder -> Int)
-> (WideBuilder, Maybe WideBuilder)
-> (Int, Int)
forall (a :: * -> * -> *) b c b' c'.
Arrow a =>
a b c -> a b' c' -> a (b, b') (c, c')
*** Int -> (WideBuilder -> Int) -> Maybe WideBuilder -> Int
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) = (Int, Int)
-> ((WideBuilder, Maybe WideBuilder) -> (Int, Int))
-> Maybe (WideBuilder, Maybe WideBuilder)
-> (Int, Int)
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 (BudgetCell -> [(Int, Int, Int)])
-> [BudgetCell] -> [[(Int, Int, Int)]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((BudgetDisplayCell -> (Int, Int, Int))
-> [BudgetDisplayCell] -> [(Int, Int, Int)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap BudgetDisplayCell -> (Int, Int, Int)
cellwidth ([BudgetDisplayCell] -> [(Int, Int, Int)])
-> (BudgetCell -> [BudgetDisplayCell])
-> BudgetCell
-> [(Int, Int, Int)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. BudgetCell -> [BudgetDisplayCell]
disp) [BudgetCell]
row

    -- build a list of widths for each column. In the case of transposed budget
    -- reports, the total 'row' must be included in this list
    widths :: [(Int, Int, Int)]
widths = [Int] -> [Int] -> [Int] -> [(Int, Int, Int)]
forall a b c. [a] -> [b] -> [c] -> [(a, b, c)]
zip3 [Int]
actualwidths [Int]
budgetwidths [Int]
percentwidths
      where
        actualwidths :: [Int]
actualwidths  = ([(Int, Int, Int)] -> Int) -> [[(Int, Int, Int)]] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map ([Int] -> Int
forall a. Integral a => [a] -> a
maximum' ([Int] -> Int)
-> ([(Int, Int, Int)] -> [Int]) -> [(Int, Int, Int)] -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Int, Int, Int) -> Int) -> [(Int, Int, Int)] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map (Int, Int, Int) -> Int
forall a b c. (a, b, c) -> a
first3 ) ([[(Int, Int, Int)]] -> [Int]) -> [[(Int, Int, Int)]] -> [Int]
forall a b. (a -> b) -> a -> b
$ [[(Int, Int, Int)]]
cols
        budgetwidths :: [Int]
budgetwidths  = ([(Int, Int, Int)] -> Int) -> [[(Int, Int, Int)]] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map ([Int] -> Int
forall a. Integral a => [a] -> a
maximum' ([Int] -> Int)
-> ([(Int, Int, Int)] -> [Int]) -> [(Int, Int, Int)] -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Int, Int, Int) -> Int) -> [(Int, Int, Int)] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map (Int, Int, Int) -> Int
forall a b c. (a, b, c) -> b
second3) ([[(Int, Int, Int)]] -> [Int]) -> [[(Int, Int, Int)]] -> [Int]
forall a b. (a -> b) -> a -> b
$ [[(Int, Int, Int)]]
cols
        percentwidths :: [Int]
percentwidths = ([(Int, Int, Int)] -> Int) -> [[(Int, Int, Int)]] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map ([Int] -> Int
forall a. Integral a => [a] -> a
maximum' ([Int] -> Int)
-> ([(Int, Int, Int)] -> [Int]) -> [(Int, Int, Int)] -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Int, Int, Int) -> Int) -> [(Int, Int, Int)] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map (Int, Int, Int) -> Int
forall a b c. (a, b, c) -> c
third3 ) ([[(Int, Int, Int)]] -> [Int]) -> [[(Int, Int, Int)]] -> [Int]
forall a b. (a -> b) -> a -> b
$ [[(Int, Int, Int)]]
cols
        catcolumnwidths :: [[[a]]] -> [[a]]
catcolumnwidths = ([[a]] -> [[a]] -> [[a]]) -> [[a]] -> [[[a]]] -> [[a]]
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' (([a] -> [a] -> [a]) -> [[a]] -> [[a]] -> [[a]]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith [a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
(++)) ([[a]] -> [[[a]]] -> [[a]]) -> [[a]] -> [[[a]]] -> [[a]]
forall a b. (a -> b) -> a -> b
$ [a] -> [[a]]
forall a. a -> [a]
repeat []
        cols :: [[(Int, Int, Int)]]
cols = [[(Int, Int, Int)]] -> [[(Int, Int, Int)]]
forall a. [[a]] -> [[a]]
maybetranspose ([[(Int, Int, Int)]] -> [[(Int, Int, Int)]])
-> [[(Int, Int, Int)]] -> [[(Int, Int, Int)]]
forall a b. (a -> b) -> a -> b
$ [[[(Int, Int, Int)]]] -> [[(Int, Int, Int)]]
forall a. [[[a]]] -> [[a]]
catcolumnwidths ([[[(Int, Int, Int)]]] -> [[(Int, Int, Int)]])
-> [[[(Int, Int, Int)]]] -> [[(Int, Int, Int)]]
forall a b. (a -> b) -> a -> b
$ (PeriodicReportRow DisplayName BudgetCell -> [[(Int, Int, Int)]])
-> [PeriodicReportRow DisplayName BudgetCell]
-> [[[(Int, Int, Int)]]]
forall a b. (a -> b) -> [a] -> [b]
map ([BudgetCell] -> [[(Int, Int, Int)]]
cellswidth ([BudgetCell] -> [[(Int, Int, Int)]])
-> (PeriodicReportRow DisplayName BudgetCell -> [BudgetCell])
-> PeriodicReportRow DisplayName BudgetCell
-> [[(Int, Int, Int)]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PeriodicReportRow DisplayName BudgetCell -> [BudgetCell]
forall a a. PeriodicReportRow a a -> [a]
rowToBudgetCells) [PeriodicReportRow DisplayName BudgetCell]
items [[[(Int, Int, Int)]]]
-> [[[(Int, Int, Int)]]] -> [[[(Int, Int, Int)]]]
forall a. [a] -> [a] -> [a]
++ [[BudgetCell] -> [[(Int, Int, Int)]]
cellswidth ([BudgetCell] -> [[(Int, Int, Int)]])
-> [BudgetCell] -> [[(Int, Int, Int)]]
forall a b. (a -> b) -> a -> b
$ PeriodicReportRow () BudgetCell -> [BudgetCell]
forall a a. PeriodicReportRow a a -> [a]
rowToBudgetCells PeriodicReportRow () BudgetCell
tr]

    -- split a BudgetCell into BudgetDisplayCell's (one per commodity when applicable)
    showcell :: BudgetShowMixed -> BudgetPercBudget -> BudgetCell -> BudgetDisplayRow
    showcell :: BudgetShowMixed
-> BudgetPercBudget -> BudgetCell -> [BudgetDisplayCell]
showcell BudgetShowMixed
showmixed BudgetPercBudget
percbudget (Maybe MixedAmount
actual, Maybe MixedAmount
mbudget) = [WideBuilder]
-> [Maybe (WideBuilder, Maybe WideBuilder)] -> [BudgetDisplayCell]
forall a b. [a] -> [b] -> [(a, b)]
zip (BudgetShowMixed
showmixed MixedAmount
actual') [Maybe (WideBuilder, Maybe WideBuilder)]
full
      where
        actual' :: MixedAmount
actual' = MixedAmount -> Maybe MixedAmount -> MixedAmount
forall a. a -> Maybe a -> a
fromMaybe MixedAmount
nullmixedamt Maybe MixedAmount
actual

        budgetAndPerc :: MixedAmount -> [(WideBuilder, Maybe WideBuilder)]
budgetAndPerc MixedAmount
b = ([WideBuilder]
 -> [Maybe WideBuilder] -> [(WideBuilder, Maybe WideBuilder)])
-> ([WideBuilder], [Maybe WideBuilder])
-> [(WideBuilder, Maybe WideBuilder)]
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry [WideBuilder]
-> [Maybe WideBuilder] -> [(WideBuilder, Maybe WideBuilder)]
forall a b. [a] -> [b] -> [(a, b)]
zip
          ( BudgetShowMixed
showmixed MixedAmount
b
          , (Percentage -> WideBuilder)
-> Maybe Percentage -> Maybe WideBuilder
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (AccountName -> WideBuilder
wbFromText (AccountName -> WideBuilder)
-> (Percentage -> AccountName) -> Percentage -> WideBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> AccountName
T.pack (String -> AccountName)
-> (Percentage -> String) -> Percentage -> AccountName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Percentage -> String
forall a. Show a => a -> String
show (Percentage -> String)
-> (Percentage -> Percentage) -> Percentage -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> Percentage -> Percentage
forall i. Integral i => Word8 -> DecimalRaw i -> DecimalRaw i
roundTo Word8
0) (Maybe Percentage -> Maybe WideBuilder)
-> [Maybe Percentage] -> [Maybe WideBuilder]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> BudgetPercBudget
percbudget MixedAmount
actual' MixedAmount
b
          )

        full :: [Maybe (WideBuilder, Maybe WideBuilder)]
full
          | Just MixedAmount
b <- Maybe MixedAmount
mbudget = (WideBuilder, Maybe WideBuilder)
-> Maybe (WideBuilder, Maybe WideBuilder)
forall a. a -> Maybe a
Just ((WideBuilder, Maybe WideBuilder)
 -> Maybe (WideBuilder, Maybe WideBuilder))
-> [(WideBuilder, Maybe WideBuilder)]
-> [Maybe (WideBuilder, Maybe WideBuilder)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> MixedAmount -> [(WideBuilder, Maybe WideBuilder)]
budgetAndPerc MixedAmount
b
          | Bool
otherwise         = Maybe (WideBuilder, Maybe WideBuilder)
-> [Maybe (WideBuilder, Maybe WideBuilder)]
forall a. a -> [a]
repeat Maybe (WideBuilder, Maybe WideBuilder)
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) =
            (AccountName -> Builder
TB.fromText (AccountName -> Builder) -> (Int -> AccountName) -> Int -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int -> AccountName -> AccountName)
-> AccountName -> Int -> AccountName
forall a b c. (a -> b -> c) -> b -> a -> c
flip Int -> AccountName -> AccountName
T.replicate AccountName
" " (Int -> Builder) -> Int -> Builder
forall a b. (a -> b) -> a -> b
$ Int
actualwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
w) Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder
b

        (Int
totalpercentwidth, Int
totalbudgetwidth) =
          let totalpercentwidth :: Int
totalpercentwidth = if Int
percentwidth Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 then Int
0 else Int
percentwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
5
           in ( Int
totalpercentwidth
              , if Int
budgetwidth Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 then Int
0 else Int
budgetwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
totalpercentwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3
              )

        -- | Display a padded budget string
        budgetb :: (WideBuilder, Maybe WideBuilder) -> Builder
budgetb (WideBuilder
budget, Maybe WideBuilder
perc) =
          let perct :: AccountName
perct = case Maybe WideBuilder
perc of
                Maybe WideBuilder
Nothing  -> Int -> AccountName -> AccountName
T.replicate Int
totalpercentwidth AccountName
" "
                Just pct -> Int -> AccountName -> AccountName
T.replicate (Int
percentwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
- WideBuilder -> Int
wbWidth WideBuilder
pct) AccountName
" " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> WideBuilder -> AccountName
wbToText WideBuilder
pct AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
"% of "
           in AccountName -> Builder
TB.fromText (AccountName -> Builder) -> AccountName -> Builder
forall a b. (a -> b) -> a -> b
$ AccountName
" [" AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
perct AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> Int -> AccountName -> AccountName
T.replicate (Int
budgetwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
- WideBuilder -> Int
wbWidth WideBuilder
budget) AccountName
" " AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> WideBuilder -> AccountName
wbToText WideBuilder
budget AccountName -> AccountName -> AccountName
forall a. Semigroup a => a -> a -> a
<> AccountName
"]"

        emptyBudget :: Builder
emptyBudget = AccountName -> Builder
TB.fromText (AccountName -> Builder) -> AccountName -> Builder
forall a b. (a -> b) -> a -> b
$ Int -> AccountName -> AccountName
T.replicate Int
totalbudgetwidth AccountName
" "

        full :: WideBuilder
full = (Builder -> Int -> WideBuilder) -> Int -> Builder -> WideBuilder
forall a b c. (a -> b -> c) -> b -> a -> c
flip Builder -> Int -> WideBuilder
WideBuilder (Int
actualwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
totalbudgetwidth) (Builder -> WideBuilder) -> Builder -> WideBuilder
forall a b. (a -> b) -> a -> b
$
            WideBuilder -> Builder
toPadded WideBuilder
actual Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder
-> ((WideBuilder, Maybe WideBuilder) -> Builder)
-> Maybe (WideBuilder, Maybe WideBuilder)
-> Builder
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Builder
emptyBudget (WideBuilder, Maybe WideBuilder) -> Builder
budgetb Maybe (WideBuilder, Maybe WideBuilder)
mbudget

    -- | Calculate the percentage of actual change to budget goal to show, if any.
    -- If valuing at cost, both amounts are converted to cost before comparing.
    -- A percentage will not be shown if:
    -- - actual or goal are not the same, single, commodity
    -- - the goal is zero
    percentage :: Change -> BudgetGoal -> Maybe Percentage
    percentage :: MixedAmount -> MixedAmount -> Maybe Percentage
percentage MixedAmount
actual MixedAmount
budget =
      case (MixedAmount -> [Amount]
costedAmounts MixedAmount
actual, MixedAmount -> [Amount]
costedAmounts MixedAmount
budget) of
        ([Amount
a], [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
amountLooksZero Amount
a) Bool -> Bool -> Bool
&& Bool -> Bool
not (Amount -> Bool
amountLooksZero Amount
b)
            -> Percentage -> Maybe Percentage
forall a. a -> Maybe a
Just (Percentage -> Maybe Percentage) -> Percentage -> Maybe Percentage
forall a b. (a -> b) -> a -> b
$ Percentage
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
        ([Amount], [Amount])
_   -> -- trace (pshow $ (maybecost actual, maybecost budget))  -- debug missing percentage
               Maybe Percentage
forall a. Maybe a
Nothing
      where
        costedAmounts :: MixedAmount -> [Amount]
costedAmounts = case Maybe ConversionOp
conversionop_ of
            Just ConversionOp
ToCost -> MixedAmount -> [Amount]
amounts (MixedAmount -> [Amount])
-> (MixedAmount -> MixedAmount) -> MixedAmount -> [Amount]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MixedAmount -> MixedAmount
mixedAmountCost
            Maybe ConversionOp
_           -> MixedAmount -> [Amount]
amounts

    -- | Calculate the percentage of actual change to budget goal for a particular commodity
    percentage' :: Change -> BudgetGoal -> CommoditySymbol -> Maybe Percentage
    percentage' :: MixedAmount -> MixedAmount -> AccountName -> Maybe Percentage
percentage' MixedAmount
am MixedAmount
bm AccountName
c = case ((,) (Maybe Amount -> Maybe Amount -> (Maybe Amount, Maybe Amount))
-> (MixedAmount -> Maybe Amount)
-> MixedAmount
-> MixedAmount
-> (Maybe Amount, Maybe Amount)
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` (Amount -> Bool) -> [Amount] -> Maybe Amount
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (AccountName -> AccountName -> Bool
forall a. Eq a => a -> a -> Bool
(==) AccountName
c (AccountName -> Bool) -> (Amount -> AccountName) -> Amount -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Amount -> AccountName
acommodity) ([Amount] -> Maybe Amount)
-> (MixedAmount -> [Amount]) -> MixedAmount -> Maybe Amount
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MixedAmount -> [Amount]
amounts) MixedAmount
am MixedAmount
bm of
        (Just Amount
a, Just Amount
b) -> MixedAmount -> MixedAmount -> Maybe Percentage
percentage (Amount -> MixedAmount
mixedAmount Amount
a) (Amount -> MixedAmount
mixedAmount Amount
b)
        (Maybe Amount, Maybe Amount)
_                -> Maybe Percentage
forall a. Maybe a
Nothing

-- XXX generalise this with multiBalanceReportAsCsv ?
-- | Render a budget report as CSV. Like multiBalanceReportAsCsv,
-- but includes alternating actual and budget amount columns.
budgetReportAsCsv :: ReportOpts -> BudgetReport -> [[Text]]
budgetReportAsCsv :: ReportOpts -> BudgetReport -> [[AccountName]]
budgetReportAsCsv
  ReportOpts{Bool
Int
[AccountName]
[Status]
Maybe Int
Maybe AccountName
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
no_total_ :: Bool
row_total_ :: Bool
declared_ :: Bool
drop_ :: Int
accountlistmode_ :: AccountListMode
budgetpat_ :: Maybe AccountName
balanceaccum_ :: BalanceAccumulation
balancecalc_ :: BalanceCalculation
txn_dates_ :: Bool
related_ :: Bool
average_ :: Bool
querystring_ :: [AccountName]
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
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 -> [AccountName]
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 AccountName
interval_ :: ReportOpts -> Interval
infer_prices_ :: ReportOpts -> Bool
empty_ :: ReportOpts -> Bool
accountlistmode_ :: ReportOpts -> AccountListMode
..}
  (PeriodicReport [DateSpan]
colspans [PeriodicReportRow DisplayName BudgetCell]
items PeriodicReportRow () BudgetCell
tr)
  = (if Bool
transpose_ then [[AccountName]] -> [[AccountName]]
forall a. [[a]] -> [[a]]
transpose else [[AccountName]] -> [[AccountName]]
forall a. a -> a
id) ([[AccountName]] -> [[AccountName]])
-> [[AccountName]] -> [[AccountName]]
forall a b. (a -> b) -> a -> b
$

  -- heading row
  (AccountName
"Account" AccountName -> [AccountName] -> [AccountName]
forall a. a -> [a] -> [a]
:
  [AccountName
"Commodity" | Layout
layout_ Layout -> Layout -> Bool
forall a. Eq a => a -> a -> Bool
== Layout
LayoutBare ]
   [AccountName] -> [AccountName] -> [AccountName]
forall a. [a] -> [a] -> [a]
++ (DateSpan -> [AccountName]) -> [DateSpan] -> [AccountName]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\DateSpan
span -> [DateSpan -> AccountName
showDateSpan DateSpan
span, AccountName
"budget"]) [DateSpan]
colspans
   [AccountName] -> [AccountName] -> [AccountName]
forall a. [a] -> [a] -> [a]
++ [[AccountName]] -> [AccountName]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[AccountName
"Total"  ,AccountName
"budget"] | Bool
row_total_]
   [AccountName] -> [AccountName] -> [AccountName]
forall a. [a] -> [a] -> [a]
++ [[AccountName]] -> [AccountName]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[AccountName
"Average",AccountName
"budget"] | Bool
average_]
  ) [AccountName] -> [[AccountName]] -> [[AccountName]]
forall a. a -> [a] -> [a]
:

  -- account rows
  (PeriodicReportRow DisplayName BudgetCell -> [[AccountName]])
-> [PeriodicReportRow DisplayName BudgetCell] -> [[AccountName]]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ((PeriodicReportRow DisplayName BudgetCell -> AccountName)
-> PeriodicReportRow DisplayName BudgetCell -> [[AccountName]]
forall a.
(PeriodicReportRow a BudgetCell -> AccountName)
-> PeriodicReportRow a BudgetCell -> [[AccountName]]
rowAsTexts PeriodicReportRow DisplayName BudgetCell -> AccountName
forall a. PeriodicReportRow DisplayName a -> AccountName
prrFullName) [PeriodicReportRow DisplayName BudgetCell]
items

  -- totals row
  [[AccountName]] -> [[AccountName]] -> [[AccountName]]
forall a. [a] -> [a] -> [a]
++ [[[AccountName]]] -> [[AccountName]]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [ (PeriodicReportRow () BudgetCell -> AccountName)
-> PeriodicReportRow () BudgetCell -> [[AccountName]]
forall a.
(PeriodicReportRow a BudgetCell -> AccountName)
-> PeriodicReportRow a BudgetCell -> [[AccountName]]
rowAsTexts (AccountName -> PeriodicReportRow () BudgetCell -> AccountName
forall a b. a -> b -> a
const AccountName
"Total:") PeriodicReportRow () BudgetCell
tr | Bool -> Bool
not Bool
no_total_ ]

  where
    flattentuples :: [(a, a)] -> [a]
flattentuples [(a, a)]
abs = [[a]] -> [a]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[a
a,a
b] | (a
a,a
b) <- [(a, a)]
abs]
    showNorm :: Maybe MixedAmount -> AccountName
showNorm = AccountName
-> (MixedAmount -> AccountName) -> Maybe MixedAmount -> AccountName
forall b a. b -> (a -> b) -> Maybe a -> b
maybe AccountName
"" (WideBuilder -> AccountName
wbToText (WideBuilder -> AccountName)
-> (MixedAmount -> WideBuilder) -> MixedAmount -> AccountName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AmountDisplayOpts -> MixedAmount -> WideBuilder
showMixedAmountB AmountDisplayOpts
oneLine)

    rowAsTexts :: (PeriodicReportRow a BudgetCell -> Text)
               -> PeriodicReportRow a BudgetCell
               -> [[Text]]
    rowAsTexts :: (PeriodicReportRow a BudgetCell -> AccountName)
-> PeriodicReportRow a BudgetCell -> [[AccountName]]
rowAsTexts PeriodicReportRow a BudgetCell -> AccountName
render row :: PeriodicReportRow a BudgetCell
row@(PeriodicReportRow a
_ [BudgetCell]
as (Maybe MixedAmount
rowtot,Maybe MixedAmount
budgettot) (Maybe MixedAmount
rowavg, Maybe MixedAmount
budgetavg))
      | Layout
layout_ Layout -> Layout -> Bool
forall a. Eq a => a -> a -> Bool
/= Layout
LayoutBare = [PeriodicReportRow a BudgetCell -> AccountName
render PeriodicReportRow a BudgetCell
row AccountName -> [AccountName] -> [AccountName]
forall a. a -> [a] -> [a]
: (Maybe MixedAmount -> AccountName)
-> [Maybe MixedAmount] -> [AccountName]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Maybe MixedAmount -> AccountName
showNorm [Maybe MixedAmount]
all]
      | Bool
otherwise =
            [[AccountName]] -> [[AccountName]]
joinNames ([[AccountName]] -> [[AccountName]])
-> ([Maybe MixedAmount] -> [[AccountName]])
-> [Maybe MixedAmount]
-> [[AccountName]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (AccountName -> [AccountName] -> [AccountName])
-> [AccountName] -> [[AccountName]] -> [[AccountName]]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (:) [AccountName]
cs  -- add symbols and names
          ([[AccountName]] -> [[AccountName]])
-> ([Maybe MixedAmount] -> [[AccountName]])
-> [Maybe MixedAmount]
-> [[AccountName]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[AccountName]] -> [[AccountName]]
forall a. [[a]] -> [[a]]
transpose                   -- each row becomes a list of Text quantities
          ([[AccountName]] -> [[AccountName]])
-> ([Maybe MixedAmount] -> [[AccountName]])
-> [Maybe MixedAmount]
-> [[AccountName]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Maybe MixedAmount -> [AccountName])
-> [Maybe MixedAmount] -> [[AccountName]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((WideBuilder -> AccountName) -> [WideBuilder] -> [AccountName]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap WideBuilder -> AccountName
wbToText ([WideBuilder] -> [AccountName])
-> (Maybe MixedAmount -> [WideBuilder])
-> Maybe MixedAmount
-> [AccountName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AmountDisplayOpts -> BudgetShowMixed
showMixedAmountLinesB AmountDisplayOpts
oneLine{displayOrder :: Maybe [AccountName]
displayOrder=[AccountName] -> Maybe [AccountName]
forall a. a -> Maybe a
Just [AccountName]
cs, displayMinWidth :: Maybe Int
displayMinWidth=Maybe Int
forall a. Maybe a
Nothing}
                 BudgetShowMixed
-> (Maybe MixedAmount -> MixedAmount)
-> Maybe MixedAmount
-> [WideBuilder]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.MixedAmount -> Maybe MixedAmount -> MixedAmount
forall a. a -> Maybe a -> a
fromMaybe MixedAmount
nullmixedamt)
          ([Maybe MixedAmount] -> [[AccountName]])
-> [Maybe MixedAmount] -> [[AccountName]]
forall a b. (a -> b) -> a -> b
$ [Maybe MixedAmount]
all
      where
        cs :: [AccountName]
cs = Set AccountName -> [AccountName]
forall a. Set a -> [a]
S.toList (Set AccountName -> [AccountName])
-> ([MixedAmount] -> Set AccountName)
-> [MixedAmount]
-> [AccountName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Set AccountName -> Set AccountName -> Set AccountName)
-> Set AccountName -> [Set AccountName] -> Set AccountName
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Set AccountName -> Set AccountName -> Set AccountName
forall a. Ord a => Set a -> Set a -> Set a
S.union Set AccountName
forall a. Monoid a => a
mempty ([Set AccountName] -> Set AccountName)
-> ([MixedAmount] -> [Set AccountName])
-> [MixedAmount]
-> Set AccountName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (MixedAmount -> Set AccountName)
-> [MixedAmount] -> [Set AccountName]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap MixedAmount -> Set AccountName
maCommodities ([MixedAmount] -> [AccountName]) -> [MixedAmount] -> [AccountName]
forall a b. (a -> b) -> a -> b
$ [Maybe MixedAmount] -> [MixedAmount]
forall a. [Maybe a] -> [a]
catMaybes [Maybe MixedAmount]
all
        all :: [Maybe MixedAmount]
all = [BudgetCell] -> [Maybe MixedAmount]
forall a. [(a, a)] -> [a]
flattentuples [BudgetCell]
as
            [Maybe MixedAmount] -> [Maybe MixedAmount] -> [Maybe MixedAmount]
forall a. [a] -> [a] -> [a]
++ [[Maybe MixedAmount]] -> [Maybe MixedAmount]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[Maybe MixedAmount
rowtot, Maybe MixedAmount
budgettot] | Bool
row_total_]
            [Maybe MixedAmount] -> [Maybe MixedAmount] -> [Maybe MixedAmount]
forall a. [a] -> [a] -> [a]
++ [[Maybe MixedAmount]] -> [Maybe MixedAmount]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[Maybe MixedAmount
rowavg, Maybe MixedAmount
budgetavg] | Bool
average_]

        joinNames :: [[AccountName]] -> [[AccountName]]
joinNames = ([AccountName] -> [AccountName])
-> [[AccountName]] -> [[AccountName]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (PeriodicReportRow a BudgetCell -> AccountName
render PeriodicReportRow a BudgetCell
row AccountName -> [AccountName] -> [AccountName]
forall a. a -> [a] -> [a]
:)

-- tests

tests_BudgetReport :: TestTree
tests_BudgetReport = String -> [TestTree] -> TestTree
testGroup String
"BudgetReport" [
 ]