{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE KindSignatures #-}

-- | This module provides data definitions and functions to work with journal
-- entries.
module Haspara.Accounting.Journal where

import qualified Data.Aeson as Aeson
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 (..), amountFromValue)
import Haspara.Accounting.Side (Side (..))
import Haspara.Internal.Aeson (commonAesonOptions)
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
  { forall (precision :: Nat) account event.
Journal precision account event
-> [JournalEntry precision account event]
journalEntries :: [JournalEntry precision account event]
  }
  deriving (forall (precision :: Nat) account event x.
Rep (Journal precision account event) x
-> Journal precision account event
forall (precision :: Nat) account event x.
Journal precision account event
-> Rep (Journal precision account event) x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall (precision :: Nat) account event x.
Rep (Journal precision account event) x
-> Journal precision account event
$cfrom :: forall (precision :: Nat) account event x.
Journal precision account event
-> Rep (Journal precision account event) x
Generic, Int -> Journal precision account event -> ShowS
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
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
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)


instance (KnownNat precision, Aeson.FromJSON account, Aeson.FromJSON event) => Aeson.FromJSON (Journal precision account event) where
  parseJSON :: Value -> Parser (Journal precision account event)
parseJSON = forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
Aeson.genericParseJSON forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"journal"


instance (KnownNat precision, Aeson.ToJSON account, Aeson.ToJSON event) => Aeson.ToJSON (Journal precision account event) where
  toJSON :: Journal precision account event -> Value
toJSON = forall a.
(Generic a, GToJSON' Value Zero (Rep a)) =>
Options -> a -> Value
Aeson.genericToJSON forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"journal"
  toEncoding :: Journal precision account event -> Encoding
toEncoding = forall a.
(Generic a, GToJSON' Encoding Zero (Rep a)) =>
Options -> a -> Encoding
Aeson.genericToEncoding forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"journal"


-- | 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
  { forall (precision :: Nat) account event.
JournalEntry precision account event -> Text
journalEntryId :: !T.Text
  , forall (precision :: Nat) account event.
JournalEntry precision account event -> Day
journalEntryDate :: !Day
  , forall (precision :: Nat) account event.
JournalEntry precision account event
-> [JournalEntryItem precision account event]
journalEntryItems :: ![JournalEntryItem precision account event]
  , forall (precision :: Nat) account event.
JournalEntry precision account event -> Text
journalEntryDescription :: !T.Text
  }
  deriving (forall (precision :: Nat) account event x.
Rep (JournalEntry precision account event) x
-> JournalEntry precision account event
forall (precision :: Nat) account event x.
JournalEntry precision account event
-> Rep (JournalEntry precision account event) x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall (precision :: Nat) account event x.
Rep (JournalEntry precision account event) x
-> JournalEntry precision account event
$cfrom :: forall (precision :: Nat) account event x.
JournalEntry precision account event
-> Rep (JournalEntry precision account event) x
Generic, Int -> JournalEntry precision account event -> ShowS
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
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
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)


instance (KnownNat precision, Aeson.FromJSON account, Aeson.FromJSON event) => Aeson.FromJSON (JournalEntry precision account event) where
  parseJSON :: Value -> Parser (JournalEntry precision account event)
parseJSON = forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
Aeson.genericParseJSON forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"journalEntry"


instance (KnownNat precision, Aeson.ToJSON account, Aeson.ToJSON event) => Aeson.ToJSON (JournalEntry precision account event) where
  toJSON :: JournalEntry precision account event -> Value
toJSON = forall a.
(Generic a, GToJSON' Value Zero (Rep a)) =>
Options -> a -> Value
Aeson.genericToJSON forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"journalEntry"
  toEncoding :: JournalEntry precision account event -> Encoding
toEncoding = forall a.
(Generic a, GToJSON' Encoding Zero (Rep a)) =>
Options -> a -> Encoding
Aeson.genericToEncoding forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"journalEntry"


-- | Returns the total debit amount of a journal entry.
journalEntryTotalDebit
  :: KnownNat precision
  => JournalEntry precision account event
  -> UnsignedQuantity precision
journalEntryTotalDebit :: forall (precision :: Nat) account event.
KnownNat precision =>
JournalEntry precision account event -> UnsignedQuantity precision
journalEntryTotalDebit =
  forall (s :: Nat).
KnownNat s =>
[UnsignedQuantity s] -> UnsignedQuantity s
sumUnsignedQuantity
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (precision :: Nat).
Amount precision -> UnsignedQuantity precision
amountValue forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (precision :: Nat) account event.
JournalEntryItem precision account event -> Amount precision
journalEntryItemAmount)
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
filter (forall a. Eq a => a -> a -> Bool
(==) Side
SideDebit forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (precision :: Nat). Amount precision -> Side
amountSide forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (precision :: Nat) account event.
JournalEntryItem precision account event -> Amount precision
journalEntryItemAmount)
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. 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 :: forall (precision :: Nat) account event.
KnownNat precision =>
JournalEntry precision account event -> UnsignedQuantity precision
journalEntryTotalCredit =
  forall (s :: Nat).
KnownNat s =>
[UnsignedQuantity s] -> UnsignedQuantity s
sumUnsignedQuantity
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (precision :: Nat).
Amount precision -> UnsignedQuantity precision
amountValue forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (precision :: Nat) account event.
JournalEntryItem precision account event -> Amount precision
journalEntryItemAmount)
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
filter (forall a. Eq a => a -> a -> Bool
(==) Side
SideCredit forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (precision :: Nat). Amount precision -> Side
amountSide forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (precision :: Nat) account event.
JournalEntryItem precision account event -> Amount precision
journalEntryItemAmount)
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. 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 :: forall (precision :: Nat) account event.
KnownNat precision =>
JournalEntry precision account event -> Bool
isJournalEntryBalanced =
  forall a. Eq a => a -> a -> Bool
(==)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (precision :: Nat) account event.
KnownNat precision =>
JournalEntry precision account event -> UnsignedQuantity precision
journalEntryTotalDebit
    forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> 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
  { forall (precision :: Nat) account event.
JournalEntryItem precision account event -> Amount precision
journalEntryItemAmount :: !(Amount precision)
  , forall (precision :: Nat) account event.
JournalEntryItem precision account event -> Account account
journalEntryItemAccount :: !(Account account)
  , forall (precision :: Nat) account event.
JournalEntryItem precision account event -> event
journalEntryItemEvent :: !event
  , forall (precision :: Nat) account event.
JournalEntryItem precision account event
-> Maybe (JournalEntryItemInventoryEvent account event)
journalEntryItemInventoryEvent :: !(Maybe (JournalEntryItemInventoryEvent account event))
  }
  deriving (JournalEntryItem precision account event
-> JournalEntryItem precision account event -> Bool
forall (precision :: Nat) account event.
(Eq account, Eq event) =>
JournalEntryItem precision account event
-> JournalEntryItem precision account event -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: 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 (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
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$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
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
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
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)


instance (KnownNat precision, Aeson.FromJSON account, Aeson.FromJSON event) => Aeson.FromJSON (JournalEntryItem precision account event) where
  parseJSON :: Value -> Parser (JournalEntryItem precision account event)
parseJSON = forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
Aeson.genericParseJSON forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"journalEntryItem"


instance (KnownNat precision, Aeson.ToJSON account, Aeson.ToJSON event) => Aeson.ToJSON (JournalEntryItem precision account event) where
  toJSON :: JournalEntryItem precision account event -> Value
toJSON = forall a.
(Generic a, GToJSON' Value Zero (Rep a)) =>
Options -> a -> Value
Aeson.genericToJSON forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"journalEntryItem"
  toEncoding :: JournalEntryItem precision account event -> Encoding
toEncoding = forall a.
(Generic a, GToJSON' Encoding Zero (Rep a)) =>
Options -> a -> Encoding
Aeson.genericToEncoding forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"journalEntryItem"


-- | Data definition for inventory event.
data JournalEntryItemInventoryEvent account event = JournalEntryItemInventoryEvent
  { forall account event.
JournalEntryItemInventoryEvent account event -> Account account
journalEntryItemInventoryEventPnlAccount :: !(Account account)
  , forall account event.
JournalEntryItemInventoryEvent account event -> event
journalEntryItemInventoryEventEvent :: !event
  , forall account event.
JournalEntryItemInventoryEvent account event -> Quantity 12
journalEntryItemInventoryEventQuantity :: !(Quantity 12)
  }
  deriving (JournalEntryItemInventoryEvent account event
-> JournalEntryItemInventoryEvent account event -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall account event.
(Eq account, Eq event) =>
JournalEntryItemInventoryEvent account event
-> JournalEntryItemInventoryEvent account event -> Bool
/= :: JournalEntryItemInventoryEvent account event
-> JournalEntryItemInventoryEvent account event -> Bool
$c/= :: forall account event.
(Eq account, Eq event) =>
JournalEntryItemInventoryEvent account event
-> JournalEntryItemInventoryEvent account event -> Bool
== :: JournalEntryItemInventoryEvent account event
-> JournalEntryItemInventoryEvent account event -> Bool
$c== :: forall account event.
(Eq account, Eq event) =>
JournalEntryItemInventoryEvent account event
-> JournalEntryItemInventoryEvent account event -> Bool
Eq, forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall account event x.
Rep (JournalEntryItemInventoryEvent account event) x
-> JournalEntryItemInventoryEvent account event
forall account event x.
JournalEntryItemInventoryEvent account event
-> Rep (JournalEntryItemInventoryEvent account event) x
$cto :: forall account event x.
Rep (JournalEntryItemInventoryEvent account event) x
-> JournalEntryItemInventoryEvent account event
$cfrom :: forall account event x.
JournalEntryItemInventoryEvent account event
-> Rep (JournalEntryItemInventoryEvent account event) x
Generic, Int -> JournalEntryItemInventoryEvent account event -> ShowS
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall account event.
(Show account, Show event) =>
Int -> JournalEntryItemInventoryEvent account event -> ShowS
forall account event.
(Show account, Show event) =>
[JournalEntryItemInventoryEvent account event] -> ShowS
forall account event.
(Show account, Show event) =>
JournalEntryItemInventoryEvent account event -> String
showList :: [JournalEntryItemInventoryEvent account event] -> ShowS
$cshowList :: forall account event.
(Show account, Show event) =>
[JournalEntryItemInventoryEvent account event] -> ShowS
show :: JournalEntryItemInventoryEvent account event -> String
$cshow :: forall account event.
(Show account, Show event) =>
JournalEntryItemInventoryEvent account event -> String
showsPrec :: Int -> JournalEntryItemInventoryEvent account event -> ShowS
$cshowsPrec :: forall account event.
(Show account, Show event) =>
Int -> JournalEntryItemInventoryEvent account event -> ShowS
Show)


instance (Aeson.FromJSON account, Aeson.FromJSON event) => Aeson.FromJSON (JournalEntryItemInventoryEvent account event) where
  parseJSON :: Value -> Parser (JournalEntryItemInventoryEvent account event)
parseJSON = forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
Aeson.genericParseJSON forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"journalEntryItemInventoryEvent"


instance (Aeson.ToJSON account, Aeson.ToJSON event) => Aeson.ToJSON (JournalEntryItemInventoryEvent account event) where
  toJSON :: JournalEntryItemInventoryEvent account event -> Value
toJSON = forall a.
(Generic a, GToJSON' Value Zero (Rep a)) =>
Options -> a -> Value
Aeson.genericToJSON forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"journalEntryItemInventoryEvent"
  toEncoding :: JournalEntryItemInventoryEvent account event -> Encoding
toEncoding = forall a.
(Generic a, GToJSON' Encoding Zero (Rep a)) =>
Options -> a -> Encoding
Aeson.genericToEncoding forall a b. (a -> b) -> a -> b
$ String -> Options
commonAesonOptions String
"journalEntryItemInventoryEvent"


-- | 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 :: forall (precision :: Nat) account event.
KnownNat precision =>
Quantity precision
-> Account account
-> event
-> JournalEntryItem precision account event
mkJournalEntryItemFromValue Quantity precision
val Account account
acc event
evt =
  JournalEntryItem
    { journalEntryItemAmount :: Amount precision
journalEntryItemAmount = forall (precision :: Nat).
KnownNat precision =>
AccountKind -> Quantity precision -> Amount precision
amountFromValue (forall o. Account o -> AccountKind
accountKind Account account
acc) Quantity precision
val
    , journalEntryItemAccount :: Account account
journalEntryItemAccount = Account account
acc
    , journalEntryItemEvent :: event
journalEntryItemEvent = event
evt
    , journalEntryItemInventoryEvent :: Maybe (JournalEntryItemInventoryEvent account event)
journalEntryItemInventoryEvent = forall a. Maybe a
Nothing
    }


-- | Creates a 'JournalEntryItem' with inventory event informationfrom the given
-- signed /value/, the account it belongs to and the event it is originating
-- from.
--
-- The /value/ is defined as in 'amountFromValue' function.
mkInventoryJournalEntryItemFromValue
  :: KnownNat precision
  => Quantity precision
  -> Account account
  -> event
  -> Account account
  -> event
  -> Quantity 12
  -> JournalEntryItem precision account event
mkInventoryJournalEntryItemFromValue :: forall (precision :: Nat) account event.
KnownNat precision =>
Quantity precision
-> Account account
-> event
-> Account account
-> event
-> Quantity 12
-> JournalEntryItem precision account event
mkInventoryJournalEntryItemFromValue Quantity precision
val Account account
acc event
evt Account account
pnlacc event
pnlevt Quantity 12
evtqty =
  JournalEntryItem
    { journalEntryItemAmount :: Amount precision
journalEntryItemAmount = forall (precision :: Nat).
KnownNat precision =>
AccountKind -> Quantity precision -> Amount precision
amountFromValue (forall o. Account o -> AccountKind
accountKind Account account
acc) Quantity precision
val
    , journalEntryItemAccount :: Account account
journalEntryItemAccount = Account account
acc
    , journalEntryItemEvent :: event
journalEntryItemEvent = event
evt
    , journalEntryItemInventoryEvent :: Maybe (JournalEntryItemInventoryEvent account event)
journalEntryItemInventoryEvent =
        forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$
          JournalEntryItemInventoryEvent
            { journalEntryItemInventoryEventPnlAccount :: Account account
journalEntryItemInventoryEventPnlAccount = Account account
pnlacc
            , journalEntryItemInventoryEventQuantity :: Quantity 12
journalEntryItemInventoryEventQuantity = Quantity 12
evtqty
            , journalEntryItemInventoryEventEvent :: event
journalEntryItemInventoryEventEvent = event
pnlevt
            }
    }