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.
This commit is contained in:
parent
6fb0910996
commit
d1f63334ee
@ -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`, `amt:>N`, `amt:>=N` - match postings with a single-commodity
|
||||
|
||||
@ -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="",
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -82,7 +82,7 @@ entryFromTimeLogInOut i o
|
||||
tsourcepos = tlsourcepos i,
|
||||
tdate = idate,
|
||||
tdate2 = Nothing,
|
||||
tstatus = True,
|
||||
tstatus = Cleared,
|
||||
tcode = "",
|
||||
tdescription = desc,
|
||||
tcomment = "",
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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="",
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -81,7 +81,7 @@ $newline never
|
||||
<li> <b><tt>desc:REGEXP</tt></b> - filter on description
|
||||
<li> <b><tt>date:PERIODEXP</tt></b>, <b><tt>date2:PERIODEXP</tt></b> - filter on date or secondary date
|
||||
<li> <b><tt>code:REGEXP</tt></b> - filter on transaction's code (eg check number)
|
||||
<li> <b><tt>status:*</tt></b>, <b><tt>status:!</tt></b>, <b><tt>status:</tt></b> - filter on transaction's status flag (eg cleared status)
|
||||
<li> <b><tt>status:*</tt></b>, <b><tt>status:!</tt></b>, <b><tt>status:</tt></b> - filter on transaction's cleared status (cleared, pending, uncleared)
|
||||
<!-- <li> <b><tt>empty:BOOL</tt></b> - filter on whether amount is zero -->
|
||||
<li> <b><tt>amt:N</tt></b>, <b><tt>amt:<N</tt></b>, <b><tt>amt:>N</tt></b> - filter on the unsigned amount magnitude. Or with a sign before N, filter on the signed value. (Single-commodity amounts only.)
|
||||
<li> <b><tt>cur:REGEXP</tt></b> - filter on the currency/commodity symbol (must match all of it). Dollar sign must be written as <tt>\$</tt>
|
||||
|
||||
@ -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="",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user