dev: AmountPrice,UnitPrice,TotalPrice -> AmountCost,UnitCost,TotalCost; related renames
This commit is contained in:
parent
e201775e93
commit
8102bd9c2b
@ -12,13 +12,13 @@ It has a (possibly null) 'CommoditySymbol' and a numeric quantity:
|
|||||||
0
|
0
|
||||||
@
|
@
|
||||||
|
|
||||||
It may also have an assigned 'Price', representing this amount's per-unit
|
It may also have an 'AmountCost', representing this amount's per-unit
|
||||||
or total cost in a different commodity. If present, this is rendered like
|
or total cost in a different commodity. If present, this is rendered like
|
||||||
so:
|
so:
|
||||||
|
|
||||||
@
|
@
|
||||||
EUR 2 \@ $1.50 (unit price)
|
EUR 2 \@ $1.50 (unit cost)
|
||||||
EUR 2 \@\@ $3 (total price)
|
EUR 2 \@\@ $3 (total cost)
|
||||||
@
|
@
|
||||||
|
|
||||||
A 'MixedAmount' is zero or more simple amounts, so can represent multiple
|
A 'MixedAmount' is zero or more simple amounts, so can represent multiple
|
||||||
@ -31,12 +31,11 @@ commodities; this is the type most often used:
|
|||||||
@
|
@
|
||||||
|
|
||||||
A mixed amount is always \"normalised\", it has no more than one amount
|
A mixed amount is always \"normalised\", it has no more than one amount
|
||||||
in each commodity and price. When calling 'amounts' it will have no zero
|
in each commodity and cost. When calling 'amounts' it will have no zero
|
||||||
amounts, or just a single zero amount and no other amounts.
|
amounts, or just a single zero amount and no other amounts.
|
||||||
|
|
||||||
Limited arithmetic with simple and mixed amounts is supported, best used
|
Limited arithmetic with simple and mixed amounts is supported, best used
|
||||||
with similar amounts since it mostly ignores assigned prices and commodity
|
with similar amounts since it mostly ignores costss and commodity exchange rates.
|
||||||
exchange rates.
|
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
@ -253,11 +252,11 @@ noColour = AmountDisplayOpts {
|
|||||||
, displayColour = False
|
, displayColour = False
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | Display Amount and MixedAmount with no prices.
|
-- | Display Amount and MixedAmount with no costs.
|
||||||
noCost :: AmountDisplayOpts
|
noCost :: AmountDisplayOpts
|
||||||
noCost = def{displayCost=False}
|
noCost = def{displayCost=False}
|
||||||
|
|
||||||
-- | Display Amount and MixedAmount on one line with no prices.
|
-- | Display Amount and MixedAmount on one line with no costs.
|
||||||
oneLine :: AmountDisplayOpts
|
oneLine :: AmountDisplayOpts
|
||||||
oneLine = def{displayOneLine=True, displayCost=False}
|
oneLine = def{displayOneLine=True, displayCost=False}
|
||||||
|
|
||||||
@ -297,15 +296,15 @@ usd n = nullamt{acommodity="$", aquantity=roundTo 2 n, astyle=amountstyle{asprec
|
|||||||
eur n = nullamt{acommodity="€", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Precision 2}}
|
eur n = nullamt{acommodity="€", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Precision 2}}
|
||||||
gbp n = nullamt{acommodity="£", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Precision 2}}
|
gbp n = nullamt{acommodity="£", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Precision 2}}
|
||||||
per n = nullamt{acommodity="%", aquantity=n, astyle=amountstyle{asprecision=Precision 1, ascommodityside=R, ascommodityspaced=True}}
|
per n = nullamt{acommodity="%", aquantity=n, astyle=amountstyle{asprecision=Precision 1, ascommodityside=R, ascommodityspaced=True}}
|
||||||
amt `at` priceamt = amt{aprice=Just $ UnitPrice priceamt}
|
amt `at` costamt = amt{aprice=Just $ UnitCost costamt}
|
||||||
amt @@ priceamt = amt{aprice=Just $ TotalPrice priceamt}
|
amt @@ costamt = amt{aprice=Just $ TotalCost costamt}
|
||||||
|
|
||||||
-- | Apply a binary arithmetic operator to two amounts, which should
|
-- | Apply a binary arithmetic operator to two amounts, which should
|
||||||
-- be in the same commodity if non-zero (warning, this is not checked).
|
-- be in the same commodity if non-zero (warning, this is not checked).
|
||||||
-- A zero result keeps the commodity of the second amount.
|
-- A zero result keeps the commodity of the second amount.
|
||||||
-- The result's display style is that of the second amount, with
|
-- The result's display style is that of the second amount, with
|
||||||
-- precision set to the highest of either amount.
|
-- precision set to the highest of either amount.
|
||||||
-- Prices are ignored and discarded.
|
-- Costs are ignored and discarded.
|
||||||
-- Remember: the caller is responsible for ensuring both amounts have the same commodity.
|
-- Remember: the caller is responsible for ensuring both amounts have the same commodity.
|
||||||
similarAmountsOp :: (Quantity -> Quantity -> Quantity) -> Amount -> Amount -> Amount
|
similarAmountsOp :: (Quantity -> Quantity -> Quantity) -> Amount -> Amount -> Amount
|
||||||
similarAmountsOp op Amount{acommodity=_, aquantity=q1, astyle=AmountStyle{asprecision=p1}}
|
similarAmountsOp op Amount{acommodity=_, aquantity=q1, astyle=AmountStyle{asprecision=p1}}
|
||||||
@ -316,41 +315,41 @@ similarAmountsOp op Amount{acommodity=_, aquantity=q1, astyle=AmountStyle{aspre
|
|||||||
-- otherwise = error "tried to do simple arithmetic with amounts in different commodities"
|
-- otherwise = error "tried to do simple arithmetic with amounts in different commodities"
|
||||||
|
|
||||||
-- | Convert an amount to the specified commodity, ignoring and discarding
|
-- | Convert an amount to the specified commodity, ignoring and discarding
|
||||||
-- any assigned prices and assuming an exchange rate of 1.
|
-- any costs and assuming an exchange rate of 1.
|
||||||
amountWithCommodity :: CommoditySymbol -> Amount -> Amount
|
amountWithCommodity :: CommoditySymbol -> Amount -> Amount
|
||||||
amountWithCommodity c a = a{acommodity=c, aprice=Nothing}
|
amountWithCommodity c a = a{acommodity=c, aprice=Nothing}
|
||||||
|
|
||||||
-- | Convert a amount to its "cost" or "selling price" in another commodity,
|
-- | Convert a amount to its "cost" or "selling price" in another commodity,
|
||||||
-- using its attached transaction price if it has one. Notes:
|
-- using its attached cost if it has one. Notes:
|
||||||
--
|
--
|
||||||
-- - price amounts must be MixedAmounts with exactly one component Amount
|
-- - cost amounts must be MixedAmounts with exactly one component Amount
|
||||||
-- (or there will be a runtime error XXX)
|
-- (or there will be a runtime error XXX)
|
||||||
--
|
--
|
||||||
-- - price amounts should be positive in the Journal
|
-- - cost amounts should be positive in the Journal
|
||||||
-- (though this is currently not enforced)
|
-- (though this is currently not enforced)
|
||||||
amountCost :: Amount -> Amount
|
amountCost :: Amount -> Amount
|
||||||
amountCost a@Amount{aquantity=q, aprice=mp} =
|
amountCost a@Amount{aquantity=q, aprice=mp} =
|
||||||
case mp of
|
case mp of
|
||||||
Nothing -> a
|
Nothing -> a
|
||||||
Just (UnitPrice p@Amount{aquantity=pq}) -> p{aquantity=pq * q}
|
Just (UnitCost p@Amount{aquantity=pq}) -> p{aquantity=pq * q}
|
||||||
Just (TotalPrice p@Amount{aquantity=pq}) -> p{aquantity=pq}
|
Just (TotalCost p@Amount{aquantity=pq}) -> p{aquantity=pq}
|
||||||
|
|
||||||
-- | Strip all prices from an Amount
|
-- | Strip all costs from an Amount
|
||||||
amountStripCost :: Amount -> Amount
|
amountStripCost :: Amount -> Amount
|
||||||
amountStripCost a = a{aprice=Nothing}
|
amountStripCost a = a{aprice=Nothing}
|
||||||
|
|
||||||
-- | Apply a function to an amount's quantity (and its total price, if it has one).
|
-- | Apply a function to an amount's quantity (and its total cost, if it has one).
|
||||||
transformAmount :: (Quantity -> Quantity) -> Amount -> Amount
|
transformAmount :: (Quantity -> Quantity) -> Amount -> Amount
|
||||||
transformAmount f a@Amount{aquantity=q,aprice=p} = a{aquantity=f q, aprice=f' <$> p}
|
transformAmount f a@Amount{aquantity=q,aprice=p} = a{aquantity=f q, aprice=f' <$> p}
|
||||||
where
|
where
|
||||||
f' (TotalPrice a1@Amount{aquantity=pq}) = TotalPrice a1{aquantity = f pq}
|
f' (TotalCost a1@Amount{aquantity=pq}) = TotalCost a1{aquantity = f pq}
|
||||||
f' p' = p'
|
f' p' = p'
|
||||||
|
|
||||||
-- | Divide an amount's quantity (and total cost, if any) by some number.
|
-- | Divide an amount's quantity (and total cost, if any) by some number.
|
||||||
divideAmount :: Quantity -> Amount -> Amount
|
divideAmount :: Quantity -> Amount -> Amount
|
||||||
divideAmount n = transformAmount (/n)
|
divideAmount n = transformAmount (/n)
|
||||||
|
|
||||||
-- | Multiply an amount's quantity (and its total price, if it has one) by a constant.
|
-- | Multiply an amount's quantity (and its total cost, if it has one) by a constant.
|
||||||
multiplyAmount :: Quantity -> Amount -> Amount
|
multiplyAmount :: Quantity -> Amount -> Amount
|
||||||
multiplyAmount n = transformAmount (*n)
|
multiplyAmount n = transformAmount (*n)
|
||||||
|
|
||||||
@ -359,7 +358,7 @@ multiplyAmount n = transformAmount (*n)
|
|||||||
invertAmount :: Amount -> Amount
|
invertAmount :: Amount -> Amount
|
||||||
invertAmount a@Amount{aquantity=q} = a{aquantity=1/q}
|
invertAmount a@Amount{aquantity=q} = a{aquantity=1/q}
|
||||||
|
|
||||||
-- | Is this amount negative ? The price is ignored.
|
-- | Is this amount negative ? The cost is ignored.
|
||||||
isNegativeAmount :: Amount -> Bool
|
isNegativeAmount :: Amount -> Bool
|
||||||
isNegativeAmount Amount{aquantity=q} = q < 0
|
isNegativeAmount Amount{aquantity=q} = q < 0
|
||||||
|
|
||||||
@ -370,26 +369,26 @@ amountRoundedQuantity Amount{aquantity=q, astyle=AmountStyle{asprecision=mp}} =
|
|||||||
NaturalPrecision -> q
|
NaturalPrecision -> q
|
||||||
Precision p -> roundTo p q
|
Precision p -> roundTo p q
|
||||||
|
|
||||||
-- | Apply a test to both an Amount and its total price, if it has one.
|
-- | Apply a test to both an Amount and its total cost, if it has one.
|
||||||
testAmountAndTotalPrice :: (Amount -> Bool) -> Amount -> Bool
|
testAmountAndTotalCost :: (Amount -> Bool) -> Amount -> Bool
|
||||||
testAmountAndTotalPrice f amt = case aprice amt of
|
testAmountAndTotalCost f amt = case aprice amt of
|
||||||
Just (TotalPrice price) -> f amt && f price
|
Just (TotalCost cost) -> f amt && f cost
|
||||||
_ -> f amt
|
_ -> f amt
|
||||||
|
|
||||||
-- | Do this Amount and (and its total price, if it has one) appear to be zero
|
-- | Do this Amount and (and its total cost, if it has one) appear to be zero
|
||||||
-- when rendered with its display precision ?
|
-- when rendered with its display precision ?
|
||||||
-- The display precision should usually have a specific value here;
|
-- The display precision should usually have a specific value here;
|
||||||
-- if unset, it will be treated like NaturalPrecision.
|
-- if unset, it will be treated like NaturalPrecision.
|
||||||
amountLooksZero :: Amount -> Bool
|
amountLooksZero :: Amount -> Bool
|
||||||
amountLooksZero = testAmountAndTotalPrice looksZero
|
amountLooksZero = testAmountAndTotalCost looksZero
|
||||||
where
|
where
|
||||||
looksZero Amount{aquantity=Decimal e q, astyle=AmountStyle{asprecision=p}} = case p of
|
looksZero Amount{aquantity=Decimal e q, astyle=AmountStyle{asprecision=p}} = case p of
|
||||||
Precision d -> if e > d then abs q <= 5*10^(e-d-1) else q == 0
|
Precision d -> if e > d then abs q <= 5*10^(e-d-1) else q == 0
|
||||||
NaturalPrecision -> q == 0
|
NaturalPrecision -> q == 0
|
||||||
|
|
||||||
-- | Is this Amount (and its total price, if it has one) exactly zero, ignoring its display precision ?
|
-- | Is this Amount (and its total cost, if it has one) exactly zero, ignoring its display precision ?
|
||||||
amountIsZero :: Amount -> Bool
|
amountIsZero :: Amount -> Bool
|
||||||
amountIsZero = testAmountAndTotalPrice (\Amount{aquantity=Decimal _ q} -> q == 0)
|
amountIsZero = testAmountAndTotalCost (\Amount{aquantity=Decimal _ q} -> q == 0)
|
||||||
|
|
||||||
-- | Does this amount's internal Decimal representation have the
|
-- | Does this amount's internal Decimal representation have the
|
||||||
-- maximum number of digits, suggesting that it probably is
|
-- maximum number of digits, suggesting that it probably is
|
||||||
@ -522,8 +521,8 @@ instance HasAmounts Amount where
|
|||||||
|
|
||||||
mcost1 = case mcost0 of
|
mcost1 = case mcost0 of
|
||||||
Nothing -> Nothing
|
Nothing -> Nothing
|
||||||
Just (UnitPrice ca@Amount{aquantity=cq, astyle=cs, acommodity=ccomm}) -> Just $ UnitPrice ca{astyle=mknewstyle True cq cs ccomm}
|
Just (UnitCost ca@Amount{aquantity=cq, astyle=cs, acommodity=ccomm}) -> Just $ UnitCost ca{astyle=mknewstyle True cq cs ccomm}
|
||||||
Just (TotalPrice ca@Amount{aquantity=cq, astyle=cs, acommodity=ccomm}) -> Just $ TotalPrice ca{astyle=mknewstyle True cq cs ccomm}
|
Just (TotalCost ca@Amount{aquantity=cq, astyle=cs, acommodity=ccomm}) -> Just $ TotalCost ca{astyle=mknewstyle True cq cs ccomm}
|
||||||
|
|
||||||
mknewstyle :: Bool -> Quantity -> AmountStyle -> CommoditySymbol -> AmountStyle
|
mknewstyle :: Bool -> Quantity -> AmountStyle -> CommoditySymbol -> AmountStyle
|
||||||
mknewstyle iscost oldq olds com =
|
mknewstyle iscost oldq olds com =
|
||||||
@ -620,14 +619,14 @@ withDecimalPoint = flip setAmountDecimalPoint
|
|||||||
showAmountCostB :: Amount -> WideBuilder
|
showAmountCostB :: Amount -> WideBuilder
|
||||||
showAmountCostB amt = case aprice amt of
|
showAmountCostB amt = case aprice amt of
|
||||||
Nothing -> mempty
|
Nothing -> mempty
|
||||||
Just (UnitPrice pa) -> WideBuilder (TB.fromString " @ ") 3 <> showAmountB noColour{displayZeroCommodity=True} pa
|
Just (UnitCost pa) -> WideBuilder (TB.fromString " @ ") 3 <> showAmountB noColour{displayZeroCommodity=True} pa
|
||||||
Just (TotalPrice pa) -> WideBuilder (TB.fromString " @@ ") 4 <> showAmountB noColour{displayZeroCommodity=True} (sign pa)
|
Just (TotalCost pa) -> WideBuilder (TB.fromString " @@ ") 4 <> showAmountB noColour{displayZeroCommodity=True} (sign pa)
|
||||||
where sign = if aquantity amt < 0 then negate else id
|
where sign = if aquantity amt < 0 then negate else id
|
||||||
|
|
||||||
showAmountCostDebug :: Maybe AmountPrice -> String
|
showAmountCostDebug :: Maybe AmountCost -> String
|
||||||
showAmountCostDebug Nothing = ""
|
showAmountCostDebug Nothing = ""
|
||||||
showAmountCostDebug (Just (UnitPrice pa)) = " @ " ++ showAmountDebug pa
|
showAmountCostDebug (Just (UnitCost pa)) = " @ " ++ showAmountDebug pa
|
||||||
showAmountCostDebug (Just (TotalPrice pa)) = " @@ " ++ showAmountDebug pa
|
showAmountCostDebug (Just (TotalCost pa)) = " @@ " ++ showAmountDebug pa
|
||||||
|
|
||||||
-- | Get the string representation of an amount, based on its
|
-- | Get the string representation of an amount, based on its
|
||||||
-- commodity's display settings. String representations equivalent to
|
-- commodity's display settings. String representations equivalent to
|
||||||
@ -657,8 +656,8 @@ showAmountB
|
|||||||
,displayForceDecimalMark, displayCost, displayColour}
|
,displayForceDecimalMark, displayCost, displayColour}
|
||||||
a@Amount{astyle=style} =
|
a@Amount{astyle=style} =
|
||||||
color $ case ascommodityside style of
|
color $ case ascommodityside style of
|
||||||
L -> (if displayCommodity then wbFromText comm <> space else mempty) <> quantity' <> price
|
L -> (if displayCommodity then wbFromText comm <> space else mempty) <> quantity' <> cost
|
||||||
R -> quantity' <> (if displayCommodity then space <> wbFromText comm else mempty) <> price
|
R -> quantity' <> (if displayCommodity then space <> wbFromText comm else mempty) <> cost
|
||||||
where
|
where
|
||||||
color = if displayColour && isNegativeAmount a then colorB Dull Red else id
|
color = if displayColour && isNegativeAmount a then colorB Dull Red else id
|
||||||
quantity = showAmountQuantity displayForceDecimalMark $
|
quantity = showAmountQuantity displayForceDecimalMark $
|
||||||
@ -667,7 +666,7 @@ showAmountB
|
|||||||
| amountLooksZero a && not displayZeroCommodity = (WideBuilder (TB.singleton '0') 1, "")
|
| amountLooksZero a && not displayZeroCommodity = (WideBuilder (TB.singleton '0') 1, "")
|
||||||
| otherwise = (quantity, quoteCommoditySymbolIfNeeded $ acommodity a)
|
| otherwise = (quantity, quoteCommoditySymbolIfNeeded $ acommodity a)
|
||||||
space = if not (T.null comm) && ascommodityspaced style then WideBuilder (TB.singleton ' ') 1 else mempty
|
space = if not (T.null comm) && ascommodityspaced style then WideBuilder (TB.singleton ' ') 1 else mempty
|
||||||
price = if displayCost then showAmountCostB a else mempty
|
cost = if displayCost then showAmountCostB a else mempty
|
||||||
|
|
||||||
-- | Colour version. For a negative amount, adds ANSI codes to change the colour,
|
-- | Colour version. For a negative amount, adds ANSI codes to change the colour,
|
||||||
-- currently to hard-coded red.
|
-- currently to hard-coded red.
|
||||||
@ -676,7 +675,7 @@ showAmountB
|
|||||||
cshowAmount :: Amount -> String
|
cshowAmount :: Amount -> String
|
||||||
cshowAmount = wbUnpack . showAmountB def{displayColour=True}
|
cshowAmount = wbUnpack . showAmountB def{displayColour=True}
|
||||||
|
|
||||||
-- | Get the string representation of an amount, without any \@ price.
|
-- | Get the string representation of an amount, without any \@ cost.
|
||||||
--
|
--
|
||||||
-- > showAmountWithoutPrice = wbUnpack . showAmountB noCost
|
-- > showAmountWithoutPrice = wbUnpack . showAmountB noCost
|
||||||
showAmountWithoutPrice :: Amount -> String
|
showAmountWithoutPrice :: Amount -> String
|
||||||
@ -760,9 +759,9 @@ instance Num MixedAmount where
|
|||||||
-- | Calculate the key used to store an Amount within a MixedAmount.
|
-- | Calculate the key used to store an Amount within a MixedAmount.
|
||||||
amountKey :: Amount -> MixedAmountKey
|
amountKey :: Amount -> MixedAmountKey
|
||||||
amountKey amt@Amount{acommodity=c} = case aprice amt of
|
amountKey amt@Amount{acommodity=c} = case aprice amt of
|
||||||
Nothing -> MixedAmountKeyNoPrice c
|
Nothing -> MixedAmountKeyNoCost c
|
||||||
Just (TotalPrice p) -> MixedAmountKeyTotalPrice c (acommodity p)
|
Just (TotalCost p) -> MixedAmountKeyTotalCost c (acommodity p)
|
||||||
Just (UnitPrice p) -> MixedAmountKeyUnitPrice c (acommodity p) (aquantity p)
|
Just (UnitCost p) -> MixedAmountKeyUnitCost c (acommodity p) (aquantity p)
|
||||||
|
|
||||||
-- | The empty mixed amount.
|
-- | The empty mixed amount.
|
||||||
nullmixedamt :: MixedAmount
|
nullmixedamt :: MixedAmount
|
||||||
@ -798,7 +797,7 @@ maAddAmount (Mixed ma) a = Mixed $ M.insertWith sumSimilarAmountsUsingFirstPrice
|
|||||||
maAddAmounts :: Foldable t => MixedAmount -> t Amount -> MixedAmount
|
maAddAmounts :: Foldable t => MixedAmount -> t Amount -> MixedAmount
|
||||||
maAddAmounts = foldl' maAddAmount
|
maAddAmounts = foldl' maAddAmount
|
||||||
|
|
||||||
-- | Negate mixed amount's quantities (and total prices, if any).
|
-- | Negate mixed amount's quantities (and total costs, if any).
|
||||||
maNegate :: MixedAmount -> MixedAmount
|
maNegate :: MixedAmount -> MixedAmount
|
||||||
maNegate = transformMixedAmount negate
|
maNegate = transformMixedAmount negate
|
||||||
|
|
||||||
@ -817,15 +816,15 @@ maMinus a = maPlus a . maNegate
|
|||||||
maSum :: Foldable t => t MixedAmount -> MixedAmount
|
maSum :: Foldable t => t MixedAmount -> MixedAmount
|
||||||
maSum = foldl' maPlus nullmixedamt
|
maSum = foldl' maPlus nullmixedamt
|
||||||
|
|
||||||
-- | Divide a mixed amount's quantities (and total prices, if any) by a constant.
|
-- | Divide a mixed amount's quantities (and total costs, if any) by a constant.
|
||||||
divideMixedAmount :: Quantity -> MixedAmount -> MixedAmount
|
divideMixedAmount :: Quantity -> MixedAmount -> MixedAmount
|
||||||
divideMixedAmount n = transformMixedAmount (/n)
|
divideMixedAmount n = transformMixedAmount (/n)
|
||||||
|
|
||||||
-- | Multiply a mixed amount's quantities (and total prices, if any) by a constant.
|
-- | Multiply a mixed amount's quantities (and total costs, if any) by a constant.
|
||||||
multiplyMixedAmount :: Quantity -> MixedAmount -> MixedAmount
|
multiplyMixedAmount :: Quantity -> MixedAmount -> MixedAmount
|
||||||
multiplyMixedAmount n = transformMixedAmount (*n)
|
multiplyMixedAmount n = transformMixedAmount (*n)
|
||||||
|
|
||||||
-- | Apply a function to a mixed amount's quantities (and its total prices, if it has any).
|
-- | Apply a function to a mixed amount's quantities (and its total costs, if it has any).
|
||||||
transformMixedAmount :: (Quantity -> Quantity) -> MixedAmount -> MixedAmount
|
transformMixedAmount :: (Quantity -> Quantity) -> MixedAmount -> MixedAmount
|
||||||
transformMixedAmount f = mapMixedAmountUnsafe (transformAmount f)
|
transformMixedAmount f = mapMixedAmountUnsafe (transformAmount f)
|
||||||
|
|
||||||
@ -869,7 +868,7 @@ maIsNonZero = not . mixedAmountIsZero
|
|||||||
-- | Get a mixed amount's component amounts, with some cleanups.
|
-- | Get a mixed amount's component amounts, with some cleanups.
|
||||||
-- The following descriptions are old and possibly wrong:
|
-- The following descriptions are old and possibly wrong:
|
||||||
--
|
--
|
||||||
-- * amounts in the same commodity are combined unless they have different prices or total prices
|
-- * amounts in the same commodity are combined unless they have different costs or total costs
|
||||||
--
|
--
|
||||||
-- * multiple zero amounts, all with the same non-null commodity, are replaced by just the last of them, preserving the commodity and amount style (all but the last zero amount are discarded)
|
-- * multiple zero amounts, all with the same non-null commodity, are replaced by just the last of them, preserving the commodity and amount style (all but the last zero amount are discarded)
|
||||||
--
|
--
|
||||||
@ -915,8 +914,8 @@ amountsPreservingZeros (Mixed ma)
|
|||||||
-- | Get a mixed amount's component amounts without normalising zero and missing
|
-- | Get a mixed amount's component amounts without normalising zero and missing
|
||||||
-- amounts. This is used for JSON serialisation, so the order is important. In
|
-- amounts. This is used for JSON serialisation, so the order is important. In
|
||||||
-- particular, we want the Amounts given in the order of the MixedAmountKeys,
|
-- particular, we want the Amounts given in the order of the MixedAmountKeys,
|
||||||
-- i.e. lexicographically first by commodity, then by price commodity, then by
|
-- i.e. lexicographically first by commodity, then by cost commodity, then by
|
||||||
-- unit price from most negative to most positive.
|
-- unit cost from most negative to most positive.
|
||||||
amountsRaw :: MixedAmount -> [Amount]
|
amountsRaw :: MixedAmount -> [Amount]
|
||||||
amountsRaw (Mixed ma) = toList ma
|
amountsRaw (Mixed ma) = toList ma
|
||||||
|
|
||||||
@ -940,18 +939,18 @@ unifyMixedAmount = foldM combine 0 . amounts
|
|||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
|
|
||||||
-- | Sum same-commodity amounts in a lossy way, applying the first
|
-- | Sum same-commodity amounts in a lossy way, applying the first
|
||||||
-- price to the result and discarding any other prices. Only used as a
|
-- cost to the result and discarding any other costs. Only used as a
|
||||||
-- rendering helper.
|
-- rendering helper.
|
||||||
sumSimilarAmountsUsingFirstPrice :: Amount -> Amount -> Amount
|
sumSimilarAmountsUsingFirstPrice :: Amount -> Amount -> Amount
|
||||||
sumSimilarAmountsUsingFirstPrice a b = (a + b){aprice=p}
|
sumSimilarAmountsUsingFirstPrice a b = (a + b){aprice=p}
|
||||||
where
|
where
|
||||||
p = case (aprice a, aprice b) of
|
p = case (aprice a, aprice b) of
|
||||||
(Just (TotalPrice ap), Just (TotalPrice bp))
|
(Just (TotalCost ap), Just (TotalCost bp))
|
||||||
-> Just . TotalPrice $ ap{aquantity = aquantity ap + aquantity bp }
|
-> Just . TotalCost $ ap{aquantity = aquantity ap + aquantity bp }
|
||||||
_ -> aprice a
|
_ -> aprice a
|
||||||
|
|
||||||
-- -- | Sum same-commodity amounts. If there were different prices, set
|
-- -- | Sum same-commodity amounts. If there were different costs, set
|
||||||
-- -- the price to a special marker indicating "various". Only used as a
|
-- -- the cost to a special marker indicating "various". Only used as a
|
||||||
-- -- rendering helper.
|
-- -- rendering helper.
|
||||||
-- sumSimilarAmountsNotingPriceDifference :: [Amount] -> Amount
|
-- sumSimilarAmountsNotingPriceDifference :: [Amount] -> Amount
|
||||||
-- sumSimilarAmountsNotingPriceDifference [] = nullamt
|
-- sumSimilarAmountsNotingPriceDifference [] = nullamt
|
||||||
@ -976,8 +975,8 @@ mapMixedAmount :: (Amount -> Amount) -> MixedAmount -> MixedAmount
|
|||||||
mapMixedAmount f (Mixed ma) = mixed . map f $ toList ma
|
mapMixedAmount f (Mixed ma) = mixed . map f $ toList ma
|
||||||
|
|
||||||
-- | Apply a transform to a mixed amount's component 'Amount's, which does not
|
-- | Apply a transform to a mixed amount's component 'Amount's, which does not
|
||||||
-- affect the key of the amount (i.e. doesn't change the commodity, price
|
-- affect the key of the amount (i.e. doesn't change the commodity, cost
|
||||||
-- commodity, or unit price amount). This condition is not checked.
|
-- commodity, or unit cost amount). This condition is not checked.
|
||||||
mapMixedAmountUnsafe :: (Amount -> Amount) -> MixedAmount -> MixedAmount
|
mapMixedAmountUnsafe :: (Amount -> Amount) -> MixedAmount -> MixedAmount
|
||||||
mapMixedAmountUnsafe f (Mixed ma) = Mixed $ M.map f ma -- Use M.map instead of fmap to maintain strictness
|
mapMixedAmountUnsafe f (Mixed ma) = Mixed $ M.map f ma -- Use M.map instead of fmap to maintain strictness
|
||||||
|
|
||||||
@ -985,8 +984,8 @@ mapMixedAmountUnsafe f (Mixed ma) = Mixed $ M.map f ma -- Use M.map instead of
|
|||||||
-- possible (see amountCost).
|
-- possible (see amountCost).
|
||||||
mixedAmountCost :: MixedAmount -> MixedAmount
|
mixedAmountCost :: MixedAmount -> MixedAmount
|
||||||
mixedAmountCost (Mixed ma) =
|
mixedAmountCost (Mixed ma) =
|
||||||
foldl' (\m a -> maAddAmount m (amountCost a)) (Mixed noPrices) withPrices
|
foldl' (\m a -> maAddAmount m (amountCost a)) (Mixed noCosts) withCosts
|
||||||
where (noPrices, withPrices) = M.partition (isNothing . aprice) ma
|
where (noCosts, withCosts) = M.partition (isNothing . aprice) ma
|
||||||
|
|
||||||
-- -- | MixedAmount derived Eq instance in Types.hs doesn't know that we
|
-- -- | 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.
|
-- -- want $0 = EUR0 = 0. Yet we don't want to drag all this code over there.
|
||||||
@ -1031,7 +1030,7 @@ mixedAmountUnstyled = mapMixedAmountUnsafe amountUnstyled
|
|||||||
|
|
||||||
-- | Get the string representation of a mixed amount, after
|
-- | Get the string representation of a mixed amount, after
|
||||||
-- normalising it to one amount per commodity. Assumes amounts have
|
-- normalising it to one amount per commodity. Assumes amounts have
|
||||||
-- no or similar prices, otherwise this can show misleading prices.
|
-- no or similar costs, otherwise this can show misleading costs.
|
||||||
--
|
--
|
||||||
-- > showMixedAmount = wbUnpack . showMixedAmountB noColour
|
-- > showMixedAmount = wbUnpack . showMixedAmountB noColour
|
||||||
showMixedAmount :: MixedAmount -> String
|
showMixedAmount :: MixedAmount -> String
|
||||||
@ -1050,7 +1049,7 @@ showMixedAmountOneLine = wbUnpack . showMixedAmountB oneLine{displayCost=True}
|
|||||||
showMixedAmountWithZeroCommodity :: MixedAmount -> String
|
showMixedAmountWithZeroCommodity :: MixedAmount -> String
|
||||||
showMixedAmountWithZeroCommodity = wbUnpack . showMixedAmountB noColour{displayZeroCommodity=True}
|
showMixedAmountWithZeroCommodity = wbUnpack . showMixedAmountB noColour{displayZeroCommodity=True}
|
||||||
|
|
||||||
-- | Get the string representation of a mixed amount, without showing any transaction prices.
|
-- | Get the string representation of a mixed amount, without showing any costs.
|
||||||
-- With a True argument, adds ANSI codes to show negative amounts in red.
|
-- With a True argument, adds ANSI codes to show negative amounts in red.
|
||||||
--
|
--
|
||||||
-- > showMixedAmountWithoutPrice c = wbUnpack . showMixedAmountB noCost{displayColour=c}
|
-- > showMixedAmountWithoutPrice c = wbUnpack . showMixedAmountB noCost{displayColour=c}
|
||||||
@ -1058,7 +1057,7 @@ showMixedAmountWithoutPrice :: Bool -> MixedAmount -> String
|
|||||||
showMixedAmountWithoutPrice c = wbUnpack . showMixedAmountB noCost{displayColour=c}
|
showMixedAmountWithoutPrice c = wbUnpack . showMixedAmountB noCost{displayColour=c}
|
||||||
|
|
||||||
-- | Get the one-line string representation of a mixed amount, but without
|
-- | Get the one-line string representation of a mixed amount, but without
|
||||||
-- any \@ prices.
|
-- any \@ costs.
|
||||||
-- With a True argument, adds ANSI codes to show negative amounts in red.
|
-- With a True argument, adds ANSI codes to show negative amounts in red.
|
||||||
--
|
--
|
||||||
-- > showMixedAmountOneLineWithoutPrice c = wbUnpack . showMixedAmountB oneLine{displayColour=c}
|
-- > showMixedAmountOneLineWithoutPrice c = wbUnpack . showMixedAmountB oneLine{displayColour=c}
|
||||||
@ -1225,7 +1224,7 @@ mixedAmountSetPrecisionMin p = mapMixedAmountUnsafe (amountSetPrecisionMin p)
|
|||||||
mixedAmountSetPrecisionMax :: Word8 -> MixedAmount -> MixedAmount
|
mixedAmountSetPrecisionMax :: Word8 -> MixedAmount -> MixedAmount
|
||||||
mixedAmountSetPrecisionMax p = mapMixedAmountUnsafe (amountSetPrecisionMax p)
|
mixedAmountSetPrecisionMax p = mapMixedAmountUnsafe (amountSetPrecisionMax p)
|
||||||
|
|
||||||
-- | Remove all prices from a MixedAmount.
|
-- | Remove all costs from a MixedAmount.
|
||||||
mixedAmountStripPrices :: MixedAmount -> MixedAmount
|
mixedAmountStripPrices :: MixedAmount -> MixedAmount
|
||||||
mixedAmountStripPrices (Mixed ma) =
|
mixedAmountStripPrices (Mixed ma) =
|
||||||
foldl' (\m a -> maAddAmount m a{aprice=Nothing}) (Mixed noPrices) withPrices
|
foldl' (\m a -> maAddAmount m a{aprice=Nothing}) (Mixed noPrices) withPrices
|
||||||
@ -1240,9 +1239,9 @@ tests_Amount = testGroup "Amount" [
|
|||||||
|
|
||||||
testCase "amountCost" $ do
|
testCase "amountCost" $ do
|
||||||
amountCost (eur 1) @?= eur 1
|
amountCost (eur 1) @?= eur 1
|
||||||
amountCost (eur 2){aprice=Just $ UnitPrice $ usd 2} @?= usd 4
|
amountCost (eur 2){aprice=Just $ UnitCost $ usd 2} @?= usd 4
|
||||||
amountCost (eur 1){aprice=Just $ TotalPrice $ usd 2} @?= usd 2
|
amountCost (eur 1){aprice=Just $ TotalCost $ usd 2} @?= usd 2
|
||||||
amountCost (eur (-1)){aprice=Just $ TotalPrice $ usd (-2)} @?= usd (-2)
|
amountCost (eur (-1)){aprice=Just $ TotalCost $ usd (-2)} @?= usd (-2)
|
||||||
|
|
||||||
,testCase "amountLooksZero" $ do
|
,testCase "amountLooksZero" $ do
|
||||||
assertBool "" $ amountLooksZero nullamt
|
assertBool "" $ amountLooksZero nullamt
|
||||||
@ -1250,9 +1249,9 @@ tests_Amount = testGroup "Amount" [
|
|||||||
|
|
||||||
,testCase "negating amounts" $ do
|
,testCase "negating amounts" $ do
|
||||||
negate (usd 1) @?= (usd 1){aquantity= -1}
|
negate (usd 1) @?= (usd 1){aquantity= -1}
|
||||||
let b = (usd 1){aprice=Just $ UnitPrice $ eur 2} in negate b @?= b{aquantity= -1}
|
let b = (usd 1){aprice=Just $ UnitCost $ eur 2} in negate b @?= b{aquantity= -1}
|
||||||
|
|
||||||
,testCase "adding amounts without prices" $ do
|
,testCase "adding amounts without costs" $ do
|
||||||
(usd 1.23 + usd (-1.23)) @?= usd 0
|
(usd 1.23 + usd (-1.23)) @?= usd 0
|
||||||
(usd 1.23 + usd (-1.23)) @?= usd 0
|
(usd 1.23 + usd (-1.23)) @?= usd 0
|
||||||
(usd (-1.23) + usd (-1.23)) @?= usd (-2.46)
|
(usd (-1.23) + usd (-1.23)) @?= usd (-2.46)
|
||||||
@ -1285,7 +1284,7 @@ tests_Amount = testGroup "Amount" [
|
|||||||
])
|
])
|
||||||
@?= mixedAmount (usd 0 `withPrecision` Precision 3)
|
@?= mixedAmount (usd 0 `withPrecision` Precision 3)
|
||||||
|
|
||||||
,testCase "adding mixed amounts with total prices" $ do
|
,testCase "adding mixed amounts with total costs" $ do
|
||||||
maSum (map mixedAmount
|
maSum (map mixedAmount
|
||||||
[usd 1 @@ eur 1
|
[usd 1 @@ eur 1
|
||||||
,usd (-2) @@ eur 1
|
,usd (-2) @@ eur 1
|
||||||
@ -1307,13 +1306,13 @@ tests_Amount = testGroup "Amount" [
|
|||||||
,testGroup "amounts" [
|
,testGroup "amounts" [
|
||||||
testCase "a missing amount overrides any other amounts" $
|
testCase "a missing amount overrides any other amounts" $
|
||||||
amounts (mixed [usd 1, missingamt]) @?= [missingamt]
|
amounts (mixed [usd 1, missingamt]) @?= [missingamt]
|
||||||
,testCase "unpriced same-commodity amounts are combined" $
|
,testCase "costless same-commodity amounts are combined" $
|
||||||
amounts (mixed [usd 0, usd 2]) @?= [usd 2]
|
amounts (mixed [usd 0, usd 2]) @?= [usd 2]
|
||||||
,testCase "amounts with same unit price are combined" $
|
,testCase "amounts with same unit cost are combined" $
|
||||||
amounts (mixed [usd 1 `at` eur 1, usd 1 `at` eur 1]) @?= [usd 2 `at` eur 1]
|
amounts (mixed [usd 1 `at` eur 1, usd 1 `at` eur 1]) @?= [usd 2 `at` eur 1]
|
||||||
,testCase "amounts with different unit prices are not combined" $
|
,testCase "amounts with different unit costs are not combined" $
|
||||||
amounts (mixed [usd 1 `at` eur 1, usd 1 `at` eur 2]) @?= [usd 1 `at` eur 1, usd 1 `at` eur 2]
|
amounts (mixed [usd 1 `at` eur 1, usd 1 `at` eur 2]) @?= [usd 1 `at` eur 1, usd 1 `at` eur 2]
|
||||||
,testCase "amounts with total prices are combined" $
|
,testCase "amounts with total costs are combined" $
|
||||||
amounts (mixed [usd 1 @@ eur 1, usd 1 @@ eur 1]) @?= [usd 2 @@ eur 2]
|
amounts (mixed [usd 1 @@ eur 1, usd 1 @@ eur 1]) @?= [usd 2 @@ eur 2]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -357,13 +357,13 @@ costInferrerFor t pt = maybe id infercost inferFromAndTo
|
|||||||
, poriginal = Just $ originalPosting p }
|
, poriginal = Just $ originalPosting p }
|
||||||
| otherwise = p
|
| otherwise = p
|
||||||
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 TotalCost.
|
||||||
-- Otherwise divide the conversion equally among the Amounts by using a unit price.
|
-- Otherwise divide the conversion equally among the Amounts by using a unit price.
|
||||||
conversionprice = case filter (== acommodity fromamount) pcommodities of
|
conversionprice = case filter (== acommodity fromamount) pcommodities of
|
||||||
[_] -> TotalPrice $ negate toamount
|
[_] -> TotalCost $ negate toamount
|
||||||
_ -> UnitPrice $ negate unitprice `withPrecision` unitprecision
|
_ -> UnitCost $ negate unitcost `withPrecision` unitprecision
|
||||||
|
|
||||||
unitprice = aquantity fromamount `divideAmount` toamount
|
unitcost = 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
|
||||||
|
|||||||
@ -906,7 +906,7 @@ canonicalStyle a b = a{asprecision=prec, asdecimalmark=decmark, asdigitgroups=mg
|
|||||||
-- fixmixedamount = mapMixedAmount fixamount
|
-- fixmixedamount = mapMixedAmount fixamount
|
||||||
-- fixamount = fixprice
|
-- fixamount = fixprice
|
||||||
-- fixprice a@Amount{price=Just _} = a
|
-- fixprice a@Amount{price=Just _} = a
|
||||||
-- fixprice a@Amount{commodity=c} = a{price=maybe Nothing (Just . UnitPrice) $ journalPriceDirectiveFor j d c}
|
-- fixprice a@Amount{commodity=c} = a{price=maybe Nothing (Just . UnitCost) $ journalPriceDirectiveFor j d c}
|
||||||
|
|
||||||
-- -- | Get the price for a commodity on the specified day from the price database, if known.
|
-- -- | Get the price for a commodity on the specified day from the price database, if known.
|
||||||
-- -- Does only one lookup step, ie will not look up the price of a price.
|
-- -- Does only one lookup step, ie will not look up the price of a price.
|
||||||
@ -972,8 +972,8 @@ journalMarkRedundantCosts j = do
|
|||||||
-- amountCommodities :: Amount -> [CommoditySymbol]
|
-- amountCommodities :: Amount -> [CommoditySymbol]
|
||||||
-- amountCommodities Amount{acommodity=c,aprice=p} =
|
-- amountCommodities Amount{acommodity=c,aprice=p} =
|
||||||
-- case p of Nothing -> [c]
|
-- case p of Nothing -> [c]
|
||||||
-- Just (UnitPrice ma) -> c:(concatMap amountCommodities $ amounts ma)
|
-- Just (UnitCost ma) -> c:(concatMap amountCommodities $ amounts ma)
|
||||||
-- Just (TotalPrice ma) -> c:(concatMap amountCommodities $ amounts ma)
|
-- Just (TotalCost ma) -> c:(concatMap amountCommodities $ amounts ma)
|
||||||
|
|
||||||
-- | Get an ordered list of amounts in this journal which can
|
-- | Get an ordered list of amounts in this journal which can
|
||||||
-- influence canonical amount display styles. Those amounts are, in
|
-- influence canonical amount display styles. Those amounts are, in
|
||||||
|
|||||||
@ -102,7 +102,7 @@ instance ToJSON MixedAmount where
|
|||||||
toEncoding = toEncoding . amountsRaw
|
toEncoding = toEncoding . amountsRaw
|
||||||
|
|
||||||
instance ToJSON BalanceAssertion
|
instance ToJSON BalanceAssertion
|
||||||
instance ToJSON AmountPrice
|
instance ToJSON AmountCost
|
||||||
instance ToJSON MarketPrice
|
instance ToJSON MarketPrice
|
||||||
instance ToJSON PostingType
|
instance ToJSON PostingType
|
||||||
|
|
||||||
@ -208,7 +208,7 @@ instance FromJSON MixedAmount where
|
|||||||
parseJSON = fmap (mixed :: [Amount] -> MixedAmount) . parseJSON
|
parseJSON = fmap (mixed :: [Amount] -> MixedAmount) . parseJSON
|
||||||
|
|
||||||
instance FromJSON BalanceAssertion
|
instance FromJSON BalanceAssertion
|
||||||
instance FromJSON AmountPrice
|
instance FromJSON AmountCost
|
||||||
instance FromJSON MarketPrice
|
instance FromJSON MarketPrice
|
||||||
instance FromJSON PostingType
|
instance FromJSON PostingType
|
||||||
instance FromJSON Posting
|
instance FromJSON Posting
|
||||||
|
|||||||
@ -398,8 +398,8 @@ amountToBeancount a@Amount{acommodity=c,astyle=s,aprice=mp} = a{acommodity=c', a
|
|||||||
s' = s{ascommodityside=R, ascommodityspaced=True}
|
s' = s{ascommodityside=R, ascommodityspaced=True}
|
||||||
mp' = costToBeancount <$> mp
|
mp' = costToBeancount <$> mp
|
||||||
where
|
where
|
||||||
costToBeancount (TotalPrice amt) = TotalPrice $ amountToBeancount amt
|
costToBeancount (TotalCost amt) = TotalCost $ amountToBeancount amt
|
||||||
costToBeancount (UnitPrice amt) = UnitPrice $ amountToBeancount amt
|
costToBeancount (UnitCost amt) = UnitCost $ amountToBeancount amt
|
||||||
|
|
||||||
-- | Like showAccountName for Beancount journal format.
|
-- | Like showAccountName for Beancount journal format.
|
||||||
-- Calls accountNameToBeancount first.
|
-- Calls accountNameToBeancount first.
|
||||||
|
|||||||
@ -400,7 +400,7 @@ transactionInferCostsFromEquity dryrun acctTypes t = first (annotateErrorWithTra
|
|||||||
addCostIfMatchesOneAmount :: Amount -> Amount -> Posting -> Maybe (Posting, Amount)
|
addCostIfMatchesOneAmount :: Amount -> Amount -> Posting -> Maybe (Posting, Amount)
|
||||||
addCostIfMatchesOneAmount a1 a2 p = do
|
addCostIfMatchesOneAmount a1 a2 p = do
|
||||||
a <- postingSingleAmount p
|
a <- postingSingleAmount p
|
||||||
let newp cost = p{pamount = mixedAmount a{aprice = Just $ TotalPrice cost}}
|
let newp cost = p{pamount = mixedAmount a{aprice = Just $ TotalCost cost}}
|
||||||
if
|
if
|
||||||
| amountsMatch (-a1) a -> Just (newp a2, a2)
|
| amountsMatch (-a1) a -> Just (newp a2, a2)
|
||||||
| amountsMatch (-a2) a -> Just (newp a1, a1)
|
| amountsMatch (-a2) a -> Just (newp a1, a1)
|
||||||
|
|||||||
@ -131,7 +131,7 @@ tmPostingRuleToFunction verbosetags styles query querytxt tmpr =
|
|||||||
matchedamount = dbg6 "matchedamount" . filterMixedAmount (symq `matchesAmount`) $ pamount p
|
matchedamount = dbg6 "matchedamount" . filterMixedAmount (symq `matchesAmount`) $ pamount p
|
||||||
-- Handle a matched amount with a total price carefully so as to keep the transaction balanced (#928).
|
-- Handle a matched amount with a total price carefully so as to keep the transaction balanced (#928).
|
||||||
-- Approach 1: convert to a unit price and increase the display precision slightly
|
-- Approach 1: convert to a unit price and increase the display precision slightly
|
||||||
-- Mixed as = dbg6 "multipliedamount" $ n `multiplyMixedAmount` mixedAmountTotalPriceToUnitPrice matchedamount
|
-- Mixed as = dbg6 "multipliedamount" $ n `multiplyMixedAmount` mixedAmountTotalCostToUnitCost matchedamount
|
||||||
-- Approach 2: multiply the total price (keeping it positive) as well as the quantity
|
-- Approach 2: multiply the total price (keeping it positive) as well as the quantity
|
||||||
as = dbg6 "multipliedamount" $ multiplyMixedAmount n matchedamount
|
as = dbg6 "multipliedamount" $ multiplyMixedAmount n matchedamount
|
||||||
in
|
in
|
||||||
|
|||||||
@ -171,7 +171,7 @@ data AccountType =
|
|||||||
| Revenue
|
| Revenue
|
||||||
| Expense
|
| Expense
|
||||||
| Cash -- ^ a subtype of Asset - liquid assets to show in cashflow report
|
| Cash -- ^ a subtype of Asset - liquid assets to show in cashflow report
|
||||||
| Conversion -- ^ a subtype of Equity - account in which to generate conversion postings for transaction prices
|
| Conversion -- ^ a subtype of Equity - account with which to balance commodity conversions
|
||||||
deriving (Eq,Ord,Generic)
|
deriving (Eq,Ord,Generic)
|
||||||
|
|
||||||
instance Show AccountType where
|
instance Show AccountType where
|
||||||
@ -246,7 +246,7 @@ deriving instance Generic (DecimalRaw a)
|
|||||||
-- | An amount's per-unit or total cost/selling price in another
|
-- | An amount's per-unit or total cost/selling price in another
|
||||||
-- commodity, as recorded in the journal entry eg with @ or @@.
|
-- commodity, as recorded in the journal entry eg with @ or @@.
|
||||||
-- "Cost", formerly AKA "transaction price". The amount is always positive.
|
-- "Cost", formerly AKA "transaction price". The amount is always positive.
|
||||||
data AmountPrice = UnitPrice !Amount | TotalPrice !Amount
|
data AmountCost = UnitCost !Amount | TotalCost !Amount
|
||||||
deriving (Eq,Ord,Generic,Show)
|
deriving (Eq,Ord,Generic,Show)
|
||||||
|
|
||||||
-- | Every Amount has one of these, influencing how the amount is displayed.
|
-- | Every Amount has one of these, influencing how the amount is displayed.
|
||||||
@ -312,7 +312,7 @@ data Amount = Amount {
|
|||||||
acommodity :: !CommoditySymbol, -- commodity symbol, or special value "AUTO"
|
acommodity :: !CommoditySymbol, -- commodity symbol, or special value "AUTO"
|
||||||
aquantity :: !Quantity, -- numeric quantity, or zero in case of "AUTO"
|
aquantity :: !Quantity, -- numeric quantity, or zero in case of "AUTO"
|
||||||
astyle :: !AmountStyle,
|
astyle :: !AmountStyle,
|
||||||
aprice :: !(Maybe AmountPrice) -- ^ the (fixed, transaction-specific) price for this amount, if any
|
aprice :: !(Maybe AmountCost) -- ^ the (fixed, transaction-specific) cost in another commodity of this amount, if any
|
||||||
} deriving (Eq,Ord,Generic,Show)
|
} deriving (Eq,Ord,Generic,Show)
|
||||||
|
|
||||||
-- | Types with this class have one or more amounts,
|
-- | Types with this class have one or more amounts,
|
||||||
@ -350,41 +350,41 @@ maCompare (Mixed a) (Mixed b) = go (M.toList a) (M.toList b)
|
|||||||
go ((_,x):xs) [] = compareQuantities (Just x) Nothing <> go xs []
|
go ((_,x):xs) [] = compareQuantities (Just x) Nothing <> go xs []
|
||||||
go [] ((_,y):ys) = compareQuantities Nothing (Just y) <> go [] ys
|
go [] ((_,y):ys) = compareQuantities Nothing (Just y) <> go [] ys
|
||||||
go [] [] = EQ
|
go [] [] = EQ
|
||||||
compareQuantities = comparing (maybe 0 aquantity) <> comparing (maybe 0 totalprice)
|
compareQuantities = comparing (maybe 0 aquantity) <> comparing (maybe 0 totalcost)
|
||||||
totalprice x = case aprice x of
|
totalcost x = case aprice x of
|
||||||
Just (TotalPrice p) -> aquantity p
|
Just (TotalCost p) -> aquantity p
|
||||||
_ -> 0
|
_ -> 0
|
||||||
|
|
||||||
-- | Stores the CommoditySymbol of the Amount, along with the CommoditySymbol of
|
-- | Stores the CommoditySymbol of the Amount, along with the CommoditySymbol of
|
||||||
-- the price, and its unit price if being used.
|
-- the cost, and its unit cost if being used.
|
||||||
data MixedAmountKey
|
data MixedAmountKey
|
||||||
= MixedAmountKeyNoPrice !CommoditySymbol
|
= MixedAmountKeyNoCost !CommoditySymbol
|
||||||
| MixedAmountKeyTotalPrice !CommoditySymbol !CommoditySymbol
|
| MixedAmountKeyTotalCost !CommoditySymbol !CommoditySymbol
|
||||||
| MixedAmountKeyUnitPrice !CommoditySymbol !CommoditySymbol !Quantity
|
| MixedAmountKeyUnitCost !CommoditySymbol !CommoditySymbol !Quantity
|
||||||
deriving (Eq,Generic,Show)
|
deriving (Eq,Generic,Show)
|
||||||
|
|
||||||
-- | We don't auto-derive the Ord instance because it would give an undesired ordering.
|
-- | We don't auto-derive the Ord instance because it would give an undesired ordering.
|
||||||
-- We want the keys to be sorted lexicographically:
|
-- We want the keys to be sorted lexicographically:
|
||||||
-- (1) By the primary commodity of the amount.
|
-- (1) By the primary commodity of the amount.
|
||||||
-- (2) By the commodity of the price, with no price being first.
|
-- (2) By the commodity of the cost, with no cost being first.
|
||||||
-- (3) By the unit price, from most negative to most positive, with total prices
|
-- (3) By the unit cost, from most negative to most positive, with total costs
|
||||||
-- before unit prices.
|
-- before unit costs.
|
||||||
-- For example, we would like the ordering to give
|
-- For example, we would like the ordering to give
|
||||||
-- MixedAmountKeyNoPrice "X" < MixedAmountKeyTotalPrice "X" "Z" < MixedAmountKeyNoPrice "Y"
|
-- MixedAmountKeyNoCost "X" < MixedAmountKeyTotalCost "X" "Z" < MixedAmountKeyNoCost "Y"
|
||||||
instance Ord MixedAmountKey where
|
instance Ord MixedAmountKey where
|
||||||
compare = comparing commodity <> comparing pCommodity <> comparing pPrice
|
compare = comparing commodity <> comparing pCommodity <> comparing pCost
|
||||||
where
|
where
|
||||||
commodity (MixedAmountKeyNoPrice c) = c
|
commodity (MixedAmountKeyNoCost c) = c
|
||||||
commodity (MixedAmountKeyTotalPrice c _) = c
|
commodity (MixedAmountKeyTotalCost c _) = c
|
||||||
commodity (MixedAmountKeyUnitPrice c _ _) = c
|
commodity (MixedAmountKeyUnitCost c _ _) = c
|
||||||
|
|
||||||
pCommodity (MixedAmountKeyNoPrice _) = Nothing
|
pCommodity (MixedAmountKeyNoCost _) = Nothing
|
||||||
pCommodity (MixedAmountKeyTotalPrice _ pc) = Just pc
|
pCommodity (MixedAmountKeyTotalCost _ pc) = Just pc
|
||||||
pCommodity (MixedAmountKeyUnitPrice _ pc _) = Just pc
|
pCommodity (MixedAmountKeyUnitCost _ pc _) = Just pc
|
||||||
|
|
||||||
pPrice (MixedAmountKeyNoPrice _) = Nothing
|
pCost (MixedAmountKeyNoCost _) = Nothing
|
||||||
pPrice (MixedAmountKeyTotalPrice _ _) = Nothing
|
pCost (MixedAmountKeyTotalCost _ _) = Nothing
|
||||||
pPrice (MixedAmountKeyUnitPrice _ _ q) = Just q
|
pCost (MixedAmountKeyUnitCost _ _ q) = Just q
|
||||||
|
|
||||||
data PostingType = RegularPosting | VirtualPosting | BalancedVirtualPosting
|
data PostingType = RegularPosting | VirtualPosting | BalancedVirtualPosting
|
||||||
deriving (Eq,Show,Generic)
|
deriving (Eq,Show,Generic)
|
||||||
@ -439,7 +439,7 @@ data Posting = Posting {
|
|||||||
ptransaction :: Maybe Transaction, -- ^ this posting's parent transaction (co-recursive types).
|
ptransaction :: Maybe Transaction, -- ^ this posting's parent transaction (co-recursive types).
|
||||||
-- Tying this knot gets tedious, Maybe makes it easier/optional.
|
-- Tying this knot gets tedious, Maybe makes it easier/optional.
|
||||||
poriginal :: Maybe Posting -- ^ When this posting has been transformed in some way
|
poriginal :: Maybe Posting -- ^ When this posting has been transformed in some way
|
||||||
-- (eg its amount or price was inferred, or the account name was
|
-- (eg its amount or cost was inferred, or the account name was
|
||||||
-- changed by a pivot or budget report), this references the original
|
-- changed by a pivot or budget report), this references the original
|
||||||
-- untransformed posting (which will have Nothing in this field).
|
-- untransformed posting (which will have Nothing in this field).
|
||||||
} deriving (Generic)
|
} deriving (Generic)
|
||||||
|
|||||||
@ -113,8 +113,8 @@ priceDirectiveToMarketPrice PriceDirective{..} =
|
|||||||
-- decimal digits; or if they seem to be infinite, defaultPrecisionLimit.
|
-- decimal digits; or if they seem to be infinite, defaultPrecisionLimit.
|
||||||
amountPriceDirectiveFromCost :: Day -> Amount -> Maybe PriceDirective
|
amountPriceDirectiveFromCost :: Day -> Amount -> Maybe PriceDirective
|
||||||
amountPriceDirectiveFromCost d amt@Amount{acommodity=fromcomm, aquantity=n} = case aprice amt of
|
amountPriceDirectiveFromCost d amt@Amount{acommodity=fromcomm, aquantity=n} = case aprice amt of
|
||||||
Just (UnitPrice u) -> Just $ pd{pdamount=u}
|
Just (UnitCost u) -> Just $ pd{pdamount=u}
|
||||||
Just (TotalPrice t) | n /= 0 -> Just $ pd{pdamount=u}
|
Just (TotalCost t) | n /= 0 -> Just $ pd{pdamount=u}
|
||||||
where u = amountSetFullPrecisionOr Nothing $ divideAmount n t
|
where u = amountSetFullPrecisionOr Nothing $ divideAmount n t
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
where
|
where
|
||||||
|
|||||||
@ -909,14 +909,14 @@ simplecommoditysymbolp = takeWhile1P Nothing (not . isNonsimpleCommodityChar)
|
|||||||
|
|
||||||
-- | Ledger-style cost notation:
|
-- | Ledger-style cost notation:
|
||||||
-- @ UNITAMT, @@ TOTALAMT, (@) UNITAMT, or (@@) TOTALAMT. The () are ignored.
|
-- @ UNITAMT, @@ TOTALAMT, (@) UNITAMT, or (@@) TOTALAMT. The () are ignored.
|
||||||
costp :: Amount -> JournalParser m AmountPrice
|
costp :: Amount -> JournalParser m AmountCost
|
||||||
costp baseAmt =
|
costp baseAmt =
|
||||||
-- dbg "costp" $
|
-- dbg "costp" $
|
||||||
label "transaction price" $ do
|
label "transaction price" $ do
|
||||||
-- https://www.ledger-cli.org/3.0/doc/ledger3.html#Virtual-posting-costs
|
-- https://www.ledger-cli.org/3.0/doc/ledger3.html#Virtual-posting-costs
|
||||||
parenthesised <- option False $ char '(' >> pure True
|
parenthesised <- option False $ char '(' >> pure True
|
||||||
char '@'
|
char '@'
|
||||||
totalPrice <- char '@' $> True <|> pure False
|
totalCost <- char '@' $> True <|> pure False
|
||||||
when parenthesised $ void $ char ')'
|
when parenthesised $ void $ char ')'
|
||||||
|
|
||||||
lift skipNonNewlineSpaces
|
lift skipNonNewlineSpaces
|
||||||
@ -925,9 +925,9 @@ costp baseAmt =
|
|||||||
let amtsign' = signum $ aquantity baseAmt
|
let amtsign' = signum $ aquantity baseAmt
|
||||||
amtsign = if amtsign' == 0 then 1 else amtsign'
|
amtsign = if amtsign' == 0 then 1 else amtsign'
|
||||||
|
|
||||||
pure $ if totalPrice
|
pure $ if totalCost
|
||||||
then TotalPrice priceAmount{aquantity=amtsign * aquantity priceAmount}
|
then TotalCost priceAmount{aquantity=amtsign * aquantity priceAmount}
|
||||||
else UnitPrice priceAmount
|
else UnitCost priceAmount
|
||||||
|
|
||||||
-- | A valuation function or value can be written in double parentheses after an amount.
|
-- | A valuation function or value can be written in double parentheses after an amount.
|
||||||
valuationexprp :: JournalParser m ()
|
valuationexprp :: JournalParser m ()
|
||||||
@ -1586,7 +1586,7 @@ tests_Common = testGroup "Common" [
|
|||||||
acommodity="$"
|
acommodity="$"
|
||||||
,aquantity=10 -- need to test internal precision with roundTo ? I think not
|
,aquantity=10 -- need to test internal precision with roundTo ? I think not
|
||||||
,astyle=amountstyle{asprecision=Precision 0, asdecimalmark=Nothing}
|
,astyle=amountstyle{asprecision=Precision 0, asdecimalmark=Nothing}
|
||||||
,aprice=Just $ UnitPrice $
|
,aprice=Just $ UnitCost $
|
||||||
nullamt{
|
nullamt{
|
||||||
acommodity="€"
|
acommodity="€"
|
||||||
,aquantity=0.5
|
,aquantity=0.5
|
||||||
@ -1598,7 +1598,7 @@ tests_Common = testGroup "Common" [
|
|||||||
acommodity="$"
|
acommodity="$"
|
||||||
,aquantity=10
|
,aquantity=10
|
||||||
,astyle=amountstyle{asprecision=Precision 0, asdecimalmark=Nothing}
|
,astyle=amountstyle{asprecision=Precision 0, asdecimalmark=Nothing}
|
||||||
,aprice=Just $ TotalPrice $
|
,aprice=Just $ TotalCost $
|
||||||
nullamt{
|
nullamt{
|
||||||
acommodity="€"
|
acommodity="€"
|
||||||
,aquantity=5
|
,aquantity=5
|
||||||
|
|||||||
@ -180,8 +180,8 @@ roi CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{_rsReportOpts=ReportO
|
|||||||
|
|
||||||
timeWeightedReturn styles showCashFlow prettyTables investmentsQuery trans mixedAmountValue (OneSpan begin end valueBeforeAmt valueAfter cashFlow pnl) = do
|
timeWeightedReturn styles showCashFlow prettyTables investmentsQuery trans mixedAmountValue (OneSpan begin end valueBeforeAmt valueAfter cashFlow pnl) = do
|
||||||
let valueBefore = unMix valueBeforeAmt
|
let valueBefore = unMix valueBeforeAmt
|
||||||
let initialUnitPrice = 100 :: Decimal
|
let initialUnitCost = 100 :: Decimal
|
||||||
let initialUnits = valueBefore / initialUnitPrice
|
let initialUnits = valueBefore / initialUnitCost
|
||||||
let changes =
|
let changes =
|
||||||
-- If cash flow and PnL changes happen on the same day, this
|
-- If cash flow and PnL changes happen on the same day, this
|
||||||
-- will sort PnL changes to come before cash flows (on any
|
-- will sort PnL changes to come before cash flows (on any
|
||||||
@ -227,29 +227,29 @@ timeWeightedReturn styles showCashFlow prettyTables investmentsQuery trans mixed
|
|||||||
let units =
|
let units =
|
||||||
tail $
|
tail $
|
||||||
scanl
|
scanl
|
||||||
(\(_, _, unitPrice, unitBalance) (date, amt) ->
|
(\(_, _, unitCost, unitBalance) (date, amt) ->
|
||||||
let valueOnDate = unMix $ mixedAmountValue end date $ total trans (And [investmentsQuery, Date (DateSpan Nothing (Just $ Exact date))])
|
let valueOnDate = unMix $ mixedAmountValue end date $ total trans (And [investmentsQuery, Date (DateSpan Nothing (Just $ Exact date))])
|
||||||
in
|
in
|
||||||
case amt of
|
case amt of
|
||||||
Right amt' ->
|
Right amt' ->
|
||||||
-- we are buying or selling
|
-- we are buying or selling
|
||||||
let unitsBoughtOrSold = unMix amt' / unitPrice
|
let unitsBoughtOrSold = unMix amt' / unitCost
|
||||||
in (valueOnDate, unitsBoughtOrSold, unitPrice, unitBalance + unitsBoughtOrSold)
|
in (valueOnDate, unitsBoughtOrSold, unitCost, unitBalance + unitsBoughtOrSold)
|
||||||
Left pnl' ->
|
Left pnl' ->
|
||||||
-- PnL change
|
-- PnL change
|
||||||
let valueAfterDate = valueOnDate + unMix pnl'
|
let valueAfterDate = valueOnDate + unMix pnl'
|
||||||
unitPrice' = valueAfterDate/unitBalance
|
unitCost' = valueAfterDate/unitBalance
|
||||||
in (valueOnDate, 0, unitPrice', unitBalance))
|
in (valueOnDate, 0, unitCost', unitBalance))
|
||||||
(0, 0, initialUnitPrice, initialUnits)
|
(0, 0, initialUnitCost, initialUnits)
|
||||||
$ dbg3 "changes" changes
|
$ dbg3 "changes" changes
|
||||||
|
|
||||||
let finalUnitBalance = if null units then initialUnits else let (_,_,_,u) = last units in u
|
let finalUnitBalance = if null units then initialUnits else let (_,_,_,u) = last units in u
|
||||||
finalUnitPrice = if finalUnitBalance == 0 then
|
finalUnitCost = if finalUnitBalance == 0 then
|
||||||
if null units then initialUnitPrice
|
if null units then initialUnitCost
|
||||||
else let (_,_,lastUnitPrice,_) = last units in lastUnitPrice
|
else let (_,_,lastUnitCost,_) = last units in lastUnitCost
|
||||||
else (unMix valueAfter) / finalUnitBalance
|
else (unMix valueAfter) / finalUnitBalance
|
||||||
-- Technically, totalTWR should be (100*(finalUnitPrice - initialUnitPrice) / initialUnitPrice), but initalUnitPrice is 100, so 100/100 == 1
|
-- Technically, totalTWR should be (100*(finalUnitCost - initialUnitCost) / initialUnitCost), but initalUnitCost is 100, so 100/100 == 1
|
||||||
totalTWR = roundTo 2 $ (finalUnitPrice - initialUnitPrice)
|
totalTWR = roundTo 2 $ (finalUnitCost - initialUnitCost)
|
||||||
(startYear, _, _) = toGregorian begin
|
(startYear, _, _) = toGregorian begin
|
||||||
years = fromIntegral (diffDays end begin) / (if isLeapYear startYear then 366 else 365) :: Double
|
years = fromIntegral (diffDays end begin) / (if isLeapYear startYear then 366 else 365) :: Double
|
||||||
annualizedTWR = 100*((1+(realToFrac totalTWR/100))**(1/years)-1) :: Double
|
annualizedTWR = 100*((1+(realToFrac totalTWR/100))**(1/years)-1) :: Double
|
||||||
@ -264,7 +264,7 @@ timeWeightedReturn styles showCashFlow prettyTables investmentsQuery trans mixed
|
|||||||
dates = add begin dates'
|
dates = add begin dates'
|
||||||
cashflows = add valueBeforeAmt cashflows'
|
cashflows = add valueBeforeAmt cashflows'
|
||||||
unitsBoughtOrSold = add initialUnits unitsBoughtOrSold'
|
unitsBoughtOrSold = add initialUnits unitsBoughtOrSold'
|
||||||
unitPrices = add initialUnitPrice unitPrices'
|
unitPrices = add initialUnitCost unitPrices'
|
||||||
unitBalances = add initialUnits unitBalances'
|
unitBalances = add initialUnits unitBalances'
|
||||||
|
|
||||||
TL.putStr $ Tab.render prettyTables id id T.pack
|
TL.putStr $ Tab.render prettyTables id id T.pack
|
||||||
@ -283,7 +283,7 @@ timeWeightedReturn styles showCashFlow prettyTables investmentsQuery trans mixed
|
|||||||
| udelta <- map showDecimal unitsBoughtOrSold ])
|
| udelta <- map showDecimal unitsBoughtOrSold ])
|
||||||
|
|
||||||
printf "Final unit price: %s/%s units = %s\nTotal TWR: %s%%.\nPeriod: %.2f years.\nAnnualized TWR: %.2f%%\n\n"
|
printf "Final unit price: %s/%s units = %s\nTotal TWR: %s%%.\nPeriod: %.2f years.\nAnnualized TWR: %.2f%%\n\n"
|
||||||
(showMixedAmount $ styleAmounts styles valueAfter) (showDecimal finalUnitBalance) (showDecimal finalUnitPrice) (showDecimal totalTWR) years annualizedTWR
|
(showMixedAmount $ styleAmounts styles valueAfter) (showDecimal finalUnitBalance) (showDecimal finalUnitCost) (showDecimal totalTWR) years annualizedTWR
|
||||||
|
|
||||||
return ((realToFrac totalTWR) :: Double, annualizedTWR)
|
return ((realToFrac totalTWR) :: Double, annualizedTWR)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user