{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-|

Common helpers for making multi-section balance report commands
like balancesheet, cashflow, and incomestatement.

-}

module Hledger.Cli.CompoundBalanceCommand (
  CompoundBalanceCommandSpec(..)
 ,compoundBalanceCommandMode
 ,compoundBalanceCommand
) where

import Data.List (foldl')
import Data.Maybe (fromMaybe, mapMaybe)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TB
import Data.Time.Calendar (Day, addDays)
import System.Console.CmdArgs.Explicit as C
import Hledger.Read.CsvUtils (CSV, printCSV, printTSV)
import Lucid as L hiding (value_)
import Safe (tailDef)
import Text.Tabular.AsciiWide as Tab hiding (render)

import Hledger
import Hledger.Cli.Commands.Balance
import Hledger.Cli.CliOptions
import Hledger.Cli.Utils (unsupportedOutputFormatError, writeOutputLazyText)

-- | Description of a compound balance report command,
-- from which we generate the command's cmdargs mode and IO action.
-- A compound balance report command shows one or more sections/subreports,
-- each with its own title and subtotals row, in a certain order,
-- plus a grand totals row if there's more than one section.
-- Examples are the balancesheet, cashflow and incomestatement commands.
--
-- Compound balance reports do sign normalisation: they show all account balances
-- as normally positive, unlike the ordinary BalanceReport and most hledger commands
-- which show income/liability/equity balances as normally negative.
-- Each subreport specifies the normal sign of its amounts, and whether
-- it should be added to or subtracted from the grand total.
--
data CompoundBalanceCommandSpec = CompoundBalanceCommandSpec {
  CompoundBalanceCommandSpec -> [Char]
cbcdoc      :: CommandDoc,                      -- ^ the command's name(s) and documentation
  CompoundBalanceCommandSpec -> [Char]
cbctitle    :: String,                          -- ^ overall report title
  CompoundBalanceCommandSpec -> [CBCSubreportSpec DisplayName]
cbcqueries  :: [CBCSubreportSpec DisplayName],  -- ^ subreport details
  CompoundBalanceCommandSpec -> BalanceAccumulation
cbcaccum    :: BalanceAccumulation              -- ^ how to accumulate balances (per-period, cumulative, historical)
                                                  --   (overrides command line flags)
}

-- | Generate a cmdargs option-parsing mode from a compound balance command
-- specification.
compoundBalanceCommandMode :: CompoundBalanceCommandSpec -> Mode RawOpts
compoundBalanceCommandMode :: CompoundBalanceCommandSpec -> Mode RawOpts
compoundBalanceCommandMode CompoundBalanceCommandSpec{[Char]
[CBCSubreportSpec DisplayName]
BalanceAccumulation
cbcdoc :: CompoundBalanceCommandSpec -> [Char]
cbctitle :: CompoundBalanceCommandSpec -> [Char]
cbcqueries :: CompoundBalanceCommandSpec -> [CBCSubreportSpec DisplayName]
cbcaccum :: CompoundBalanceCommandSpec -> BalanceAccumulation
cbcdoc :: [Char]
cbctitle :: [Char]
cbcqueries :: [CBCSubreportSpec DisplayName]
cbcaccum :: BalanceAccumulation
..} =
  [Char]
-> [Flag RawOpts]
-> [([Char], [Flag RawOpts])]
-> [Flag RawOpts]
-> ([Arg RawOpts], Maybe (Arg RawOpts))
-> Mode RawOpts
hledgerCommandMode
   [Char]
cbcdoc
   ([[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"sum"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"sum")
      [Char]
"show sum of posting amounts (default)"
   ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"valuechange"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"valuechange")
      [Char]
"show total change of period-end historical balance value (caused by deposits, withdrawals, market price fluctuations)"
    ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"gain"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"gain")
      [Char]
"show unrealised capital gain/loss (historical balance value minus cost basis)"
   ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"budget"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"budget")
      [Char]
"show sum of posting amounts compared to budget goals defined by periodic transactions\n "

   ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"change"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"change")
       ([Char]
"accumulate amounts from column start to column end (in multicolumn reports)"
           [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ BalanceAccumulation -> [Char]
defaultMarker BalanceAccumulation
PerPeriod)
    ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"cumulative"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"cumulative")
       ([Char]
"accumulate amounts from report start (specified by e.g. -b/--begin) to column end"
           [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ BalanceAccumulation -> [Char]
defaultMarker BalanceAccumulation
Cumulative)
    ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"historical",[Char]
"H"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"historical")
       ([Char]
"accumulate amounts from journal start to column end (includes postings before report start date)"
           [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ BalanceAccumulation -> [Char]
defaultMarker BalanceAccumulation
Historical [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"\n ")
    ]
    [Flag RawOpts] -> [Flag RawOpts] -> [Flag RawOpts]
forall a. [a] -> [a] -> [a]
++ Bool -> [Flag RawOpts]
flattreeflags Bool
True [Flag RawOpts] -> [Flag RawOpts] -> [Flag RawOpts]
forall a. [a] -> [a] -> [a]
++
    [[[Char]] -> Update RawOpts -> [Char] -> [Char] -> Flag RawOpts
forall a. [[Char]] -> Update a -> [Char] -> [Char] -> Flag a
flagReq  [[Char]
"drop"] (\[Char]
s RawOpts
opts -> RawOpts -> Either [Char] RawOpts
forall a b. b -> Either a b
Right (RawOpts -> Either [Char] RawOpts)
-> RawOpts -> Either [Char] RawOpts
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char] -> RawOpts -> RawOpts
setopt [Char]
"drop" [Char]
s RawOpts
opts) [Char]
"N" [Char]
"flat mode: omit N leading account name parts"
    ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"declared"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"declared") [Char]
"include non-parent declared accounts (best used with -E)"
    ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"average",[Char]
"A"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"average") [Char]
"show a row average column (in multicolumn reports)"
    ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"row-total",[Char]
"T"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"row-total") [Char]
"show a row total column (in multicolumn reports)"
    ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"summary-only"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"summary-only") [Char]
"display only row summaries (e.g. row total, average) (in multicolumn reports)"
    ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"no-total",[Char]
"N"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"no-total") [Char]
"omit the final total row"
    ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"no-elide"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"no-elide") [Char]
"don't squash boring parent accounts (in tree mode)"
    ,[[Char]] -> Update RawOpts -> [Char] -> [Char] -> Flag RawOpts
forall a. [[Char]] -> Update a -> [Char] -> [Char] -> Flag a
flagReq  [[Char]
"format"] (\[Char]
s RawOpts
opts -> RawOpts -> Either [Char] RawOpts
forall a b. b -> Either a b
Right (RawOpts -> Either [Char] RawOpts)
-> RawOpts -> Either [Char] RawOpts
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char] -> RawOpts -> RawOpts
setopt [Char]
"format" [Char]
s RawOpts
opts) [Char]
"FORMATSTR" [Char]
"use this custom line format (in simple reports)"
    ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"sort-amount",[Char]
"S"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"sort-amount") [Char]
"sort by amount instead of account code/name"
    ,[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"percent", [Char]
"%"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"percent") [Char]
"express values in percentage of each column's total"
    ,[[Char]] -> Update RawOpts -> [Char] -> [Char] -> Flag RawOpts
forall a. [[Char]] -> Update a -> [Char] -> [Char] -> Flag a
flagReq  [[Char]
"layout"] (\[Char]
s RawOpts
opts -> RawOpts -> Either [Char] RawOpts
forall a b. b -> Either a b
Right (RawOpts -> Either [Char] RawOpts)
-> RawOpts -> Either [Char] RawOpts
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char] -> RawOpts -> RawOpts
setopt [Char]
"layout" [Char]
s RawOpts
opts) [Char]
"ARG"
      ([[Char]] -> [Char]
unlines
        [[Char]
"how to show multi-commodity amounts:"
        ,[Char]
"'wide[,WIDTH]': all commodities on one line"
        ,[Char]
"'tall'        : each commodity on a new line"
        ,[Char]
"'bare'        : bare numbers, symbols in a column"
        ])
    ,[[Char]] -> Flag RawOpts
outputFormatFlag [[Char]
"txt",[Char]
"html",[Char]
"csv",[Char]
"tsv",[Char]
"json"]
    ,Flag RawOpts
outputFileFlag
    ])
    [([Char], [Flag RawOpts])
generalflagsgroup1]
    ([Flag RawOpts]
hiddenflags [Flag RawOpts] -> [Flag RawOpts] -> [Flag RawOpts]
forall a. [a] -> [a] -> [a]
++
      [ [[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
flagNone [[Char]
"commodity-column"] ([Char] -> RawOpts -> RawOpts
setboolopt [Char]
"commodity-column")
        [Char]
"show commodity symbols in a separate column, amounts as bare numbers, one row per commodity"
      ])
    ([], Arg RawOpts -> Maybe (Arg RawOpts)
forall a. a -> Maybe a
Just (Arg RawOpts -> Maybe (Arg RawOpts))
-> Arg RawOpts -> Maybe (Arg RawOpts)
forall a b. (a -> b) -> a -> b
$ [Char] -> Arg RawOpts
argsFlag [Char]
"[QUERY]")
 where
   defaultMarker :: BalanceAccumulation -> String
   defaultMarker :: BalanceAccumulation -> [Char]
defaultMarker BalanceAccumulation
bacc | BalanceAccumulation
bacc BalanceAccumulation -> BalanceAccumulation -> Bool
forall a. Eq a => a -> a -> Bool
== BalanceAccumulation
cbcaccum = [Char]
" (default)"
                      | Bool
otherwise        = [Char]
""

-- | Generate a runnable command from a compound balance command specification.
compoundBalanceCommand :: CompoundBalanceCommandSpec -> (CliOpts -> Journal -> IO ())
compoundBalanceCommand :: CompoundBalanceCommandSpec -> CliOpts -> Journal -> IO ()
compoundBalanceCommand CompoundBalanceCommandSpec{[Char]
[CBCSubreportSpec DisplayName]
BalanceAccumulation
cbcdoc :: CompoundBalanceCommandSpec -> [Char]
cbctitle :: CompoundBalanceCommandSpec -> [Char]
cbcqueries :: CompoundBalanceCommandSpec -> [CBCSubreportSpec DisplayName]
cbcaccum :: CompoundBalanceCommandSpec -> BalanceAccumulation
cbcdoc :: [Char]
cbctitle :: [Char]
cbcqueries :: [CBCSubreportSpec DisplayName]
cbcaccum :: BalanceAccumulation
..} opts :: CliOpts
opts@CliOpts{reportspec_ :: CliOpts -> ReportSpec
reportspec_=ReportSpec
rspec, rawopts_ :: CliOpts -> RawOpts
rawopts_=RawOpts
rawopts} Journal
j = do
    CliOpts -> Text -> IO ()
writeOutputLazyText CliOpts
opts (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ CompoundPeriodicReport DisplayName MixedAmount -> Text
render (CompoundPeriodicReport DisplayName MixedAmount -> Text)
-> CompoundPeriodicReport DisplayName MixedAmount -> Text
forall a b. (a -> b) -> a -> b
$ Map Text AmountStyle
-> CompoundPeriodicReport DisplayName MixedAmount
-> CompoundPeriodicReport DisplayName MixedAmount
forall a. HasAmounts a => Map Text AmountStyle -> a -> a
styleAmounts Map Text AmountStyle
styles CompoundPeriodicReport DisplayName MixedAmount
cbr
  where
    styles :: Map Text AmountStyle
styles = Rounding -> Journal -> Map Text AmountStyle
journalCommodityStylesWith Rounding
HardRounding Journal
j
    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
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
interval_ :: ReportOpts -> Interval
statuses_ :: ReportOpts -> [Status]
conversionop_ :: ReportOpts -> Maybe ConversionOp
value_ :: ReportOpts -> Maybe ValuationType
infer_prices_ :: ReportOpts -> Bool
depth_ :: ReportOpts -> Maybe Int
date2_ :: ReportOpts -> Bool
empty_ :: 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
budgetpat_ :: ReportOpts -> Maybe Text
accountlistmode_ :: ReportOpts -> AccountListMode
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
..} = ReportSpec -> ReportOpts
_rsReportOpts ReportSpec
rspec
    -- use the default balance type for this report, unless the user overrides
    mbalanceAccumulationOverride :: Maybe BalanceAccumulation
mbalanceAccumulationOverride = RawOpts -> Maybe BalanceAccumulation
balanceAccumulationOverride RawOpts
rawopts
    balanceaccumulation :: BalanceAccumulation
balanceaccumulation = BalanceAccumulation
-> Maybe BalanceAccumulation -> BalanceAccumulation
forall a. a -> Maybe a -> a
fromMaybe BalanceAccumulation
cbcaccum Maybe BalanceAccumulation
mbalanceAccumulationOverride
    -- Set balance type in the report options.
    ropts' :: ReportOpts
ropts' = ReportOpts
ropts{balanceaccum_=balanceaccumulation}

    title :: Text
title =
      [Char] -> Text
T.pack [Char]
cbctitle
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" "
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
titledatestr
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> (Text -> Text) -> Maybe Text -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"" (Text
" "Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>) Maybe Text
mtitleclarification
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
valuationdesc
      where

        -- XXX #1078 the title of ending balance reports
        -- (Historical) should mention the end date(s) shown as
        -- column heading(s) (not the date span of the transactions).
        -- Also the dates should not be simplified (it should show
        -- "2008/01/01-2008/12/31", not "2008").
        titledatestr :: Text
titledatestr = case BalanceAccumulation
balanceaccumulation of
            BalanceAccumulation
Historical -> [Day] -> Text
showEndDates [Day]
enddates
            BalanceAccumulation
_          -> DateSpan -> Text
showDateSpan DateSpan
requestedspan
          where
            enddates :: [Day]
enddates = (Day -> Day) -> [Day] -> [Day]
forall a b. (a -> b) -> [a] -> [b]
map (Integer -> Day -> Day
addDays (-Integer
1)) ([Day] -> [Day]) -> ([DateSpan] -> [Day]) -> [DateSpan] -> [Day]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (DateSpan -> Maybe Day) -> [DateSpan] -> [Day]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe DateSpan -> Maybe Day
spanEnd ([DateSpan] -> [Day]) -> [DateSpan] -> [Day]
forall a b. (a -> b) -> a -> b
$ CompoundPeriodicReport DisplayName MixedAmount -> [DateSpan]
forall a b. CompoundPeriodicReport a b -> [DateSpan]
cbrDates CompoundPeriodicReport DisplayName MixedAmount
cbr  -- these spans will always have a definite end date
            requestedspan :: DateSpan
requestedspan = (DateSpan, [DateSpan]) -> DateSpan
forall a b. (a, b) -> a
fst ((DateSpan, [DateSpan]) -> DateSpan)
-> (DateSpan, [DateSpan]) -> DateSpan
forall a b. (a -> b) -> a -> b
$ Journal -> ReportSpec -> (DateSpan, [DateSpan])
reportSpan Journal
j ReportSpec
rspec

        -- when user overrides, add an indication to the report title
        -- Do we need to deal with overridden BalanceCalculation?
        mtitleclarification :: Maybe Text
mtitleclarification = case (BalanceCalculation
balancecalc_, BalanceAccumulation
balanceaccumulation, Maybe BalanceAccumulation
mbalanceAccumulationOverride) of
            (BalanceCalculation
CalcValueChange, BalanceAccumulation
PerPeriod,  Maybe BalanceAccumulation
_              ) -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"(Period-End Value Changes)"
            (BalanceCalculation
CalcValueChange, BalanceAccumulation
Cumulative, Maybe BalanceAccumulation
_              ) -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"(Cumulative Period-End Value Changes)"
            (BalanceCalculation
CalcGain,        BalanceAccumulation
PerPeriod,  Maybe BalanceAccumulation
_              ) -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"(Incremental Gain)"
            (BalanceCalculation
CalcGain,        BalanceAccumulation
Cumulative, Maybe BalanceAccumulation
_              ) -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"(Cumulative Gain)"
            (BalanceCalculation
CalcGain,        BalanceAccumulation
Historical, Maybe BalanceAccumulation
_              ) -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"(Historical Gain)"
            (BalanceCalculation
_,               BalanceAccumulation
_,          Just BalanceAccumulation
PerPeriod ) -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"(Balance Changes)"
            (BalanceCalculation
_,               BalanceAccumulation
_,          Just BalanceAccumulation
Cumulative) -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"(Cumulative Ending Balances)"
            (BalanceCalculation
_,               BalanceAccumulation
_,          Just BalanceAccumulation
Historical) -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"(Historical Ending Balances)"
            (BalanceCalculation, BalanceAccumulation,
 Maybe BalanceAccumulation)
_                                              -> Maybe Text
forall a. Maybe a
Nothing

        valuationdesc :: Text
valuationdesc =
          (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) | Bool
changingValuation -> Text
""
               Just (AtEnd Maybe Text
_mc)        -> Text
", valued at period ends"
               Just (AtNow Maybe Text
_mc)        -> Text
", current value"
               Just (AtDate Day
today Maybe Text
_mc) -> Text
", valued at " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Day -> Text
showDate Day
today
               Maybe ValuationType
Nothing                 -> Text
"")

        changingValuation :: Bool
changingValuation = case (BalanceCalculation
balancecalc_, BalanceAccumulation
balanceaccum_) of
            (BalanceCalculation
CalcValueChange, BalanceAccumulation
PerPeriod)  -> Bool
True
            (BalanceCalculation
CalcValueChange, BalanceAccumulation
Cumulative) -> Bool
True
            (BalanceCalculation, BalanceAccumulation)
_                             -> Bool
False

    -- make a CompoundBalanceReport.
    cbr' :: CompoundPeriodicReport DisplayName MixedAmount
cbr' = ReportSpec
-> Journal
-> [CBCSubreportSpec DisplayName]
-> CompoundPeriodicReport DisplayName MixedAmount
forall a.
ReportSpec
-> Journal
-> [CBCSubreportSpec a]
-> CompoundPeriodicReport a MixedAmount
compoundBalanceReport ReportSpec
rspec{_rsReportOpts=ropts'} Journal
j [CBCSubreportSpec DisplayName]
cbcqueries
    cbr :: CompoundPeriodicReport DisplayName MixedAmount
cbr  = CompoundPeriodicReport DisplayName MixedAmount
cbr'{cbrTitle=title}

    -- render appropriately
    render :: CompoundPeriodicReport DisplayName MixedAmount -> Text
render = case CliOpts -> [Char]
outputFormatFromOpts CliOpts
opts of
      [Char]
"txt"  -> ReportOpts
-> CompoundPeriodicReport DisplayName MixedAmount -> Text
compoundBalanceReportAsText ReportOpts
ropts'
      [Char]
"csv"  -> [[Text]] -> Text
printCSV ([[Text]] -> Text)
-> (CompoundPeriodicReport DisplayName MixedAmount -> [[Text]])
-> CompoundPeriodicReport DisplayName MixedAmount
-> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ReportOpts
-> CompoundPeriodicReport DisplayName MixedAmount -> [[Text]]
compoundBalanceReportAsCsv ReportOpts
ropts'
      [Char]
"tsv"  -> [[Text]] -> Text
printTSV ([[Text]] -> Text)
-> (CompoundPeriodicReport DisplayName MixedAmount -> [[Text]])
-> CompoundPeriodicReport DisplayName MixedAmount
-> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ReportOpts
-> CompoundPeriodicReport DisplayName MixedAmount -> [[Text]]
compoundBalanceReportAsCsv ReportOpts
ropts'
      [Char]
"html" -> HtmlT Identity () -> Text
forall a. Html a -> Text
L.renderText (HtmlT Identity () -> Text)
-> (CompoundPeriodicReport DisplayName MixedAmount
    -> HtmlT Identity ())
-> CompoundPeriodicReport DisplayName MixedAmount
-> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ReportOpts
-> CompoundPeriodicReport DisplayName MixedAmount
-> HtmlT Identity ()
compoundBalanceReportAsHtml ReportOpts
ropts'
      [Char]
"json" -> CompoundPeriodicReport DisplayName MixedAmount -> Text
forall a. ToJSON a => a -> Text
toJsonText
      [Char]
x      -> [Char] -> CompoundPeriodicReport DisplayName MixedAmount -> Text
forall a. [Char] -> a
error' ([Char] -> CompoundPeriodicReport DisplayName MixedAmount -> Text)
-> [Char] -> CompoundPeriodicReport DisplayName MixedAmount -> Text
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char]
unsupportedOutputFormatError [Char]
x

-- | Summarise one or more (inclusive) end dates, in a way that's
-- visually different from showDateSpan, suggesting discrete end dates
-- rather than a continuous span.
showEndDates :: [Day] -> T.Text
showEndDates :: [Day] -> Text
showEndDates [Day]
es = case [Day]
es of
  -- cf showPeriod
  (Day
e:Day
_:[Day]
_) -> Day -> Text
showDate Day
e Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".." Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Day -> Text
showDate ([Day] -> Day
forall a. HasCallStack => [a] -> a
last [Day]
es)
  [Day
e]     -> Day -> Text
showDate Day
e
  []      -> Text
""

-- | Render a compound balance report as plain text suitable for console output.
{- Eg:
Balance Sheet

             ||  2017/12/31    Total  Average
=============++===============================
 Assets      ||
-------------++-------------------------------
 assets:b    ||           1        1        1
-------------++-------------------------------
             ||           1        1        1
=============++===============================
 Liabilities ||
-------------++-------------------------------
-------------++-------------------------------
             ||
=============++===============================
 Total       ||           1        1        1

-}
compoundBalanceReportAsText :: ReportOpts -> CompoundPeriodicReport DisplayName MixedAmount -> TL.Text
compoundBalanceReportAsText :: ReportOpts
-> CompoundPeriodicReport DisplayName MixedAmount -> Text
compoundBalanceReportAsText ReportOpts
ropts
  (CompoundPeriodicReport Text
title [DateSpan]
_colspans [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
subreports PeriodicReportRow () MixedAmount
netrow) =
    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 Table Text Text WideBuilder
bigtable'
  where
    bigtable :: Table Text Text WideBuilder
bigtable =
      case ((Text, PeriodicReport DisplayName MixedAmount, Bool)
 -> Table Text Text WideBuilder)
-> [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
-> [Table Text Text WideBuilder]
forall a b. (a -> b) -> [a] -> [b]
map (ReportOpts
-> (Text, PeriodicReport DisplayName MixedAmount, Bool)
-> Table Text Text WideBuilder
forall {c}.
ReportOpts
-> (Text, PeriodicReport DisplayName MixedAmount, c)
-> Table Text Text WideBuilder
subreportAsTable ReportOpts
ropts) [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
subreports of
        []   -> Table Text Text WideBuilder
forall rh ch a. Table rh ch a
Tab.empty
        Table Text Text WideBuilder
r:[Table Text Text WideBuilder]
rs -> (Table Text Text WideBuilder
 -> Table Text Text WideBuilder -> Table Text Text WideBuilder)
-> Table Text Text WideBuilder
-> [Table Text Text WideBuilder]
-> Table Text Text WideBuilder
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' (Properties
-> Table Text Text WideBuilder
-> Table Text Text WideBuilder
-> Table Text Text WideBuilder
forall rh ch a ch2.
Properties -> Table rh ch a -> Table rh ch2 a -> Table rh ch a
concatTables Properties
DoubleLine) Table Text Text WideBuilder
r [Table Text Text WideBuilder]
rs
    bigtable' :: Table Text Text WideBuilder
bigtable'
      | ReportOpts -> Bool
no_total_ ReportOpts
ropts Bool -> Bool -> Bool
|| [(Text, PeriodicReport DisplayName MixedAmount, Bool)] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
subreports Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1 =
          Table Text Text WideBuilder
bigtable
      | Bool
otherwise =
        let totalrows :: [[WideBuilder]]
totalrows = ReportOpts -> PeriodicReportRow () MixedAmount -> [[WideBuilder]]
forall a.
ReportOpts -> PeriodicReportRow a MixedAmount -> [[WideBuilder]]
multiBalanceRowAsTableText ReportOpts
ropts PeriodicReportRow () MixedAmount
netrow
            rh :: Header Text
rh = Properties -> [Header Text] -> Header Text
forall h. Properties -> [Header h] -> Header h
Tab.Group Properties
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
Header (Text
"Net:" Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: Int -> Text -> [Text]
forall a. Int -> a -> [a]
replicate ([[WideBuilder]] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [[WideBuilder]]
totalrows Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) Text
"")
            ch :: Header [a]
ch = [a] -> Header [a]
forall h. h -> Header h
Header [] -- ignored
         in ((Properties
-> Table Text Text WideBuilder
-> Table Text [Any] WideBuilder
-> Table Text Text WideBuilder
forall rh ch a ch2.
Properties -> Table rh ch a -> Table rh ch2 a -> Table rh ch a
concatTables Properties
Tab.DoubleLine) Table Text Text WideBuilder
bigtable (Table Text [Any] WideBuilder -> Table Text Text WideBuilder)
-> Table Text [Any] WideBuilder -> Table Text Text 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
Table Header Text
rh Header [Any]
forall {a}. Header [a]
ch [[WideBuilder]]
totalrows)

    -- | Convert a named multi balance report to a table suitable for
    -- concatenating with others to make a compound balance report table.
    subreportAsTable :: ReportOpts
-> (Text, PeriodicReport DisplayName MixedAmount, c)
-> Table Text Text WideBuilder
subreportAsTable ReportOpts
ropts1 (Text
title1, PeriodicReport DisplayName MixedAmount
r, c
_) = Table Text Text WideBuilder
t
      where
        -- convert to table
        Table Header Text
lefthdrs Header Text
tophdrs [[WideBuilder]]
cells = ReportOpts
-> PeriodicReport DisplayName MixedAmount
-> Table Text Text WideBuilder
balanceReportAsTable ReportOpts
ropts1 PeriodicReport DisplayName MixedAmount
r
        -- tweak the layout
        t :: Table Text Text WideBuilder
t = Header Text
-> Header Text -> [[WideBuilder]] -> Table Text Text WideBuilder
forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
Table (Properties -> [Header Text] -> Header Text
forall h. Properties -> [Header h] -> Header h
Tab.Group Properties
Tab.SingleLine [Text -> Header Text
forall h. h -> Header h
Tab.Header Text
title1, Header Text
lefthdrs]) Header Text
tophdrs ([][WideBuilder] -> [[WideBuilder]] -> [[WideBuilder]]
forall a. a -> [a] -> [a]
:[[WideBuilder]]
cells)

-- | Render a compound balance report as CSV.
-- Subreports' CSV is concatenated, with the headings rows replaced by a
-- subreport title row, and an overall title row, one headings row, and an
-- optional overall totals row is added.
compoundBalanceReportAsCsv :: ReportOpts -> CompoundPeriodicReport DisplayName MixedAmount -> CSV
compoundBalanceReportAsCsv :: ReportOpts
-> CompoundPeriodicReport DisplayName MixedAmount -> [[Text]]
compoundBalanceReportAsCsv ReportOpts
ropts (CompoundPeriodicReport Text
title [DateSpan]
colspans [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
subreports PeriodicReportRow () MixedAmount
netrow) =
    [[Text]] -> [[Text]]
addtotals ([[Text]] -> [[Text]]) -> [[Text]] -> [[Text]]
forall a b. (a -> b) -> a -> b
$
      Text -> [Text]
forall {a}. IsString a => a -> [a]
padRow Text
title
      [Text] -> [[Text]] -> [[Text]]
forall a. a -> [a] -> [a]
: ( Text
"Account"
        Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: [Text
"Commodity" | ReportOpts -> Layout
layout_ ReportOpts
ropts 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 (ReportOpts -> BalanceAccumulation
balanceaccum_ ReportOpts
ropts) [DateSpan]
colspans) [DateSpan]
colspans
        [Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++ (if ReportOpts -> Bool
row_total_ ReportOpts
ropts then [Text
"Total"] else [])
        [Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++ (if ReportOpts -> Bool
average_ ReportOpts
ropts then [Text
"Average"] else [])
        )
      [Text] -> [[Text]] -> [[Text]]
forall a. a -> [a] -> [a]
: ((Text, PeriodicReport DisplayName MixedAmount, Bool) -> [[Text]])
-> [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
-> [[Text]]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (ReportOpts
-> (Text, PeriodicReport DisplayName MixedAmount, Bool) -> [[Text]]
forall {c}.
ReportOpts
-> (Text, PeriodicReport DisplayName MixedAmount, c) -> [[Text]]
subreportAsCsv ReportOpts
ropts) [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
subreports
  where
    -- | Add a subreport title row and drop the heading row.
    subreportAsCsv :: ReportOpts
-> (Text, PeriodicReport DisplayName MixedAmount, c) -> [[Text]]
subreportAsCsv ReportOpts
ropts1 (Text
subreporttitle, PeriodicReport DisplayName MixedAmount
multibalreport, c
_) =
      Text -> [Text]
forall {a}. IsString a => a -> [a]
padRow Text
subreporttitle [Text] -> [[Text]] -> [[Text]]
forall a. a -> [a] -> [a]
:
      [[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
tailDef [] (ReportOpts -> PeriodicReport DisplayName MixedAmount -> [[Text]]
multiBalanceReportAsCsv ReportOpts
ropts1 PeriodicReport DisplayName MixedAmount
multibalreport)
    padRow :: a -> [a]
padRow a
s = Int -> [a] -> [a]
forall a. Int -> [a] -> [a]
take Int
numcols ([a] -> [a]) -> [a] -> [a]
forall a b. (a -> b) -> a -> b
$ a
s a -> [a] -> [a]
forall a. a -> [a] -> [a]
: a -> [a]
forall a. a -> [a]
repeat a
""
      where
        numcols :: Int
numcols
          | [(Text, PeriodicReport DisplayName MixedAmount, Bool)] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
subreports = Int
1
          | Bool
otherwise =
            (Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+) (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ -- account name column
            (if ReportOpts -> Layout
layout_ ReportOpts
ropts Layout -> Layout -> Bool
forall a. Eq a => a -> a -> Bool
== Layout
LayoutBare then (Int
1Int -> Int -> Int
forall a. Num a => a -> a -> a
+) else Int -> Int
forall a. a -> a
id) (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$
            (if ReportOpts -> Bool
row_total_ ReportOpts
ropts then (Int
1Int -> Int -> Int
forall a. Num a => a -> a -> a
+) else Int -> Int
forall a. a -> a
id) (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$
            (if ReportOpts -> Bool
average_ ReportOpts
ropts then (Int
1Int -> Int -> Int
forall a. Num a => a -> a -> a
+) else Int -> Int
forall a. a -> a
id) (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$
            [Int] -> Int
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ -- depends on non-null subreports
            ((Text, PeriodicReport DisplayName MixedAmount, Bool) -> Int)
-> [(Text, PeriodicReport DisplayName MixedAmount, Bool)] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map ([DateSpan] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([DateSpan] -> Int)
-> ((Text, PeriodicReport DisplayName MixedAmount, Bool)
    -> [DateSpan])
-> (Text, PeriodicReport DisplayName MixedAmount, Bool)
-> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PeriodicReport DisplayName MixedAmount -> [DateSpan]
forall a b. PeriodicReport a b -> [DateSpan]
prDates (PeriodicReport DisplayName MixedAmount -> [DateSpan])
-> ((Text, PeriodicReport DisplayName MixedAmount, Bool)
    -> PeriodicReport DisplayName MixedAmount)
-> (Text, PeriodicReport DisplayName MixedAmount, Bool)
-> [DateSpan]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text, PeriodicReport DisplayName MixedAmount, Bool)
-> PeriodicReport DisplayName MixedAmount
forall {a} {b} {c}. (a, b, c) -> b
second3) [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
subreports
    addtotals :: [[Text]] -> [[Text]]
addtotals
      | ReportOpts -> Bool
no_total_ ReportOpts
ropts Bool -> Bool -> Bool
|| [(Text, PeriodicReport DisplayName MixedAmount, Bool)] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
subreports Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1 = [[Text]] -> [[Text]]
forall a. a -> a
id
      | Bool
otherwise = ([[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
++ ([Text] -> [Text]) -> [[Text]] -> [[Text]]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Text
"Net:" Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: ) (ReportOpts
-> [DateSpan] -> PeriodicReportRow () MixedAmount -> [[Text]]
forall a.
ReportOpts
-> [DateSpan] -> PeriodicReportRow a MixedAmount -> [[Text]]
multiBalanceRowAsCsvText ReportOpts
ropts [DateSpan]
colspans PeriodicReportRow () MixedAmount
netrow))

-- | Render a compound balance report as HTML.
compoundBalanceReportAsHtml :: ReportOpts -> CompoundPeriodicReport DisplayName MixedAmount -> Html ()
compoundBalanceReportAsHtml :: ReportOpts
-> CompoundPeriodicReport DisplayName MixedAmount
-> HtmlT Identity ()
compoundBalanceReportAsHtml ReportOpts
ropts CompoundPeriodicReport DisplayName MixedAmount
cbr =
  let
    CompoundPeriodicReport Text
title [DateSpan]
colspans [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
subreports PeriodicReportRow () MixedAmount
netrow = CompoundPeriodicReport DisplayName MixedAmount
cbr
    colspanattr :: Attribute
colspanattr = Text -> Attribute
colspan_ (Text -> Attribute) -> Text -> Attribute
forall a b. (a -> b) -> a -> b
$ [Char] -> Text
T.pack ([Char] -> Text) -> [Char] -> Text
forall a b. (a -> b) -> a -> b
$ Int -> [Char]
forall a. Show a => a -> [Char]
show (Int -> [Char]) -> Int -> [Char]
forall a b. (a -> b) -> a -> b
$ [Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [
      Int
1,
      [DateSpan] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [DateSpan]
colspans,
      if ReportOpts -> Bool
row_total_ ReportOpts
ropts then Int
1 else Int
0,
      if ReportOpts -> Bool
average_ ReportOpts
ropts then Int
1 else Int
0,
      if ReportOpts -> Layout
layout_ ReportOpts
ropts Layout -> Layout -> Bool
forall a. Eq a => a -> a -> Bool
== Layout
LayoutBare then Int
1 else Int
0
      ]
    leftattr :: Attribute
leftattr = Text -> Attribute
forall arg result. TermRaw arg result => arg -> result
style_ Text
"text-align:left"
    blankrow :: HtmlT Identity ()
blankrow = HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
tr_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ [Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
td_ [Attribute
colspanattr] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ [Char] -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => [Char] -> HtmlT m ()
toHtmlRaw ([Char]
"&nbsp;"::String)

    titlerows :: [HtmlT Identity ()]
titlerows =
      (HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
tr_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ [Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
th_ [Attribute
colspanattr, Attribute
leftattr] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
h2_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ Text -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => Text -> HtmlT m ()
toHtml Text
title)
      HtmlT Identity () -> [HtmlT Identity ()] -> [HtmlT Identity ()]
forall a. a -> [a] -> [a]
: [[Text] -> HtmlT Identity ()
thRow ([Text] -> HtmlT Identity ()) -> [Text] -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$
         Text
"" Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: [Text
"Commodity" | ReportOpts -> Layout
layout_ ReportOpts
ropts 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 (ReportOpts -> BalanceAccumulation
balanceaccum_ ReportOpts
ropts) [DateSpan]
colspans) [DateSpan]
colspans
         [Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++ (if ReportOpts -> Bool
row_total_ ReportOpts
ropts then [Text
"Total"] else [])
         [Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++ (if ReportOpts -> Bool
average_ ReportOpts
ropts then [Text
"Average"] else [])
        ]

    thRow :: [T.Text] -> Html ()
    thRow :: [Text] -> HtmlT Identity ()
thRow = HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
tr_ (HtmlT Identity () -> HtmlT Identity ())
-> ([Text] -> HtmlT Identity ()) -> [Text] -> HtmlT Identity ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [HtmlT Identity ()] -> HtmlT Identity ()
forall a. Monoid a => [a] -> a
mconcat ([HtmlT Identity ()] -> HtmlT Identity ())
-> ([Text] -> [HtmlT Identity ()]) -> [Text] -> HtmlT Identity ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text -> HtmlT Identity ()) -> [Text] -> [HtmlT Identity ()]
forall a b. (a -> b) -> [a] -> [b]
map (HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
th_ (HtmlT Identity () -> HtmlT Identity ())
-> (Text -> HtmlT Identity ()) -> Text -> HtmlT Identity ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => Text -> HtmlT m ()
toHtml)

    -- Make rows for a subreport: its title row, not the headings row,
    -- the data rows, any totals row, and a blank row for whitespace.
    subreportrows :: (T.Text, MultiBalanceReport, Bool) -> [Html ()]
    subreportrows :: (Text, PeriodicReport DisplayName MixedAmount, Bool)
-> [HtmlT Identity ()]
subreportrows (Text
subreporttitle, PeriodicReport DisplayName MixedAmount
mbr, Bool
_increasestotal) =
      let
        (HtmlT Identity ()
_,[HtmlT Identity ()]
bodyrows,[HtmlT Identity ()]
mtotalsrows) = ReportOpts
-> PeriodicReport DisplayName MixedAmount
-> (HtmlT Identity (), [HtmlT Identity ()], [HtmlT Identity ()])
multiBalanceReportHtmlRows ReportOpts
ropts PeriodicReport DisplayName MixedAmount
mbr
      in
           [HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
tr_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ [Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
th_ [Attribute
colspanattr, Attribute
leftattr] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ Text -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => Text -> HtmlT m ()
toHtml Text
subreporttitle]
        [HtmlT Identity ()] -> [HtmlT Identity ()] -> [HtmlT Identity ()]
forall a. [a] -> [a] -> [a]
++ [HtmlT Identity ()]
bodyrows
        [HtmlT Identity ()] -> [HtmlT Identity ()] -> [HtmlT Identity ()]
forall a. [a] -> [a] -> [a]
++ [HtmlT Identity ()]
mtotalsrows
        [HtmlT Identity ()] -> [HtmlT Identity ()] -> [HtmlT Identity ()]
forall a. [a] -> [a] -> [a]
++ [HtmlT Identity ()
blankrow]

    totalrows :: [HtmlT Identity ()]
totalrows | ReportOpts -> Bool
no_total_ ReportOpts
ropts Bool -> Bool -> Bool
|| [(Text, PeriodicReport DisplayName MixedAmount, Bool)] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
subreports Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1 = []
      | Bool
otherwise = ReportOpts -> [Text] -> HtmlT Identity ()
multiBalanceReportHtmlFootRow ReportOpts
ropts ([Text] -> HtmlT Identity ()) -> [[Text]] -> [HtmlT Identity ()]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ((Text
"Net:" Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
:) ([Text] -> [Text]) -> [[Text]] -> [[Text]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ReportOpts
-> [DateSpan] -> PeriodicReportRow () MixedAmount -> [[Text]]
forall a.
ReportOpts
-> [DateSpan] -> PeriodicReportRow a MixedAmount -> [[Text]]
multiBalanceRowAsCsvText ReportOpts
ropts [DateSpan]
colspans PeriodicReportRow () MixedAmount
netrow)
  in do
    Text -> HtmlT Identity ()
forall arg result. TermRaw arg result => arg -> result
style_ ([Text] -> Text
T.unlines [Text
""
      ,Text
"td { padding:0 0.5em; }"
      ,Text
"td:nth-child(1) { white-space:nowrap; }"
      ,Text
"tr:nth-child(even) td { background-color:#eee; }"
      ])
    [Attribute] -> HtmlT Identity ()
forall (m :: * -> *). Applicative m => [Attribute] -> HtmlT m ()
link_ [Text -> Attribute
rel_ Text
"stylesheet", Text -> Attribute
href_ Text
"hledger.css"]
    HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
table_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ [HtmlT Identity ()] -> HtmlT Identity ()
forall a. Monoid a => [a] -> a
mconcat ([HtmlT Identity ()] -> HtmlT Identity ())
-> [HtmlT Identity ()] -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$
         [HtmlT Identity ()]
titlerows
      [HtmlT Identity ()] -> [HtmlT Identity ()] -> [HtmlT Identity ()]
forall a. [a] -> [a] -> [a]
++ [HtmlT Identity ()
blankrow]
      [HtmlT Identity ()] -> [HtmlT Identity ()] -> [HtmlT Identity ()]
forall a. [a] -> [a] -> [a]
++ ((Text, PeriodicReport DisplayName MixedAmount, Bool)
 -> [HtmlT Identity ()])
-> [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
-> [HtmlT Identity ()]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Text, PeriodicReport DisplayName MixedAmount, Bool)
-> [HtmlT Identity ()]
subreportrows [(Text, PeriodicReport DisplayName MixedAmount, Bool)]
subreports
      [HtmlT Identity ()] -> [HtmlT Identity ()] -> [HtmlT Identity ()]
forall a. [a] -> [a] -> [a]
++ [HtmlT Identity ()]
totalrows