imp: default V accounts become just E when a new V account is declared

The equity:conversion account, and its variations equity:trade(s) and equity:trading,
normally detected as V/Conversion type, now become ordinary E/Equity accounts
if some other account is declared as V/Conversion type.

This is motivated by the next commit, in which `check accounts` will
stop warning about conversion accounts and their subaccounts,
which means all of the above names and their subaccounts would remain
always exempt from strict account checking.

Now, if the user declares their own conversion account, those default
accounts will become controllable by account checking again.
Which at least reduces the allowlist a bit.

Hopefully this won't cause hassles.
This commit is contained in:
Simon Michael 2024-10-17 12:23:52 -10:00
parent da11d74ae6
commit d985e89bad
3 changed files with 90 additions and 25 deletions

View File

@ -21,6 +21,7 @@ module Hledger.Data.AccountName (
,accountNameTreeFrom
,accountSummarisedName
,accountNameInferType
,accountNameInferTypeExcept
,accountNameType
,defaultBaseConversionAccount
,assetAccountRegex
@ -84,27 +85,6 @@ acctsepchar = ':'
acctsep :: Text
acctsep = T.pack [acctsepchar]
-- accountNameComponents :: AccountName -> [String]
-- accountNameComponents = splitAtElement acctsepchar
accountNameComponents :: AccountName -> [Text]
accountNameComponents = T.splitOn acctsep
accountNameFromComponents :: [Text] -> AccountName
accountNameFromComponents = T.intercalate acctsep
accountLeafName :: AccountName -> Text
accountLeafName = last . accountNameComponents
-- | Truncate all account name components but the last to two characters.
accountSummarisedName :: AccountName -> Text
accountSummarisedName a
-- length cs > 1 = take 2 (head cs) ++ ":" ++ a'
| length cs > 1 = T.intercalate ":" (map (T.take 2) $ init cs) <> ":" <> a'
| otherwise = a'
where
cs = accountNameComponents a
a' = accountLeafName a
-- The base conversion account name used by --infer-equity,
-- when no other account of type V/Conversion has been declared.
defaultBaseConversionAccount = "equity:conversion"
@ -132,6 +112,15 @@ accountNameInferType a
| regexMatchText expenseAccountRegex a = Just Expense
| otherwise = Nothing
-- | Like accountNameInferType, but exclude the provided types from the guesses.
-- Used eg to prevent "equity:conversion" being inferred as Conversion when a different
-- account has been declared with that type.
accountNameInferTypeExcept :: [AccountType] -> AccountName -> Maybe AccountType
accountNameInferTypeExcept excludedtypes a =
case accountNameInferType a of
Just t | not $ t `elem` excludedtypes -> Just t
_ -> Nothing
-- Extract the 'AccountType' of an 'AccountName' by looking it up in the
-- provided Map, traversing the parent accounts if necessary. If none of those
-- work, try 'accountNameInferType'.
@ -139,6 +128,28 @@ accountNameType :: M.Map AccountName AccountType -> AccountName -> Maybe Account
accountNameType atypes a = asum (map (`M.lookup` atypes) $ a : parentAccountNames a)
<|> accountNameInferType a
-- accountNameComponents :: AccountName -> [String]
-- accountNameComponents = splitAtElement acctsepchar
accountNameComponents :: AccountName -> [Text]
accountNameComponents = T.splitOn acctsep
accountNameFromComponents :: [Text] -> AccountName
accountNameFromComponents = T.intercalate acctsep
accountLeafName :: AccountName -> Text
accountLeafName = last . accountNameComponents
-- | Truncate all account name components but the last to two characters.
accountSummarisedName :: AccountName -> Text
accountSummarisedName a
-- length cs > 1 = take 2 (head cs) ++ ":" ++ a'
| length cs > 1 = T.intercalate ":" (map (T.take 2) $ init cs) <> ":" <> a'
| otherwise = a'
where
cs = accountNameComponents a
a' = accountLeafName a
-- | The level (depth) of an account name.
--
-- >>> accountNameLevel "" -- special case

View File

@ -579,7 +579,7 @@ journalAddAccountTypes j = j{jaccounttypes = journalAccountTypes j}
-- | An account type inherited from the parent account(s),
-- and whether it was originally declared by an account directive (true) or inferred from an account name (false).
type ParentAccountType = ( AccountType, Bool )
type ParentAccountType = (AccountType, Bool)
-- | Build a map of all known account types, explicitly declared
-- or inferred from the account's parent or name.
@ -587,11 +587,12 @@ journalAccountTypes :: Journal -> M.Map AccountName AccountType
journalAccountTypes j = M.fromList [(a,acctType) | (a, Just (acctType,_)) <- flatten t']
where
t = accountNameTreeFrom $ journalAccountNames j :: Tree AccountName
-- Map from the top of the account tree down to the leaves, applying any explicitly declared account types,
-- Traverse downward through the account tree, applying any explicitly declared account types,
-- otherwise inferring account types from account names when possible, and propagating account types downward.
-- Declared account types (possibly inherited from parent) are preferred, inferred types are used as a fallback.
t' = setTypeHereAndBelow Nothing t :: Tree (AccountName, Maybe (AccountType, Bool))
where
declaredtypes = M.keys $ jdeclaredaccounttypes j
declaredtypesbyname = journalDeclaredAccountTypes j & fmap (,True)
setTypeHereAndBelow :: Maybe ParentAccountType -> Tree AccountName -> Tree (AccountName, Maybe ParentAccountType)
setTypeHereAndBelow mparenttype (Node a subs) = Node (a, mnewtype) (map (setTypeHereAndBelow mnewtype) subs)
@ -601,9 +602,9 @@ journalAccountTypes j = M.fromList [(a,acctType) | (a, Just (acctType,_)) <- fla
mthisacctdeclaredtype = M.lookup a declaredtypesbyname
mparentacctdeclaredtype = if fromMaybe False $ snd <$> mparenttype then mparenttype else Nothing
mparentacctinferredtype = if not $ fromMaybe True $ snd <$> mparenttype then mparenttype else Nothing
mthisacctinferredtype = accountNameInferType a & fmap (,False)
mthisacctinferredtype = accountNameInferTypeExcept declaredtypes a & fmap (,False) -- XXX not sure about this Except logic.. but for now, tests pass
-- | Build a map of the account types explicitly declared for each account.
-- | Build a map from account names to explicitly declared account types.
journalDeclaredAccountTypes :: Journal -> M.Map AccountName AccountType
journalDeclaredAccountTypes Journal{jdeclaredaccounttypes} =
M.fromList $ concat [map (,t) as | (t,as) <- M.toList jdeclaredaccounttypes]

View File

@ -39,3 +39,56 @@ account asset
$ hledger -f - accounts --types
asset ; type: A
unknown ; type:
# ** 7. equity:conversion, and several other names, are detected as V/Conversion type by default.
<
account equity:conversion
account equity:trade
account equity:trades
account equity:trading
$ hledger -f- accounts --types
equity:conversion ; type: V
equity:trade ; type: V
equity:trades ; type: V
equity:trading ; type: V
# ** 8. If any other account name is declared with V type, those default names become just ordinary equity accounts.
<
account trade ; type: V
account equity:conversion
account equity:trade
account equity:trades
account equity:trading
$ hledger -f- accounts --types
trade ; type: V
equity:conversion ; type: E
equity:trade ; type: E
equity:trades ; type: E
equity:trading ; type: E
# ** 9. --infer-equity uses equity:conversion as its base account by default.
<
2024-01-01
a 1A
b -1B
$ hledger -f- accounts --infer-equity
a
b
equity:conversion:A-B:A
equity:conversion:A-B:B
# ** 10. With a custom conversion account declared, --infer-equity uses that instead.
<
account trade ; type:V
2024-01-01
a 1A
b -1B
$ hledger -f- accounts --infer-equity
trade
trade:A-B:A
trade:A-B:B
a
b