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

-- | This module provides data definitions and functions for trial balances.
module Haspara.Accounting.TrialBalance where

import qualified Data.Aeson as Aeson
import Data.Default (def)
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
  { forall (precision :: Nat) account event.
TrialBalance precision account event
-> [TrialBalanceItem precision account event]
trialBalanceItems :: [TrialBalanceItem precision account event]
  }
  deriving (TrialBalance precision account event
-> TrialBalance precision account event -> Bool
forall (precision :: Nat) account event.
(Eq account, Eq event) =>
TrialBalance precision account event
-> TrialBalance precision account event -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: 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 (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
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$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
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
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
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 = 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
"trialBalance"


instance (KnownNat precision, Aeson.ToJSON account, Aeson.ToJSON event) => Aeson.ToJSON (TrialBalance precision account event) where
  toJSON :: TrialBalance 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
"trialBalance"
  toEncoding :: TrialBalance 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
"trialBalance"


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


instance (KnownNat precision, Aeson.ToJSON account, Aeson.ToJSON event) => Aeson.ToJSON (TrialBalanceItem precision account event) where
  toJSON :: TrialBalanceItem 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
"trialBalanceItem"
  toEncoding :: TrialBalanceItem 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
"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 :: forall (precision :: Nat) account event.
KnownNat precision =>
TrialBalanceItem precision account event -> Amount precision
trialBalanceItemAmount = forall (precision :: Nat).
KnownNat precision =>
Balance precision -> Amount precision
amountFromBalance forall b c a. (b -> c) -> (a -> b) -> a -> c
. 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 :: forall (precision :: Nat) account event.
KnownNat precision =>
GeneralLedger precision account event
-> TrialBalance precision account event
prepareTrialBalance = forall (precision :: Nat) account event.
[TrialBalanceItem precision account event]
-> TrialBalance precision account event
TrialBalance 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) account event.
KnownNat precision =>
Ledger precision account event
-> TrialBalanceItem precision account event
mkTrialBalanceItem forall b c a. (b -> c) -> (a -> b) -> a -> c
. 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 :: forall (precision :: Nat) account event.
KnownNat precision =>
Ledger precision account event
-> TrialBalanceItem precision account event
mkTrialBalanceItem Ledger precision account event
ledger =
  forall (precision :: Nat) account event.
Ledger precision account event
-> Balance precision -> TrialBalanceItem precision account event
TrialBalanceItem Ledger precision account event
ledger (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 :: forall (precision :: Nat) account event.
KnownNat precision =>
TrialBalance precision account event
-> (Balance precision, Balance precision)
trialBalanceTotals (TrialBalance [TrialBalanceItem precision account event]
items) =
  let itemsFromDb :: [Amount precision]
itemsFromDb = forall (precision :: Nat).
KnownNat precision =>
Balance precision -> Amount precision
amountFromBalance forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (precision :: Nat) account event.
TrialBalanceItem precision account event -> Balance precision
trialBalanceItemBalance forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> 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). Balance precision -> Side
balanceSide forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (precision :: Nat) account event.
TrialBalanceItem precision account event -> Balance precision
trialBalanceItemBalance) [TrialBalanceItem precision account event]
items
      itemsFromCr :: [Amount precision]
itemsFromCr = forall (precision :: Nat).
KnownNat precision =>
Balance precision -> Amount precision
amountFromBalance forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (precision :: Nat) account event.
TrialBalanceItem precision account event -> Balance precision
trialBalanceItemBalance forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> 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). Balance precision -> Side
balanceSide forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (precision :: Nat) account event.
TrialBalanceItem precision account event -> Balance precision
trialBalanceItemBalance) [TrialBalanceItem precision account event]
items
      totalDb :: Balance precision
totalDb = forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl forall (precision :: Nat).
KnownNat precision =>
Balance precision -> Amount precision -> Balance precision
updateBalance (forall (precision :: Nat).
Side
-> Quantity precision
-> Inventory 8 12 precision
-> Balance precision
Balance Side
SideDebit Quantity precision
0 forall a. Default a => a
def) [Amount precision]
itemsFromDb
      totalCr :: Balance precision
totalCr = forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl forall (precision :: Nat).
KnownNat precision =>
Balance precision -> Amount precision -> Balance precision
updateBalance (forall (precision :: Nat).
Side
-> Quantity precision
-> Inventory 8 12 precision
-> Balance precision
Balance Side
SideCredit Quantity precision
0 forall a. Default a => a
def) [Amount precision]
itemsFromCr
   in (Balance precision
totalDb, Balance precision
totalCr)