-- | This module provides definitions for amounts used as in accounting.
--
-- For balance definition that allows "Negative Balance" phenomenon, see
-- 'Haspara.Accounting.Balance'.

{-# LANGUAGE DataKinds #-}

module Haspara.Accounting.Amount where

import qualified Data.Aeson                 as Aeson
import           GHC.Generics               (Generic)
import           GHC.TypeLits               (KnownNat, Nat)
import           Haspara.Accounting.Account (AccountKind(..))
import           Haspara.Accounting.Side    (Side(..), sideByAccountKind)
import           Haspara.Internal.Aeson     (commonAesonOptions)
import           Haspara.Quantity           (Quantity, UnsignedQuantity, absQuantity)
import           Refined                    (unrefine)


-- | Data definition for amounts.
data Amount (precision :: Nat) = Amount
  { Amount precision -> Side
amountSide  :: !Side
  , Amount precision -> UnsignedQuantity precision
amountValue :: !(UnsignedQuantity precision)
  }
  deriving (Amount precision -> Amount precision -> Bool
(Amount precision -> Amount precision -> Bool)
-> (Amount precision -> Amount precision -> Bool)
-> Eq (Amount precision)
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall (precision :: Nat).
Amount precision -> Amount precision -> Bool
/= :: Amount precision -> Amount precision -> Bool
$c/= :: forall (precision :: Nat).
Amount precision -> Amount precision -> Bool
== :: Amount precision -> Amount precision -> Bool
$c== :: forall (precision :: Nat).
Amount precision -> Amount precision -> Bool
Eq, (forall x. Amount precision -> Rep (Amount precision) x)
-> (forall x. Rep (Amount precision) x -> Amount precision)
-> Generic (Amount precision)
forall x. Rep (Amount precision) x -> Amount precision
forall x. Amount precision -> Rep (Amount precision) x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall (precision :: Nat) x.
Rep (Amount precision) x -> Amount precision
forall (precision :: Nat) x.
Amount precision -> Rep (Amount precision) x
$cto :: forall (precision :: Nat) x.
Rep (Amount precision) x -> Amount precision
$cfrom :: forall (precision :: Nat) x.
Amount precision -> Rep (Amount precision) x
Generic, Eq (Amount precision)
Eq (Amount precision)
-> (Amount precision -> Amount precision -> Ordering)
-> (Amount precision -> Amount precision -> Bool)
-> (Amount precision -> Amount precision -> Bool)
-> (Amount precision -> Amount precision -> Bool)
-> (Amount precision -> Amount precision -> Bool)
-> (Amount precision -> Amount precision -> Amount precision)
-> (Amount precision -> Amount precision -> Amount precision)
-> Ord (Amount precision)
Amount precision -> Amount precision -> Bool
Amount precision -> Amount precision -> Ordering
Amount precision -> Amount precision -> Amount precision
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
forall (precision :: Nat). Eq (Amount precision)
forall (precision :: Nat).
Amount precision -> Amount precision -> Bool
forall (precision :: Nat).
Amount precision -> Amount precision -> Ordering
forall (precision :: Nat).
Amount precision -> Amount precision -> Amount precision
min :: Amount precision -> Amount precision -> Amount precision
$cmin :: forall (precision :: Nat).
Amount precision -> Amount precision -> Amount precision
max :: Amount precision -> Amount precision -> Amount precision
$cmax :: forall (precision :: Nat).
Amount precision -> Amount precision -> Amount precision
>= :: Amount precision -> Amount precision -> Bool
$c>= :: forall (precision :: Nat).
Amount precision -> Amount precision -> Bool
> :: Amount precision -> Amount precision -> Bool
$c> :: forall (precision :: Nat).
Amount precision -> Amount precision -> Bool
<= :: Amount precision -> Amount precision -> Bool
$c<= :: forall (precision :: Nat).
Amount precision -> Amount precision -> Bool
< :: Amount precision -> Amount precision -> Bool
$c< :: forall (precision :: Nat).
Amount precision -> Amount precision -> Bool
compare :: Amount precision -> Amount precision -> Ordering
$ccompare :: forall (precision :: Nat).
Amount precision -> Amount precision -> Ordering
$cp1Ord :: forall (precision :: Nat). Eq (Amount precision)
Ord, Int -> Amount precision -> ShowS
[Amount precision] -> ShowS
Amount precision -> String
(Int -> Amount precision -> ShowS)
-> (Amount precision -> String)
-> ([Amount precision] -> ShowS)
-> Show (Amount precision)
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall (precision :: Nat).
KnownNat precision =>
Int -> Amount precision -> ShowS
forall (precision :: Nat).
KnownNat precision =>
[Amount precision] -> ShowS
forall (precision :: Nat).
KnownNat precision =>
Amount precision -> String
showList :: [Amount precision] -> ShowS
$cshowList :: forall (precision :: Nat).
KnownNat precision =>
[Amount precision] -> ShowS
show :: Amount precision -> String
$cshow :: forall (precision :: Nat).
KnownNat precision =>
Amount precision -> String
showsPrec :: Int -> Amount precision -> ShowS
$cshowsPrec :: forall (precision :: Nat).
KnownNat precision =>
Int -> Amount precision -> ShowS
Show)


-- | 'Aeson.FromJSON' instance for 'Amount'.
--
-- >>> Aeson.eitherDecode "{\"side\": \"db\", \"value\": 42}" :: Either String (Amount 2)
-- Right (Amount {amountSide = SideDebit, amountValue = Refined 42.00})
-- >>> Aeson.eitherDecode "{\"side\": \"cr\", \"value\": 42}" :: Either String (Amount 2)
-- Right (Amount {amountSide = SideCredit, amountValue = Refined 42.00})
instance KnownNat precision => Aeson.FromJSON (Amount precision) where
  parseJSON :: Value -> Parser (Amount precision)
parseJSON = Options -> Value -> Parser (Amount precision)
forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
Aeson.genericParseJSON (Options -> Value -> Parser (Amount precision))
-> Options -> Value -> Parser (Amount precision)
forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"amount"


-- | 'Aeson.ToJSON' instance for 'Amount'.
--
-- >>> import Haspara.Accounting.Side
-- >>> import Haspara.Quantity
-- >>> import Refined.Unsafe
-- >>> Aeson.encode (Amount SideDebit (unsafeRefine (mkQuantity 42 :: Quantity 2)))
-- "{\"side\":\"db\",\"value\":42.0}"
-- >>> Aeson.encode (Amount SideCredit (unsafeRefine (mkQuantity 42 :: Quantity 2)))
-- "{\"side\":\"cr\",\"value\":42.0}"
-- >>> Aeson.eitherDecode (Aeson.encode (Amount SideDebit (unsafeRefine (mkQuantity 42 :: Quantity 2)))) :: Either String (Amount 2)
-- Right (Amount {amountSide = SideDebit, amountValue = Refined 42.00})
-- >>> Aeson.eitherDecode (Aeson.encode (Amount SideCredit (unsafeRefine (mkQuantity 42 :: Quantity 2)))) :: Either String (Amount 2)
-- Right (Amount {amountSide = SideCredit, amountValue = Refined 42.00})
instance KnownNat precision => Aeson.ToJSON (Amount precision) where
  toJSON :: Amount precision -> Value
toJSON = Options -> Amount precision -> Value
forall a.
(Generic a, GToJSON' Value Zero (Rep a)) =>
Options -> a -> Value
Aeson.genericToJSON (Options -> Amount precision -> Value)
-> Options -> Amount precision -> Value
forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"amount"


-- | Returns the debit value of the 'Amount', if any.
amountDebit :: KnownNat precision => Amount precision -> Maybe (UnsignedQuantity precision)
amountDebit :: Amount precision -> Maybe (UnsignedQuantity precision)
amountDebit (Amount Side
SideDebit UnsignedQuantity precision
value) = UnsignedQuantity precision -> Maybe (UnsignedQuantity precision)
forall a. a -> Maybe a
Just UnsignedQuantity precision
value
amountDebit Amount precision
_                        = Maybe (UnsignedQuantity precision)
forall a. Maybe a
Nothing


-- | Returns the credit value of the 'Amount', if any.
amountCredit :: KnownNat precision => Amount precision -> Maybe (UnsignedQuantity precision)
amountCredit :: Amount precision -> Maybe (UnsignedQuantity precision)
amountCredit (Amount Side
SideCredit UnsignedQuantity precision
value) = UnsignedQuantity precision -> Maybe (UnsignedQuantity precision)
forall a. a -> Maybe a
Just UnsignedQuantity precision
value
amountCredit Amount precision
_                         = Maybe (UnsignedQuantity precision)
forall a. Maybe a
Nothing


-- | Builds the 'Amount' for the given /value/ for the given 'AccountKind'.
--
-- The /value/ concept here refers to the value of a particular economic event
-- as in the contribution of that event to the net-worth of the entity.
--
-- This definition of the value is different than what we refer to in
-- 'amountFromQuantity'. In 'amountFromQuantity' the /quantity/ is simply
-- reflecting the increment or decrement in a particular account of a particular
-- 'AccountKind'.
--
-- For example, consider getting a loan: There are two immediate events due to
-- this exchange:
--
-- 1. Inflow of cash of some quantity to an 'AccountKindAsset' account.
-- 2. Inflow of loan contract with some notional value of the same quantity to a
--    'AccountKindLiability acount.
--
-- Let's say, the notional is USD 1,000. Therefore:
--
-- 1. Inflow of USD 1,000 to the cash account.
-- 2. Inflow of a Loan Contract of USD 1,000 to the liability account.
--
-- Conventionally, the latter is reflected as follow:
--
-- >>> import Haspara.Quantity
-- >>> amountFromQuantity AccountKindLiability (mkQuantity 1000 :: Quantity 2)
-- Amount {amountSide = SideCredit, amountValue = Refined 1000.00}
--
-- However, if the call-site is referring to values as in the net effect of the
-- event to the net-worth of the entity, then:
--
-- >>> amountFromValue AccountKindLiability (mkQuantity (-1000) :: Quantity 2)
-- Amount {amountSide = SideCredit, amountValue = Refined 1000.00}
--
-- For reference, given:
--
-- >>> let valPos = mkQuantity 42 :: Quantity 2
-- >>> let valNeg = mkQuantity (-42) :: Quantity 2
--
-- ..., let's consider following events:
--
-- We have an inflow and outflow of some assets, respectively:
--
-- >>> amountFromValue AccountKindAsset valPos
-- Amount {amountSide = SideDebit, amountValue = Refined 42.00}
-- >>> amountFromValue AccountKindAsset valNeg
-- Amount {amountSide = SideCredit, amountValue = Refined 42.00}
--
-- We have some decrease and increase in our liabilities, respectively:
--
-- >>> amountFromValue AccountKindLiability valPos
-- Amount {amountSide = SideDebit, amountValue = Refined 42.00}
-- >>> amountFromValue AccountKindLiability valNeg
-- Amount {amountSide = SideCredit, amountValue = Refined 42.00}
--
-- We have some increase and decrease in our equity, respectively:
--
-- >>> amountFromValue AccountKindEquity valPos
-- Amount {amountSide = SideCredit, amountValue = Refined 42.00}
-- >>> amountFromValue AccountKindEquity valNeg
-- Amount {amountSide = SideDebit, amountValue = Refined 42.00}
--
-- We have some profit and loss in our PnL, respectively:
--
-- >>> amountFromValue AccountKindRevenue valPos
-- Amount {amountSide = SideCredit, amountValue = Refined 42.00}
-- >>> amountFromValue AccountKindRevenue valNeg
-- Amount {amountSide = SideDebit, amountValue = Refined 42.00}
--
-- We have some decrease and increase in our expenses, respectively:
--
-- >>> amountFromValue AccountKindExpense valPos
-- Amount {amountSide = SideCredit, amountValue = Refined 42.00}
-- >>> amountFromValue AccountKindExpense valNeg
-- Amount {amountSide = SideDebit, amountValue = Refined 42.00}
amountFromValue
  :: KnownNat precision
  => AccountKind
  -> Quantity precision
  -> Amount precision
amountFromValue :: AccountKind -> Quantity precision -> Amount precision
amountFromValue AccountKind
k Quantity precision
q = case AccountKind
k of
  AccountKind
AccountKindAsset     -> Amount :: forall (precision :: Nat).
Side -> UnsignedQuantity precision -> Amount precision
Amount { amountSide :: Side
amountSide = if Quantity precision
q Quantity precision -> Quantity precision -> Bool
forall a. Ord a => a -> a -> Bool
>= Quantity precision
0 then Side
SideDebit else Side
SideCredit, amountValue :: UnsignedQuantity precision
amountValue = Quantity precision -> UnsignedQuantity precision
forall (s :: Nat). KnownNat s => Quantity s -> UnsignedQuantity s
absQuantity Quantity precision
q }
  AccountKind
AccountKindLiability -> Amount :: forall (precision :: Nat).
Side -> UnsignedQuantity precision -> Amount precision
Amount { amountSide :: Side
amountSide = if Quantity precision
q Quantity precision -> Quantity precision -> Bool
forall a. Ord a => a -> a -> Bool
>= Quantity precision
0 then Side
SideDebit else Side
SideCredit, amountValue :: UnsignedQuantity precision
amountValue = Quantity precision -> UnsignedQuantity precision
forall (s :: Nat). KnownNat s => Quantity s -> UnsignedQuantity s
absQuantity Quantity precision
q }
  AccountKind
AccountKindEquity    -> Amount :: forall (precision :: Nat).
Side -> UnsignedQuantity precision -> Amount precision
Amount { amountSide :: Side
amountSide = if Quantity precision
q Quantity precision -> Quantity precision -> Bool
forall a. Ord a => a -> a -> Bool
>= Quantity precision
0 then Side
SideCredit else Side
SideDebit, amountValue :: UnsignedQuantity precision
amountValue = Quantity precision -> UnsignedQuantity precision
forall (s :: Nat). KnownNat s => Quantity s -> UnsignedQuantity s
absQuantity Quantity precision
q }
  AccountKind
AccountKindRevenue   -> Amount :: forall (precision :: Nat).
Side -> UnsignedQuantity precision -> Amount precision
Amount { amountSide :: Side
amountSide = if Quantity precision
q Quantity precision -> Quantity precision -> Bool
forall a. Ord a => a -> a -> Bool
>= Quantity precision
0 then Side
SideCredit else Side
SideDebit, amountValue :: UnsignedQuantity precision
amountValue = Quantity precision -> UnsignedQuantity precision
forall (s :: Nat). KnownNat s => Quantity s -> UnsignedQuantity s
absQuantity Quantity precision
q }
  AccountKind
AccountKindExpense   -> Amount :: forall (precision :: Nat).
Side -> UnsignedQuantity precision -> Amount precision
Amount { amountSide :: Side
amountSide = if Quantity precision
q Quantity precision -> Quantity precision -> Bool
forall a. Ord a => a -> a -> Bool
>= Quantity precision
0 then Side
SideCredit else Side
SideDebit, amountValue :: UnsignedQuantity precision
amountValue = Quantity precision -> UnsignedQuantity precision
forall (s :: Nat). KnownNat s => Quantity s -> UnsignedQuantity s
absQuantity Quantity precision
q }


-- | Returns the value for the given 'Amount' for the given 'AccountKind'.
--
-- This is dual to 'amountFromValue'.
--
-- For values of positive and negative net-effect on the net-worth of the
-- entity, respectively:
--
-- >>> import Haspara.Quantity
-- >>> let valPos = mkQuantity 42 :: Quantity 2
-- >>> let valNeg = mkQuantity (-42) :: Quantity 2
--
-- ..., for a @check@ function that checks if the roundtrip to a value is
-- successful for a given 'AccountKind':
--
-- >>> let check = \k v -> v == valueFromAmount k (amountFromValue k v)
--
-- ..., and for the list of 'AccountKind's.
--
-- >>> let kinds = [minBound .. maxBound] :: [AccountKind]
-- >>> kinds
-- [AccountKindAsset,AccountKindLiability,AccountKindEquity,AccountKindRevenue,AccountKindExpense]
--
-- All checks should pass:
--
-- >>> all (\k -> check k valPos && check k valNeg) kinds
-- True
valueFromAmount
  :: KnownNat precision
  => AccountKind
  -> Amount precision
  -> Quantity precision
valueFromAmount :: AccountKind -> Amount precision -> Quantity precision
valueFromAmount AccountKind
k (Amount Side
s UnsignedQuantity precision
v) = case (AccountKind
k, Side
s, UnsignedQuantity precision -> Quantity precision
forall p x. Refined p x -> x
unrefine UnsignedQuantity precision
v) of
  (AccountKind
AccountKindAsset, Side
SideDebit, Quantity precision
q)      -> Quantity precision
q
  (AccountKind
AccountKindAsset, Side
SideCredit, Quantity precision
q)     -> -Quantity precision
q
  (AccountKind
AccountKindLiability, Side
SideDebit, Quantity precision
q)  -> Quantity precision
q
  (AccountKind
AccountKindLiability, Side
SideCredit, Quantity precision
q) -> -Quantity precision
q
  (AccountKind
AccountKindEquity, Side
SideDebit, Quantity precision
q)     -> -Quantity precision
q
  (AccountKind
AccountKindEquity, Side
SideCredit, Quantity precision
q)    -> Quantity precision
q
  (AccountKind
AccountKindRevenue, Side
SideDebit, Quantity precision
q)    -> -Quantity precision
q
  (AccountKind
AccountKindRevenue, Side
SideCredit, Quantity precision
q)   -> Quantity precision
q
  (AccountKind
AccountKindExpense, Side
SideDebit, Quantity precision
q)    -> -Quantity precision
q
  (AccountKind
AccountKindExpense, Side
SideCredit, Quantity precision
q)   -> Quantity precision
q


-- | Builds the 'Amount' value for the given account kind and quantity.
--
-- The concept of /quantity/ here refers to the conventional concept of what it
-- means for an 'Haspara.Accounting.Account.Account' of a given 'AccountKind'.
--
-- For example, a loan of USD 1,000 has an increase in our liabilities.
-- Therefore, the quantity is expected to be positive:
--
-- >>> import Haspara.Quantity
-- >>> amountFromQuantity AccountKindLiability (mkQuantity 1000 :: Quantity 2)
-- Amount {amountSide = SideCredit, amountValue = Refined 1000.00}
--
-- Note 'amountFromValue' function if you are rather working with values that
-- are conceptually different than the /quantity/ here whereby a /value/ refers
-- to the value of a particular economic event as in the contribution of that
-- event to the net-worth of the entity. Therefore, above example would be
-- reflected as follows to get the same 'Amount' value:
--
-- >>> amountFromValue AccountKindLiability (mkQuantity (-1000) :: Quantity 2)
-- Amount {amountSide = SideCredit, amountValue = Refined 1000.00}
--
-- Check 'amountFromValue' documentation for further information.
amountFromQuantity
  :: KnownNat precision
  => AccountKind
  -> Quantity precision
  -> Amount precision
amountFromQuantity :: AccountKind -> Quantity precision -> Amount precision
amountFromQuantity AccountKind
k Quantity precision
q =
  Amount :: forall (precision :: Nat).
Side -> UnsignedQuantity precision -> Amount precision
Amount
    { amountSide :: Side
amountSide = AccountKind -> Quantity precision -> Side
forall (precision :: Nat).
KnownNat precision =>
AccountKind -> Quantity precision -> Side
sideByAccountKind AccountKind
k Quantity precision
q
    , amountValue :: UnsignedQuantity precision
amountValue = Quantity precision -> UnsignedQuantity precision
forall (s :: Nat). KnownNat s => Quantity s -> UnsignedQuantity s
absQuantity Quantity precision
q
    }


-- | Returns the quantity for the given amount.
--
-- This is dual to 'amountFromQuantity'.
quantityFromAmount
  :: KnownNat precision
  => AccountKind
  -> Amount precision
  -> Quantity precision
quantityFromAmount :: AccountKind -> Amount precision -> Quantity precision
quantityFromAmount AccountKind
k (Amount Side
side UnsignedQuantity precision
absValue) =
  let
    value :: Quantity precision
value = UnsignedQuantity precision -> Quantity precision
forall p x. Refined p x -> x
unrefine UnsignedQuantity precision
absValue
  in
    case (AccountKind
k, Side
side) of
      (AccountKind
AccountKindAsset, Side
SideDebit)      -> Quantity precision
value
      (AccountKind
AccountKindAsset, Side
SideCredit)     -> -Quantity precision
value
      (AccountKind
AccountKindLiability, Side
SideDebit)  -> -Quantity precision
value
      (AccountKind
AccountKindLiability, Side
SideCredit) -> Quantity precision
value
      (AccountKind
AccountKindEquity, Side
SideDebit)     -> -Quantity precision
value
      (AccountKind
AccountKindEquity, Side
SideCredit)    -> Quantity precision
value
      (AccountKind
AccountKindRevenue, Side
SideDebit)    -> -Quantity precision
value
      (AccountKind
AccountKindRevenue, Side
SideCredit)   -> Quantity precision
value
      (AccountKind
AccountKindExpense, Side
SideDebit)    -> Quantity precision
value
      (AccountKind
AccountKindExpense, Side
SideCredit)   -> -Quantity precision
value