    {-|
    A simple 'Amount' is some quantity of money, shares, or anything else.
    It has a (possibly null) 'CommoditySymbol' and a numeric quantity:

    @
      $1
      £-50
      EUR 3.44
      GOOG 500
      1.5h
      90 apples
      0
    @

    It may also have an assigned 'Price', representing this amount's per-unit
    or total cost in a different commodity. If present, this is rendered like
    so:

    @
      EUR 2 \@ $1.50  (unit price)
      EUR 2 \@\@ $3   (total price)
    @

    A 'MixedAmount' is zero or more simple amounts, so can represent multiple
    commodities; this is the type most often used:

    @
      0
      $50 + EUR 3
      16h + $13.55 + AAPL 500 + 6 oranges
    @

    When a mixed amount has been \"normalised\", it has no more than one amount
    in each commodity and no zero amounts; or it has just a single zero amount
    and no others.

    Limited arithmetic with simple and mixed amounts is supported, best used
    with similar amounts since it mostly ignores assigned prices and commodity
    exchange rates.

    -}

    {-# LANGUAGE StandaloneDeriving, RecordWildCards, OverloadedStrings #-}

    module Hledger.Data.Amount (
      -- * Amount
      amount,
      nullamt,
      missingamt,
      num,
      usd,
      eur,
      gbp,
      per,
      hrs,
      at,
      (@@),
      amountWithCommodity,
      -- ** arithmetic
      amountCost,
      amountIsZero,
      amountLooksZero,
      divideAmount,
      multiplyAmount,
      divideAmountAndPrice,
      multiplyAmountAndPrice,
      amountTotalPriceToUnitPrice,
      -- ** rendering
      amountstyle,
      styleAmount,
      styleAmountExceptPrecision,
      showAmount,
      cshowAmount,
      showAmountWithZeroCommodity,
      showAmountDebug,
      showAmountWithoutPrice,
      setAmountPrecision,
      withPrecision,
      setFullPrecision,
      setAmountInternalPrecision,
      withInternalPrecision,
      setAmountDecimalPoint,
      withDecimalPoint,
      canonicaliseAmount,
      -- * MixedAmount
      nullmixedamt,
      missingmixedamt,
      mixed,
      amounts,
      filterMixedAmount,
      filterMixedAmountByCommodity,
      mapMixedAmount,
      normaliseMixedAmountSquashPricesForDisplay,
      normaliseMixedAmount,
      unifyMixedAmount,
      mixedAmountStripPrices,
      -- ** arithmetic
      mixedAmountCost,
      divideMixedAmount,
      multiplyMixedAmount,
      divideMixedAmountAndPrice,
      multiplyMixedAmountAndPrice,
      averageMixedAmounts,
      isNegativeAmount,
      isNegativeMixedAmount,
      mixedAmountIsZero,
      mixedAmountLooksZero,
      mixedAmountTotalPriceToUnitPrice,
      -- ** rendering
      styleMixedAmount,
      showMixedAmount,
      showMixedAmountOneLine,
      showMixedAmountDebug,
      showMixedAmountWithoutPrice,
      showMixedAmountOneLineWithoutPrice,
      showMixedAmountElided,
      showMixedAmountWithZeroCommodity,
      showMixedAmountWithPrecision,
      setMixedAmountPrecision,
      canonicaliseMixedAmount,
      -- * misc.
      ltraceamount,
      tests_Amount
    ) where

    import Control.Monad (foldM)
    import Data.Char (isDigit)
    import Data.Decimal (decimalPlaces, normalizeDecimal, roundTo)
    import Data.Function (on)
    import Data.List
    import qualified Data.Map as M
    import Data.Map (findWithDefault)
    import Data.Maybe
    import qualified Data.Text as T
    import Data.Word (Word8)
    import Safe (lastDef, maximumMay)
    import Text.Printf

    import Hledger.Data.Types
    import Hledger.Data.Commodity
    import Hledger.Utils

    deriving instance Show MarketPrice

    -------------------------------------------------------------------------------
    -- Amount styles

    -- | Default amount style
    amountstyle :: AmountStyle
    amountstyle = Side
    -> Bool
    -> AmountPrecision
    -> Maybe Char
    -> Maybe DigitGroupStyle
    -> AmountStyle
    AmountStyle Side
    L Bool
    False (Word8 -> AmountPrecision
    Precision 0) (Char -> Maybe Char
    forall a. a -> Maybe a
    Just '.') Maybe DigitGroupStyle
    forall a. Maybe a
    Nothing

    -------------------------------------------------------------------------------
    -- Amount

    instance Num Amount where
        abs :: Amount -> Amount
    abs a :: Amount
    a@Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    q}    = Amount
    a{aquantity :: Quantity
    aquantity=Quantity -> Quantity
    forall a. Num a => a -> a
    abs Quantity
    q}
        signum :: Amount -> Amount
    signum a :: Amount
    a@Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    q} = Amount
    a{aquantity :: Quantity
    aquantity=Quantity -> Quantity
    forall a. Num a => a -> a
    signum Quantity
    q}
        fromInteger :: Integer -> Amount
    fromInteger i :: Integer
    i                = Amount
    nullamt{aquantity :: Quantity
    aquantity=Integer -> Quantity
    forall a. Num a => Integer -> a
    fromInteger Integer
    i}
        negate :: Amount -> Amount
    negate a :: Amount
    a@Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    q} = Amount
    a{aquantity :: Quantity
    aquantity= -Quantity
    q}
        + :: Amount -> Amount -> Amount
    (+)                          = (Quantity -> Quantity -> Quantity) -> Amount -> Amount -> Amount
    similarAmountsOp Quantity -> Quantity -> Quantity
    forall a. Num a => a -> a -> a
    (+)
        (-)                          = (Quantity -> Quantity -> Quantity) -> Amount -> Amount -> Amount
    similarAmountsOp (-)
        * :: Amount -> Amount -> Amount
    (*)                          = (Quantity -> Quantity -> Quantity) -> Amount -> Amount -> Amount
    similarAmountsOp Quantity -> Quantity -> Quantity
    forall a. Num a => a -> a -> a
    (*)

    -- | The empty simple amount.
    amount, nullamt :: Amount
    amount :: Amount
    amount = Amount :: CommoditySymbol
    -> Quantity -> Bool -> AmountStyle -> Maybe AmountPrice -> Amount
    Amount{acommodity :: CommoditySymbol
    acommodity="", aquantity :: Quantity
    aquantity=0, aprice :: Maybe AmountPrice
    aprice=Maybe AmountPrice
    forall a. Maybe a
    Nothing, astyle :: AmountStyle
    astyle=AmountStyle
    amountstyle, aismultiplier :: Bool
    aismultiplier=Bool
    False}
    nullamt :: Amount
    nullamt = Amount
    amount

    -- | A temporary value for parsed transactions which had no amount specified.
    missingamt :: Amount
    missingamt :: Amount
    missingamt = Amount
    amount{acommodity :: CommoditySymbol
    acommodity="AUTO"}

    -- Handy amount constructors for tests.
    -- usd/eur/gbp round their argument to a whole number of pennies/cents.
    num :: Quantity -> Amount
    num n :: Quantity
    n = Amount
    amount{acommodity :: CommoditySymbol
    acommodity="",  aquantity :: Quantity
    aquantity=Quantity
    n}
    hrs :: Quantity -> Amount
    hrs n :: Quantity
    n = Amount
    amount{acommodity :: CommoditySymbol
    acommodity="h", aquantity :: Quantity
    aquantity=Quantity
    n,           astyle :: AmountStyle
    astyle=AmountStyle
    amountstyle{asprecision :: AmountPrecision
    asprecision=Word8 -> AmountPrecision
    Precision 2, ascommodityside :: Side
    ascommodityside=Side
    R}}
    usd :: Quantity -> Amount
    usd n :: Quantity
    n = Amount
    amount{acommodity :: CommoditySymbol
    acommodity="$", aquantity :: Quantity
    aquantity=Word8 -> Quantity -> Quantity
    forall i. Integral i => Word8 -> DecimalRaw i -> DecimalRaw i
    roundTo 2 Quantity
    n, astyle :: AmountStyle
    astyle=AmountStyle
    amountstyle{asprecision :: AmountPrecision
    asprecision=Word8 -> AmountPrecision
    Precision 2}}
    eur :: Quantity -> Amount
    eur n :: Quantity
    n = Amount
    amount{acommodity :: CommoditySymbol
    acommodity="€", aquantity :: Quantity
    aquantity=Word8 -> Quantity -> Quantity
    forall i. Integral i => Word8 -> DecimalRaw i -> DecimalRaw i
    roundTo 2 Quantity
    n, astyle :: AmountStyle
    astyle=AmountStyle
    amountstyle{asprecision :: AmountPrecision
    asprecision=Word8 -> AmountPrecision
    Precision 2}}
    gbp :: Quantity -> Amount
    gbp n :: Quantity
    n = Amount
    amount{acommodity :: CommoditySymbol
    acommodity="£", aquantity :: Quantity
    aquantity=Word8 -> Quantity -> Quantity
    forall i. Integral i => Word8 -> DecimalRaw i -> DecimalRaw i
    roundTo 2 Quantity
    n, astyle :: AmountStyle
    astyle=AmountStyle
    amountstyle{asprecision :: AmountPrecision
    asprecision=Word8 -> AmountPrecision
    Precision 2}}
    per :: Quantity -> Amount
    per n :: Quantity
    n = Amount
    amount{acommodity :: CommoditySymbol
    acommodity="%", aquantity :: Quantity
    aquantity=Quantity
    n,           astyle :: AmountStyle
    astyle=AmountStyle
    amountstyle{asprecision :: AmountPrecision
    asprecision=Word8 -> AmountPrecision
    Precision 1, ascommodityside :: Side
    ascommodityside=Side
    R, ascommodityspaced :: Bool
    ascommodityspaced=Bool
    True}}
    amt :: Amount
    amt at :: Amount -> Amount -> Amount
    `at` priceamt :: Amount
    priceamt = Amount
    amt{aprice :: Maybe AmountPrice
    aprice=AmountPrice -> Maybe AmountPrice
    forall a. a -> Maybe a
    Just (AmountPrice -> Maybe AmountPrice)
    -> AmountPrice -> Maybe AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Amount -> AmountPrice
    UnitPrice Amount
    priceamt}
    amt :: Amount
    amt @@ :: Amount -> Amount -> Amount
    @@ priceamt :: Amount
    priceamt = Amount
    amt{aprice :: Maybe AmountPrice
    aprice=AmountPrice -> Maybe AmountPrice
    forall a. a -> Maybe a
    Just (AmountPrice -> Maybe AmountPrice)
    -> AmountPrice -> Maybe AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Amount -> AmountPrice
    TotalPrice Amount
    priceamt}

    -- | Apply a binary arithmetic operator to two amounts, which should
    -- be in the same commodity if non-zero (warning, this is not checked).
    -- A zero result keeps the commodity of the second amount.
    -- The result's display style is that of the second amount, with
    -- precision set to the highest of either amount.
    -- Prices are ignored and discarded.
    -- 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 :: Quantity -> Quantity -> Quantity
    op Amount{acommodity :: Amount -> CommoditySymbol
    acommodity=CommoditySymbol
    _,  aquantity :: Amount -> Quantity
    aquantity=Quantity
    q1, astyle :: Amount -> AmountStyle
    astyle=AmountStyle{asprecision :: AmountStyle -> AmountPrecision
    asprecision=AmountPrecision
    p1}}
                        Amount{acommodity :: Amount -> CommoditySymbol
    acommodity=CommoditySymbol
    c2, aquantity :: Amount -> Quantity
    aquantity=Quantity
    q2, astyle :: Amount -> AmountStyle
    astyle=s2 :: AmountStyle
    s2@AmountStyle{asprecision :: AmountStyle -> AmountPrecision
    asprecision=AmountPrecision
    p2}} =
       -- trace ("a1:"++showAmountDebug a1) $ trace ("a2:"++showAmountDebug a2) $ traceWith (("= :"++).showAmountDebug)
       Amount
    amount{acommodity :: CommoditySymbol
    acommodity=CommoditySymbol
    c2, aquantity :: Quantity
    aquantity=Quantity
    q1 Quantity -> Quantity -> Quantity
    `op` Quantity
    q2, astyle :: AmountStyle
    astyle=AmountStyle
    s2{asprecision :: AmountPrecision
    asprecision=AmountPrecision -> AmountPrecision -> AmountPrecision
    forall a. Ord a => a -> a -> a
    max AmountPrecision
    p1 AmountPrecision
    p2}}
      --  c1==c2 || q1==0 || q2==0 =
      --  otherwise = error "tried to do simple arithmetic with amounts in different commodities"

    -- | Convert an amount to the specified commodity, ignoring and discarding
    -- any assigned prices and assuming an exchange rate of 1.
    amountWithCommodity :: CommoditySymbol -> Amount -> Amount
    amountWithCommodity :: CommoditySymbol -> Amount -> Amount
    amountWithCommodity c :: CommoditySymbol
    c a :: Amount
    a = Amount
    a{acommodity :: CommoditySymbol
    acommodity=CommoditySymbol
    c, aprice :: Maybe AmountPrice
    aprice=Maybe AmountPrice
    forall a. Maybe a
    Nothing}

    -- | Convert a amount to its "cost" or "selling price" in another commodity,
    -- using its attached transaction price if it has one.  Notes:
    --
    -- - price amounts must be MixedAmounts with exactly one component Amount
    --   (or there will be a runtime error XXX)
    --
    -- - price amounts should be positive
    --   (though this is currently not enforced)
    amountCost :: Amount -> Amount
    amountCost :: Amount -> Amount
    amountCost a :: Amount
    a@Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    q, aprice :: Amount -> Maybe AmountPrice
    aprice=Maybe AmountPrice
    mp} =
        case Maybe AmountPrice
    mp of
          Nothing                                  -> Amount
    a
          Just (UnitPrice  p :: Amount
    p@Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    pq}) -> Amount
    p{aquantity :: Quantity
    aquantity=Quantity
    pq Quantity -> Quantity -> Quantity
    forall a. Num a => a -> a -> a
    * Quantity
    q}
          Just (TotalPrice p :: Amount
    p@Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    pq}) -> Amount
    p{aquantity :: Quantity
    aquantity=Quantity
    pq Quantity -> Quantity -> Quantity
    forall a. Num a => a -> a -> a
    * Quantity -> Quantity
    forall a. Num a => a -> a
    signum Quantity
    q}

    -- | Replace an amount's TotalPrice, if it has one, with an equivalent UnitPrice.
    -- Has no effect on amounts without one.
    -- Also increases the unit price's display precision to show one extra decimal place,
    -- to help keep transaction amounts balancing.
    -- Does Decimal division, might be some rounding/irrational number issues.
    amountTotalPriceToUnitPrice :: Amount -> Amount
    amountTotalPriceToUnitPrice :: Amount -> Amount
    amountTotalPriceToUnitPrice
        a :: Amount
    a@Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    q, aprice :: Amount -> Maybe AmountPrice
    aprice=Just (TotalPrice pa :: Amount
    pa@Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    pq, astyle :: Amount -> AmountStyle
    astyle=AmountStyle
    ps})}
        = Amount
    a{aprice :: Maybe AmountPrice
    aprice = AmountPrice -> Maybe AmountPrice
    forall a. a -> Maybe a
    Just (AmountPrice -> Maybe AmountPrice)
    -> AmountPrice -> Maybe AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Amount -> AmountPrice
    UnitPrice Amount
    pa{aquantity :: Quantity
    aquantity=Quantity -> Quantity
    forall a. Num a => a -> a
    abs (Quantity
    pqQuantity -> Quantity -> Quantity
    forall a. Fractional a => a -> a -> a
    /Quantity
    q), astyle :: AmountStyle
    astyle=AmountStyle
    ps{asprecision :: AmountPrecision
    asprecision=AmountPrecision
    pp}}}
      where
        -- Increase the precision by 1, capping at the max bound.
        pp :: AmountPrecision
    pp = case AmountStyle -> AmountPrecision
    asprecision AmountStyle
    ps of
                    NaturalPrecision -> AmountPrecision
    NaturalPrecision
                    Precision p :: Word8
    p      -> Word8 -> AmountPrecision
    Precision (Word8 -> AmountPrecision) -> Word8 -> AmountPrecision
    forall a b. (a -> b) -> a -> b
    $ if Word8
    p Word8 -> Word8 -> Bool
    forall a. Eq a => a -> a -> Bool
    == Word8
    forall a. Bounded a => a
    maxBound then Word8
    forall a. Bounded a => a
    maxBound else Word8
    p Word8 -> Word8 -> Word8
    forall a. Num a => a -> a -> a
    + 1
    amountTotalPriceToUnitPrice a :: Amount
    a = Amount
    a

    -- | Divide an amount's quantity by a constant.
    divideAmount :: Quantity -> Amount -> Amount
    divideAmount :: Quantity -> Amount -> Amount
    divideAmount n :: Quantity
    n a :: Amount
    a@Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    q} = Amount
    a{aquantity :: Quantity
    aquantity=Quantity
    qQuantity -> Quantity -> Quantity
    forall a. Fractional a => a -> a -> a
    /Quantity
    n}

    -- | Multiply an amount's quantity by a constant.
    multiplyAmount :: Quantity -> Amount -> Amount
    multiplyAmount :: Quantity -> Amount -> Amount
    multiplyAmount n :: Quantity
    n a :: Amount
    a@Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    q} = Amount
    a{aquantity :: Quantity
    aquantity=Quantity
    qQuantity -> Quantity -> Quantity
    forall a. Num a => a -> a -> a
    *Quantity
    n}

    -- | Divide an amount's quantity (and its total price, if it has one) by a constant.
    -- The total price will be kept positive regardless of the multiplier's sign.
    divideAmountAndPrice :: Quantity -> Amount -> Amount
    divideAmountAndPrice :: Quantity -> Amount -> Amount
    divideAmountAndPrice n :: Quantity
    n a :: Amount
    a@Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    q,aprice :: Amount -> Maybe AmountPrice
    aprice=Maybe AmountPrice
    p} = Amount
    a{aquantity :: Quantity
    aquantity=Quantity
    qQuantity -> Quantity -> Quantity
    forall a. Fractional a => a -> a -> a
    /Quantity
    n, aprice :: Maybe AmountPrice
    aprice=AmountPrice -> AmountPrice
    f (AmountPrice -> AmountPrice)
    -> Maybe AmountPrice -> Maybe AmountPrice
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Maybe AmountPrice
    p}
      where
        f :: AmountPrice -> AmountPrice
    f (TotalPrice a :: Amount
    a) = Amount -> AmountPrice
    TotalPrice (Amount -> AmountPrice) -> Amount -> AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Amount -> Amount
    forall a. Num a => a -> a
    abs (Amount -> Amount) -> Amount -> Amount
    forall a b. (a -> b) -> a -> b
    $ Quantity
    n Quantity -> Amount -> Amount
    `divideAmount` Amount
    a
        f p :: AmountPrice
    p = AmountPrice
    p

    -- | Multiply an amount's quantity (and its total price, if it has one) by a constant.
    -- The total price will be kept positive regardless of the multiplier's sign.
    multiplyAmountAndPrice :: Quantity -> Amount -> Amount
    multiplyAmountAndPrice :: Quantity -> Amount -> Amount
    multiplyAmountAndPrice n :: Quantity
    n a :: Amount
    a@Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    q,aprice :: Amount -> Maybe AmountPrice
    aprice=Maybe AmountPrice
    p} = Amount
    a{aquantity :: Quantity
    aquantity=Quantity
    qQuantity -> Quantity -> Quantity
    forall a. Num a => a -> a -> a
    *Quantity
    n, aprice :: Maybe AmountPrice
    aprice=AmountPrice -> AmountPrice
    f (AmountPrice -> AmountPrice)
    -> Maybe AmountPrice -> Maybe AmountPrice
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Maybe AmountPrice
    p}
      where
        f :: AmountPrice -> AmountPrice
    f (TotalPrice a :: Amount
    a) = Amount -> AmountPrice
    TotalPrice (Amount -> AmountPrice) -> Amount -> AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Amount -> Amount
    forall a. Num a => a -> a
    abs (Amount -> Amount) -> Amount -> Amount
    forall a b. (a -> b) -> a -> b
    $ Quantity
    n Quantity -> Amount -> Amount
    `multiplyAmount` Amount
    a
        f p :: AmountPrice
    p = AmountPrice
    p

    -- | Is this amount negative ? The price is ignored.
    isNegativeAmount :: Amount -> Bool
    isNegativeAmount :: Amount -> Bool
    isNegativeAmount Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    q} = Quantity
    q Quantity -> Quantity -> Bool
    forall a. Ord a => a -> a -> Bool
    < 0

    -- | Round an Amount's Quantity to its specified display precision. If that is
    -- NaturalPrecision, this does nothing.
    amountRoundedQuantity :: Amount -> Quantity
    amountRoundedQuantity :: Amount -> Quantity
    amountRoundedQuantity Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    q, astyle :: Amount -> AmountStyle
    astyle=AmountStyle{asprecision :: AmountStyle -> AmountPrecision
    asprecision=AmountPrecision
    p}} = case AmountPrecision
    p of
        NaturalPrecision -> Quantity
    q
        Precision p' :: Word8
    p'     -> Word8 -> Quantity -> Quantity
    forall i. Integral i => Word8 -> DecimalRaw i -> DecimalRaw i
    roundTo Word8
    p' Quantity
    q

    -- | Does mixed amount appear to be zero when rendered with its
    -- display precision ?
    amountLooksZero :: Amount -> Bool
    amountLooksZero :: Amount -> Bool
    amountLooksZero = (0Quantity -> Quantity -> Bool
    forall a. Eq a => a -> a -> Bool
    ==) (Quantity -> Bool) -> (Amount -> Quantity) -> Amount -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Amount -> Quantity
    amountRoundedQuantity

    -- | Is this amount exactly zero, ignoring its display precision ?
    amountIsZero :: Amount -> Bool
    amountIsZero :: Amount -> Bool
    amountIsZero Amount{aquantity :: Amount -> Quantity
    aquantity=Quantity
    q} = Quantity
    q Quantity -> Quantity -> Bool
    forall a. Eq a => a -> a -> Bool
    == 0

    -- | Get the string representation of an amount, based on its commodity's
    -- display settings except using the specified precision.
    showAmountWithPrecision :: AmountPrecision -> Amount -> String
    showAmountWithPrecision :: AmountPrecision -> Amount -> String
    showAmountWithPrecision p :: AmountPrecision
    p = Amount -> String
    showAmount (Amount -> String) -> (Amount -> Amount) -> Amount -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . AmountPrecision -> Amount -> Amount
    setAmountPrecision AmountPrecision
    p

    -- | Set an amount's display precision, flipped.
    withPrecision :: Amount -> AmountPrecision -> Amount
    withPrecision :: Amount -> AmountPrecision -> Amount
    withPrecision = (AmountPrecision -> Amount -> Amount)
    -> Amount -> AmountPrecision -> Amount
    forall a b c. (a -> b -> c) -> b -> a -> c
    flip AmountPrecision -> Amount -> Amount
    setAmountPrecision

    -- | Set an amount's display precision.
    setAmountPrecision :: AmountPrecision -> Amount -> Amount
    setAmountPrecision :: AmountPrecision -> Amount -> Amount
    setAmountPrecision p :: AmountPrecision
    p a :: Amount
    a@Amount{astyle :: Amount -> AmountStyle
    astyle=AmountStyle
    s} = Amount
    a{astyle :: AmountStyle
    astyle=AmountStyle
    s{asprecision :: AmountPrecision
    asprecision=AmountPrecision
    p}}

    -- | Increase an amount's display precision, if needed, to enough decimal places
    -- to show it exactly (showing all significant decimal digits, excluding trailing
    -- zeros).
    setFullPrecision :: Amount -> Amount
    setFullPrecision :: Amount -> Amount
    setFullPrecision a :: Amount
    a = AmountPrecision -> Amount -> Amount
    setAmountPrecision AmountPrecision
    p Amount
    a
      where
        p :: AmountPrecision
    p                = AmountPrecision -> AmountPrecision -> AmountPrecision
    forall a. Ord a => a -> a -> a
    max AmountPrecision
    displayprecision AmountPrecision
    naturalprecision
        displayprecision :: AmountPrecision
    displayprecision = AmountStyle -> AmountPrecision
    asprecision (AmountStyle -> AmountPrecision) -> AmountStyle -> AmountPrecision
    forall a b. (a -> b) -> a -> b
    $ Amount -> AmountStyle
    astyle Amount
    a
        naturalprecision :: AmountPrecision
    naturalprecision = Word8 -> AmountPrecision
    Precision (Word8 -> AmountPrecision)
    -> (Quantity -> Word8) -> Quantity -> AmountPrecision
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Quantity -> Word8
    forall i. DecimalRaw i -> Word8
    decimalPlaces (Quantity -> Word8) -> (Quantity -> Quantity) -> Quantity -> Word8
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Quantity -> Quantity
    forall i. Integral i => DecimalRaw i -> DecimalRaw i
    normalizeDecimal (Quantity -> AmountPrecision) -> Quantity -> AmountPrecision
    forall a b. (a -> b) -> a -> b
    $ Amount -> Quantity
    aquantity Amount
    a

    -- | Get a string representation of an amount for debugging,
    -- appropriate to the current debug level. 9 shows maximum detail.
    showAmountDebug :: Amount -> String
    showAmountDebug :: Amount -> String
    showAmountDebug Amount{acommodity :: Amount -> CommoditySymbol
    acommodity=CommoditySymbol
    "AUTO"} = "(missing)"
    showAmountDebug Amount{..} = String -> String -> String -> String -> ShowS
    forall r. PrintfType r => String -> r
    printf "Amount {acommodity=%s, aquantity=%s, aprice=%s, astyle=%s}" (CommoditySymbol -> String
    forall a. Show a => a -> String
    show CommoditySymbol
    acommodity) (Quantity -> String
    forall a. Show a => a -> String
    show Quantity
    aquantity) (Maybe AmountPrice -> String
    showAmountPriceDebug Maybe AmountPrice
    aprice) (AmountStyle -> String
    forall a. Show a => a -> String
    show AmountStyle
    astyle)

    -- | Get the string representation of an amount, without any \@ price.
    -- With a True argument, adds ANSI codes to show negative amounts in red.
    showAmountWithoutPrice :: Bool -> Amount -> String
    showAmountWithoutPrice :: Bool -> Amount -> String
    showAmountWithoutPrice c :: Bool
    c a :: Amount
    a = Amount -> String
    showamt Amount
    a{aprice :: Maybe AmountPrice
    aprice=Maybe AmountPrice
    forall a. Maybe a
    Nothing}
      where
        showamt :: Amount -> String
    showamt = if Bool
    c then Amount -> String
    cshowAmount else Amount -> String
    showAmount

    -- | Set an amount's internal precision, ie rounds the Decimal representing
    -- the amount's quantity to some number of decimal places.
    -- Rounding is done with Data.Decimal's default roundTo function:
    -- "If the value ends in 5 then it is rounded to the nearest even value (Banker's Rounding)".
    -- Does not change the amount's display precision.
    -- Intended only for internal use, eg when comparing amounts in tests.
    setAmountInternalPrecision :: Word8 -> Amount -> Amount
    setAmountInternalPrecision :: Word8 -> Amount -> Amount
    setAmountInternalPrecision p :: Word8
    p a :: Amount
    a@Amount{ aquantity :: Amount -> Quantity
    aquantity=Quantity
    q, astyle :: Amount -> AmountStyle
    astyle=AmountStyle
    s } = Amount
    a{
       astyle :: AmountStyle
    astyle=AmountStyle
    s{asprecision :: AmountPrecision
    asprecision=Word8 -> AmountPrecision
    Precision Word8
    p}
      ,aquantity :: Quantity
    aquantity=Word8 -> Quantity -> Quantity
    forall i. Integral i => Word8 -> DecimalRaw i -> DecimalRaw i
    roundTo Word8
    p Quantity
    q
      }

    -- | Set an amount's internal precision, flipped.
    -- Intended only for internal use, eg when comparing amounts in tests.
    withInternalPrecision :: Amount -> Word8 -> Amount
    withInternalPrecision :: Amount -> Word8 -> Amount
    withInternalPrecision = (Word8 -> Amount -> Amount) -> Amount -> Word8 -> Amount
    forall a b c. (a -> b -> c) -> b -> a -> c
    flip Word8 -> Amount -> Amount
    setAmountInternalPrecision

    -- | Set (or clear) an amount's display decimal point.
    setAmountDecimalPoint :: Maybe Char -> Amount -> Amount
    setAmountDecimalPoint :: Maybe Char -> Amount -> Amount
    setAmountDecimalPoint mc :: Maybe Char
    mc a :: Amount
    a@Amount{ astyle :: Amount -> AmountStyle
    astyle=AmountStyle
    s } = Amount
    a{ astyle :: AmountStyle
    astyle=AmountStyle
    s{asdecimalpoint :: Maybe Char
    asdecimalpoint=Maybe Char
    mc} }

    -- | Set (or clear) an amount's display decimal point, flipped.
    withDecimalPoint :: Amount -> Maybe Char -> Amount
    withDecimalPoint :: Amount -> Maybe Char -> Amount
    withDecimalPoint = (Maybe Char -> Amount -> Amount) -> Amount -> Maybe Char -> Amount
    forall a b c. (a -> b -> c) -> b -> a -> c
    flip Maybe Char -> Amount -> Amount
    setAmountDecimalPoint

    showAmountPrice :: Maybe AmountPrice -> String
    showAmountPrice :: Maybe AmountPrice -> String
    showAmountPrice Nothing                = ""
    showAmountPrice (Just (UnitPrice pa :: Amount
    pa))  = " @ "  String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ Amount -> String
    showAmount Amount
    pa
    showAmountPrice (Just (TotalPrice pa :: Amount
    pa)) = " @@ " String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ Amount -> String
    showAmount Amount
    pa

    showAmountPriceDebug :: Maybe AmountPrice -> String
    showAmountPriceDebug :: Maybe AmountPrice -> String
    showAmountPriceDebug Nothing                = ""
    showAmountPriceDebug (Just (UnitPrice pa :: Amount
    pa))  = " @ "  String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ Amount -> String
    showAmountDebug Amount
    pa
    showAmountPriceDebug (Just (TotalPrice pa :: Amount
    pa)) = " @@ " String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ Amount -> String
    showAmountDebug Amount
    pa

    -- | Given a map of standard commodity display styles, apply the
    -- appropriate one to this amount. If there's no standard style for
    -- this amount's commodity, return the amount unchanged.
    styleAmount :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
    styleAmount :: Map CommoditySymbol AmountStyle -> Amount -> Amount
    styleAmount styles :: Map CommoditySymbol AmountStyle
    styles a :: Amount
    a =
      case CommoditySymbol
    -> Map CommoditySymbol AmountStyle -> Maybe AmountStyle
    forall k a. Ord k => k -> Map k a -> Maybe a
    M.lookup (Amount -> CommoditySymbol
    acommodity Amount
    a) Map CommoditySymbol AmountStyle
    styles of
        Just s :: AmountStyle
    s  -> Amount
    a{astyle :: AmountStyle
    astyle=AmountStyle
    s}
        Nothing -> Amount
    a

    -- | Like styleAmount, but keep the number of decimal places unchanged.
    styleAmountExceptPrecision :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
    styleAmountExceptPrecision :: Map CommoditySymbol AmountStyle -> Amount -> Amount
    styleAmountExceptPrecision styles :: Map CommoditySymbol AmountStyle
    styles a :: Amount
    a@Amount{astyle :: Amount -> AmountStyle
    astyle=AmountStyle{asprecision :: AmountStyle -> AmountPrecision
    asprecision=AmountPrecision
    origp}} =
      case CommoditySymbol
    -> Map CommoditySymbol AmountStyle -> Maybe AmountStyle
    forall k a. Ord k => k -> Map k a -> Maybe a
    M.lookup (Amount -> CommoditySymbol
    acommodity Amount
    a) Map CommoditySymbol AmountStyle
    styles of
        Just s :: AmountStyle
    s  -> Amount
    a{astyle :: AmountStyle
    astyle=AmountStyle
    s{asprecision :: AmountPrecision
    asprecision=AmountPrecision
    origp}}
        Nothing -> Amount
    a

    -- | Get the string representation of an amount, based on its
    -- commodity's display settings. String representations equivalent to
    -- zero are converted to just \"0\". The special "missing" amount is
    -- displayed as the empty string.
    showAmount :: Amount -> String
    showAmount :: Amount -> String
    showAmount = Bool -> Amount -> String
    showAmountHelper Bool
    False

    -- | Colour version. For a negative amount, adds ANSI codes to change the colour,
    -- currently to hard-coded red.
    cshowAmount :: Amount -> String
    cshowAmount :: Amount -> String
    cshowAmount a :: Amount
    a =
      (if Amount -> Bool
    isNegativeAmount Amount
    a then ColorIntensity -> Color -> ShowS
    color ColorIntensity
    Dull Color
    Red else ShowS
    forall a. a -> a
    id) ShowS -> ShowS
    forall a b. (a -> b) -> a -> b
    $
      Bool -> Amount -> String
    showAmountHelper Bool
    False Amount
    a

    showAmountHelper :: Bool -> Amount -> String
    showAmountHelper :: Bool -> Amount -> String
    showAmountHelper _ Amount{acommodity :: Amount -> CommoditySymbol
    acommodity=CommoditySymbol
    "AUTO"} = ""
    showAmountHelper showzerocommodity :: Bool
    showzerocommodity a :: Amount
    a@Amount{acommodity :: Amount -> CommoditySymbol
    acommodity=CommoditySymbol
    c, aprice :: Amount -> Maybe AmountPrice
    aprice=Maybe AmountPrice
    mp, astyle :: Amount -> AmountStyle
    astyle=AmountStyle{..}} =
        case Side
    ascommodityside of
          L -> String -> String -> String -> String -> ShowS
    forall r. PrintfType r => String -> r
    printf "%s%s%s%s" (CommoditySymbol -> String
    T.unpack CommoditySymbol
    c') String
    space String
    quantity' String
    price
          R -> String -> String -> String -> String -> ShowS
    forall r. PrintfType r => String -> r
    printf "%s%s%s%s" String
    quantity' String
    space (CommoditySymbol -> String
    T.unpack CommoditySymbol
    c') String
    price
        where
          quantity :: String
    quantity = Amount -> String
    showamountquantity Amount
    a
          (quantity' :: String
    quantity',c' :: CommoditySymbol
    c') | Amount -> Bool
    amountLooksZero Amount
    a Bool -> Bool -> Bool
    && Bool -> Bool
    not Bool
    showzerocommodity = ("0","")
                         | Bool
    otherwise = (String
    quantity, CommoditySymbol -> CommoditySymbol
    quoteCommoditySymbolIfNeeded CommoditySymbol
    c)
          space :: String
    space = if Bool -> Bool
    not (CommoditySymbol -> Bool
    T.null CommoditySymbol
    c') Bool -> Bool -> Bool
    && Bool
    ascommodityspaced then " " else "" :: String
          price :: String
    price = Maybe AmountPrice -> String
    showAmountPrice Maybe AmountPrice
    mp

    -- | Like showAmount, but show a zero amount's commodity if it has one.
    showAmountWithZeroCommodity :: Amount -> String
    showAmountWithZeroCommodity :: Amount -> String
    showAmountWithZeroCommodity = Bool -> Amount -> String
    showAmountHelper Bool
    True

    -- | Get the string representation of the number part of of an amount,
    -- using the display settings from its commodity.
    showamountquantity :: Amount -> String
    showamountquantity :: Amount -> String
    showamountquantity amt :: Amount
    amt@Amount{astyle :: Amount -> AmountStyle
    astyle=AmountStyle{asdecimalpoint :: AmountStyle -> Maybe Char
    asdecimalpoint=Maybe Char
    mdec, asdigitgroups :: AmountStyle -> Maybe DigitGroupStyle
    asdigitgroups=Maybe DigitGroupStyle
    mgrps}} =
        Char -> Maybe DigitGroupStyle -> ShowS
    punctuatenumber (Char -> Maybe Char -> Char
    forall a. a -> Maybe a -> a
    fromMaybe '.' Maybe Char
    mdec) Maybe DigitGroupStyle
    mgrps ShowS -> (Quantity -> String) -> Quantity -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Quantity -> String
    forall a. Show a => a -> String
    show (Quantity -> String) -> Quantity -> String
    forall a b. (a -> b) -> a -> b
    $ Amount -> Quantity
    amountRoundedQuantity Amount
    amt

    -- | Replace a number string's decimal mark with the specified
    -- character, and add the specified digit group marks. The last digit
    -- group will be repeated as needed.
    punctuatenumber :: Char -> Maybe DigitGroupStyle -> String -> String
    punctuatenumber :: Char -> Maybe DigitGroupStyle -> ShowS
    punctuatenumber dec :: Char
    dec mgrps :: Maybe DigitGroupStyle
    mgrps s :: String
    s = String
    sign String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ ShowS
    forall a. [a] -> [a]
    reverse (Maybe DigitGroupStyle -> ShowS
    applyDigitGroupStyle Maybe DigitGroupStyle
    mgrps (ShowS
    forall a. [a] -> [a]
    reverse String
    int)) String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ String
    frac''
        where
          (sign :: String
    sign,num :: String
    num) = (Char -> Bool) -> String -> (String, String)
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    break Char -> Bool
    isDigit String
    s
          (int :: String
    int,frac :: String
    frac) = (Char -> Bool) -> String -> (String, String)
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    break (Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    =='.') String
    num
          frac' :: String
    frac' = (Char -> Bool) -> ShowS
    forall a. (a -> Bool) -> [a] -> [a]
    dropWhile (Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    =='.') String
    frac
          frac'' :: String
    frac'' | String -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null String
    frac' = ""
                 | Bool
    otherwise  = Char
    decChar -> ShowS
    forall a. a -> [a] -> [a]
    :String
    frac'

    applyDigitGroupStyle :: Maybe DigitGroupStyle -> String -> String
    applyDigitGroupStyle :: Maybe DigitGroupStyle -> ShowS
    applyDigitGroupStyle Nothing s :: String
    s = String
    s
    applyDigitGroupStyle (Just (DigitGroups c :: Char
    c gs :: [Word8]
    gs)) s :: String
    s = [Word8] -> ShowS
    forall a. Integral a => [a] -> ShowS
    addseps ([Word8] -> [Word8]
    forall a. [a] -> [a]
    repeatLast [Word8]
    gs) String
    s
      where
        addseps :: [a] -> ShowS
    addseps [] s :: String
    s = String
    s
        addseps (g :: a
    g:gs :: [a]
    gs) s :: String
    s
          | Int -> Integer
    forall a. Integral a => a -> Integer
    toInteger (String -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length String
    s) Integer -> Integer -> Bool
    forall a. Ord a => a -> a -> Bool
    <= a -> Integer
    forall a. Integral a => a -> Integer
    toInteger a
    g = String
    s
          | Bool
    otherwise     = let (part :: String
    part,rest :: String
    rest) = a -> String -> (String, String)
    forall i a. Integral i => i -> [a] -> ([a], [a])
    genericSplitAt a
    g String
    s
                            in String
    part String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ Char
    c Char -> ShowS
    forall a. a -> [a] -> [a]
    : [a] -> ShowS
    addseps [a]
    gs String
    rest
        repeatLast :: [a] -> [a]
    repeatLast [] = []
        repeatLast gs :: [a]
    gs = [a] -> [a]
    forall a. [a] -> [a]
    init [a]
    gs [a] -> [a] -> [a]
    forall a. [a] -> [a] -> [a]
    ++ a -> [a]
    forall a. a -> [a]
    repeat ([a] -> a
    forall a. [a] -> a
    last [a]
    gs)

    -- like journalCanonicaliseAmounts
    -- | Canonicalise an amount's display style using the provided commodity style map.
    canonicaliseAmount :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
    canonicaliseAmount :: Map CommoditySymbol AmountStyle -> Amount -> Amount
    canonicaliseAmount styles :: Map CommoditySymbol AmountStyle
    styles a :: Amount
    a@Amount{acommodity :: Amount -> CommoditySymbol
    acommodity=CommoditySymbol
    c, astyle :: Amount -> AmountStyle
    astyle=AmountStyle
    s} = Amount
    a{astyle :: AmountStyle
    astyle=AmountStyle
    s'}
        where
          s' :: AmountStyle
    s' = AmountStyle
    -> CommoditySymbol
    -> Map CommoditySymbol AmountStyle
    -> AmountStyle
    forall k a. Ord k => a -> k -> Map k a -> a
    findWithDefault AmountStyle
    s CommoditySymbol
    c Map CommoditySymbol AmountStyle
    styles

    -------------------------------------------------------------------------------
    -- MixedAmount

    instance Num MixedAmount where
        fromInteger :: Integer -> MixedAmount
    fromInteger i :: Integer
    i = [Amount] -> MixedAmount
    Mixed [Integer -> Amount
    forall a. Num a => Integer -> a
    fromInteger Integer
    i]
        negate :: MixedAmount -> MixedAmount
    negate (Mixed as :: [Amount]
    as) = [Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Amount) -> [Amount] -> [Amount]
    forall a b. (a -> b) -> [a] -> [b]
    map Amount -> Amount
    forall a. Num a => a -> a
    negate [Amount]
    as
        + :: MixedAmount -> MixedAmount -> MixedAmount
    (+) (Mixed as :: [Amount]
    as) (Mixed bs :: [Amount]
    bs) = MixedAmount -> MixedAmount
    normaliseMixedAmount (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ [Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ [Amount]
    as [Amount] -> [Amount] -> [Amount]
    forall a. [a] -> [a] -> [a]
    ++ [Amount]
    bs
        * :: MixedAmount -> MixedAmount -> MixedAmount
    (*)    = String -> MixedAmount -> MixedAmount -> MixedAmount
    forall a. String -> a
    error' "error, mixed amounts do not support multiplication" -- PARTIAL:
        abs :: MixedAmount -> MixedAmount
    abs    = String -> MixedAmount -> MixedAmount
    forall a. String -> a
    error' "error, mixed amounts do not support abs"
        signum :: MixedAmount -> MixedAmount
    signum = String -> MixedAmount -> MixedAmount
    forall a. String -> a
    error' "error, mixed amounts do not support signum"

    -- | The empty mixed amount.
    nullmixedamt :: MixedAmount
    nullmixedamt :: MixedAmount
    nullmixedamt = [Amount] -> MixedAmount
    Mixed []

    -- | A temporary value for parsed transactions which had no amount specified.
    missingmixedamt :: MixedAmount
    missingmixedamt :: MixedAmount
    missingmixedamt = [Amount] -> MixedAmount
    Mixed [Amount
    missingamt]

    -- | Convert amounts in various commodities into a normalised MixedAmount.
    mixed :: [Amount] -> MixedAmount
    mixed :: [Amount] -> MixedAmount
    mixed = MixedAmount -> MixedAmount
    normaliseMixedAmount (MixedAmount -> MixedAmount)
    -> ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [Amount] -> MixedAmount
    Mixed

    -- | Simplify a mixed amount's component amounts:
    --
    -- * amounts in the same commodity are combined unless they have different prices or total prices
    --
    -- * 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 with multiple commodities, or no commodities, are replaced by one commodity-less zero amount
    --
    -- * an empty amount list is replaced by one commodity-less zero amount
    --
    -- * the special "missing" mixed amount remains unchanged
    --
    normaliseMixedAmount :: MixedAmount -> MixedAmount
    normaliseMixedAmount :: MixedAmount -> MixedAmount
    normaliseMixedAmount = Bool -> MixedAmount -> MixedAmount
    normaliseHelper Bool
    False

    normaliseHelper :: Bool -> MixedAmount -> MixedAmount
    normaliseHelper :: Bool -> MixedAmount -> MixedAmount
    normaliseHelper squashprices :: Bool
    squashprices (Mixed as :: [Amount]
    as)
      | Amount
    missingamt Amount -> [Amount] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `elem` [Amount]
    as = MixedAmount
    missingmixedamt -- missingamt should always be alone, but detect it even if not
      | [Amount] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [Amount]
    nonzeros        = [Amount] -> MixedAmount
    Mixed [Amount
    newzero]
      | Bool
    otherwise            = [Amount] -> MixedAmount
    Mixed [Amount]
    nonzeros
      where
        newzero :: Amount
    newzero = Amount -> [Amount] -> Amount
    forall a. a -> [a] -> a
    lastDef Amount
    nullamt ([Amount] -> Amount) -> [Amount] -> Amount
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Bool) -> [Amount] -> [Amount]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Bool -> Bool
    not (Bool -> Bool) -> (Amount -> Bool) -> Amount -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . CommoditySymbol -> Bool
    T.null (CommoditySymbol -> Bool)
    -> (Amount -> CommoditySymbol) -> Amount -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Amount -> CommoditySymbol
    acommodity) [Amount]
    zeros
        (zeros :: [Amount]
    zeros, nonzeros :: [Amount]
    nonzeros) = (Amount -> Bool) -> [Amount] -> ([Amount], [Amount])
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    partition Amount -> Bool
    amountIsZero ([Amount] -> ([Amount], [Amount]))
    -> [Amount] -> ([Amount], [Amount])
    forall a b. (a -> b) -> a -> b
    $
                            ([Amount] -> Amount) -> [[Amount]] -> [Amount]
    forall a b. (a -> b) -> [a] -> [b]
    map [Amount] -> Amount
    sumSimilarAmountsUsingFirstPrice ([[Amount]] -> [Amount]) -> [[Amount]] -> [Amount]
    forall a b. (a -> b) -> a -> b
    $
                            (Amount -> Amount -> Bool) -> [Amount] -> [[Amount]]
    forall a. (a -> a -> Bool) -> [a] -> [[a]]
    groupBy Amount -> Amount -> Bool
    groupfn ([Amount] -> [[Amount]]) -> [Amount] -> [[Amount]]
    forall a b. (a -> b) -> a -> b
    $
                            (Amount -> Amount -> Ordering) -> [Amount] -> [Amount]
    forall a. (a -> a -> Ordering) -> [a] -> [a]
    sortBy Amount -> Amount -> Ordering
    sortfn
                            [Amount]
    as
        sortfn :: Amount -> Amount -> Ordering
    sortfn  | Bool
    squashprices = CommoditySymbol -> CommoditySymbol -> Ordering
    forall a. Ord a => a -> a -> Ordering
    compare (CommoditySymbol -> CommoditySymbol -> Ordering)
    -> (Amount -> CommoditySymbol) -> Amount -> Amount -> Ordering
    forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
    `on` Amount -> CommoditySymbol
    acommodity
                | Bool
    otherwise    = (CommoditySymbol, Maybe AmountPrice)
    -> (CommoditySymbol, Maybe AmountPrice) -> Ordering
    forall a. Ord a => a -> a -> Ordering
    compare ((CommoditySymbol, Maybe AmountPrice)
     -> (CommoditySymbol, Maybe AmountPrice) -> Ordering)
    -> (Amount -> (CommoditySymbol, Maybe AmountPrice))
    -> Amount
    -> Amount
    -> Ordering
    forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
    `on` \a :: Amount
    a -> (Amount -> CommoditySymbol
    acommodity Amount
    a, Amount -> Maybe AmountPrice
    aprice Amount
    a)
        groupfn :: Amount -> Amount -> Bool
    groupfn | Bool
    squashprices = CommoditySymbol -> CommoditySymbol -> Bool
    forall a. Eq a => a -> a -> Bool
    (==) (CommoditySymbol -> CommoditySymbol -> Bool)
    -> (Amount -> CommoditySymbol) -> Amount -> Amount -> Bool
    forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
    `on` Amount -> CommoditySymbol
    acommodity
                | Bool
    otherwise    = \a1 :: Amount
    a1 a2 :: Amount
    a2 -> Amount -> CommoditySymbol
    acommodity Amount
    a1 CommoditySymbol -> CommoditySymbol -> Bool
    forall a. Eq a => a -> a -> Bool
    == Amount -> CommoditySymbol
    acommodity Amount
    a2 Bool -> Bool -> Bool
    && Amount -> Amount -> Bool
    combinableprices Amount
    a1 Amount
    a2

        combinableprices :: Amount -> Amount -> Bool
    combinableprices Amount{aprice :: Amount -> Maybe AmountPrice
    aprice=Maybe AmountPrice
    Nothing} Amount{aprice :: Amount -> Maybe AmountPrice
    aprice=Maybe AmountPrice
    Nothing} = Bool
    True
        combinableprices Amount{aprice :: Amount -> Maybe AmountPrice
    aprice=Just (UnitPrice p1 :: Amount
    p1)} Amount{aprice :: Amount -> Maybe AmountPrice
    aprice=Just (UnitPrice p2 :: Amount
    p2)} = Amount
    p1 Amount -> Amount -> Bool
    forall a. Eq a => a -> a -> Bool
    == Amount
    p2
        combinableprices _ _ = Bool
    False

    -- | Like normaliseMixedAmount, but combine each commodity's amounts
    -- into just one by throwing away all prices except the first. This is
    -- only used as a rendering helper, and could show a misleading price.
    normaliseMixedAmountSquashPricesForDisplay :: MixedAmount -> MixedAmount
    normaliseMixedAmountSquashPricesForDisplay :: MixedAmount -> MixedAmount
    normaliseMixedAmountSquashPricesForDisplay = Bool -> MixedAmount -> MixedAmount
    normaliseHelper Bool
    True

    -- | Unify a MixedAmount to a single commodity value if possible.
    -- Like normaliseMixedAmount, this consolidates amounts of the same commodity
    -- and discards zero amounts; but this one insists on simplifying to
    -- a single commodity, and will return Nothing if this is not possible.
    unifyMixedAmount :: MixedAmount -> Maybe Amount
    unifyMixedAmount :: MixedAmount -> Maybe Amount
    unifyMixedAmount = (Amount -> Amount -> Maybe Amount)
    -> Amount -> [Amount] -> Maybe Amount
    forall (t :: * -> *) (m :: * -> *) b a.
    (Foldable t, Monad m) =>
    (b -> a -> m b) -> b -> t a -> m b
    foldM Amount -> Amount -> Maybe Amount
    combine 0 ([Amount] -> Maybe Amount)
    -> (MixedAmount -> [Amount]) -> MixedAmount -> Maybe Amount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . MixedAmount -> [Amount]
    amounts
      where
        combine :: Amount -> Amount -> Maybe Amount
    combine amount :: Amount
    amount result :: Amount
    result
          | Amount -> Bool
    amountIsZero Amount
    amount                    = Amount -> Maybe Amount
    forall a. a -> Maybe a
    Just Amount
    result
          | Amount -> Bool
    amountIsZero Amount
    result                    = Amount -> Maybe Amount
    forall a. a -> Maybe a
    Just Amount
    amount
          | Amount -> CommoditySymbol
    acommodity Amount
    amount CommoditySymbol -> CommoditySymbol -> Bool
    forall a. Eq a => a -> a -> Bool
    == Amount -> CommoditySymbol
    acommodity Amount
    result = Amount -> Maybe Amount
    forall a. a -> Maybe a
    Just (Amount -> Maybe Amount) -> Amount -> Maybe Amount
    forall a b. (a -> b) -> a -> b
    $ Amount
    amount Amount -> Amount -> Amount
    forall a. Num a => a -> a -> a
    + Amount
    result
          | Bool
    otherwise                              = Maybe Amount
    forall a. Maybe a
    Nothing

    -- | Sum same-commodity amounts in a lossy way, applying the first
    -- price to the result and discarding any other prices. Only used as a
    -- rendering helper.
    sumSimilarAmountsUsingFirstPrice :: [Amount] -> Amount
    sumSimilarAmountsUsingFirstPrice :: [Amount] -> Amount
    sumSimilarAmountsUsingFirstPrice [] = Amount
    nullamt
    sumSimilarAmountsUsingFirstPrice as :: [Amount]
    as = ([Amount] -> Amount
    forall a. Num a => [a] -> a
    sumStrict [Amount]
    as){aprice :: Maybe AmountPrice
    aprice=Amount -> Maybe AmountPrice
    aprice (Amount -> Maybe AmountPrice) -> Amount -> Maybe AmountPrice
    forall a b. (a -> b) -> a -> b
    $ [Amount] -> Amount
    forall a. [a] -> a
    head [Amount]
    as}

    -- -- | Sum same-commodity amounts. If there were different prices, set
    -- -- the price to a special marker indicating "various". Only used as a
    -- -- rendering helper.
    -- sumSimilarAmountsNotingPriceDifference :: [Amount] -> Amount
    -- sumSimilarAmountsNotingPriceDifference [] = nullamt
    -- sumSimilarAmountsNotingPriceDifference as = undefined

    -- | Get a mixed amount's component amounts.
    amounts :: MixedAmount -> [Amount]
    amounts :: MixedAmount -> [Amount]
    amounts (Mixed as :: [Amount]
    as) = [Amount]
    as

    -- | Filter a mixed amount's component amounts by a predicate.
    filterMixedAmount :: (Amount -> Bool) -> MixedAmount -> MixedAmount
    filterMixedAmount :: (Amount -> Bool) -> MixedAmount -> MixedAmount
    filterMixedAmount p :: Amount -> Bool
    p (Mixed as :: [Amount]
    as) = [Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Bool) -> [Amount] -> [Amount]
    forall a. (a -> Bool) -> [a] -> [a]
    filter Amount -> Bool
    p [Amount]
    as

    -- | Return an unnormalised MixedAmount containing exactly one Amount
    -- with the specified commodity and the quantity of that commodity
    -- found in the original. NB if Amount's quantity is zero it will be
    -- discarded next time the MixedAmount gets normalised.
    filterMixedAmountByCommodity :: CommoditySymbol -> MixedAmount -> MixedAmount
    filterMixedAmountByCommodity :: CommoditySymbol -> MixedAmount -> MixedAmount
    filterMixedAmountByCommodity c :: CommoditySymbol
    c (Mixed as :: [Amount]
    as) = [Amount] -> MixedAmount
    Mixed [Amount]
    as'
      where
        as' :: [Amount]
    as' = case (Amount -> Bool) -> [Amount] -> [Amount]
    forall a. (a -> Bool) -> [a] -> [a]
    filter ((CommoditySymbol -> CommoditySymbol -> Bool
    forall a. Eq a => a -> a -> Bool
    ==CommoditySymbol
    c) (CommoditySymbol -> Bool)
    -> (Amount -> CommoditySymbol) -> Amount -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Amount -> CommoditySymbol
    acommodity) [Amount]
    as of
                []   -> [Amount
    nullamt{acommodity :: CommoditySymbol
    acommodity=CommoditySymbol
    c}]
                as'' :: [Amount]
    as'' -> [[Amount] -> Amount
    forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
    sum [Amount]
    as'']

    -- | Apply a transform to a mixed amount's component 'Amount's.
    mapMixedAmount :: (Amount -> Amount) -> MixedAmount -> MixedAmount
    mapMixedAmount :: (Amount -> Amount) -> MixedAmount -> MixedAmount
    mapMixedAmount f :: Amount -> Amount
    f (Mixed as :: [Amount]
    as) = [Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Amount) -> [Amount] -> [Amount]
    forall a b. (a -> b) -> [a] -> [b]
    map Amount -> Amount
    f [Amount]
    as

    -- | Convert all component amounts to cost/selling price where
    -- possible (see amountCost).
    mixedAmountCost :: MixedAmount -> MixedAmount
    mixedAmountCost :: MixedAmount -> MixedAmount
    mixedAmountCost (Mixed as :: [Amount]
    as) = [Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Amount) -> [Amount] -> [Amount]
    forall a b. (a -> b) -> [a] -> [b]
    map Amount -> Amount
    amountCost [Amount]
    as

    -- | Divide a mixed amount's quantities by a constant.
    divideMixedAmount :: Quantity -> MixedAmount -> MixedAmount
    divideMixedAmount :: Quantity -> MixedAmount -> MixedAmount
    divideMixedAmount n :: Quantity
    n = (Amount -> Amount) -> MixedAmount -> MixedAmount
    mapMixedAmount (Quantity -> Amount -> Amount
    divideAmount Quantity
    n)

    -- | Multiply a mixed amount's quantities by a constant.
    multiplyMixedAmount :: Quantity -> MixedAmount -> MixedAmount
    multiplyMixedAmount :: Quantity -> MixedAmount -> MixedAmount
    multiplyMixedAmount n :: Quantity
    n = (Amount -> Amount) -> MixedAmount -> MixedAmount
    mapMixedAmount (Quantity -> Amount -> Amount
    multiplyAmount Quantity
    n)

    -- | Divide a mixed amount's quantities (and total prices, if any) by a constant.
    -- The total prices will be kept positive regardless of the multiplier's sign.
    divideMixedAmountAndPrice :: Quantity -> MixedAmount -> MixedAmount
    divideMixedAmountAndPrice :: Quantity -> MixedAmount -> MixedAmount
    divideMixedAmountAndPrice n :: Quantity
    n = (Amount -> Amount) -> MixedAmount -> MixedAmount
    mapMixedAmount (Quantity -> Amount -> Amount
    divideAmountAndPrice Quantity
    n)

    -- | Multiply a mixed amount's quantities (and total prices, if any) by a constant.
    -- The total prices will be kept positive regardless of the multiplier's sign.
    multiplyMixedAmountAndPrice :: Quantity -> MixedAmount -> MixedAmount
    multiplyMixedAmountAndPrice :: Quantity -> MixedAmount -> MixedAmount
    multiplyMixedAmountAndPrice n :: Quantity
    n = (Amount -> Amount) -> MixedAmount -> MixedAmount
    mapMixedAmount (Quantity -> Amount -> Amount
    multiplyAmountAndPrice Quantity
    n)

    -- | Calculate the average of some mixed amounts.
    averageMixedAmounts :: [MixedAmount] -> MixedAmount
    averageMixedAmounts :: [MixedAmount] -> MixedAmount
    averageMixedAmounts [] = 0
    averageMixedAmounts as :: [MixedAmount]
    as = Int -> Quantity
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral ([MixedAmount] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [MixedAmount]
    as) Quantity -> MixedAmount -> MixedAmount
    `divideMixedAmount` [MixedAmount] -> MixedAmount
    forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
    sum [MixedAmount]
    as

    -- | Is this mixed amount negative, if we can tell that unambiguously?
    -- Ie when normalised, are all individual commodity amounts negative ?
    isNegativeMixedAmount :: MixedAmount -> Maybe Bool
    isNegativeMixedAmount :: MixedAmount -> Maybe Bool
    isNegativeMixedAmount m :: MixedAmount
    m =
      case MixedAmount -> [Amount]
    amounts (MixedAmount -> [Amount]) -> MixedAmount -> [Amount]
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> MixedAmount
    normaliseMixedAmountSquashPricesForDisplay MixedAmount
    m of
        []  -> Bool -> Maybe Bool
    forall a. a -> Maybe a
    Just Bool
    False
        [a :: Amount
    a] -> Bool -> Maybe Bool
    forall a. a -> Maybe a
    Just (Bool -> Maybe Bool) -> Bool -> Maybe Bool
    forall a b. (a -> b) -> a -> b
    $ Amount -> Bool
    isNegativeAmount Amount
    a
        as :: [Amount]
    as | (Amount -> Bool) -> [Amount] -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    all Amount -> Bool
    isNegativeAmount [Amount]
    as -> Bool -> Maybe Bool
    forall a. a -> Maybe a
    Just Bool
    True
        as :: [Amount]
    as | Bool -> Bool
    not ((Amount -> Bool) -> [Amount] -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    any Amount -> Bool
    isNegativeAmount [Amount]
    as) -> Bool -> Maybe Bool
    forall a. a -> Maybe a
    Just Bool
    False
        _ -> Maybe Bool
    forall a. Maybe a
    Nothing  -- multiple amounts with different signs

    -- | Does this mixed amount appear to be zero when rendered with its
    -- display precision ?
    mixedAmountLooksZero :: MixedAmount -> Bool
    mixedAmountLooksZero :: MixedAmount -> Bool
    mixedAmountLooksZero = (Amount -> Bool) -> [Amount] -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    all Amount -> Bool
    amountLooksZero ([Amount] -> Bool)
    -> (MixedAmount -> [Amount]) -> MixedAmount -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . MixedAmount -> [Amount]
    amounts (MixedAmount -> [Amount])
    -> (MixedAmount -> MixedAmount) -> MixedAmount -> [Amount]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . MixedAmount -> MixedAmount
    normaliseMixedAmountSquashPricesForDisplay

    -- | Is this mixed amount exactly zero, ignoring display precisions ?
    mixedAmountIsZero :: MixedAmount -> Bool
    mixedAmountIsZero :: MixedAmount -> Bool
    mixedAmountIsZero = (Amount -> Bool) -> [Amount] -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    all Amount -> Bool
    amountIsZero ([Amount] -> Bool)
    -> (MixedAmount -> [Amount]) -> MixedAmount -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . MixedAmount -> [Amount]
    amounts (MixedAmount -> [Amount])
    -> (MixedAmount -> MixedAmount) -> MixedAmount -> [Amount]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . MixedAmount -> MixedAmount
    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.
    -- mixedAmountEquals :: MixedAmount -> MixedAmount -> Bool
    -- mixedAmountEquals a b = amounts a' == amounts b' || (mixedAmountLooksZero a' && mixedAmountLooksZero b')
    --     where a' = normaliseMixedAmountSquashPricesForDisplay a
    --           b' = normaliseMixedAmountSquashPricesForDisplay b

    -- | Given a map of standard commodity display styles, apply the
    -- appropriate one to each individual amount.
    styleMixedAmount :: M.Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount
    styleMixedAmount :: Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount
    styleMixedAmount styles :: Map CommoditySymbol AmountStyle
    styles (Mixed as :: [Amount]
    as) = [Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Amount) -> [Amount] -> [Amount]
    forall a b. (a -> b) -> [a] -> [b]
    map (Map CommoditySymbol AmountStyle -> Amount -> Amount
    styleAmount Map CommoditySymbol AmountStyle
    styles) [Amount]
    as

    -- | Get the string representation of a mixed amount, after
    -- normalising it to one amount per commodity. Assumes amounts have
    -- no or similar prices, otherwise this can show misleading prices.
    showMixedAmount :: MixedAmount -> String
    showMixedAmount :: MixedAmount -> String
    showMixedAmount = Bool -> Bool -> MixedAmount -> String
    showMixedAmountHelper Bool
    False Bool
    False

    -- | Like showMixedAmount, but zero amounts are shown with their
    -- commodity if they have one.
    showMixedAmountWithZeroCommodity :: MixedAmount -> String
    showMixedAmountWithZeroCommodity :: MixedAmount -> String
    showMixedAmountWithZeroCommodity = Bool -> Bool -> MixedAmount -> String
    showMixedAmountHelper Bool
    True Bool
    False

    -- | Get the one-line string representation of a mixed amount.
    showMixedAmountOneLine :: MixedAmount -> String
    showMixedAmountOneLine :: MixedAmount -> String
    showMixedAmountOneLine = Bool -> Bool -> MixedAmount -> String
    showMixedAmountHelper Bool
    False Bool
    True

    showMixedAmountHelper :: Bool -> Bool -> MixedAmount -> String
    showMixedAmountHelper :: Bool -> Bool -> MixedAmount -> String
    showMixedAmountHelper showzerocommodity :: Bool
    showzerocommodity useoneline :: Bool
    useoneline m :: MixedAmount
    m =
      [String] -> String
    join ([String] -> String) -> [String] -> String
    forall a b. (a -> b) -> a -> b
    $ (Amount -> String) -> [Amount] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map Amount -> String
    showamt ([Amount] -> [String]) -> [Amount] -> [String]
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> [Amount]
    amounts (MixedAmount -> [Amount]) -> MixedAmount -> [Amount]
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> MixedAmount
    normaliseMixedAmountSquashPricesForDisplay MixedAmount
    m
      where
        join :: [String] -> String
    join | Bool
    useoneline = String -> [String] -> String
    forall a. [a] -> [[a]] -> [a]
    intercalate ", "
             | Bool
    otherwise  = [String] -> String
    vConcatRightAligned
        showamt :: Amount -> String
    showamt | Bool
    showzerocommodity = Amount -> String
    showAmountWithZeroCommodity
                | Bool
    otherwise         = Amount -> String
    showAmount

    -- | Compact labelled trace of a mixed amount, for debugging.
    ltraceamount :: String -> MixedAmount -> MixedAmount
    ltraceamount :: String -> MixedAmount -> MixedAmount
    ltraceamount s :: String
    s = (MixedAmount -> String) -> MixedAmount -> MixedAmount
    forall a. Show a => (a -> String) -> a -> a
    traceWith (((String
    s String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ ": ") String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++)ShowS -> (MixedAmount -> String) -> MixedAmount -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .MixedAmount -> String
    showMixedAmount)

    -- | Set the display precision in the amount's commodities.
    setMixedAmountPrecision :: AmountPrecision -> MixedAmount -> MixedAmount
    setMixedAmountPrecision :: AmountPrecision -> MixedAmount -> MixedAmount
    setMixedAmountPrecision p :: AmountPrecision
    p (Mixed as :: [Amount]
    as) = [Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Amount) -> [Amount] -> [Amount]
    forall a b. (a -> b) -> [a] -> [b]
    map (AmountPrecision -> Amount -> Amount
    setAmountPrecision AmountPrecision
    p) [Amount]
    as

    -- | Get the string representation of a mixed amount, showing each of its
    -- component amounts with the specified precision, ignoring their
    -- commoditys' display precision settings.
    showMixedAmountWithPrecision :: AmountPrecision -> MixedAmount -> String
    showMixedAmountWithPrecision :: AmountPrecision -> MixedAmount -> String
    showMixedAmountWithPrecision p :: AmountPrecision
    p m :: MixedAmount
    m =
        [String] -> String
    vConcatRightAligned ([String] -> String) -> [String] -> String
    forall a b. (a -> b) -> a -> b
    $ (Amount -> String) -> [Amount] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map (AmountPrecision -> Amount -> String
    showAmountWithPrecision AmountPrecision
    p) ([Amount] -> [String]) -> [Amount] -> [String]
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> [Amount]
    amounts (MixedAmount -> [Amount]) -> MixedAmount -> [Amount]
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> MixedAmount
    normaliseMixedAmountSquashPricesForDisplay MixedAmount
    m

    -- | Get an unambiguous string representation of a mixed amount for debugging.
    showMixedAmountDebug :: MixedAmount -> String
    showMixedAmountDebug :: MixedAmount -> String
    showMixedAmountDebug m :: MixedAmount
    m | MixedAmount
    m MixedAmount -> MixedAmount -> Bool
    forall a. Eq a => a -> a -> Bool
    == MixedAmount
    missingmixedamt = "(missing)"
                           | Bool
    otherwise       = String -> ShowS
    forall r. PrintfType r => String -> r
    printf "Mixed [%s]" String
    as
        where as :: String
    as = String -> [String] -> String
    forall a. [a] -> [[a]] -> [a]
    intercalate "\n       " ([String] -> String) -> [String] -> String
    forall a b. (a -> b) -> a -> b
    $ (Amount -> String) -> [Amount] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map Amount -> String
    showAmountDebug ([Amount] -> [String]) -> [Amount] -> [String]
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> [Amount]
    amounts MixedAmount
    m

    -- TODO these and related fns are comically complicated:

    -- | Get the string representation of a mixed amount, without showing any transaction prices.
    -- With a True argument, adds ANSI codes to show negative amounts in red.
    showMixedAmountWithoutPrice :: Bool -> MixedAmount -> String
    showMixedAmountWithoutPrice :: Bool -> MixedAmount -> String
    showMixedAmountWithoutPrice c :: Bool
    c m :: MixedAmount
    m = String -> [String] -> String
    forall a. [a] -> [[a]] -> [a]
    intercalate "\n" ([String] -> String) -> [String] -> String
    forall a b. (a -> b) -> a -> b
    $ (Amount -> String) -> [Amount] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map Amount -> String
    showamt [Amount]
    as
      where
        Mixed as :: [Amount]
    as = MixedAmount -> MixedAmount
    normaliseMixedAmountSquashPricesForDisplay (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> MixedAmount
    mixedAmountStripPrices MixedAmount
    m
        showamt :: Amount -> String
    showamt a :: Amount
    a =
          (if Bool
    c Bool -> Bool -> Bool
    && Amount -> Bool
    isNegativeAmount Amount
    a then ColorIntensity -> Color -> ShowS
    color ColorIntensity
    Dull Color
    Red else ShowS
    forall a. a -> a
    id) ShowS -> ShowS
    forall a b. (a -> b) -> a -> b
    $
          String -> ShowS
    forall r. PrintfType r => String -> r
    printf (String -> Int -> String
    forall r. PrintfType r => String -> r
    printf "%%%ds" Int
    width) ShowS -> ShowS
    forall a b. (a -> b) -> a -> b
    $ Bool -> Amount -> String
    showAmountWithoutPrice Bool
    c Amount
    a
          where
            width :: Int
    width = Int -> Maybe Int -> Int
    forall a. a -> Maybe a -> a
    fromMaybe 0 (Maybe Int -> Int) -> ([Int] -> Maybe Int) -> [Int] -> Int
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [Int] -> Maybe Int
    forall a. Ord a => [a] -> Maybe a
    maximumMay ([Int] -> Int) -> [Int] -> Int
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Int) -> [Amount] -> [Int]
    forall a b. (a -> b) -> [a] -> [b]
    map (String -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length (String -> Int) -> (Amount -> String) -> Amount -> Int
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Amount -> String
    showAmount) [Amount]
    as

    mixedAmountStripPrices :: MixedAmount -> MixedAmount
    mixedAmountStripPrices :: MixedAmount -> MixedAmount
    mixedAmountStripPrices (Mixed as :: [Amount]
    as) = [Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Amount) -> [Amount] -> [Amount]
    forall a b. (a -> b) -> [a] -> [b]
    map (\a :: Amount
    a -> Amount
    a{aprice :: Maybe AmountPrice
    aprice=Maybe AmountPrice
    forall a. Maybe a
    Nothing}) [Amount]
    as

    -- | Get the one-line string representation of a mixed amount, but without
    -- any \@ prices.
    -- With a True argument, adds ANSI codes to show negative amounts in red.
    showMixedAmountOneLineWithoutPrice :: Bool -> MixedAmount -> String
    showMixedAmountOneLineWithoutPrice :: Bool -> MixedAmount -> String
    showMixedAmountOneLineWithoutPrice c :: Bool
    c m :: MixedAmount
    m =
      String -> [String] -> String
    forall a. [a] -> [[a]] -> [a]
    intercalate ", " ([String] -> String) -> [String] -> String
    forall a b. (a -> b) -> a -> b
    $ (Amount -> String) -> [Amount] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map (Bool -> Amount -> String
    showAmountWithoutPrice Bool
    c) [Amount]
    as
        where
          (Mixed as :: [Amount]
    as) = MixedAmount -> MixedAmount
    normaliseMixedAmountSquashPricesForDisplay (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> MixedAmount
    stripPrices MixedAmount
    m
          stripPrices :: MixedAmount -> MixedAmount
    stripPrices (Mixed as :: [Amount]
    as) = [Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Amount) -> [Amount] -> [Amount]
    forall a b. (a -> b) -> [a] -> [b]
    map Amount -> Amount
    stripprice [Amount]
    as where stripprice :: Amount -> Amount
    stripprice a :: Amount
    a = Amount
    a{aprice :: Maybe AmountPrice
    aprice=Maybe AmountPrice
    forall a. Maybe a
    Nothing}

    -- | Like showMixedAmountOneLineWithoutPrice, but show at most two commodities,
    -- with a elision indicator if there are more.
    -- With a True argument, adds ANSI codes to show negative amounts in red.
    showMixedAmountElided :: Bool -> MixedAmount -> String
    showMixedAmountElided :: Bool -> MixedAmount -> String
    showMixedAmountElided c :: Bool
    c m :: MixedAmount
    m = String -> [String] -> String
    forall a. [a] -> [[a]] -> [a]
    intercalate ", " ([String] -> String) -> [String] -> String
    forall a b. (a -> b) -> a -> b
    $ Int -> [String] -> [String]
    forall a. Int -> [a] -> [a]
    take 2 [String]
    astrs [String] -> [String] -> [String]
    forall a. [a] -> [a] -> [a]
    ++ [String]
    elisionstr
      where
        astrs :: [String]
    astrs = (Amount -> String) -> [Amount] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map (Bool -> Amount -> String
    showAmountWithoutPrice Bool
    c) [Amount]
    as
          where
            (Mixed as :: [Amount]
    as) = MixedAmount -> MixedAmount
    normaliseMixedAmountSquashPricesForDisplay (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> MixedAmount
    stripPrices MixedAmount
    m
              where
                stripPrices :: MixedAmount -> MixedAmount
    stripPrices (Mixed as :: [Amount]
    as) = [Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Amount) -> [Amount] -> [Amount]
    forall a b. (a -> b) -> [a] -> [b]
    map Amount -> Amount
    stripprice [Amount]
    as
                  where
                    stripprice :: Amount -> Amount
    stripprice a :: Amount
    a = Amount
    a{aprice :: Maybe AmountPrice
    aprice=Maybe AmountPrice
    forall a. Maybe a
    Nothing}
        elisionstr :: [String]
    elisionstr | Int
    n Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    > 2     = [Int -> String
    forall a. Show a => a -> String
    show (Int
    n Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - 2) String -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ " more.."]
                   | Bool
    otherwise = []
          where
            n :: Int
    n = [String] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [String]
    astrs

    -- | Canonicalise a mixed amount's display styles using the provided commodity style map.
    canonicaliseMixedAmount :: M.Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount
    canonicaliseMixedAmount :: Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount
    canonicaliseMixedAmount styles :: Map CommoditySymbol AmountStyle
    styles (Mixed as :: [Amount]
    as) = [Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Amount) -> [Amount] -> [Amount]
    forall a b. (a -> b) -> [a] -> [b]
    map (Map CommoditySymbol AmountStyle -> Amount -> Amount
    canonicaliseAmount Map CommoditySymbol AmountStyle
    styles) [Amount]
    as

    -- | Replace each component amount's TotalPrice, if it has one, with an equivalent UnitPrice.
    -- Has no effect on amounts without one.
    -- Does Decimal division, might be some rounding/irrational number issues.
    mixedAmountTotalPriceToUnitPrice :: MixedAmount -> MixedAmount
    mixedAmountTotalPriceToUnitPrice :: MixedAmount -> MixedAmount
    mixedAmountTotalPriceToUnitPrice (Mixed as :: [Amount]
    as) = [Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount) -> [Amount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Amount -> Amount) -> [Amount] -> [Amount]
    forall a b. (a -> b) -> [a] -> [b]
    map Amount -> Amount
    amountTotalPriceToUnitPrice [Amount]
    as

    -------------------------------------------------------------------------------
    -- tests

    tests_Amount :: TestTree
    tests_Amount = String -> [TestTree] -> TestTree
    tests "Amount" [
       String -> [TestTree] -> TestTree
    tests "Amount" [

         String -> Assertion -> TestTree
    test "amountCost" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
           Amount -> Amount
    amountCost (Quantity -> Amount
    eur 1) Amount -> Amount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Quantity -> Amount
    eur 1
           Amount -> Amount
    amountCost (Quantity -> Amount
    eur 2){aprice :: Maybe AmountPrice
    aprice=AmountPrice -> Maybe AmountPrice
    forall a. a -> Maybe a
    Just (AmountPrice -> Maybe AmountPrice)
    -> AmountPrice -> Maybe AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Amount -> AmountPrice
    UnitPrice (Amount -> AmountPrice) -> Amount -> AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd 2} Amount -> Amount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Quantity -> Amount
    usd 4
           Amount -> Amount
    amountCost (Quantity -> Amount
    eur 1){aprice :: Maybe AmountPrice
    aprice=AmountPrice -> Maybe AmountPrice
    forall a. a -> Maybe a
    Just (AmountPrice -> Maybe AmountPrice)
    -> AmountPrice -> Maybe AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Amount -> AmountPrice
    TotalPrice (Amount -> AmountPrice) -> Amount -> AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd 2} Amount -> Amount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Quantity -> Amount
    usd 2
           Amount -> Amount
    amountCost (Quantity -> Amount
    eur (-1)){aprice :: Maybe AmountPrice
    aprice=AmountPrice -> Maybe AmountPrice
    forall a. a -> Maybe a
    Just (AmountPrice -> Maybe AmountPrice)
    -> AmountPrice -> Maybe AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Amount -> AmountPrice
    TotalPrice (Amount -> AmountPrice) -> Amount -> AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd 2} Amount -> Amount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Quantity -> Amount
    usd (-2)

        ,String -> Assertion -> TestTree
    test "amountLooksZero" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
           HasCallStack => String -> Bool -> Assertion
    String -> Bool -> Assertion
    assertBool "" (Bool -> Assertion) -> Bool -> Assertion
    forall a b. (a -> b) -> a -> b
    $ Amount -> Bool
    amountLooksZero Amount
    amount
           HasCallStack => String -> Bool -> Assertion
    String -> Bool -> Assertion
    assertBool "" (Bool -> Assertion) -> Bool -> Assertion
    forall a b. (a -> b) -> a -> b
    $ Amount -> Bool
    amountLooksZero (Amount -> Bool) -> Amount -> Bool
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd 0

        ,String -> Assertion -> TestTree
    test "negating amounts" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
           Amount -> Amount
    forall a. Num a => a -> a
    negate (Quantity -> Amount
    usd 1) Amount -> Amount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= (Quantity -> Amount
    usd 1){aquantity :: Quantity
    aquantity= -1}
           let b :: Amount
    b = (Quantity -> Amount
    usd 1){aprice :: Maybe AmountPrice
    aprice=AmountPrice -> Maybe AmountPrice
    forall a. a -> Maybe a
    Just (AmountPrice -> Maybe AmountPrice)
    -> AmountPrice -> Maybe AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Amount -> AmountPrice
    UnitPrice (Amount -> AmountPrice) -> Amount -> AmountPrice
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    eur 2} in Amount -> Amount
    forall a. Num a => a -> a
    negate Amount
    b Amount -> Amount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Amount
    b{aquantity :: Quantity
    aquantity= -1}

        ,String -> Assertion -> TestTree
    test "adding amounts without prices" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
           (Quantity -> Amount
    usd 1.23 Amount -> Amount -> Amount
    forall a. Num a => a -> a -> a
    + Quantity -> Amount
    usd (-1.23)) Amount -> Amount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Quantity -> Amount
    usd 0
           (Quantity -> Amount
    usd 1.23 Amount -> Amount -> Amount
    forall a. Num a => a -> a -> a
    + Quantity -> Amount
    usd (-1.23)) Amount -> Amount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Quantity -> Amount
    usd 0
           (Quantity -> Amount
    usd (-1.23) Amount -> Amount -> Amount
    forall a. Num a => a -> a -> a
    + Quantity -> Amount
    usd (-1.23)) Amount -> Amount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Quantity -> Amount
    usd (-2.46)
           [Amount] -> Amount
    forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
    sum [Quantity -> Amount
    usd 1.23,Quantity -> Amount
    usd (-1.23),Quantity -> Amount
    usd (-1.23),-(Quantity -> Amount
    usd (-1.23))] Amount -> Amount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Quantity -> Amount
    usd 0
           -- highest precision is preserved
           AmountStyle -> AmountPrecision
    asprecision (Amount -> AmountStyle
    astyle (Amount -> AmountStyle) -> Amount -> AmountStyle
    forall a b. (a -> b) -> a -> b
    $ [Amount] -> Amount
    forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
    sum [Quantity -> Amount
    usd 1 Amount -> AmountPrecision -> Amount
    `withPrecision` Word8 -> AmountPrecision
    Precision 1, Quantity -> Amount
    usd 1 Amount -> AmountPrecision -> Amount
    `withPrecision` Word8 -> AmountPrecision
    Precision 3]) AmountPrecision -> AmountPrecision -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Word8 -> AmountPrecision
    Precision 3
           AmountStyle -> AmountPrecision
    asprecision (Amount -> AmountStyle
    astyle (Amount -> AmountStyle) -> Amount -> AmountStyle
    forall a b. (a -> b) -> a -> b
    $ [Amount] -> Amount
    forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
    sum [Quantity -> Amount
    usd 1 Amount -> AmountPrecision -> Amount
    `withPrecision` Word8 -> AmountPrecision
    Precision 3, Quantity -> Amount
    usd 1 Amount -> AmountPrecision -> Amount
    `withPrecision` Word8 -> AmountPrecision
    Precision 1]) AmountPrecision -> AmountPrecision -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Word8 -> AmountPrecision
    Precision 3
           -- adding different commodities assumes conversion rate 1
           HasCallStack => String -> Bool -> Assertion
    String -> Bool -> Assertion
    assertBool "" (Bool -> Assertion) -> Bool -> Assertion
    forall a b. (a -> b) -> a -> b
    $ Amount -> Bool
    amountLooksZero (Quantity -> Amount
    usd 1.23 Amount -> Amount -> Amount
    forall a. Num a => a -> a -> a
    - Quantity -> Amount
    eur 1.23)

        ,String -> Assertion -> TestTree
    test "showAmount" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
          Amount -> String
    showAmount (Quantity -> Amount
    usd 0 Amount -> Amount -> Amount
    forall a. Num a => a -> a -> a
    + Quantity -> Amount
    gbp 0) String -> String -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= "0"

      ]

      ,String -> [TestTree] -> TestTree
    tests "MixedAmount" [

         String -> Assertion -> TestTree
    test "adding mixed amounts to zero, the commodity and amount style are preserved" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $
          [MixedAmount] -> MixedAmount
    forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
    sum ((Amount -> MixedAmount) -> [Amount] -> [MixedAmount]
    forall a b. (a -> b) -> [a] -> [b]
    map ([Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount)
    -> (Amount -> [Amount]) -> Amount -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Amount -> [Amount] -> [Amount]
    forall a. a -> [a] -> [a]
    :[]))
                   [Quantity -> Amount
    usd 1.25
                   ,Quantity -> Amount
    usd (-1) Amount -> AmountPrecision -> Amount
    `withPrecision` Word8 -> AmountPrecision
    Precision 3
                   ,Quantity -> Amount
    usd (-0.25)
                   ])
            MixedAmount -> MixedAmount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= [Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 0 Amount -> AmountPrecision -> Amount
    `withPrecision` Word8 -> AmountPrecision
    Precision 3]

        ,String -> Assertion -> TestTree
    test "adding mixed amounts with total prices" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
          [MixedAmount] -> MixedAmount
    forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
    sum ((Amount -> MixedAmount) -> [Amount] -> [MixedAmount]
    forall a b. (a -> b) -> [a] -> [b]
    map ([Amount] -> MixedAmount
    Mixed ([Amount] -> MixedAmount)
    -> (Amount -> [Amount]) -> Amount -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Amount -> [Amount] -> [Amount]
    forall a. a -> [a] -> [a]
    :[]))
           [Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    @@ Quantity -> Amount
    eur 1
           ,Quantity -> Amount
    usd (-2) Amount -> Amount -> Amount
    @@ Quantity -> Amount
    eur 1
           ])
            MixedAmount -> MixedAmount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= [Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    @@ Quantity -> Amount
    eur 1
                       ,Quantity -> Amount
    usd (-2) Amount -> Amount -> Amount
    @@ Quantity -> Amount
    eur 1
                       ]

        ,String -> Assertion -> TestTree
    test "showMixedAmount" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
           MixedAmount -> String
    showMixedAmount ([Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 1]) String -> String -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= "$1.00"
           MixedAmount -> String
    showMixedAmount ([Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    `at` Quantity -> Amount
    eur 2]) String -> String -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= "$1.00 @ €2.00"
           MixedAmount -> String
    showMixedAmount ([Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 0]) String -> String -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= "0"
           MixedAmount -> String
    showMixedAmount ([Amount] -> MixedAmount
    Mixed []) String -> String -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= "0"
           MixedAmount -> String
    showMixedAmount MixedAmount
    missingmixedamt String -> String -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= ""

        ,String -> Assertion -> TestTree
    test "showMixedAmountWithoutPrice" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
          let a :: Amount
    a = Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    `at` Quantity -> Amount
    eur 2
          Bool -> MixedAmount -> String
    showMixedAmountWithoutPrice Bool
    False ([Amount] -> MixedAmount
    Mixed [Amount
    a]) String -> String -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= "$1.00"
          Bool -> MixedAmount -> String
    showMixedAmountWithoutPrice Bool
    False ([Amount] -> MixedAmount
    Mixed [Amount
    a, -Amount
    a]) String -> String -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= "0"

        ,String -> [TestTree] -> TestTree
    tests "normaliseMixedAmount" [
           String -> Assertion -> TestTree
    test "a missing amount overrides any other amounts" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $
            MixedAmount -> MixedAmount
    normaliseMixedAmount ([Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 1, Amount
    missingamt]) MixedAmount -> MixedAmount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= MixedAmount
    missingmixedamt
          ,String -> Assertion -> TestTree
    test "unpriced same-commodity amounts are combined" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $
            MixedAmount -> MixedAmount
    normaliseMixedAmount ([Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 0, Quantity -> Amount
    usd 2]) MixedAmount -> MixedAmount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= [Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 2]
          ,String -> Assertion -> TestTree
    test "amounts with same unit price are combined" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $
            MixedAmount -> MixedAmount
    normaliseMixedAmount ([Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    `at` Quantity -> Amount
    eur 1, Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    `at` Quantity -> Amount
    eur 1]) MixedAmount -> MixedAmount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= [Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 2 Amount -> Amount -> Amount
    `at` Quantity -> Amount
    eur 1]
          ,String -> Assertion -> TestTree
    test "amounts with different unit prices are not combined" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $
            MixedAmount -> MixedAmount
    normaliseMixedAmount ([Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    `at` Quantity -> Amount
    eur 1, Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    `at` Quantity -> Amount
    eur 2]) MixedAmount -> MixedAmount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= [Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    `at` Quantity -> Amount
    eur 1, Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    `at` Quantity -> Amount
    eur 2]
          ,String -> Assertion -> TestTree
    test "amounts with total prices are not combined" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $
            MixedAmount -> MixedAmount
    normaliseMixedAmount ([Amount] -> MixedAmount
    Mixed  [Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    @@ Quantity -> Amount
    eur 1, Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    @@ Quantity -> Amount
    eur 1]) MixedAmount -> MixedAmount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= [Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    @@ Quantity -> Amount
    eur 1, Quantity -> Amount
    usd 1 Amount -> Amount -> Amount
    @@ Quantity -> Amount
    eur 1]
        ]

        ,String -> Assertion -> TestTree
    test "normaliseMixedAmountSquashPricesForDisplay" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
           MixedAmount -> MixedAmount
    normaliseMixedAmountSquashPricesForDisplay ([Amount] -> MixedAmount
    Mixed []) MixedAmount -> MixedAmount -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= [Amount] -> MixedAmount
    Mixed [Amount
    nullamt]
           HasCallStack => String -> Bool -> Assertion
    String -> Bool -> Assertion
    assertBool "" (Bool -> Assertion) -> Bool -> Assertion
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> Bool
    mixedAmountLooksZero (MixedAmount -> Bool) -> MixedAmount -> Bool
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> MixedAmount
    normaliseMixedAmountSquashPricesForDisplay
            ([Amount] -> MixedAmount
    Mixed [Quantity -> Amount
    usd 10
                   ,Quantity -> Amount
    usd 10 Amount -> Amount -> Amount
    @@ Quantity -> Amount
    eur 7
                   ,Quantity -> Amount
    usd (-10)
                   ,Quantity -> Amount
    usd (-10) Amount -> Amount -> Amount
    @@ Quantity -> Amount
    eur 7
                   ])

      ]

     ]