lib: Distinguish between an Amount having quantity (or rounded quantity 0), and having both quantity and totalprice 0 (or rounded to 0).

This commit is contained in:
Stephen Morgan 2021-01-16 21:43:24 +11:00 committed by Simon Michael
parent 81b778a389
commit ecca7f4e0c
3 changed files with 35 additions and 6 deletions

View File

@ -63,6 +63,8 @@ module Hledger.Data.Amount (
amountCost,
amountIsZero,
amountLooksZero,
amountAndPriceIsZero,
amountAndPriceLooksZero,
divideAmount,
multiplyAmount,
divideAmountAndPrice,
@ -114,6 +116,8 @@ module Hledger.Data.Amount (
isNegativeMixedAmount,
mixedAmountIsZero,
mixedAmountLooksZero,
mixedAmountAndPriceIsZero,
mixedAmountAndPriceLooksZero,
mixedAmountTotalPriceToUnitPrice,
-- ** rendering
styleMixedAmount,
@ -324,10 +328,27 @@ amountRoundedQuantity Amount{aquantity=q, astyle=AmountStyle{asprecision=p}} = c
amountLooksZero :: Amount -> Bool
amountLooksZero = (0==) . amountRoundedQuantity
-- | Does mixed amount and its price appear to be zero when rendered with its
-- display precision ?
amountAndPriceLooksZero :: Amount -> Bool
amountAndPriceLooksZero amt = amountLooksZero amt && priceLooksZero
where
priceLooksZero = case aprice amt of
Just (TotalPrice p) -> amountLooksZero p
_ -> True
-- | Is this amount exactly zero, ignoring its display precision ?
amountIsZero :: Amount -> Bool
amountIsZero Amount{aquantity=q} = q == 0
-- | Are this amount and its price exactly zero, ignoring its display precision ?
amountAndPriceIsZero :: Amount -> Bool
amountAndPriceIsZero amt@Amount{aquantity=q} = q == 0 && priceIsZero
where
priceIsZero = case aprice amt of
Just (TotalPrice p) -> amountIsZero p
_ -> True
-- | Set an amount's display precision, flipped.
withPrecision :: Amount -> AmountPrecision -> Amount
withPrecision = flip amountSetPrecision
@ -496,8 +517,7 @@ applyDigitGroupStyle (Just (DigitGroups c (g:gs))) l s = addseps (g:|gs) (toInte
-- | Canonicalise an amount's display style using the provided commodity style map.
canonicaliseAmount :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
canonicaliseAmount styles a@Amount{acommodity=c, astyle=s} = a{astyle=s'}
where
s' = findWithDefault s c styles
where s' = M.findWithDefault s c styles
-------------------------------------------------------------------------------
-- MixedAmount
@ -658,10 +678,19 @@ isNegativeMixedAmount m =
mixedAmountLooksZero :: MixedAmount -> Bool
mixedAmountLooksZero = all amountLooksZero . amounts . normaliseMixedAmountSquashPricesForDisplay
-- | Does this mixed amount and its price appear to be zero when rendered with its
-- display precision ?
mixedAmountAndPriceLooksZero :: MixedAmount -> Bool
mixedAmountAndPriceLooksZero = all amountAndPriceLooksZero . amounts . normaliseMixedAmountSquashPricesForDisplay
-- | Is this mixed amount exactly zero, ignoring display precisions ?
mixedAmountIsZero :: MixedAmount -> Bool
mixedAmountIsZero = all amountIsZero . amounts . normaliseMixedAmountSquashPricesForDisplay
-- | Is this mixed amount exactly zero, ignoring display precisions ?
mixedAmountAndPriceIsZero :: MixedAmount -> Bool
mixedAmountAndPriceIsZero = all amountAndPriceIsZero . amounts . normaliseMixedAmountSquashPricesForDisplay
-- -- | MixedAmount derived Eq instance in Types.hs doesn't know that we
-- -- want $0 = EUR0 = 0. Yet we don't want to drag all this code over there.
-- -- For now, use this when cross-commodity zero equality is important.

View File

@ -257,7 +257,7 @@ isPostingInDateSpan' PrimaryDate s = spanContainsDate s . postingDate
isPostingInDateSpan' SecondaryDate s = spanContainsDate s . postingDate2
isEmptyPosting :: Posting -> Bool
isEmptyPosting = mixedAmountLooksZero . pamount
isEmptyPosting = mixedAmountAndPriceLooksZero . pamount
-- AccountName stuff that depends on PostingType

View File

@ -367,8 +367,8 @@ transactionCheckBalanced mstyles t = errs
-- check for mixed signs, detecting nonzeros at display precision
canonicalise = maybe id canonicaliseMixedAmount mstyles
signsOk ps =
case filter (not.mixedAmountLooksZero) $ map (canonicalise.mixedAmountCost.pamount) ps of
signsOk ps =
case filter (not.mixedAmountAndPriceLooksZero) $ map (canonicalise.mixedAmountCost.pamount) ps of
nonzeros | length nonzeros >= 2
-> length (nubSort $ mapMaybe isNegativeMixedAmount nonzeros) > 1
_ -> True
@ -378,7 +378,7 @@ transactionCheckBalanced mstyles t = errs
(rsum, bvsum) = (sumPostings rps, sumPostings bvps)
(rsumcost, bvsumcost) = (mixedAmountCost rsum, mixedAmountCost bvsum)
(rsumdisplay, bvsumdisplay) = (canonicalise rsumcost, canonicalise bvsumcost)
(rsumok, bvsumok) = (mixedAmountLooksZero rsumdisplay, mixedAmountLooksZero bvsumdisplay)
(rsumok, bvsumok) = (mixedAmountAndPriceLooksZero rsumdisplay, mixedAmountAndPriceLooksZero bvsumdisplay)
-- generate error messages, showing amounts with their original precision
errs = filter (not.null) [rmsg, bvmsg]