-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | A library providing definitions to work with monetary values. -- -- Please see the README on GitHub at -- https://github.com/telostat/haspara#readme @package haspara @version 0.0.0.1 module Haspara.Accounting.AccountKind data AccountKind AccountKindAsset :: AccountKind AccountKindLiability :: AccountKind AccountKindEquity :: AccountKind AccountKindRevenue :: AccountKind AccountKindExpense :: AccountKind accountKindText :: AccountKind -> Text instance GHC.Show.Show Haspara.Accounting.AccountKind.AccountKind instance GHC.Classes.Ord Haspara.Accounting.AccountKind.AccountKind instance GHC.Generics.Generic Haspara.Accounting.AccountKind.AccountKind instance GHC.Classes.Eq Haspara.Accounting.AccountKind.AccountKind instance GHC.Enum.Enum Haspara.Accounting.AccountKind.AccountKind instance Data.Hashable.Class.Hashable Haspara.Accounting.AccountKind.AccountKind instance Data.Aeson.Types.FromJSON.FromJSON Haspara.Accounting.AccountKind.AccountKind instance Data.Aeson.Types.ToJSON.ToJSON Haspara.Accounting.AccountKind.AccountKind module Haspara.Accounting.Account -- | Account model. -- --
-- >>> import Haspara.Accounting.AccountKind (AccountKind(..))
--
-- >>> import qualified Data.Aeson as Aeson
--
-- >>> let acc = Account AccountKindAsset (1 ::Int)
--
-- >>> Aeson.encode acc
-- "{\"kind\":\"ASSET\",\"object\":1}"
--
-- >>> Aeson.decode (Aeson.encode acc) :: Maybe (Account Int)
-- Just (Account {accountKind = AccountKindAsset, accountObject = 1})
--
-- >>> Aeson.decode (Aeson.encode acc) == Just acc
-- True
--
data Account o
Account :: !AccountKind -> !o -> Account o
[accountKind] :: Account o -> !AccountKind
[accountObject] :: Account o -> !o
instance Data.Aeson.Types.ToJSON.ToJSON o => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Account.Account o)
instance Data.Aeson.Types.FromJSON.FromJSON o => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Account.Account o)
instance GHC.Show.Show o => GHC.Show.Show (Haspara.Accounting.Account.Account o)
instance GHC.Classes.Ord o => GHC.Classes.Ord (Haspara.Accounting.Account.Account o)
instance GHC.Generics.Generic (Haspara.Accounting.Account.Account o)
instance GHC.Classes.Eq o => GHC.Classes.Eq (Haspara.Accounting.Account.Account o)
instance Data.Hashable.Class.Hashable o => Data.Hashable.Class.Hashable (Haspara.Accounting.Account.Account o)
-- | This module provides internal definitions for modeling and working
-- with currencies.
module Haspara.Internal.Currency
-- | Type encoding for currencies.
newtype Currency
MkCurrency :: Text -> Currency
[currencyCode] :: Currency -> Text
-- | Smart constructor for Currency values within MonadError
-- context.
--
-- -- >>> currency "" :: Either String Currency -- Left "Currency code error! Expecting at least 3 uppercase characters, but received: \"\"" -- -- >>> currency " " :: Either String Currency -- Left "Currency code error! Expecting at least 3 uppercase characters, but received: \" \"" -- -- >>> currency "AB" :: Either String Currency -- Left "Currency code error! Expecting at least 3 uppercase characters, but received: \"AB\"" -- -- >>> currency " ABC " :: Either String Currency -- Left "Currency code error! Expecting at least 3 uppercase characters, but received: \" ABC \"" -- -- >>> currency "ABC" :: Either String Currency -- Right ABC --currency :: MonadError String m => Text -> m Currency -- | Smart constructor for Currency values within MonadFail -- context. -- --
-- >>> currencyFail "" :: Maybe Currency -- Nothing -- -- >>> currencyFail "US" :: Maybe Currency -- Nothing -- -- >>> currencyFail "usd" :: Maybe Currency -- Nothing -- -- >>> currencyFail "USD" :: Maybe Currency -- Just USD --currencyFail :: MonadFail m => Text -> m Currency -- | Parser that parses currency codes. -- --
-- >>> MP.runParser currencyCodeParser "Example" ""
-- Left (ParseErrorBundle {bundleErrors = TrivialError 0 (Just EndOfInput) (fromList []) :| [], bundlePosState = PosState {pstateInput = "", pstateOffset = 0, pstateSourcePos = SourcePos {sourceName = "Example", sourceLine = Pos 1, sourceColumn = Pos 1}, pstateTabWidth = Pos 8, pstateLinePrefix = ""}})
--
-- >>> MP.runParser currencyCodeParser "Example" " "
-- Left (ParseErrorBundle {bundleErrors = TrivialError 0 (Just (Tokens (' ' :| ""))) (fromList []) :| [], bundlePosState = PosState {pstateInput = " ", pstateOffset = 0, pstateSourcePos = SourcePos {sourceName = "Example", sourceLine = Pos 1, sourceColumn = Pos 1}, pstateTabWidth = Pos 8, pstateLinePrefix = ""}})
--
-- >>> MP.runParser currencyCodeParser "Example" "a"
-- Left (ParseErrorBundle {bundleErrors = TrivialError 0 (Just (Tokens ('a' :| ""))) (fromList []) :| [], bundlePosState = PosState {pstateInput = "a", pstateOffset = 0, pstateSourcePos = SourcePos {sourceName = "Example", sourceLine = Pos 1, sourceColumn = Pos 1}, pstateTabWidth = Pos 8, pstateLinePrefix = ""}})
--
-- >>> MP.runParser currencyCodeParser "Example" "A"
-- Left (ParseErrorBundle {bundleErrors = TrivialError 1 (Just EndOfInput) (fromList []) :| [], bundlePosState = PosState {pstateInput = "A", pstateOffset = 0, pstateSourcePos = SourcePos {sourceName = "Example", sourceLine = Pos 1, sourceColumn = Pos 1}, pstateTabWidth = Pos 8, pstateLinePrefix = ""}})
--
-- >>> MP.runParser currencyCodeParser "Example" "AB"
-- Left (ParseErrorBundle {bundleErrors = TrivialError 2 (Just EndOfInput) (fromList []) :| [], bundlePosState = PosState {pstateInput = "AB", pstateOffset = 0, pstateSourcePos = SourcePos {sourceName = "Example", sourceLine = Pos 1, sourceColumn = Pos 1}, pstateTabWidth = Pos 8, pstateLinePrefix = ""}})
--
-- >>> MP.runParser currencyCodeParser "Example" "ABC"
-- Right "ABC"
--
currencyCodeParser :: Parsec Void Text Text
newtype CurrencyPair
MkCurrencyPair :: (Currency, Currency) -> CurrencyPair
[unCurrencyPair] :: CurrencyPair -> (Currency, Currency)
toTuple :: CurrencyPair -> (Currency, Currency)
baseCurrency :: CurrencyPair -> Currency
quoteCurrency :: CurrencyPair -> Currency
currencyPair :: MonadError String m => Currency -> Currency -> m CurrencyPair
currencyPairFail :: MonadFail m => Currency -> Currency -> m CurrencyPair
instance Language.Haskell.TH.Syntax.Lift Haspara.Internal.Currency.Currency
instance GHC.Classes.Ord Haspara.Internal.Currency.Currency
instance Data.Hashable.Class.Hashable Haspara.Internal.Currency.Currency
instance GHC.Classes.Eq Haspara.Internal.Currency.Currency
instance Language.Haskell.TH.Syntax.Lift Haspara.Internal.Currency.CurrencyPair
instance GHC.Classes.Ord Haspara.Internal.Currency.CurrencyPair
instance Data.Hashable.Class.Hashable Haspara.Internal.Currency.CurrencyPair
instance GHC.Classes.Eq Haspara.Internal.Currency.CurrencyPair
instance GHC.Show.Show Haspara.Internal.Currency.CurrencyPair
instance GHC.Show.Show Haspara.Internal.Currency.Currency
instance Data.String.IsString Haspara.Internal.Currency.Currency
instance Data.Aeson.Types.FromJSON.FromJSON Haspara.Internal.Currency.Currency
instance Data.Aeson.Types.ToJSON.ToJSON Haspara.Internal.Currency.Currency
-- | This module provides base data definitions and functions for
-- Haspara library.
module Haspara.Currency
-- | Type encoding for currencies.
data Currency
currencyCode :: Currency -> Text
-- | Smart constructor for Currency values within MonadError
-- context.
--
-- -- >>> currency "" :: Either String Currency -- Left "Currency code error! Expecting at least 3 uppercase characters, but received: \"\"" -- -- >>> currency " " :: Either String Currency -- Left "Currency code error! Expecting at least 3 uppercase characters, but received: \" \"" -- -- >>> currency "AB" :: Either String Currency -- Left "Currency code error! Expecting at least 3 uppercase characters, but received: \"AB\"" -- -- >>> currency " ABC " :: Either String Currency -- Left "Currency code error! Expecting at least 3 uppercase characters, but received: \" ABC \"" -- -- >>> currency "ABC" :: Either String Currency -- Right ABC --currency :: MonadError String m => Text -> m Currency -- | Smart constructor for Currency values within MonadFail -- context. -- --
-- >>> currencyFail "" :: Maybe Currency -- Nothing -- -- >>> currencyFail "US" :: Maybe Currency -- Nothing -- -- >>> currencyFail "usd" :: Maybe Currency -- Nothing -- -- >>> currencyFail "USD" :: Maybe Currency -- Just USD --currencyFail :: MonadFail m => Text -> m Currency data CurrencyPair toTuple :: CurrencyPair -> (Currency, Currency) baseCurrency :: CurrencyPair -> Currency quoteCurrency :: CurrencyPair -> Currency currencyPair :: MonadError String m => Currency -> Currency -> m CurrencyPair currencyPairFail :: MonadFail m => Currency -> Currency -> m CurrencyPair -- | This module provides data definitions and functions for date values. module Haspara.Internal.Date -- | Type encoding for date values. -- -- This is a convenience wrapper around Day type. It helps us to -- avoid defining orphan instances. newtype Date MkDate :: Day -> Date -- | Builds a Date from a given Day. -- --
-- >>> fromDay (read "2021-01-01") -- 2021-01-01 --fromDay :: Day -> Date -- | Builds a Date from a given year, month and day as in Gregorian -- calendar. -- --
-- >>> fromYMD 2021 1 1 -- 2021-01-01 --fromYMD :: Integer -> Int -> Int -> Date -- | Attempts to parse and return Date from a given String -- with ISO format. -- --
-- >>> fromString "2021-01-01" :: Maybe Date -- Just 2021-01-01 -- -- >>> fromString "20210101" :: Maybe Date -- Nothing --fromString :: MonadFail m => String -> m Date -- | Attempts to parse and return Date from a given String -- with given date format. -- --
-- >>> fromFormattedString "%Y-%m-%d" "2021-01-01" :: Maybe Date -- Just 2021-01-01 -- -- >>> fromFormattedString "%Y%m%d" "20210101" :: Maybe Date -- Just 2021-01-01 -- -- >>> fromFormattedString "%Y%m%d" "202101" :: Maybe Date -- Nothing --fromFormattedString :: MonadFail m => String -> String -> m Date -- | Attempts to parse and return Date from a given Text with -- ISO format. -- --
-- >>> fromText "2021-01-01" :: Maybe Date -- Just 2021-01-01 -- -- >>> fromText "20210101" :: Maybe Date -- Nothing --fromText :: MonadFail m => Text -> m Date -- | Attempts to parse and return Date from a given Text with -- ISO format. -- --
-- >>> fromFormattedText "%Y-%m-%d" "2021-01-01" :: Maybe Date -- Just 2021-01-01 -- -- >>> fromFormattedText "%Y%m%d" "20210101" :: Maybe Date -- Just 2021-01-01 -- -- >>> fromFormattedText "%Y%m%d" "202101" :: Maybe Date -- Nothing --fromFormattedText :: MonadFail m => String -> Text -> m Date -- | Converts Date value to a Day value. -- --
-- >>> toDay (read "2021-01-01") -- 2021-01-01 --toDay :: Date -> Day -- | Converts Date value to a 3-tuple of year, month and day. -- --
-- >>> toYMD (read "2020-12-31") -- (2020,12,31) --toYMD :: Date -> (Integer, Int, Int) -- | Converts Date value into a String value with ISO format. -- --
-- >>> toString (read "2021-01-01") -- "2021-01-01" --toString :: Date -> String -- | Converts Date value into a String value with the given -- format. -- --
-- >>> toFormattedString "%Y-%m-%d" (read "2021-01-01") -- "2021-01-01" -- -- >>> toFormattedString "%d/%m/%Y" (read "2021-01-01") -- "01/01/2021" --toFormattedString :: String -> Date -> String -- | Converts Date value into a Text value with ISO format. -- --
-- >>> toText (read "2021-01-01") -- "2021-01-01" --toText :: Date -> Text -- | Converts Date value into a Text value with the given -- format. -- --
-- >>> toFormattedText "%Y-%m-%d" (read "2021-01-01") -- "2021-01-01" -- -- >>> toFormattedText "%d/%m/%Y" (read "2021-01-01") -- "01/01/2021" --toFormattedText :: String -> Date -> Text -- | Adds (or subtracts) some days. -- --
-- >>> addDays (-1) $ fromYMD 2021 1 1 -- 2020-12-31 -- -- >>> addDays 1 $ addDays (-1) $ fromYMD 2021 1 1 -- 2021-01-01 --addDays :: Integer -> Date -> Date instance GHC.Classes.Ord Haspara.Internal.Date.Date instance GHC.Enum.Enum Haspara.Internal.Date.Date instance GHC.Classes.Eq Haspara.Internal.Date.Date instance Data.Hashable.Class.Hashable Haspara.Internal.Date.Date instance GHC.Read.Read Haspara.Internal.Date.Date instance GHC.Show.Show Haspara.Internal.Date.Date instance Data.Aeson.Types.FromJSON.FromJSON Haspara.Internal.Date.Date instance Data.Aeson.Types.ToJSON.ToJSON Haspara.Internal.Date.Date -- | This module provides definitions and functions to encode and work on -- date values. module Haspara.Date -- | Type encoding for date values. -- -- This is a convenience wrapper around Day type. It helps us to -- avoid defining orphan instances. data Date -- | Builds a Date from a given Day. -- --
-- >>> fromDay (read "2021-01-01") -- 2021-01-01 --fromDay :: Day -> Date -- | Builds a Date from a given year, month and day as in Gregorian -- calendar. -- --
-- >>> fromYMD 2021 1 1 -- 2021-01-01 --fromYMD :: Integer -> Int -> Int -> Date -- | Attempts to parse and return Date from a given String -- with ISO format. -- --
-- >>> fromString "2021-01-01" :: Maybe Date -- Just 2021-01-01 -- -- >>> fromString "20210101" :: Maybe Date -- Nothing --fromString :: MonadFail m => String -> m Date -- | Attempts to parse and return Date from a given String -- with given date format. -- --
-- >>> fromFormattedString "%Y-%m-%d" "2021-01-01" :: Maybe Date -- Just 2021-01-01 -- -- >>> fromFormattedString "%Y%m%d" "20210101" :: Maybe Date -- Just 2021-01-01 -- -- >>> fromFormattedString "%Y%m%d" "202101" :: Maybe Date -- Nothing --fromFormattedString :: MonadFail m => String -> String -> m Date -- | Attempts to parse and return Date from a given Text with -- ISO format. -- --
-- >>> fromText "2021-01-01" :: Maybe Date -- Just 2021-01-01 -- -- >>> fromText "20210101" :: Maybe Date -- Nothing --fromText :: MonadFail m => Text -> m Date -- | Attempts to parse and return Date from a given Text with -- ISO format. -- --
-- >>> fromFormattedText "%Y-%m-%d" "2021-01-01" :: Maybe Date -- Just 2021-01-01 -- -- >>> fromFormattedText "%Y%m%d" "20210101" :: Maybe Date -- Just 2021-01-01 -- -- >>> fromFormattedText "%Y%m%d" "202101" :: Maybe Date -- Nothing --fromFormattedText :: MonadFail m => String -> Text -> m Date -- | Converts Date value to a Day value. -- --
-- >>> toDay (read "2021-01-01") -- 2021-01-01 --toDay :: Date -> Day -- | Converts Date value to a 3-tuple of year, month and day. -- --
-- >>> toYMD (read "2020-12-31") -- (2020,12,31) --toYMD :: Date -> (Integer, Int, Int) -- | Converts Date value into a String value with ISO format. -- --
-- >>> toString (read "2021-01-01") -- "2021-01-01" --toString :: Date -> String -- | Converts Date value into a String value with the given -- format. -- --
-- >>> toFormattedString "%Y-%m-%d" (read "2021-01-01") -- "2021-01-01" -- -- >>> toFormattedString "%d/%m/%Y" (read "2021-01-01") -- "01/01/2021" --toFormattedString :: String -> Date -> String -- | Converts Date value into a Text value with ISO format. -- --
-- >>> toText (read "2021-01-01") -- "2021-01-01" --toText :: Date -> Text -- | Converts Date value into a Text value with the given -- format. -- --
-- >>> toFormattedText "%Y-%m-%d" (read "2021-01-01") -- "2021-01-01" -- -- >>> toFormattedText "%d/%m/%Y" (read "2021-01-01") -- "01/01/2021" --toFormattedText :: String -> Date -> Text -- | Adds (or subtracts) some days. -- --
-- >>> addDays (-1) $ fromYMD 2021 1 1 -- 2020-12-31 -- -- >>> addDays 1 $ addDays (-1) $ fromYMD 2021 1 1 -- 2021-01-01 --addDays :: Integer -> Date -> Date module Haspara.Internal.Id -- | Type encoding for entity identifiers. -- -- This encoding allows us to provide a phantom type for distinguishing -- between identifiers of varying types and an underlying identifier -- type. -- -- For example: -- --
-- >>> data A = A -- -- >>> data B = B -- -- >>> data C = C -- -- >>> type IdA = Id A Int -- -- >>> type IdB = Id B Int -- -- >>> type IdC = Id C String -- -- >>> let idA = Id 1 :: IdA -- -- >>> let idB = Id 1 :: IdB -- -- >>> let idC = Id "C1" :: IdC -- -- >>> idA -- 1 -- -- >>> idB -- 1 -- -- >>> idC -- "C1" -- -- >>> idA == idA -- True -- -- >>> -- idA == idB -- Compile error as: Couldn't match type ‘B’ with ‘A’ ---- -- Hashes, on the otherhand, can be compared: -- --
-- >>> import Data.Hashable -- -- >>> hash idA == hash idB -- True --newtype Id a b Id :: b -> Id a b [unId] :: Id a b -> b -- | Type encoding for a lookup table from entity Ids to -- corresponding entities. -- --
-- >>> data A = A Int String deriving Show -- -- >>> type IdA = Id A Int -- -- >>> let a1 = A 1 "a1" -- -- >>> let a2 = A 2 "a2" -- -- >>> let a3 = A 3 "a3" -- -- >>> let table = HM.fromList [(Id 1, a1), (Id 2, a2), (Id 3, a3)] :: IdLookup A Int -- -- >>> HM.lookup (Id 1) table -- Just (A 1 "a1") --type IdLookup a b = HashMap (Id a b) a instance Data.Hashable.Class.Hashable b => Data.Hashable.Class.Hashable (Haspara.Internal.Id.Id a b) instance GHC.Classes.Ord b => GHC.Classes.Ord (Haspara.Internal.Id.Id a b) instance GHC.Classes.Eq b => GHC.Classes.Eq (Haspara.Internal.Id.Id a b) instance GHC.Show.Show b => GHC.Show.Show (Haspara.Internal.Id.Id a b) instance Data.Aeson.Types.FromJSON.FromJSON b => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Internal.Id.Id a b) instance Data.Aeson.Types.ToJSON.ToJSON b => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Internal.Id.Id a b) module Haspara.Id -- | Type encoding for entity identifiers. -- -- This encoding allows us to provide a phantom type for distinguishing -- between identifiers of varying types and an underlying identifier -- type. -- -- For example: -- --
-- >>> data A = A -- -- >>> data B = B -- -- >>> data C = C -- -- >>> type IdA = Id A Int -- -- >>> type IdB = Id B Int -- -- >>> type IdC = Id C String -- -- >>> let idA = Id 1 :: IdA -- -- >>> let idB = Id 1 :: IdB -- -- >>> let idC = Id "C1" :: IdC -- -- >>> idA -- 1 -- -- >>> idB -- 1 -- -- >>> idC -- "C1" -- -- >>> idA == idA -- True -- -- >>> -- idA == idB -- Compile error as: Couldn't match type ‘B’ with ‘A’ ---- -- Hashes, on the otherhand, can be compared: -- --
-- >>> import Data.Hashable -- -- >>> hash idA == hash idB -- True --newtype Id a b Id :: b -> Id a b [unId] :: Id a b -> b -- | Type encoding for a lookup table from entity Ids to -- corresponding entities. -- --
-- >>> data A = A Int String deriving Show -- -- >>> type IdA = Id A Int -- -- >>> let a1 = A 1 "a1" -- -- >>> let a2 = A 2 "a2" -- -- >>> let a3 = A 3 "a3" -- -- >>> let table = HM.fromList [(Id 1, a1), (Id 2, a2), (Id 3, a3)] :: IdLookup A Int -- -- >>> HM.lookup (Id 1) table -- Just (A 1 "a1") --type IdLookup a b = HashMap (Id a b) a module Haspara.Internal.Quantity -- | Type encoding for common quantity values with given scaling (digits -- after the decimal point). -- --
-- >>> 42 :: Quantity 0 -- 42 -- -- >>> 42 :: Quantity 1 -- 42.0 -- -- >>> 42 :: Quantity 2 -- 42.00 -- -- >>> 41 + 1 :: Quantity 2 -- 42.00 -- -- >>> 43 - 1 :: Quantity 2 -- 42.00 -- -- >>> 2 * 3 * 7 :: Quantity 2 -- 42.00 -- -- >>> negate (-42) :: Quantity 2 -- 42.00 -- -- >>> abs (-42) :: Quantity 2 -- 42.00 -- -- >>> signum (-42) :: Quantity 2 -- -1.00 -- -- >>> fromInteger 42 :: Quantity 2 -- 42.00 -- -- >>> quantity 0.415 :: Quantity 2 -- 0.42 -- -- >>> quantity 0.425 :: Quantity 2 -- 0.42 -- -- >>> quantityLossless 0.42 :: Either String (Quantity 2) -- Right 0.42 -- -- >>> quantityLossless 0.415 :: Either String (Quantity 2) -- Left "Underflow while trying to create quantity: 0.415" --newtype Quantity (s :: Nat) MkQuantity :: Decimal RoundHalfEven s Integer -> Quantity (s :: Nat) [unQuantity] :: Quantity (s :: Nat) -> Decimal RoundHalfEven s Integer -- | Constructs Quantity values from Scientific values in a -- lossy way. -- -- This function uses quantityAux in case that the lossless -- attempt fails. We could have used quantityAux directly. -- However, quantityAux is doing too much (see -- roundScientific). Therefore, we are first attempting a lossless -- construction (see quantityLossless) and we fallback to -- quantityAux in case the lossless construction fails. -- --
-- >>> quantity 0 :: Quantity 0 -- 0 -- -- >>> quantity 0 :: Quantity 1 -- 0.0 -- -- >>> quantity 0 :: Quantity 2 -- 0.00 -- -- >>> quantity 0.04 :: Quantity 1 -- 0.0 -- -- >>> quantity 0.05 :: Quantity 1 -- 0.0 -- -- >>> quantity 0.06 :: Quantity 1 -- 0.1 -- -- >>> quantity 0.14 :: Quantity 1 -- 0.1 -- -- >>> quantity 0.15 :: Quantity 1 -- 0.2 -- -- >>> quantity 0.16 :: Quantity 1 -- 0.2 -- -- >>> quantity 0.04 :: Quantity 2 -- 0.04 -- -- >>> quantity 0.05 :: Quantity 2 -- 0.05 -- -- >>> quantity 0.06 :: Quantity 2 -- 0.06 -- -- >>> quantity 0.14 :: Quantity 2 -- 0.14 -- -- >>> quantity 0.15 :: Quantity 2 -- 0.15 -- -- >>> quantity 0.16 :: Quantity 2 -- 0.16 -- -- >>> quantity 0.04 :: Quantity 3 -- 0.040 -- -- >>> quantity 0.05 :: Quantity 3 -- 0.050 -- -- >>> quantity 0.06 :: Quantity 3 -- 0.060 -- -- >>> quantity 0.14 :: Quantity 3 -- 0.140 -- -- >>> quantity 0.15 :: Quantity 3 -- 0.150 -- -- >>> quantity 0.16 :: Quantity 3 -- 0.160 --quantity :: KnownNat s => Scientific -> Quantity s -- | Auxiliary function for quantity implementation. -- -- See quantity why we need this function and why we haven't used -- it as the direct implementation of quantity. -- -- Call-sites should avoid using this function directly due to its -- performance characteristics. quantityAux :: forall s. KnownNat s => Scientific -> Quantity s -- | Constructs Quantity values from Scientific values in a -- lossy way. -- --
-- >>> quantityLossless 0 :: Either String (Quantity 0) -- Right 0 -- -- >>> quantityLossless 0 :: Either String (Quantity 1) -- Right 0.0 -- -- >>> quantityLossless 0 :: Either String (Quantity 2) -- Right 0.00 -- -- >>> quantityLossless 0.04 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 4.0e-2" -- -- >>> quantityLossless 0.05 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 5.0e-2" -- -- >>> quantityLossless 0.06 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 6.0e-2" -- -- >>> quantityLossless 0.14 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 0.14" -- -- >>> quantityLossless 0.15 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 0.15" -- -- >>> quantityLossless 0.16 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 0.16" -- -- >>> quantityLossless 0.04 :: Either String (Quantity 2) -- Right 0.04 -- -- >>> quantityLossless 0.05 :: Either String (Quantity 2) -- Right 0.05 -- -- >>> quantityLossless 0.06 :: Either String (Quantity 2) -- Right 0.06 -- -- >>> quantityLossless 0.14 :: Either String (Quantity 2) -- Right 0.14 -- -- >>> quantityLossless 0.15 :: Either String (Quantity 2) -- Right 0.15 -- -- >>> quantityLossless 0.16 :: Either String (Quantity 2) -- Right 0.16 -- -- >>> quantityLossless 0.04 :: Either String (Quantity 3) -- Right 0.040 -- -- >>> quantityLossless 0.05 :: Either String (Quantity 3) -- Right 0.050 -- -- >>> quantityLossless 0.06 :: Either String (Quantity 3) -- Right 0.060 -- -- >>> quantityLossless 0.14 :: Either String (Quantity 3) -- Right 0.140 -- -- >>> quantityLossless 0.15 :: Either String (Quantity 3) -- Right 0.150 -- -- >>> quantityLossless 0.16 :: Either String (Quantity 3) -- Right 0.160 --quantityLossless :: (KnownNat s, MonadError String m) => Scientific -> m (Quantity s) -- | Rounds given quantity by k digits. -- --
-- >>> roundQuantity (quantity 0.415 :: Quantity 3) :: Quantity 2 -- 0.42 -- -- >>> roundQuantity (quantity 0.425 :: Quantity 3) :: Quantity 2 -- 0.42 --roundQuantity :: KnownNat k => Quantity (n + k) -> Quantity n -- | Multiplies two quantities with different scales and rounds back to the -- scale of the frst operand. -- --
-- >>> times (quantity 0.42 :: Quantity 2) (quantity 0.42 :: Quantity 2) -- 0.18 --times :: (KnownNat s, KnownNat k) => Quantity s -> Quantity k -> Quantity s -- | Multiplies two quantities with different scales. -- --
-- >>> timesLossless (quantity 0.42 :: Quantity 2) (quantity 0.42 :: Quantity 2) -- 0.1764 --timesLossless :: (KnownNat s, KnownNat k) => Quantity s -> Quantity k -> Quantity (s + k) -- | Rounds a given scientific into a new scientific with given max digits -- after decimal point. -- -- This uses half-even rounding method. -- --
-- >>> roundScientific 0 0.4 -- 0.0 -- -- >>> roundScientific 0 0.5 -- 0.0 -- -- >>> roundScientific 0 0.6 -- 1.0 -- -- >>> roundScientific 0 1.4 -- 1.0 -- -- >>> roundScientific 0 1.5 -- 2.0 -- -- >>> roundScientific 0 1.6 -- 2.0 -- -- >>> roundScientific 1 0.04 -- 0.0 -- -- >>> roundScientific 1 0.05 -- 0.0 -- -- >>> roundScientific 1 0.06 -- 0.1 -- -- >>> roundScientific 1 0.14 -- 0.1 -- -- >>> roundScientific 1 0.15 -- 0.2 -- -- >>> roundScientific 1 0.16 -- 0.2 -- -- >>> roundScientific 1 3.650 -- 3.6 -- -- >>> roundScientific 1 3.740 -- 3.7 -- -- >>> roundScientific 1 3.749 -- 3.7 -- -- >>> roundScientific 1 3.750 -- 3.8 -- -- >>> roundScientific 1 3.751 -- 3.8 -- -- >>> roundScientific 1 3.760 -- 3.8 -- -- >>> roundScientific 1 (-3.650) -- -3.6 -- -- >>> roundScientific 1 (-3.740) -- -3.7 -- -- >>> roundScientific 1 (-3.749) -- -3.7 -- -- >>> roundScientific 1 (-3.750) -- -3.8 -- -- >>> roundScientific 1 (-3.751) -- -3.8 -- -- >>> roundScientific 1 (-3.760) -- -3.8 ---- -- TODO: Refactor to improve the performance of this function. roundScientific :: Int -> Scientific -> Scientific instance GHC.TypeNats.KnownNat s => GHC.Num.Num (Haspara.Internal.Quantity.Quantity s) instance GHC.Generics.Generic (Haspara.Internal.Quantity.Quantity s) instance GHC.Classes.Ord (Haspara.Internal.Quantity.Quantity s) instance GHC.Classes.Eq (Haspara.Internal.Quantity.Quantity s) instance Language.Haskell.TH.Syntax.Lift (Numeric.Decimal.Internal.Decimal Numeric.Decimal.RoundHalfEven s GHC.Integer.Type.Integer) instance Language.Haskell.TH.Syntax.Lift (Haspara.Internal.Quantity.Quantity s) instance GHC.TypeNats.KnownNat s => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Internal.Quantity.Quantity s) instance GHC.TypeNats.KnownNat s => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Internal.Quantity.Quantity s) instance GHC.TypeNats.KnownNat s => GHC.Num.Num (Numeric.Decimal.BoundedArithmetic.Arith (Haspara.Internal.Quantity.Quantity s)) instance GHC.TypeNats.KnownNat s => GHC.Real.Fractional (Numeric.Decimal.BoundedArithmetic.Arith (Haspara.Internal.Quantity.Quantity s)) instance GHC.TypeNats.KnownNat s => GHC.Show.Show (Haspara.Internal.Quantity.Quantity s) -- | This module provides internal definitions for modeling and working -- with FX rates. module Haspara.Internal.FXQuote -- | Type encoding for FX rates. data FXQuote (s :: Nat) MkFXQuote :: !Date -> !CurrencyPair -> !Refined Positive (Quantity s) -> FXQuote (s :: Nat) -- | Actual date of the FX rate. [fxQuoteDate] :: FXQuote (s :: Nat) -> !Date -- | Currency pair of the FX rate. [fxQuotePair] :: FXQuote (s :: Nat) -> !CurrencyPair -- | Rate value of the FX rate. [fxQuoteRate] :: FXQuote (s :: Nat) -> !Refined Positive (Quantity s) -- | Smart constructor for FXQuote values within MonadError -- context. fxquote :: (KnownNat s, MonadError String m) => Date -> Currency -> Currency -> Scientific -> m (FXQuote s) -- | Smart constructor for FXQuote values within MonadFail -- context. fxquoteFail :: (KnownNat s, MonadFail m) => Date -> Currency -> Currency -> Scientific -> m (FXQuote s) -- | Unsafe FXQuote constructor that errors if it fails. fxquoteUnsafe :: KnownNat s => Date -> Currency -> Currency -> Scientific -> FXQuote s instance GHC.Classes.Ord (Haspara.Internal.FXQuote.FXQuote s) instance GHC.Classes.Eq (Haspara.Internal.FXQuote.FXQuote s) instance GHC.TypeNats.KnownNat s => GHC.Show.Show (Haspara.Internal.FXQuote.FXQuote s) instance GHC.TypeNats.KnownNat s => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Internal.FXQuote.FXQuote s) instance GHC.TypeNats.KnownNat s => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Internal.FXQuote.FXQuote s) module Haspara.Internal.Money data Money (s :: Nat) MoneySome :: Date -> Currency -> Quantity s -> Money (s :: Nat) MoneyZero :: Money (s :: Nat) MoneyFail :: String -> Money (s :: Nat) mkMoney :: KnownNat s => Date -> Currency -> Quantity s -> Money s mkMoneyFromScientific :: KnownNat s => Date -> Currency -> Scientific -> Money s moneyDate :: KnownNat s => Money s -> Maybe Date moneyCurrency :: KnownNat s => Money s -> Maybe Currency moneyQuantity :: KnownNat s => Money s -> Maybe (Quantity s) -- | Converts the given Money value to another given currency with -- the given rate. -- --
-- >>> import Haspara -- -- >>> let eur = either error id $ currency "EUR" -- -- >>> let usd = either error id $ currency "USD" -- -- >>> let date = read "2021-01-01" :: Date -- -- >>> let eurmoney = mkMoney date eur (quantity 0.42 :: Quantity 2) :: Money 2 -- -- >>> convert eurmoney eur (quantity 1 :: Quantity 4) -- MoneySome 2021-01-01 EUR 0.42 -- -- >>> convert eurmoney usd (quantity 1 :: Quantity 4) -- MoneySome 2021-01-01 USD 0.42 -- -- >>> convert eurmoney usd (quantity 1.1516 :: Quantity 4) -- MoneySome 2021-01-01 USD 0.48 --convert :: (KnownNat s, KnownNat k) => Money s -> Currency -> Quantity k -> Money s -- | Converts the given Money value to another currency with the -- given FXQuote. convertWithQuote :: (KnownNat s, KnownNat k) => Money s -> FXQuote k -> Money s instance GHC.TypeNats.KnownNat s => GHC.Show.Show (Haspara.Internal.Money.Money s) instance GHC.Classes.Ord (Haspara.Internal.Money.Money s) instance GHC.Classes.Eq (Haspara.Internal.Money.Money s) instance GHC.TypeNats.KnownNat s => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Internal.Money.Money s) instance GHC.TypeNats.KnownNat s => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Internal.Money.Money s) module Haspara.Internal.FXQuoteDatabase type FXQuoteDatabase (n :: Nat) = HashMap CurrencyPair (FXQuotePairDatabase n) data FXQuotePairDatabase (n :: Nat) FXQuotePairDatabase :: !CurrencyPair -> !HashMap Date (FXQuote n) -> !Date -> !Date -> FXQuotePairDatabase (n :: Nat) [fxQuotePairDatabasePair] :: FXQuotePairDatabase (n :: Nat) -> !CurrencyPair [fxQuotePairDatabaseTable] :: FXQuotePairDatabase (n :: Nat) -> !HashMap Date (FXQuote n) [fxQuotePairDatabaseSince] :: FXQuotePairDatabase (n :: Nat) -> !Date [fxQuotePairDatabaseUntil] :: FXQuotePairDatabase (n :: Nat) -> !Date findFXQuote :: KnownNat n => FXQuoteDatabase n -> CurrencyPair -> Date -> Maybe (FXQuote n) findFXQuoteAux :: KnownNat n => Date -> FXQuotePairDatabase n -> Maybe (FXQuote n) -- | This module provides base data definitions and functions for -- Haspara library. module Haspara.FXQuote -- | Type encoding for FX rates. data FXQuote (s :: Nat) -- | Actual date of the FX rate. fxQuoteDate :: FXQuote s -> Date -- | Currency pair of the FX rate. fxQuotePair :: FXQuote s -> CurrencyPair -- | Rate value of the FX rate. fxQuoteRate :: FXQuote s -> Refined Positive (Quantity s) -- | Smart constructor for FXQuote values within MonadError -- context. fxquote :: (KnownNat s, MonadError String m) => Date -> Currency -> Currency -> Scientific -> m (FXQuote s) -- | Smart constructor for FXQuote values within MonadFail -- context. fxquoteFail :: (KnownNat s, MonadFail m) => Date -> Currency -> Currency -> Scientific -> m (FXQuote s) type FXQuoteDatabase (n :: Nat) = HashMap CurrencyPair (FXQuotePairDatabase n) data FXQuotePairDatabase (n :: Nat) FXQuotePairDatabase :: !CurrencyPair -> !HashMap Date (FXQuote n) -> !Date -> !Date -> FXQuotePairDatabase (n :: Nat) [fxQuotePairDatabasePair] :: FXQuotePairDatabase (n :: Nat) -> !CurrencyPair [fxQuotePairDatabaseTable] :: FXQuotePairDatabase (n :: Nat) -> !HashMap Date (FXQuote n) [fxQuotePairDatabaseSince] :: FXQuotePairDatabase (n :: Nat) -> !Date [fxQuotePairDatabaseUntil] :: FXQuotePairDatabase (n :: Nat) -> !Date findFXQuote :: KnownNat n => FXQuoteDatabase n -> CurrencyPair -> Date -> Maybe (FXQuote n) -- | This module provides base data definitions and functions for -- Haspara library. module Haspara.Money data Money (s :: Nat) MoneySome :: Date -> Currency -> Quantity s -> Money (s :: Nat) MoneyZero :: Money (s :: Nat) MoneyFail :: String -> Money (s :: Nat) moneyDate :: KnownNat s => Money s -> Maybe Date moneyCurrency :: KnownNat s => Money s -> Maybe Currency moneyQuantity :: KnownNat s => Money s -> Maybe (Quantity s) mkMoney :: KnownNat s => Date -> Currency -> Quantity s -> Money s mkMoneyFromScientific :: KnownNat s => Date -> Currency -> Scientific -> Money s -- | Converts the given Money value to another given currency with -- the given rate. -- --
-- >>> import Haspara -- -- >>> let eur = either error id $ currency "EUR" -- -- >>> let usd = either error id $ currency "USD" -- -- >>> let date = read "2021-01-01" :: Date -- -- >>> let eurmoney = mkMoney date eur (quantity 0.42 :: Quantity 2) :: Money 2 -- -- >>> convert eurmoney eur (quantity 1 :: Quantity 4) -- MoneySome 2021-01-01 EUR 0.42 -- -- >>> convert eurmoney usd (quantity 1 :: Quantity 4) -- MoneySome 2021-01-01 USD 0.42 -- -- >>> convert eurmoney usd (quantity 1.1516 :: Quantity 4) -- MoneySome 2021-01-01 USD 0.48 --convert :: (KnownNat s, KnownNat k) => Money s -> Currency -> Quantity k -> Money s -- | Converts the given Money value to another currency with the -- given FXQuote. convertWithQuote :: (KnownNat s, KnownNat k) => Money s -> FXQuote k -> Money s -- | This module provides definitions and functions to encode and work on -- quantities with fixed decimal points. module Haspara.Quantity -- | Type encoding for common quantity values with given scaling (digits -- after the decimal point). -- --
-- >>> 42 :: Quantity 0 -- 42 -- -- >>> 42 :: Quantity 1 -- 42.0 -- -- >>> 42 :: Quantity 2 -- 42.00 -- -- >>> 41 + 1 :: Quantity 2 -- 42.00 -- -- >>> 43 - 1 :: Quantity 2 -- 42.00 -- -- >>> 2 * 3 * 7 :: Quantity 2 -- 42.00 -- -- >>> negate (-42) :: Quantity 2 -- 42.00 -- -- >>> abs (-42) :: Quantity 2 -- 42.00 -- -- >>> signum (-42) :: Quantity 2 -- -1.00 -- -- >>> fromInteger 42 :: Quantity 2 -- 42.00 -- -- >>> quantity 0.415 :: Quantity 2 -- 0.42 -- -- >>> quantity 0.425 :: Quantity 2 -- 0.42 -- -- >>> quantityLossless 0.42 :: Either String (Quantity 2) -- Right 0.42 -- -- >>> quantityLossless 0.415 :: Either String (Quantity 2) -- Left "Underflow while trying to create quantity: 0.415" --data Quantity (s :: Nat) unQuantity :: Quantity s -> Decimal RoundHalfEven s Integer -- | Constructs Quantity values from Scientific values in a -- lossy way. -- -- This function uses quantityAux in case that the lossless -- attempt fails. We could have used quantityAux directly. -- However, quantityAux is doing too much (see -- roundScientific). Therefore, we are first attempting a lossless -- construction (see quantityLossless) and we fallback to -- quantityAux in case the lossless construction fails. -- --
-- >>> quantity 0 :: Quantity 0 -- 0 -- -- >>> quantity 0 :: Quantity 1 -- 0.0 -- -- >>> quantity 0 :: Quantity 2 -- 0.00 -- -- >>> quantity 0.04 :: Quantity 1 -- 0.0 -- -- >>> quantity 0.05 :: Quantity 1 -- 0.0 -- -- >>> quantity 0.06 :: Quantity 1 -- 0.1 -- -- >>> quantity 0.14 :: Quantity 1 -- 0.1 -- -- >>> quantity 0.15 :: Quantity 1 -- 0.2 -- -- >>> quantity 0.16 :: Quantity 1 -- 0.2 -- -- >>> quantity 0.04 :: Quantity 2 -- 0.04 -- -- >>> quantity 0.05 :: Quantity 2 -- 0.05 -- -- >>> quantity 0.06 :: Quantity 2 -- 0.06 -- -- >>> quantity 0.14 :: Quantity 2 -- 0.14 -- -- >>> quantity 0.15 :: Quantity 2 -- 0.15 -- -- >>> quantity 0.16 :: Quantity 2 -- 0.16 -- -- >>> quantity 0.04 :: Quantity 3 -- 0.040 -- -- >>> quantity 0.05 :: Quantity 3 -- 0.050 -- -- >>> quantity 0.06 :: Quantity 3 -- 0.060 -- -- >>> quantity 0.14 :: Quantity 3 -- 0.140 -- -- >>> quantity 0.15 :: Quantity 3 -- 0.150 -- -- >>> quantity 0.16 :: Quantity 3 -- 0.160 --quantity :: KnownNat s => Scientific -> Quantity s -- | Constructs Quantity values from Scientific values in a -- lossy way. -- --
-- >>> quantityLossless 0 :: Either String (Quantity 0) -- Right 0 -- -- >>> quantityLossless 0 :: Either String (Quantity 1) -- Right 0.0 -- -- >>> quantityLossless 0 :: Either String (Quantity 2) -- Right 0.00 -- -- >>> quantityLossless 0.04 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 4.0e-2" -- -- >>> quantityLossless 0.05 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 5.0e-2" -- -- >>> quantityLossless 0.06 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 6.0e-2" -- -- >>> quantityLossless 0.14 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 0.14" -- -- >>> quantityLossless 0.15 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 0.15" -- -- >>> quantityLossless 0.16 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 0.16" -- -- >>> quantityLossless 0.04 :: Either String (Quantity 2) -- Right 0.04 -- -- >>> quantityLossless 0.05 :: Either String (Quantity 2) -- Right 0.05 -- -- >>> quantityLossless 0.06 :: Either String (Quantity 2) -- Right 0.06 -- -- >>> quantityLossless 0.14 :: Either String (Quantity 2) -- Right 0.14 -- -- >>> quantityLossless 0.15 :: Either String (Quantity 2) -- Right 0.15 -- -- >>> quantityLossless 0.16 :: Either String (Quantity 2) -- Right 0.16 -- -- >>> quantityLossless 0.04 :: Either String (Quantity 3) -- Right 0.040 -- -- >>> quantityLossless 0.05 :: Either String (Quantity 3) -- Right 0.050 -- -- >>> quantityLossless 0.06 :: Either String (Quantity 3) -- Right 0.060 -- -- >>> quantityLossless 0.14 :: Either String (Quantity 3) -- Right 0.140 -- -- >>> quantityLossless 0.15 :: Either String (Quantity 3) -- Right 0.150 -- -- >>> quantityLossless 0.16 :: Either String (Quantity 3) -- Right 0.160 --quantityLossless :: (KnownNat s, MonadError String m) => Scientific -> m (Quantity s) -- | Rounds given quantity by k digits. -- --
-- >>> roundQuantity (quantity 0.415 :: Quantity 3) :: Quantity 2 -- 0.42 -- -- >>> roundQuantity (quantity 0.425 :: Quantity 3) :: Quantity 2 -- 0.42 --roundQuantity :: KnownNat k => Quantity (n + k) -> Quantity n -- | Multiplies two quantities with different scales and rounds back to the -- scale of the frst operand. -- --
-- >>> times (quantity 0.42 :: Quantity 2) (quantity 0.42 :: Quantity 2) -- 0.18 --times :: (KnownNat s, KnownNat k) => Quantity s -> Quantity k -> Quantity s -- | Multiplies two quantities with different scales. -- --
-- >>> timesLossless (quantity 0.42 :: Quantity 2) (quantity 0.42 :: Quantity 2) -- 0.1764 --timesLossless :: (KnownNat s, KnownNat k) => Quantity s -> Quantity k -> Quantity (s + k) module Haspara.Accounting.Types type UnsignedQuantity s = Refined NonNegative (Quantity s) module Haspara module Haspara.Accounting.Event -- | Encoding of an increment/decrement event. -- --
-- >>> :set -XDataKinds
--
-- >>> import Refined
--
-- >>> let date = read "2021-01-01"
--
-- >>> let oid = 1 :: Int
--
-- >>> let qty = $$(refineTH 42) :: UnsignedQuantity 2
--
-- >>> let event = EventDecrement date oid qty
--
-- >>> let json = Aeson.encode event
--
-- >>> json
-- "{\"qty\":42.0,\"obj\":1,\"date\":\"2021-01-01\",\"type\":\"DECREMENT\"}"
--
-- >>> Aeson.decode json :: Maybe (Event Int 2)
-- Just (EventDecrement 2021-01-01 1 (Refined 42.00))
--
-- >>> Aeson.decode json == Just event
-- True
--
data Event o (s :: Nat)
EventDecrement :: Date -> o -> UnsignedQuantity s -> Event o (s :: Nat)
EventIncrement :: Date -> o -> UnsignedQuantity s -> Event o (s :: Nat)
eventDate :: KnownNat s => Event o s -> Date
eventObject :: KnownNat s => Event o s -> o
negateEvent :: KnownNat s => Event o s -> Event o s
mkEvent :: (MonadError String m, KnownNat s) => Date -> o -> Quantity s -> m (Event o s)
instance (GHC.Show.Show o, GHC.TypeNats.KnownNat s) => GHC.Show.Show (Haspara.Accounting.Event.Event o s)
instance GHC.Classes.Ord o => GHC.Classes.Ord (Haspara.Accounting.Event.Event o s)
instance GHC.Classes.Eq o => GHC.Classes.Eq (Haspara.Accounting.Event.Event o s)
instance (Data.Aeson.Types.FromJSON.FromJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Event.Event o s)
instance (Data.Aeson.Types.ToJSON.ToJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Event.Event o s)
module Haspara.Accounting.Entry
-- | Encoding of a posting entry.
--
--
-- >>> :set -XDataKinds
--
-- >>> import Refined
--
-- >>> let date = read "2021-01-01"
--
-- >>> let oid = 1 :: Int
--
-- >>> let qty = $$(refineTH 42) :: UnsignedQuantity 2
--
-- >>> let entry = EntryDebit date oid qty
--
-- >>> let json = Aeson.encode entry
--
-- >>> json
-- "{\"qty\":42.0,\"obj\":1,\"date\":\"2021-01-01\",\"type\":\"DEBIT\"}"
--
-- >>> Aeson.decode json :: Maybe (Entry Int 2)
-- Just (EntryDebit 2021-01-01 1 (Refined 42.00))
--
-- >>> Aeson.decode json == Just entry
-- True
--
data Entry o (s :: Nat)
EntryDebit :: Date -> o -> UnsignedQuantity s -> Entry o (s :: Nat)
EntryCredit :: Date -> o -> UnsignedQuantity s -> Entry o (s :: Nat)
entryDate :: KnownNat s => Entry o s -> Date
entryQuantity :: KnownNat s => Entry o s -> Quantity s
entryObject :: KnownNat s => Entry o s -> o
entryDebit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)
entryCredit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)
-- | TODO: table
buildEntry :: KnownNat s => Event o s -> AccountKind -> Entry o s
instance (GHC.Show.Show o, GHC.TypeNats.KnownNat s) => GHC.Show.Show (Haspara.Accounting.Entry.Entry o s)
instance GHC.Classes.Ord o => GHC.Classes.Ord (Haspara.Accounting.Entry.Entry o s)
instance GHC.Classes.Eq o => GHC.Classes.Eq (Haspara.Accounting.Entry.Entry o s)
instance (Data.Aeson.Types.FromJSON.FromJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Entry.Entry o s)
instance (Data.Aeson.Types.ToJSON.ToJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Entry.Entry o s)
module Haspara.Accounting.Posting
-- | Type encoding for a posting.
--
--
-- >>> :set -XDataKinds
--
-- >>> import Haspara.Accounting
--
-- >>> import Refined
--
-- >>> import qualified Data.Aeson as Aeson
--
-- >>> import qualified Data.List.NonEmpty as NE
--
-- >>> let date = read "2021-01-01"
--
-- >>> let oid = 1 :: Int
--
-- >>> let qty = $$(refineTH 42) :: UnsignedQuantity 2
--
-- >>> let event = EventDecrement date oid qty
--
-- >>> let account = Account AccountKindAsset ("Cash" :: String, 1 ::Int)
--
-- >>> let posting = Posting . NE.fromList $ [(event, account)]
--
-- >>> let json = Aeson.encode posting
--
-- >>> json
-- "[[{\"qty\":42.0,\"obj\":1,\"date\":\"2021-01-01\",\"type\":\"DECREMENT\"},{\"kind\":\"ASSET\",\"object\":[\"Cash\",1]}]]"
--
-- >>> Aeson.decode json :: Maybe (Posting (String, Int) Int 2)
-- Just (Posting ((EventDecrement 2021-01-01 1 (Refined 42.00),Account {accountKind = AccountKindAsset, accountObject = ("Cash",1)}) :| []))
--
-- >>> Aeson.decode json == Just posting
-- True
--
newtype Posting a o (s :: Nat)
Posting :: NonEmpty (Event o s, Account a) -> Posting a o (s :: Nat)
postingEvents :: KnownNat s => Posting a o s -> [o]
post :: KnownNat s => Posting a o s -> [(Account a, Entry o s)]
instance (Data.Aeson.Types.ToJSON.ToJSON a, Data.Aeson.Types.ToJSON.ToJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Posting.Posting a o s)
instance (GHC.TypeNats.KnownNat s, Data.Aeson.Types.FromJSON.FromJSON o, Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Posting.Posting a o s)
instance (GHC.TypeNats.KnownNat s, GHC.Show.Show o, GHC.Show.Show a) => GHC.Show.Show (Haspara.Accounting.Posting.Posting a o s)
instance (GHC.Classes.Ord o, GHC.Classes.Ord a) => GHC.Classes.Ord (Haspara.Accounting.Posting.Posting a o s)
instance GHC.Generics.Generic (Haspara.Accounting.Posting.Posting a o s)
instance (GHC.Classes.Eq o, GHC.Classes.Eq a) => GHC.Classes.Eq (Haspara.Accounting.Posting.Posting a o s)
module Haspara.Accounting.Ledger
data Ledger a o (s :: Nat)
Ledger :: !Account a -> !Quantity s -> !Quantity s -> ![LedgerItem o s] -> Ledger a o (s :: Nat)
[ledgerAccount] :: Ledger a o (s :: Nat) -> !Account a
[ledgerOpening] :: Ledger a o (s :: Nat) -> !Quantity s
[ledgerClosing] :: Ledger a o (s :: Nat) -> !Quantity s
[ledgerRunning] :: Ledger a o (s :: Nat) -> ![LedgerItem o s]
data LedgerItem o (s :: Nat)
LedgerItem :: !Entry o s -> !Quantity s -> LedgerItem o (s :: Nat)
[ledgerItemEntry] :: LedgerItem o (s :: Nat) -> !Entry o s
[ledgerItemBalance] :: LedgerItem o (s :: Nat) -> !Quantity s
mkLedger :: KnownNat s => Account a -> Quantity s -> [Entry o s] -> Ledger a o s
addEntry :: KnownNat s => Ledger a o s -> Entry o s -> Ledger a o s
instance (GHC.TypeNats.KnownNat s, Data.Aeson.Types.ToJSON.ToJSON o) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Ledger.LedgerItem o s)
instance (Data.Aeson.Types.FromJSON.FromJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Ledger.LedgerItem o s)
instance (GHC.Show.Show o, GHC.TypeNats.KnownNat s) => GHC.Show.Show (Haspara.Accounting.Ledger.LedgerItem o s)
instance GHC.Classes.Ord o => GHC.Classes.Ord (Haspara.Accounting.Ledger.LedgerItem o s)
instance GHC.Generics.Generic (Haspara.Accounting.Ledger.LedgerItem o s)
instance GHC.Classes.Eq o => GHC.Classes.Eq (Haspara.Accounting.Ledger.LedgerItem o s)
instance (Data.Aeson.Types.ToJSON.ToJSON o, Data.Aeson.Types.ToJSON.ToJSON a, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Ledger.Ledger a o s)
instance (GHC.TypeNats.KnownNat s, Data.Aeson.Types.FromJSON.FromJSON a, Data.Aeson.Types.FromJSON.FromJSON o) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Ledger.Ledger a o s)
instance (GHC.TypeNats.KnownNat s, GHC.Show.Show a, GHC.Show.Show o) => GHC.Show.Show (Haspara.Accounting.Ledger.Ledger a o s)
instance (GHC.Classes.Ord a, GHC.Classes.Ord o) => GHC.Classes.Ord (Haspara.Accounting.Ledger.Ledger a o s)
instance GHC.Generics.Generic (Haspara.Accounting.Ledger.Ledger a o s)
instance (GHC.Classes.Eq a, GHC.Classes.Eq o) => GHC.Classes.Eq (Haspara.Accounting.Ledger.Ledger a o s)
module Haspara.Accounting
-- | Account model.
--
--
-- >>> import Haspara.Accounting.AccountKind (AccountKind(..))
--
-- >>> import qualified Data.Aeson as Aeson
--
-- >>> let acc = Account AccountKindAsset (1 ::Int)
--
-- >>> Aeson.encode acc
-- "{\"kind\":\"ASSET\",\"object\":1}"
--
-- >>> Aeson.decode (Aeson.encode acc) :: Maybe (Account Int)
-- Just (Account {accountKind = AccountKindAsset, accountObject = 1})
--
-- >>> Aeson.decode (Aeson.encode acc) == Just acc
-- True
--
data Account o
Account :: !AccountKind -> !o -> Account o
[accountKind] :: Account o -> !AccountKind
[accountObject] :: Account o -> !o
data AccountKind
AccountKindAsset :: AccountKind
AccountKindLiability :: AccountKind
AccountKindEquity :: AccountKind
AccountKindRevenue :: AccountKind
AccountKindExpense :: AccountKind
accountKindText :: AccountKind -> Text
-- | Encoding of a posting entry.
--
--
-- >>> :set -XDataKinds
--
-- >>> import Refined
--
-- >>> let date = read "2021-01-01"
--
-- >>> let oid = 1 :: Int
--
-- >>> let qty = $$(refineTH 42) :: UnsignedQuantity 2
--
-- >>> let entry = EntryDebit date oid qty
--
-- >>> let json = Aeson.encode entry
--
-- >>> json
-- "{\"qty\":42.0,\"obj\":1,\"date\":\"2021-01-01\",\"type\":\"DEBIT\"}"
--
-- >>> Aeson.decode json :: Maybe (Entry Int 2)
-- Just (EntryDebit 2021-01-01 1 (Refined 42.00))
--
-- >>> Aeson.decode json == Just entry
-- True
--
data Entry o (s :: Nat)
EntryDebit :: Date -> o -> UnsignedQuantity s -> Entry o (s :: Nat)
EntryCredit :: Date -> o -> UnsignedQuantity s -> Entry o (s :: Nat)
-- | TODO: table
buildEntry :: KnownNat s => Event o s -> AccountKind -> Entry o s
-- | Encoding of an increment/decrement event.
--
--
-- >>> :set -XDataKinds
--
-- >>> import Refined
--
-- >>> let date = read "2021-01-01"
--
-- >>> let oid = 1 :: Int
--
-- >>> let qty = $$(refineTH 42) :: UnsignedQuantity 2
--
-- >>> let event = EventDecrement date oid qty
--
-- >>> let json = Aeson.encode event
--
-- >>> json
-- "{\"qty\":42.0,\"obj\":1,\"date\":\"2021-01-01\",\"type\":\"DECREMENT\"}"
--
-- >>> Aeson.decode json :: Maybe (Event Int 2)
-- Just (EventDecrement 2021-01-01 1 (Refined 42.00))
--
-- >>> Aeson.decode json == Just event
-- True
--
data Event o (s :: Nat)
EventDecrement :: Date -> o -> UnsignedQuantity s -> Event o (s :: Nat)
EventIncrement :: Date -> o -> UnsignedQuantity s -> Event o (s :: Nat)
eventDate :: KnownNat s => Event o s -> Date
eventObject :: KnownNat s => Event o s -> o
negateEvent :: KnownNat s => Event o s -> Event o s
mkEvent :: (MonadError String m, KnownNat s) => Date -> o -> Quantity s -> m (Event o s)
-- | Type encoding for a posting.
--
--
-- >>> :set -XDataKinds
--
-- >>> import Haspara.Accounting
--
-- >>> import Refined
--
-- >>> import qualified Data.Aeson as Aeson
--
-- >>> import qualified Data.List.NonEmpty as NE
--
-- >>> let date = read "2021-01-01"
--
-- >>> let oid = 1 :: Int
--
-- >>> let qty = $$(refineTH 42) :: UnsignedQuantity 2
--
-- >>> let event = EventDecrement date oid qty
--
-- >>> let account = Account AccountKindAsset ("Cash" :: String, 1 ::Int)
--
-- >>> let posting = Posting . NE.fromList $ [(event, account)]
--
-- >>> let json = Aeson.encode posting
--
-- >>> json
-- "[[{\"qty\":42.0,\"obj\":1,\"date\":\"2021-01-01\",\"type\":\"DECREMENT\"},{\"kind\":\"ASSET\",\"object\":[\"Cash\",1]}]]"
--
-- >>> Aeson.decode json :: Maybe (Posting (String, Int) Int 2)
-- Just (Posting ((EventDecrement 2021-01-01 1 (Refined 42.00),Account {accountKind = AccountKindAsset, accountObject = ("Cash",1)}) :| []))
--
-- >>> Aeson.decode json == Just posting
-- True
--
newtype Posting a o (s :: Nat)
Posting :: NonEmpty (Event o s, Account a) -> Posting a o (s :: Nat)
postingEvents :: KnownNat s => Posting a o s -> [o]
post :: KnownNat s => Posting a o s -> [(Account a, Entry o s)]
type UnsignedQuantity s = Refined NonNegative (Quantity s)
data Ledger a o (s :: Nat)
Ledger :: !Account a -> !Quantity s -> !Quantity s -> ![LedgerItem o s] -> Ledger a o (s :: Nat)
[ledgerAccount] :: Ledger a o (s :: Nat) -> !Account a
[ledgerOpening] :: Ledger a o (s :: Nat) -> !Quantity s
[ledgerClosing] :: Ledger a o (s :: Nat) -> !Quantity s
[ledgerRunning] :: Ledger a o (s :: Nat) -> ![LedgerItem o s]
data LedgerItem o (s :: Nat)
LedgerItem :: !Entry o s -> !Quantity s -> LedgerItem o (s :: Nat)
[ledgerItemEntry] :: LedgerItem o (s :: Nat) -> !Entry o s
[ledgerItemBalance] :: LedgerItem o (s :: Nat) -> !Quantity s
mkLedger :: KnownNat s => Account a -> Quantity s -> [Entry o s] -> Ledger a o s
addEntry :: KnownNat s => Ledger a o s -> Entry o s -> Ledger a o s
entryDate :: KnownNat s => Entry o s -> Date
entryObject :: KnownNat s => Entry o s -> o
entryQuantity :: KnownNat s => Entry o s -> Quantity s
entryDebit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)
entryCredit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)
-- | This module provides template-haskell functions for various
-- Base definitions.
module Haspara.TH
-- | Constructs a Quantity value at compile-time using
-- -XTemplateHaskell.
--
-- -- >>> :set -XDataKinds -- -- >>> $$(quantityTH 0.00) :: Quantity 2 -- 0.00 -- -- >>> $$(quantityTH 0.09) :: Quantity 2 -- 0.09 -- -- >>> $$(quantityTH 0.009) :: Quantity 2 -- ... -- ..."Underflow while trying to create quantity: 9.0e-3" -- ... -- -- >>> $$(quantityTH 0.009) :: Quantity 3 -- 0.009 --quantityTH :: KnownNat s => Scientific -> Q (TExp (Quantity s)) -- | Constructs a Currency value at compile-time using -- -XTemplateHaskell. -- --
-- >>> $$(currencyTH "USD") -- USD -- -- >>> $$(currencyTH "usd") -- ... -- ...Currency code error! Expecting at least 3 uppercase characters, but received: "usd" -- ... --currencyTH :: Text -> Q (TExp Currency) -- | Constructs a CurrencyPair value at compile-time using -- -XTemplateHaskell. -- --
-- >>> $$(currencyPairTH "EUR" "USD") -- EUR/USD -- -- >>> $$(currencyPairTH "USD" "USD") -- ... -- ...Can not create currency pair from same currencies: USD and USD -- ... -- -- >>> $$(currencyPairTH "USD" "eur") -- ... -- ...Currency code error! Expecting at least 3 uppercase characters, but received: "eur" -- ... --currencyPairTH :: Text -> Text -> Q (TExp CurrencyPair)