From fb8a8b15bb434b87ac4fa33f4c301fb0d5bb0aeb Mon Sep 17 00:00:00 2001 From: Lawrence Date: Tue, 28 Sep 2021 11:39:29 -0500 Subject: [PATCH 1/2] imp: journal: inclusive balance assignments of 0 will zero subaccounts Implements https://github.com/simonmichael/hledger/issues/1689. --- hledger-lib/Hledger/Data/Balancing.hs | 42 +++++++++----- hledger/test/journal/balance-assertions.test | 59 ++++++++++++++++++++ 2 files changed, 88 insertions(+), 13 deletions(-) diff --git a/hledger-lib/Hledger/Data/Balancing.hs b/hledger-lib/Hledger/Data/Balancing.hs index 644fcc39efc..99bef86d44c 100644 --- a/hledger-lib/Hledger/Data/Balancing.hs +++ b/hledger-lib/Hledger/Data/Balancing.hs @@ -474,7 +474,7 @@ balanceTransactionAndCheckAssertionsB (Right t@Transaction{tpostings=ps}) = do mapM_ checkIllegalBalanceAssignmentB ps -- for each posting, infer its amount from the balance assignment if applicable, -- update the account's running balance and check the balance assertion if any - ps' <- mapM (addOrAssignAmountAndCheckAssertionB . postingStripPrices) ps + ps' <- fmap concat $ mapM (addOrAssignAmountAndCheckAssertionB . postingStripPrices) ps -- infer any remaining missing amounts, and make sure the transaction is now fully balanced styles <- R.reader bsStyles case balanceTransactionHelper defbalancingopts{commodity_styles_=styles} t{tpostings=ps'} of @@ -490,30 +490,46 @@ balanceTransactionAndCheckAssertionsB (Right t@Transaction{tpostings=ps}) = do -- reset the running balance to, the assigned balance. -- If it has a missing amount and no balance assignment, leave it for later. -- Then test the balance assertion if any. -addOrAssignAmountAndCheckAssertionB :: Posting -> Balancing s Posting +addOrAssignAmountAndCheckAssertionB :: Posting -> Balancing s [Posting] addOrAssignAmountAndCheckAssertionB p@Posting{paccount=acc, pamount=amt, pbalanceassertion=mba} -- an explicit posting amount | hasAmount p = do newbal <- addToRunningBalanceB acc amt whenM (R.reader bsAssrt) $ checkBalanceAssertionB p newbal - return p + return [p] -- no explicit posting amount, but there is a balance assignment - | Just BalanceAssertion{baamount,batotal,bainclusive} <- mba = do - newbal <- if batotal - -- a total balance assignment (==, all commodities) - then return $ mixedAmount baamount - -- a partial balance assignment (=, one commodity) - else do - oldbalothercommodities <- filterMixedAmount ((acommodity baamount /=) . acommodity) <$> getRunningBalanceB acc - return $ maAddAmount oldbalothercommodities baamount + | Just ba@BalanceAssertion{baamount,bainclusive} <- mba, + not bainclusive || not (amountIsZero baamount) = do + newbal <- assignmentAmount ba <$> getRunningBalanceB acc diff <- (if bainclusive then setInclusiveRunningBalanceB else setRunningBalanceB) acc newbal let p' = p{pamount=diff, poriginal=Just $ originalPosting p} whenM (R.reader bsAssrt) $ checkBalanceAssertionB p' newbal - return p' + return [p'] + + -- inclusive balance assignment with zero amount. generate postings to zero out all subaccounts + | Just ba@BalanceAssertion{} <- mba = do + newbals <- withRunningBalance $ \BalancingState{bsBalances} -> + fmap (fmap (assignmentAmount ba)) + . filter ((acc `T.isPrefixOf`) . fst) + <$> H.toList bsBalances + diffs <- forM newbals (uncurry setRunningBalanceB) + forM (zip diffs newbals) $ \(diff, (account, actual)) -> do + let p' = p{pamount=diff, paccount=account, poriginal=Just $ originalPosting p} + whenM (R.reader bsAssrt) $ checkBalanceAssertionB p' actual + return p' -- no explicit posting amount, no balance assignment - | otherwise = return p + | otherwise = return [p] + where + assignmentAmount BalanceAssertion{baamount,batotal} bal + | batotal = + -- a total balance assignment (==, all commodities) + mixedAmount baamount + | otherwise = + -- a partial balance assignment (=, one commodity) + let oldbalothercommodities = filterMixedAmount ((acommodity baamount /=) . acommodity) bal + in maAddAmount oldbalothercommodities baamount -- | Add the posting's amount to its account's running balance, and -- optionally check the posting's balance assertion if any. diff --git a/hledger/test/journal/balance-assertions.test b/hledger/test/journal/balance-assertions.test index 824ffbe41a3..b1f4c0d905c 100755 --- a/hledger/test/journal/balance-assertions.test +++ b/hledger/test/journal/balance-assertions.test @@ -455,3 +455,62 @@ $ hledger -f- print -x (a) -2 ==* 1 >=0 + +# 26. Inclusive balance assignments to 0 set all subaccount balances to 0 +< +2020-09-27 + revenues -1 BYN + revenues:forex gain -1 BYN + revenues:forex gain:unrealised -1 FOO + assets:fudge + +2021-09-27 + revenues ==* 0 + equity:retained earnings + +$ hledger -f- print -x +> +2020-09-27 + revenues -1 BYN + revenues:forex gain -1 BYN + revenues:forex gain:unrealised -1 FOO + assets:fudge 2 BYN + assets:fudge 1 FOO + +2021-09-27 + revenues 1 BYN ==* 0 + revenues:forex gain 1 BYN ==* 0 + revenues:forex gain:unrealised 1 FOO ==* 0 + equity:retained earnings -2 BYN + equity:retained earnings -1 FOO + +>=0 + +# 27. Inclusive partial balance assignments to 0 set all relevant subaccount balances to 0 +< +2020-09-27 + revenues -1 BYN + revenues:forex gain -1 BYN + revenues:forex gain:unrealised -1 FOO + assets:fudge + +2021-09-27 + revenues =* 0 BYN + equity:retained earnings + +$ hledger -f- print -x +> +2020-09-27 + revenues -1 BYN + revenues:forex gain -1 BYN + revenues:forex gain:unrealised -1 FOO + assets:fudge 2 BYN + assets:fudge 1 FOO + +2021-09-27 + revenues 1 BYN =* 0 BYN + revenues:forex gain 1 BYN =* 0 BYN + revenues:forex gain:unrealised 0 =* 0 BYN + equity:retained earnings -2 BYN + +>=0 From c1094a76c13528d17ab3a6c81338a9599e1e3c1f Mon Sep 17 00:00:00 2001 From: Lawrence Date: Tue, 28 Sep 2021 11:56:49 -0500 Subject: [PATCH 2/2] fix: sort accounts of inclusive assignment to ensure consistent posting order --- hledger-lib/Hledger/Data/Balancing.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/hledger-lib/Hledger/Data/Balancing.hs b/hledger-lib/Hledger/Data/Balancing.hs index 99bef86d44c..92775fde17d 100644 --- a/hledger-lib/Hledger/Data/Balancing.hs +++ b/hledger-lib/Hledger/Data/Balancing.hs @@ -511,6 +511,7 @@ addOrAssignAmountAndCheckAssertionB p@Posting{paccount=acc, pamount=amt, pbalanc | Just ba@BalanceAssertion{} <- mba = do newbals <- withRunningBalance $ \BalancingState{bsBalances} -> fmap (fmap (assignmentAmount ba)) + . sortOn fst . filter ((acc `T.isPrefixOf`) . fst) <$> H.toList bsBalances diffs <- forM newbals (uncurry setRunningBalanceB)