-- 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.2 -- | This module provides definitions for modeling and working with -- currencies. module Haspara.Currency -- | Type encoding for currency symbol values with a syntax of -- [A-Z]{3}[A-Z]*. -- -- Currency values can be constructed via mkCurrencyError -- that works in MonadError Text context: -- --
-- >>> mkCurrencyError "EUR" :: Either T.Text Currency -- Right EUR ---- -- ... or via mkCurrencyFail that works in MonadFail -- context: -- --
-- >>> mkCurrencyFail "EUR" :: Maybe Currency -- Just EUR ---- -- An IsString instance is provided as well which is unsafe but -- convenient: -- --
-- >>> "EUR" :: Currency -- EUR --newtype Currency MkCurrency :: Text -> Currency [currencyCode] :: Currency -> Text -- | Smart constructor for Currency values within MonadError -- context. -- --
-- >>> mkCurrencyError "" :: Either T.Text Currency -- Left "Currency code error! Expecting at least 3 uppercase ASCII letters, but received: " -- -- >>> mkCurrencyError " " :: Either T.Text Currency -- Left "Currency code error! Expecting at least 3 uppercase ASCII letters, but received: " -- -- >>> mkCurrencyError "AB" :: Either T.Text Currency -- Left "Currency code error! Expecting at least 3 uppercase ASCII letters, but received: AB" -- -- >>> mkCurrencyError " ABC " :: Either T.Text Currency -- Left "Currency code error! Expecting at least 3 uppercase ASCII letters, but received: ABC " -- -- >>> mkCurrencyError "ABC" :: Either T.Text Currency -- Right ABC --mkCurrencyError :: MonadError Text m => Text -> m Currency -- | Smart constructor for Currency values within MonadFail -- context. -- --
-- >>> mkCurrencyFail "" :: Maybe Currency -- Nothing -- -- >>> mkCurrencyFail "US" :: Maybe Currency -- Nothing -- -- >>> mkCurrencyFail "usd" :: Maybe Currency -- Nothing -- -- >>> mkCurrencyFail "USD" :: Maybe Currency -- Just USD --mkCurrencyFail :: 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"
--
-- >>> MP.runParser currencyCodeParser "Example" "ABCD"
-- Right "ABCD"
--
-- >>> MP.runParser currencyCodeParser "Example" " ABCD "
-- Left (ParseErrorBundle {bundleErrors = TrivialError 0 (Just (Tokens (' ' :| ""))) (fromList []) :| [], bundlePosState = PosState {pstateInput = " ABCD ", pstateOffset = 0, pstateSourcePos = SourcePos {sourceName = "Example", sourceLine = Pos 1, sourceColumn = Pos 1}, pstateTabWidth = Pos 8, pstateLinePrefix = ""}})
--
currencyCodeParser :: Parsec Void Text Text
-- | Type encoding of a currency pair.
--
-- CurrencyPair values are constructed via the data constructor:
--
-- -- >>> CurrencyPair "EUR" "USD" -- EUR/USD ---- -- FromJSON and ToJSON instances are provided as well: -- --
-- >>> Aeson.decode "{\"base\": \"EUR\", \"quote\": \"EUR\"}" :: Maybe CurrencyPair
-- Just EUR/EUR
--
-- >>> Aeson.encode (CurrencyPair "EUR" "USD")
-- "{\"base\":\"EUR\",\"quote\":\"USD\"}"
--
data CurrencyPair
CurrencyPair :: !Currency -> !Currency -> CurrencyPair
-- | Base currency of the currency pair. Also referred to as
-- counter currency.
[currencyPairBase] :: CurrencyPair -> !Currency
-- | Quote currency of the currency pair. Also referred to as
-- transaction currency.
[currencyPairQuote] :: CurrencyPair -> !Currency
-- | Converts a CurrencyPair to a 2-tuple of Currency values.
--
-- -- >>> toCurrencyTuple (CurrencyPair "EUR" "USD") -- (EUR,USD) --toCurrencyTuple :: CurrencyPair -> (Currency, Currency) -- | Converts a 2-tuple of Currency values to a CurrencyPair. -- --
-- >>> fromCurrencyTuple ("EUR", "USD")
-- EUR/USD
--
fromCurrencyTuple :: (Currency, Currency) -> CurrencyPair
instance Language.Haskell.TH.Syntax.Lift Haspara.Currency.Currency
instance GHC.Classes.Ord Haspara.Currency.Currency
instance Data.Hashable.Class.Hashable Haspara.Currency.Currency
instance GHC.Classes.Eq Haspara.Currency.Currency
instance Data.Aeson.Types.ToJSON.ToJSON Haspara.Currency.CurrencyPair
instance Data.Aeson.Types.FromJSON.FromJSON Haspara.Currency.CurrencyPair
instance Language.Haskell.TH.Syntax.Lift Haspara.Currency.CurrencyPair
instance GHC.Classes.Ord Haspara.Currency.CurrencyPair
instance GHC.Generics.Generic Haspara.Currency.CurrencyPair
instance GHC.Classes.Eq Haspara.Currency.CurrencyPair
instance GHC.Show.Show Haspara.Currency.CurrencyPair
instance Data.String.IsString Haspara.Currency.Currency
instance GHC.Show.Show Haspara.Currency.Currency
instance Data.Aeson.Types.FromJSON.FromJSON Haspara.Currency.Currency
instance Data.Aeson.Types.ToJSON.ToJSON Haspara.Currency.Currency
-- | This module provides helper definitions for Data.Aeson.
module Haspara.Internal.Aeson
-- | Type definition for string modifiers that uppercase a given symbol.
data UpperCase
instance Deriving.Aeson.StringModifier Haspara.Internal.Aeson.UpperCase
-- | This module provides definitions for acccounts and types of accounts
-- as they are used in accounting reporting.
module Haspara.Accounting.Account
-- | Type encoding for ledger account type.
--
-- This type covers both balance sheet and income statement account
-- types:
--
-- -- >>> Data.Aeson.decode @AccountKind "\"ASSET\"" -- Just AccountKindAsset -- -- >>> Data.Aeson.decode @AccountKind "\"LIABILITY\"" -- Just AccountKindLiability -- -- >>> Data.Aeson.decode @AccountKind "\"EQUITY\"" -- Just AccountKindEquity -- -- >>> Data.Aeson.decode @AccountKind "\"REVENUE\"" -- Just AccountKindRevenue -- -- >>> Data.Aeson.decode @AccountKind "\"EXPENSE\"" -- Just AccountKindExpense -- -- >>> Data.Aeson.encode AccountKindAsset -- "\"ASSET\"" -- -- >>> Data.Aeson.encode AccountKindLiability -- "\"LIABILITY\"" -- -- >>> Data.Aeson.encode AccountKindEquity -- "\"EQUITY\"" -- -- >>> Data.Aeson.encode AccountKindRevenue -- "\"REVENUE\"" -- -- >>> Data.Aeson.encode AccountKindExpense -- "\"EXPENSE\"" --data AccountKind AccountKindAsset :: AccountKind AccountKindLiability :: AccountKind AccountKindEquity :: AccountKind AccountKindRevenue :: AccountKind AccountKindExpense :: AccountKind -- | Provides textual representation of a given AccountKind. -- --
-- >>> accountKindText AccountKindAsset -- "Asset" -- -- >>> accountKindText AccountKindLiability -- "Liability" -- -- >>> accountKindText AccountKindEquity -- "Equity" -- -- >>> accountKindText AccountKindRevenue -- "Revenue" -- -- >>> accountKindText AccountKindExpense -- "Expense" --accountKindText :: AccountKind -> Text -- | Type encoding for account values. -- -- This definition provides both the AccountKind and an arbitrary -- object identifying the account. This arbitrary nature provides -- flexibility to use-site to use its own account identity and -- accompanying information when required. -- --
-- >>> let acc = Account AccountKindAsset (1 ::Int)
--
-- >>> Data.Aeson.encode acc
-- "{\"kind\":\"ASSET\",\"object\":1}"
--
-- >>> Data.Aeson.decode @(Account Int) (Data.Aeson.encode acc)
-- Just (Account {accountKind = AccountKindAsset, accountObject = 1})
--
-- >>> Data.Aeson.decode (Data.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 Haspara.Accounting.Account.AccountKind
instance Data.Aeson.Types.FromJSON.FromJSON Haspara.Accounting.Account.AccountKind
instance GHC.Show.Show Haspara.Accounting.Account.AccountKind
instance GHC.Classes.Ord Haspara.Accounting.Account.AccountKind
instance GHC.Generics.Generic Haspara.Accounting.Account.AccountKind
instance GHC.Classes.Eq Haspara.Accounting.Account.AccountKind
instance GHC.Enum.Enum Haspara.Accounting.Account.AccountKind
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)
instance Data.Hashable.Class.Hashable Haspara.Accounting.Account.AccountKind
-- | This module provides definitions for modeling and working with
-- quantities with fixed decimal points.
module Haspara.Quantity
-- | Type encoding for quantity values with a 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 -- -- >>> mkQuantity 0.415 :: Quantity 2 -- 0.42 -- -- >>> mkQuantity 0.425 :: Quantity 2 -- 0.42 -- -- >>> mkQuantityLossless 0.42 :: Either String (Quantity 2) -- Right 0.42 -- -- >>> mkQuantityLossless 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 -- | Type definition for unsigned Quantity values. type UnsignedQuantity s = Refined NonNegative (Quantity s) -- | Constructs Quantity values from Scientific values in a -- lossy way. -- -- This function uses mkQuantityAux in case that the lossless -- attempt fails. We could have used mkQuantityAux directly. -- However, mkQuantityAux is doing too much (see -- roundScientific). Therefore, we are first attempting a lossless -- construction (see mkQuantityLossless) and we fallback to -- mkQuantityAux in case the lossless construction fails. -- --
-- >>> mkQuantity 0 :: Quantity 0 -- 0 -- -- >>> mkQuantity 0 :: Quantity 1 -- 0.0 -- -- >>> mkQuantity 0 :: Quantity 2 -- 0.00 -- -- >>> mkQuantity 0.04 :: Quantity 1 -- 0.0 -- -- >>> mkQuantity 0.05 :: Quantity 1 -- 0.0 -- -- >>> mkQuantity 0.06 :: Quantity 1 -- 0.1 -- -- >>> mkQuantity 0.14 :: Quantity 1 -- 0.1 -- -- >>> mkQuantity 0.15 :: Quantity 1 -- 0.2 -- -- >>> mkQuantity 0.16 :: Quantity 1 -- 0.2 -- -- >>> mkQuantity 0.04 :: Quantity 2 -- 0.04 -- -- >>> mkQuantity 0.05 :: Quantity 2 -- 0.05 -- -- >>> mkQuantity 0.06 :: Quantity 2 -- 0.06 -- -- >>> mkQuantity 0.14 :: Quantity 2 -- 0.14 -- -- >>> mkQuantity 0.15 :: Quantity 2 -- 0.15 -- -- >>> mkQuantity 0.16 :: Quantity 2 -- 0.16 -- -- >>> mkQuantity 0.04 :: Quantity 3 -- 0.040 -- -- >>> mkQuantity 0.05 :: Quantity 3 -- 0.050 -- -- >>> mkQuantity 0.06 :: Quantity 3 -- 0.060 -- -- >>> mkQuantity 0.14 :: Quantity 3 -- 0.140 -- -- >>> mkQuantity 0.15 :: Quantity 3 -- 0.150 -- -- >>> mkQuantity 0.16 :: Quantity 3 -- 0.160 --mkQuantity :: KnownNat s => Scientific -> Quantity s -- | Constructs Quantity values from Scientific values in a -- lossy way. -- --
-- >>> mkQuantityLossless 0 :: Either String (Quantity 0) -- Right 0 -- -- >>> mkQuantityLossless 0 :: Either String (Quantity 1) -- Right 0.0 -- -- >>> mkQuantityLossless 0 :: Either String (Quantity 2) -- Right 0.00 -- -- >>> mkQuantityLossless 0.04 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 4.0e-2" -- -- >>> mkQuantityLossless 0.05 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 5.0e-2" -- -- >>> mkQuantityLossless 0.06 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 6.0e-2" -- -- >>> mkQuantityLossless 0.14 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 0.14" -- -- >>> mkQuantityLossless 0.15 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 0.15" -- -- >>> mkQuantityLossless 0.16 :: Either String (Quantity 1) -- Left "Underflow while trying to create quantity: 0.16" -- -- >>> mkQuantityLossless 0.04 :: Either String (Quantity 2) -- Right 0.04 -- -- >>> mkQuantityLossless 0.05 :: Either String (Quantity 2) -- Right 0.05 -- -- >>> mkQuantityLossless 0.06 :: Either String (Quantity 2) -- Right 0.06 -- -- >>> mkQuantityLossless 0.14 :: Either String (Quantity 2) -- Right 0.14 -- -- >>> mkQuantityLossless 0.15 :: Either String (Quantity 2) -- Right 0.15 -- -- >>> mkQuantityLossless 0.16 :: Either String (Quantity 2) -- Right 0.16 -- -- >>> mkQuantityLossless 0.04 :: Either String (Quantity 3) -- Right 0.040 -- -- >>> mkQuantityLossless 0.05 :: Either String (Quantity 3) -- Right 0.050 -- -- >>> mkQuantityLossless 0.06 :: Either String (Quantity 3) -- Right 0.060 -- -- >>> mkQuantityLossless 0.14 :: Either String (Quantity 3) -- Right 0.140 -- -- >>> mkQuantityLossless 0.15 :: Either String (Quantity 3) -- Right 0.150 -- -- >>> mkQuantityLossless 0.16 :: Either String (Quantity 3) -- Right 0.160 --mkQuantityLossless :: (KnownNat s, MonadError String m) => Scientific -> m (Quantity s) -- | Rounds given quantity by k digits. -- --
-- >>> roundQuantity (mkQuantity 0.415 :: Quantity 3) :: Quantity 2 -- 0.42 -- -- >>> roundQuantity (mkQuantity 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 (mkQuantity 0.42 :: Quantity 2) (mkQuantity 0.42 :: Quantity 2) -- 0.18 --times :: (KnownNat s, KnownNat k) => Quantity s -> Quantity k -> Quantity s -- | Multiplies two quantities with different scales. -- --
-- >>> timesLossless (mkQuantity 0.42 :: Quantity 2) (mkQuantity 0.42 :: Quantity 2) -- 0.1764 --timesLossless :: (KnownNat s, KnownNat k) => Quantity s -> Quantity k -> Quantity (s + k) -- | Auxiliary function for constructing Quantity values. -- -- See mkQuantity why we need this function and why we haven't -- used it as the direct implementation of mkQuantity. -- -- Call-sites should avoid using this function directly due to its -- performance characteristics. mkQuantityAux :: forall s. KnownNat s => Scientific -> Quantity s -- | 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.Quantity.Quantity s) instance GHC.Generics.Generic (Haspara.Quantity.Quantity s) instance GHC.Classes.Ord (Haspara.Quantity.Quantity s) instance GHC.Classes.Eq (Haspara.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.Quantity.Quantity s) instance GHC.TypeNats.KnownNat s => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Quantity.Quantity s) instance GHC.TypeNats.KnownNat s => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Quantity.Quantity s) instance GHC.TypeNats.KnownNat s => GHC.Num.Num (Numeric.Decimal.BoundedArithmetic.Arith (Haspara.Quantity.Quantity s)) instance GHC.TypeNats.KnownNat s => GHC.Real.Fractional (Numeric.Decimal.BoundedArithmetic.Arith (Haspara.Quantity.Quantity s)) instance GHC.TypeNats.KnownNat s => GHC.Show.Show (Haspara.Quantity.Quantity s) -- | This module provides definitions for modeling and working with foreign -- exchange (FX) rate quotations. module Haspara.FxQuote -- | Type encoding for FX rate quotations with fixed precision. -- -- An FX rate quotation is a 3-tuple of: -- --
-- >>> --data FxQuote (s :: Nat) MkFxQuote :: !CurrencyPair -> !Day -> !Refined Positive (Quantity s) -> FxQuote (s :: Nat) -- | Currency pair of the FX rate. [fxQuotePair] :: FxQuote (s :: Nat) -> !CurrencyPair -- | Actual date of the FX rate. [fxQuoteDate] :: FxQuote (s :: Nat) -> !Day -- | (Positive) rate value of the FX rate. [fxQuoteRate] :: FxQuote (s :: Nat) -> !Refined Positive (Quantity s) -- | Smart constructor for FxQuote values within -- MonadError Text context. -- -- The rate is expected to be a positive value. If it is not, the -- function will throw an error. -- --
-- >>> mkFxQuoteError @(Either _) @2 (read "2021-12-31") "EUR" "USD" 1.16
-- Right (MkFxQuote {fxQuotePair = EUR/USD, fxQuoteDate = 2021-12-31, fxQuoteRate = Refined 1.16})
--
-- >>> mkFxQuoteError @(Either _) @2 (read "2021-12-31") "EUR" "USD" (-1.16)
-- Left "Can not create FX Rate. Error was: The predicate (GreaterThan 0) failed with the message: Value is not greater than 0\n"
--
mkFxQuoteError :: MonadError Text m => KnownNat s => Day -> Currency -> Currency -> Scientific -> m (FxQuote s)
-- | Smart constructor for FxQuote values within MonadFail
-- context.
--
-- The rate is expected to be a positive value. If it is not, the
-- function will fail. >>> mkFxQuoteFail Maybe 2 (read
-- "2021-12-31") EUR USD 1.16 Just (MkFxQuote {fxQuotePair
-- = EUR/USD, fxQuoteDate = 2021-12-31, fxQuoteRate = Refined 1.16})
-- >>> mkFxQuoteFail Maybe 2 (read "2021-12-31")
-- EUR USD (-1.16) Nothing
mkFxQuoteFail :: MonadFail m => KnownNat s => Day -> Currency -> Currency -> Scientific -> m (FxQuote s)
-- | Unsafe FxQuote constructor that errors if it fails.
--
--
-- >>> mkFxQuoteUnsafe @2 (read "2021-12-31") "EUR" "USD" 1.16
-- MkFxQuote {fxQuotePair = EUR/USD, fxQuoteDate = 2021-12-31, fxQuoteRate = Refined 1.16}
--
-- >>> mkFxQuoteUnsafe @2 (read "2021-12-31") "EUR" "USD" (-1.16)
-- ...
-- ...Can not create FX Rate. Error was: The predicate (GreaterThan 0) failed with the message: Value is not greater than 0
-- ...
--
mkFxQuoteUnsafe :: KnownNat s => Day -> Currency -> Currency -> Scientific -> FxQuote s
-- | Type encoding for a dictionary-based FX rate quotation database for
-- various CurrencyPair values.
type FxQuoteDatabase (n :: Nat) = Map CurrencyPair (FxQuotePairDatabase n)
-- | Type encoding for FX rate quotation database for a
-- CurrencyPair.
data FxQuotePairDatabase (n :: Nat)
FxQuotePairDatabase :: !CurrencyPair -> !Map Day (FxQuote n) -> !Day -> !Day -> FxQuotePairDatabase (n :: Nat)
[fxQuotePairDatabasePair] :: FxQuotePairDatabase (n :: Nat) -> !CurrencyPair
[fxQuotePairDatabaseTable] :: FxQuotePairDatabase (n :: Nat) -> !Map Day (FxQuote n)
[fxQuotePairDatabaseSince] :: FxQuotePairDatabase (n :: Nat) -> !Day
[fxQuotePairDatabaseUntil] :: FxQuotePairDatabase (n :: Nat) -> !Day
-- | Attempts to find and return the FX quotation for a given
-- CurrencyPair as of a give Day in a given
-- FxQuoteDatabase.
findFxQuote :: KnownNat n => FxQuoteDatabase n -> CurrencyPair -> Day -> Maybe (FxQuote n)
-- | Attempts to find and return the FX quotation as of a give Day
-- in a given FxQuotePairDatabase.
findFxQuoteAux :: KnownNat n => Day -> FxQuotePairDatabase n -> Maybe (FxQuote n)
instance GHC.TypeNats.KnownNat s => Data.Aeson.Types.ToJSON.ToJSON (Haspara.FxQuote.FxQuote s)
instance GHC.TypeNats.KnownNat s => Data.Aeson.Types.FromJSON.FromJSON (Haspara.FxQuote.FxQuote s)
instance GHC.TypeNats.KnownNat s => GHC.Show.Show (Haspara.FxQuote.FxQuote s)
instance GHC.Classes.Ord (Haspara.FxQuote.FxQuote s)
instance GHC.Generics.Generic (Haspara.FxQuote.FxQuote s)
instance GHC.Classes.Eq (Haspara.FxQuote.FxQuote s)
-- | This module provides definitions for modeling and working with
-- monetary values.
module Haspara.Monetary
-- | Type encoding for dated monetary values.
--
-- A dated monetary value is a 3-tuple of:
--
-- -- convertAsofM DATE2 CCY2 (Money DATE1 CCY1 QTY1) === convertM CCY2 (Money DATE2 CCY1 QTY1) --convertAsofM :: (Monetary m, HasCallStack) => KnownNat s => Day -> Currency -> Money s -> m (Money s) -- | Attempts to convert the given Money to another using the given -- FxQuote value. -- -- This function runs some guards before attempting to do the conversion: -- --
-- >>> :set -XDataKinds
--
-- >>> let date = read "2021-01-01"
--
-- >>> let oid = 1 :: Int
--
-- >>> let qty = $$(Refined.refineTH 42) :: UnsignedQuantity 2
--
-- >>> let event = EventDecrement date oid qty
--
-- >>> let json = Data.Aeson.encode event
--
-- >>> json
-- "{\"qty\":42.0,\"type\":\"DECREMENT\",\"obj\":1,\"date\":\"2021-01-01\"}"
--
-- >>> Data.Aeson.decode @(Event Int 2) json
-- Just (EventDecrement 2021-01-01 1 (Refined 42.00))
--
-- >>> Data.Aeson.decode json == Just event
-- True
--
data Event o (s :: Nat)
EventDecrement :: Day -> o -> UnsignedQuantity s -> Event o (s :: Nat)
EventIncrement :: Day -> o -> UnsignedQuantity s -> Event o (s :: Nat)
-- | Returns the date of the event.
eventDate :: KnownNat s => Event o s -> Day
-- | Returns the source object of the event.
eventObject :: KnownNat s => Event o s -> o
-- | Negates the event.
negateEvent :: KnownNat s => Event o s -> Event o s
-- | Smart constuctor for Event values.
mkEvent :: MonadError String m => KnownNat s => Day -> 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)
-- | This module provides definitions for postings, ledgers and ledger
-- entries.
module Haspara.Accounting.Ledger
-- | Type encoding of a 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]
-- | Type encoding of a ledger item.
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
-- | Creates a ledger from a given list of Entry values.
mkLedger :: KnownNat s => Account a -> Quantity s -> [Entry o s] -> Ledger a o s
-- | Adds a new entry to a ledger.
addEntry :: KnownNat s => Ledger a o s -> Entry o s -> Ledger a 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,\"type\":\"DECREMENT\",\"obj\":1,\"date\":\"2021-01-01\"},{\"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)
-- | Returns the list of posting event sources.
postingEvents :: KnownNat s => Posting a o s -> [o]
-- | Posts an event.
post :: KnownNat s => Posting a o s -> [(Account a, Entry o s)]
-- | 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,\"type\":\"DEBIT\",\"obj\":1,\"date\":\"2021-01-01\"}"
--
-- >>> 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 :: Day -> o -> UnsignedQuantity s -> Entry o (s :: Nat)
EntryCredit :: Day -> o -> UnsignedQuantity s -> Entry o (s :: Nat)
-- | Returns the date of the posting entry.
entryDate :: KnownNat s => Entry o s -> Day
-- | Returns the quantity of the posting entry.
entryQuantity :: KnownNat s => Entry o s -> Quantity s
-- | Returns the source object of the posting entry.
entryObject :: KnownNat s => Entry o s -> o
-- | Returns the debit quantity of the posting entry.
entryDebit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)
-- | Returns the credit quantity of the posting entry.
entryCredit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)
-- | Consumes an event and a type of account, and produces a posting entry.
--
-- Note the following map as a guide:
--
-- TODO: table
buildEntry :: KnownNat s => Event o s -> AccountKind -> 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.Ledger.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.Ledger.Posting a o s)
instance (GHC.TypeNats.KnownNat s, GHC.Show.Show o, GHC.Show.Show a) => GHC.Show.Show (Haspara.Accounting.Ledger.Posting a o s)
instance (GHC.Classes.Ord o, GHC.Classes.Ord a) => GHC.Classes.Ord (Haspara.Accounting.Ledger.Posting a o s)
instance GHC.Generics.Generic (Haspara.Accounting.Ledger.Posting a o s)
instance (GHC.Classes.Eq o, GHC.Classes.Eq a) => GHC.Classes.Eq (Haspara.Accounting.Ledger.Posting a o s)
instance (GHC.Show.Show o, GHC.TypeNats.KnownNat s) => GHC.Show.Show (Haspara.Accounting.Ledger.Entry o s)
instance GHC.Classes.Ord o => GHC.Classes.Ord (Haspara.Accounting.Ledger.Entry o s)
instance GHC.Classes.Eq o => GHC.Classes.Eq (Haspara.Accounting.Ledger.Entry 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)
instance (Data.Aeson.Types.FromJSON.FromJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.FromJSON.FromJSON (Haspara.Accounting.Ledger.Entry o s)
instance (Data.Aeson.Types.ToJSON.ToJSON o, GHC.TypeNats.KnownNat s) => Data.Aeson.Types.ToJSON.ToJSON (Haspara.Accounting.Ledger.Entry o s)
-- | This module provides a collection of definitions for a rudimentary
-- accounting functionality.
module Haspara.Accounting
-- | Type encoding for account values.
--
-- This definition provides both the AccountKind and an arbitrary
-- object identifying the account. This arbitrary nature provides
-- flexibility to use-site to use its own account identity and
-- accompanying information when required.
--
--
-- >>> let acc = Account AccountKindAsset (1 ::Int)
--
-- >>> Data.Aeson.encode acc
-- "{\"kind\":\"ASSET\",\"object\":1}"
--
-- >>> Data.Aeson.decode @(Account Int) (Data.Aeson.encode acc)
-- Just (Account {accountKind = AccountKindAsset, accountObject = 1})
--
-- >>> Data.Aeson.decode (Data.Aeson.encode acc) == Just acc
-- True
--
data Account o
Account :: !AccountKind -> !o -> Account o
[accountKind] :: Account o -> !AccountKind
[accountObject] :: Account o -> !o
-- | Type encoding for ledger account type.
--
-- This type covers both balance sheet and income statement account
-- types:
--
-- -- >>> Data.Aeson.decode @AccountKind "\"ASSET\"" -- Just AccountKindAsset -- -- >>> Data.Aeson.decode @AccountKind "\"LIABILITY\"" -- Just AccountKindLiability -- -- >>> Data.Aeson.decode @AccountKind "\"EQUITY\"" -- Just AccountKindEquity -- -- >>> Data.Aeson.decode @AccountKind "\"REVENUE\"" -- Just AccountKindRevenue -- -- >>> Data.Aeson.decode @AccountKind "\"EXPENSE\"" -- Just AccountKindExpense -- -- >>> Data.Aeson.encode AccountKindAsset -- "\"ASSET\"" -- -- >>> Data.Aeson.encode AccountKindLiability -- "\"LIABILITY\"" -- -- >>> Data.Aeson.encode AccountKindEquity -- "\"EQUITY\"" -- -- >>> Data.Aeson.encode AccountKindRevenue -- "\"REVENUE\"" -- -- >>> Data.Aeson.encode AccountKindExpense -- "\"EXPENSE\"" --data AccountKind AccountKindAsset :: AccountKind AccountKindLiability :: AccountKind AccountKindEquity :: AccountKind AccountKindRevenue :: AccountKind AccountKindExpense :: AccountKind -- | Provides textual representation of a given AccountKind. -- --
-- >>> accountKindText AccountKindAsset -- "Asset" -- -- >>> accountKindText AccountKindLiability -- "Liability" -- -- >>> accountKindText AccountKindEquity -- "Equity" -- -- >>> accountKindText AccountKindRevenue -- "Revenue" -- -- >>> accountKindText AccountKindExpense -- "Expense" --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,\"type\":\"DEBIT\",\"obj\":1,\"date\":\"2021-01-01\"}"
--
-- >>> 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 :: Day -> o -> UnsignedQuantity s -> Entry o (s :: Nat)
EntryCredit :: Day -> o -> UnsignedQuantity s -> Entry o (s :: Nat)
-- | Consumes an event and a type of account, and produces a posting entry.
--
-- Note the following map as a guide:
--
-- TODO: table
buildEntry :: KnownNat s => Event o s -> AccountKind -> Entry o s
-- | Type encoding of an economic increment/decrement event.
--
-- The event explicitly carries the date and quantity information along
-- with a parameterized, arbitrary object providing the source of the
-- event.
--
--
-- >>> :set -XDataKinds
--
-- >>> let date = read "2021-01-01"
--
-- >>> let oid = 1 :: Int
--
-- >>> let qty = $$(Refined.refineTH 42) :: UnsignedQuantity 2
--
-- >>> let event = EventDecrement date oid qty
--
-- >>> let json = Data.Aeson.encode event
--
-- >>> json
-- "{\"qty\":42.0,\"type\":\"DECREMENT\",\"obj\":1,\"date\":\"2021-01-01\"}"
--
-- >>> Data.Aeson.decode @(Event Int 2) json
-- Just (EventDecrement 2021-01-01 1 (Refined 42.00))
--
-- >>> Data.Aeson.decode json == Just event
-- True
--
data Event o (s :: Nat)
EventDecrement :: Day -> o -> UnsignedQuantity s -> Event o (s :: Nat)
EventIncrement :: Day -> o -> UnsignedQuantity s -> Event o (s :: Nat)
-- | Returns the date of the event.
eventDate :: KnownNat s => Event o s -> Day
-- | Returns the source object of the event.
eventObject :: KnownNat s => Event o s -> o
-- | Negates the event.
negateEvent :: KnownNat s => Event o s -> Event o s
-- | Smart constuctor for Event values.
mkEvent :: MonadError String m => KnownNat s => Day -> 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,\"type\":\"DECREMENT\",\"obj\":1,\"date\":\"2021-01-01\"},{\"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)
-- | Returns the list of posting event sources.
postingEvents :: KnownNat s => Posting a o s -> [o]
-- | Posts an event.
post :: KnownNat s => Posting a o s -> [(Account a, Entry o s)]
-- | Type encoding of a 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]
-- | Type encoding of a ledger item.
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
-- | Creates a ledger from a given list of Entry values.
mkLedger :: KnownNat s => Account a -> Quantity s -> [Entry o s] -> Ledger a o s
-- | Adds a new entry to a ledger.
addEntry :: KnownNat s => Ledger a o s -> Entry o s -> Ledger a o s
-- | Returns the date of the posting entry.
entryDate :: KnownNat s => Entry o s -> Day
-- | Returns the source object of the posting entry.
entryObject :: KnownNat s => Entry o s -> o
-- | Returns the quantity of the posting entry.
entryQuantity :: KnownNat s => Entry o s -> Quantity s
-- | Returns the debit quantity of the posting entry.
entryDebit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)
-- | Returns the credit quantity of the posting entry.
entryCredit :: KnownNat s => Entry o s -> Maybe (UnsignedQuantity s)
-- | This module provides high-level definitions of haspara
-- library.
--
-- haspara provides rudimentary (and experimental) accounting
-- functionality, too. These definitions can be found under
-- Haspara.Accounting module.
module Haspara
-- | This module provides template-haskell functions for various
-- Haspara definitions.
module Haspara.TH
-- | Constructs a Quantity value at compile-time using
-- -XTemplateHaskell.
--
-- -- >>> $$(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 ASCII letters, 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") -- USD/USD -- -- >>> $$(currencyPairTH "USD" "eur") -- ... -- ...Currency code error! Expecting at least 3 uppercase ASCII letters, but received: eur -- ... --currencyPairTH :: Text -> Text -> Q (TExp CurrencyPair)