Don't infer a txn price with same-sign amounts (#1551)
This commit is contained in:
commit
1e2ff1315b
@ -552,41 +552,42 @@ inferBalancingPrices t@Transaction{tpostings=ps} = t{tpostings=ps'}
|
|||||||
-- posting type (real or balanced virtual). If we cannot or should not infer
|
-- posting type (real or balanced virtual). If we cannot or should not infer
|
||||||
-- prices, just act as the identity on postings.
|
-- prices, just act as the identity on postings.
|
||||||
priceInferrerFor :: Transaction -> PostingType -> (Posting -> Posting)
|
priceInferrerFor :: Transaction -> PostingType -> (Posting -> Posting)
|
||||||
priceInferrerFor t pt = maybe id inferprice $ inferFromAndTo sumamounts
|
priceInferrerFor t pt = maybe id inferprice inferFromAndTo
|
||||||
where
|
where
|
||||||
postings = filter ((==pt).ptype) $ tpostings t
|
postings = filter ((==pt).ptype) $ tpostings t
|
||||||
pcommodities = map acommodity $ concatMap (amounts . pamount) postings
|
pcommodities = map acommodity $ concatMap (amounts . pamount) postings
|
||||||
sumamounts = amounts $ sumPostings postings -- amounts normalises to one amount per commodity & price
|
sumamounts = amounts $ sumPostings postings -- amounts normalises to one amount per commodity & price
|
||||||
noprices = all (isNothing . aprice) sumamounts
|
|
||||||
|
|
||||||
-- We can infer prices if there are no prices given, and exactly two commodities in the
|
-- We can infer prices if there are no prices given, exactly two commodities in the normalised
|
||||||
-- normalised sum of postings in this transaction. The amount we are converting from is
|
-- sum of postings in this transaction, and these two have opposite signs. The amount we are
|
||||||
-- the first commodity to appear in the ordered list of postings, and the commodity we
|
-- converting from is the first commodity to appear in the ordered list of postings, and the
|
||||||
-- are converting to is the other. If we cannot infer prices, return Nothing.
|
-- commodity we are converting to is the other. If we cannot infer prices, return Nothing.
|
||||||
inferFromAndTo [a,b] | noprices = asum $ map orderIfMatches pcommodities
|
inferFromAndTo = case sumamounts of
|
||||||
where orderIfMatches x | x == acommodity a = Just (a,b)
|
[a,b] | noprices, oppositesigns -> asum $ map orderIfMatches pcommodities
|
||||||
|
where
|
||||||
|
noprices = all (isNothing . aprice) sumamounts
|
||||||
|
oppositesigns = signum (aquantity a) /= signum (aquantity b)
|
||||||
|
orderIfMatches x | x == acommodity a = Just (a,b)
|
||||||
| x == acommodity b = Just (b,a)
|
| x == acommodity b = Just (b,a)
|
||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
inferFromAndTo _ = Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
-- For each posting, if the posting type matches, there is only a single amount in the posting,
|
-- For each posting, if the posting type matches, there is only a single amount in the posting,
|
||||||
-- and the commodity of the amount matches the amount we're converting from,
|
-- and the commodity of the amount matches the amount we're converting from,
|
||||||
-- then set its price based on the ratio between fromamount and toamount.
|
-- then set its price based on the ratio between fromamount and toamount.
|
||||||
inferprice (fromamount, toamount) posting
|
inferprice (fromamount, toamount) posting
|
||||||
| [a] <- amounts (pamount posting), ptype posting == pt, acommodity a == acommodity fromamount
|
| [a] <- amounts (pamount posting), ptype posting == pt, acommodity a == acommodity fromamount
|
||||||
, let totalpricesign = if aquantity a < 0 then negate else id
|
= posting{ pamount = mixedAmount a{aprice=Just conversionprice}
|
||||||
= posting{ pamount = mixedAmount a{aprice=Just $ conversionprice totalpricesign}
|
|
||||||
, poriginal = Just $ originalPosting posting }
|
, poriginal = Just $ originalPosting posting }
|
||||||
| otherwise = posting
|
| otherwise = posting
|
||||||
where
|
where
|
||||||
-- If only one Amount in the posting list matches fromamount we can use TotalPrice,
|
-- If only one Amount in the posting list matches fromamount we can use TotalPrice.
|
||||||
-- but we need to know the sign. Otherwise divide the conversion equally among the
|
-- Otherwise divide the conversion equally among the Amounts by using a unit price.
|
||||||
-- Amounts by using a unit price.
|
conversionprice = case filter (== acommodity fromamount) pcommodities of
|
||||||
conversionprice sign = case filter (== acommodity fromamount) pcommodities of
|
[_] -> TotalPrice $ negate toamount
|
||||||
[_] -> TotalPrice $ sign (abs toamount) `withPrecision` NaturalPrecision
|
_ -> UnitPrice $ negate unitprice `withPrecision` unitprecision
|
||||||
_ -> UnitPrice $ abs unitprice `withPrecision` unitprecision
|
|
||||||
|
|
||||||
unitprice = (aquantity fromamount) `divideAmount` toamount
|
unitprice = aquantity fromamount `divideAmount` toamount
|
||||||
unitprecision = case (asprecision $ astyle fromamount, asprecision $ astyle toamount) of
|
unitprecision = case (asprecision $ astyle fromamount, asprecision $ astyle toamount) of
|
||||||
(Precision a, Precision b) -> Precision . max 2 $ saturatedAdd a b
|
(Precision a, Precision b) -> Precision . max 2 $ saturatedAdd a b
|
||||||
_ -> NaturalPrecision
|
_ -> NaturalPrecision
|
||||||
@ -917,7 +918,7 @@ tests_Transaction =
|
|||||||
[ posting {paccount = "a", pamount = mixedAmount (usd 1.35)}
|
[ posting {paccount = "a", pamount = mixedAmount (usd 1.35)}
|
||||||
, posting {paccount = "b", pamount = mixedAmount (eur (-1))}
|
, posting {paccount = "b", pamount = mixedAmount (eur (-1))}
|
||||||
])) @?=
|
])) @?=
|
||||||
Right (mixedAmount $ usd 1.35 @@ (eur 1 `withPrecision` NaturalPrecision))
|
Right (mixedAmount $ usd 1.35 @@ eur 1)
|
||||||
,test "balanceTransaction balances based on cost if there are unit prices" $
|
,test "balanceTransaction balances based on cost if there are unit prices" $
|
||||||
assertRight $
|
assertRight $
|
||||||
balanceTransaction
|
balanceTransaction
|
||||||
|
|||||||
@ -231,9 +231,9 @@ $ hledger -f- print --auto
|
|||||||
$ hledger -f- print --auto
|
$ hledger -f- print --auto
|
||||||
2018-01-01 ; modified:
|
2018-01-01 ; modified:
|
||||||
Expenses:Joint:Widgets $100.00
|
Expenses:Joint:Widgets $100.00
|
||||||
Expenses:Joint $-100.00 @@ £50 ; generated-posting: = ^Expenses:Joint
|
Expenses:Joint $-100.00 @@ £50.00 ; generated-posting: = ^Expenses:Joint
|
||||||
Liabilities:Joint:Bob $50.00 @@ £25 ; generated-posting: = ^Expenses:Joint
|
Liabilities:Joint:Bob $50.00 @@ £25.00 ; generated-posting: = ^Expenses:Joint
|
||||||
Liabilities:Joint:Bill $50.00 @@ £25 ; generated-posting: = ^Expenses:Joint
|
Liabilities:Joint:Bill $50.00 @@ £25.00 ; generated-posting: = ^Expenses:Joint
|
||||||
Assets:Joint:Bank £-50.00
|
Assets:Joint:Bank £-50.00
|
||||||
|
|
||||||
>=0
|
>=0
|
||||||
|
|||||||
@ -127,7 +127,7 @@ hledger: "-" (lines 1-3)
|
|||||||
could not balance this transaction:
|
could not balance this transaction:
|
||||||
real postings all have the same sign
|
real postings all have the same sign
|
||||||
2020-01-01
|
2020-01-01
|
||||||
a 1A @@ 1B
|
a 1A
|
||||||
b 1B
|
b 1B
|
||||||
|
|
||||||
>=1
|
>=1
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user