multibalanceReport: Move responsibility for determining displayed name in multiBalanceReportWith, not at point of consumption.
This commit is contained in:
parent
0dedcfbe15
commit
5f0918217a
@ -47,8 +47,8 @@ type BudgetAverage = Average
|
|||||||
|
|
||||||
-- | A budget report tracks expected and actual changes per account and subperiod.
|
-- | A budget report tracks expected and actual changes per account and subperiod.
|
||||||
type BudgetCell = (Maybe Change, Maybe BudgetGoal)
|
type BudgetCell = (Maybe Change, Maybe BudgetGoal)
|
||||||
type BudgetReport = PeriodicReport AccountName BudgetCell
|
type BudgetReport = PeriodicReport DisplayName BudgetCell
|
||||||
type BudgetReportRow = PeriodicReportRow AccountName BudgetCell
|
type BudgetReportRow = PeriodicReportRow DisplayName BudgetCell
|
||||||
|
|
||||||
-- | Calculate budget goals from all periodic transactions,
|
-- | Calculate budget goals from all periodic transactions,
|
||||||
-- actual balance changes from the regular transactions,
|
-- actual balance changes from the regular transactions,
|
||||||
@ -99,9 +99,9 @@ sortBudgetReport ropts j (PeriodicReport ps rows trow) = PeriodicReport ps sorte
|
|||||||
sortTreeBURByActualAmount :: [BudgetReportRow] -> [BudgetReportRow]
|
sortTreeBURByActualAmount :: [BudgetReportRow] -> [BudgetReportRow]
|
||||||
sortTreeBURByActualAmount rows = sortedrows
|
sortTreeBURByActualAmount rows = sortedrows
|
||||||
where
|
where
|
||||||
anamesandrows = [(prrName r, r) | r <- rows]
|
anamesandrows = [(prrFullName r, r) | r <- rows]
|
||||||
anames = map fst anamesandrows
|
anames = map fst anamesandrows
|
||||||
atotals = [(a, tot) | PeriodicReportRow a _ _ (tot,_) _ <- rows]
|
atotals = [(displayFull a, tot) | PeriodicReportRow a _ (tot,_) _ <- rows]
|
||||||
accounttree = accountTree "root" anames
|
accounttree = accountTree "root" anames
|
||||||
accounttreewithbals = mapAccounts setibalance accounttree
|
accounttreewithbals = mapAccounts setibalance accounttree
|
||||||
where
|
where
|
||||||
@ -124,8 +124,8 @@ sortBudgetReport ropts j (PeriodicReport ps rows trow) = PeriodicReport ps sorte
|
|||||||
-- <unbudgeted> remains at the top.
|
-- <unbudgeted> remains at the top.
|
||||||
sortByAccountDeclaration rows = sortedrows
|
sortByAccountDeclaration rows = sortedrows
|
||||||
where
|
where
|
||||||
(unbudgetedrow,rows') = partition ((=="<unbudgeted>") . prrName) rows
|
(unbudgetedrow,rows') = partition ((==unbudgetedAccountName) . prrFullName) rows
|
||||||
anamesandrows = [(prrName r, r) | r <- rows']
|
anamesandrows = [(prrFullName r, r) | r <- rows']
|
||||||
anames = map fst anamesandrows
|
anames = map fst anamesandrows
|
||||||
sortedanames = sortAccountNamesByDeclaration j (tree_ ropts) anames
|
sortedanames = sortAccountNamesByDeclaration j (tree_ ropts) anames
|
||||||
sortedrows = unbudgetedrow ++ sortAccountItemsLike sortedanames anamesandrows
|
sortedrows = unbudgetedrow ++ sortAccountItemsLike sortedanames anamesandrows
|
||||||
@ -189,17 +189,17 @@ budgetRollUp budgetedaccts showunbudgeted j = j { jtxns = remapTxn <$> jtxns j }
|
|||||||
--
|
--
|
||||||
combineBudgetAndActual :: MultiBalanceReport -> MultiBalanceReport -> BudgetReport
|
combineBudgetAndActual :: MultiBalanceReport -> MultiBalanceReport -> BudgetReport
|
||||||
combineBudgetAndActual
|
combineBudgetAndActual
|
||||||
(PeriodicReport budgetperiods budgetrows (PeriodicReportRow _ _ budgettots budgetgrandtot budgetgrandavg))
|
(PeriodicReport budgetperiods budgetrows (PeriodicReportRow _ budgettots budgetgrandtot budgetgrandavg))
|
||||||
(PeriodicReport actualperiods actualrows (PeriodicReportRow _ _ actualtots actualgrandtot actualgrandavg)) =
|
(PeriodicReport actualperiods actualrows (PeriodicReportRow _ actualtots actualgrandtot actualgrandavg)) =
|
||||||
PeriodicReport periods rows totalrow
|
PeriodicReport periods rows totalrow
|
||||||
where
|
where
|
||||||
periods = nubSort . filter (/= nulldatespan) $ budgetperiods ++ actualperiods
|
periods = nubSort . filter (/= nulldatespan) $ budgetperiods ++ actualperiods
|
||||||
|
|
||||||
-- first, combine any corresponding budget goals with actual changes
|
-- first, combine any corresponding budget goals with actual changes
|
||||||
rows1 =
|
rows1 =
|
||||||
[ PeriodicReportRow acct treeindent amtandgoals totamtandgoal avgamtandgoal
|
[ PeriodicReportRow acct amtandgoals totamtandgoal avgamtandgoal
|
||||||
| PeriodicReportRow acct treeindent actualamts actualtot actualavg <- actualrows
|
| PeriodicReportRow acct actualamts actualtot actualavg <- actualrows
|
||||||
, let mbudgetgoals = Map.lookup acct budgetGoalsByAcct :: Maybe ([BudgetGoal], BudgetTotal, BudgetAverage)
|
, let mbudgetgoals = Map.lookup (displayFull acct) budgetGoalsByAcct :: Maybe ([BudgetGoal], BudgetTotal, BudgetAverage)
|
||||||
, let budgetmamts = maybe (replicate (length periods) Nothing) (map Just . first3) mbudgetgoals :: [Maybe BudgetGoal]
|
, let budgetmamts = maybe (replicate (length periods) Nothing) (map Just . first3) mbudgetgoals :: [Maybe BudgetGoal]
|
||||||
, let mbudgettot = second3 <$> mbudgetgoals :: Maybe BudgetTotal
|
, let mbudgettot = second3 <$> mbudgetgoals :: Maybe BudgetTotal
|
||||||
, let mbudgetavg = third3 <$> mbudgetgoals :: Maybe BudgetAverage
|
, let mbudgetavg = third3 <$> mbudgetgoals :: Maybe BudgetAverage
|
||||||
@ -211,14 +211,14 @@ combineBudgetAndActual
|
|||||||
]
|
]
|
||||||
where
|
where
|
||||||
budgetGoalsByAcct :: Map AccountName ([BudgetGoal], BudgetTotal, BudgetAverage) =
|
budgetGoalsByAcct :: Map AccountName ([BudgetGoal], BudgetTotal, BudgetAverage) =
|
||||||
Map.fromList [ (acct, (amts, tot, avg))
|
Map.fromList [ (displayFull acct, (amts, tot, avg))
|
||||||
| PeriodicReportRow acct _ amts tot avg <- budgetrows ]
|
| PeriodicReportRow acct amts tot avg <- budgetrows ]
|
||||||
|
|
||||||
-- next, make rows for budget goals with no actual changes
|
-- next, make rows for budget goals with no actual changes
|
||||||
rows2 =
|
rows2 =
|
||||||
[ PeriodicReportRow acct treeindent amtandgoals totamtandgoal avgamtandgoal
|
[ PeriodicReportRow acct amtandgoals totamtandgoal avgamtandgoal
|
||||||
| PeriodicReportRow acct treeindent budgetgoals budgettot budgetavg <- budgetrows
|
| PeriodicReportRow acct budgetgoals budgettot budgetavg <- budgetrows
|
||||||
, acct `notElem` map prrName rows1
|
, displayFull acct `notElem` map prrFullName rows1
|
||||||
, let acctBudgetByPeriod = Map.fromList $ zip budgetperiods budgetgoals :: Map DateSpan BudgetGoal
|
, let acctBudgetByPeriod = Map.fromList $ zip budgetperiods budgetgoals :: Map DateSpan BudgetGoal
|
||||||
, let amtandgoals = [ (Nothing, Map.lookup p acctBudgetByPeriod) | p <- periods ] :: [BudgetCell]
|
, let amtandgoals = [ (Nothing, Map.lookup p acctBudgetByPeriod) | p <- periods ] :: [BudgetCell]
|
||||||
, let totamtandgoal = (Nothing, Just budgettot)
|
, let totamtandgoal = (Nothing, Just budgettot)
|
||||||
@ -230,10 +230,10 @@ combineBudgetAndActual
|
|||||||
-- TODO: respect --sort-amount
|
-- TODO: respect --sort-amount
|
||||||
-- TODO: add --sort-budget to sort by budget goal amount
|
-- TODO: add --sort-budget to sort by budget goal amount
|
||||||
rows :: [BudgetReportRow] =
|
rows :: [BudgetReportRow] =
|
||||||
sortOn prrName $ rows1 ++ rows2
|
sortOn prrFullName $ rows1 ++ rows2
|
||||||
|
|
||||||
-- TODO: grand total & average shows 0% when there are no actual amounts, inconsistent with other cells
|
-- TODO: grand total & average shows 0% when there are no actual amounts, inconsistent with other cells
|
||||||
totalrow = PeriodicReportRow () 0
|
totalrow = PeriodicReportRow ()
|
||||||
[ (Map.lookup p totActualByPeriod, Map.lookup p totBudgetByPeriod) | p <- periods ]
|
[ (Map.lookup p totActualByPeriod, Map.lookup p totBudgetByPeriod) | p <- periods ]
|
||||||
( Just actualgrandtot, Just budgetgrandtot )
|
( Just actualgrandtot, Just budgetgrandtot )
|
||||||
( Just actualgrandavg, Just budgetgrandavg )
|
( Just actualgrandavg, Just budgetgrandavg )
|
||||||
@ -311,7 +311,7 @@ budgetReportAsText ropts@ReportOpts{..} budgetr =
|
|||||||
budgetReportAsTable :: ReportOpts -> BudgetReport -> Table String String (Maybe MixedAmount, Maybe MixedAmount)
|
budgetReportAsTable :: ReportOpts -> BudgetReport -> Table String String (Maybe MixedAmount, Maybe MixedAmount)
|
||||||
budgetReportAsTable
|
budgetReportAsTable
|
||||||
ropts
|
ropts
|
||||||
(PeriodicReport periods rows (PeriodicReportRow _ _ coltots grandtot grandavg)) =
|
(PeriodicReport periods rows (PeriodicReportRow _ coltots grandtot grandavg)) =
|
||||||
addtotalrow $
|
addtotalrow $
|
||||||
Table
|
Table
|
||||||
(T.Group NoLine $ map Header accts)
|
(T.Group NoLine $ map Header accts)
|
||||||
@ -322,10 +322,13 @@ budgetReportAsTable
|
|||||||
++ [" Total" | row_total_ ropts]
|
++ [" Total" | row_total_ ropts]
|
||||||
++ ["Average" | average_ ropts]
|
++ ["Average" | average_ ropts]
|
||||||
accts = map renderacct rows
|
accts = map renderacct rows
|
||||||
renderacct (PeriodicReportRow a i _ _ _)
|
-- FIXME. Have to check explicitly for which to render here, since
|
||||||
| tree_ ropts = replicate ((i-1)*2) ' ' ++ T.unpack (accountLeafName a)
|
-- budgetReport sets accountlistmode to ALTree. Find a principled way to do
|
||||||
| otherwise = T.unpack $ maybeAccountNameDrop ropts a
|
-- this.
|
||||||
rowvals (PeriodicReportRow _ _ as rowtot rowavg) =
|
renderacct row
|
||||||
|
| tree_ ropts = replicate ((prrDepth row - 1)*2) ' ' ++ T.unpack (prrDisplayName row)
|
||||||
|
| otherwise = T.unpack . maybeAccountNameDrop ropts $ prrFullName row
|
||||||
|
rowvals (PeriodicReportRow _ as rowtot rowavg) =
|
||||||
as ++ [rowtot | row_total_ ropts] ++ [rowavg | average_ ropts]
|
as ++ [rowtot | row_total_ ropts] ++ [rowavg | average_ ropts]
|
||||||
addtotalrow
|
addtotalrow
|
||||||
| no_total_ ropts = id
|
| no_total_ ropts = id
|
||||||
|
|||||||
@ -50,9 +50,7 @@ import Hledger.Reports.ReportTypes
|
|||||||
--
|
--
|
||||||
-- 2. a list of rows, each containing:
|
-- 2. a list of rows, each containing:
|
||||||
--
|
--
|
||||||
-- * the full account name
|
-- * the full account name, display name, and display depth
|
||||||
--
|
|
||||||
-- * the account's depth
|
|
||||||
--
|
--
|
||||||
-- * A list of amounts, one for each column.
|
-- * A list of amounts, one for each column.
|
||||||
--
|
--
|
||||||
@ -63,8 +61,8 @@ import Hledger.Reports.ReportTypes
|
|||||||
-- 3. the column totals, and the overall grand total (or zero for
|
-- 3. the column totals, and the overall grand total (or zero for
|
||||||
-- cumulative/historical reports) and grand average.
|
-- cumulative/historical reports) and grand average.
|
||||||
|
|
||||||
type MultiBalanceReport = PeriodicReport AccountName MixedAmount
|
type MultiBalanceReport = PeriodicReport DisplayName MixedAmount
|
||||||
type MultiBalanceReportRow = PeriodicReportRow AccountName MixedAmount
|
type MultiBalanceReportRow = PeriodicReportRow DisplayName MixedAmount
|
||||||
|
|
||||||
-- type alias just to remind us which AccountNames might be depth-clipped, below.
|
-- type alias just to remind us which AccountNames might be depth-clipped, below.
|
||||||
type ClippedAccountName = AccountName
|
type ClippedAccountName = AccountName
|
||||||
@ -78,7 +76,7 @@ type ClippedAccountName = AccountName
|
|||||||
-- command (in multiperiod mode) and (via multiBalanceReport') by the bs/cf/is commands.
|
-- command (in multiperiod mode) and (via multiBalanceReport') by the bs/cf/is commands.
|
||||||
multiBalanceReport :: Day -> ReportOpts -> Journal -> MultiBalanceReport
|
multiBalanceReport :: Day -> ReportOpts -> Journal -> MultiBalanceReport
|
||||||
multiBalanceReport today ropts j =
|
multiBalanceReport today ropts j =
|
||||||
multiBalanceReportWith ropts q j (journalPriceOracle infer j)
|
multiBalanceReportWith ropts q j (journalPriceOracle infer j)
|
||||||
where
|
where
|
||||||
q = queryFromOpts today ropts
|
q = queryFromOpts today ropts
|
||||||
infer = infer_value_ ropts
|
infer = infer_value_ ropts
|
||||||
@ -93,46 +91,55 @@ multiBalanceReportWith :: ReportOpts -> Query -> Journal -> PriceOracle -> Multi
|
|||||||
multiBalanceReportWith ropts@ReportOpts{..} q j priceoracle = report
|
multiBalanceReportWith ropts@ReportOpts{..} q j priceoracle = report
|
||||||
where
|
where
|
||||||
-- Queries, report/column dates.
|
-- Queries, report/column dates.
|
||||||
reportspan = dbg "reportspan" $ calculateReportSpan ropts q j
|
ropts' = dbg "ropts'" $ setDefaultAccountListMode ALFlat ropts
|
||||||
reportq = dbg "reportq" $ makeReportQuery ropts reportspan q
|
reportspan = dbg "reportspan" $ calculateReportSpan ropts' q j
|
||||||
|
reportq = dbg "reportq" $ makeReportQuery ropts' reportspan q
|
||||||
|
|
||||||
-- The matched accounts with a starting balance. All of these should appear
|
-- The matched accounts with a starting balance. All of these should appear
|
||||||
-- in the report, even if they have no postings during the report period.
|
-- in the report, even if they have no postings during the report period.
|
||||||
startbals = dbg' "startbals" $ startingBalances ropts reportq j reportspan
|
startbals = dbg' "startbals" $ startingBalances ropts' reportq j reportspan
|
||||||
|
|
||||||
-- Postings matching the query within the report period.
|
-- Postings matching the query within the report period.
|
||||||
ps :: [(Posting, Day)] = dbg'' "ps" $ getPostings ropts reportq j
|
ps :: [(Posting, Day)] = dbg'' "ps" $ getPostings ropts' reportq j
|
||||||
days = map snd ps
|
days = map snd ps
|
||||||
|
|
||||||
-- The date spans to be included as report columns.
|
-- The date spans to be included as report columns.
|
||||||
colspans = dbg "colspans" $ calculateColSpans ropts reportspan days
|
colspans = dbg "colspans" $ calculateColSpans ropts' reportspan days
|
||||||
|
|
||||||
-- Group postings into their columns.
|
-- Group postings into their columns.
|
||||||
colps = dbg'' "colps" $ calculateColumns colspans ps
|
colps = dbg'' "colps" $ calculateColumns colspans ps
|
||||||
|
|
||||||
-- Each account's balance changes across all columns.
|
-- Each account's balance changes across all columns.
|
||||||
acctchanges = dbg'' "acctchanges" $ calculateAccountChanges ropts q startbals colps
|
acctchanges = dbg'' "acctchanges" $ calculateAccountChanges ropts' q startbals colps
|
||||||
|
|
||||||
-- Process changes into normal, cumulative, or historical amounts, plus value them
|
-- Process changes into normal, cumulative, or historical amounts, plus value them
|
||||||
accumvalued = dbg'' "accumvalued" $ accumValueAmounts ropts j priceoracle colspans startbals acctchanges
|
accumvalued = dbg'' "accumvalued" $ accumValueAmounts ropts' j priceoracle colspans startbals acctchanges
|
||||||
|
|
||||||
-- All account names that will be displayed, possibly depth-clipped.
|
-- All account names that will be displayed, possibly depth-clipped.
|
||||||
displayaccts = dbg'' "displayaccts" $ displayedAccounts ropts q accumvalued
|
displayaccts = dbg'' "displayaccts" $ displayedAccounts ropts' q accumvalued
|
||||||
|
|
||||||
-- All the rows of the report.
|
-- All the rows of the report.
|
||||||
rows = dbg'' "rows" $ buildReportRows ropts reportq accumvalued
|
rows = dbg'' "rows" $ buildReportRows ropts' reportq accumvalued
|
||||||
|
|
||||||
-- Sorted report rows.
|
-- Sorted report rows.
|
||||||
sortedrows = dbg' "sortedrows" $ sortRows ropts j rows
|
sortedrows = dbg' "sortedrows" $ sortRows ropts' j rows
|
||||||
|
|
||||||
-- Calculate column totals
|
-- Calculate column totals
|
||||||
totalsrow = dbg' "totalsrow" $ calculateTotalsRow ropts displayaccts sortedrows
|
totalsrow = dbg' "totalsrow" $ calculateTotalsRow ropts' displayaccts sortedrows
|
||||||
|
|
||||||
-- Postprocess the report, negating balances and taking percentages if needed
|
-- Postprocess the report, negating balances and taking percentages if needed
|
||||||
report = dbg' "report" . postprocessReport ropts $
|
report = dbg' "report" . postprocessReport ropts' $
|
||||||
PeriodicReport colspans sortedrows totalsrow
|
PeriodicReport colspans sortedrows totalsrow
|
||||||
|
|
||||||
|
|
||||||
|
-- | Calculate the span of the report to be generated.
|
||||||
|
setDefaultAccountListMode :: AccountListMode -> ReportOpts -> ReportOpts
|
||||||
|
setDefaultAccountListMode def ropts = ropts{accountlistmode_=mode}
|
||||||
|
where
|
||||||
|
mode = case accountlistmode_ ropts of
|
||||||
|
ALDefault -> def
|
||||||
|
a -> a
|
||||||
|
|
||||||
-- | Calculate the span of the report to be generated.
|
-- | Calculate the span of the report to be generated.
|
||||||
calculateReportSpan :: ReportOpts -> Query -> Journal -> DateSpan
|
calculateReportSpan :: ReportOpts -> Query -> Journal -> DateSpan
|
||||||
calculateReportSpan ropts q j = reportspan
|
calculateReportSpan ropts q j = reportspan
|
||||||
@ -312,7 +319,7 @@ buildReportRows :: ReportOpts -> Query
|
|||||||
-> HashMap AccountName [Account]
|
-> HashMap AccountName [Account]
|
||||||
-> [MultiBalanceReportRow]
|
-> [MultiBalanceReportRow]
|
||||||
buildReportRows ropts q acctvalues =
|
buildReportRows ropts q acctvalues =
|
||||||
[ PeriodicReportRow a (accountNameLevel a) rowbals rowtot rowavg
|
[ PeriodicReportRow (name a) rowbals rowtot rowavg
|
||||||
| (a,accts) <- HM.toList acctvalues
|
| (a,accts) <- HM.toList acctvalues
|
||||||
, let rowbals = map balance accts
|
, let rowbals = map balance accts
|
||||||
-- The total and average for the row.
|
-- The total and average for the row.
|
||||||
@ -323,6 +330,7 @@ buildReportRows ropts q acctvalues =
|
|||||||
, empty_ ropts || queryDepth q == 0 || any (not . mixedAmountLooksZero) rowbals -- TODO: Remove this eventually, to be handled elswhere
|
, empty_ ropts || queryDepth q == 0 || any (not . mixedAmountLooksZero) rowbals -- TODO: Remove this eventually, to be handled elswhere
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
|
name = if tree_ ropts then treeDisplayName else flatDisplayName
|
||||||
balance = if tree_ ropts then aibalance else aebalance
|
balance = if tree_ ropts then aibalance else aebalance
|
||||||
|
|
||||||
-- | Calculate accounts which are to be displayed in the report, as well as
|
-- | Calculate accounts which are to be displayed in the report, as well as
|
||||||
@ -363,9 +371,9 @@ sortRows ropts j
|
|||||||
sortTreeMBRByAmount :: [MultiBalanceReportRow] -> [MultiBalanceReportRow]
|
sortTreeMBRByAmount :: [MultiBalanceReportRow] -> [MultiBalanceReportRow]
|
||||||
sortTreeMBRByAmount rows = sortedrows
|
sortTreeMBRByAmount rows = sortedrows
|
||||||
where
|
where
|
||||||
anamesandrows = [(prrName r, r) | r <- rows]
|
anamesandrows = [(prrFullName r, r) | r <- rows]
|
||||||
anames = map fst anamesandrows
|
anames = map fst anamesandrows
|
||||||
atotals = [(prrName r, prrTotal r) | r <- rows]
|
atotals = [(prrFullName r, prrTotal r) | r <- rows]
|
||||||
accounttree = accountTree "root" anames
|
accounttree = accountTree "root" anames
|
||||||
accounttreewithbals = mapAccounts setibalance accounttree
|
accounttreewithbals = mapAccounts setibalance accounttree
|
||||||
where
|
where
|
||||||
@ -383,7 +391,7 @@ sortRows ropts j
|
|||||||
-- Sort the report rows by account declaration order then account name.
|
-- Sort the report rows by account declaration order then account name.
|
||||||
sortMBRByAccountDeclaration rows = sortedrows
|
sortMBRByAccountDeclaration rows = sortedrows
|
||||||
where
|
where
|
||||||
anamesandrows = [(prrName r, r) | r <- rows]
|
anamesandrows = [(prrFullName r, r) | r <- rows]
|
||||||
anames = map fst anamesandrows
|
anames = map fst anamesandrows
|
||||||
sortedanames = sortAccountNamesByDeclaration j (tree_ ropts) anames
|
sortedanames = sortAccountNamesByDeclaration j (tree_ ropts) anames
|
||||||
sortedrows = sortAccountItemsLike sortedanames anamesandrows
|
sortedrows = sortAccountItemsLike sortedanames anamesandrows
|
||||||
@ -394,13 +402,13 @@ sortRows ropts j
|
|||||||
calculateTotalsRow :: ReportOpts -> HashMap ClippedAccountName (ClippedAccountName, Int)
|
calculateTotalsRow :: ReportOpts -> HashMap ClippedAccountName (ClippedAccountName, Int)
|
||||||
-> [MultiBalanceReportRow] -> PeriodicReportRow () MixedAmount
|
-> [MultiBalanceReportRow] -> PeriodicReportRow () MixedAmount
|
||||||
calculateTotalsRow ropts displayaccts rows =
|
calculateTotalsRow ropts displayaccts rows =
|
||||||
PeriodicReportRow () 0 coltotals grandtotal grandaverage
|
PeriodicReportRow () coltotals grandtotal grandaverage
|
||||||
where
|
where
|
||||||
highestlevelaccts = HM.filterWithKey (\a _ -> isHighest a) displayaccts
|
highestlevelaccts = HM.filterWithKey (\a _ -> isHighest a) displayaccts
|
||||||
where isHighest = not . any (`HM.member` displayaccts) . init . expandAccountName
|
where isHighest = not . any (`HM.member` displayaccts) . init . expandAccountName
|
||||||
|
|
||||||
colamts = transpose . map prrAmounts $ filter isHighest rows
|
colamts = transpose . map prrAmounts $ filter isHighest rows
|
||||||
where isHighest row = not (tree_ ropts) || prrName row `HM.member` highestlevelaccts
|
where isHighest row = not (tree_ ropts) || prrFullName row `HM.member` highestlevelaccts
|
||||||
|
|
||||||
-- TODO: If colamts is null, then this is empty. Do we want it to be a full
|
-- TODO: If colamts is null, then this is empty. Do we want it to be a full
|
||||||
-- column of zeros?
|
-- column of zeros?
|
||||||
@ -418,8 +426,8 @@ postprocessReport ropts (PeriodicReport spans rows totalrow) =
|
|||||||
where
|
where
|
||||||
maybeInvert = if invert_ ropts then prNegate else id
|
maybeInvert = if invert_ ropts then prNegate else id
|
||||||
percentage = if not (percent_ ropts) then id else \case
|
percentage = if not (percent_ ropts) then id else \case
|
||||||
PeriodicReportRow name d rowvals rowtotal rowavg ->
|
PeriodicReportRow name rowvals rowtotal rowavg ->
|
||||||
PeriodicReportRow name d
|
PeriodicReportRow name
|
||||||
(zipWith perdivide rowvals $ prrAmounts totalrow)
|
(zipWith perdivide rowvals $ prrAmounts totalrow)
|
||||||
(perdivide rowtotal $ prrTotal totalrow)
|
(perdivide rowtotal $ prrTotal totalrow)
|
||||||
(perdivide rowavg $ prrAverage totalrow)
|
(perdivide rowavg $ prrAverage totalrow)
|
||||||
@ -431,16 +439,17 @@ postprocessReport ropts (PeriodicReport spans rows totalrow) =
|
|||||||
-- (see ReportOpts and CompoundBalanceCommand).
|
-- (see ReportOpts and CompoundBalanceCommand).
|
||||||
balanceReportFromMultiBalanceReport :: ReportOpts -> Query -> Journal
|
balanceReportFromMultiBalanceReport :: ReportOpts -> Query -> Journal
|
||||||
-> ([(AccountName, AccountName, Int, MixedAmount)], MixedAmount)
|
-> ([(AccountName, AccountName, Int, MixedAmount)], MixedAmount)
|
||||||
balanceReportFromMultiBalanceReport opts q j = (rows', total)
|
balanceReportFromMultiBalanceReport ropts q j = (rows', total)
|
||||||
where
|
where
|
||||||
PeriodicReport _ rows (PeriodicReportRow _ _ totals _ _) =
|
PeriodicReport _ rows (PeriodicReportRow _ totals _ _) =
|
||||||
multiBalanceReportWith opts q j (journalPriceOracle (infer_value_ opts) j)
|
multiBalanceReportWith ropts q j (journalPriceOracle (infer_value_ ropts) j)
|
||||||
rows' = [( a
|
rows' = [( displayFull a
|
||||||
, if flat_ opts then a else accountLeafName a -- BalanceReport expects full account name here with --flat
|
, leafName a
|
||||||
, if tree_ opts then d-1 else 0 -- BalanceReport uses 0-based account depths
|
, if tree_ ropts then displayDepth a - 1 else 0 -- BalanceReport uses 0-based account depths
|
||||||
, headDef nullmixedamt amts -- 0 columns is illegal, should not happen, return zeroes if it does
|
, headDef nullmixedamt amts -- 0 columns is illegal, should not happen, return zeroes if it does
|
||||||
) | PeriodicReportRow a d amts _ _ <- rows]
|
) | PeriodicReportRow a amts _ _ <- rows]
|
||||||
total = headDef nullmixedamt totals
|
total = headDef nullmixedamt totals
|
||||||
|
leafName = if flat_ ropts then displayFull else displayName -- BalanceReport expects full account name here with --flat
|
||||||
|
|
||||||
|
|
||||||
-- | Transpose a Map of HashMaps to a HashMap of Maps.
|
-- | Transpose a Map of HashMaps to a HashMap of Maps.
|
||||||
@ -519,8 +528,8 @@ tests_MultiBalanceReport = tests "MultiBalanceReport" [
|
|||||||
(opts,journal) `gives` r = do
|
(opts,journal) `gives` r = do
|
||||||
let (eitems, etotal) = r
|
let (eitems, etotal) = r
|
||||||
(PeriodicReport _ aitems atotal) = multiBalanceReport nulldate opts journal
|
(PeriodicReport _ aitems atotal) = multiBalanceReport nulldate opts journal
|
||||||
showw (PeriodicReportRow acct indent lAmt amt amt')
|
showw (PeriodicReportRow a lAmt amt amt')
|
||||||
= (acct, accountLeafName acct, indent, map showMixedAmountDebug lAmt, showMixedAmountDebug amt, showMixedAmountDebug amt')
|
= (displayFull a, displayName a, displayDepth a, map showMixedAmountDebug lAmt, showMixedAmountDebug amt, showMixedAmountDebug amt')
|
||||||
(map showw aitems) @?= (map showw eitems)
|
(map showw aitems) @?= (map showw eitems)
|
||||||
showMixedAmountDebug (prrTotal atotal) @?= showMixedAmountDebug etotal -- we only check the sum of the totals
|
showMixedAmountDebug (prrTotal atotal) @?= showMixedAmountDebug etotal -- we only check the sum of the totals
|
||||||
in
|
in
|
||||||
@ -531,8 +540,8 @@ tests_MultiBalanceReport = tests "MultiBalanceReport" [
|
|||||||
,test "with -H on a populated period" $
|
,test "with -H on a populated period" $
|
||||||
(defreportopts{period_= PeriodBetween (fromGregorian 2008 1 1) (fromGregorian 2008 1 2), balancetype_=HistoricalBalance}, samplejournal) `gives`
|
(defreportopts{period_= PeriodBetween (fromGregorian 2008 1 1) (fromGregorian 2008 1 2), balancetype_=HistoricalBalance}, samplejournal) `gives`
|
||||||
(
|
(
|
||||||
[ PeriodicReportRow "assets:bank:checking" 3 [mamountp' "$1.00"] (Mixed [nullamt]) (Mixed [amt0 {aquantity=1}])
|
[ PeriodicReportRow (flatDisplayName "assets:bank:checking") [mamountp' "$1.00"] (Mixed [nullamt]) (Mixed [amt0 {aquantity=1}])
|
||||||
, PeriodicReportRow "income:salary" 2 [mamountp' "$-1.00"] (Mixed [nullamt]) (Mixed [amt0 {aquantity=(-1)}])
|
, PeriodicReportRow (flatDisplayName "income:salary") [mamountp' "$-1.00"] (Mixed [nullamt]) (Mixed [amt0 {aquantity=(-1)}])
|
||||||
],
|
],
|
||||||
Mixed [nullamt])
|
Mixed [nullamt])
|
||||||
|
|
||||||
|
|||||||
@ -17,10 +17,22 @@ module Hledger.Reports.ReportTypes
|
|||||||
, periodicReportSpan
|
, periodicReportSpan
|
||||||
, prNegate
|
, prNegate
|
||||||
, prNormaliseSign
|
, prNormaliseSign
|
||||||
|
|
||||||
|
, prMapName
|
||||||
|
, prMapMaybeName
|
||||||
|
|
||||||
|
, DisplayName(..)
|
||||||
|
, flatDisplayName
|
||||||
|
, treeDisplayName
|
||||||
|
|
||||||
|
, prrFullName
|
||||||
|
, prrDisplayName
|
||||||
|
, prrDepth
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import Data.Decimal
|
import Data.Decimal
|
||||||
|
import Data.Maybe (mapMaybe)
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Hledger.Data
|
import Hledger.Data
|
||||||
|
|
||||||
@ -72,7 +84,6 @@ data PeriodicReport a b =
|
|||||||
data PeriodicReportRow a b =
|
data PeriodicReportRow a b =
|
||||||
PeriodicReportRow
|
PeriodicReportRow
|
||||||
{ prrName :: a -- An account name.
|
{ prrName :: a -- An account name.
|
||||||
, prrDepth :: Int -- Indent level for displaying this account name in tree mode. 0, 1, 2...
|
|
||||||
, prrAmounts :: [b] -- The data value for each subperiod.
|
, prrAmounts :: [b] -- The data value for each subperiod.
|
||||||
, prrTotal :: b -- The total of this row's values.
|
, prrTotal :: b -- The total of this row's values.
|
||||||
, prrAverage :: b -- The average of this row's values.
|
, prrAverage :: b -- The average of this row's values.
|
||||||
@ -94,5 +105,57 @@ prNegate :: Num b => PeriodicReport a b -> PeriodicReport a b
|
|||||||
prNegate (PeriodicReport colspans rows totalsrow) =
|
prNegate (PeriodicReport colspans rows totalsrow) =
|
||||||
PeriodicReport colspans (map rowNegate rows) (rowNegate totalsrow)
|
PeriodicReport colspans (map rowNegate rows) (rowNegate totalsrow)
|
||||||
where
|
where
|
||||||
rowNegate (PeriodicReportRow name indent amts tot avg) =
|
rowNegate (PeriodicReportRow name amts tot avg) =
|
||||||
PeriodicReportRow name indent (map negate amts) (-tot) (-avg)
|
PeriodicReportRow name (map negate amts) (-tot) (-avg)
|
||||||
|
|
||||||
|
-- | Map a function over the row names.
|
||||||
|
prMapName :: (a -> b) -> PeriodicReport a c -> PeriodicReport b c
|
||||||
|
prMapName f report = report{prRows = map (prrMapName f) $ prRows report}
|
||||||
|
|
||||||
|
-- | Map a function over the row names, possibly discarding some.
|
||||||
|
prMapMaybeName :: (a -> Maybe b) -> PeriodicReport a c -> PeriodicReport b c
|
||||||
|
prMapMaybeName f report = report{prRows = mapMaybe (prrMapMaybeName f) $ prRows report}
|
||||||
|
|
||||||
|
-- | Map a function over the row names of the PeriodicReportRow.
|
||||||
|
prrMapName :: (a -> b) -> PeriodicReportRow a c -> PeriodicReportRow b c
|
||||||
|
prrMapName f row = row{prrName = f $ prrName row}
|
||||||
|
|
||||||
|
-- | Map maybe a function over the row names of the PeriodicReportRow.
|
||||||
|
prrMapMaybeName :: (a -> Maybe b) -> PeriodicReportRow a c -> Maybe (PeriodicReportRow b c)
|
||||||
|
prrMapMaybeName f row = case f $ prrName row of
|
||||||
|
Nothing -> Nothing
|
||||||
|
Just a -> Just row{prrName = a}
|
||||||
|
|
||||||
|
|
||||||
|
-- | A full name, display name, and depth for an account.
|
||||||
|
data DisplayName = DisplayName
|
||||||
|
{ displayFull :: AccountName
|
||||||
|
, displayName :: AccountName
|
||||||
|
, displayDepth :: Int
|
||||||
|
} deriving (Show, Eq, Ord)
|
||||||
|
|
||||||
|
instance ToJSON DisplayName where
|
||||||
|
toJSON = toJSON . displayFull
|
||||||
|
toEncoding = toEncoding . displayFull
|
||||||
|
|
||||||
|
-- | Construct a flat display name, where the full name is also displayed at
|
||||||
|
-- depth 0
|
||||||
|
flatDisplayName :: AccountName -> DisplayName
|
||||||
|
flatDisplayName a = DisplayName a a 0
|
||||||
|
|
||||||
|
-- | Construct a tree display name, where only the leaf is displayed at its
|
||||||
|
-- given depth
|
||||||
|
treeDisplayName :: AccountName -> DisplayName
|
||||||
|
treeDisplayName a = DisplayName a (accountLeafName a) (accountNameLevel a)
|
||||||
|
-- | Get the full, canonical, name of a PeriodicReportRow tagged by a
|
||||||
|
-- DisplayName.
|
||||||
|
prrFullName :: PeriodicReportRow DisplayName a -> AccountName
|
||||||
|
prrFullName = displayFull . prrName
|
||||||
|
|
||||||
|
-- | Get the display name of a PeriodicReportRow tagged by a DisplayName.
|
||||||
|
prrDisplayName :: PeriodicReportRow DisplayName a -> AccountName
|
||||||
|
prrDisplayName = displayName . prrName
|
||||||
|
|
||||||
|
-- | Get the display depth of a PeriodicReportRow tagged by a DisplayName.
|
||||||
|
prrDepth :: PeriodicReportRow DisplayName a -> Int
|
||||||
|
prrDepth = displayDepth . prrName
|
||||||
|
|||||||
@ -463,18 +463,18 @@ renderComponent1 opts (acctname, depth, total) (FormatField ljust min max field)
|
|||||||
-- and will include the final totals row unless --no-total is set.
|
-- and will include the final totals row unless --no-total is set.
|
||||||
multiBalanceReportAsCsv :: ReportOpts -> MultiBalanceReport -> CSV
|
multiBalanceReportAsCsv :: ReportOpts -> MultiBalanceReport -> CSV
|
||||||
multiBalanceReportAsCsv opts@ReportOpts{average_, row_total_}
|
multiBalanceReportAsCsv opts@ReportOpts{average_, row_total_}
|
||||||
(PeriodicReport colspans items (PeriodicReportRow _ _ coltotals tot avg)) =
|
(PeriodicReport colspans items (PeriodicReportRow _ coltotals tot avg)) =
|
||||||
maybetranspose $
|
maybetranspose $
|
||||||
("Account" : map showDateSpan colspans
|
("Account" : map showDateSpan colspans
|
||||||
++ ["Total" | row_total_]
|
++ ["Total" | row_total_]
|
||||||
++ ["Average" | average_]
|
++ ["Average" | average_]
|
||||||
) :
|
) :
|
||||||
[T.unpack (maybeAccountNameDrop opts a) :
|
[T.unpack (displayFull a) :
|
||||||
map showMixedAmountOneLineWithoutPrice
|
map showMixedAmountOneLineWithoutPrice
|
||||||
(amts
|
(amts
|
||||||
++ [rowtot | row_total_]
|
++ [rowtot | row_total_]
|
||||||
++ [rowavg | average_])
|
++ [rowavg | average_])
|
||||||
| PeriodicReportRow a _ amts rowtot rowavg <- items]
|
| PeriodicReportRow a amts rowtot rowavg <- items]
|
||||||
++
|
++
|
||||||
if no_total_ opts
|
if no_total_ opts
|
||||||
then []
|
then []
|
||||||
@ -603,7 +603,7 @@ multiBalanceReportAsText ropts@ReportOpts{..} r =
|
|||||||
-- | Build a 'Table' from a multi-column balance report.
|
-- | Build a 'Table' from a multi-column balance report.
|
||||||
balanceReportAsTable :: ReportOpts -> MultiBalanceReport -> Table String String MixedAmount
|
balanceReportAsTable :: ReportOpts -> MultiBalanceReport -> Table String String MixedAmount
|
||||||
balanceReportAsTable opts@ReportOpts{average_, row_total_, balancetype_}
|
balanceReportAsTable opts@ReportOpts{average_, row_total_, balancetype_}
|
||||||
(PeriodicReport colspans items (PeriodicReportRow _ _ coltotals tot avg)) =
|
(PeriodicReport colspans items (PeriodicReportRow _ coltotals tot avg)) =
|
||||||
maybetranspose $
|
maybetranspose $
|
||||||
addtotalrow $
|
addtotalrow $
|
||||||
Table
|
Table
|
||||||
@ -619,10 +619,9 @@ balanceReportAsTable opts@ReportOpts{average_, row_total_, balancetype_}
|
|||||||
++ [" Total" | totalscolumn]
|
++ [" Total" | totalscolumn]
|
||||||
++ ["Average" | average_]
|
++ ["Average" | average_]
|
||||||
accts = map renderacct items
|
accts = map renderacct items
|
||||||
renderacct (PeriodicReportRow a i _ _ _)
|
renderacct row =
|
||||||
| tree_ opts = replicate ((i-1)*2) ' ' ++ T.unpack (accountLeafName a)
|
replicate ((prrDepth row - 1) * 2) ' ' ++ T.unpack (prrDisplayName row)
|
||||||
| otherwise = T.unpack $ maybeAccountNameDrop opts a
|
rowvals (PeriodicReportRow _ as rowtot rowavg) = as
|
||||||
rowvals (PeriodicReportRow _ _ as rowtot rowavg) = as
|
|
||||||
++ [rowtot | totalscolumn]
|
++ [rowtot | totalscolumn]
|
||||||
++ [rowavg | average_]
|
++ [rowavg | average_]
|
||||||
addtotalrow | no_total_ opts = id
|
addtotalrow | no_total_ opts = id
|
||||||
|
|||||||
@ -203,7 +203,7 @@ compoundBalanceCommand CompoundBalanceCommandSpec{..} opts@CliOpts{reportopts_=r
|
|||||||
-- "2008/01/01-2008/12/31", not "2008").
|
-- "2008/01/01-2008/12/31", not "2008").
|
||||||
titledatestr
|
titledatestr
|
||||||
| balancetype == HistoricalBalance = showEndDates enddates
|
| balancetype == HistoricalBalance = showEndDates enddates
|
||||||
| otherwise = showDateSpan requestedspan
|
| otherwise = showDateSpan requestedspan
|
||||||
where
|
where
|
||||||
enddates = map (addDays (-1)) $ catMaybes $ map spanEnd colspans -- these spans will always have a definite end date
|
enddates = map (addDays (-1)) $ catMaybes $ map spanEnd colspans -- these spans will always have a definite end date
|
||||||
requestedspan = queryDateSpan date2_ userq `spanDefaultsFrom` journalDateSpan date2_ j
|
requestedspan = queryDateSpan date2_ userq `spanDefaultsFrom` journalDateSpan date2_ j
|
||||||
@ -271,12 +271,12 @@ compoundBalanceSubreport ropts@ReportOpts{..} userq j priceoracle subreportqfn s
|
|||||||
where
|
where
|
||||||
nonzeroaccounts =
|
nonzeroaccounts =
|
||||||
dbg5 "nonzeroaccounts" $
|
dbg5 "nonzeroaccounts" $
|
||||||
mapMaybe (\(PeriodicReportRow act _ amts _ _) ->
|
mapMaybe (\(PeriodicReportRow act amts _ _) ->
|
||||||
if not (all mixedAmountLooksZero amts) then Just act else Nothing) rows
|
if not (all mixedAmountLooksZero amts) then Just (displayFull act) else Nothing) rows
|
||||||
rows' = filter (not . emptyRow) rows
|
rows' = filter (not . emptyRow) rows
|
||||||
where
|
where
|
||||||
emptyRow (PeriodicReportRow act _ amts _ _) =
|
emptyRow (PeriodicReportRow act amts _ _) =
|
||||||
all mixedAmountLooksZero amts && not (any (act `isAccountNamePrefixOf`) nonzeroaccounts)
|
all mixedAmountLooksZero amts && not (any (displayFull act `isAccountNamePrefixOf`) nonzeroaccounts)
|
||||||
|
|
||||||
-- | Render a compound balance report as plain text suitable for console output.
|
-- | Render a compound balance report as plain text suitable for console output.
|
||||||
{- Eg:
|
{- Eg:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user