mirror of
https://github.com/simonmichael/hledger.git
synced 2024-12-31 22:31:54 +03:00
lib: Infer prices correctly even when there are only balance assignments.
This commit is contained in:
parent
7cb621b82f
commit
0078f1a520
@ -63,9 +63,10 @@ module Hledger.Data.Transaction (
|
|||||||
where
|
where
|
||||||
|
|
||||||
import Data.Default (def)
|
import Data.Default (def)
|
||||||
|
import Data.Foldable (asum)
|
||||||
import Data.List (intercalate, partition)
|
import Data.List (intercalate, partition)
|
||||||
import Data.List.Extra (nubSort)
|
import Data.List.Extra (nubSort)
|
||||||
import Data.Maybe (fromMaybe, isJust, mapMaybe)
|
import Data.Maybe (fromMaybe, isNothing, mapMaybe)
|
||||||
#if !(MIN_VERSION_base(4,11,0))
|
#if !(MIN_VERSION_base(4,11,0))
|
||||||
import Data.Semigroup ((<>))
|
import Data.Semigroup ((<>))
|
||||||
#endif
|
#endif
|
||||||
@ -548,40 +549,48 @@ inferBalancingPrices t@Transaction{tpostings=ps} = t{tpostings=ps'}
|
|||||||
|
|
||||||
-- | Generate a posting update function which assigns a suitable balancing
|
-- | Generate a posting update function which assigns a suitable balancing
|
||||||
-- price to the posting, if and as appropriate for the given transaction and
|
-- price to the posting, if and as appropriate for the given transaction and
|
||||||
-- posting type (real or balanced virtual).
|
-- posting type (real or balanced virtual). If we cannot or should not infer
|
||||||
|
-- prices, just act as the identity on postings.
|
||||||
priceInferrerFor :: Transaction -> PostingType -> (Posting -> Posting)
|
priceInferrerFor :: Transaction -> PostingType -> (Posting -> Posting)
|
||||||
priceInferrerFor t pt = inferprice
|
priceInferrerFor t pt = maybe id inferprice $ inferFromAndTo sumamounts
|
||||||
where
|
where
|
||||||
postings = filter ((==pt).ptype) $ tpostings t
|
postings = filter ((==pt).ptype) $ tpostings t
|
||||||
pmixedamounts = map pamount postings
|
pcommodities = map acommodity $ concatMap (amounts . pamount) postings
|
||||||
pcommodities = map acommodity $ concatMap amountsRaw pmixedamounts
|
sumamounts = amounts $ sumPostings postings -- amounts normalises to one amount per commodity & price
|
||||||
sumamounts = amounts $ maSum pmixedamounts -- sum normalises to one amount per commodity & price
|
noprices = all (isNothing . aprice) sumamounts
|
||||||
sumcommodities = map acommodity sumamounts
|
|
||||||
sumprices = filter isJust $ map aprice sumamounts
|
|
||||||
caninferprices = length sumcommodities == 2 && null sumprices
|
|
||||||
|
|
||||||
inferprice p@Posting{pamount=amt} = case amountsRaw amt of
|
-- We can infer prices if there are no prices given, and exactly two commodities in the
|
||||||
[a] | caninferprices && ptype p == pt && acommodity a == fromcommodity
|
-- normalised sum of postings in this transaction. The amount we are converting from is
|
||||||
-> p{ pamount=mixedAmount a{aprice=Just conversionprice}
|
-- the first commodity to appear in the ordered list of postings, and the commodity we
|
||||||
, poriginal=Just $ originalPosting p}
|
-- are converting to is the other. If we cannot infer prices, return Nothing.
|
||||||
where
|
inferFromAndTo [a,b] | noprices = asum $ map orderIfMatches pcommodities
|
||||||
fromcommodity = head $ filter (`elem` sumcommodities) pcommodities -- these heads are ugly but should be safe
|
where orderIfMatches x | x == acommodity a = Just (a,b)
|
||||||
totalpricesign = if aquantity a < 0 then negate else id
|
| x == acommodity b = Just (b,a)
|
||||||
conversionprice = case filter (==fromcommodity) pcommodities of
|
| otherwise = Nothing
|
||||||
[_] -> TotalPrice $ totalpricesign (abs toamount) `withPrecision` NaturalPrecision
|
inferFromAndTo _ = Nothing
|
||||||
_ -> UnitPrice $ abs unitprice `withPrecision` unitprecision
|
|
||||||
where
|
-- For each posting, if the posting type matches, there is only a single amount in the posting,
|
||||||
fromamount = head $ filter ((==fromcommodity).acommodity) sumamounts
|
-- and the commodity of the amount matches the amount we're converting from,
|
||||||
fromprecision = asprecision $ astyle fromamount
|
-- then set its price based on the ratio between fromamount and toamount.
|
||||||
tocommodity = head $ filter (/=fromcommodity) sumcommodities
|
inferprice (fromamount, toamount) posting
|
||||||
toamount = head $ filter ((==tocommodity).acommodity) sumamounts
|
| [a] <- amounts (pamount posting), ptype posting == pt, acommodity a == acommodity fromamount
|
||||||
toprecision = asprecision $ astyle toamount
|
, let totalpricesign = if aquantity a < 0 then negate else id
|
||||||
unitprice = aquantity fromamount `divideAmount` toamount
|
= posting{ pamount = mixedAmount a{aprice=Just $ conversionprice totalpricesign}
|
||||||
-- Sum two display precisions, capping the result at the maximum bound
|
, poriginal = Just $ originalPosting posting }
|
||||||
unitprecision = case (fromprecision, toprecision) of
|
| otherwise = posting
|
||||||
(Precision a, Precision b) -> Precision $ if maxBound - a < b then maxBound else max 2 (a + b)
|
where
|
||||||
_ -> NaturalPrecision
|
-- If only one Amount in the posting list matches fromamount we can use TotalPrice,
|
||||||
_ -> p
|
-- but we need to know the sign. Otherwise divide the conversion equally among the
|
||||||
|
-- Amounts by using a unit price.
|
||||||
|
conversionprice sign = case filter (== acommodity fromamount) pcommodities of
|
||||||
|
[_] -> TotalPrice $ sign (abs toamount) `withPrecision` NaturalPrecision
|
||||||
|
_ -> UnitPrice $ abs unitprice `withPrecision` unitprecision
|
||||||
|
|
||||||
|
unitprice = (aquantity fromamount) `divideAmount` toamount
|
||||||
|
unitprecision = case (asprecision $ astyle fromamount, asprecision $ astyle toamount) of
|
||||||
|
(Precision a, Precision b) -> Precision . max 2 $ saturatedAdd a b
|
||||||
|
_ -> NaturalPrecision
|
||||||
|
saturatedAdd a b = if maxBound - a < b then maxBound else a + b
|
||||||
|
|
||||||
-- Get a transaction's secondary date, defaulting to the primary date.
|
-- Get a transaction's secondary date, defaulting to the primary date.
|
||||||
transactionDate2 :: Transaction -> Day
|
transactionDate2 :: Transaction -> Day
|
||||||
|
@ -190,6 +190,13 @@ $ hledger -f - stats
|
|||||||
a $0 = $7
|
a $0 = $7
|
||||||
b $0 = $-7
|
b $0 = $-7
|
||||||
|
|
||||||
|
2013/1/5
|
||||||
|
(c) 100 A
|
||||||
|
|
||||||
|
2013/1/5
|
||||||
|
c = 50 B
|
||||||
|
c = 50 A
|
||||||
|
|
||||||
$ hledger -f - stats
|
$ hledger -f - stats
|
||||||
> /Transactions/
|
> /Transactions/
|
||||||
>=0
|
>=0
|
||||||
|
Loading…
Reference in New Issue
Block a user