From d1f63334eeeb0b99cc0887d4ffa62cd3313f2e49 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Sat, 16 May 2015 11:51:35 -0700 Subject: [PATCH] handle pending status correctly, add --pending (#250) A transaction/posting status of ! (pending) was effectively equivalent to * (cleared). Now it's a separate state, not matched by --cleared. The new Ledger-compatible --pending flag matches it, and so does --uncleared. The equivalent search queries are now status:*, status:! and status: (the old status:1 and status:0 spellings are deprecated). Since we interpret --uncleared and status: as "any state except cleared", it's not currently possible to match things which are neither cleared nor pending. --- doc/manual.md | 5 +- hledger-lib/Hledger/Data/Journal.hs | 10 ++-- hledger-lib/Hledger/Data/Posting.hs | 34 ++++++++---- hledger-lib/Hledger/Data/TimeLog.hs | 2 +- hledger-lib/Hledger/Data/Transaction.hs | 52 ++++++++++--------- hledger-lib/Hledger/Data/Types.hs | 14 +++-- hledger-lib/Hledger/Query.hs | 42 ++++++++------- hledger-lib/Hledger/Read/CsvReader.hs | 12 ++++- hledger-lib/Hledger/Read/JournalReader.hs | 19 ++++--- hledger-lib/Hledger/Reports/BalanceReport.hs | 2 +- hledger-lib/Hledger/Reports/PostingsReport.hs | 4 +- hledger-lib/Hledger/Reports/ReportOptions.hs | 10 ++-- .../templates/default-layout-wrapper.hamlet | 2 +- hledger/Hledger/Cli.hs | 12 ++--- hledger/Hledger/Cli/Add.hs | 2 +- hledger/Hledger/Cli/Options.hs | 5 +- hledger/Hledger/Cli/Print.hs | 4 +- 17 files changed, 141 insertions(+), 90 deletions(-) diff --git a/doc/manual.md b/doc/manual.md index a8608c029..b06473d6e 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -840,7 +840,8 @@ General flags: all at once (overrides the flags above) --date2 --aux-date use postings/txns' secondary dates instead -C --cleared include only cleared postings/txns - -U --uncleared include only uncleared postings/txns + --pending include only pending postings/txns + -U --uncleared include only uncleared (and pending) postings/txns -R --real include only non-virtual postings --depth=N hide accounts/postings deeper than N -E --empty show empty/zero things which are normally omitted @@ -962,7 +963,7 @@ A query term can be any of the following: - `date2:PERIODEXPR` - as above, but match secondary dates - `tag:NAME[=REGEX]` - match by (exact, case sensitive) [tag](#tags) name, and optionally match the tag value by regular expression. Note `tag:` will match a transaction if it or any its postings have the tag, and will match posting if it or its parent transaction has the tag. - `depth:N` - match (or display, depending on command) accounts at or above this [depth](#depth-limiting) -- `status:1` or `status:0` - match pending/cleared or uncleared transactions respectively +- `status:*` or `status:!` or `status:` - match cleared, pending, or uncleared/pending transactions respectively - `real:1` or `real:0` - match real/virtual-ness - `empty:1` or `empty:0` - match if amount is/is not zero - `amt:N`, `amt:N`, `amt:>=N` - match postings with a single-commodity diff --git a/hledger-lib/Hledger/Data/Journal.hs b/hledger-lib/Hledger/Data/Journal.hs index 9f8d6b45b..bf28637ba 100644 --- a/hledger-lib/Hledger/Data/Journal.hs +++ b/hledger-lib/Hledger/Data/Journal.hs @@ -674,7 +674,7 @@ Right samplejournal = journalBalanceTransactions $ tsourcepos=nullsourcepos, tdate=parsedate "2008/01/01", tdate2=Nothing, - tstatus=False, + tstatus=Uncleared, tcode="", tdescription="income", tcomment="", @@ -690,7 +690,7 @@ Right samplejournal = journalBalanceTransactions $ tsourcepos=nullsourcepos, tdate=parsedate "2008/06/01", tdate2=Nothing, - tstatus=False, + tstatus=Uncleared, tcode="", tdescription="gift", tcomment="", @@ -706,7 +706,7 @@ Right samplejournal = journalBalanceTransactions $ tsourcepos=nullsourcepos, tdate=parsedate "2008/06/02", tdate2=Nothing, - tstatus=False, + tstatus=Uncleared, tcode="", tdescription="save", tcomment="", @@ -722,7 +722,7 @@ Right samplejournal = journalBalanceTransactions $ tsourcepos=nullsourcepos, tdate=parsedate "2008/06/03", tdate2=Nothing, - tstatus=True, + tstatus=Cleared, tcode="", tdescription="eat & shop", tcomment="", @@ -738,7 +738,7 @@ Right samplejournal = journalBalanceTransactions $ tsourcepos=nullsourcepos, tdate=parsedate "2008/12/31", tdate2=Nothing, - tstatus=False, + tstatus=Uncleared, tcode="", tdescription="pay off", tcomment="", diff --git a/hledger-lib/Hledger/Data/Posting.hs b/hledger-lib/Hledger/Data/Posting.hs index 41faefa12..5f5fe302d 100644 --- a/hledger-lib/Hledger/Data/Posting.hs +++ b/hledger-lib/Hledger/Data/Posting.hs @@ -13,7 +13,7 @@ module Hledger.Data.Posting ( posting, post, -- * operations - postingCleared, + postingStatus, isReal, isVirtual, isBalancedVirtual, @@ -67,7 +67,7 @@ nullposting, posting :: Posting nullposting = Posting {pdate=Nothing ,pdate2=Nothing - ,pstatus=False + ,pstatus=Uncleared ,paccount="" ,pamount=nullmixedamt ,pcomment="" @@ -137,14 +137,28 @@ postingDate2 p = headDef nulldate $ catMaybes dates ,maybe Nothing (Just . tdate) $ ptransaction p ] --- |Is this posting cleared? If this posting was individually marked --- as cleared, returns True. Otherwise, return the parent --- transaction's cleared status or, if there is no parent --- transaction, return False. -postingCleared :: Posting -> Bool -postingCleared p = if pstatus p - then True - else maybe False tstatus $ ptransaction p +-- | Get a posting's cleared status: cleared or pending if explicitly set, +-- otherwise the cleared status of its parent transaction, or uncleared +-- if there is no parent transaction. +-- (Note Uncleared's ambiguity, can mean "uncleared" or "don't know". +postingStatus :: Posting -> ClearedStatus +postingStatus Posting{pstatus=s, ptransaction=mt} + | s == Uncleared = case mt of Just t -> tstatus t + Nothing -> Uncleared + | otherwise = s + +-- -- | Is this posting cleared? True if the posting is explicitly marked +-- -- cleared, false if it is marked pending, otherwise true if the +-- -- parent transaction is marked cleared or false if there is no parent +-- -- transaction. +-- -- (Note Uncleared's ambiguity, can mean "uncleared" or "don't know". +-- postingIsCleared :: Posting -> Bool +-- postingIsCleared p +-- | pstatus p == Cleared = True +-- | pstatus p == Pending = False +-- | otherwise = case ptransaction p of +-- Just t -> tstatus t == Cleared +-- Nothing -> False -- | Tags for this posting including any inherited from its parent transaction. postingAllTags :: Posting -> [Tag] diff --git a/hledger-lib/Hledger/Data/TimeLog.hs b/hledger-lib/Hledger/Data/TimeLog.hs index b381c783b..1e41c16c0 100644 --- a/hledger-lib/Hledger/Data/TimeLog.hs +++ b/hledger-lib/Hledger/Data/TimeLog.hs @@ -82,7 +82,7 @@ entryFromTimeLogInOut i o tsourcepos = tlsourcepos i, tdate = idate, tdate2 = Nothing, - tstatus = True, + tstatus = Cleared, tcode = "", tdescription = desc, tcomment = "", diff --git a/hledger-lib/Hledger/Data/Transaction.hs b/hledger-lib/Hledger/Data/Transaction.hs index a97d3c039..8203a34a9 100644 --- a/hledger-lib/Hledger/Data/Transaction.hs +++ b/hledger-lib/Hledger/Data/Transaction.hs @@ -64,7 +64,7 @@ nulltransaction = Transaction { tsourcepos=nullsourcepos, tdate=nulldate, tdate2=Nothing, - tstatus=False, + tstatus=Uncleared, tcode="", tdescription="", tcomment="", @@ -102,14 +102,14 @@ tests_showTransactionUnelided = [ nulltransaction{ tdate=parsedate "2012/05/14", tdate2=Just $ parsedate "2012/05/15", - tstatus=False, + tstatus=Uncleared, tcode="code", tdescription="desc", tcomment="tcomment1\ntcomment2\n", ttags=[("ttag1","val1")], tpostings=[ nullposting{ - pstatus=True, + pstatus=Cleared, paccount="a", pamount=Mixed [usd 1, hrs 2], pcomment="\npcomment2\n", @@ -140,7 +140,9 @@ showTransaction' elide t = date = showdate (tdate t) ++ maybe "" showedate (tdate2 t) showdate = printf "%-10s" . showDate showedate = printf "=%s" . showdate - status = if tstatus t then " *" else "" + status | tstatus t == Cleared = " *" + | tstatus t == Pending = " !" + | otherwise = "" code = if length (tcode t) > 0 then printf " (%s)" $ tcode t else "" desc = if null d then "" else " " ++ d where d = tdescription t (samelinecomment, newlinecomments) = @@ -184,7 +186,7 @@ postingAsLines elideamount ps p = showacct p = indent $ showstatus p ++ printf (printf "%%-%ds" w) (showAccountName Nothing (ptype p) (paccount p)) where - showstatus p = if pstatus p then "* " else "" + showstatus p = if pstatus p == Cleared then "* " else "" w = maximum $ map (length . paccount) ps showamt = padleft 12 . showMixedAmount @@ -194,7 +196,7 @@ tests_postingAsLines = [ let p `gives` ls = assertEqual "" ls (postingAsLines False [p] p) posting `gives` [" 0"] posting{ - pstatus=True, + pstatus=Cleared, paccount="a", pamount=Mixed [usd 1, hrs 2], pcomment="pcomment1\npcomment2\n tag3: val3 \n", @@ -382,7 +384,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [ ," assets:checking" ,"" ]) - (let t = Transaction nullsourcepos (parsedate "2007/01/28") Nothing False "" "coopportunity" "" [] + (let t = Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" [] [posting{paccount="expenses:food:groceries", pamount=Mixed [usd 47.18], ptransaction=Just t} ,posting{paccount="assets:checking", pamount=Mixed [usd (-47.18)], ptransaction=Just t} ] "" @@ -396,7 +398,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [ ," assets:checking $-47.18" ,"" ]) - (let t = Transaction nullsourcepos (parsedate "2007/01/28") Nothing False "" "coopportunity" "" [] + (let t = Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" [] [posting{paccount="expenses:food:groceries", pamount=Mixed [usd 47.18], ptransaction=Just t} ,posting{paccount="assets:checking", pamount=Mixed [usd (-47.18)], ptransaction=Just t} ] "" @@ -412,7 +414,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [ ,"" ]) (showTransaction - (txnTieKnot $ Transaction nullsourcepos (parsedate "2007/01/28") Nothing False "" "coopportunity" "" [] + (txnTieKnot $ Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" [] [posting{paccount="expenses:food:groceries", pamount=Mixed [usd 47.18]} ,posting{paccount="assets:checking", pamount=Mixed [usd (-47.19)]} ] "")) @@ -425,7 +427,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [ ,"" ]) (showTransaction - (txnTieKnot $ Transaction nullsourcepos (parsedate "2007/01/28") Nothing False "" "coopportunity" "" [] + (txnTieKnot $ Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" [] [posting{paccount="expenses:food:groceries", pamount=Mixed [usd 47.18]} ] "")) @@ -437,7 +439,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [ ,"" ]) (showTransaction - (txnTieKnot $ Transaction nullsourcepos (parsedate "2007/01/28") Nothing False "" "coopportunity" "" [] + (txnTieKnot $ Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "coopportunity" "" [] [posting{paccount="expenses:food:groceries", pamount=missingmixedamt} ] "")) @@ -450,7 +452,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [ ,"" ]) (showTransaction - (txnTieKnot $ Transaction nullsourcepos (parsedate "2010/01/01") Nothing False "" "x" "" [] + (txnTieKnot $ Transaction nullsourcepos (parsedate "2010/01/01") Nothing Uncleared "" "x" "" [] [posting{paccount="a", pamount=Mixed [num 1 `at` (usd 2 `withPrecision` 0)]} ,posting{paccount="b", pamount= missingmixedamt} ] "")) @@ -458,19 +460,19 @@ tests_Hledger_Data_Transaction = TestList $ concat [ ,"balanceTransaction" ~: do assertBool "detect unbalanced entry, sign error" (isLeft $ balanceTransaction Nothing - (Transaction nullsourcepos (parsedate "2007/01/28") Nothing False "" "test" "" [] + (Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "test" "" [] [posting{paccount="a", pamount=Mixed [usd 1]} ,posting{paccount="b", pamount=Mixed [usd 1]} ] "")) assertBool "detect unbalanced entry, multiple missing amounts" (isLeft $ balanceTransaction Nothing - (Transaction nullsourcepos (parsedate "2007/01/28") Nothing False "" "test" "" [] + (Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "test" "" [] [posting{paccount="a", pamount=missingmixedamt} ,posting{paccount="b", pamount=missingmixedamt} ] "")) - let e = balanceTransaction Nothing (Transaction nullsourcepos (parsedate "2007/01/28") Nothing False "" "" "" [] + let e = balanceTransaction Nothing (Transaction nullsourcepos (parsedate "2007/01/28") Nothing Uncleared "" "" "" [] [posting{paccount="a", pamount=Mixed [usd 1]} ,posting{paccount="b", pamount=missingmixedamt} ] "") @@ -481,7 +483,7 @@ tests_Hledger_Data_Transaction = TestList $ concat [ Right e' -> (pamount $ last $ tpostings e') Left _ -> error' "should not happen") - let e = balanceTransaction Nothing (Transaction nullsourcepos (parsedate "2011/01/01") Nothing False "" "" "" [] + let e = balanceTransaction Nothing (Transaction nullsourcepos (parsedate "2011/01/01") Nothing Uncleared "" "" "" [] [posting{paccount="a", pamount=Mixed [usd 1.35]} ,posting{paccount="b", pamount=Mixed [eur (-1)]} ] "") @@ -493,49 +495,49 @@ tests_Hledger_Data_Transaction = TestList $ concat [ Left _ -> error' "should not happen") assertBool "balanceTransaction balances based on cost if there are unit prices" (isRight $ - balanceTransaction Nothing (Transaction nullsourcepos (parsedate "2011/01/01") Nothing False "" "" "" [] + balanceTransaction Nothing (Transaction nullsourcepos (parsedate "2011/01/01") Nothing Uncleared "" "" "" [] [posting{paccount="a", pamount=Mixed [usd 1 `at` eur 2]} ,posting{paccount="a", pamount=Mixed [usd (-2) `at` eur 1]} ] "")) assertBool "balanceTransaction balances based on cost if there are total prices" (isRight $ - balanceTransaction Nothing (Transaction nullsourcepos (parsedate "2011/01/01") Nothing False "" "" "" [] + balanceTransaction Nothing (Transaction nullsourcepos (parsedate "2011/01/01") Nothing Uncleared "" "" "" [] [posting{paccount="a", pamount=Mixed [usd 1 @@ eur 1]} ,posting{paccount="a", pamount=Mixed [usd (-2) @@ eur 1]} ] "")) ,"isTransactionBalanced" ~: do - let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing False "" "a" "" [] + let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" [] [posting{paccount="b", pamount=Mixed [usd 1.00], ptransaction=Just t} ,posting{paccount="c", pamount=Mixed [usd (-1.00)], ptransaction=Just t} ] "" assertBool "detect balanced" (isTransactionBalanced Nothing t) - let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing False "" "a" "" [] + let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" [] [posting{paccount="b", pamount=Mixed [usd 1.00], ptransaction=Just t} ,posting{paccount="c", pamount=Mixed [usd (-1.01)], ptransaction=Just t} ] "" assertBool "detect unbalanced" (not $ isTransactionBalanced Nothing t) - let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing False "" "a" "" [] + let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" [] [posting{paccount="b", pamount=Mixed [usd 1.00], ptransaction=Just t} ] "" assertBool "detect unbalanced, one posting" (not $ isTransactionBalanced Nothing t) - let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing False "" "a" "" [] + let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" [] [posting{paccount="b", pamount=Mixed [usd 0], ptransaction=Just t} ] "" assertBool "one zero posting is considered balanced for now" (isTransactionBalanced Nothing t) - let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing False "" "a" "" [] + let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" [] [posting{paccount="b", pamount=Mixed [usd 1.00], ptransaction=Just t} ,posting{paccount="c", pamount=Mixed [usd (-1.00)], ptransaction=Just t} ,posting{paccount="d", pamount=Mixed [usd 100], ptype=VirtualPosting, ptransaction=Just t} ] "" assertBool "virtual postings don't need to balance" (isTransactionBalanced Nothing t) - let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing False "" "a" "" [] + let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" [] [posting{paccount="b", pamount=Mixed [usd 1.00], ptransaction=Just t} ,posting{paccount="c", pamount=Mixed [usd (-1.00)], ptransaction=Just t} ,posting{paccount="d", pamount=Mixed [usd 100], ptype=BalancedVirtualPosting, ptransaction=Just t} ] "" assertBool "balanced virtual postings need to balance among themselves" (not $ isTransactionBalanced Nothing t) - let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing False "" "a" "" [] + let t = Transaction nullsourcepos (parsedate "2009/01/01") Nothing Uncleared "" "a" "" [] [posting{paccount="b", pamount=Mixed [usd 1.00], ptransaction=Just t} ,posting{paccount="c", pamount=Mixed [usd (-1.00)], ptransaction=Just t} ,posting{paccount="d", pamount=Mixed [usd 100], ptype=BalancedVirtualPosting, ptransaction=Just t} diff --git a/hledger-lib/Hledger/Data/Types.hs b/hledger-lib/Hledger/Data/Types.hs index 57d906705..c45641661 100644 --- a/hledger-lib/Hledger/Data/Types.hs +++ b/hledger-lib/Hledger/Data/Types.hs @@ -5,7 +5,7 @@ Most data types are defined here to avoid import cycles. Here is an overview of the hledger data model: > Journal -- a journal is read from one or more data files. It contains.. -> [Transaction] -- journal transactions (aka entries), which have date, status, code, description and.. +> [Transaction] -- journal transactions (aka entries), which have date, cleared status, code, description and.. > [Posting] -- multiple account postings, which have account name and amount > [HistoricalPrice] -- historical commodity prices > @@ -116,10 +116,18 @@ data PostingType = RegularPosting | VirtualPosting | BalancedVirtualPosting type Tag = (String, String) -- ^ A tag name and (possibly empty) value. +data ClearedStatus = Uncleared | Pending | Cleared + deriving (Eq,Ord,Typeable,Data) + +instance Show ClearedStatus where -- custom show + show Uncleared = "" -- a bad idea + show Pending = "!" -- don't do it + show Cleared = "*" + data Posting = Posting { pdate :: Maybe Day, -- ^ this posting's date, if different from the transaction's pdate2 :: Maybe Day, -- ^ this posting's secondary date, if different from the transaction's - pstatus :: Bool, + pstatus :: ClearedStatus, paccount :: AccountName, pamount :: MixedAmount, pcomment :: String, -- ^ this posting's comment lines, as a single non-indented multi-line string @@ -139,7 +147,7 @@ data Transaction = Transaction { tsourcepos :: SourcePos, tdate :: Day, tdate2 :: Maybe Day, - tstatus :: Bool, -- XXX tcleared ? + tstatus :: ClearedStatus, tcode :: String, tdescription :: String, tcomment :: String, -- ^ this transaction's comment lines, as a single non-indented multi-line string diff --git a/hledger-lib/Hledger/Query.hs b/hledger-lib/Hledger/Query.hs index b2bb895e6..ce53425db 100644 --- a/hledger-lib/Hledger/Query.hs +++ b/hledger-lib/Hledger/Query.hs @@ -73,7 +73,7 @@ data Query = Any -- ^ always match | Acct String -- ^ match postings whose account matches this regexp | Date DateSpan -- ^ match if primary date in this date span | Date2 DateSpan -- ^ match if secondary date in this date span - | Status Bool -- ^ match if cleared status has this value + | Status ClearedStatus -- ^ match if cleared status has this value | Real Bool -- ^ match if "realness" (involves a real non-virtual account ?) has this value | Amt OrdPlus Quantity -- ^ match if the amount's numeric quantity is less than/greater than/equal to/unsignedly equal to some value | Sym String -- ^ match if the entire commodity symbol is matched by this regexp @@ -265,9 +265,11 @@ tests_parseQueryTerm = [ "a" `gives` (Left $ Acct "a") "acct:expenses:autres d\233penses" `gives` (Left $ Acct "expenses:autres d\233penses") "not:desc:a b" `gives` (Left $ Not $ Desc "a b") - "status:1" `gives` (Left $ Status True) - "status:0" `gives` (Left $ Status False) - "status:" `gives` (Left $ Status False) + "status:1" `gives` (Left $ Status Cleared) + "status:*" `gives` (Left $ Status Cleared) + "status:!" `gives` (Left $ Status Pending) + "status:0" `gives` (Left $ Status Uncleared) + "status:" `gives` (Left $ Status Uncleared) "real:1" `gives` (Left $ Real True) "date:2008" `gives` (Left $ Date $ DateSpan (Just $ parsedate "2008/01/01") (Just $ parsedate "2009/01/01")) "date:from 2012/5/17" `gives` (Left $ Date $ DateSpan (Just $ parsedate "2012/05/17") Nothing) @@ -333,8 +335,10 @@ parseTag s | '=' `elem` s = (n, Just $ tail v) -- -- , treating "*" or "!" as synonyms for "1". -- | Parse the boolean value part of a "status:" query. -parseStatus :: String -> Bool -parseStatus s = s `elem` (truestrings) -- ++ ["*","!"]) +parseStatus :: String -> ClearedStatus +parseStatus s | s `elem` ["1","*"] = Cleared + | s == "!" = Pending + | otherwise = Uncleared -- | Parse the boolean value part of a "status:" query. "1" means true, -- anything else will be parsed as false without error. @@ -399,7 +403,7 @@ tests_filterQuery = [ let (q,p) `gives` r = assertEqual "" r (filterQuery p q) (Any, queryIsDepth) `gives` Any (Depth 1, queryIsDepth) `gives` Depth 1 - (And [And [Status True,Depth 1]], not . queryIsDepth) `gives` Status True + (And [And [Status Cleared,Depth 1]], not . queryIsDepth) `gives` Status Cleared -- (And [Date nulldatespan, Not (Or [Any, Depth 1])], queryIsDepth) `gives` And [Not (Or [Depth 1])] ] @@ -643,7 +647,8 @@ matchesPosting (Desc r) p = regexMatchesCI r $ maybe "" tdescription $ ptransact matchesPosting (Acct r) p = regexMatchesCI r $ paccount p matchesPosting (Date span) p = span `spanContainsDate` postingDate p matchesPosting (Date2 span) p = span `spanContainsDate` postingDate2 p -matchesPosting (Status v) p = v == postingCleared p +matchesPosting (Status Uncleared) p = postingStatus p /= Cleared +matchesPosting (Status s) p = postingStatus p == s matchesPosting (Real v) p = v == isReal p matchesPosting q@(Depth _) Posting{paccount=a} = q `matchesAccount` a matchesPosting q@(Amt _ _) Posting{pamount=amt} = q `matchesMixedAmount` amt @@ -660,16 +665,16 @@ matchesPosting (Tag n (Just v)) p = isJust $ lookupTagByNameAndValue (n,v) $ pos tests_matchesPosting = [ "matchesPosting" ~: do -- matching posting status.. - assertBool "positive match on true posting status" $ - (Status True) `matchesPosting` nullposting{pstatus=True} - assertBool "negative match on true posting status" $ - not $ (Not $ Status True) `matchesPosting` nullposting{pstatus=True} - assertBool "positive match on false posting status" $ - (Status False) `matchesPosting` nullposting{pstatus=False} - assertBool "negative match on false posting status" $ - not $ (Not $ Status False) `matchesPosting` nullposting{pstatus=False} + assertBool "positive match on cleared posting status" $ + (Status Cleared) `matchesPosting` nullposting{pstatus=Cleared} + assertBool "negative match on cleared posting status" $ + not $ (Not $ Status Cleared) `matchesPosting` nullposting{pstatus=Cleared} + assertBool "positive match on unclered posting status" $ + (Status Uncleared) `matchesPosting` nullposting{pstatus=Uncleared} + assertBool "negative match on unclered posting status" $ + not $ (Not $ Status Uncleared) `matchesPosting` nullposting{pstatus=Uncleared} assertBool "positive match on true posting status acquired from transaction" $ - (Status True) `matchesPosting` nullposting{pstatus=False,ptransaction=Just nulltransaction{tstatus=True}} + (Status Cleared) `matchesPosting` nullposting{pstatus=Uncleared,ptransaction=Just nulltransaction{tstatus=Cleared}} assertBool "real:1 on real posting" $ (Real True) `matchesPosting` nullposting{ptype=RegularPosting} assertBool "real:1 on virtual posting fails" $ not $ (Real True) `matchesPosting` nullposting{ptype=VirtualPosting} assertBool "real:1 on balanced virtual posting fails" $ not $ (Real True) `matchesPosting` nullposting{ptype=BalancedVirtualPosting} @@ -701,7 +706,8 @@ matchesTransaction (Desc r) t = regexMatchesCI r $ tdescription t matchesTransaction q@(Acct _) t = any (q `matchesPosting`) $ tpostings t matchesTransaction (Date span) t = spanContainsDate span $ tdate t matchesTransaction (Date2 span) t = spanContainsDate span $ transactionDate2 t -matchesTransaction (Status v) t = v == tstatus t +matchesTransaction (Status Uncleared) t = tstatus t /= Cleared +matchesTransaction (Status s) t = tstatus t == s matchesTransaction (Real v) t = v == hasRealPostings t matchesTransaction q@(Amt _ _) t = any (q `matchesPosting`) $ tpostings t matchesTransaction (Empty _) _ = True diff --git a/hledger-lib/Hledger/Read/CsvReader.hs b/hledger-lib/Hledger/Read/CsvReader.hs index c7c44920a..264b20720 100644 --- a/hledger-lib/Hledger/Read/CsvReader.hs +++ b/hledger-lib/Hledger/Read/CsvReader.hs @@ -51,7 +51,7 @@ import Text.Printf (hPrintf,printf) import Hledger.Data import Hledger.Utils.UTF8IOCompat (getContents) import Hledger.Utils -import Hledger.Read.JournalReader (amountp) +import Hledger.Read.JournalReader (amountp, statusp) reader :: Reader @@ -602,7 +602,15 @@ transactionFromCsvRecord sourcepos rules record = t ++"or "++maybe "add a" (const "change your") mskip++" skip rule" ,"for m/d/y or d/m/y dates, use date-format %-m/%-d/%Y or date-format %-d/%-m/%Y" ] - status = maybe False ((=="*") . render) $ mfieldtemplate "status" + status = + case mfieldtemplate "status" of + Nothing -> Uncleared + Just str -> either statuserror id $ runParser (statusp <* eof) nullctx "" $ render str + where + statuserror err = error' $ unlines + ["error: could not parse \""++str++"\" as a cleared status (should be *, ! or empty)" + ,"the parse error is: "++show err + ] code = maybe "" render $ mfieldtemplate "code" description = maybe "" render $ mfieldtemplate "description" comment = maybe "" render $ mfieldtemplate "comment" diff --git a/hledger-lib/Hledger/Read/JournalReader.hs b/hledger-lib/Hledger/Read/JournalReader.hs index 039d58289..dbc0919c0 100644 --- a/hledger-lib/Hledger/Read/JournalReader.hs +++ b/hledger-lib/Hledger/Read/JournalReader.hs @@ -36,6 +36,7 @@ module Hledger.Read.JournalReader ( amountp', mamountp', numberp, + statusp, emptyorcommentlinep, followingcommentp, accountaliasp @@ -367,7 +368,7 @@ transaction = do date <- datep "transaction" edate <- optionMaybe (secondarydatep date) "secondary date" lookAhead (spacenonewline <|> newline) "whitespace or newline" - status <- status "cleared flag" + status <- statusp "cleared status" code <- codep "transaction code" description <- descriptionp >>= return . strip comment <- try followingcommentp <|> (newline >> return "") @@ -407,14 +408,14 @@ test_transaction = do nulltransaction{ tdate=parsedate "2012/05/14", tdate2=Just $ parsedate "2012/05/15", - tstatus=False, + tstatus=Uncleared, tcode="code", tdescription="desc", tcomment=" tcomment1\n tcomment2\n ttag1: val1\n", ttags=[("ttag1","val1")], tpostings=[ nullposting{ - pstatus=True, + pstatus=Cleared, paccount="a", pamount=Mixed [usd 1], pcomment=" pcomment1\n pcomment2\n ptag1: val1\n ptag2: val2\n", @@ -535,8 +536,14 @@ secondarydatep primarydate = do edate <- withDefaultYear primarydate datep return edate -status :: Stream [Char] m Char => ParsecT [Char] JournalContext m Bool -status = try (do { many spacenonewline; (char '*' <|> char '!') "status"; return True } ) <|> return False +statusp :: Stream [Char] m Char => ParsecT [Char] JournalContext m ClearedStatus +statusp = + choice' + [ many spacenonewline >> char '*' >> return Cleared + , many spacenonewline >> char '!' >> return Pending + , return Uncleared + ] + "cleared status" codep :: Stream [Char] m Char => ParsecT [Char] JournalContext m String codep = try (do { many1 spacenonewline; char '(' "codep"; code <- anyChar `manyTill` char ')'; return code } ) <|> return "" @@ -555,7 +562,7 @@ postings = many1 (try postingp) "postings" postingp :: Stream [Char] m Char => ParsecT [Char] JournalContext m Posting postingp = do many1 spacenonewline - status <- status + status <- statusp many spacenonewline account <- modifiedaccountname let (ptype, account') = (accountNamePostingType account, unbracket account) diff --git a/hledger-lib/Hledger/Reports/BalanceReport.hs b/hledger-lib/Hledger/Reports/BalanceReport.hs index 7330d3bf8..1f4b99a10 100644 --- a/hledger-lib/Hledger/Reports/BalanceReport.hs +++ b/hledger-lib/Hledger/Reports/BalanceReport.hs @@ -332,7 +332,7 @@ Right samplejournal2 = journalBalanceTransactions $ tsourcepos=nullsourcepos, tdate=parsedate "2008/01/01", tdate2=Just $ parsedate "2009/01/01", - tstatus=False, + tstatus=Uncleared, tcode="", tdescription="income", tcomment="", diff --git a/hledger-lib/Hledger/Reports/PostingsReport.hs b/hledger-lib/Hledger/Reports/PostingsReport.hs index a66299121..9428e5a09 100644 --- a/hledger-lib/Hledger/Reports/PostingsReport.hs +++ b/hledger-lib/Hledger/Reports/PostingsReport.hs @@ -228,8 +228,8 @@ tests_postingsReport = [ (Any, samplejournal) `gives` 11 -- register --depth just clips account names (Depth 2, samplejournal) `gives` 11 - (And [Depth 1, Status True, Acct "expenses"], samplejournal) `gives` 2 - (And [And [Depth 1, Status True], Acct "expenses"], samplejournal) `gives` 2 + (And [Depth 1, Status Cleared, Acct "expenses"], samplejournal) `gives` 2 + (And [And [Depth 1, Status Cleared], Acct "expenses"], samplejournal) `gives` 2 -- with query and/or command-line options assertEqual "" 11 (length $ snd $ postingsReport defreportopts Any samplejournal) diff --git a/hledger-lib/Hledger/Reports/ReportOptions.hs b/hledger-lib/Hledger/Reports/ReportOptions.hs index 99b894d75..b10310d53 100644 --- a/hledger-lib/Hledger/Reports/ReportOptions.hs +++ b/hledger-lib/Hledger/Reports/ReportOptions.hs @@ -63,6 +63,7 @@ data ReportOpts = ReportOpts { ,end_ :: Maybe Day ,period_ :: Maybe (Interval,DateSpan) ,cleared_ :: Bool + ,pending_ :: Bool ,uncleared_ :: Bool ,cost_ :: Bool ,depth_ :: Maybe Int @@ -119,6 +120,7 @@ defreportopts = ReportOpts def def def + def rawOptsToReportOpts :: RawOpts -> IO ReportOpts rawOptsToReportOpts rawopts = do @@ -128,6 +130,7 @@ rawOptsToReportOpts rawopts = do ,end_ = maybesmartdateopt d "end" rawopts ,period_ = maybeperiodopt d rawopts ,cleared_ = boolopt "cleared" rawopts + ,pending_ = boolopt "pending" rawopts ,uncleared_ = boolopt "uncleared" rawopts ,cost_ = boolopt "cost" rawopts ,depth_ = maybeintopt "depth" rawopts @@ -226,9 +229,10 @@ intervalFromOpts ReportOpts{..} = | otherwise = NoInterval -- | Get a maybe boolean representing the last cleared/uncleared option if any. -clearedValueFromOpts :: ReportOpts -> Maybe Bool -clearedValueFromOpts ReportOpts{..} | cleared_ = Just True - | uncleared_ = Just False +clearedValueFromOpts :: ReportOpts -> Maybe ClearedStatus +clearedValueFromOpts ReportOpts{..} | cleared_ = Just Cleared + | pending_ = Just Pending + | uncleared_ = Just Uncleared | otherwise = Nothing -- depthFromOpts :: ReportOpts -> Int diff --git a/hledger-web/templates/default-layout-wrapper.hamlet b/hledger-web/templates/default-layout-wrapper.hamlet index b5ac01345..d38e5dfb3 100644 --- a/hledger-web/templates/default-layout-wrapper.hamlet +++ b/hledger-web/templates/default-layout-wrapper.hamlet @@ -81,7 +81,7 @@ $newline never
  • desc:REGEXP - filter on description
  • date:PERIODEXP, date2:PERIODEXP - filter on date or secondary date
  • code:REGEXP - filter on transaction's code (eg check number) -
  • status:*, status:!, status: - filter on transaction's status flag (eg cleared status) +
  • status:*, status:!, status: - filter on transaction's cleared status (cleared, pending, uncleared)
  • amt:N, amt:<N, amt:>N - filter on the unsigned amount magnitude. Or with a sign before N, filter on the signed value. (Single-commodity amounts only.)
  • cur:REGEXP - filter on the currency/commodity symbol (must match all of it). Dollar sign must be written as \$ diff --git a/hledger/Hledger/Cli.hs b/hledger/Hledger/Cli.hs index 71c7ea71b..3b7961c40 100644 --- a/hledger/Hledger/Cli.hs +++ b/hledger/Hledger/Cli.hs @@ -346,7 +346,7 @@ journal7 = nulljournal {jtxns = tsourcepos=nullsourcepos, tdate=parsedate "2007/01/01", tdate2=Nothing, - tstatus=False, + tstatus=Uncleared, tcode="*", tdescription="opening balance", tcomment="", @@ -362,7 +362,7 @@ journal7 = nulljournal {jtxns = tsourcepos=nullsourcepos, tdate=parsedate "2007/02/01", tdate2=Nothing, - tstatus=False, + tstatus=Uncleared, tcode="*", tdescription="ayres suites", tcomment="", @@ -378,7 +378,7 @@ journal7 = nulljournal {jtxns = tsourcepos=nullsourcepos, tdate=parsedate "2007/01/02", tdate2=Nothing, - tstatus=False, + tstatus=Uncleared, tcode="*", tdescription="auto transfer to savings", tcomment="", @@ -394,7 +394,7 @@ journal7 = nulljournal {jtxns = tsourcepos=nullsourcepos, tdate=parsedate "2007/01/03", tdate2=Nothing, - tstatus=False, + tstatus=Uncleared, tcode="*", tdescription="poquito mas", tcomment="", @@ -410,7 +410,7 @@ journal7 = nulljournal {jtxns = tsourcepos=nullsourcepos, tdate=parsedate "2007/01/03", tdate2=Nothing, - tstatus=False, + tstatus=Uncleared, tcode="*", tdescription="verizon", tcomment="", @@ -426,7 +426,7 @@ journal7 = nulljournal {jtxns = tsourcepos=nullsourcepos, tdate=parsedate "2007/01/03", tdate2=Nothing, - tstatus=False, + tstatus=Uncleared, tcode="*", tdescription="discover", tcomment="", diff --git a/hledger/Hledger/Cli/Add.hs b/hledger/Hledger/Cli/Add.hs index 98e26ef57..89ebac6f7 100644 --- a/hledger/Hledger/Cli/Add.hs +++ b/hledger/Hledger/Cli/Add.hs @@ -147,7 +147,7 @@ transactionWizard es@EntryState{..} = do balancedPostingsWizard = do ps <- postingsWizard es2{esPostings=[]} let t = nulltransaction{tdate=date - ,tstatus=False + ,tstatus=Uncleared ,tcode=code ,tdescription=desc ,tcomment=comment diff --git a/hledger/Hledger/Cli/Options.hs b/hledger/Hledger/Cli/Options.hs index 01018f9b3..a905340f8 100644 --- a/hledger/Hledger/Cli/Options.hs +++ b/hledger/Hledger/Cli/Options.hs @@ -121,8 +121,9 @@ reportflags = [ ,flagReq ["period","p"] (\s opts -> Right $ setopt "period" s opts) "PERIODEXP" "set start date, end date, and/or reporting interval all at once (overrides the flags above)" ,flagNone ["date2","aux-date"] (setboolopt "date2") "use postings/txns' secondary dates instead" - ,flagNone ["cleared","C"] (setboolopt "cleared") "include only pending/cleared postings/txns" - ,flagNone ["uncleared","U"] (setboolopt "uncleared") "include only uncleared postings/txns" + ,flagNone ["cleared","C"] (setboolopt "cleared") "include only cleared postings/txns" + ,flagNone ["pending"] (setboolopt "pending") "include only pending postings/txns" + ,flagNone ["uncleared","U"] (setboolopt "uncleared") "include only uncleared (and pending) postings/txns" ,flagNone ["real","R"] (setboolopt "real") "include only non-virtual postings" ,flagReq ["depth"] (\s opts -> Right $ setopt "depth" s opts) "N" "hide accounts/postings deeper than N" ,flagNone ["empty","E"] (setboolopt "empty") "show empty/zero things which are normally omitted" diff --git a/hledger/Hledger/Cli/Print.hs b/hledger/Hledger/Cli/Print.hs index fc851704c..a39960328 100644 --- a/hledger/Hledger/Cli/Print.hs +++ b/hledger/Hledger/Cli/Print.hs @@ -100,7 +100,7 @@ transactionToCSV n t = description = tdescription t date = showDate (tdate t) date2 = maybe "" showDate (tdate2 t) - status = if tstatus t then "*" else "" + status = show $ tstatus t code = tcode t comment = chomp $ strip $ tcomment t @@ -116,7 +116,7 @@ postingToCSV p = amounts where Mixed amounts = pamount p - status = if pstatus p then "*" else "" + status = show $ pstatus p account = showAccountName Nothing (ptype p) (paccount p) comment = chomp $ strip $ pcomment p