From b165f796cc04b48cb0b50e0db51757dcc2226c96 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Fri, 24 Jun 2011 01:29:01 +0000 Subject: [PATCH] web: start txn-centric register refactoring, account register shows most recent first --- .../web/templates/registerreport2.hamlet | 10 ++ .../web/templates/registerreport2item.hamlet | 6 + hledger-web/Handlers.hs | 51 +++++-- hledger/Hledger/Cli/Register.hs | 138 ++++++++++-------- 4 files changed, 138 insertions(+), 67 deletions(-) create mode 100644 hledger-web/.hledger/web/templates/registerreport2.hamlet create mode 100644 hledger-web/.hledger/web/templates/registerreport2item.hamlet diff --git a/hledger-web/.hledger/web/templates/registerreport2.hamlet b/hledger-web/.hledger/web/templates/registerreport2.hamlet new file mode 100644 index 000000000..98b023072 --- /dev/null +++ b/hledger-web/.hledger/web/templates/registerreport2.hamlet @@ -0,0 +1,10 @@ +Date + Description + Account + Amount + #{balancelabel} + + $forall i <- numberRegisterReport2Items items + ^{itemAsHtml vd i} diff --git a/hledger-web/.hledger/web/templates/registerreport2item.hamlet b/hledger-web/.hledger/web/templates/registerreport2item.hamlet new file mode 100644 index 000000000..866ccf28e --- /dev/null +++ b/hledger-web/.hledger/web/templates/registerreport2item.hamlet @@ -0,0 +1,6 @@ +#{date} + #{elideRight 30 desc} + #{elideRight 40 acct} + #{mixedAmountAsHtml amt} + #{mixedAmountAsHtml bal} diff --git a/hledger-web/Handlers.hs b/hledger-web/Handlers.hs index 884a648c5..b6dc347b4 100644 --- a/hledger-web/Handlers.hs +++ b/hledger-web/Handlers.hs @@ -61,9 +61,11 @@ postJournalR = handlePost -- | The main register view, with accounts sidebar. getRegisterR :: Handler RepHtml getRegisterR = do - vd@VD{opts=opts,j=j} <- getViewData + vd@VD{opts=opts,qopts=qopts,m=m,j=j} <- getViewData let sidecontent = balanceReportAsHtml opts vd{q=""} $ balanceReport opts nullfilterspec j - maincontent = registerReportAsHtml opts vd $ accountOrJournalRegisterReport vd j + maincontent = + case inAccountMatcher qopts of Just m' -> registerReport2AsHtml opts vd $ accountRegisterReport opts j m m' + Nothing -> registerReportAsHtml opts vd $ registerReport opts nullfilterspec $ filterJournalPostings2 m j editform' = editform vd defaultLayout $ do setTitle "hledger-web register" @@ -86,19 +88,21 @@ postJournalOnlyR = handlePost -- | A simple postings view, like hledger register (with editing.) getRegisterOnlyR :: Handler RepHtml getRegisterOnlyR = do - vd@VD{opts=opts,j=j} <- getViewData + vd@VD{opts=opts,qopts=qopts,m=m,j=j} <- getViewData defaultLayout $ do setTitle "hledger-web register only" - addHamlet $ registerReportAsHtml opts vd $ accountOrJournalRegisterReport vd j + addHamlet $ + case inAccountMatcher qopts of Just m' -> registerReport2AsHtml opts vd $ accountRegisterReport opts j m m' + Nothing -> registerReportAsHtml opts vd $ registerReport opts nullfilterspec $ filterJournalPostings2 m j postRegisterOnlyR :: Handler RepPlain postRegisterOnlyR = handlePost --- temporary helper - use the new account register report when in:ACCT is specified. -accountOrJournalRegisterReport :: ViewData -> Journal -> RegisterReport -accountOrJournalRegisterReport VD{opts=opts,m=m,qopts=qopts} j = - case inAccountMatcher qopts of Just m' -> accountRegisterReport opts j m m' - Nothing -> registerReport opts nullfilterspec $ filterJournalPostings2 m j +-- -- temporary helper - use the new account register report when in:ACCT is specified. +-- accountOrJournalRegisterReport :: ViewData -> Journal -> RegisterReport +-- accountOrJournalRegisterReport VD{opts=opts,m=m,qopts=qopts} j = +-- case inAccountMatcher qopts of Just m' -> accountRegisterReport opts j m m' +-- Nothing -> registerReport opts nullfilterspec $ filterJournalPostings2 m j -- | A simple accounts view, like hledger balance. If the Accept header -- specifies json, returns the chart of accounts as json. @@ -167,6 +171,34 @@ registerReportAsHtml _ vd (balancelabel,items) = $(Settings.hamletFile "register acct = paccount posting accturl = (here, [("q", pack $ accountUrl acct)]) +-- mark II +registerReport2AsHtml :: [Opt] -> ViewData -> RegisterReport2 -> Hamlet AppRoute +registerReport2AsHtml _ vd (balancelabel,items) = $(Settings.hamletFile "registerreport2") + where + itemAsHtml :: ViewData -> (Int, Bool, Bool, Bool, RegisterReport2Item) -> Hamlet AppRoute + itemAsHtml VD{here=here} (n, newd, newm, newy, (t, acct, amt, bal)) = $(Settings.hamletFile "registerreport2item") + 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)]) + +numberRegisterReport2Items :: [RegisterReport2Item] -> [(Int,Bool,Bool,Bool,RegisterReport2Item)] +numberRegisterReport2Items [] = [] +numberRegisterReport2Items is = number 0 nulldate is + where + number :: Int -> Day -> [RegisterReport2Item] -> [(Int,Bool,Bool,Bool,RegisterReport2Item)] + number _ _ [] = [] + 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 + newyear = dy/=prevdy + (dy,dm,_) = toGregorian d + (prevdy,prevdm,_) = toGregorian prevd + mixedAmountAsHtml b = preEscapedString $ addclass $ intercalate "
" $ lines $ show b where addclass = printf "%s" (c :: String) c = case isNegativeMixedAmount b of Just True -> "negative amount" @@ -468,3 +500,4 @@ numberTransactions is = number 0 nulldate is newyear = dy/=prevdy (dy,dm,_) = toGregorian d (prevdy,prevdm,_) = toGregorian prevd + diff --git a/hledger/Hledger/Cli/Register.hs b/hledger/Hledger/Cli/Register.hs index 952c8cf1e..de18e19bd 100644 --- a/hledger/Hledger/Cli/Register.hs +++ b/hledger/Hledger/Cli/Register.hs @@ -8,6 +8,8 @@ A ledger-compatible @register@ command. module Hledger.Cli.Register ( RegisterReport ,RegisterReportItem + ,RegisterReport2 + ,RegisterReport2Item ,register ,registerReport ,accountRegisterReport @@ -48,6 +50,17 @@ type RegisterReportItem = (Maybe (Day, String) -- transaction date and descripti ,MixedAmount -- balance so far ) +-- | Register report mark II, used in hledger-web's account register (see "accountRegisterReport". +type RegisterReport2 = (String -- a possibly null label for the running balance column + ,[RegisterReport2Item] -- line items, one per transaction + ) +-- | A single register report 2 line item, representing one transaction to/from the focussed account. +type RegisterReport2Item = (Transaction -- the corresponding transaction + ,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 + ) + -- | Print a register report. register :: [Opt] -> [String] -> Journal -> IO () register opts args j = do @@ -82,7 +95,73 @@ registerReportItemAsText _ (dd, p, b) = concatTopPadded [datedesc, pstr, " ", ba showPostingWithBalanceForVty showtxninfo p b = registerReportItemAsText [] $ mkitem showtxninfo p b --- | Get a register report with the specified options for this journal. +totallabel = "Total" +balancelabel = "Balance" + +-- | Get an account register report with the specified options for this +-- journal. An account register report is like the traditional account +-- register seen in bank statements and personal finance programs. It is +-- focussed on one account only; it shows this account's transactions' +-- postings to other accounts; and if there is no transaction filtering in +-- effect other than a start date, it shows a historically-accurate +-- running balance for this account. Once additional filters are applied, +-- the running balance reverts to a running total starting at 0. +-- Does not handle reporting intervals. +-- Items are returned most recent first. +accountRegisterReport :: [Opt] -> Journal -> Matcher -> Matcher -> RegisterReport2 +accountRegisterReport opts j m thisacctmatcher = (label, items) + where + -- | interval == NoInterval = items + -- | otherwise = summarisePostingsByInterval interval depth empty filterspan displayps + + -- 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,(-)) + | matcherIsStartDateOnly effective m = (sumPostings priorps,balancelabel,(-)) + | otherwise = (nullmixedamt,totallabel,(+)) + where + priorps = -- ltrace "priorps" $ + filter (matchesPosting + (-- ltrace "priormatcher" $ + MatchAnd [thisacctmatcher, tostartdatematcher])) + $ transactionsPostings ts + 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 + +-- | 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) -> [RegisterReport2Item] +accountRegisterReportItems [] _ _ _ _ = [] +accountRegisterReportItems (t@Transaction{tpostings=ps}:ts) displaymatcher _ bal sumfn = + case i of Just i' -> i':is + Nothing -> is + where + (i,bal'') = case filter (displaymatcher `matchesPosting`) ps of + [] -> (Nothing,bal) -- maybe a virtual transaction, or transfer to self + [p] -> (Just (t, acct, amt, bal'), bal') + where + acct = paccount p + amt = pamount p + bal' = bal `sumfn` amt + ps' -> (Just (t,acct,amt,bal'), bal') + where + acct = "SPLIT ("++intercalate ", " (map (accountLeafName . paccount) ps')++")" + amt = sum $ map pamount ps' + bal' = bal `sumfn` amt + is = (accountRegisterReportItems ts displaymatcher t bal'' sumfn) + +-- | Get a traditional register report with the specified options for this journal. -- This is a journal register report, covering the whole journal like -- ledger's register command; for an account-specific register see -- accountRegisterReport. @@ -102,63 +181,6 @@ registerReport opts fspec j = (totallabel,postingsToRegisterReportItems ps nullp filterspan = datespan fspec (interval, depth, empty) = (intervalFromOpts opts, depthFromOpts opts, Empty `elem` opts) --- | Get an account register report with the specified options for this --- journal. An account register report is like the traditional account --- register seen in bank statements and personal finance programs. It is --- focussed on one account only; it shows this account's transactions' --- postings to other accounts; and if there is no transaction filtering in --- effect other than a start date, it shows a historically-accurate --- running balance for this account. Once additional filters are applied, --- the running balance reverts to a running total starting at 0. --- --- Does not handle reporting intervals. --- -accountRegisterReport :: [Opt] -> Journal -> Matcher -> Matcher -> RegisterReport -accountRegisterReport opts j m thisacctmatcher = (label, postingsToRegisterReportItems displayps nullposting startbal sumfn) - where - -- displayps' | interval == NoInterval = displayps - -- | otherwise = summarisePostingsByInterval interval depth empty filterspan displayps - - -- transactions affecting this account - 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,(-)) - | matcherIsStartDateOnly effective m = (sumPostings priorps,balancelabel,(-)) - | otherwise = (nullmixedamt,totallabel,(+)) - where - priorps = -- ltrace "priorps" $ - filter (matchesPosting - (-- ltrace "priormatcher" $ - MatchAnd [thisacctmatcher, tostartdatematcher])) - $ transactionsPostings ts - tostartdatematcher = MatchDate True (DateSpan Nothing startdate) - startdate = matcherStartDate effective m - effective = Effective `elem` opts - - -- postings to display: this account's transactions' "other" postings, with any additional filter applied - -- XXX would be better to collapse multiple postings from one txn into one (expandable) "split" item - displayps = -- ltrace "displayps" $ - catMaybes $ map displayPostingFromTransaction ts - - displaymatcher = -- ltrace "displaymatcher" $ - MatchAnd [negateMatcher thisacctmatcher, m] - - -- get the other account posting from this transaction, or if there - -- is more than one make a dummy posting indicating that - displayPostingFromTransaction :: Transaction -> Maybe Posting - displayPostingFromTransaction Transaction{tpostings=ps} = - case filter (displaymatcher `matchesPosting`) ps of - [] -> Nothing -- a virtual transaction, maybe - [p] -> Just p - ps'@(p':_) -> Just p'{paccount=splitdesc,pamount=splitamt} - where splitdesc = "SPLIT ("++intercalate ", " (map (accountLeafName . paccount) ps')++")" - splitamt = sum $ map pamount ps' - -totallabel = "Total" -balancelabel = "Balance" - -- | Generate register report line items. postingsToRegisterReportItems :: [Posting] -> Posting -> MixedAmount -> (MixedAmount -> MixedAmount -> MixedAmount) -> [RegisterReportItem] postingsToRegisterReportItems [] _ _ _ = []