-- | This module provides data definitions and functions to work with journal
-- entries.

{-# LANGUAGE DataKinds #-}

module Haspara.Accounting.Journal where

import qualified Data.Text                  as T
import           Data.Time                  (Day)
import           GHC.Generics               (Generic)
import           GHC.TypeLits               (KnownNat, Nat)
import           Haspara.Accounting.Account (Account(accountKind))
import           Haspara.Accounting.Amount  (Amount(..), amountFromQuantity, amountFromValue)
import           Haspara.Accounting.Side    (Side(..))
import           Haspara.Quantity           (Quantity, UnsignedQuantity, sumUnsignedQuantity)


-- | Data definition for the journal entries of interest (like a general
-- ledger.)
--
-- A 'Journal' is a list of 'JournalEntry' records which are polymorphic over
-- the precision of the monetary quantities, the account and event objects.
newtype Journal (precision :: Nat) account event = Journal
  { Journal precision account event
-> [JournalEntry precision account event]
journalEntries :: [JournalEntry precision account event]
  }
  deriving (Int -> Journal precision account event -> ShowS
[Journal precision account event] -> ShowS
Journal precision account event -> String
(Int -> Journal precision account event -> ShowS)
-> (Journal precision account event -> String)
-> ([Journal precision account event] -> ShowS)
-> Show (Journal precision account event)
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
Int -> Journal precision account event -> ShowS
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
[Journal precision account event] -> ShowS
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
Journal precision account event -> String
showList :: [Journal precision account event] -> ShowS
$cshowList :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
[Journal precision account event] -> ShowS
show :: Journal precision account event -> String
$cshow :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
Journal precision account event -> String
showsPrec :: Int -> Journal precision account event -> ShowS
$cshowsPrec :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
Int -> Journal precision account event -> ShowS
Show)


-- | Data definition for a journal entry.
--
-- A journal entry has a (unique) identifier, date and description, and a list
-- of 'JournalEntryItem's. Journal entry definition is polymorphic over the
-- precision of the monetary quantities, the account and event objects.
data JournalEntry (precision :: Nat) account event = JournalEntry
  { JournalEntry precision account event -> Text
journalEntryId          :: !T.Text
  , JournalEntry precision account event -> Day
journalEntryDate        :: !Day
  , JournalEntry precision account event
-> [JournalEntryItem precision account event]
journalEntryItems       :: ![JournalEntryItem precision account event]
  , JournalEntry precision account event -> Text
journalEntryDescription :: !T.Text
  }
  deriving (Int -> JournalEntry precision account event -> ShowS
[JournalEntry precision account event] -> ShowS
JournalEntry precision account event -> String
(Int -> JournalEntry precision account event -> ShowS)
-> (JournalEntry precision account event -> String)
-> ([JournalEntry precision account event] -> ShowS)
-> Show (JournalEntry precision account event)
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
Int -> JournalEntry precision account event -> ShowS
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
[JournalEntry precision account event] -> ShowS
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
JournalEntry precision account event -> String
showList :: [JournalEntry precision account event] -> ShowS
$cshowList :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
[JournalEntry precision account event] -> ShowS
show :: JournalEntry precision account event -> String
$cshow :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
JournalEntry precision account event -> String
showsPrec :: Int -> JournalEntry precision account event -> ShowS
$cshowsPrec :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
Int -> JournalEntry precision account event -> ShowS
Show)


-- | Returns the total debit amount of a journal entry.
journalEntryTotalDebit
  :: KnownNat precision
  => JournalEntry precision account event
  -> UnsignedQuantity precision
journalEntryTotalDebit :: JournalEntry precision account event -> UnsignedQuantity precision
journalEntryTotalDebit =
    [UnsignedQuantity precision] -> UnsignedQuantity precision
forall (s :: Nat).
KnownNat s =>
[UnsignedQuantity s] -> UnsignedQuantity s
sumUnsignedQuantity
  ([UnsignedQuantity precision] -> UnsignedQuantity precision)
-> (JournalEntry precision account event
    -> [UnsignedQuantity precision])
-> JournalEntry precision account event
-> UnsignedQuantity precision
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (JournalEntryItem precision account event
 -> UnsignedQuantity precision)
-> [JournalEntryItem precision account event]
-> [UnsignedQuantity precision]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Amount precision -> UnsignedQuantity precision
forall (precision :: Nat).
Amount precision -> UnsignedQuantity precision
amountValue (Amount precision -> UnsignedQuantity precision)
-> (JournalEntryItem precision account event -> Amount precision)
-> JournalEntryItem precision account event
-> UnsignedQuantity precision
forall b c a. (b -> c) -> (a -> b) -> a -> c
. JournalEntryItem precision account event -> Amount precision
forall (precision :: Nat) account event.
JournalEntryItem precision account event -> Amount precision
journalEntryItemAmount)
  ([JournalEntryItem precision account event]
 -> [UnsignedQuantity precision])
-> (JournalEntry precision account event
    -> [JournalEntryItem precision account event])
-> JournalEntry precision account event
-> [UnsignedQuantity precision]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (JournalEntryItem precision account event -> Bool)
-> [JournalEntryItem precision account event]
-> [JournalEntryItem precision account event]
forall a. (a -> Bool) -> [a] -> [a]
filter (Side -> Side -> Bool
forall a. Eq a => a -> a -> Bool
(==) Side
SideDebit (Side -> Bool)
-> (JournalEntryItem precision account event -> Side)
-> JournalEntryItem precision account event
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Amount precision -> Side
forall (precision :: Nat). Amount precision -> Side
amountSide (Amount precision -> Side)
-> (JournalEntryItem precision account event -> Amount precision)
-> JournalEntryItem precision account event
-> Side
forall b c a. (b -> c) -> (a -> b) -> a -> c
. JournalEntryItem precision account event -> Amount precision
forall (precision :: Nat) account event.
JournalEntryItem precision account event -> Amount precision
journalEntryItemAmount)
  ([JournalEntryItem precision account event]
 -> [JournalEntryItem precision account event])
-> (JournalEntry precision account event
    -> [JournalEntryItem precision account event])
-> JournalEntry precision account event
-> [JournalEntryItem precision account event]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. JournalEntry precision account event
-> [JournalEntryItem precision account event]
forall (precision :: Nat) account event.
JournalEntry precision account event
-> [JournalEntryItem precision account event]
journalEntryItems


-- | Returns the total credit amount of a journal entry.
journalEntryTotalCredit
  :: KnownNat precision
  => JournalEntry precision account event
  -> UnsignedQuantity precision
journalEntryTotalCredit :: JournalEntry precision account event -> UnsignedQuantity precision
journalEntryTotalCredit =
    [UnsignedQuantity precision] -> UnsignedQuantity precision
forall (s :: Nat).
KnownNat s =>
[UnsignedQuantity s] -> UnsignedQuantity s
sumUnsignedQuantity
  ([UnsignedQuantity precision] -> UnsignedQuantity precision)
-> (JournalEntry precision account event
    -> [UnsignedQuantity precision])
-> JournalEntry precision account event
-> UnsignedQuantity precision
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (JournalEntryItem precision account event
 -> UnsignedQuantity precision)
-> [JournalEntryItem precision account event]
-> [UnsignedQuantity precision]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Amount precision -> UnsignedQuantity precision
forall (precision :: Nat).
Amount precision -> UnsignedQuantity precision
amountValue (Amount precision -> UnsignedQuantity precision)
-> (JournalEntryItem precision account event -> Amount precision)
-> JournalEntryItem precision account event
-> UnsignedQuantity precision
forall b c a. (b -> c) -> (a -> b) -> a -> c
. JournalEntryItem precision account event -> Amount precision
forall (precision :: Nat) account event.
JournalEntryItem precision account event -> Amount precision
journalEntryItemAmount)
  ([JournalEntryItem precision account event]
 -> [UnsignedQuantity precision])
-> (JournalEntry precision account event
    -> [JournalEntryItem precision account event])
-> JournalEntry precision account event
-> [UnsignedQuantity precision]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (JournalEntryItem precision account event -> Bool)
-> [JournalEntryItem precision account event]
-> [JournalEntryItem precision account event]
forall a. (a -> Bool) -> [a] -> [a]
filter (Side -> Side -> Bool
forall a. Eq a => a -> a -> Bool
(==) Side
SideCredit (Side -> Bool)
-> (JournalEntryItem precision account event -> Side)
-> JournalEntryItem precision account event
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Amount precision -> Side
forall (precision :: Nat). Amount precision -> Side
amountSide (Amount precision -> Side)
-> (JournalEntryItem precision account event -> Amount precision)
-> JournalEntryItem precision account event
-> Side
forall b c a. (b -> c) -> (a -> b) -> a -> c
. JournalEntryItem precision account event -> Amount precision
forall (precision :: Nat) account event.
JournalEntryItem precision account event -> Amount precision
journalEntryItemAmount)
  ([JournalEntryItem precision account event]
 -> [JournalEntryItem precision account event])
-> (JournalEntry precision account event
    -> [JournalEntryItem precision account event])
-> JournalEntry precision account event
-> [JournalEntryItem precision account event]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. JournalEntry precision account event
-> [JournalEntryItem precision account event]
forall (precision :: Nat) account event.
JournalEntry precision account event
-> [JournalEntryItem precision account event]
journalEntryItems


-- | Predicate to check if a journal entry is balanced or not.
--
-- The logical check is indeed whether the total debit amount is equal to the
-- total credit amount or not.
isJournalEntryBalanced
  :: KnownNat precision
  => JournalEntry precision account event
  -> Bool
isJournalEntryBalanced :: JournalEntry precision account event -> Bool
isJournalEntryBalanced = UnsignedQuantity precision -> UnsignedQuantity precision -> Bool
forall a. Eq a => a -> a -> Bool
(==)
  (UnsignedQuantity precision -> UnsignedQuantity precision -> Bool)
-> (JournalEntry precision account event
    -> UnsignedQuantity precision)
-> JournalEntry precision account event
-> UnsignedQuantity precision
-> Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> JournalEntry precision account event -> UnsignedQuantity precision
forall (precision :: Nat) account event.
KnownNat precision =>
JournalEntry precision account event -> UnsignedQuantity precision
journalEntryTotalDebit
  (JournalEntry precision account event
 -> UnsignedQuantity precision -> Bool)
-> (JournalEntry precision account event
    -> UnsignedQuantity precision)
-> JournalEntry precision account event
-> Bool
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> JournalEntry precision account event -> UnsignedQuantity precision
forall (precision :: Nat) account event.
KnownNat precision =>
JournalEntry precision account event -> UnsignedQuantity precision
journalEntryTotalCredit


-- | Data definition for a journal entry item.
--
-- A journal entry item has a 'Side', an unsigned quantity as amount, an account
-- that it belongs to and the event the item is originating from. Journal entry
-- item definition is polymorphic over the precision of the monetary quantities,
-- the account and event objects.
data JournalEntryItem (precision :: Nat) account event = JournalEntryItem
  { JournalEntryItem precision account event -> Amount precision
journalEntryItemAmount  :: !(Amount precision)
  , JournalEntryItem precision account event -> Account account
journalEntryItemAccount :: !(Account account)
  , JournalEntryItem precision account event -> event
journalEntryItemEvent   :: !event
  }
  deriving (JournalEntryItem precision account event
-> JournalEntryItem precision account event -> Bool
(JournalEntryItem precision account event
 -> JournalEntryItem precision account event -> Bool)
-> (JournalEntryItem precision account event
    -> JournalEntryItem precision account event -> Bool)
-> Eq (JournalEntryItem precision account event)
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall (precision :: Nat) account event.
(Eq account, Eq event) =>
JournalEntryItem precision account event
-> JournalEntryItem precision account event -> Bool
/= :: JournalEntryItem precision account event
-> JournalEntryItem precision account event -> Bool
$c/= :: forall (precision :: Nat) account event.
(Eq account, Eq event) =>
JournalEntryItem precision account event
-> JournalEntryItem precision account event -> Bool
== :: JournalEntryItem precision account event
-> JournalEntryItem precision account event -> Bool
$c== :: forall (precision :: Nat) account event.
(Eq account, Eq event) =>
JournalEntryItem precision account event
-> JournalEntryItem precision account event -> Bool
Eq, (forall x.
 JournalEntryItem precision account event
 -> Rep (JournalEntryItem precision account event) x)
-> (forall x.
    Rep (JournalEntryItem precision account event) x
    -> JournalEntryItem precision account event)
-> Generic (JournalEntryItem precision account event)
forall x.
Rep (JournalEntryItem precision account event) x
-> JournalEntryItem precision account event
forall x.
JournalEntryItem precision account event
-> Rep (JournalEntryItem precision account event) x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall (precision :: Nat) account event x.
Rep (JournalEntryItem precision account event) x
-> JournalEntryItem precision account event
forall (precision :: Nat) account event x.
JournalEntryItem precision account event
-> Rep (JournalEntryItem precision account event) x
$cto :: forall (precision :: Nat) account event x.
Rep (JournalEntryItem precision account event) x
-> JournalEntryItem precision account event
$cfrom :: forall (precision :: Nat) account event x.
JournalEntryItem precision account event
-> Rep (JournalEntryItem precision account event) x
Generic, Int -> JournalEntryItem precision account event -> ShowS
[JournalEntryItem precision account event] -> ShowS
JournalEntryItem precision account event -> String
(Int -> JournalEntryItem precision account event -> ShowS)
-> (JournalEntryItem precision account event -> String)
-> ([JournalEntryItem precision account event] -> ShowS)
-> Show (JournalEntryItem precision account event)
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
Int -> JournalEntryItem precision account event -> ShowS
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
[JournalEntryItem precision account event] -> ShowS
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
JournalEntryItem precision account event -> String
showList :: [JournalEntryItem precision account event] -> ShowS
$cshowList :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
[JournalEntryItem precision account event] -> ShowS
show :: JournalEntryItem precision account event -> String
$cshow :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
JournalEntryItem precision account event -> String
showsPrec :: Int -> JournalEntryItem precision account event -> ShowS
$cshowsPrec :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
Int -> JournalEntryItem precision account event -> ShowS
Show)


-- | Creates a 'JournalEntryItem' from the given signed /quantity/, the account
-- it belongs to and the event it is originating from.
--
-- The /quantity/ is defined as in 'amountFromQuantity' function.
mkJournalEntryItemFromQuantity
  :: KnownNat precision
  => Quantity precision
  -> Account account
  -> event
  -> JournalEntryItem precision account event
mkJournalEntryItemFromQuantity :: Quantity precision
-> Account account
-> event
-> JournalEntryItem precision account event
mkJournalEntryItemFromQuantity Quantity precision
qty Account account
acc event
evt =
  JournalEntryItem :: forall (precision :: Nat) account event.
Amount precision
-> Account account
-> event
-> JournalEntryItem precision account event
JournalEntryItem
    { journalEntryItemAmount :: Amount precision
journalEntryItemAmount = AccountKind -> Quantity precision -> Amount precision
forall (precision :: Nat).
KnownNat precision =>
AccountKind -> Quantity precision -> Amount precision
amountFromQuantity (Account account -> AccountKind
forall o. Account o -> AccountKind
accountKind Account account
acc) Quantity precision
qty
    , journalEntryItemAccount :: Account account
journalEntryItemAccount = Account account
acc
    , journalEntryItemEvent :: event
journalEntryItemEvent = event
evt
    }


-- | Creates a 'JournalEntryItem' from the given signed /value/, the account it
-- belongs to and the event it is originating from.
--
-- The /value/ is defined as in 'amountFromValue' function.
mkJournalEntryItemFromValue
  :: KnownNat precision
  => Quantity precision
  -> Account account
  -> event
  -> JournalEntryItem precision account event
mkJournalEntryItemFromValue :: Quantity precision
-> Account account
-> event
-> JournalEntryItem precision account event
mkJournalEntryItemFromValue Quantity precision
val Account account
acc event
evt =
  JournalEntryItem :: forall (precision :: Nat) account event.
Amount precision
-> Account account
-> event
-> JournalEntryItem precision account event
JournalEntryItem
    { journalEntryItemAmount :: Amount precision
journalEntryItemAmount = AccountKind -> Quantity precision -> Amount precision
forall (precision :: Nat).
KnownNat precision =>
AccountKind -> Quantity precision -> Amount precision
amountFromValue (Account account -> AccountKind
forall o. Account o -> AccountKind
accountKind Account account
acc) Quantity precision
val
    , journalEntryItemAccount :: Account account
journalEntryItemAccount = Account account
acc
    , journalEntryItemEvent :: event
journalEntryItemEvent = event
evt
    }