csv: refine unknown accounts more thoroughly, a better fix for #1192

This commit is contained in:
Simon Michael 2020-02-26 22:45:49 -08:00
parent 58bb0df5ab
commit 02f2e3bd9b
2 changed files with 52 additions and 37 deletions

View File

@ -741,6 +741,7 @@ type CsvRecord = [String]
showRules rules record = showRules rules record =
unlines $ catMaybes [ (("the "++fld++" rule is: ")++) <$> getEffectiveAssignment rules record fld | fld <- journalfieldnames] unlines $ catMaybes [ (("the "++fld++" rule is: ")++) <$> getEffectiveAssignment rules record fld | fld <- journalfieldnames]
-- warning: 200 line beast ahead. How to simplify ?
transactionFromCsvRecord :: SourcePos -> CsvRules -> CsvRecord -> Transaction transactionFromCsvRecord :: SourcePos -> CsvRules -> CsvRecord -> Transaction
transactionFromCsvRecord sourcepos rules record = t transactionFromCsvRecord sourcepos rules record = t
where where
@ -803,7 +804,7 @@ transactionFromCsvRecord sourcepos rules record = t
unknownExpenseAccount = "expenses:unknown" unknownExpenseAccount = "expenses:unknown"
unknownIncomeAccount = "income:unknown" unknownIncomeAccount = "income:unknown"
parsePosting' :: String -> JournalFieldName -> JournalFieldName -> JournalFieldName -> JournalFieldName -> JournalFieldName -> JournalFieldName -> Maybe (String, Posting) parsePosting' :: String -> JournalFieldName -> JournalFieldName -> JournalFieldName -> JournalFieldName -> JournalFieldName -> JournalFieldName -> Maybe (Posting, Bool)
parsePosting' number accountFld amountFld amountInFld amountOutFld balanceFld commentFld = parsePosting' number accountFld amountFld amountInFld amountOutFld balanceFld commentFld =
let currency = maybe (fromMaybe "" mdefaultcurrency) render $ let currency = maybe (fromMaybe "" mdefaultcurrency) render $
(mfieldtemplate ("currency"++number) `or `mfieldtemplate "currency") (mfieldtemplate ("currency"++number) `or `mfieldtemplate "currency")
@ -812,32 +813,39 @@ transactionFromCsvRecord sourcepos rules record = t
(mfieldtemplate accountFld `or` mdirective ("default-account" ++ number))) (mfieldtemplate accountFld `or` mdirective ("default-account" ++ number)))
mbalance = (parsebalance currency number.render) =<< mfieldtemplate balanceFld mbalance = (parsebalance currency number.render) =<< mfieldtemplate balanceFld
comment = T.pack $ maybe "" render $ mfieldtemplate commentFld comment = T.pack $ maybe "" render $ mfieldtemplate commentFld
maccount :: Maybe AccountName =
-- figure out the account name to use for this posting, if any, and
-- whether it is the unknown account which may be improved later,
-- when we know the posting's final amount.
maccountAndIsFinal :: Maybe (AccountName, Bool) =
case maccount' of case maccount' of
-- accountN is set to the empty string - this posting will be suppressed -- accountN is set to the empty string - no posting will be generated
Just "" -> Nothing Just "" -> Nothing
-- accountN is set -- accountN is set (possibly to "expenses:unknown" ! #1192) -
Just a -> Just a -- don't let it be changed.
Just a -> Just (a, True)
-- accountN is unset -- accountN is unset
Nothing -> Nothing ->
case (mamount, mbalance) of case (mamount, mbalance) of
-- amountN is set, or implied by balanceN - accountN will be -- amountN is set, or implied by balanceN - set accountN to
-- set to the unknown account (expenses:unknown, for now) -- set to the default unknown account (expenses:unknown)
(Just _, _) -> Just unknownExpenseAccount -- and allow it to be improved later
(_, Just _) -> Just unknownExpenseAccount (Just _, _) -> Just (unknownExpenseAccount, False)
-- amountN is also unset, this posting will be suppressed (_, Just _) -> Just (unknownExpenseAccount, False)
-- amountN is also unset - no posting will be generated
(Nothing, Nothing) -> Nothing (Nothing, Nothing) -> Nothing
in in
-- if there's an account N, make a posting N -- if there's an account N, make a posting N
case maccount of case maccountAndIsFinal of
Nothing -> Nothing Nothing -> Nothing
Just acct -> Just (acct, final) ->
Just (number, posting{paccount = accountNameWithoutPostingType acct Just (posting{paccount = accountNameWithoutPostingType acct
,pamount = fromMaybe missingmixedamt mamount ,pamount = fromMaybe missingmixedamt mamount
,ptransaction = Just t ,ptransaction = Just t
,pbalanceassertion = toAssertion <$> mbalance ,pbalanceassertion = toAssertion <$> mbalance
,pcomment = comment ,pcomment = comment
,ptype = accountNamePostingType acct}) ,ptype = accountNamePostingType acct}
,final)
parsePosting number = parsePosting number =
parsePosting' number parsePosting' number
@ -866,33 +874,40 @@ transactionFromCsvRecord sourcepos rules record = t
("balance1" `withAlias` "balance") ("balance1" `withAlias` "balance")
"comment1" -- comment1 does not have legacy alias "comment1" -- comment1 does not have legacy alias
postings' = catMaybes $ posting1:[ parsePosting i | x<-[2..9], let i = show x] postings' = catMaybes $ posting1 : [parsePosting i | x<-[2..9], let i = show x]
-- Handle some special cases to mimic pre-1.16 behaviour, for
-- compatibility; and also, wherever default "unknown" accounts were used,
-- refine these based on the sign of the final posting amount.
postings = postings =
case postings' of case postings' of
-- pre-1.16 compatibility: when rules generate just one posting, and -- when rules generate just one posting, and it's a type that needs to
-- it's a type that needs to be balanced, generate the second posting -- be balanced, generate the second posting to balance it.
-- to balance it, with appropriate unknown account name [(p1,final)] ->
[("1",p1)] ->
if ptype p1 == VirtualPosting if ptype p1 == VirtualPosting
then [p1] then [p1']
else [p1, p2] else [p1', p2]
where where
p2 = improveUnknownAccountName $ p1' = (if final then id else improveUnknownAccountName) p1
p2 = improveUnknownAccountName
nullposting{paccount=unknownExpenseAccount nullposting{paccount=unknownExpenseAccount
,pamount=costOfMixedAmount (-pamount p1) ,pamount=costOfMixedAmount (-pamount p1)
,ptransaction=Just t} ,ptransaction=Just t}
-- pre-1.16 compatibility: when rules generate exactly two postings, -- pre-1.16 compatibility: when rules generate exactly two postings,
-- and the second has no amount, give it the balancing amount, and -- and only the second has no amount, give it the balancing amount.
-- refine the account name if it's unknown [(p1,final1), (p2,final2)] ->
[("1",p1), ("2",p2)] -> case (pamount p1 == missingmixedamt, pamount p2 == missingmixedamt) of
case (pamount p1 == missingmixedamt , pamount p2 == missingmixedamt) of (False, True) -> [p1',p2']
(False, True) -> [p1, improveUnknownAccountName $ p2{pamount=costOfMixedAmount(-(pamount p1))}] where p2' = (if final2 then id else improveUnknownAccountName)
_ -> [p1, p2] p2{pamount=costOfMixedAmount(-(pamount p1))}
_ -> [p1', p2']
where p2' = (if final2 then id else improveUnknownAccountName) p2
where
p1' = (if final1 then id else improveUnknownAccountName) p1
-- otherwise, refine any unknown account names in all postings -- otherwise, refine an unknown account name in all postings.
_ -> map (improveUnknownAccountName . snd) postings' ps -> [(if final then id else improveUnknownAccountName) p | (p,final) <- ps]
where where
-- If this posting has the "expenses:unknown" account name, maybe -- If this posting has the "expenses:unknown" account name, maybe
-- replace that with "income:unknown" now that we know the amount's sign. -- replace that with "income:unknown" now that we know the amount's sign.

View File

@ -608,8 +608,8 @@ fields date, amount, account1
account2 expenses:unknown account2 expenses:unknown
$ ./hledger-csv $ ./hledger-csv
2020-01-01 2020-01-01
a 1 a 1
expenses:unknown -1 expenses:unknown -1
>=0 >=0