diff --git a/hledger-lib/Hledger/Reports/TransactionsReports.hs b/hledger-lib/Hledger/Reports/TransactionsReports.hs index 20ca8aa6d..b433de226 100644 --- a/hledger-lib/Hledger/Reports/TransactionsReports.hs +++ b/hledger-lib/Hledger/Reports/TransactionsReports.hs @@ -24,13 +24,13 @@ module Hledger.Reports.TransactionsReports ( where import Data.List -import Data.Maybe import Data.Ord -- import Test.HUnit import Hledger.Data import Hledger.Query import Hledger.Reports.ReportOptions +import Hledger.Utils -- | A transactions report includes a list of transactions @@ -43,8 +43,8 @@ import Hledger.Reports.ReportOptions type TransactionsReport = (String -- label for the balance column, eg "balance" or "total" ,[TransactionsReportItem] -- line items, one per transaction ) -type TransactionsReportItem = (Transaction -- the corresponding transaction - ,Transaction -- the transaction with postings to the current account(s) removed +type TransactionsReportItem = (Transaction -- the original journal transaction, unmodified + ,Transaction -- the transaction as seen from a particular account ,Bool -- is this a split, ie more than one other account posting ,String -- a display string describing the other account(s), if any ,MixedAmount -- the amount posted to the current account(s) (or total amount posted) @@ -67,36 +67,56 @@ journalTransactionsReport :: ReportOpts -> Journal -> Query -> TransactionsRepor journalTransactionsReport opts j q = (totallabel, items) where -- XXX items' first element should be the full transaction with all postings - items = reverse $ accountTransactionsReportItems q Nothing nullmixedamt id ts + items = reverse $ accountTransactionsReportItems q None nullmixedamt id ts ts = sortBy (comparing date) $ filter (q `matchesTransaction`) $ jtxns $ journalSelectingAmountFromOpts opts j date = transactionDateFn opts ------------------------------------------------------------------------------- --- | Select transactions within one or more current accounts, and make a --- transactions report relative to those account(s). This means: +-- | An account transactions report represents transactions affecting +-- a particular account (or possibly several accounts, but we don't +-- use that). It is used by hledger-web's account register view, where +-- we want to show one row per journal transaction, with: -- --- 1. it shows transactions from the point of view of the current account(s). --- The transaction amount is the amount posted to the current account(s). --- The other accounts' names are provided. +-- - the total increase/decrease to the current account -- --- 2. With no transaction filtering in effect other than a start date, it --- shows the accurate historical running balance for the current account(s). --- Otherwise it shows a running total starting at 0. +-- - the names of the other account(s) posted to/from -- --- This is used by eg hledger-web's account register view. Currently, --- reporting intervals are not supported, and report items are most --- recent first. -accountTransactionsReport :: ReportOpts -> Journal -> Query -> Query -> TransactionsReport +-- - transaction dates adjusted to the date of the earliest posting to +-- the current account, if those postings have their own dates +-- +-- Currently, reporting intervals are not supported, and report items +-- are most recent first. +-- +type AccountTransactionsReport = + (String -- label for the balance column, eg "balance" or "total" + ,[AccountTransactionsReportItem] -- line items, one per transaction + ) + +type AccountTransactionsReportItem = + ( + Transaction -- the original journal transaction + ,Transaction -- the adjusted account transaction + ,Bool -- is this a split, ie with more than one posting to other account(s) + ,String -- a display string describing the other account(s), if any + ,MixedAmount -- the amount posted to the current account(s) (or total amount posted) + ,MixedAmount -- the running balance for the current account(s) after this transaction + ) + +accountTransactionsReport :: ReportOpts -> Journal -> Query -> Query -> AccountTransactionsReport accountTransactionsReport opts j q thisacctquery = (label, items) where - -- transactions affecting this account, in date order - curq = filterQuery queryIsSym q - ts = sortBy (comparing tdate) $ - filter (matchesTransaction thisacctquery) $ - jtxns $ - filterJournalAmounts curq $ - journalSelectingAmountFromOpts opts j + -- transactions with excluded currencies removed + ts1 = jtxns $ + filterJournalAmounts (filterQuery queryIsSym q) $ + journalSelectingAmountFromOpts opts j + -- affecting this account + ts2 = filter (matchesTransaction thisacctquery) ts1 + -- with dates adjusted for account transactions report + ts3 = map (setTransactionDateToPostingDate q thisacctquery) ts2 + -- and sorted + ts = sortBy (comparing tdate) ts3 + -- starting balance: if we are filtering by a start date and nothing else, -- the sum of postings to this account before that date; otherwise zero. (startbal,label) | queryIsNull q = (nullmixedamt, balancelabel) @@ -110,33 +130,54 @@ accountTransactionsReport opts j q thisacctquery = (label, items) $ transactionsPostings ts tostartdatequery = Date (DateSpan Nothing startdate) startdate = queryStartDate (date2_ opts) q - items = reverse $ accountTransactionsReportItems q (Just thisacctquery) startbal negate ts -totallabel = "Total" -balancelabel = "Balance" + items = reverse $ -- see also registerChartHtml + accountTransactionsReportItems q thisacctquery startbal negate ts + +-- | Adjust a transaction's date to the earliest date of postings to a +-- particular account, if any, after filtering with a certain query. +setTransactionDateToPostingDate :: Query -> Query -> Transaction -> Transaction +setTransactionDateToPostingDate query thisacctquery t = t' + where + queryps = tpostings $ filterTransactionPostings query t + thisacctps = filter (matchesPosting thisacctquery) queryps + t' = case thisacctps of + [] -> t + _ -> t{tdate=d} + where + d | null ds = tdate t + | otherwise = minimum ds + ds = map postingDate thisacctps + -- no opts here, don't even bother with that date/date2 rigmarole + +totallabel = "Running Total" +balancelabel = "Historical Balance" -- | Generate transactions report items from a list of transactions, --- using the provided query and current account queries, starting balance, --- sign-setting function and balance-summing function. -accountTransactionsReportItems :: Query -> Maybe Query -> MixedAmount -> (MixedAmount -> MixedAmount) -> [Transaction] -> [TransactionsReportItem] +-- using the provided query and current account queries, starting +-- balance, sign-setting function and balance-summing function. With a +-- "this account" query of None, this can be used the for the +-- journalTransactionsReport also. +accountTransactionsReportItems :: Query -> Query -> MixedAmount -> (MixedAmount -> MixedAmount) -> [Transaction] -> [TransactionsReportItem] accountTransactionsReportItems _ _ _ _ [] = [] -accountTransactionsReportItems query thisacctquery bal signfn (t:ts) = +accountTransactionsReportItems query thisacctquery bal signfn (torig:ts) = -- This is used for both accountTransactionsReport and journalTransactionsReport, -- which makes it a bit overcomplicated case i of Just i' -> i':is Nothing -> is where - tmatched@Transaction{tpostings=psmatched} = filterTransactionPostings query t - (psthisacct,psotheracct) = case thisacctquery of Just m -> partition (matchesPosting m) psmatched - Nothing -> ([],psmatched) - numotheraccts = length $ nub $ map paccount psotheracct - amt = negate $ sum $ map pamount psthisacct - acct | isNothing thisacctquery = summarisePostingAccounts psmatched - | numotheraccts == 0 = summarisePostingAccounts psthisacct - | otherwise = summarisePostingAccounts psotheracct - (i,bal') = case psmatched of + tacct@Transaction{tpostings=queryps} = dbg0 "tacct" $ filterTransactionPostings query torig + (thisacctps, otheracctps) = -- partition (matchesPosting thisacctquery) queryps + case thisacctquery of None -> ([],queryps) + q -> partition (matchesPosting q) queryps + amt = negate $ sum $ map pamount thisacctps + numotheraccts = length $ nub $ map paccount otheracctps + otheracctstr | thisacctquery == None = summarisePostingAccounts queryps + | numotheraccts == 0 = summarisePostingAccounts thisacctps + | otherwise = summarisePostingAccounts otheracctps + (i,bal') = case queryps of [] -> (Nothing,bal) - _ -> (Just (t, tmatched, numotheraccts > 1, acct, a, b), b) + _ -> (Just (torig, tacct, numotheraccts > 1, otheracctstr, a, b), b) where a = signfn amt b = bal + a diff --git a/hledger-web/Handler/RegisterR.hs b/hledger-web/Handler/RegisterR.hs index f58532458..0ad8e01cd 100644 --- a/hledger-web/Handler/RegisterR.hs +++ b/hledger-web/Handler/RegisterR.hs @@ -5,6 +5,7 @@ module Handler.RegisterR where import Import import Data.Maybe +import Safe import Handler.Common import Handler.Post @@ -43,7 +44,7 @@ postRegisterR = handlePost -- Generate html for an account register, including a balance chart and transaction list. registerReportHtml :: WebOpts -> ViewData -> TransactionsReport -> HtmlUrl AppRoute registerReportHtml opts vd r = [hamlet| - ^{registerChartHtml $ map snd $ transactionsReportByCommodity r} + ^{registerChartHtml $ transactionsReportByCommodity r} ^{registerItemsHtml opts vd r} |] @@ -56,7 +57,7 @@ registerItemsHtml _ vd (balancelabel,items) = [hamlet| Date Description - To/From Account + To/From Account(s) Amount Out/In #{balancelabel'} $forall i <- numberTransactionsReportItems items @@ -68,11 +69,11 @@ registerItemsHtml _ vd (balancelabel,items) = [hamlet| -- filtering = m /= Any itemAsHtml :: ViewData -> (Int, Bool, Bool, Bool, TransactionsReportItem) -> HtmlUrl AppRoute - itemAsHtml VD{..} (n, newd, newm, _, (t, _, split, acct, amt, bal)) = [hamlet| + itemAsHtml VD{..} (n, newd, newm, _, (torig, tacct, split, acct, amt, bal)) = [hamlet| - + #{date} - #{elideRight 30 desc} + #{elideRight 30 desc} #{elideRight 40 acct} $if showamt @@ -85,7 +86,7 @@ registerItemsHtml _ vd (balancelabel,items) = [hamlet| datetransition | newm = "newmonth" | newd = "newday" | otherwise = "" :: String - (firstposting, date, desc) = (False, show $ tdate t, tdescription t) + (firstposting, date, desc) = (False, show $ tdate tacct, tdescription tacct) -- acctquery = (here, [("q", pack $ accountQuery acct)]) showamt = not split || not (isZeroMixedAmount amt) @@ -95,20 +96,22 @@ registerItemsHtml _ vd (balancelabel,items) = [hamlet| -- Data.Foldable.Foldable t1 => -- t1 (Transaction, t2, t3, t4, t5, MixedAmount) -- -> t -> Text.Blaze.Internal.HtmlM () -registerChartHtml :: [[TransactionsReportItem]] -> HtmlUrl AppRoute -registerChartHtml itemss = +registerChartHtml :: [(String, [TransactionsReportItem])] -> HtmlUrl AppRoute +registerChartHtml percommoditytxnreports = -- have to make sure plot is not called when our container (maincontent) -- is hidden, eg with add form toggled [hamlet| - + +