fix: check mixed-cost balances correctly again; cleanup (#2150)
The code is a bit clearer, and it no longer discards amounts other than the first when the running balance contains multiple costs. (This bug was exposed by the fix for #2039).
This commit is contained in:
parent
8ec46baec9
commit
e694e7869d
@ -46,7 +46,6 @@ import qualified Data.Set as S
|
|||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Time.Calendar (fromGregorian)
|
import Data.Time.Calendar (fromGregorian)
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import Safe (headDef)
|
|
||||||
import Text.Printf (printf)
|
import Text.Printf (printf)
|
||||||
|
|
||||||
import Hledger.Utils
|
import Hledger.Utils
|
||||||
@ -540,7 +539,7 @@ journalBalanceTransactions bopts' j' =
|
|||||||
balanceTransactionAndCheckAssertionsB :: Either Posting Transaction -> Balancing s ()
|
balanceTransactionAndCheckAssertionsB :: Either Posting Transaction -> Balancing s ()
|
||||||
balanceTransactionAndCheckAssertionsB (Left p@Posting{}) =
|
balanceTransactionAndCheckAssertionsB (Left p@Posting{}) =
|
||||||
-- Update the account's running balance and check the balance assertion if any.
|
-- Update the account's running balance and check the balance assertion if any.
|
||||||
-- Note, cost is ignored when checking balance assertions, currently.
|
-- Cost is ignored when checking balance assertions currently.
|
||||||
void $ addAmountAndCheckAssertionB $ postingStripCosts p
|
void $ addAmountAndCheckAssertionB $ postingStripCosts p
|
||||||
balanceTransactionAndCheckAssertionsB (Right t@Transaction{tpostings=ps}) = do
|
balanceTransactionAndCheckAssertionsB (Right t@Transaction{tpostings=ps}) = do
|
||||||
-- make sure we can handle the balance assignments
|
-- make sure we can handle the balance assignments
|
||||||
@ -552,7 +551,7 @@ balanceTransactionAndCheckAssertionsB (Right t@Transaction{tpostings=ps}) = do
|
|||||||
ps' <- ps
|
ps' <- ps
|
||||||
& zip [1..] -- attach original positions
|
& zip [1..] -- attach original positions
|
||||||
& sortOn (postingDate.snd) -- sort by date
|
& sortOn (postingDate.snd) -- sort by date
|
||||||
& mapM (addOrAssignAmountAndCheckAssertionB) -- infer amount, check assertion on each one
|
& mapM addOrAssignAmountAndCheckAssertionB -- infer amount, check assertion on each one
|
||||||
<&> sortOn fst -- restore original order
|
<&> sortOn fst -- restore original order
|
||||||
<&> map snd -- discard positions
|
<&> map snd -- discard positions
|
||||||
|
|
||||||
@ -616,7 +615,7 @@ addAmountAndCheckAssertionB p = return p
|
|||||||
-- are ignored; if it is total, they will cause the assertion to fail.
|
-- are ignored; if it is total, they will cause the assertion to fail.
|
||||||
checkBalanceAssertionB :: Posting -> MixedAmount -> Balancing s ()
|
checkBalanceAssertionB :: Posting -> MixedAmount -> Balancing s ()
|
||||||
checkBalanceAssertionB p@Posting{pbalanceassertion=Just (BalanceAssertion{baamount,batotal})} actualbal =
|
checkBalanceAssertionB p@Posting{pbalanceassertion=Just (BalanceAssertion{baamount,batotal})} actualbal =
|
||||||
forM_ (baamount : otheramts) $ \amt -> checkBalanceAssertionOneCommodityB p amt actualbal
|
forM_ (baamount : otheramts) $ \amt -> checkBalanceAssertionOneCommodityB p amt actualbal
|
||||||
where
|
where
|
||||||
assertedcomm = acommodity baamount
|
assertedcomm = acommodity baamount
|
||||||
otheramts | batotal = map (\a -> a{aquantity=0}) . amountsRaw
|
otheramts | batotal = map (\a -> a{aquantity=0}) . amountsRaw
|
||||||
@ -625,15 +624,17 @@ checkBalanceAssertionB p@Posting{pbalanceassertion=Just (BalanceAssertion{baamou
|
|||||||
checkBalanceAssertionB _ _ = return ()
|
checkBalanceAssertionB _ _ = return ()
|
||||||
|
|
||||||
-- | Does this (single commodity) expected balance match the amount of that
|
-- | Does this (single commodity) expected balance match the amount of that
|
||||||
-- commodity in the given (multicommodity) actual balance ? If not, returns a
|
-- commodity in the given (multicommodity) actual balance, ignoring costs ?
|
||||||
-- balance assertion failure message based on the provided posting. To match,
|
-- If not, returns a balance assertion failure message based on the provided posting.
|
||||||
-- the amounts must be exactly equal (display precision is ignored here).
|
-- To match, the amounts must be exactly equal (display precision is ignored here).
|
||||||
-- If the assertion is inclusive, the expected amount is compared with the account's
|
-- If the assertion is inclusive, the expected amount is compared with the account's
|
||||||
-- subaccount-inclusive balance; otherwise, with the subaccount-exclusive balance.
|
-- subaccount-inclusive balance; otherwise, with the subaccount-exclusive balance.
|
||||||
checkBalanceAssertionOneCommodityB :: Posting -> Amount -> MixedAmount -> Balancing s ()
|
checkBalanceAssertionOneCommodityB :: Posting -> Amount -> MixedAmount -> Balancing s ()
|
||||||
checkBalanceAssertionOneCommodityB p@Posting{paccount=assertedacct} assertedcommbal actualbal = do
|
checkBalanceAssertionOneCommodityB p@Posting{paccount=assertedacct} assertedcommbal actualbal = do
|
||||||
let isinclusive = maybe False bainclusive $ pbalanceassertion p
|
let isinclusive = maybe False bainclusive $ pbalanceassertion p
|
||||||
let istotal = maybe False batotal $ pbalanceassertion p
|
let istotal = maybe False batotal $ pbalanceassertion p
|
||||||
|
-- mstyles <- R.reader bsStyles
|
||||||
|
-- let styled = maybe id styleAmounts mstyles
|
||||||
actualbal' <-
|
actualbal' <-
|
||||||
if isinclusive
|
if isinclusive
|
||||||
then
|
then
|
||||||
@ -646,27 +647,31 @@ checkBalanceAssertionOneCommodityB p@Posting{paccount=assertedacct} assertedcomm
|
|||||||
bsBalances
|
bsBalances
|
||||||
else return actualbal
|
else return actualbal
|
||||||
let
|
let
|
||||||
assertedcomm = acommodity assertedcommbal
|
assertedcomm = acommodity assertedcommbal
|
||||||
assertedcommbalcostless = amountStripCost assertedcommbal
|
|
||||||
actualcommbalcostless = amountStripCost . headDef nullamt . amountsRaw . filterMixedAmountByCommodity assertedcomm $ actualbal'
|
|
||||||
|
|
||||||
-- test the assertion. Costs are ignored currently.
|
-- The asserted single-commodity balance, without cost
|
||||||
|
assertedcommbalcostless = amountStripCost assertedcommbal
|
||||||
|
|
||||||
|
-- The balance in this commodity, from the current multi-commodity running balance at this point.
|
||||||
|
-- This is unnormalised, and could include one or more different costs.
|
||||||
|
actualcommbal = filterMixedAmountByCommodity assertedcomm $ actualbal'
|
||||||
|
|
||||||
|
-- The above balance without costs, as a single Amount (Amount's + discards costs).
|
||||||
|
actualcommbalcostless = sum $ amountsRaw actualcommbal
|
||||||
|
|
||||||
|
-- test the assertion
|
||||||
pass =
|
pass =
|
||||||
aquantity (
|
aquantity assertedcommbalcostless
|
||||||
-- traceWith (("asserted:"++).showAmountDebug)
|
|
||||||
assertedcommbalcostless)
|
|
||||||
==
|
==
|
||||||
aquantity (
|
aquantity actualcommbalcostless
|
||||||
-- traceWith (("actual:"++).showAmountDebug)
|
|
||||||
actualcommbalcostless)
|
|
||||||
|
|
||||||
errmsg = chomp $ printf (unlines
|
errmsg = chomp $ printf (unlines
|
||||||
[ "%s:",
|
[ "%s:",
|
||||||
"%s\n",
|
"%s\n",
|
||||||
"Balance assertion failed in %s",
|
"Balance assertion failed in %s",
|
||||||
"%s at this point, %s, ignoring costs,",
|
"%s at this point, %s, ignoring costs,",
|
||||||
"the expected balance is: %s", -- (at display precision: %s)",
|
"the expected balance is: %s",
|
||||||
"but the calculated balance is: %s", -- (at display precision: %s)",
|
"but the calculated balance is: %s",
|
||||||
"(difference: %s)",
|
"(difference: %s)",
|
||||||
"To troubleshoot, check this account's running balance with assertions disabled, eg:",
|
"To troubleshoot, check this account's running balance with assertions disabled, eg:",
|
||||||
"hledger reg -I '%s'%s"
|
"hledger reg -I '%s'%s"
|
||||||
@ -675,11 +680,12 @@ checkBalanceAssertionOneCommodityB p@Posting{paccount=assertedacct} assertedcomm
|
|||||||
(sourcePosPretty pos) -- position
|
(sourcePosPretty pos) -- position
|
||||||
(textChomp ex) -- journal excerpt
|
(textChomp ex) -- journal excerpt
|
||||||
acct -- asserted account
|
acct -- asserted account
|
||||||
(if istotal then "Across all commodities" else "In commodity " <> assertedcommstr) -- asserted commodity (partial assertion) or all commodities (total assertion)
|
(if istotal then "Across all commodities" else "In commodity " <> assertedcommstr) -- asserted commodity or all commodities ?
|
||||||
(if isinclusive then "including subaccounts" else "excluding subaccounts" :: String) -- inclusive or exclusive balance asserted ?
|
(if isinclusive then "including subaccounts" else "excluding subaccounts" :: String) -- inclusive or exclusive balance asserted ?
|
||||||
assertedcommbalstrpadded
|
(pad assertedstr) -- asserted amount, without cost
|
||||||
actualcommbalstrpadded
|
(pad actualstr) -- actual amount, without cost
|
||||||
diffstr -- diff
|
-- <> " (with costs: " <> T.pack (showMixedAmountWith fmt actualcommbal) <> ")" -- debugging
|
||||||
|
diffstr -- their difference
|
||||||
(acct ++ if isinclusive then "" else "$") -- query matching the account(s) postings
|
(acct ++ if isinclusive then "" else "$") -- query matching the account(s) postings
|
||||||
(if istotal then "" else (" cur:" ++ quoteForCommandLine (T.unpack assertedcomm))) -- query matching the commodity(ies)
|
(if istotal then "" else (" cur:" ++ quoteForCommandLine (T.unpack assertedcomm))) -- query matching the commodity(ies)
|
||||||
|
|
||||||
@ -689,17 +695,12 @@ checkBalanceAssertionOneCommodityB p@Posting{paccount=assertedacct} assertedcomm
|
|||||||
pos = baposition ass
|
pos = baposition ass
|
||||||
(_,_,_,ex) = makeBalanceAssertionErrorExcerpt p
|
(_,_,_,ex) = makeBalanceAssertionErrorExcerpt p
|
||||||
assertedcommstr = if T.null assertedcomm then "\"\"" else assertedcomm
|
assertedcommstr = if T.null assertedcomm then "\"\"" else assertedcomm
|
||||||
showamt = showAmountWithZeroCommodity
|
fmt = oneLineFmt{displayZeroCommodity=True}
|
||||||
assertedcommbalstr = showamt assertedcommbalcostless
|
assertedstr = showAmountWith fmt assertedcommbalcostless
|
||||||
actualcommbalstr = showamt actualcommbalcostless
|
actualstr = showAmountWith fmt actualcommbalcostless
|
||||||
amtswidth = max (length assertedcommbalstr) (length actualcommbalstr)
|
diffstr = showAmountWith fmt $ assertedcommbalcostless - actualcommbalcostless
|
||||||
assertedcommbalstrpadded = fitText (Just amtswidth) Nothing False False $ T.pack assertedcommbalstr
|
pad = fitText (Just w) Nothing False False . T.pack where w = max (length assertedstr) (length actualstr)
|
||||||
actualcommbalstrpadded = fitText (Just amtswidth) Nothing False False $ T.pack actualcommbalstr
|
|
||||||
-- diffstr = show $ aquantity assertedcommbal - aquantity actualcommbalcostless
|
|
||||||
diffstr = showamt $ assertedcommbal - actualcommbalcostless
|
|
||||||
|
|
||||||
-- (showDate $ postingDate p)
|
|
||||||
-- (asprecision $ astyle actualcommbalodity) -- should be the standard display precision I think
|
|
||||||
|
|
||||||
unless pass $ throwError errmsg
|
unless pass $ throwError errmsg
|
||||||
{- XXX
|
{- XXX
|
||||||
|
|||||||
@ -488,3 +488,21 @@ $ hledger -f- print -x date:2022-01-02
|
|||||||
assets:usd €10
|
assets:usd €10
|
||||||
|
|
||||||
>=
|
>=
|
||||||
|
|
||||||
|
# ** 29. -10 A should be inferred for a.
|
||||||
|
# And the 0 B balaance assertion should ignore costs and succeed,
|
||||||
|
# even though the balance is 1 B @@ 10 A - 1 B. (#2150)
|
||||||
|
<
|
||||||
|
2024-01-01
|
||||||
|
a 10 A
|
||||||
|
e
|
||||||
|
|
||||||
|
2024-01-02
|
||||||
|
b 1 B @@ 10 A
|
||||||
|
a = 0 A
|
||||||
|
|
||||||
|
2024-01-03
|
||||||
|
b -1 B = 0 B
|
||||||
|
e
|
||||||
|
|
||||||
|
$ hledger -f - check
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user