bal: support --value-at=p/t with multiperiod reports (#329)
This commit is contained in:
parent
74c381cc88
commit
8d7eacd73f
@ -85,7 +85,6 @@ type ClippedAccountName = AccountName
|
|||||||
multiBalanceReport :: ReportOpts -> Query -> Journal -> MultiBalanceReport
|
multiBalanceReport :: ReportOpts -> Query -> Journal -> MultiBalanceReport
|
||||||
multiBalanceReport ropts@ReportOpts{..} q j =
|
multiBalanceReport ropts@ReportOpts{..} q j =
|
||||||
(if invert_ then mbrNegate else id) $
|
(if invert_ then mbrNegate else id) $
|
||||||
(if value_ then mbrValue ropts j else id) $
|
|
||||||
MultiBalanceReport (displayspans, sorteditems, totalsrow)
|
MultiBalanceReport (displayspans, sorteditems, totalsrow)
|
||||||
where
|
where
|
||||||
symq = dbg1 "symq" $ filterQuery queryIsSym $ dbg1 "requested q" q
|
symq = dbg1 "symq" $ filterQuery queryIsSym $ dbg1 "requested q" q
|
||||||
@ -139,14 +138,39 @@ multiBalanceReport ropts@ReportOpts{..} q j =
|
|||||||
| empty_ = dbg1 "displayspan (-E)" reportspan -- all the requested intervals
|
| empty_ = dbg1 "displayspan (-E)" reportspan -- all the requested intervals
|
||||||
| otherwise = dbg1 "displayspan" $ requestedspan `spanIntersect` matchedspan -- exclude leading/trailing empty intervals
|
| otherwise = dbg1 "displayspan" $ requestedspan `spanIntersect` matchedspan -- exclude leading/trailing empty intervals
|
||||||
matchedspan = dbg1 "matchedspan" $ postingsDateSpan' (whichDateFromOpts ropts) ps
|
matchedspan = dbg1 "matchedspan" $ postingsDateSpan' (whichDateFromOpts ropts) ps
|
||||||
-- Group postings into their columns.
|
-- Group postings into their columns, with the column end dates.
|
||||||
psPerSpan :: [[Posting]] =
|
psPerSpan :: [([Posting], Maybe Day)] =
|
||||||
dbg1 "psPerSpan"
|
dbg1 "psPerSpan"
|
||||||
[filter (isPostingInDateSpan' (whichDateFromOpts ropts) s) ps | s <- displayspans]
|
[(filter (isPostingInDateSpan' (whichDateFromOpts ropts) s) ps, spanEnd s) | s <- displayspans]
|
||||||
|
-- Check if we'll be doing valuation. Here's how it's done in the various cases:
|
||||||
|
-- balance -M --value-at
|
||||||
|
-- transaction: convert each posting to value before calculating table cell amounts (balance change or ending balance) ?
|
||||||
|
-- period: convert each table cell amount (balance change or ending balance) to its value at period end
|
||||||
|
-- date: convert each table cell amount to its value at date
|
||||||
|
mvalueat = if value_ then Just value_at_ else Nothing
|
||||||
|
today = fromMaybe (error' "postingsReport: ReportOpts today_ is unset so could not satisfy --value-at=now") today_
|
||||||
|
-- If --value-at=transaction is in effect, convert the postings to value before summing.
|
||||||
|
maybeValuedPsPerSpan :: [([Posting], Maybe Day)] =
|
||||||
|
case mvalueat of
|
||||||
|
Just AtTransaction -> [([postingValueAtDate j (postingDate p) p | p <- ps], periodend) | (ps,periodend) <- psPerSpan]
|
||||||
|
_ -> psPerSpan
|
||||||
-- In each column, calculate the change in each account that has postings.
|
-- In each column, calculate the change in each account that has postings.
|
||||||
|
-- And if --value-at is in effect (except --value-at=transaction), convert these change amounts to value.
|
||||||
postedAcctBalChangesPerSpan :: [[(ClippedAccountName, MixedAmount)]] =
|
postedAcctBalChangesPerSpan :: [[(ClippedAccountName, MixedAmount)]] =
|
||||||
dbg1 "postedAcctBalChangesPerSpan" $
|
dbg1 "postedAcctBalChangesPerSpan" $
|
||||||
map postingAcctBals psPerSpan
|
[postingAcctBals valuedps
|
||||||
|
| (ps,periodend) <- maybeValuedPsPerSpan
|
||||||
|
, let periodlastday = maybe
|
||||||
|
(error' "multiBalanceReport: expected a subperiod end date") -- XXX shouldn't happen
|
||||||
|
(addDays (-1))
|
||||||
|
periodend
|
||||||
|
, let valuedps =
|
||||||
|
case mvalueat of
|
||||||
|
Just AtPeriod -> [postingValueAtDate j periodlastday p | p <- ps]
|
||||||
|
Just AtNow -> [postingValueAtDate j today p | p <- ps]
|
||||||
|
Just (AtDate d) -> [postingValueAtDate j d p | p <- ps]
|
||||||
|
_ -> ps
|
||||||
|
]
|
||||||
where
|
where
|
||||||
postingAcctBals :: [Posting] -> [(ClippedAccountName, MixedAmount)]
|
postingAcctBals :: [Posting] -> [(ClippedAccountName, MixedAmount)]
|
||||||
postingAcctBals ps = [(aname a, (if tree_ ropts then aibalance else aebalance) a) | a <- as]
|
postingAcctBals ps = [(aname a, (if tree_ ropts then aibalance else aebalance) a) | a <- as]
|
||||||
@ -192,8 +216,8 @@ multiBalanceReport ropts@ReportOpts{..} q j =
|
|||||||
[(a, accountLeafName a, accountNameLevel a, displayedBals, rowtot, rowavg)
|
[(a, accountLeafName a, accountNameLevel a, displayedBals, rowtot, rowavg)
|
||||||
| (a,changes) <- acctBalChanges
|
| (a,changes) <- acctBalChanges
|
||||||
, let displayedBals = case balancetype_ of
|
, let displayedBals = case balancetype_ of
|
||||||
HistoricalBalance -> drop 1 $ scanl (+) (startingBalanceFor a) changes
|
HistoricalBalance -> drop 1 $ scanl (+) (startingBalanceFor a) changes -- XXX need to value per period
|
||||||
CumulativeChange -> drop 1 $ scanl (+) nullmixedamt changes
|
CumulativeChange -> drop 1 $ scanl (+) 0 changes
|
||||||
_ -> changes
|
_ -> changes
|
||||||
, let rowtot = sum displayedBals
|
, let rowtot = sum displayedBals
|
||||||
, let rowavg = averageMixedAmounts displayedBals
|
, let rowavg = averageMixedAmounts displayedBals
|
||||||
@ -274,43 +298,6 @@ multiBalanceReportSpan :: MultiBalanceReport -> DateSpan
|
|||||||
multiBalanceReportSpan (MultiBalanceReport ([], _, _)) = DateSpan Nothing Nothing
|
multiBalanceReportSpan (MultiBalanceReport ([], _, _)) = DateSpan Nothing Nothing
|
||||||
multiBalanceReportSpan (MultiBalanceReport (colspans, _, _)) = DateSpan (spanStart $ head colspans) (spanEnd $ last colspans)
|
multiBalanceReportSpan (MultiBalanceReport (colspans, _, _)) = DateSpan (spanStart $ head colspans) (spanEnd $ last colspans)
|
||||||
|
|
||||||
-- | Convert all the posting amounts in a MultiBalanceReport to their
|
|
||||||
-- default valuation commodities. This means using the Journal's most
|
|
||||||
-- recent applicable market prices before the valuation date.
|
|
||||||
-- The valuation date is set with --value-at and can be:
|
|
||||||
-- each posting's date,
|
|
||||||
-- or the last day in the report subperiod,
|
|
||||||
-- or today's date (gives an error if today_ is not set in ReportOpts),
|
|
||||||
-- or a specified date.
|
|
||||||
mbrValue :: ReportOpts -> Journal -> MultiBalanceReport -> MultiBalanceReport
|
|
||||||
mbrValue ReportOpts{..} Journal{..} (MultiBalanceReport (spans, rows, (coltotals, rowtotaltotal, rowavgtotal))) =
|
|
||||||
MultiBalanceReport (
|
|
||||||
spans
|
|
||||||
,[(acct, acct', depth, map (uncurry val) $ zip ends rowamts, val end rowtotal, val end rowavg)
|
|
||||||
| (acct, acct', depth, rowamts, rowtotal, rowavg) <- rows]
|
|
||||||
,(map (uncurry val) $ zip ends coltotals
|
|
||||||
,val end rowtotaltotal
|
|
||||||
,val end rowavgtotal)
|
|
||||||
)
|
|
||||||
where
|
|
||||||
ends = map (addDays (-1) . fromMaybe (error' "mbrValue: expected all report periods to have an end date") . spanEnd) spans -- XXX shouldn't happen
|
|
||||||
end = lastDef (error' "mbrValue: expected at least one report subperiod") ends -- XXX shouldn't happen
|
|
||||||
val periodend amt = mixedAmountValue prices valuationdate amt
|
|
||||||
where
|
|
||||||
-- prices are in parse order - sort into date then parse order,
|
|
||||||
-- & reversed for quick lookup of the latest price.
|
|
||||||
prices = reverse $ sortOn mpdate jmarketprices
|
|
||||||
valuationdate = case value_at_ of
|
|
||||||
AtTransaction ->
|
|
||||||
error' "sorry, --value-at=transaction with balance reports is not yet supported"
|
|
||||||
AtPeriod | average_ || row_total_ ->
|
|
||||||
error' "sorry, --value-at=period with -T or -A in periodic balance reports is not yet supported"
|
|
||||||
AtPeriod -> periodend
|
|
||||||
AtNow -> case today_ of
|
|
||||||
Just d -> d
|
|
||||||
Nothing -> error' "mbrValue: ReportOpts today_ is unset so could not satisfy --value-at=now"
|
|
||||||
AtDate d -> d
|
|
||||||
|
|
||||||
-- | Generates a simple non-columnar BalanceReport, but using multiBalanceReport,
|
-- | Generates a simple non-columnar BalanceReport, but using multiBalanceReport,
|
||||||
-- in order to support --historical. Does not support tree-mode boring parent eliding.
|
-- in order to support --historical. Does not support tree-mode boring parent eliding.
|
||||||
-- If the normalbalance_ option is set, it adjusts the sorting and sign of amounts
|
-- If the normalbalance_ option is set, it adjusts the sorting and sign of amounts
|
||||||
|
|||||||
@ -323,11 +323,7 @@ balance opts@CliOpts{rawopts_=rawopts,reportopts_=ropts@ReportOpts{..}} j = do
|
|||||||
|
|
||||||
else
|
else
|
||||||
if multiperiod then do -- multi period balance report
|
if multiperiod then do -- multi period balance report
|
||||||
-- With --value-at=transaction, convert all amounts to value before summing them.
|
let report = multiBalanceReport ropts (queryFromOpts d ropts) j
|
||||||
let j' | value_at_ == AtTransaction =
|
|
||||||
error' "sorry, --value-at=transaction with balance reports is not yet supported" -- journalValueAtTransactionDate ropts j
|
|
||||||
| otherwise = j
|
|
||||||
report = multiBalanceReport ropts (queryFromOpts d ropts) j'
|
|
||||||
render = case format of
|
render = case format of
|
||||||
"csv" -> (++ "\n") . printCSV . multiBalanceReportAsCsv ropts
|
"csv" -> (++ "\n") . printCSV . multiBalanceReportAsCsv ropts
|
||||||
"html" -> (++ "\n") . TL.unpack . L.renderText . multiBalanceReportAsHtml ropts
|
"html" -> (++ "\n") . TL.unpack . L.renderText . multiBalanceReportAsHtml ropts
|
||||||
@ -335,16 +331,13 @@ balance opts@CliOpts{rawopts_=rawopts,reportopts_=ropts@ReportOpts{..}} j = do
|
|||||||
writeOutput opts $ render report
|
writeOutput opts $ render report
|
||||||
|
|
||||||
else do -- single period simple balance report
|
else do -- single period simple balance report
|
||||||
-- With --value-at=transaction, convert all amounts to value before summing them.
|
let report
|
||||||
let j' | value_at_ == AtTransaction = journalValueAtTransactionDate ropts j
|
|
||||||
| otherwise = j
|
|
||||||
report
|
|
||||||
| balancetype_ `elem` [HistoricalBalance, CumulativeChange]
|
| balancetype_ `elem` [HistoricalBalance, CumulativeChange]
|
||||||
= let ropts' | flat_ ropts = ropts
|
= let ropts' | flat_ ropts = ropts
|
||||||
| otherwise = ropts{accountlistmode_=ALTree}
|
| otherwise = ropts{accountlistmode_=ALTree}
|
||||||
in balanceReportFromMultiBalanceReport ropts' (queryFromOpts d ropts) j'
|
in balanceReportFromMultiBalanceReport ropts' (queryFromOpts d ropts) j
|
||||||
-- for historical balances we must use balanceReportFromMultiBalanceReport (also forces --no-elide)
|
-- for historical balances we must use balanceReportFromMultiBalanceReport (also forces --no-elide)
|
||||||
| otherwise = balanceReport ropts (queryFromOpts d ropts) j' -- simple Ledger-style balance report
|
| otherwise = balanceReport ropts (queryFromOpts d ropts) j -- simple Ledger-style balance report
|
||||||
render = case format of
|
render = case format of
|
||||||
"csv" -> \ropts r -> (++ "\n") $ printCSV $ balanceReportAsCsv ropts r
|
"csv" -> \ropts r -> (++ "\n") $ printCSV $ balanceReportAsCsv ropts r
|
||||||
"html" -> \_ _ -> error' "Sorry, HTML output is not yet implemented for this kind of report." -- TODO
|
"html" -> \_ _ -> error' "Sorry, HTML output is not yet implemented for this kind of report." -- TODO
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import Text.Tabular as T
|
|||||||
import Hledger
|
import Hledger
|
||||||
import Hledger.Cli.Commands.Balance
|
import Hledger.Cli.Commands.Balance
|
||||||
import Hledger.Cli.CliOptions
|
import Hledger.Cli.CliOptions
|
||||||
import Hledger.Cli.Utils (journalValueAtTransactionDate, writeOutput)
|
import Hledger.Cli.Utils (writeOutput)
|
||||||
|
|
||||||
-- | Description of a compound balance report command,
|
-- | Description of a compound balance report command,
|
||||||
-- from which we generate the command's cmdargs mode and IO action.
|
-- from which we generate the command's cmdargs mode and IO action.
|
||||||
@ -209,15 +209,11 @@ compoundBalanceCommand CompoundBalanceCommandSpec{..} opts@CliOpts{reportopts_=r
|
|||||||
compoundBalanceSubreport :: ReportOpts -> Query -> Journal -> (Journal -> Query) -> NormalSign -> MultiBalanceReport
|
compoundBalanceSubreport :: ReportOpts -> Query -> Journal -> (Journal -> Query) -> NormalSign -> MultiBalanceReport
|
||||||
compoundBalanceSubreport ropts@ReportOpts{..} userq j subreportqfn subreportnormalsign = r'
|
compoundBalanceSubreport ropts@ReportOpts{..} userq j subreportqfn subreportnormalsign = r'
|
||||||
where
|
where
|
||||||
-- With --value-at=transaction and a periodic report, convert all amounts to value before summing them.
|
|
||||||
j' | value_at_ == AtTransaction && interval_ /= NoInterval = journalValueAtTransactionDate ropts j
|
|
||||||
| otherwise = j
|
|
||||||
|
|
||||||
-- force --empty to ensure same columns in all sections
|
-- force --empty to ensure same columns in all sections
|
||||||
ropts' = ropts { empty_=True, normalbalance_=Just subreportnormalsign }
|
ropts' = ropts { empty_=True, normalbalance_=Just subreportnormalsign }
|
||||||
-- run the report
|
-- run the report
|
||||||
q = And [subreportqfn j', userq]
|
q = And [subreportqfn j, userq]
|
||||||
r@(MultiBalanceReport (dates, rows, totals)) = multiBalanceReport ropts' q j'
|
r@(MultiBalanceReport (dates, rows, totals)) = multiBalanceReport ropts' q j
|
||||||
-- if user didn't specify --empty, now remove the all-zero rows, unless they have non-zero subaccounts
|
-- if user didn't specify --empty, now remove the all-zero rows, unless they have non-zero subaccounts
|
||||||
-- in this report
|
-- in this report
|
||||||
r' | empty_ = r
|
r' | empty_ = r
|
||||||
|
|||||||
@ -14,7 +14,6 @@ module Hledger.Cli.Utils
|
|||||||
writeOutput,
|
writeOutput,
|
||||||
journalTransform,
|
journalTransform,
|
||||||
journalAddForecast,
|
journalAddForecast,
|
||||||
journalValueAtTransactionDate,
|
|
||||||
journalReload,
|
journalReload,
|
||||||
journalReloadIfChanged,
|
journalReloadIfChanged,
|
||||||
journalFileIsNewer,
|
journalFileIsNewer,
|
||||||
@ -121,24 +120,6 @@ anonymise j
|
|||||||
where
|
where
|
||||||
anon = T.pack . flip showHex "" . (fromIntegral :: Int -> Word32) . hash
|
anon = T.pack . flip showHex "" . (fromIntegral :: Int -> Word32) . hash
|
||||||
|
|
||||||
-- journalApplyValue and friends are here not in Hledger.Data.Journal
|
|
||||||
-- because they use ReportOpts.
|
|
||||||
|
|
||||||
-- | Convert all the journal's posting amounts to their market value
|
|
||||||
-- as of each posting's date. Needed when converting some periodic
|
|
||||||
-- reports to value, when --value-at=transaction (only).
|
|
||||||
-- See eg Register.hs.
|
|
||||||
journalValueAtTransactionDate :: ReportOpts -> Journal -> Journal
|
|
||||||
journalValueAtTransactionDate ReportOpts{..} j@Journal{..}
|
|
||||||
| value_at_ /= AtTransaction = j
|
|
||||||
| otherwise = j{jtxns = map txnvalue jtxns}
|
|
||||||
where
|
|
||||||
txnvalue t@Transaction{..} = t{tpostings=map postingvalue tpostings}
|
|
||||||
postingvalue p@Posting{..} = p{pamount=mixedAmountValue prices (postingDate p) pamount}
|
|
||||||
-- prices are in parse order - sort into date then parse order,
|
|
||||||
-- reversed for quick lookup of the latest price.
|
|
||||||
prices = reverse $ sortOn mpdate jmarketprices
|
|
||||||
|
|
||||||
-- | Generate periodic transactions from all periodic transaction rules in the journal.
|
-- | Generate periodic transactions from all periodic transaction rules in the journal.
|
||||||
-- These transactions are added to the in-memory Journal (but not the on-disk file).
|
-- These transactions are added to the in-memory Journal (but not the on-disk file).
|
||||||
--
|
--
|
||||||
|
|||||||
@ -612,9 +612,10 @@ Here are the ones currently supported
|
|||||||
| register | Y | Y | Y |
|
| register | Y | Y | Y |
|
||||||
| register, multiperiod | Y | Y | Y |
|
| register, multiperiod | Y | Y | Y |
|
||||||
| balance | Y | Y | Y |
|
| balance | Y | Y | Y |
|
||||||
| balance, multiperiod | - | Y | Y |
|
| balance, multiperiod | Y | Y | Y |
|
||||||
| balance, multiperiod, -T/-A | - | - | Y |
|
| balance, multiperiod, -T/-A | Y | Y | Y |
|
||||||
| register/balance, multiperiod, -T/-A, -H | ? | ? | ? |
|
| register/balance, multiperiod, -T/-A, -H | ? | ? | ? |
|
||||||
|
| balance, --budget | ? | ? | ? |
|
||||||
|
|
||||||
## Combining -B and -V
|
## Combining -B and -V
|
||||||
|
|
||||||
|
|||||||
@ -299,15 +299,13 @@ $ hledger -f- bal -V
|
|||||||
|
|
||||||
# 29. multicolumn balance report valued at transaction
|
# 29. multicolumn balance report valued at transaction
|
||||||
$ hledger -f- bal -MTA --value-at=transaction
|
$ hledger -f- bal -MTA --value-at=transaction
|
||||||
>2 /not yet supported/
|
Balance changes in 2000q1:
|
||||||
>=1
|
|
||||||
# Balance changes in 2000q1:
|
|| Jan Feb Mar Total Average
|
||||||
#
|
===++=================================
|
||||||
# || Jan Feb Mar Total Average
|
a || 1 B 2 B 3 B 6 B 2 B
|
||||||
# ===++=================================
|
---++---------------------------------
|
||||||
# a || 1 B 2 B 3 B 6 B 2 B
|
|| 1 B 2 B 3 B 6 B 2 B
|
||||||
# ---++---------------------------------
|
|
||||||
# || 1 B 2 B 3 B 6 B 2 B
|
|
||||||
|
|
||||||
# 30. multicolumn balance report valued at period end
|
# 30. multicolumn balance report valued at period end
|
||||||
$ hledger -f- bal -M --value-at=period
|
$ hledger -f- bal -M --value-at=period
|
||||||
@ -321,18 +319,25 @@ Balance changes in 2000q1:
|
|||||||
|
|
||||||
# 31. multicolumn balance report valued at period end with -T or -A
|
# 31. multicolumn balance report valued at period end with -T or -A
|
||||||
$ hledger -f- bal -M --value-at=period -TA
|
$ hledger -f- bal -M --value-at=period -TA
|
||||||
>2 /not yet supported/
|
|
||||||
>=1
|
|
||||||
|
|
||||||
# 32. multicolumn balance report valued at other date
|
|
||||||
$ hledger -f- bal -M --value-at=2000-01-15
|
|
||||||
Balance changes in 2000q1:
|
Balance changes in 2000q1:
|
||||||
|
|
||||||
|| Jan Feb Mar
|
|| Jan Feb Mar Total Average
|
||||||
===++===============
|
===++=================================
|
||||||
a || 5 B 5 B 5 B
|
a || 5 B 2 B 3 B 10 B 3 B
|
||||||
---++---------------
|
---++---------------------------------
|
||||||
|| 5 B 5 B 5 B
|
|| 5 B 2 B 3 B 10 B 3 B
|
||||||
|
# >2 /not yet supported/
|
||||||
|
# >=1
|
||||||
|
|
||||||
|
# 32. multicolumn balance report valued at other date
|
||||||
|
$ hledger -f- bal -MTA --value-at=2000-01-15
|
||||||
|
Balance changes in 2000q1:
|
||||||
|
|
||||||
|
|| Jan Feb Mar Total Average
|
||||||
|
===++=================================
|
||||||
|
a || 5 B 5 B 5 B 15 B 5 B
|
||||||
|
---++---------------------------------
|
||||||
|
|| 5 B 5 B 5 B 15 B 5 B
|
||||||
|
|
||||||
# 33. multicolumn balance report valued today (with today >= 2000-04-01)
|
# 33. multicolumn balance report valued today (with today >= 2000-04-01)
|
||||||
$ hledger -f- bal -M --value-at=now
|
$ hledger -f- bal -M --value-at=now
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user