feat: accounts: add Gain (G) account type as subtype of Revenue [#2522]
Add a new account type Gain with single-letter code G as a subtype of Revenue, similar to how Cash is a subtype of Asset and Conversion is a subtype of Equity. This enables tracking capital gains/losses separately while still including them in income statements and close --retain. Usage: account revenues:capital ; type: G - type:G matches only Gain accounts - type:R matches both Revenue and Gain (subtype matching) - Auto-detection from account names matching: ^(income|revenue)s?:(capital[- ]?)?(gains?|loss(es)?)(:|$) e.g. income:gains, revenue:capital-gains, income:losses
This commit is contained in:
parent
c7732c7600
commit
e678e09704
@ -30,6 +30,7 @@ module Hledger.Data.AccountName (
|
||||
,equityAccountRegex
|
||||
,conversionAccountRegex
|
||||
,revenueAccountRegex
|
||||
,gainAccountRegex
|
||||
,expenseAccountRegex
|
||||
,acctsep
|
||||
,acctsepchar
|
||||
@ -99,6 +100,7 @@ liabilityAccountRegex = toRegexCI' "^(debts?|liabilit(y|ies))(:|$)"
|
||||
equityAccountRegex = toRegexCI' "^equity(:|$)"
|
||||
conversionAccountRegex = toRegexCI' "^equity:(trade|trades|trading|conversion)(:|$)"
|
||||
revenueAccountRegex = toRegexCI' "^(income|revenue)s?(:|$)"
|
||||
gainAccountRegex = toRegexCI' "^(income|revenue)s?:(capital[- ]?)?(gains?|loss(es)?)(:|$)"
|
||||
expenseAccountRegex = toRegexCI' "^expenses?(:|$)"
|
||||
|
||||
-- | Try to guess an account's type from its name,
|
||||
@ -110,6 +112,7 @@ accountNameInferType a
|
||||
| regexMatchText liabilityAccountRegex a = Just Liability
|
||||
| regexMatchText conversionAccountRegex a = Just Conversion
|
||||
| regexMatchText equityAccountRegex a = Just Equity
|
||||
| regexMatchText gainAccountRegex a = Just Gain
|
||||
| regexMatchText revenueAccountRegex a = Just Revenue
|
||||
| regexMatchText expenseAccountRegex a = Just Expense
|
||||
| otherwise = Nothing
|
||||
@ -447,6 +450,13 @@ tests_AccountName = testGroup "AccountName" [
|
||||
accountNameInferType "revenues" @?= Just Revenue
|
||||
accountNameInferType "revenue" @?= Just Revenue
|
||||
accountNameInferType "income" @?= Just Revenue
|
||||
accountNameInferType "income:gains" @?= Just Gain
|
||||
accountNameInferType "revenue:gain" @?= Just Gain
|
||||
accountNameInferType "revenues:capital-gains" @?= Just Gain
|
||||
accountNameInferType "income:capitalgain" @?= Just Gain
|
||||
accountNameInferType "income:losses" @?= Just Gain
|
||||
accountNameInferType "revenue:capital-loss" @?= Just Gain
|
||||
accountNameInferType "income:gains:realized" @?= Just Gain
|
||||
,testCase "joinAccountNames" $ do
|
||||
joinAccountNames "assets" "cash" @?= "assets:cash"
|
||||
joinAccountNames "assets:cash" "a" @?= "assets:cash:a"
|
||||
|
||||
@ -181,6 +181,7 @@ data AccountType =
|
||||
| Expense
|
||||
| Cash -- ^ a subtype of Asset - liquid assets to show in cashflow report
|
||||
| Conversion -- ^ a subtype of Equity - account with which to balance commodity conversions
|
||||
| Gain -- ^ a subtype of Revenue - capital gains/losses
|
||||
deriving (Eq,Ord,Generic)
|
||||
|
||||
instance Show AccountType where
|
||||
@ -191,6 +192,7 @@ instance Show AccountType where
|
||||
show Expense = "X"
|
||||
show Cash = "C"
|
||||
show Conversion = "V"
|
||||
show Gain = "G"
|
||||
|
||||
isBalanceSheetAccountType :: AccountType -> Bool
|
||||
isBalanceSheetAccountType t = t `elem` [
|
||||
@ -204,7 +206,8 @@ isBalanceSheetAccountType t = t `elem` [
|
||||
isIncomeStatementAccountType :: AccountType -> Bool
|
||||
isIncomeStatementAccountType t = t `elem` [
|
||||
Revenue,
|
||||
Expense
|
||||
Expense,
|
||||
Gain
|
||||
]
|
||||
|
||||
-- | Check whether the first argument is a subtype of the second: either equal
|
||||
@ -219,6 +222,8 @@ isAccountSubtypeOf Cash Cash = True
|
||||
isAccountSubtypeOf Cash Asset = True
|
||||
isAccountSubtypeOf Conversion Conversion = True
|
||||
isAccountSubtypeOf Conversion Equity = True
|
||||
isAccountSubtypeOf Gain Gain = True
|
||||
isAccountSubtypeOf Gain Revenue = True
|
||||
isAccountSubtypeOf _ _ = False
|
||||
|
||||
-- not worth the trouble, letters defined in accountdirectivep for now
|
||||
|
||||
@ -514,11 +514,11 @@ parseTypeCodes s =
|
||||
help = "type:'s argument should be one or more of " ++ accountTypeChoices False
|
||||
|
||||
accountTypeChoices :: Bool -> String
|
||||
accountTypeChoices allowlongform =
|
||||
intercalate ", "
|
||||
accountTypeChoices allowlongform =
|
||||
intercalate ", "
|
||||
-- keep synced with parseAccountType
|
||||
$ ["A","L","E","R","X","C","V"]
|
||||
++ if allowlongform then ["Asset","Liability","Equity","Revenue","Expense","Cash","Conversion"] else []
|
||||
$ ["A","L","E","R","X","C","V","G"]
|
||||
++ if allowlongform then ["Asset","Liability","Equity","Revenue","Expense","Cash","Conversion","Gain"] else []
|
||||
|
||||
-- | Case-insensitively parse one single-letter code, or one long-form word if permitted, to an account type.
|
||||
-- On failure, returns the unparseable text.
|
||||
@ -533,6 +533,7 @@ parseAccountType allowlongform s =
|
||||
"x" -> Right Expense
|
||||
"c" -> Right Cash
|
||||
"v" -> Right Conversion
|
||||
"g" -> Right Gain
|
||||
"asset" | allowlongform -> Right Asset
|
||||
"liability" | allowlongform -> Right Liability
|
||||
"equity" | allowlongform -> Right Equity
|
||||
@ -540,6 +541,7 @@ parseAccountType allowlongform s =
|
||||
"expense" | allowlongform -> Right Expense
|
||||
"cash" | allowlongform -> Right Cash
|
||||
"conversion" | allowlongform -> Right Conversion
|
||||
"gains" | allowlongform -> Right Gain
|
||||
_ -> Left $ T.unpack s
|
||||
|
||||
-- | Parse the value part of a "status:" query, or return an error.
|
||||
|
||||
@ -563,10 +563,12 @@ parseAccountTypeCode s =
|
||||
"c" -> Right Cash
|
||||
"conversion" -> Right Conversion
|
||||
"v" -> Right Conversion
|
||||
"gains" -> Right Gain
|
||||
"g" -> Right Gain
|
||||
_ -> Left err
|
||||
where
|
||||
err = T.unpack $ "invalid account type code "<>s<>", should be one of " <>
|
||||
T.intercalate ", " ["A","L","E","R","X","C","V","Asset","Liability","Equity","Revenue","Expense","Cash","Conversion"]
|
||||
T.intercalate ", " ["A","L","E","R","X","C","V","G","Asset","Liability","Equity","Revenue","Expense","Cash","Conversion","Gain"]
|
||||
|
||||
-- Add an account declaration to the journal, auto-numbering it.
|
||||
addAccountDeclaration :: (AccountName,Text,[Tag],SourcePos) -> JournalParser m ()
|
||||
|
||||
@ -2212,16 +2212,17 @@ and two more representing changes in these:
|
||||
| `Revenue` | `R` | inflows (also known as `Income`) |
|
||||
| `Expense` | `X` | outflows |
|
||||
|
||||
hledger also uses a couple of subtypes:
|
||||
hledger also uses a few subtypes:
|
||||
|
||||
||||
|
||||
|-|-|-|
|
||||
| `Cash` | `C` | liquid assets |
|
||||
| `Conversion` | `V` | commodity conversions equity |
|
||||
| `Cash` | `C` | liquid assets (subtype of Asset) |
|
||||
| `Conversion` | `V` | commodity conversions equity (subtype of Equity) |
|
||||
| `Gain` | `G` | capital gains/losses (subtype of Revenue) |
|
||||
|
||||
<!-- [liquid assets]: https://en.wikipedia.org/wiki/Cash_and_cash_equivalents -->
|
||||
|
||||
As a convenience, hledger will detect these types automatically from english account names.
|
||||
As a convenience, hledger will detect most of these types automatically from english account names.
|
||||
But it's better to declare them explicitly by adding a `type:` [tag](#tags) in the account directives.
|
||||
The tag's value can be any of the types or one-letter abbreviations above.
|
||||
|
||||
@ -2239,6 +2240,8 @@ account assets:bank ; type: C
|
||||
account assets:cash ; type: C
|
||||
|
||||
account equity:conversion ; type: V
|
||||
|
||||
account revenues:capital ; type: G
|
||||
```
|
||||
|
||||
This enables the easy [balancesheet], [balancesheetequity], [cashflow] and [incomestatement] reports, and querying by [type:](#queries).
|
||||
@ -5682,8 +5685,9 @@ Match unmarked, pending, or cleared transactions respectively.
|
||||
**`type:TYPECODES`**\
|
||||
Match by account type (see [Declaring accounts > Account types](#account-types)).
|
||||
`TYPECODES` is one or more of the single-letter account type codes
|
||||
`ALERXCV`, case insensitive.
|
||||
Note `type:A` and `type:E` will also match their respective subtypes `C` (Cash) and `V` (Conversion).
|
||||
`ALERXCVG`, case insensitive.
|
||||
Note `type:A`, `type:E`, and `type:R` will also match their respective subtypes
|
||||
`C` (Cash), `V` (Conversion), and `G` (Gain).
|
||||
Certain kinds of account alias can disrupt account types, see
|
||||
[Rewriting accounts > Aliases and account types](#aliases-and-account-types).
|
||||
|
||||
|
||||
@ -274,3 +274,23 @@ $ hledger -f- close --clopen -e 2001 --round=hard -c '$1.0'
|
||||
equity:opening/closing balances
|
||||
|
||||
>=
|
||||
|
||||
# ** 20. With --retain, Gain accounts (subtype of Revenue) are also closed.
|
||||
<
|
||||
account revenues ; type:R
|
||||
account gains ; type:G
|
||||
account expenses ; type:X
|
||||
|
||||
2016/1/1
|
||||
revenues $-100
|
||||
gains $-50
|
||||
expenses $150
|
||||
|
||||
$ hledger close -f- -e 2017 --retain
|
||||
2016-12-31 retain earnings ; retain:
|
||||
revenues $100 = $0
|
||||
gains $50 = $0
|
||||
expenses $-150 = $0
|
||||
equity:retained earnings
|
||||
|
||||
>=0
|
||||
|
||||
@ -123,6 +123,7 @@ account a:aa:aaa ; type:L
|
||||
(a:aa) 1
|
||||
(a:aa:aaa) 1
|
||||
|
||||
|
||||
# ** 6. bs will detect proper accounts even with an intervening parent account (#1921)
|
||||
$ hledger -f- bs -N
|
||||
Balance Sheet 2021-01-01
|
||||
@ -136,3 +137,63 @@ Balance Sheet 2021-01-01
|
||||
-------------++------------
|
||||
a || -1
|
||||
a:aa:aaa || -1
|
||||
|
||||
# ** 7. Gains accounts appear in income statement as revenue subtype
|
||||
<
|
||||
account revenues ; type:R
|
||||
account gains ; type:G
|
||||
account expenses ; type:X
|
||||
|
||||
2020-01-01
|
||||
revenues -100
|
||||
gains -50
|
||||
expenses 150
|
||||
|
||||
$ hledger -f- is
|
||||
Income Statement 2020-01-01
|
||||
|
||||
|| 2020-01-01
|
||||
==========++============
|
||||
Revenues ||
|
||||
----------++------------
|
||||
revenues || 100
|
||||
gains || 50
|
||||
----------++------------
|
||||
|| 150
|
||||
==========++============
|
||||
Expenses ||
|
||||
----------++------------
|
||||
expenses || 150
|
||||
----------++------------
|
||||
|| 150
|
||||
==========++============
|
||||
Net: || 0
|
||||
|
||||
# ** 8. Gain accounts are auto-detected from common naming patterns
|
||||
<
|
||||
2020-01-01
|
||||
income:gains -50
|
||||
revenue:capital-gains -30
|
||||
income:losses -20
|
||||
expenses 100
|
||||
|
||||
$ hledger -f- is
|
||||
Income Statement 2020-01-01
|
||||
|
||||
|| 2020-01-01
|
||||
=======================++============
|
||||
Revenues ||
|
||||
-----------------------++------------
|
||||
income:gains || 50
|
||||
income:losses || 20
|
||||
revenue:capital-gains || 30
|
||||
-----------------------++------------
|
||||
|| 100
|
||||
=======================++============
|
||||
Expenses ||
|
||||
-----------------------++------------
|
||||
expenses || 100
|
||||
-----------------------++------------
|
||||
|| 100
|
||||
=======================++============
|
||||
Net: || 0
|
||||
|
||||
@ -135,3 +135,38 @@ $ hledger -f- reg type:ae
|
||||
2022-02-02 Test (assets:cash) 1 1
|
||||
(equity:conversion) 2 3
|
||||
(equity:conversion) -2 1
|
||||
|
||||
# ** 16. type:g matches gains accounts
|
||||
<
|
||||
account gains ; type:G
|
||||
|
||||
2022-02-02 Test
|
||||
(gains) 1
|
||||
|
||||
$ hledger -f- accounts type:g
|
||||
gains
|
||||
|
||||
# ** 17. type:r matches both revenue and gains (subtype matching)
|
||||
<
|
||||
account revenue ; type:R
|
||||
account gains ; type:G
|
||||
|
||||
2022-02-02 Test
|
||||
(revenue) 1
|
||||
(gains) 1
|
||||
|
||||
$ hledger -f- accounts type:r
|
||||
revenue
|
||||
gains
|
||||
|
||||
# ** 18. type:g matches auto-detected gain accounts
|
||||
<
|
||||
2022-02-02 Test
|
||||
(income:gains) 1
|
||||
(revenue:capital-gains) 1
|
||||
(income:losses) 1
|
||||
|
||||
$ hledger -f- accounts type:g
|
||||
income:gains
|
||||
income:losses
|
||||
revenue:capital-gains
|
||||
|
||||
Loading…
Reference in New Issue
Block a user