{-# 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', 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 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
import Data.Ord (comparing)
import Control.Monad ((>=>))


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_ = ALTree }
    showunbudgeted :: Bool
showunbudgeted = ReportOpts -> Bool
empty_ ReportOpts
ropts
    budgetedaccts :: Set Text
budgetedaccts =
      String -> Set Text -> Set Text
forall a. Show a => String -> a -> a
dbg3 String
"budgetedacctsinperiod" (Set Text -> Set Text) -> Set Text -> Set Text
forall a b. (a -> b) -> a -> b
$
      [Text] -> Set Text
forall a. Ord a => [a] -> Set a
S.fromList ([Text] -> Set Text) -> [Text] -> Set Text
forall a b. (a -> b) -> a -> b
$
      [Text] -> [Text]
expandAccountNames ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$
      [Posting] -> [Text]
accountNamesFromPostings ([Posting] -> [Text]) -> [Posting] -> [Text]
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
pt -> Bool -> PeriodicTransaction -> DateSpan -> [Transaction]
runPeriodicTransaction Bool
False PeriodicTransaction
pt DateSpan
reportspan) ([PeriodicTransaction] -> [Transaction])
-> [PeriodicTransaction] -> [Transaction]
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) =
        String -> MultiBalanceReport -> MultiBalanceReport
forall a. Show a => String -> a -> a
dbg5 String
"budgetgoalreport" (MultiBalanceReport -> MultiBalanceReport)
-> MultiBalanceReport -> MultiBalanceReport
forall a b. (a -> b) -> a -> b
$ ReportSpec
-> Journal -> PriceOracle -> Set Text -> MultiBalanceReport
multiBalanceReportWith ReportSpec
rspec{_rsReportOpts=ropts{empty_=True}} Journal
budgetj PriceOracle
priceoracle Set Text
forall a. Monoid a => a
mempty
    budgetedacctsseen :: Set Text
budgetedacctsseen = [Text] -> Set Text
forall a. Ord a => [a] -> Set a
S.fromList ([Text] -> Set Text) -> [Text] -> Set Text
forall a b. (a -> b) -> a -> b
$ (PeriodicReportRow DisplayName Change -> Text)
-> [PeriodicReportRow DisplayName Change] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map PeriodicReportRow DisplayName Change -> Text
forall a. PeriodicReportRow DisplayName a -> Text
prrFullName [PeriodicReportRow DisplayName Change]
budgetgoalitems
    actualreport :: MultiBalanceReport
actualreport@(PeriodicReport [DateSpan]
actualspans [PeriodicReportRow DisplayName Change]
_ PeriodicReportRow () Change
_) =
        String -> MultiBalanceReport -> MultiBalanceReport
forall a. Show a => String -> a -> a
dbg5 String
"actualreport"     (MultiBalanceReport -> MultiBalanceReport)
-> MultiBalanceReport -> MultiBalanceReport
forall a b. (a -> b) -> a -> b
$ ReportSpec
-> Journal -> PriceOracle -> Set Text -> MultiBalanceReport
multiBalanceReportWith ReportSpec
rspec{_rsReportOpts=ropts{empty_=True}} Journal
actualj PriceOracle
priceoracle Set Text
budgetedacctsseen
    budgetgoalreport' :: MultiBalanceReport
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 Change]
-> PeriodicReportRow () Change
-> MultiBalanceReport
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

-- | Use all (or all matched by --budget's argument) periodic transactions in the journal 
-- to generate budget goal transactions in the specified date span (and before, to support
-- --historical. The precise start date is the natural start date of the largest interval
-- of the active periodic transaction rules that is on or before the earlier of journal start date,
-- report start date.)
-- 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
$  -- PARTIAL:
    (Journal -> Either String Journal
journalStyleAmounts (Journal -> Either String Journal)
-> (Journal -> Either String Journal)
-> Journal
-> Either String Journal
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 = budgetts }
  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
$ Maybe EFDay -> Maybe EFDay -> DateSpan
DateSpan (Day -> EFDay
Exact (Day -> EFDay) -> Maybe Day -> Maybe EFDay
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe Day
mbudgetgoalsstartdate) (Day -> EFDay
Exact (Day -> EFDay) -> Maybe Day -> Maybe EFDay
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> DateSpan -> Maybe Day
spanEnd DateSpan
reportspan)
      where
        mbudgetgoalsstartdate :: Maybe Day
mbudgetgoalsstartdate =
          -- We want to also generate budget goal txns before the report start date, in case -H is used.
          -- What should the actual starting date for goal txns be ? This gets tricky. 
          -- Consider a journal with a "~ monthly" periodic transaction rule, where the first transaction is on 1/5.
          -- Users will certainly expect a budget goal for january, but "~ monthly" generates transactions
          -- on the first of month, and starting from 1/5 would exclude 1/1.
          -- Secondly, consider a rule like "~ every february 2nd from 2020/01"; we should not start that
          -- before 2020-02-02.
          -- Hopefully the following algorithm produces intuitive behaviour in general:
          -- from the earlier of the journal start date and the report start date,
          -- move backward to the nearest natural start date of the largest period seen among the
          -- active periodic transactions, unless that is disallowed by a start date in the periodic rule.
          -- (Do we need to pay attention to an end date in the rule ? Don't think so.)
          -- (So with "~ monthly", the journal start date 1/5 is adjusted to 1/1.)
          case Maybe Day -> [Maybe Day] -> Maybe Day
forall a. Ord a => a -> [a] -> a
minimumDef Maybe Day
forall a. Maybe a
Nothing ([Maybe Day] -> Maybe Day) -> [Maybe Day] -> Maybe Day
forall a b. (a -> b) -> a -> b
$ (Maybe Day -> Bool) -> [Maybe Day] -> [Maybe Day]
forall a. (a -> Bool) -> [a] -> [a]
filter Maybe Day -> Bool
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 -> Maybe Day
forall a. Maybe a
Nothing
            Just Day
d  -> Day -> Maybe Day
forall a. a -> Maybe a
Just Day
d'
              where
                -- the interval and any date span of the periodic transaction with longest period
                (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 = (PeriodicTransaction -> PeriodicTransaction -> Ordering)
-> [PeriodicTransaction] -> PeriodicTransaction
forall (t :: * -> *) a.
Foldable t =>
(a -> a -> Ordering) -> t a -> a
maximumBy ((PeriodicTransaction -> Interval)
-> PeriodicTransaction -> PeriodicTransaction -> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing PeriodicTransaction -> Interval
ptinterval) [PeriodicTransaction]
pts  -- PARTIAL: maximumBy won't fail
                -- the natural start of this interval on or before the journal/report start
                intervalstart :: Day
intervalstart = Interval -> Day -> Day
intervalBoundaryBefore Interval
intervl Day
d
                -- the natural interval start before the journal/report start,
                -- or the rule-specified start if later,
                -- but no later than the journal/report start.
                d' :: Day
d' = Day -> Day -> Day
forall a. Ord a => a -> a -> a
min Day
d (Day -> Day) -> Day -> Day
forall a b. (a -> b) -> a -> b
$ Day -> (Day -> Day) -> Maybe Day -> Day
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Day
intervalstart (Day -> Day -> Day
forall a. Ord a => a -> a -> a
max Day
intervalstart) (Maybe Day -> Day) -> Maybe Day -> Day
forall a b. (a -> b) -> a -> b
$ DateSpan -> Maybe Day
spanStart DateSpan
spn

    -- 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
    pat :: Text
pat = Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe Text
"" (Maybe Text -> Text) -> Maybe Text -> Text
forall a b. (a -> b) -> a -> b
$ String -> Maybe Text -> Maybe Text
forall a. Show a => String -> a -> a
dbg3 String
"budget pattern" (Maybe Text -> Maybe Text) -> Maybe Text -> Maybe Text
forall a b. (a -> b) -> a -> b
$ Text -> Text
T.toLower (Text -> Text) -> Maybe Text -> Maybe Text
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 =
      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 <- [PeriodicTransaction]
budgetpts
      , Transaction
t <- Bool -> PeriodicTransaction -> DateSpan -> [Transaction]
runPeriodicTransaction Bool
False 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 = T.pack "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 Text -> Bool -> Journal -> Journal
journalWithBudgetAccountNames Set Text
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
.[Text] -> String
forall a. Show a => a -> String
pshow([Text] -> String) -> (Journal -> [Text]) -> Journal -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Journal -> [Text]
journalAccountNamesUsed) (Journal -> Journal) -> Journal -> Journal
forall a b. (a -> b) -> a -> b
$
  Journal
j { jtxns = remapTxn <$> jtxns 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 = remapAccount $ paccount p, poriginal = poriginal p <|> Just p }
    remapAccount :: Text -> Text
remapAccount Text
a
      | Text
a Text -> Set Text -> Bool
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 Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
acctsep Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
a else Text
u
      where
        budgetedparent :: Maybe Text
budgetedparent = (Text -> Bool) -> [Text] -> Maybe Text
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (Text -> Set Text -> Bool
forall a. Ord a => a -> Set a -> Bool
`S.member` Set Text
budgetedaccts) ([Text] -> Maybe Text) -> [Text] -> Maybe Text
forall a b. (a -> b) -> a -> b
$ Text -> [Text]
parentAccountNames Text
a
        u :: Text
u = Text
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
-> 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)) =
    [DateSpan]
-> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
-> PeriodicReportRow () (Maybe Change, Maybe Change)
-> BudgetReport
forall a b.
[DateSpan]
-> [PeriodicReportRow a b]
-> PeriodicReportRow () b
-> PeriodicReport a b
PeriodicReport [DateSpan]
periods [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
sortedrows PeriodicReportRow () (Maybe Change, Maybe Change)
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 (Maybe Change, Maybe Change)]
rows1 =
      [ DisplayName
-> [(Maybe Change, Maybe Change)]
-> (Maybe Change, Maybe Change)
-> (Maybe Change, Maybe Change)
-> PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
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       = Text
-> HashMap Text ([Change], Change, Change)
-> Maybe ([Change], Change, Change)
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        = [Maybe Change]
-> (([Change], Change, Change) -> [Maybe Change])
-> Maybe ([Change], Change, Change)
-> [Maybe Change]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Maybe Change
forall a. Maybe a
Nothing Maybe Change -> [DateSpan] -> [Maybe Change]
forall a b. a -> [b] -> [a]
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ [DateSpan]
periods) ((Change -> Maybe Change) -> [Change] -> [Maybe Change]
forall a b. (a -> b) -> [a] -> [b]
map Change -> Maybe Change
forall a. a -> Maybe a
Just ([Change] -> [Maybe Change])
-> (([Change], Change, Change) -> [Change])
-> ([Change], Change, Change)
-> [Maybe Change]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Change], Change, Change) -> [Change]
forall {a} {b} {c}. (a, b, c) -> a
first3) Maybe ([Change], Change, Change)
mbudgetgoals :: [Maybe BudgetGoal]
      , let mbudgettot :: Maybe Change
mbudgettot         = ([Change], Change, Change) -> Change
forall {a} {b} {c}. (a, b, c) -> b
second3 (([Change], Change, Change) -> Change)
-> Maybe ([Change], Change, Change) -> Maybe Change
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe ([Change], Change, Change)
mbudgetgoals :: Maybe BudgetTotal
      , let mbudgetavg :: Maybe Change
mbudgetavg         = ([Change], Change, Change) -> Change
forall {a} {b} {c}. (a, b, c) -> c
third3 (([Change], Change, Change) -> Change)
-> Maybe ([Change], Change, Change) -> Maybe Change
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 = [(DateSpan, Change)] -> Map DateSpan Change
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [ (DateSpan
p,Change
budgetamt) | (DateSpan
p, Just Change
budgetamt) <- [DateSpan] -> [Maybe Change] -> [(DateSpan, Maybe Change)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
budgetperiods [Maybe Change]
budgetmamts ] :: Map DateSpan BudgetGoal
      , let acctActualByPeriod :: Map DateSpan Change
acctActualByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [ (DateSpan
p,Change
actualamt) | (DateSpan
p, Just Change
actualamt) <- [DateSpan] -> [Maybe Change] -> [(DateSpan, Maybe Change)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
actualperiods ((Change -> Maybe Change) -> [Change] -> [Maybe Change]
forall a b. (a -> b) -> [a] -> [b]
map Change -> Maybe Change
forall a. a -> Maybe a
Just [Change]
actualamts) ] :: Map DateSpan Change
      , let amtandgoals :: [(Maybe Change, Maybe Change)]
amtandgoals        = [ (DateSpan -> Map DateSpan Change -> Maybe Change
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
acctActualByPeriod, DateSpan -> Map DateSpan Change -> Maybe Change
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
acctBudgetByPeriod) | DateSpan
p <- [DateSpan]
periods ] :: [BudgetCell]
      , let totamtandgoal :: (Maybe Change, Maybe Change)
totamtandgoal      = (Change -> Maybe Change
forall a. a -> Maybe a
Just Change
actualtot, Maybe Change
mbudgettot)
      , let avgamtandgoal :: (Maybe Change, Maybe Change)
avgamtandgoal      = (Change -> Maybe Change
forall a. a -> Maybe a
Just Change
actualavg, Maybe Change
mbudgetavg)
      ]
      where
        HashMap Text ([Change], Change, Change)
budgetGoalsByAcct :: HashMap AccountName ([BudgetGoal], BudgetTotal, BudgetAverage) =
          [(Text, ([Change], Change, Change))]
-> HashMap Text ([Change], Change, Change)
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 ]

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

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

    totalrow :: PeriodicReportRow () (Maybe Change, Maybe Change)
totalrow = ()
-> [(Maybe Change, Maybe Change)]
-> (Maybe Change, Maybe Change)
-> (Maybe Change, Maybe Change)
-> PeriodicReportRow () (Maybe Change, Maybe Change)
forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
PeriodicReportRow ()
        [ (DateSpan -> Map DateSpan Change -> Maybe Change
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
totActualByPeriod, DateSpan -> Map DateSpan Change -> Maybe Change
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup DateSpan
p Map DateSpan Change
totBudgetByPeriod) | DateSpan
p <- [DateSpan]
periods ]
        ( Change -> Maybe Change
forall a. a -> Maybe a
Just Change
actualgrandtot, Change -> Maybe Change
budget Change
budgetgrandtot )
        ( Change -> Maybe Change
forall a. a -> Maybe a
Just Change
actualgrandavg, Change -> Maybe Change
budget Change
budgetgrandavg )
      where
        totBudgetByPeriod :: Map DateSpan Change
totBudgetByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(DateSpan, Change)] -> Map DateSpan Change)
-> [(DateSpan, Change)] -> Map DateSpan Change
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> [Change] -> [(DateSpan, Change)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
budgetperiods [Change]
budgettots :: Map DateSpan BudgetTotal
        totActualByPeriod :: Map DateSpan Change
totActualByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(DateSpan, Change)] -> Map DateSpan Change)
-> [(DateSpan, Change)] -> Map DateSpan Change
forall a b. (a -> b) -> a -> b
$ [DateSpan] -> [Change] -> [(DateSpan, Change)]
forall a b. [a] -> [b] -> [(a, b)]
zip [DateSpan]
actualperiods [Change]
actualtots :: Map DateSpan Change
        budget :: Change -> Maybe Change
budget Change
b = if Change -> Bool
mixedAmountLooksZero Change
b then Maybe Change
forall a. Maybe a
Nothing else Change -> Maybe Change
forall a. a -> Maybe a
Just Change
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
[Text]
[Status]
Maybe Int
Maybe Text
Maybe NormalSign
Maybe ValuationType
Maybe ConversionOp
Interval
Period
StringFormat
Layout
AccountListMode
BalanceAccumulation
BalanceCalculation
accountlistmode_ :: ReportOpts -> AccountListMode
empty_ :: ReportOpts -> Bool
infer_prices_ :: ReportOpts -> Bool
interval_ :: ReportOpts -> Interval
budgetpat_ :: ReportOpts -> Maybe Text
period_ :: Period
interval_ :: Interval
statuses_ :: [Status]
conversionop_ :: Maybe ConversionOp
value_ :: Maybe ValuationType
infer_prices_ :: Bool
depth_ :: Maybe Int
date2_ :: Bool
empty_ :: Bool
no_elide_ :: Bool
real_ :: Bool
format_ :: StringFormat
pretty_ :: Bool
querystring_ :: [Text]
average_ :: Bool
related_ :: Bool
txn_dates_ :: Bool
balancecalc_ :: BalanceCalculation
balanceaccum_ :: BalanceAccumulation
budgetpat_ :: Maybe Text
accountlistmode_ :: AccountListMode
drop_ :: Int
declared_ :: Bool
row_total_ :: Bool
no_total_ :: Bool
summary_only_ :: Bool
show_costs_ :: Bool
sort_amount_ :: Bool
percent_ :: Bool
invert_ :: Bool
normalbalance_ :: Maybe NormalSign
color_ :: Bool
transpose_ :: Bool
layout_ :: Layout
period_ :: ReportOpts -> Period
statuses_ :: ReportOpts -> [Status]
conversionop_ :: ReportOpts -> Maybe ConversionOp
value_ :: ReportOpts -> Maybe ValuationType
depth_ :: ReportOpts -> Maybe Int
date2_ :: ReportOpts -> Bool
no_elide_ :: ReportOpts -> Bool
real_ :: ReportOpts -> Bool
format_ :: ReportOpts -> StringFormat
pretty_ :: ReportOpts -> Bool
querystring_ :: ReportOpts -> [Text]
average_ :: ReportOpts -> Bool
related_ :: ReportOpts -> Bool
txn_dates_ :: ReportOpts -> Bool
balancecalc_ :: ReportOpts -> BalanceCalculation
balanceaccum_ :: ReportOpts -> BalanceAccumulation
drop_ :: ReportOpts -> Int
declared_ :: ReportOpts -> Bool
row_total_ :: ReportOpts -> Bool
no_total_ :: ReportOpts -> Bool
summary_only_ :: ReportOpts -> Bool
show_costs_ :: ReportOpts -> Bool
sort_amount_ :: ReportOpts -> Bool
percent_ :: ReportOpts -> Bool
invert_ :: ReportOpts -> Bool
normalbalance_ :: ReportOpts -> Maybe NormalSign
color_ :: ReportOpts -> Bool
transpose_ :: ReportOpts -> Bool
layout_ :: ReportOpts -> Layout
..} BudgetReport
budgetr = Builder -> Text
TB.toLazyText (Builder -> Text) -> Builder -> Text
forall a b. (a -> b) -> a -> b
$
    Text -> Builder
TB.fromText Text
title Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Text -> Builder
TB.fromText Text
"\n\n" Builder -> Builder -> Builder
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 " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> DateSpan -> Text
showDateSpan (BudgetReport -> DateSpan
forall a b. PeriodicReport a b -> DateSpan
periodicReportSpan BudgetReport
budgetr)
           Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (case Maybe ConversionOp
conversionop_ of
                 Just ConversionOp
ToCost -> Text
", converted to cost"
                 Maybe ConversionOp
_           -> Text
"")
           Text -> Text -> 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 " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Day -> Text
showDate Day
d
                 Maybe ValuationType
Nothing             -> Text
"")
           Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":"

-- | Build a 'Table' from a multi-column balance report.
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
accountlistmode_ :: ReportOpts -> AccountListMode
empty_ :: ReportOpts -> Bool
infer_prices_ :: ReportOpts -> Bool
interval_ :: ReportOpts -> Interval
budgetpat_ :: ReportOpts -> Maybe Text
period_ :: ReportOpts -> Period
statuses_ :: ReportOpts -> [Status]
conversionop_ :: ReportOpts -> Maybe ConversionOp
value_ :: ReportOpts -> Maybe ValuationType
depth_ :: ReportOpts -> Maybe Int
date2_ :: ReportOpts -> Bool
no_elide_ :: ReportOpts -> Bool
real_ :: ReportOpts -> Bool
format_ :: ReportOpts -> StringFormat
pretty_ :: ReportOpts -> Bool
querystring_ :: ReportOpts -> [Text]
average_ :: ReportOpts -> Bool
related_ :: ReportOpts -> Bool
txn_dates_ :: ReportOpts -> Bool
balancecalc_ :: ReportOpts -> BalanceCalculation
balanceaccum_ :: ReportOpts -> BalanceAccumulation
drop_ :: ReportOpts -> Int
declared_ :: ReportOpts -> Bool
row_total_ :: ReportOpts -> Bool
no_total_ :: ReportOpts -> Bool
summary_only_ :: ReportOpts -> Bool
show_costs_ :: ReportOpts -> Bool
sort_amount_ :: ReportOpts -> Bool
percent_ :: ReportOpts -> Bool
invert_ :: ReportOpts -> Bool
normalbalance_ :: ReportOpts -> Maybe NormalSign
color_ :: ReportOpts -> Bool
transpose_ :: ReportOpts -> Bool
layout_ :: ReportOpts -> Layout
period_ :: Period
interval_ :: Interval
statuses_ :: [Status]
conversionop_ :: Maybe ConversionOp
value_ :: Maybe ValuationType
infer_prices_ :: Bool
depth_ :: Maybe Int
date2_ :: Bool
empty_ :: Bool
no_elide_ :: Bool
real_ :: Bool
format_ :: StringFormat
pretty_ :: Bool
querystring_ :: [Text]
average_ :: Bool
related_ :: Bool
txn_dates_ :: Bool
balancecalc_ :: BalanceCalculation
balanceaccum_ :: BalanceAccumulation
budgetpat_ :: Maybe Text
accountlistmode_ :: AccountListMode
drop_ :: Int
declared_ :: Bool
row_total_ :: Bool
no_total_ :: Bool
summary_only_ :: Bool
show_costs_ :: Bool
sort_amount_ :: Bool
percent_ :: Bool
invert_ :: Bool
normalbalance_ :: Maybe NormalSign
color_ :: Bool
transpose_ :: Bool
layout_ :: Layout
..}
  (PeriodicReport [DateSpan]
spans [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
items PeriodicReportRow () (Maybe Change, Maybe Change)
tr) =
    Table Text Text WideBuilder -> Table Text Text WideBuilder
forall {rh} {a}. Table rh rh a -> Table rh rh a
maybetransposetable (Table Text Text WideBuilder -> Table Text Text WideBuilder)
-> Table Text Text WideBuilder -> Table Text Text WideBuilder
forall a b. (a -> b) -> a -> b
$
    Table Text Text WideBuilder -> Table Text Text WideBuilder
forall {ch}. Table Text ch WideBuilder -> Table Text ch WideBuilder
addtotalrow (Table Text Text WideBuilder -> Table Text Text WideBuilder)
-> Table Text Text WideBuilder -> Table Text Text WideBuilder
forall a b. (a -> b) -> a -> b
$
    Header Text
-> Header Text -> [[WideBuilder]] -> Table Text Text WideBuilder
forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
Tab.Table
      (Properties -> [Header Text] -> Header Text
forall h. Properties -> [Header h] -> Header h
Tab.Group Properties
Tab.NoLine ([Header Text] -> Header Text) -> [Header Text] -> Header Text
forall a b. (a -> b) -> a -> b
$ (Text -> Header Text) -> [Text] -> [Header Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Header Text
forall h. h -> Header h
Tab.Header [Text]
accts)
      (Properties -> [Header Text] -> Header Text
forall h. Properties -> [Header h] -> Header h
Tab.Group Properties
Tab.NoLine ([Header Text] -> Header Text) -> [Header Text] -> Header Text
forall a b. (a -> b) -> a -> b
$ (Text -> Header Text) -> [Text] -> [Header Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Header Text
forall h. h -> Header h
Tab.Header [Text]
colheadings)
      [[WideBuilder]]
rows
  where
    colheadings :: [Text]
colheadings = [Text
"Commodity" | Layout
layout_ Layout -> Layout -> Bool
forall a. Eq a => a -> a -> Bool
== Layout
LayoutBare]
                  [Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++ (DateSpan -> Text) -> [DateSpan] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (BalanceAccumulation -> [DateSpan] -> DateSpan -> Text
reportPeriodName BalanceAccumulation
balanceaccum_ [DateSpan]
spans) [DateSpan]
spans
                  [Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++ [Text
"  Total" | Bool
row_total_]
                  [Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++ [Text
"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 -> Text
renderacct PeriodicReportRow DisplayName a
row = case AccountListMode
accountlistmode_ of
        AccountListMode
ALTree -> Int -> Text -> Text
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) Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> PeriodicReportRow DisplayName a -> Text
forall a. PeriodicReportRow DisplayName a -> Text
prrDisplayName PeriodicReportRow DisplayName a
row
        AccountListMode
ALFlat -> Int -> Text -> Text
accountNameDrop (Int
drop_) (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ PeriodicReportRow DisplayName a -> Text
forall a. PeriodicReportRow DisplayName a -> Text
prrFullName PeriodicReportRow DisplayName a
row

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

    ([Text]
accts, [[WideBuilder]]
rows, [[WideBuilder]]
totalrows) = ([Text]
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 :: [[(Text, WideBuilder, [BudgetDisplayCell])]]
shownitems = ((PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
 -> [(Text, WideBuilder, [BudgetDisplayCell])])
-> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
-> [[(Text, WideBuilder, [BudgetDisplayCell])]]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
i -> ((WideBuilder, [BudgetDisplayCell])
 -> (Text, WideBuilder, [BudgetDisplayCell]))
-> [(WideBuilder, [BudgetDisplayCell])]
-> [(Text, WideBuilder, [BudgetDisplayCell])]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\(WideBuilder
cs, [BudgetDisplayCell]
cvals) -> (PeriodicReportRow DisplayName (Maybe Change, Maybe Change) -> Text
forall a. PeriodicReportRow DisplayName a -> Text
renderacct PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
i, WideBuilder
cs, [BudgetDisplayCell]
cvals)) ([(WideBuilder, [BudgetDisplayCell])]
 -> [(Text, WideBuilder, [BudgetDisplayCell])])
-> ([(Maybe Change, Maybe Change)]
    -> [(WideBuilder, [BudgetDisplayCell])])
-> [(Maybe Change, Maybe Change)]
-> [(Text, WideBuilder, [BudgetDisplayCell])]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(Maybe Change, Maybe Change)]
-> [(WideBuilder, [BudgetDisplayCell])]
showrow ([(Maybe Change, Maybe Change)]
 -> [(Text, WideBuilder, [BudgetDisplayCell])])
-> [(Maybe Change, Maybe Change)]
-> [(Text, WideBuilder, [BudgetDisplayCell])]
forall a b. (a -> b) -> a -> b
$ PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
-> [(Maybe Change, Maybe Change)]
forall {a} {a}. PeriodicReportRow a a -> [a]
rowToBudgetCells PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
i) [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
items)
        ([Text]
accts', [WideBuilder]
itemscs, [[BudgetDisplayCell]]
texts) = [(Text, WideBuilder, [BudgetDisplayCell])]
-> ([Text], [WideBuilder], [[BudgetDisplayCell]])
forall a b c. [(a, b, c)] -> ([a], [b], [c])
unzip3 ([(Text, WideBuilder, [BudgetDisplayCell])]
 -> ([Text], [WideBuilder], [[BudgetDisplayCell]]))
-> [(Text, WideBuilder, [BudgetDisplayCell])]
-> ([Text], [WideBuilder], [[BudgetDisplayCell]])
forall a b. (a -> b) -> a -> b
$ [[(Text, WideBuilder, [BudgetDisplayCell])]]
-> [(Text, WideBuilder, [BudgetDisplayCell])]
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 ([(Maybe Change, Maybe Change)]
 -> [(WideBuilder, [BudgetDisplayCell])])
-> [(Maybe Change, Maybe Change)]
-> [(WideBuilder, [BudgetDisplayCell])]
forall a b. (a -> b) -> a -> b
$ PeriodicReportRow () (Maybe Change, Maybe Change)
-> [(Maybe Change, Maybe Change)]
forall {a} {a}. PeriodicReportRow a a -> [a]
rowToBudgetCells PeriodicReportRow () (Maybe Change, Maybe Change)
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 a. [a] -> 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 a b. (a -> b) -> [a] -> [b]
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. (a -> b) -> [a] -> [b]
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 a b. (a -> b) -> [a] -> [b]
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. (a -> b) -> [a] -> [b]
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

        -- with --layout=bare, begin with a commodity column
        prependcs :: [a] -> [[a]] -> [[a]]
prependcs [a]
cs
          | Layout
layout_ Layout -> Layout -> Bool
forall a. Eq a => a -> a -> Bool
== Layout
LayoutBare = (a -> [a] -> [a]) -> [a] -> [[a]] -> [[a]]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (:) [a]
cs
          | Bool
otherwise             = [[a]] -> [[a]]
forall a. a -> a
id

    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 a. [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 a. [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 :: [Text] -> (BudgetShowMixed, BudgetPercBudget)
rowfuncs [Text]
cs = case Layout
layout_ of
      LayoutWide Maybe Int
width ->
           ( WideBuilder -> [WideBuilder]
forall a. a -> [a]
forall (f :: * -> *) a. Applicative f => a -> f a
pure (WideBuilder -> [WideBuilder])
-> (Change -> WideBuilder) -> BudgetShowMixed
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AmountDisplayOpts -> Change -> WideBuilder
showMixedAmountB AmountDisplayOpts
oneLine{displayMaxWidth=width, displayColour=color_}
           , \Change
a -> Maybe Percentage -> [Maybe Percentage]
forall a. a -> [a]
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Percentage -> [Maybe Percentage])
-> (Change -> Maybe Percentage) -> Change -> [Maybe Percentage]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Change -> Change -> Maybe Percentage
percentage Change
a)
      Layout
_ -> ( AmountDisplayOpts -> BudgetShowMixed
showMixedAmountLinesB AmountDisplayOpts
noCost{displayCommodity=layout_/=LayoutBare, displayCommodityOrder=Just cs, displayMinWidth=Nothing, displayColour=color_}
           , \Change
a Change
b -> (Text -> Maybe Percentage) -> [Text] -> [Maybe Percentage]
forall a b. (a -> b) -> [a] -> [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   [WideBuilder]
-> [[BudgetDisplayCell]] -> [(WideBuilder, [BudgetDisplayCell])]
forall a b. [a] -> [b] -> [(a, b)]
zip ((Text -> WideBuilder) -> [Text] -> [WideBuilder]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> WideBuilder
wbFromText [Text]
cs)
          ([[BudgetDisplayCell]] -> [(WideBuilder, [BudgetDisplayCell])])
-> ([(Maybe Change, Maybe Change)] -> [[BudgetDisplayCell]])
-> [(Maybe Change, Maybe Change)]
-> [(WideBuilder, [BudgetDisplayCell])]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[BudgetDisplayCell]] -> [[BudgetDisplayCell]]
forall a. [[a]] -> [[a]]
transpose
          ([[BudgetDisplayCell]] -> [[BudgetDisplayCell]])
-> ([(Maybe Change, Maybe Change)] -> [[BudgetDisplayCell]])
-> [(Maybe Change, Maybe Change)]
-> [[BudgetDisplayCell]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Maybe Change, Maybe Change) -> [BudgetDisplayCell])
-> [(Maybe Change, Maybe Change)] -> [[BudgetDisplayCell]]
forall a b. (a -> b) -> [a] -> [b]
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)
          ([(Maybe Change, Maybe Change)]
 -> [(WideBuilder, [BudgetDisplayCell])])
-> [(Maybe Change, Maybe Change)]
-> [(WideBuilder, [BudgetDisplayCell])]
forall a b. (a -> b) -> a -> b
$ [(Maybe Change, Maybe Change)]
row

    budgetCellsCommodities :: [(Maybe Change, Maybe Change)] -> [Text]
budgetCellsCommodities = Set Text -> [Text]
forall a. Set a -> [a]
S.toList (Set Text -> [Text])
-> ([(Maybe Change, Maybe Change)] -> Set Text)
-> [(Maybe Change, Maybe Change)]
-> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Set Text -> Set Text -> Set Text)
-> Set Text -> [Set Text] -> Set Text
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Set Text -> Set Text -> Set Text
forall a. Ord a => Set a -> Set a -> Set a
S.union Set Text
forall a. Monoid a => a
mempty ([Set Text] -> Set Text)
-> ([(Maybe Change, Maybe Change)] -> [Set Text])
-> [(Maybe Change, Maybe Change)]
-> Set Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Maybe Change, Maybe Change) -> Set Text)
-> [(Maybe Change, Maybe Change)] -> [Set Text]
forall a b. (a -> b) -> [a] -> [b]
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 Set Text -> Set Text -> Set Text
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 = Set Text -> (Change -> Set Text) -> Maybe Change -> Set Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Set Text
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 (WideBuilder -> Int)
-> (Maybe WideBuilder -> Int)
-> (WideBuilder, Maybe WideBuilder)
-> (Int, Int)
forall b c b' c'. (b -> c) -> (b' -> c') -> (b, b') -> (c, c')
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 ((Maybe Change, Maybe Change) -> [(Int, Int, Int)])
-> [(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((BudgetDisplayCell -> (Int, Int, Int))
-> [BudgetDisplayCell] -> [(Int, Int, Int)]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap BudgetDisplayCell -> (Int, Int, Int)
cellwidth ([BudgetDisplayCell] -> [(Int, Int, Int)])
-> ((Maybe Change, Maybe Change) -> [BudgetDisplayCell])
-> (Maybe Change, Maybe Change)
-> [(Int, Int, Int)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Maybe Change, Maybe Change) -> [BudgetDisplayCell]
disp) [(Maybe Change, Maybe Change)]
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 b a. (b -> a -> b) -> b -> [a] -> b
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 (Maybe Change, Maybe Change)
 -> [[(Int, Int, Int)]])
-> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
-> [[[(Int, Int, Int)]]]
forall a b. (a -> b) -> [a] -> [b]
map ([(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]]
cellswidth ([(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]])
-> (PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
    -> [(Maybe Change, Maybe Change)])
-> PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
-> [[(Int, Int, Int)]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
-> [(Maybe Change, Maybe Change)]
forall {a} {a}. PeriodicReportRow a a -> [a]
rowToBudgetCells) [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
items [[[(Int, Int, Int)]]]
-> [[[(Int, Int, Int)]]] -> [[[(Int, Int, Int)]]]
forall a. [a] -> [a] -> [a]
++ [[(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]]
cellswidth ([(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]])
-> [(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]]
forall a b. (a -> b) -> a -> b
$ PeriodicReportRow () (Maybe Change, Maybe Change)
-> [(Maybe Change, Maybe Change)]
forall {a} {a}. PeriodicReportRow a a -> [a]
rowToBudgetCells PeriodicReportRow () (Maybe Change, Maybe Change)
tr]

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

        budgetAndPerc :: Change -> [(WideBuilder, Maybe WideBuilder)]
budgetAndPerc Change
b = 
          [WideBuilder]
-> [Maybe WideBuilder] -> [(WideBuilder, Maybe WideBuilder)]
forall a b. [a] -> [b] -> [(a, b)]
zip (BudgetShowMixed
showmixed Change
b) ((Percentage -> WideBuilder)
-> Maybe Percentage -> Maybe WideBuilder
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Text -> WideBuilder
wbFromText (Text -> WideBuilder)
-> (Percentage -> Text) -> Percentage -> WideBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
T.pack (String -> Text) -> (Percentage -> String) -> Percentage -> Text
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 Change
actual' Change
b)

        full :: [Maybe (WideBuilder, Maybe WideBuilder)]
full
          | Just Change
b <- Maybe Change
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
<$> Change -> [(WideBuilder, Maybe WideBuilder)]
budgetAndPerc Change
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) =
            (Text -> Builder
TB.fromText (Text -> Builder) -> (Int -> Text) -> Int -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int -> Text -> Text) -> Text -> Int -> Text
forall a b c. (a -> b -> c) -> b -> a -> c
flip Int -> Text -> Text
T.replicate Text
" " (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 :: 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 Int -> Int -> Int
forall a. Num a => a -> a -> a
- WideBuilder -> Int
wbWidth WideBuilder
pct) Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> WideBuilder -> Text
wbToText WideBuilder
pct Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"% of "
           in Text -> Builder
TB.fromText (Text -> Builder) -> Text -> Builder
forall a b. (a -> b) -> a -> b
$ Text
" [" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
perct Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text -> Text
T.replicate (Int
budgetwidth Int -> Int -> Int
forall a. Num a => a -> a -> a
- WideBuilder -> Int
wbWidth WideBuilder
budget) Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> WideBuilder -> Text
wbToText WideBuilder
budget Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"]"

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

        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 :: 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 Text -> Text -> Bool
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)
            -> 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 :: Change -> [Amount]
costedAmounts = case Maybe ConversionOp
conversionop_ of
            Just ConversionOp
ToCost -> Change -> [Amount]
amounts (Change -> [Amount]) -> (Change -> Change) -> Change -> [Amount]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Change -> Change
mixedAmountCost
            Maybe ConversionOp
_           -> Change -> [Amount]
amounts

    -- | Calculate the percentage of actual change to budget goal for a particular commodity
    percentage' :: Change -> BudgetGoal -> CommoditySymbol -> Maybe Percentage
    percentage' :: Change -> Change -> Text -> Maybe Percentage
percentage' Change
am Change
bm Text
c = case ((,) (Maybe Amount -> Maybe Amount -> (Maybe Amount, Maybe Amount))
-> (Change -> Maybe Amount)
-> Change
-> Change
-> (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 (Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
(==) Text
c (Text -> Bool) -> (Amount -> Text) -> Amount -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Amount -> Text
acommodity) ([Amount] -> Maybe Amount)
-> (Change -> [Amount]) -> Change -> Maybe Amount
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)
_                -> 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 -> [[Text]]
budgetReportAsCsv
  ReportOpts{Bool
Int
[Text]
[Status]
Maybe Int
Maybe Text
Maybe NormalSign
Maybe ValuationType
Maybe ConversionOp
Interval
Period
StringFormat
Layout
AccountListMode
BalanceAccumulation
BalanceCalculation
accountlistmode_ :: ReportOpts -> AccountListMode
empty_ :: ReportOpts -> Bool
infer_prices_ :: ReportOpts -> Bool
interval_ :: ReportOpts -> Interval
budgetpat_ :: ReportOpts -> Maybe Text
period_ :: ReportOpts -> Period
statuses_ :: ReportOpts -> [Status]
conversionop_ :: ReportOpts -> Maybe ConversionOp
value_ :: ReportOpts -> Maybe ValuationType
depth_ :: ReportOpts -> Maybe Int
date2_ :: ReportOpts -> Bool
no_elide_ :: ReportOpts -> Bool
real_ :: ReportOpts -> Bool
format_ :: ReportOpts -> StringFormat
pretty_ :: ReportOpts -> Bool
querystring_ :: ReportOpts -> [Text]
average_ :: ReportOpts -> Bool
related_ :: ReportOpts -> Bool
txn_dates_ :: ReportOpts -> Bool
balancecalc_ :: ReportOpts -> BalanceCalculation
balanceaccum_ :: ReportOpts -> BalanceAccumulation
drop_ :: ReportOpts -> Int
declared_ :: ReportOpts -> Bool
row_total_ :: ReportOpts -> Bool
no_total_ :: ReportOpts -> Bool
summary_only_ :: ReportOpts -> Bool
show_costs_ :: ReportOpts -> Bool
sort_amount_ :: ReportOpts -> Bool
percent_ :: ReportOpts -> Bool
invert_ :: ReportOpts -> Bool
normalbalance_ :: ReportOpts -> Maybe NormalSign
color_ :: ReportOpts -> Bool
transpose_ :: ReportOpts -> Bool
layout_ :: ReportOpts -> Layout
period_ :: Period
interval_ :: Interval
statuses_ :: [Status]
conversionop_ :: Maybe ConversionOp
value_ :: Maybe ValuationType
infer_prices_ :: Bool
depth_ :: Maybe Int
date2_ :: Bool
empty_ :: Bool
no_elide_ :: Bool
real_ :: Bool
format_ :: StringFormat
pretty_ :: Bool
querystring_ :: [Text]
average_ :: Bool
related_ :: Bool
txn_dates_ :: Bool
balancecalc_ :: BalanceCalculation
balanceaccum_ :: BalanceAccumulation
budgetpat_ :: Maybe Text
accountlistmode_ :: AccountListMode
drop_ :: Int
declared_ :: Bool
row_total_ :: Bool
no_total_ :: Bool
summary_only_ :: Bool
show_costs_ :: Bool
sort_amount_ :: Bool
percent_ :: Bool
invert_ :: Bool
normalbalance_ :: Maybe NormalSign
color_ :: Bool
transpose_ :: Bool
layout_ :: Layout
..}
  (PeriodicReport [DateSpan]
colspans [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
items PeriodicReportRow () (Maybe Change, Maybe Change)
tr)
  = (if Bool
transpose_ then [[Text]] -> [[Text]]
forall a. [[a]] -> [[a]]
transpose else [[Text]] -> [[Text]]
forall a. a -> a
id) ([[Text]] -> [[Text]]) -> [[Text]] -> [[Text]]
forall a b. (a -> b) -> a -> b
$

  -- heading row
  

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

  -- account rows
  

  -- account rows
  (PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
 -> [[Text]])
-> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
-> [[Text]]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ((PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
 -> Text)
-> PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
-> [[Text]]
forall a.
(PeriodicReportRow a (Maybe Change, Maybe Change) -> Text)
-> PeriodicReportRow a (Maybe Change, Maybe Change) -> [[Text]]
rowAsTexts PeriodicReportRow DisplayName (Maybe Change, Maybe Change) -> Text
forall a. PeriodicReportRow DisplayName a -> Text
prrFullName) [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
items

  -- totals row
  [[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
++ [[[Text]]] -> [[Text]]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [ (PeriodicReportRow () (Maybe Change, Maybe Change) -> Text)
-> PeriodicReportRow () (Maybe Change, Maybe Change) -> [[Text]]
forall a.
(PeriodicReportRow a (Maybe Change, Maybe Change) -> Text)
-> PeriodicReportRow a (Maybe Change, Maybe Change) -> [[Text]]
rowAsTexts (Text -> PeriodicReportRow () (Maybe Change, Maybe Change) -> Text
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 = [[a]] -> [a]
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 = Text -> (Change -> Text) -> Maybe Change -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"" (WideBuilder -> Text
wbToText (WideBuilder -> Text) -> (Change -> WideBuilder) -> Change -> Text
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_ Layout -> Layout -> Bool
forall a. Eq a => a -> a -> Bool
/= Layout
LayoutBare = [PeriodicReportRow a (Maybe Change, Maybe Change) -> Text
render PeriodicReportRow a (Maybe Change, Maybe Change)
row Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: (Maybe Change -> Text) -> [Maybe Change] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
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 ([[Text]] -> [[Text]])
-> ([Maybe Change] -> [[Text]]) -> [Maybe Change] -> [[Text]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text -> [Text] -> [Text]) -> [Text] -> [[Text]] -> [[Text]]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (:) [Text]
cs  -- add symbols and names
          ([[Text]] -> [[Text]])
-> ([Maybe Change] -> [[Text]]) -> [Maybe Change] -> [[Text]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[Text]] -> [[Text]]
forall a. [[a]] -> [[a]]
transpose                   -- each row becomes a list of Text quantities
          ([[Text]] -> [[Text]])
-> ([Maybe Change] -> [[Text]]) -> [Maybe Change] -> [[Text]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Maybe Change -> [Text]) -> [Maybe Change] -> [[Text]]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((WideBuilder -> Text) -> [WideBuilder] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap WideBuilder -> Text
wbToText ([WideBuilder] -> [Text])
-> (Maybe Change -> [WideBuilder]) -> Maybe Change -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AmountDisplayOpts -> BudgetShowMixed
showMixedAmountLinesB AmountDisplayOpts
dopts BudgetShowMixed
-> (Maybe Change -> Change) -> Maybe Change -> [WideBuilder]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Change -> Maybe Change -> Change
forall a. a -> Maybe a -> a
fromMaybe Change
nullmixedamt)
          ([Maybe Change] -> [[Text]]) -> [Maybe Change] -> [[Text]]
forall a b. (a -> b) -> a -> b
$ [Maybe Change]
vals
      where
        cs :: [Text]
cs = Set Text -> [Text]
forall a. Set a -> [a]
S.toList (Set Text -> [Text])
-> ([Change] -> Set Text) -> [Change] -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Set Text -> Set Text -> Set Text)
-> Set Text -> [Set Text] -> Set Text
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Set Text -> Set Text -> Set Text
forall a. Ord a => Set a -> Set a -> Set a
S.union Set Text
forall a. Monoid a => a
mempty ([Set Text] -> Set Text)
-> ([Change] -> [Set Text]) -> [Change] -> Set Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Change -> Set Text) -> [Change] -> [Set Text]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Change -> Set Text
maCommodities ([Change] -> [Text]) -> [Change] -> [Text]
forall a b. (a -> b) -> a -> b
$ [Maybe Change] -> [Change]
forall a. [Maybe a] -> [a]
catMaybes [Maybe Change]
vals
        dopts :: AmountDisplayOpts
dopts = AmountDisplayOpts
oneLine{displayCommodity=layout_ /= LayoutBare, displayCommodityOrder=Just cs, displayMinWidth=Nothing}
        vals :: [Maybe Change]
vals = [(Maybe Change, Maybe Change)] -> [Maybe Change]
forall {a}. [(a, a)] -> [a]
flattentuples [(Maybe Change, Maybe Change)]
as
            [Maybe Change] -> [Maybe Change] -> [Maybe Change]
forall a. [a] -> [a] -> [a]
++ [[Maybe Change]] -> [Maybe Change]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[Maybe Change
rowtot, Maybe Change
budgettot] | Bool
row_total_]
            [Maybe Change] -> [Maybe Change] -> [Maybe Change]
forall a. [a] -> [a] -> [a]
++ [[Maybe Change]] -> [Maybe Change]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[Maybe Change
rowavg, Maybe Change
budgetavg] | Bool
average_]

        joinNames :: [[Text]] -> [[Text]]
joinNames = ([Text] -> [Text]) -> [[Text]] -> [[Text]]
forall a b. (a -> b) -> [a] -> [b]
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 Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
:)

-- tests

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