hax-0.0.1: src/HAX/Accounting.hs
{-# LANGUAGE
ExistentialQuantification
, NoMonomorphismRestriction
#-}
-- | This module provides
--
-- * the types to build a 'World' of accounting 'Entity's and
--
-- * functions to compute the ledger resulting from the entities'
-- accounting actions
--
-- * built from combinators found in "Bookkeeping" and "Assets".
--
-- Use "Report" to display the results.
module HAX.Accounting where
import HAX.Bookkeeping.Internal
import HAX.Bookkeeping
import Control.Monad.RWS.Strict
import HAX.Common
import qualified Data.Map as M
-- * Accounting Entities
data World = World { wLife :: (ADate,ADate) -- ^ time interval
, wEntites :: [Entity]
}
-- | An entity keeping accounts over its assets.
data Entity = forall body . Body body => Entity {
entName :: EntityName
-- ^ the name of the entity. This will serve as an 'AccountName'
-- when booking transaction between this entity an another entity.
, entAssets :: [AssetName]
-- ^ the entity's asset accounts
, entMonthlyAction :: AccountingRW body ()
-- the entity's monthly accounting action
, entBody :: body
-- ^ the entity's 'Body', defining its nominal acounts.
}
-- | The body of an entity.
class Body body where
-- | The only requirement is that each Body has a list of nominal
-- accounts associated with it. See "Germany" for instances.
nominalAccounts :: body -> [String]
bodyMonthly :: AccountingRW body () -- ^ custom actin of the entity
-> AccountingRW body () -- ^ monthly action
-- * Running actions and generating ledgers
-- | Executes an accounting action in a simple environment (for
-- testing purposes) with no accounts.
--
-- >>> simple (date 12 2016) NatuerlichePerson{pGeburt=date 12 1960} alter
-- 56
simple :: ADate -> body -> AccountingRW body a -> IO a
simple date body action = do
bals <- newArray ((date,1),(date,1)) 0
txns <- newArray (date,date) []
let ledger = UNSAFE_Ledger bals txns
fst <$> evalRWST action (Env ledger M.empty date body Nothing) ()
-- | Generate the ledger of the 'World'. It will include all nominal
-- and asset accounts of all 'n' entities, as well as 'n*(n-1)'
-- transactional accounts between each pair of entities.
--
-- Furthermore, it will check the balances of these transactional
-- accounts at the end of every month using
-- 'checkTransactionalAcountSymmetry'.
generate :: World -> IO FullLedger
generate (World times entities) = do
let entityNames = entName <$> entities
eAccounts (Entity name assets _ body) =
assets ++ nominalAccounts body
++ (filter (name /=) entityNames)
allAccounts = M.fromList $ zip accountList [1..]
accountList = [ FAN (entName e) a | e <- entities, a <- eAccounts e ]
nAccs = M.size allAccounts
when (length accountList /= M.size allAccounts) $
error $ "AccountNames are not unique: "++
(unlines$show<$>accountList)
printf "Created a ledger with %v accounts\n" nAccs
bals <- newArray ((fst times,1),(snd times,nAccs)) 0
txns <- newArray times []
let ledger = UNSAFE_Ledger bals txns
monthAction = do
uNSAFE_carryOver
checkTransactionalAcountSymmetry entityNames
sequence_ [withRWT (\e -> e{eBody=body, eName=Just name})
$ bodyMonthly action
| (Entity name _ action body) <- entities ]
result <- sequence [ runRWST monthAction
(Env ledger allAccounts date () Nothing) ()
| date <- range times ]
-- this is safe, as the ioarrays do not leave this function
ledger' <- lunsafeFix ledger
return $ FullLedger ledger' allAccounts
withRWT :: (r' -> r) -> RWST r w () m a -> RWST r' w () m a
withRWT f = withRWST $ \r s -> (f r,())
evalRWT :: Monad m => RWST r () () m a -> r -> m a
evalRWT action env = liftM fst $ evalRWST action env ()
-- * Helpers
-- | For each pair of entities, 'entity1' and 'entity2', there are two
-- transactional accounts, as each entity keeps its own books about
-- its transactions with the other entity. If no entity made a
-- mistake, the accounts should have opposite balances that cancel
-- each other. This function informs about a violation of this
-- property.
checkTransactionalAcountSymmetry :: (Monoid w, Ledger l) => [EntityName] -> Acc s l w ()
checkTransactionalAcountSymmetry entities = do
let pairs = [ FAN e1 e2 | e1:rest <- tails entities, e2 <- rest ]
cb = currentBalance . UNSAFE_AccountN
date <- reader eDate
forM_ pairs $ \p -> do
v1 <- cb p
v2 <- negate <$> cb (swapFAN p)
when (v1 /= v2) $ lift $ printf
"symmtery violation at %v: %s %v /= %s %v\n"
date (show p) v1 (show $ swapFAN p) v2
-- | constructs an action reading an information from the current body
readBody :: Monoid w => (s -> a) -> Acc s l w a
readBody f = reader $ f . eBody
-- | construct an action that depends on the body
withBody :: Monoid w => (s -> Acc s l w a) -> Acc s l w a
withBody = (reader eBody >>=)