pwncash/lib/Internal/Types.hs

792 lines
22 KiB
Haskell
Raw Normal View History

{-# LANGUAGE DeriveAnyClass #-}
2023-05-07 20:29:33 -04:00
{-# LANGUAGE DerivingVia #-}
2023-02-26 18:57:40 -05:00
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
2023-02-26 18:57:40 -05:00
{-# LANGUAGE UndecidableInstances #-}
2022-12-11 17:51:11 -05:00
module Internal.Types where
2023-05-07 20:29:33 -04:00
import Control.Monad.Except
import Data.Fix (Fix (..), foldFix)
import Data.Functor.Foldable (embed)
import qualified Data.Functor.Foldable.TH as TH
2023-02-01 23:02:07 -05:00
import Database.Persist.Sql hiding (Desc, In, Statement)
2023-02-26 18:57:40 -05:00
import Database.Persist.TH
import Dhall hiding (embed, maybe)
import Dhall.TH
2023-04-17 00:34:09 -04:00
import Internal.TH (deriveProduct)
import Language.Haskell.TH.Syntax (Lift)
import RIO
import qualified RIO.Map as M
2023-02-25 22:56:23 -05:00
import qualified RIO.NonEmpty as NE
import qualified RIO.Text as T
import RIO.Time
2023-02-01 23:02:07 -05:00
import Text.Regex.TDFA
2022-12-11 17:51:11 -05:00
-------------------------------------------------------------------------------
-- DHALL CONFIG
2022-12-11 17:51:11 -05:00
-------------------------------------------------------------------------------
makeHaskellTypesWith
2023-04-17 00:34:09 -04:00
(defaultGenerateOptions {generateToDhallInstance = False, generateFromDhallInstance = False})
[ MultipleConstructors "SqlConfig" "(./dhall/Types.dhall).SqlConfig"
, MultipleConstructors "TimeUnit" "(./dhall/Types.dhall).TimeUnit"
, MultipleConstructors "Weekday" "(./dhall/Types.dhall).Weekday"
, MultipleConstructors "WeekdayPat" "(./dhall/Types.dhall).WeekdayPat"
, MultipleConstructors "MDYPat" "(./dhall/Types.dhall).MDYPat"
, MultipleConstructors "DatePat" "(./dhall/Types.dhall).DatePat"
, MultipleConstructors "YMDMatcher" "(./dhall/Types.dhall).YMDMatcher"
, MultipleConstructors "DateMatcher" "(./dhall/Types.dhall).DateMatcher"
, MultipleConstructors "EntryNumGetter" "(./dhall/Types.dhall).EntryNumGetter"
2023-02-26 12:03:35 -05:00
, MultipleConstructors "BudgetCurrency" "(./dhall/Types.dhall).BudgetCurrency"
, MultipleConstructors "BudgetTransferType" "(./dhall/Types.dhall).BudgetTransferType"
, MultipleConstructors "TaxMethod" "(./dhall/Types.dhall).TaxMethod"
2023-05-14 19:20:10 -04:00
, MultipleConstructors "PeriodType" "(./dhall/Types.dhall).PeriodType"
, SingleConstructor "Currency" "Currency" "(./dhall/Types.dhall).Currency"
2023-02-26 22:53:12 -05:00
, SingleConstructor "Tag" "Tag" "(./dhall/Types.dhall).Tag"
, SingleConstructor "TaggedAcnt" "TaggedAcnt" "(./dhall/Types.dhall).TaggedAcnt"
, SingleConstructor "Gregorian" "Gregorian" "(./dhall/Types.dhall).Gregorian"
, SingleConstructor "GregorianM" "GregorianM" "(./dhall/Types.dhall).GregorianM"
, SingleConstructor "Interval" "Interval" "(./dhall/Types.dhall).Interval"
, SingleConstructor "TemporalScope" "TemporalScope" "(./dhall/Types.dhall).TemporalScope"
, SingleConstructor "RepeatPat" "RepeatPat" "(./dhall/Types.dhall).RepeatPat"
, SingleConstructor "ModPat" "ModPat" "(./dhall/Types.dhall).ModPat.Type"
, SingleConstructor "CronPat" "CronPat" "(./dhall/Types.dhall).CronPat.Type"
, SingleConstructor "ValMatcher" "ValMatcher" "(./dhall/Types.dhall).ValMatcher.Type"
, SingleConstructor "Amount" "Amount" "(./dhall/Types.dhall).Amount"
, SingleConstructor "AcntSet" "AcntSet" "(./dhall/Types.dhall).AcntSet.Type"
, SingleConstructor "TransferMatcher" "TransferMatcher" "(./dhall/Types.dhall).TransferMatcher.Type"
2023-02-13 19:57:39 -05:00
, SingleConstructor "ShadowTransfer" "ShadowTransfer" "(./dhall/Types.dhall).ShadowTransfer"
2023-04-17 00:34:09 -04:00
, -- , SingleConstructor "Income" "Income" "(./dhall/Types.dhall).Income.Type"
SingleConstructor "Exchange" "Exchange" "(./dhall/Types.dhall).Exchange"
, SingleConstructor "Field" "Field" "(./dhall/Types.dhall).Field"
, SingleConstructor "Entry" "Entry" "(./dhall/Types.dhall).Entry"
, SingleConstructor "PretaxValue" "PretaxValue" "(./dhall/Types.dhall).PretaxValue"
, SingleConstructor "TaxBracket" "TaxBracket" "(./dhall/Types.dhall).TaxBracket"
, SingleConstructor "TaxProgression" "TaxProgression" "(./dhall/Types.dhall).TaxProgression"
, SingleConstructor "TaxValue" "TaxValue" "(./dhall/Types.dhall).TaxValue"
, SingleConstructor "PosttaxValue" "PosttaxValue" "(./dhall/Types.dhall).PosttaxValue"
, SingleConstructor "BudgetTransferValue" "BudgetTransferValue" "(./dhall/Types.dhall).BudgetTransferValue"
2023-05-14 19:20:10 -04:00
, SingleConstructor "Period" "Period" "(./dhall/Types.dhall).Period"
, SingleConstructor "HourlyPeriod" "HourlyPeriod" "(./dhall/Types.dhall).HourlyPeriod"
2023-04-17 00:34:09 -04:00
-- , SingleConstructor "ToTx" "ToTx" "(./dhall/Types.dhall).ToTx"
2023-04-30 23:28:16 -04:00
-- , SingleConstructor "FieldMatcher" "FieldMatcher" "(./dhall/Types.dhall).FieldMatcher_"
2023-04-17 00:34:09 -04:00
-- , SingleConstructor "Match" "Match" "(./dhall/Types.dhall).Match_"
-- , SingleConstructor "Budget" "Budget" "(./dhall/Types.dhall).Budget"
-- SingleConstructor "Transfer" "Transfer" "(./dhall/Types.dhall).Transfer"
]
deriveProduct
["Eq", "Show", "Generic", "FromDhall"]
[ "Currency"
, "Tag"
, "TimeUnit"
, "Weekday"
, "WeekdayPat"
, "RepeatPat"
, "MDYPat"
, "Gregorian"
, "GregorianM"
, "Interval"
, "ModPat"
, "CronPat"
, "DatePat"
, "TaggedAcnt"
, "Budget"
, "Income"
, "ShadowTransfer"
, "TransferMatcher"
2023-04-17 00:34:09 -04:00
, "AcntSet"
, "DateMatcher"
, "ValMatcher"
, "YMDMatcher"
2023-04-17 00:34:09 -04:00
, "BudgetCurrency"
, "Exchange"
, "EntryNumGetter"
, "TemporalScope"
2023-04-17 00:34:09 -04:00
, "SqlConfig"
, "PretaxValue"
, "TaxValue"
, "TaxBracket"
, "TaxProgression"
, "TaxMethod"
, "PosttaxValue"
, "BudgetTransferValue"
, "BudgetTransferType"
2023-05-14 19:20:10 -04:00
, "Period"
, "PeriodType"
, "HourlyPeriod"
]
2022-12-11 18:53:54 -05:00
2022-12-11 17:51:11 -05:00
-------------------------------------------------------------------------------
2023-02-26 18:57:40 -05:00
-- lots of instances for dhall types
2022-12-11 17:51:11 -05:00
2022-12-14 23:59:23 -05:00
deriving instance Lift Currency
2022-12-14 23:59:23 -05:00
deriving instance Hashable Currency
2022-12-11 17:51:11 -05:00
2023-02-26 22:53:12 -05:00
deriving instance Lift Tag
deriving instance Hashable Tag
deriving instance Ord TimeUnit
2022-12-14 23:59:23 -05:00
deriving instance Hashable TimeUnit
2022-12-11 17:51:11 -05:00
deriving instance Ord Weekday
2022-12-14 23:59:23 -05:00
deriving instance Hashable Weekday
2022-12-11 17:51:11 -05:00
2023-02-02 23:18:36 -05:00
deriving instance Enum Weekday
deriving instance Ord WeekdayPat
2022-12-14 23:59:23 -05:00
deriving instance Hashable WeekdayPat
2022-12-11 17:51:11 -05:00
deriving instance Ord RepeatPat
2022-12-14 23:59:23 -05:00
deriving instance Hashable RepeatPat
2022-12-11 17:51:11 -05:00
deriving instance Ord MDYPat
2022-12-14 23:59:23 -05:00
deriving instance Hashable MDYPat
2022-12-11 17:51:11 -05:00
2022-12-14 23:59:23 -05:00
deriving instance Hashable Gregorian
2022-12-11 17:51:11 -05:00
2022-12-14 23:59:23 -05:00
deriving instance Hashable GregorianM
2022-12-11 17:51:11 -05:00
-- Dhall.TH rearranges my fields :(
instance Ord Gregorian where
compare
Gregorian {gYear = y, gMonth = m, gDay = d}
Gregorian {gYear = y', gMonth = m', gDay = d'} =
compare y y'
<> compare m m'
<> compare d d'
instance Ord GregorianM where
compare
GregorianM {gmYear = y, gmMonth = m}
GregorianM {gmYear = y', gmMonth = m'} = compare y y' <> compare m m'
deriving instance Hashable Interval
deriving instance Ord ModPat
2022-12-14 23:59:23 -05:00
deriving instance Hashable ModPat
2022-12-11 17:51:11 -05:00
deriving instance Ord CronPat
2022-12-14 23:59:23 -05:00
deriving instance Hashable CronPat
2022-12-11 17:51:11 -05:00
deriving instance Ord DatePat
2022-12-14 23:59:23 -05:00
deriving instance Hashable DatePat
2022-12-11 17:51:11 -05:00
type BudgetTransfer =
Transfer TaggedAcnt BudgetCurrency DatePat BudgetTransferValue
2023-05-04 21:48:21 -04:00
deriving instance Hashable BudgetTransfer
deriving instance Generic BudgetTransfer
deriving instance FromDhall BudgetTransfer
data Budget = Budget
2023-04-30 23:28:16 -04:00
{ bgtLabel :: Text
, bgtIncomes :: [Income]
, bgtPretax :: [MultiAllocation PretaxValue]
, bgtTax :: [MultiAllocation TaxValue]
, bgtPosttax :: [MultiAllocation PosttaxValue]
, bgtTransfers :: [BudgetTransfer]
, bgtShadowTransfers :: [ShadowTransfer]
}
deriving instance Hashable PretaxValue
deriving instance Hashable TaxBracket
deriving instance Hashable TaxProgression
deriving instance Hashable TaxMethod
deriving instance Hashable TaxValue
deriving instance Hashable PosttaxValue
2023-02-05 18:45:56 -05:00
deriving instance Hashable Budget
deriving instance Hashable BudgetTransferValue
deriving instance Hashable BudgetTransferType
2023-02-26 22:53:12 -05:00
deriving instance Hashable TaggedAcnt
deriving instance Ord TaggedAcnt
type CurID = T.Text
data Income = Income
2023-05-04 21:48:21 -04:00
{ incGross :: Double
, incCurrency :: CurID
, incWhen :: DatePat
, incPretax :: [SingleAllocation PretaxValue]
, incTaxes :: [SingleAllocation TaxValue]
, incPosttax :: [SingleAllocation PosttaxValue]
, incFrom :: TaggedAcnt
, incToBal :: TaggedAcnt
2023-05-14 19:20:10 -04:00
, incPayPeriod :: !Period
}
2023-05-14 19:20:10 -04:00
deriving instance Hashable HourlyPeriod
deriving instance Hashable PeriodType
deriving instance Hashable Period
deriving instance Hashable Income
deriving instance (Ord w, Ord v) => Ord (Amount w v)
deriving instance Generic (Amount w v)
2023-05-04 21:48:21 -04:00
deriving instance (FromDhall v, FromDhall w) => FromDhall (Amount w v)
deriving instance (Hashable v, Hashable w) => Hashable (Amount w v)
2023-05-04 21:48:21 -04:00
-- deriving instance (Generic w, Generic v, FromDhall w, FromDhall v) => FromDhall (Amount w v)
deriving instance (Show w, Show v) => Show (Amount w v)
deriving instance (Eq w, Eq v) => Eq (Amount w v)
2022-12-11 17:51:11 -05:00
2023-02-26 12:03:35 -05:00
deriving instance Hashable Exchange
deriving instance Hashable BudgetCurrency
data Allocation w v = Allocation
{ alloTo :: TaggedAcnt
, alloAmts :: [Amount w v]
, alloCur :: CurID
}
2023-04-17 00:34:09 -04:00
deriving (Eq, Show, Generic, Hashable)
instance Bifunctor Amount where
bimap f g a@Amount {amtWhen, amtValue} = a {amtWhen = f amtWhen, amtValue = g amtValue}
instance Bifunctor Allocation where
bimap f g a@Allocation {alloAmts} = a {alloAmts = fmap (bimap f g) alloAmts}
deriving instance (Generic w, Generic v, FromDhall w, FromDhall v) => FromDhall (Allocation w v)
type MultiAllocation = Allocation Interval
type SingleAllocation = Allocation ()
2023-01-30 21:47:17 -05:00
toPersistText :: Show a => a -> PersistValue
toPersistText = PersistText . T.pack . show
fromPersistText :: Read a => T.Text -> PersistValue -> Either T.Text a
fromPersistText what (PersistText t) = case readMaybe $ T.unpack t of
Just v -> Right v
Nothing -> Left $ T.unwords ["error when reading", what, "from text:", t]
fromPersistText what x =
Left $ T.unwords ["error when deserializing", what, "; got", T.pack (show x)]
2023-04-17 00:34:09 -04:00
deriving instance Ord Interval
data Transfer a c w v = Transfer
{ transFrom :: a
, transTo :: a
, transAmounts :: [Amount w v]
, transCurrency :: c
}
2023-05-04 21:48:21 -04:00
deriving (Eq, Show)
2022-12-11 17:51:11 -05:00
2023-02-13 19:57:39 -05:00
deriving instance Hashable ShadowTransfer
deriving instance Hashable AcntSet
deriving instance Hashable TransferMatcher
2023-02-13 19:57:39 -05:00
deriving instance Hashable ValMatcher
2023-02-26 18:57:40 -05:00
deriving instance Hashable YMDMatcher
2023-02-26 18:57:40 -05:00
deriving instance Hashable DateMatcher
2023-02-26 18:57:40 -05:00
-- TODO this just looks silly...but not sure how to simplify it
instance Ord YMDMatcher where
2023-02-26 18:57:40 -05:00
compare (Y y) (Y y') = compare y y'
compare (YM g) (YM g') = compare g g'
compare (YMD g) (YMD g') = compare g g'
compare (Y y) (YM g) = compare y (gmYear g) <> LT
compare (Y y) (YMD g) = compare y (gYear g) <> LT
compare (YM g) (Y y') = compare (gmYear g) y' <> GT
compare (YMD g) (Y y') = compare (gYear g) y' <> GT
compare (YM g) (YMD g') = compare g (gregM g') <> LT
compare (YMD g) (YM g') = compare (gregM g) g' <> GT
gregM :: Gregorian -> GregorianM
gregM Gregorian {gYear = y, gMonth = m} =
GregorianM {gmYear = y, gmMonth = m}
instance Ord DateMatcher where
2023-02-26 18:57:40 -05:00
compare (On d) (On d') = compare d d'
compare (In d r) (In d' r') = compare d d' <> compare r r'
compare (On d) (In d' _) = compare d d' <> LT
compare (In d _) (On d') = compare d d' <> GT
deriving instance Hashable EntryNumGetter
2023-02-26 18:57:40 -05:00
-------------------------------------------------------------------------------
-- top level type with fixed account tree to unroll the recursion in the dhall
-- account tree type
data AccountTree
= Placeholder T.Text T.Text [AccountTree]
| Account T.Text T.Text
deriving (Eq, Generic, Hashable)
TH.makeBaseFunctor ''AccountTree
deriving instance Generic (AccountTreeF a)
deriving instance FromDhall a => FromDhall (AccountTreeF a)
data AccountRoot_ a = AccountRoot_
{ arAssets :: ![a]
, arEquity :: ![a]
, arExpenses :: ![a]
, arIncome :: ![a]
, arLiabilities :: ![a]
}
deriving (Generic)
type AccountRootF = AccountRoot_ (Fix AccountTreeF)
deriving instance FromDhall AccountRootF
type AccountRoot = AccountRoot_ AccountTree
data Config_ a = Config_
{ global :: !TemporalScope
2023-02-26 18:57:40 -05:00
, budget :: ![Budget]
, currencies :: ![Currency]
, statements :: ![History]
2023-02-26 18:57:40 -05:00
, accounts :: !a
2023-02-26 22:53:12 -05:00
, tags :: ![Tag]
2023-02-26 18:57:40 -05:00
, sqlConfig :: !SqlConfig
}
deriving (Generic)
type ConfigF = Config_ AccountRootF
type Config = Config_ AccountRoot
unfix :: ConfigF -> Config
unfix c@Config_ {accounts = a} = c {accounts = a'}
where
a' =
AccountRoot_
{ arAssets = unfixTree arAssets
, arEquity = unfixTree arEquity
, arExpenses = unfixTree arExpenses
, arIncome = unfixTree arIncome
, arLiabilities = unfixTree arLiabilities
}
unfixTree f = foldFix embed <$> f a
instance FromDhall a => FromDhall (Config_ a)
2022-12-11 17:51:11 -05:00
--------------------------------------------------------------------------------
2023-02-26 18:57:40 -05:00
-- dhall type overrides (since dhall can't import types with parameters...yet)
2023-02-26 22:53:12 -05:00
-- TODO newtypes for these?
2023-02-26 18:57:40 -05:00
type AcntID = T.Text
2023-02-26 22:53:12 -05:00
type TagID = T.Text
2023-05-04 21:48:21 -04:00
type HistTransfer = Transfer AcntID CurID DatePat Double
deriving instance Generic HistTransfer
deriving instance Hashable HistTransfer
deriving instance FromDhall HistTransfer
data History
= HistTransfer !HistTransfer
| HistStatement !Statement
2023-05-04 21:48:21 -04:00
deriving (Eq, Generic, Hashable, FromDhall)
2022-12-11 17:51:11 -05:00
2023-04-30 23:28:16 -04:00
type EntryGetter = Entry SplitAcnt (Maybe EntryNumGetter) SplitCur TagID
2022-12-11 17:51:11 -05:00
2023-04-30 23:28:16 -04:00
instance FromDhall EntryGetter
2022-12-11 17:51:11 -05:00
deriving instance (Show a, Show c, Show v, Show t) => Show (Entry a v c t)
2023-04-17 00:34:09 -04:00
deriving instance Generic (Entry a v c t)
2023-04-17 00:34:09 -04:00
deriving instance (Hashable a, Hashable v, Hashable c, Hashable t) => Hashable (Entry a v c t)
2023-04-17 00:34:09 -04:00
deriving instance (Eq a, Eq v, Eq c, Eq t) => Eq (Entry a v c t)
2023-04-17 00:34:09 -04:00
2022-12-11 17:51:11 -05:00
data Tx s = Tx
{ txDescr :: !T.Text
, txDate :: !Day
, txSplits :: ![s]
}
deriving (Generic)
2022-12-11 17:51:11 -05:00
2023-04-30 23:28:16 -04:00
type ExpTx = Tx EntryGetter
2022-12-11 17:51:11 -05:00
instance FromDhall ExpTx
2022-12-11 17:51:11 -05:00
data TxOpts re = TxOpts
{ toDate :: !T.Text
, toAmount :: !T.Text
, toDesc :: !T.Text
, toOther :: ![T.Text]
, toDateFmt :: !T.Text
, toAmountFmt :: !re
}
deriving (Eq, Generic, Hashable, Show, FromDhall)
data Statement = Statement
2023-04-30 23:28:16 -04:00
{ stmtPaths :: ![FilePath]
, stmtParsers :: ![StatementParser T.Text]
, stmtDelim :: !Word
, stmtTxOpts :: !(TxOpts T.Text)
, stmtSkipLines :: !Natural
}
deriving (Eq, Hashable, Generic, FromDhall)
2022-12-11 17:51:11 -05:00
-- | the value of a field in split (text version)
-- can either be a raw (constant) value, a lookup from the record, or a map
-- between the lookup and some other value
2023-04-30 23:28:16 -04:00
data EntryTextGetter t
= ConstT !t
| LookupT !T.Text
2023-01-30 22:57:42 -05:00
| MapT !(FieldMap T.Text t)
| Map2T !(FieldMap (T.Text, T.Text) t)
deriving (Eq, Generic, Hashable, Show, FromDhall)
2022-12-11 17:51:11 -05:00
2023-04-30 23:28:16 -04:00
type SplitCur = EntryTextGetter CurID
2022-12-11 17:51:11 -05:00
2023-04-30 23:28:16 -04:00
type SplitAcnt = EntryTextGetter AcntID
2022-12-11 17:51:11 -05:00
2023-04-17 00:34:09 -04:00
deriving instance (Show k, Show v) => Show (Field k v)
deriving instance (Eq k, Eq v) => Eq (Field k v)
deriving instance Generic (Field k v)
deriving instance (Hashable k, Hashable v) => Hashable (Field k v)
deriving instance Foldable (Field k)
deriving instance Traversable (Field k)
deriving instance (FromDhall k, FromDhall v) => FromDhall (Field k v)
2023-02-01 23:02:07 -05:00
instance Functor (Field f) where
fmap f (Field k v) = Field k $ f v
2022-12-11 17:51:11 -05:00
type FieldMap k v = Field k (M.Map k v)
2023-04-30 23:28:16 -04:00
data FieldMatcher re
2023-02-01 23:02:07 -05:00
= Desc !(Field T.Text re)
| Val !(Field T.Text ValMatcher)
2023-02-01 23:02:07 -05:00
deriving (Eq, Hashable, Generic, FromDhall, Functor, Foldable, Traversable)
2023-04-30 23:28:16 -04:00
deriving instance Show (FieldMatcher T.Text)
2022-12-11 17:51:11 -05:00
2023-04-30 23:28:16 -04:00
data TxGetter = TxGetter
{ tgCurrency :: !SplitCur
, tgAcnt :: !SplitAcnt
, tgEntries :: ![EntryGetter]
}
deriving (Eq, Generic, Hashable, Show, FromDhall)
2022-12-11 17:51:11 -05:00
2023-04-30 23:28:16 -04:00
data StatementParser re = StatementParser
{ spDate :: !(Maybe DateMatcher)
, spVal :: !ValMatcher
, spDesc :: !(Maybe re)
, spOther :: ![FieldMatcher re]
, spTx :: !(Maybe TxGetter)
, spTimes :: !(Maybe Natural)
, spPriority :: !Integer
}
2023-02-01 23:02:07 -05:00
deriving (Eq, Generic, Hashable, FromDhall, Functor)
2023-04-30 23:28:16 -04:00
deriving instance Show (StatementParser T.Text)
2022-12-11 17:51:11 -05:00
--------------------------------------------------------------------------------
2023-02-26 18:57:40 -05:00
-- DATABASE MODEL
--------------------------------------------------------------------------------
2022-12-11 17:51:11 -05:00
2023-02-26 18:57:40 -05:00
share
[mkPersist sqlSettings, mkMigrate "migrateAll"]
[persistLowerCase|
CommitR sql=commits
hash Int
type ConfigType
deriving Show Eq
CurrencyR sql=currencies
symbol T.Text
fullname T.Text
2023-05-04 21:48:21 -04:00
precision Int
2023-02-26 18:57:40 -05:00
deriving Show Eq
2023-02-26 22:53:12 -05:00
TagR sql=tags
symbol T.Text
fullname T.Text
deriving Show Eq
2023-02-26 18:57:40 -05:00
AccountR sql=accounts
name T.Text
fullpath T.Text
desc T.Text
deriving Show Eq
AccountPathR sql=account_paths
parent AccountRId OnDeleteCascade
child AccountRId OnDeleteCascade
depth Int
deriving Show Eq
TransactionR sql=transactions
commit CommitRId OnDeleteCascade
date Day
description T.Text
deriving Show Eq
SplitR sql=splits
transaction TransactionRId OnDeleteCascade
currency CurrencyRId OnDeleteCascade
account AccountRId OnDeleteCascade
memo T.Text
value Rational
deriving Show Eq
2023-02-26 22:53:12 -05:00
TagRelationR sql=tag_relations
split SplitRId OnDeleteCascade
tag TagRId OnDeleteCascade
2023-02-26 18:57:40 -05:00
BudgetLabelR sql=budget_labels
split SplitRId OnDeleteCascade
budgetName T.Text
deriving Show Eq
|]
2022-12-11 17:51:11 -05:00
--------------------------------------------------------------------------------
-- database cache types
2022-12-11 17:51:11 -05:00
data ConfigHashes = ConfigHashes
{ chIncome :: ![Int]
, chExpense :: ![Int]
, chManual :: ![Int]
, chImport :: ![Int]
}
2022-12-11 17:51:11 -05:00
data ConfigType = CTBudget | CTManual | CTImport
deriving (Eq, Show, Read, Enum)
2022-12-11 17:51:11 -05:00
instance PersistFieldSql ConfigType where
sqlType _ = SqlString
2022-12-11 17:51:11 -05:00
instance PersistField ConfigType where
toPersistValue = PersistText . T.pack . show
-- TODO these error messages *might* be good enough?
fromPersistValue (PersistText v) =
maybe (Left $ "could not parse: " <> v) Right $ readMaybe $ T.unpack v
fromPersistValue _ = Left "wrong type"
2022-12-11 17:51:11 -05:00
2023-02-26 18:57:40 -05:00
type AccountMap = M.Map AcntID (AccountRId, AcntSign, AcntType)
2023-05-04 21:48:21 -04:00
type CurrencyMap = M.Map CurID (CurrencyRId, Natural)
2023-02-26 18:57:40 -05:00
2023-02-26 22:53:12 -05:00
type TagMap = M.Map TagID TagRId
2023-02-26 18:57:40 -05:00
data DBState = DBState
{ kmCurrency :: !CurrencyMap
, kmAccount :: !AccountMap
2023-02-26 22:53:12 -05:00
, kmTag :: !TagMap
2023-02-26 18:57:40 -05:00
, kmBudgetInterval :: !Bounds
, kmStatementInterval :: !Bounds
, kmNewCommits :: ![Int]
2023-05-13 13:53:43 -04:00
, kmOldCommits :: ![Int]
2023-02-26 18:57:40 -05:00
, kmConfigDir :: !FilePath
2023-05-13 13:53:43 -04:00
, kmTagAll :: ![Entity TagR]
, kmAcntPaths :: ![AccountPathR]
, kmAcntsOld :: ![Entity AccountR]
, kmCurrenciesOld :: ![Entity CurrencyR]
2023-02-26 18:57:40 -05:00
}
2023-05-04 21:48:21 -04:00
type CurrencyM = Reader CurrencyMap
type KeySplit = Entry AccountRId Rational CurrencyRId TagRId
2023-02-26 18:57:40 -05:00
type KeyTx = Tx KeySplit
type TreeR = Tree ([T.Text], AccountRId)
type Balances = M.Map AccountRId Rational
2023-05-07 20:29:33 -04:00
type BalanceM = ReaderT (MVar Balances)
2023-02-26 18:57:40 -05:00
2023-05-07 20:29:33 -04:00
type MonadFinance = MonadReader DBState
2023-02-26 18:57:40 -05:00
2023-05-07 20:29:33 -04:00
askDBState :: MonadFinance m => (DBState -> a) -> m a
askDBState = asks
2023-02-26 18:57:40 -05:00
class MonadUnliftIO m => MonadBalance m where
askBalances :: m (MVar Balances)
withBalances :: (Balances -> m a) -> m a
withBalances f = do
bs <- askBalances
withMVar bs f
modifyBalances :: (Balances -> m (Balances, a)) -> m a
modifyBalances f = do
bs <- askBalances
modifyMVar bs f
lookupBalance :: AccountRId -> m Rational
lookupBalance i = withBalances $ return . fromMaybe 0 . M.lookup i
addBalance :: AccountRId -> Rational -> m ()
addBalance i v =
modifyBalances $ return . (,()) . M.alter (Just . maybe v (v +)) i
2022-12-11 17:51:11 -05:00
-------------------------------------------------------------------------------
-- misc
2022-12-11 17:51:11 -05:00
data AcntType
= AssetT
| EquityT
| ExpenseT
| IncomeT
| LiabilityT
deriving (Show, Eq, Ord, Lift, Hashable, Generic, Read, FromDhall)
2022-12-14 23:59:23 -05:00
atName :: AcntType -> T.Text
atName AssetT = "asset"
atName EquityT = "equity"
atName ExpenseT = "expense"
atName IncomeT = "income"
2022-12-14 23:59:23 -05:00
atName LiabilityT = "liability"
data AcntPath = AcntPath
{ apType :: !AcntType
, apChildren :: ![T.Text]
}
deriving (Eq, Ord, Show, Lift, Hashable, Generic, Read, FromDhall)
2022-12-14 23:59:23 -05:00
data TxRecord = TxRecord
{ trDate :: !Day
, trAmount :: !Rational
, trDesc :: !T.Text
, trOther :: !(M.Map T.Text T.Text)
2023-01-30 22:57:42 -05:00
, trFile :: !FilePath
}
deriving (Show, Eq, Ord)
2022-12-14 23:59:23 -05:00
type Bounds = (Day, Natural)
2022-12-11 17:51:11 -05:00
data Keyed a = Keyed
{ kKey :: !Int64
, kVal :: !a
}
deriving (Eq, Show, Functor)
2022-12-11 17:51:11 -05:00
data Tree a = Branch !a ![Tree a] | Leaf !a deriving (Show)
2022-12-11 17:51:11 -05:00
data AcntSign = Credit | Debit
deriving (Show)
2022-12-11 17:51:11 -05:00
sign2Int :: AcntSign -> Int
sign2Int Debit = 1
2022-12-11 17:51:11 -05:00
sign2Int Credit = 1
accountSign :: AcntType -> AcntSign
accountSign AssetT = Debit
accountSign ExpenseT = Debit
accountSign IncomeT = Credit
2022-12-11 17:51:11 -05:00
accountSign LiabilityT = Credit
accountSign EquityT = Credit
2022-12-11 17:51:11 -05:00
type RawSplit = Entry AcntID (Maybe Rational) CurID TagID
2022-12-11 17:51:11 -05:00
type BalSplit = Entry AcntID Rational CurID TagID
2022-12-11 17:51:11 -05:00
type RawTx = Tx RawSplit
2022-12-11 17:51:11 -05:00
type BalTx = Tx BalSplit
2023-01-30 22:57:42 -05:00
data MatchRes a = MatchPass !a | MatchFail | MatchSkip
2023-01-07 23:42:04 -05:00
2023-02-26 18:57:40 -05:00
--------------------------------------------------------------------------------
-- exception types
2023-01-24 23:24:41 -05:00
data BalanceType = TooFewSplits | NotOneBlank deriving (Show)
2023-01-07 23:42:04 -05:00
2023-01-27 23:33:34 -05:00
data MatchType = MatchNumeric | MatchText deriving (Show)
2023-02-26 22:53:12 -05:00
data SplitIDType = AcntField | CurField | TagField deriving (Show)
2023-01-27 23:33:34 -05:00
data LookupSuberr
2023-01-30 22:57:42 -05:00
= SplitIDField !SplitIDType
2023-01-27 23:33:34 -05:00
| SplitValField
2023-01-30 22:57:42 -05:00
| MatchField !MatchType
| DBKey !SplitIDType
2023-01-27 23:33:34 -05:00
deriving (Show)
2023-01-25 23:04:54 -05:00
2023-01-27 20:31:13 -05:00
data AllocationSuberr
= NoAllocations
| ExceededTotal
| MissingBlank
| TooManyBlanks
deriving (Show)
data PatternSuberr = ZeroLength | ZeroRepeats deriving (Show)
2023-01-24 23:24:41 -05:00
data InsertError
2023-01-30 22:57:42 -05:00
= RegexError !T.Text
| MatchValPrecisionError !Natural !Natural
2023-02-25 22:56:23 -05:00
| AccountError !AcntID !(NE.NonEmpty AcntType)
2023-01-30 22:57:42 -05:00
| InsertIOError !T.Text
| ParseError !T.Text
| ConversionError !T.Text
2023-01-30 22:57:42 -05:00
| LookupError !LookupSuberr !T.Text
| BalanceError !BalanceType !CurID ![RawSplit]
| IncomeError !Day !T.Text !Rational
| PatternError !Natural !Natural !(Maybe Natural) !PatternSuberr
| BoundsError !Gregorian !(Maybe Gregorian)
2023-02-01 23:02:07 -05:00
| StatementError ![TxRecord] ![MatchRe]
2023-05-14 19:20:10 -04:00
| PeriodError !Day !Day
2023-01-24 23:24:41 -05:00
deriving (Show)
2023-01-07 23:42:04 -05:00
2023-05-07 20:29:33 -04:00
newtype InsertException = InsertException [InsertError]
deriving (Show, Semigroup) via [InsertError]
2023-01-07 23:42:04 -05:00
instance Exception InsertException
2023-01-24 23:24:41 -05:00
2023-05-07 20:29:33 -04:00
type MonadInsertError = MonadError InsertException
2023-05-07 20:29:33 -04:00
type InsertExceptT = ExceptT InsertException
2023-05-04 21:48:21 -04:00
2023-05-07 20:29:33 -04:00
type InsertExcept = InsertExceptT Identity
2023-05-04 21:48:21 -04:00
data XGregorian = XGregorian
{ xgYear :: !Int
, xgMonth :: !Int
, xgDay :: !Int
, xgDayOfWeek :: !Int
}
2023-02-01 23:02:07 -05:00
2023-04-30 23:28:16 -04:00
type MatchRe = StatementParser (T.Text, Regex)
2023-02-01 23:02:07 -05:00
type TxOptsRe = TxOpts (T.Text, Regex)
2023-04-30 23:28:16 -04:00
type FieldMatcherRe = FieldMatcher (T.Text, Regex)
2023-02-01 23:02:07 -05:00
2023-04-30 23:28:16 -04:00
instance Show (StatementParser (T.Text, Regex)) where
2023-02-01 23:02:07 -05:00
show = show . fmap fst