-- | This module provides data definitions and functions for trial balances.

{-# LANGUAGE DataKinds #-}

module Haspara.Accounting.TrialBalance where

import qualified Data.Aeson                 as Aeson
import           GHC.Generics               (Generic)
import           GHC.TypeLits               (KnownNat, Nat)
import           Haspara.Accounting.Amount  (Amount)
import           Haspara.Accounting.Balance (Balance(Balance, balanceSide), amountFromBalance, updateBalance)
import           Haspara.Accounting.Ledger  (GeneralLedger(generalLedgerLedgers), Ledger, ledgerClosing)
import           Haspara.Accounting.Side    (Side(..))
import           Haspara.Internal.Aeson     (commonAesonOptions)


-- | Data definition for a trial balance.
newtype TrialBalance (precision :: Nat) account event = TrialBalance
  { TrialBalance precision account event
-> [TrialBalanceItem precision account event]
trialBalanceItems :: [TrialBalanceItem precision account event]
  }
  deriving (TrialBalance precision account event
-> TrialBalance precision account event -> Bool
(TrialBalance precision account event
 -> TrialBalance precision account event -> Bool)
-> (TrialBalance precision account event
    -> TrialBalance precision account event -> Bool)
-> Eq (TrialBalance precision account event)
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall (precision :: Nat) account event.
(Eq account, Eq event) =>
TrialBalance precision account event
-> TrialBalance precision account event -> Bool
/= :: TrialBalance precision account event
-> TrialBalance precision account event -> Bool
$c/= :: forall (precision :: Nat) account event.
(Eq account, Eq event) =>
TrialBalance precision account event
-> TrialBalance precision account event -> Bool
== :: TrialBalance precision account event
-> TrialBalance precision account event -> Bool
$c== :: forall (precision :: Nat) account event.
(Eq account, Eq event) =>
TrialBalance precision account event
-> TrialBalance precision account event -> Bool
Eq, (forall x.
 TrialBalance precision account event
 -> Rep (TrialBalance precision account event) x)
-> (forall x.
    Rep (TrialBalance precision account event) x
    -> TrialBalance precision account event)
-> Generic (TrialBalance precision account event)
forall x.
Rep (TrialBalance precision account event) x
-> TrialBalance precision account event
forall x.
TrialBalance precision account event
-> Rep (TrialBalance 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 (TrialBalance precision account event) x
-> TrialBalance precision account event
forall (precision :: Nat) account event x.
TrialBalance precision account event
-> Rep (TrialBalance precision account event) x
$cto :: forall (precision :: Nat) account event x.
Rep (TrialBalance precision account event) x
-> TrialBalance precision account event
$cfrom :: forall (precision :: Nat) account event x.
TrialBalance precision account event
-> Rep (TrialBalance precision account event) x
Generic, Int -> TrialBalance precision account event -> ShowS
[TrialBalance precision account event] -> ShowS
TrialBalance precision account event -> String
(Int -> TrialBalance precision account event -> ShowS)
-> (TrialBalance precision account event -> String)
-> ([TrialBalance precision account event] -> ShowS)
-> Show (TrialBalance 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 -> TrialBalance precision account event -> ShowS
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
[TrialBalance precision account event] -> ShowS
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
TrialBalance precision account event -> String
showList :: [TrialBalance precision account event] -> ShowS
$cshowList :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
[TrialBalance precision account event] -> ShowS
show :: TrialBalance precision account event -> String
$cshow :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
TrialBalance precision account event -> String
showsPrec :: Int -> TrialBalance precision account event -> ShowS
$cshowsPrec :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
Int -> TrialBalance precision account event -> ShowS
Show)


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


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


-- | Data definition for a trial balance item.
data TrialBalanceItem (precision :: Nat) account event = TrialBalanceItem
  { TrialBalanceItem precision account event
-> Ledger precision account event
trialBalanceItemLedger  :: !(Ledger precision account event)
  , TrialBalanceItem precision account event -> Balance precision
trialBalanceItemBalance :: !(Balance precision)
  }
  deriving (TrialBalanceItem precision account event
-> TrialBalanceItem precision account event -> Bool
(TrialBalanceItem precision account event
 -> TrialBalanceItem precision account event -> Bool)
-> (TrialBalanceItem precision account event
    -> TrialBalanceItem precision account event -> Bool)
-> Eq (TrialBalanceItem precision account event)
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall (precision :: Nat) account event.
(Eq account, Eq event) =>
TrialBalanceItem precision account event
-> TrialBalanceItem precision account event -> Bool
/= :: TrialBalanceItem precision account event
-> TrialBalanceItem precision account event -> Bool
$c/= :: forall (precision :: Nat) account event.
(Eq account, Eq event) =>
TrialBalanceItem precision account event
-> TrialBalanceItem precision account event -> Bool
== :: TrialBalanceItem precision account event
-> TrialBalanceItem precision account event -> Bool
$c== :: forall (precision :: Nat) account event.
(Eq account, Eq event) =>
TrialBalanceItem precision account event
-> TrialBalanceItem precision account event -> Bool
Eq, (forall x.
 TrialBalanceItem precision account event
 -> Rep (TrialBalanceItem precision account event) x)
-> (forall x.
    Rep (TrialBalanceItem precision account event) x
    -> TrialBalanceItem precision account event)
-> Generic (TrialBalanceItem precision account event)
forall x.
Rep (TrialBalanceItem precision account event) x
-> TrialBalanceItem precision account event
forall x.
TrialBalanceItem precision account event
-> Rep (TrialBalanceItem 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 (TrialBalanceItem precision account event) x
-> TrialBalanceItem precision account event
forall (precision :: Nat) account event x.
TrialBalanceItem precision account event
-> Rep (TrialBalanceItem precision account event) x
$cto :: forall (precision :: Nat) account event x.
Rep (TrialBalanceItem precision account event) x
-> TrialBalanceItem precision account event
$cfrom :: forall (precision :: Nat) account event x.
TrialBalanceItem precision account event
-> Rep (TrialBalanceItem precision account event) x
Generic, Int -> TrialBalanceItem precision account event -> ShowS
[TrialBalanceItem precision account event] -> ShowS
TrialBalanceItem precision account event -> String
(Int -> TrialBalanceItem precision account event -> ShowS)
-> (TrialBalanceItem precision account event -> String)
-> ([TrialBalanceItem precision account event] -> ShowS)
-> Show (TrialBalanceItem 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 -> TrialBalanceItem precision account event -> ShowS
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
[TrialBalanceItem precision account event] -> ShowS
forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
TrialBalanceItem precision account event -> String
showList :: [TrialBalanceItem precision account event] -> ShowS
$cshowList :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
[TrialBalanceItem precision account event] -> ShowS
show :: TrialBalanceItem precision account event -> String
$cshow :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
TrialBalanceItem precision account event -> String
showsPrec :: Int -> TrialBalanceItem precision account event -> ShowS
$cshowsPrec :: forall (precision :: Nat) account event.
(KnownNat precision, Show account, Show event) =>
Int -> TrialBalanceItem precision account event -> ShowS
Show)


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


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


-- | Returns the amount of the trial balance item. This is a simple conversion
-- from 'Balance' to 'Amount'.
trialBalanceItemAmount
  :: KnownNat precision
  => TrialBalanceItem precision account event
  -> Amount precision
trialBalanceItemAmount :: TrialBalanceItem precision account event -> Amount precision
trialBalanceItemAmount = Balance precision -> Amount precision
forall (precision :: Nat).
KnownNat precision =>
Balance precision -> Amount precision
amountFromBalance (Balance precision -> Amount precision)
-> (TrialBalanceItem precision account event -> Balance precision)
-> TrialBalanceItem precision account event
-> Amount precision
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TrialBalanceItem precision account event -> Balance precision
forall (precision :: Nat) account event.
TrialBalanceItem precision account event -> Balance precision
trialBalanceItemBalance


-- | Given a general ledger, prepares the trial balance.
prepareTrialBalance
  :: KnownNat precision
  => GeneralLedger precision account event
  -> TrialBalance precision account event
prepareTrialBalance :: GeneralLedger precision account event
-> TrialBalance precision account event
prepareTrialBalance = [TrialBalanceItem precision account event]
-> TrialBalance precision account event
forall (precision :: Nat) account event.
[TrialBalanceItem precision account event]
-> TrialBalance precision account event
TrialBalance ([TrialBalanceItem precision account event]
 -> TrialBalance precision account event)
-> (GeneralLedger precision account event
    -> [TrialBalanceItem precision account event])
-> GeneralLedger precision account event
-> TrialBalance precision account event
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Ledger precision account event
 -> TrialBalanceItem precision account event)
-> [Ledger precision account event]
-> [TrialBalanceItem precision account event]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Ledger precision account event
-> TrialBalanceItem precision account event
forall (precision :: Nat) account event.
KnownNat precision =>
Ledger precision account event
-> TrialBalanceItem precision account event
mkTrialBalanceItem ([Ledger precision account event]
 -> [TrialBalanceItem precision account event])
-> (GeneralLedger precision account event
    -> [Ledger precision account event])
-> GeneralLedger precision account event
-> [TrialBalanceItem precision account event]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. GeneralLedger precision account event
-> [Ledger precision account event]
forall (precision :: Nat) account event.
GeneralLedger precision account event
-> [Ledger precision account event]
generalLedgerLedgers


-- | Converts a 'Ledger' to a 'TrialBalanceItem'.
mkTrialBalanceItem
  :: KnownNat precision
  => Ledger precision account event
  -> TrialBalanceItem precision account event
mkTrialBalanceItem :: Ledger precision account event
-> TrialBalanceItem precision account event
mkTrialBalanceItem Ledger precision account event
ledger =
  Ledger precision account event
-> Balance precision -> TrialBalanceItem precision account event
forall (precision :: Nat) account event.
Ledger precision account event
-> Balance precision -> TrialBalanceItem precision account event
TrialBalanceItem Ledger precision account event
ledger (Ledger precision account event -> Balance precision
forall (precision :: Nat) account event.
KnownNat precision =>
Ledger precision account event -> Balance precision
ledgerClosing Ledger precision account event
ledger)


-- | Computes the trial balance totals as a 2-tuple of total debits and total
-- credits.
trialBalanceTotals
  :: KnownNat precision
  => TrialBalance precision account event
  -> (Balance precision, Balance precision)
trialBalanceTotals :: TrialBalance precision account event
-> (Balance precision, Balance precision)
trialBalanceTotals (TrialBalance [TrialBalanceItem precision account event]
items) =
  let
    itemsFromDb :: [Amount precision]
itemsFromDb = Balance precision -> Amount precision
forall (precision :: Nat).
KnownNat precision =>
Balance precision -> Amount precision
amountFromBalance (Balance precision -> Amount precision)
-> (TrialBalanceItem precision account event -> Balance precision)
-> TrialBalanceItem precision account event
-> Amount precision
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TrialBalanceItem precision account event -> Balance precision
forall (precision :: Nat) account event.
TrialBalanceItem precision account event -> Balance precision
trialBalanceItemBalance (TrialBalanceItem precision account event -> Amount precision)
-> [TrialBalanceItem precision account event] -> [Amount precision]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (TrialBalanceItem precision account event -> Bool)
-> [TrialBalanceItem precision account event]
-> [TrialBalanceItem precision account event]
forall a. (a -> Bool) -> [a] -> [a]
filter (Side -> Side -> Bool
forall a. Eq a => a -> a -> Bool
(==) Side
SideDebit (Side -> Bool)
-> (TrialBalanceItem precision account event -> Side)
-> TrialBalanceItem precision account event
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Balance precision -> Side
forall (precision :: Nat). Balance precision -> Side
balanceSide (Balance precision -> Side)
-> (TrialBalanceItem precision account event -> Balance precision)
-> TrialBalanceItem precision account event
-> Side
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TrialBalanceItem precision account event -> Balance precision
forall (precision :: Nat) account event.
TrialBalanceItem precision account event -> Balance precision
trialBalanceItemBalance) [TrialBalanceItem precision account event]
items
    itemsFromCr :: [Amount precision]
itemsFromCr = Balance precision -> Amount precision
forall (precision :: Nat).
KnownNat precision =>
Balance precision -> Amount precision
amountFromBalance (Balance precision -> Amount precision)
-> (TrialBalanceItem precision account event -> Balance precision)
-> TrialBalanceItem precision account event
-> Amount precision
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TrialBalanceItem precision account event -> Balance precision
forall (precision :: Nat) account event.
TrialBalanceItem precision account event -> Balance precision
trialBalanceItemBalance (TrialBalanceItem precision account event -> Amount precision)
-> [TrialBalanceItem precision account event] -> [Amount precision]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (TrialBalanceItem precision account event -> Bool)
-> [TrialBalanceItem precision account event]
-> [TrialBalanceItem precision account event]
forall a. (a -> Bool) -> [a] -> [a]
filter (Side -> Side -> Bool
forall a. Eq a => a -> a -> Bool
(==) Side
SideCredit (Side -> Bool)
-> (TrialBalanceItem precision account event -> Side)
-> TrialBalanceItem precision account event
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Balance precision -> Side
forall (precision :: Nat). Balance precision -> Side
balanceSide (Balance precision -> Side)
-> (TrialBalanceItem precision account event -> Balance precision)
-> TrialBalanceItem precision account event
-> Side
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TrialBalanceItem precision account event -> Balance precision
forall (precision :: Nat) account event.
TrialBalanceItem precision account event -> Balance precision
trialBalanceItemBalance) [TrialBalanceItem precision account event]
items
    totalDb :: Balance precision
totalDb = (Balance precision -> Amount precision -> Balance precision)
-> Balance precision -> [Amount precision] -> Balance precision
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl Balance precision -> Amount precision -> Balance precision
forall (precision :: Nat).
KnownNat precision =>
Balance precision -> Amount precision -> Balance precision
updateBalance (Side -> Quantity precision -> Balance precision
forall (precision :: Nat).
Side -> Quantity precision -> Balance precision
Balance Side
SideDebit Quantity precision
0) [Amount precision]
itemsFromDb
    totalCr :: Balance precision
totalCr = (Balance precision -> Amount precision -> Balance precision)
-> Balance precision -> [Amount precision] -> Balance precision
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl Balance precision -> Amount precision -> Balance precision
forall (precision :: Nat).
KnownNat precision =>
Balance precision -> Amount precision -> Balance precision
updateBalance (Side -> Quantity precision -> Balance precision
forall (precision :: Nat).
Side -> Quantity precision -> Balance precision
Balance Side
SideCredit Quantity precision
0) [Amount precision]
itemsFromCr
  in
    (Balance precision
totalDb, Balance precision
totalCr)