diff --git a/hledger-web/.hledger/web/static/hledger.js b/hledger-web/.hledger/web/static/hledger.js
index 02986c104..f0723bf8a 100644
--- a/hledger-web/.hledger/web/static/hledger.js
+++ b/hledger-web/.hledger/web/static/hledger.js
@@ -14,6 +14,7 @@ $(document).ready(function() {
/* set up various show/hide toggles */
$('#search-help-link').click(function() { $('#search-help').slideToggle('fast'); });
$('#accounts-toggle-link').click(function() { $('#accounts').slideToggle('fast'); });
+ $('.postings-toggle-link').click(function() { $(this).parent().parent().nextUntil(':not(.posting)').toggle(); event.preventDefault(); });
});
diff --git a/hledger-web/.hledger/web/static/style.css b/hledger-web/.hledger/web/static/style.css
index b5050cc67..3231401ad 100644
--- a/hledger-web/.hledger/web/static/style.css
+++ b/hledger-web/.hledger/web/static/style.css
@@ -112,6 +112,9 @@ table.registerreport { border-spacing:0; }
table.registerreport tr { vertical-align:top; }
table.registerreport td { padding-bottom:0.2em; }
table.registerreport .date { white-space:nowrap; }
+table.registerreport tr.posting { display:none; font-size:smaller; }
+table.registerreport tr.posting .account { padding-left:1.5em; }
+table.registerreport tr.posting .amount { padding-right:0.5em; }
tr.firstposting td { }
tr.newday td { border-top: 1px solid black; }
tr.newmonth td { border-top: 2px solid black; }
diff --git a/hledger-web/.hledger/web/templates/accountregisterreportitem.hamlet b/hledger-web/.hledger/web/templates/accountregisterreportitem.hamlet
index 866ccf28e..7a0a7631f 100644
--- a/hledger-web/.hledger/web/templates/accountregisterreportitem.hamlet
+++ b/hledger-web/.hledger/web/templates/accountregisterreportitem.hamlet
@@ -1,6 +1,24 @@
#{date}
- #{elideRight 30 desc}
- #{elideRight 40 acct}
- #{mixedAmountAsHtml amt}
+ #{elideRight 30 desc}
+
+ $if split
+ #{elideRight 40 acct}
+
+ $if showamt
+ #{mixedAmountAsHtml amt}
#{mixedAmountAsHtml bal}
+$if split
+ $forall p <- tpostings t'
+ #{elideRight 40 $ paccount p}
+ #{mixedAmountAsHtml $ pamount p}
+
diff --git a/hledger-web/.hledger/web/templates/balancereportitem.hamlet b/hledger-web/.hledger/web/templates/balancereportitem.hamlet
index a656ea0cb..e705d41c6 100644
--- a/hledger-web/.hledger/web/templates/balancereportitem.hamlet
+++ b/hledger-web/.hledger/web/templates/balancereportitem.hamlet
@@ -1,12 +1,13 @@
#{adisplay}
+ #{adisplay}
+subs
-
- -others
+ +subs
+
#{mixedAmountAsHtml abal}
(#{numpostingsinacct acct})
diff --git a/hledger-web/.hledger/web/templates/postingregisterreportitem.hamlet b/hledger-web/.hledger/web/templates/postingregisterreportitem.hamlet
index cb87271a8..95c89ba04 100644
--- a/hledger-web/.hledger/web/templates/postingregisterreportitem.hamlet
+++ b/hledger-web/.hledger/web/templates/postingregisterreportitem.hamlet
@@ -1,6 +1,6 @@
#{date}
#{elideRight 30 desc}
- #{elideRight 40 acct}
+ #{elideRight 40 acct}
#{mixedAmountAsHtml $ pamount posting}
#{mixedAmountAsHtml b}
diff --git a/hledger-web/Handlers.hs b/hledger-web/Handlers.hs
index 012e4c0ef..d3d6b3de8 100644
--- a/hledger-web/Handlers.hs
+++ b/hledger-web/Handlers.hs
@@ -65,7 +65,7 @@ getRegisterR = do
let sidecontent = balanceReportAsHtml opts vd{q=""} $ balanceReport opts nullfilterspec j
maincontent =
case inAccountMatcher qopts of Just m' -> accountRegisterReportAsHtml opts vd $ accountRegisterReport opts j m m'
- Nothing -> postingRegisterReportAsHtml opts vd $ postingRegisterReport opts nullfilterspec $ filterJournalPostings2 m j
+ Nothing -> accountRegisterReportAsHtml opts vd $ journalRegisterReport opts j m
editform' = editform vd
defaultLayout $ do
setTitle "hledger-web register"
@@ -93,7 +93,7 @@ getRegisterOnlyR = do
setTitle "hledger-web register only"
addHamlet $
case inAccountMatcher qopts of Just m' -> accountRegisterReportAsHtml opts vd $ accountRegisterReport opts j m m'
- Nothing -> postingRegisterReportAsHtml opts vd $ postingRegisterReport opts nullfilterspec $ filterJournalPostings2 m j
+ Nothing -> accountRegisterReportAsHtml opts vd $ journalRegisterReport opts j m
postRegisterOnlyR :: Handler RepPlain
postRegisterOnlyR = handlePost
@@ -119,34 +119,37 @@ getAccountsJsonR = do
-- helpers
-accountUrl :: String -> String
-accountUrl a = "inacct:" ++ quoteIfSpaced a -- (accountNameToAccountRegex a)
+accountQuery :: AccountName -> String
+accountQuery a = "inacct:" ++ quoteIfSpaced a -- (accountNameToAccountRegex a)
-accountsUrl :: String -> String
-accountsUrl a = "inaccts:" ++ quoteIfSpaced a -- (accountNameToAccountRegex a)
+accountsQuery :: AccountName -> String
+accountsQuery a = "inaccts:" ++ quoteIfSpaced a -- (accountNameToAccountRegex a)
-accountsOnlyUrl :: String -> String
-accountsOnlyUrl a = "inacctsonly:" ++ quoteIfSpaced a -- (accountNameToAccountRegex a)
+accountsOnlyQuery :: AccountName -> String
+accountsOnlyQuery a = "inacctsonly:" ++ quoteIfSpaced a -- (accountNameToAccountRegex a)
+
+-- accountUrl :: AppRoute -> AccountName -> (AppRoute,[(String,ByteString)])
+accountUrl r a = (r, [("q",pack $ accountQuery a)])
-- | Render a balance report as HTML.
balanceReportAsHtml :: [Opt] -> ViewData -> BalanceReport -> Hamlet AppRoute
balanceReportAsHtml _ vd@VD{here=here,q=q,m=m,qopts=qopts,j=j} (items,total) = $(Settings.hamletFile "balancereport")
where
l = journalToLedger nullfilterspec j
- numpostingsinacct = length . apostings . ledgerAccount l
inacctmatcher = inAccountMatcher qopts
allaccts = isNothing inacctmatcher
itemAsHtml :: ViewData -> BalanceReportItem -> Hamlet AppRoute
itemAsHtml VD{here=here,q=q} (acct, adisplay, aindent, abal) = $(Settings.hamletFile "balancereportitem")
where
+ numpostings = length $ apostings $ ledgerAccount l acct
depthclass = "depth"++show aindent
inacctclass = case inacctmatcher of
Just m -> if m `matchesAccount` acct then "inacct" else "notinacct"
Nothing -> "" :: String
indent = preEscapedString $ concat $ replicate (2 * aindent) " "
- accturl = (here, [("q", pack $ accountUrl acct)])
- acctsurl = (here, [("q", pack $ accountsUrl acct)])
- acctsonlyurl = (here, [("q", pack $ accountsOnlyUrl acct)])
+ acctquery = (here, [("q", pack $ accountQuery acct)])
+ acctsquery = (here, [("q", pack $ accountsQuery acct)])
+ acctsonlyquery = (here, [("q", pack $ accountsOnlyQuery acct)])
-- | Render a journal report as HTML.
journalReportAsHtml :: [Opt] -> ViewData -> JournalReport -> Hamlet AppRoute
@@ -158,54 +161,23 @@ journalReportAsHtml _ vd items = $(Settings.hamletFile "journalreport")
evenodd = if even n then "even" else "odd" :: String
txn = trimnl $ showTransaction t where trimnl = reverse . dropWhile (=='\n') . reverse
--- | Render a register report as HTML.
--- Journal-wide postings register, when no account has focus.
-postingRegisterReportAsHtml :: [Opt] -> ViewData -> PostingRegisterReport -> Hamlet AppRoute
-postingRegisterReportAsHtml _ vd (balancelabel,items) = $(Settings.hamletFile "postingregisterreport")
- where
- itemAsHtml :: ViewData -> (Int, Bool, Bool, Bool, PostingRegisterReportItem) -> Hamlet AppRoute
- itemAsHtml VD{here=here} (n, newd, newm, newy, (ds, posting, b)) = $(Settings.hamletFile "postingregisterreportitem")
- where
- evenodd = if even n then "even" else "odd" :: String
- datetransition | newm = "newmonth"
- | newd = "newday"
- | otherwise = "" :: String
- (firstposting, date, desc) = case ds of Just (da, de) -> ("firstposting", show da, de)
- Nothing -> ("", "", "") :: (String,String,String)
- acct = paccount posting
- accturl = (here, [("q", pack $ accountUrl acct)])
-
--- Add incrementing transaction numbers to a list of register report items
--- starting at 1. Also add three flags that are true if the date, month,
--- and year is different from the previous item's.
-numberPostingRegisterReportItems :: [PostingRegisterReportItem] -> [(Int,Bool,Bool,Bool,PostingRegisterReportItem)]
-numberPostingRegisterReportItems [] = []
-numberPostingRegisterReportItems is = number 0 nulldate is
- where
- number :: Int -> Day -> [PostingRegisterReportItem] -> [(Int,Bool,Bool,Bool,PostingRegisterReportItem)]
- number _ _ [] = []
- number n prevd (i@(Nothing, _, _) :is) = (n,False,False,False,i) :(number n prevd is)
- number n prevd (i@(Just (d,_), _, _):is) = (n+1,newday,newmonth,newyear,i):(number (n+1) d is)
- where
- newday = d/=prevd
- newmonth = dm/=prevdm || dy/=prevdy
- newyear = dy/=prevdy
- (dy,dm,_) = toGregorian d
- (prevdy,prevdm,_) = toGregorian prevd
-
-- Account-specific transaction register, when an account is focussed.
accountRegisterReportAsHtml :: [Opt] -> ViewData -> AccountRegisterReport -> Hamlet AppRoute
accountRegisterReportAsHtml _ vd (balancelabel,items) = $(Settings.hamletFile "accountregisterreport")
where
itemAsHtml :: ViewData -> (Int, Bool, Bool, Bool, AccountRegisterReportItem) -> Hamlet AppRoute
- itemAsHtml VD{here=here} (n, newd, newm, newy, (t, acct, amt, bal)) = $(Settings.hamletFile "accountregisterreportitem")
+ itemAsHtml VD{here=here} (n, newd, newm, newy, (t, t', split, acct, amt, bal)) = $(Settings.hamletFile "accountregisterreportitem")
where
evenodd = if even n then "even" else "odd" :: String
datetransition | newm = "newmonth"
| newd = "newday"
| otherwise = "" :: String
(firstposting, date, desc) = (False, show $ tdate t, tdescription t)
- accturl = (here, [("q", pack $ accountUrl acct)])
+ acctquery = (here, [("q", pack $ accountQuery acct)])
+ showamt = not split || not (isZeroMixedAmount amt)
+
+stringIfLongerThan :: Int -> String -> String
+stringIfLongerThan n s = if length s > n then s else ""
numberAccountRegisterReportItems :: [AccountRegisterReportItem] -> [(Int,Bool,Bool,Bool,AccountRegisterReportItem)]
numberAccountRegisterReportItems [] = []
@@ -213,7 +185,7 @@ numberAccountRegisterReportItems is = number 0 nulldate is
where
number :: Int -> Day -> [AccountRegisterReportItem] -> [(Int,Bool,Bool,Bool,AccountRegisterReportItem)]
number _ _ [] = []
- number n prevd (i@(Transaction{tdate=d},_,_,_):is) = (n+1,newday,newmonth,newyear,i):(number (n+1) d is)
+ number n prevd (i@(Transaction{tdate=d},_,_,_,_,_):is) = (n+1,newday,newmonth,newyear,i):(number (n+1) d is)
where
newday = d/=prevd
newmonth = dm/=prevdm || dy/=prevdy
diff --git a/hledger/Hledger/Cli/Register.hs b/hledger/Hledger/Cli/Register.hs
index da17d9f5c..40a0105c0 100644
--- a/hledger/Hledger/Cli/Register.hs
+++ b/hledger/Hledger/Cli/Register.hs
@@ -13,6 +13,7 @@ module Hledger.Cli.Register (
,register
,postingRegisterReport
,accountRegisterReport
+ ,journalRegisterReport
,postingRegisterReportAsText
,showPostingWithBalanceForVty
,tests_Hledger_Cli_Register
@@ -58,6 +59,8 @@ type AccountRegisterReport = (String -- label for the balan
-- | A single account register line item, representing one transaction to/from the focussed account.
type AccountRegisterReportItem = (Transaction -- the corresponding transaction
+ ,Transaction -- the transaction with postings to the focussed account removed
+ ,Bool -- is this a split (more than one other-account posting) ?
,String -- the (possibly aggregated) account info to display
,MixedAmount -- the (possibly aggregated) amount to display (sum of the other-account postings)
,MixedAmount -- the running balance for the focussed account after this transaction
@@ -103,7 +106,7 @@ balancelabel = "Balance"
-- | Get a ledger-style posting register report, with the specified options,
-- for the whole journal. See also "accountRegisterReport".
postingRegisterReport :: [Opt] -> FilterSpec -> Journal -> PostingRegisterReport
-postingRegisterReport opts fspec j = (totallabel,postingRegisterItems ps nullposting startbal (+))
+postingRegisterReport opts fspec j = (totallabel, postingRegisterItems ps nullposting startbal (+))
where
ps | interval == NoInterval = displayableps
| otherwise = summarisePostingsByInterval interval depth empty filterspan displayableps
@@ -173,10 +176,19 @@ datedisplayexpr = do
where
compareop = choice $ map (try . string) ["<=",">=","==","<","=",">"]
--- | Get a quicken/gnucash-style account register report, with the
--- specified options, for the currently focussed account (or possibly the
--- focussed account plus sub-accounts.) This differs from
--- "postingRegisterReport" in several ways:
+-- | Get a ledger-style register report showing all matched transactions and postings.
+-- Similar to "postingRegisterReport" except it uses matchers and
+-- per-transaction report items like "accountRegisterReport".
+journalRegisterReport :: [Opt] -> Journal -> Matcher -> AccountRegisterReport
+journalRegisterReport opts j@Journal{jtxns=ts} m = (totallabel, items)
+ where
+ ts' = sortBy (comparing tdate) $ filter (not . null . tpostings) $ map (filterTransactionPostings m) ts
+ items = reverse $ accountRegisterReportItems m MatchAny nullmixedamt (+) ts'
+
+-- | Get a conventional account register report, with the specified
+-- options, for the currently focussed account (or possibly the focussed
+-- account plus sub-accounts.) This differs from "postingRegisterReport"
+-- in several ways:
--
-- 1. it shows transactions, from the point of view of the focussed
-- account. The other account's name and posted amount is displayed,
@@ -195,7 +207,6 @@ accountRegisterReport opts j m thisacctmatcher = (label, items)
where
-- transactions affecting this account, in date order
ts = sortBy (comparing tdate) $ filter (matchesTransaction thisacctmatcher) $ jtxns j
-
-- 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, sumfn) | matcherIsNull m = (nullmixedamt,balancelabel,(-))
@@ -210,35 +221,45 @@ accountRegisterReport opts j m thisacctmatcher = (label, items)
tostartdatematcher = MatchDate True (DateSpan Nothing startdate)
startdate = matcherStartDate effective m
effective = Effective `elem` opts
-
- displaymatcher = -- ltrace "displaymatcher" $
- MatchAnd [negateMatcher thisacctmatcher, m]
-
- items = reverse $ accountRegisterReportItems ts displaymatcher nulltransaction startbal sumfn
+ items = reverse $ accountRegisterReportItems m thisacctmatcher startbal sumfn ts
-- | Generate account register line items from a list of transactions,
--- using the provided matcher (postings not matching this will not affect
--- the displayed item), starting transaction, starting balance, and
--- balance summing function.
-accountRegisterReportItems :: [Transaction] -> Matcher -> Transaction -> MixedAmount -> (MixedAmount -> MixedAmount -> MixedAmount) -> [AccountRegisterReportItem]
-accountRegisterReportItems [] _ _ _ _ = []
-accountRegisterReportItems (t@Transaction{tpostings=ps}:ts) displaymatcher _ bal sumfn =
+-- using the provided query and "this account" matchers, starting balance,
+-- and balance summing function.
+accountRegisterReportItems :: Matcher -> Matcher -> MixedAmount -> (MixedAmount -> MixedAmount -> MixedAmount) -> [Transaction] -> [AccountRegisterReportItem]
+accountRegisterReportItems _ _ _ _ [] = []
+accountRegisterReportItems matcher thisacctmatcher bal sumfn (t@Transaction{tpostings=ps}:ts) =
case i of Just i' -> i':is
Nothing -> is
where
- (i,bal'') = case filter (displaymatcher `matchesPosting`) ps of
+ thisacctps = tpostings $ filterTransactionPostings thisacctmatcher t
+ numthisacctsposted = length $ nub $ map paccount thisacctps
+ displaymatcher | numthisacctsposted > 1 = matcher
+ | otherwise = MatchAnd [negateMatcher thisacctmatcher, matcher]
+ t'@Transaction{tpostings=ps'} = filterTransactionPostings displaymatcher t
+ (i,bal'') = case ps' of
[] -> (Nothing,bal) -- maybe a virtual transaction, or transfer to self
- [p] -> (Just (t, acct, amt, bal'), bal')
+ [p] -> (Just (t, t', False, acct, amt, bal'), bal')
where
acct = paccount p
amt = pamount p
bal' = bal `sumfn` amt
- ps' -> (Just (t,acct,amt,bal'), bal')
+ ps' -> (Just (t, t', True, acct, amt, bal'), bal')
where
- acct = "SPLIT ("++intercalate ", " (map (accountLeafName . paccount) ps')++")"
+ -- describe split as from ..., to ... (not always right)
+ acct = case (simplify tos, simplify froms) of
+ ([],ts) -> "to "++commafy ts
+ (fs,[]) -> "from "++commafy fs
+ (fs,ts) -> "to "++commafy ts++" from "++commafy fs
+ where (tos,froms) = partition (fromMaybe False . isNegativeMixedAmount . pamount) ps'
+ simplify = nub . map (accountLeafName . paccount)
+ commafy = intercalate ", "
amt = sum $ map pamount ps'
bal' = bal `sumfn` amt
- is = (accountRegisterReportItems ts displaymatcher t bal'' sumfn)
+ is = accountRegisterReportItems matcher thisacctmatcher bal'' sumfn ts
+
+filterTransactionPostings :: Matcher -> Transaction -> Transaction
+filterTransactionPostings m t@Transaction{tpostings=ps} = t{tpostings=filter (m `matchesPosting`) ps}
-- XXX confusing, refactor