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 | ||||||
|                              | x == acommodity b = Just (b,a) |         where | ||||||
|                              | otherwise         = Nothing |           noprices      = all (isNothing . aprice) sumamounts | ||||||
|     inferFromAndTo _ = Nothing |           oppositesigns = signum (aquantity a) /= signum (aquantity b) | ||||||
|  |           orderIfMatches x | x == acommodity a = Just (a,b) | ||||||
|  |                            | x == acommodity b = Just (b,a) | ||||||
|  |                            | otherwise         = 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 | ||||||
|  | |||||||
| @ -230,11 +230,11 @@ $ hledger -f- print --auto | |||||||
| # 12. | # 12. | ||||||
| $ 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