packages feed

haspara-0.0.0.4: src/Haspara/Accounting/Journal.hs

-- | 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
  { journalEntries :: [JournalEntry precision account event]
  }
  deriving (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
  { journalEntryId          :: !T.Text
  , journalEntryDate        :: !Day
  , journalEntryItems       :: ![JournalEntryItem precision account event]
  , journalEntryDescription :: !T.Text
  }
  deriving (Show)


-- | Returns the total debit amount of a journal entry.
journalEntryTotalDebit
  :: KnownNat precision
  => JournalEntry precision account event
  -> UnsignedQuantity precision
journalEntryTotalDebit =
    sumUnsignedQuantity
  . fmap (amountValue . journalEntryItemAmount)
  . filter ((==) SideDebit . amountSide . journalEntryItemAmount)
  . journalEntryItems


-- | Returns the total credit amount of a journal entry.
journalEntryTotalCredit
  :: KnownNat precision
  => JournalEntry precision account event
  -> UnsignedQuantity precision
journalEntryTotalCredit =
    sumUnsignedQuantity
  . fmap (amountValue . journalEntryItemAmount)
  . filter ((==) SideCredit . amountSide . journalEntryItemAmount)
  . 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 = (==)
  <$> journalEntryTotalDebit
  <*> 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
  { journalEntryItemAmount  :: !(Amount precision)
  , journalEntryItemAccount :: !(Account account)
  , journalEntryItemEvent   :: !event
  }
  deriving (Eq, Generic, 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 qty acc evt =
  JournalEntryItem
    { journalEntryItemAmount = amountFromQuantity (accountKind acc) qty
    , journalEntryItemAccount = acc
    , journalEntryItemEvent = 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 val acc evt =
  JournalEntryItem
    { journalEntryItemAmount = amountFromValue (accountKind acc) val
    , journalEntryItemAccount = acc
    , journalEntryItemEvent = evt
    }