mirror of
https://github.com/simonmichael/hledger.git
synced 2024-11-08 07:09:28 +03:00
web: make journal register work like account registers; show multiple postings
This commit is contained in:
parent
1fe21904eb
commit
ec426d620c
@ -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(); });
|
||||
|
||||
});
|
||||
|
||||
|
@ -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; }
|
||||
|
@ -1,6 +1,24 @@
|
||||
<tr.item.#{evenodd}.#{firstposting}.#{datetransition}
|
||||
<td.date>#{date}
|
||||
<td.description title="#{desc}">#{elideRight 30 desc}
|
||||
<td.account><a href="@?{accturl}" title="#{acct}">#{elideRight 40 acct}
|
||||
<td.amount align=right>#{mixedAmountAsHtml amt}
|
||||
<td.description title="#{show t}">#{elideRight 30 desc}
|
||||
<td.account>
|
||||
$if split
|
||||
<a title="#{acct}"
|
||||
#{elideRight 40 acct}
|
||||
|
||||
<a.postings-toggle-link.togglelink href="#" title="Toggle postings"
|
||||
[+/-]
|
||||
$else
|
||||
<a href="@?{acctquery}" title="Go to #{acct}">#{elideRight 40 acct}
|
||||
<td.amount align=right>
|
||||
$if showamt
|
||||
#{mixedAmountAsHtml amt}
|
||||
<td.balance align=right>#{mixedAmountAsHtml bal}
|
||||
$if split
|
||||
$forall p <- tpostings t'
|
||||
<tr.item.#{evenodd}.posting
|
||||
<td.date
|
||||
<td.description
|
||||
<td.account> <a href="@?{accountUrl here $ paccount p}" title="#{stringIfLongerThan 40 $ paccount p}">#{elideRight 40 $ paccount p}
|
||||
<td.amount align=right>#{mixedAmountAsHtml $ pamount p}
|
||||
<td.balance align=right>
|
||||
|
@ -1,12 +1,13 @@
|
||||
<tr.item.#{inacctclass}
|
||||
<td.account.#{depthclass}
|
||||
#{indent}
|
||||
<a href="@?{accturl}">#{adisplay}
|
||||
<a href="@?{acctquery}" title="Focus on this account">#{adisplay}
|
||||
<span.accountextralinks
|
||||
|
||||
<a href="@?{acctsurl}">+subs
|
||||
|
||||
<a href="@?{acctsonlyurl}">-others
|
||||
<a href="@?{acctsquery}" title="Focus on this account and sub-accounts">+subs
|
||||
<!--
|
||||
|
||||
<a href="@?{acctsonlyquery}" title="Focus on this account and sub-accounts and hide others">-others -->
|
||||
|
||||
<td.balance align=right>#{mixedAmountAsHtml abal}
|
||||
<td.numpostings align=right>(#{numpostingsinacct acct})
|
||||
|
@ -1,6 +1,6 @@
|
||||
<tr.item.#{evenodd}.#{firstposting}.#{datetransition}
|
||||
<td.date>#{date}
|
||||
<td.description title="#{desc}">#{elideRight 30 desc}
|
||||
<td.account><a href="@?{accturl}" title="#{acct}">#{elideRight 40 acct}
|
||||
<td.account><a href="@?{acctquery}" title="#{acct}">#{elideRight 40 acct}
|
||||
<td.amount align=right>#{mixedAmountAsHtml $ pamount posting}
|
||||
<td.balance align=right>#{mixedAmountAsHtml b}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user