Compare commits
No commits in common. "4eae92eb01fc4f08ab86d515f85b6e08702cdb6e" and "61aabf45a38e4a95292701df7aae92b53db258c8" have entirely different histories.
4eae92eb01
...
61aabf45a3
|
@ -26,6 +26,7 @@ source-repository head
|
||||||
library
|
library
|
||||||
exposed-modules:
|
exposed-modules:
|
||||||
Internal.Config
|
Internal.Config
|
||||||
|
Internal.Database.Model
|
||||||
Internal.Database.Ops
|
Internal.Database.Ops
|
||||||
Internal.Insert
|
Internal.Insert
|
||||||
Internal.Statement
|
Internal.Statement
|
||||||
|
|
|
@ -6,14 +6,10 @@ let CurID = Text
|
||||||
|
|
||||||
let AcntID = Text
|
let AcntID = Text
|
||||||
|
|
||||||
let TagID = Text
|
|
||||||
|
|
||||||
let SqlConfig {- TODO pgsql -} = < Sqlite : Text | Postgres >
|
let SqlConfig {- TODO pgsql -} = < Sqlite : Text | Postgres >
|
||||||
|
|
||||||
let Currency = { curSymbol : CurID, curFullname : Text }
|
let Currency = { curSymbol : CurID, curFullname : Text }
|
||||||
|
|
||||||
let Tag = { tagID : TagID, tagDesc : Text }
|
|
||||||
|
|
||||||
let Gregorian = { gYear : Natural, gMonth : Natural, gDay : Natural }
|
let Gregorian = { gYear : Natural, gMonth : Natural, gDay : Natural }
|
||||||
|
|
||||||
let GregorianM = { gmYear : Natural, gmMonth : Natural }
|
let GregorianM = { gmYear : Natural, gmMonth : Natural }
|
||||||
|
@ -185,6 +181,10 @@ let Import =
|
||||||
|
|
||||||
let Statement = < StmtManual : Manual | StmtImport : Import >
|
let Statement = < StmtManual : Manual | StmtImport : Import >
|
||||||
|
|
||||||
|
let ExpenseBucket = < Fixed | Investment | Savings | Guiltless >
|
||||||
|
|
||||||
|
let IncomeBucket = < PreTax | IntraTax | PostTax >
|
||||||
|
|
||||||
let Amount = { amtValue : Decimal, amtDesc : Text }
|
let Amount = { amtValue : Decimal, amtDesc : Text }
|
||||||
|
|
||||||
let AmountType = < FixedAmt | Percent | Target >
|
let AmountType = < FixedAmt | Percent | Target >
|
||||||
|
@ -193,18 +193,25 @@ let TimeAmount = { taWhen : DatePat, taAmt : Amount, taAmtType : AmountType }
|
||||||
|
|
||||||
let Tax = { taxAcnt : AcntID, taxValue : Decimal }
|
let Tax = { taxAcnt : AcntID, taxValue : Decimal }
|
||||||
|
|
||||||
|
let TransferTarget =
|
||||||
|
< ExpenseTarget :
|
||||||
|
{ _1xtarget :
|
||||||
|
{- this is the only place expense accounts may be specified -}
|
||||||
|
AcntID
|
||||||
|
, _2xtarget : ExpenseBucket
|
||||||
|
}
|
||||||
|
| GenericTarget : AcntID
|
||||||
|
>
|
||||||
|
|
||||||
let Exchange =
|
let Exchange =
|
||||||
{ xFromCur : CurID, xToCur : CurID, xAcnt : AcntID, xRate : Decimal }
|
{ xFromCur : CurID, xToCur : CurID, xAcnt : AcntID, xRate : Decimal }
|
||||||
|
|
||||||
let BudgetCurrency = < NoX : CurID | X : Exchange >
|
let BudgetCurrency = < NoX : CurID | X : Exchange >
|
||||||
|
|
||||||
let TaggedAcnt = { taAcnt : AcntID, taTags : List TagID }
|
|
||||||
|
|
||||||
let Allocation =
|
let Allocation =
|
||||||
{ alloTo : TaggedAcnt
|
{ alloPath : TransferTarget
|
||||||
, alloTags : List TagID
|
|
||||||
, alloAmts : List Amount
|
, alloAmts : List Amount
|
||||||
, alloCur : BudgetCurrency
|
, alloCurrency : BudgetCurrency
|
||||||
}
|
}
|
||||||
|
|
||||||
let Income =
|
let Income =
|
||||||
|
@ -214,16 +221,16 @@ let Income =
|
||||||
, incFrom :
|
, incFrom :
|
||||||
{- this must be an income AcntID, and is the only place income
|
{- this must be an income AcntID, and is the only place income
|
||||||
accounts may be specified in the entire budget -}
|
accounts may be specified in the entire budget -}
|
||||||
TaggedAcnt
|
AcntID
|
||||||
, incPretax : List Allocation
|
, incPretax : List Allocation
|
||||||
, incTaxes : List Tax
|
, incTaxes : List Tax
|
||||||
, incPosttax : List Allocation
|
, incPosttax : List Allocation
|
||||||
, incToBal : TaggedAcnt
|
, incToBal : TransferTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
let Transfer =
|
let Transfer =
|
||||||
{ transFrom : TaggedAcnt
|
{ transFrom : AcntID
|
||||||
, transTo : TaggedAcnt
|
, transTo : TransferTarget
|
||||||
, transAmounts : List TimeAmount
|
, transAmounts : List TimeAmount
|
||||||
, transCurrency : BudgetCurrency
|
, transCurrency : BudgetCurrency
|
||||||
}
|
}
|
||||||
|
@ -249,8 +256,8 @@ let ShadowMatch =
|
||||||
}
|
}
|
||||||
|
|
||||||
let ShadowTransfer =
|
let ShadowTransfer =
|
||||||
{ stFrom : TaggedAcnt
|
{ stFrom : AcntID
|
||||||
, stTo : TaggedAcnt
|
, stTo : AcntID
|
||||||
, stCurrency : CurID
|
, stCurrency : CurID
|
||||||
, stDesc : Text
|
, stDesc : Text
|
||||||
, stMatch : ShadowMatch.Type
|
, stMatch : ShadowMatch.Type
|
||||||
|
@ -268,7 +275,6 @@ in { CurID
|
||||||
, AcntID
|
, AcntID
|
||||||
, SqlConfig
|
, SqlConfig
|
||||||
, Currency
|
, Currency
|
||||||
, Tag
|
|
||||||
, Interval
|
, Interval
|
||||||
, Global
|
, Global
|
||||||
, Gregorian
|
, Gregorian
|
||||||
|
@ -302,16 +308,18 @@ in { CurID
|
||||||
, Statement
|
, Statement
|
||||||
, Transfer
|
, Transfer
|
||||||
, Income
|
, Income
|
||||||
|
, IncomeBucket
|
||||||
|
, ExpenseBucket
|
||||||
, Budget
|
, Budget
|
||||||
, Tax
|
, Tax
|
||||||
, Allocation
|
, Allocation
|
||||||
, Amount
|
, Amount
|
||||||
, TimeAmount
|
, TimeAmount
|
||||||
, AmountType
|
, AmountType
|
||||||
|
, TransferTarget
|
||||||
, ShadowMatch
|
, ShadowMatch
|
||||||
, ShadowTransfer
|
, ShadowTransfer
|
||||||
, AcntSet
|
, AcntSet
|
||||||
, BudgetCurrency
|
, BudgetCurrency
|
||||||
, Exchange
|
, Exchange
|
||||||
, TaggedAcnt
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
|
||||||
|
module Internal.Database.Model where
|
||||||
|
|
||||||
|
import Database.Esqueleto.Experimental
|
||||||
|
import Database.Persist.TH
|
||||||
|
import Internal.Types
|
||||||
|
import RIO
|
||||||
|
import qualified RIO.Map as M
|
||||||
|
import qualified RIO.Text as T
|
||||||
|
import RIO.Time
|
||||||
|
|
||||||
|
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
|
||||||
|
deriving Show Eq
|
||||||
|
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
|
||||||
|
BudgetLabelR sql=budget_labels
|
||||||
|
split SplitRId OnDeleteCascade
|
||||||
|
budgetName T.Text
|
||||||
|
deriving Show Eq
|
||||||
|
ExpenseBucketR sql=expense_buckets
|
||||||
|
budgetLabel BudgetLabelRId OnDeleteCascade
|
||||||
|
bucket ExpenseBucket
|
||||||
|
deriving Show Eq
|
||||||
|
IncomeBucketR sql=income_buckets
|
||||||
|
budgetLabel BudgetLabelRId OnDeleteCascade
|
||||||
|
bucket IncomeBucket
|
||||||
|
deriving Show Eq
|
||||||
|
|]
|
||||||
|
|
||||||
|
type AccountMap = M.Map AcntID (AccountRId, AcntSign, AcntType)
|
||||||
|
|
||||||
|
type CurrencyMap = M.Map CurID CurrencyRId
|
||||||
|
|
||||||
|
data DBState = DBState
|
||||||
|
{ kmCurrency :: !CurrencyMap
|
||||||
|
, kmAccount :: !AccountMap
|
||||||
|
, kmBudgetInterval :: !Bounds
|
||||||
|
, kmStatementInterval :: !Bounds
|
||||||
|
, kmNewCommits :: ![Int]
|
||||||
|
, kmConfigDir :: !FilePath
|
||||||
|
}
|
||||||
|
|
||||||
|
type MappingT m = ReaderT DBState (SqlPersistT m)
|
||||||
|
|
||||||
|
type KeySplit = Split AccountRId Rational CurrencyRId
|
||||||
|
|
||||||
|
type KeyTx = Tx KeySplit
|
||||||
|
|
||||||
|
type TreeR = Tree ([T.Text], AccountRId)
|
||||||
|
|
||||||
|
type Balances = M.Map AccountRId Rational
|
||||||
|
|
||||||
|
type BalanceM m = ReaderT (MVar Balances) m
|
||||||
|
|
||||||
|
class MonadUnliftIO m => MonadFinance m where
|
||||||
|
askDBState :: (DBState -> a) -> m a
|
||||||
|
|
||||||
|
instance MonadUnliftIO m => MonadFinance (ReaderT DBState m) where
|
||||||
|
askDBState = asks
|
||||||
|
|
||||||
|
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
|
|
@ -1,6 +1,7 @@
|
||||||
module Internal.Database.Ops
|
module Internal.Database.Ops
|
||||||
( migrate_
|
( migrate_
|
||||||
, nukeTables
|
, nukeTables
|
||||||
|
-- , showBalances
|
||||||
, updateHashes
|
, updateHashes
|
||||||
, getDBState
|
, getDBState
|
||||||
, tree2Records
|
, tree2Records
|
||||||
|
@ -17,6 +18,7 @@ import Database.Persist.Sql hiding (delete, (==.), (||.))
|
||||||
import Database.Persist.Sqlite hiding (delete, (==.), (||.))
|
import Database.Persist.Sqlite hiding (delete, (==.), (||.))
|
||||||
import Database.Sqlite hiding (Config)
|
import Database.Sqlite hiding (Config)
|
||||||
import GHC.Err
|
import GHC.Err
|
||||||
|
import Internal.Database.Model
|
||||||
import Internal.Types
|
import Internal.Types
|
||||||
import Internal.Utils
|
import Internal.Utils
|
||||||
import RIO hiding (LogFunc, isNothing, on, (^.))
|
import RIO hiding (LogFunc, isNothing, on, (^.))
|
||||||
|
@ -161,13 +163,6 @@ deleteCurrency e = delete $ do
|
||||||
where
|
where
|
||||||
k = entityKey e
|
k = entityKey e
|
||||||
|
|
||||||
deleteTag :: MonadUnliftIO m => Entity TagR -> SqlPersistT m ()
|
|
||||||
deleteTag e = delete $ do
|
|
||||||
c <- from $ table @TagR
|
|
||||||
where_ (c ^. TagRId ==. val k)
|
|
||||||
where
|
|
||||||
k = entityKey e
|
|
||||||
|
|
||||||
updateAccounts :: MonadUnliftIO m => AccountRoot -> SqlPersistT m AccountMap
|
updateAccounts :: MonadUnliftIO m => AccountRoot -> SqlPersistT m AccountMap
|
||||||
updateAccounts ar = do
|
updateAccounts ar = do
|
||||||
let (acnts, paths, acntMap) = indexAcntRoot ar
|
let (acnts, paths, acntMap) = indexAcntRoot ar
|
||||||
|
@ -180,7 +175,6 @@ updateAccounts ar = do
|
||||||
mapM_ insert paths
|
mapM_ insert paths
|
||||||
return acntMap
|
return acntMap
|
||||||
|
|
||||||
-- TODO slip-n-slide code...
|
|
||||||
insertFull
|
insertFull
|
||||||
:: (MonadUnliftIO m, PersistStoreWrite b, PersistRecordBackend r b)
|
:: (MonadUnliftIO m, PersistStoreWrite b, PersistRecordBackend r b)
|
||||||
=> Entity r
|
=> Entity r
|
||||||
|
@ -203,18 +197,6 @@ currency2Record c@Currency {curSymbol, curFullname} =
|
||||||
currencyMap :: [Entity CurrencyR] -> CurrencyMap
|
currencyMap :: [Entity CurrencyR] -> CurrencyMap
|
||||||
currencyMap = M.fromList . fmap (\e -> (currencyRSymbol $ entityVal e, entityKey e))
|
currencyMap = M.fromList . fmap (\e -> (currencyRSymbol $ entityVal e, entityKey e))
|
||||||
|
|
||||||
updateTags :: MonadUnliftIO m => [Tag] -> SqlPersistT m TagMap
|
|
||||||
updateTags cs = do
|
|
||||||
let tags = fmap toRecord cs
|
|
||||||
tags' <- select $ from $ table @TagR
|
|
||||||
let (toIns, toDel) = setDiff tags tags'
|
|
||||||
mapM_ deleteTag toDel
|
|
||||||
mapM_ insertFull toIns
|
|
||||||
return $ tagMap tags
|
|
||||||
where
|
|
||||||
toRecord t@(Tag {tagID, tagDesc}) = Entity (toKey t) $ TagR tagID tagDesc
|
|
||||||
tagMap = M.fromList . fmap (\e -> (tagRSymbol $ entityVal e, entityKey e))
|
|
||||||
|
|
||||||
toKey :: (ToBackendKey SqlBackend b, Hashable a) => a -> Key b
|
toKey :: (ToBackendKey SqlBackend b, Hashable a) => a -> Key b
|
||||||
toKey = toSqlKey . fromIntegral . hash
|
toKey = toSqlKey . fromIntegral . hash
|
||||||
|
|
||||||
|
@ -326,7 +308,6 @@ getDBState
|
||||||
getDBState c = do
|
getDBState c = do
|
||||||
am <- updateAccounts $ accounts c
|
am <- updateAccounts $ accounts c
|
||||||
cm <- updateCurrencies $ currencies c
|
cm <- updateCurrencies $ currencies c
|
||||||
ts <- updateTags $ tags c
|
|
||||||
hs <- updateHashes c
|
hs <- updateHashes c
|
||||||
-- TODO not sure how I feel about this, probably will change this struct alot
|
-- TODO not sure how I feel about this, probably will change this struct alot
|
||||||
-- in the future so whatever...for now
|
-- in the future so whatever...for now
|
||||||
|
@ -338,7 +319,6 @@ getDBState c = do
|
||||||
, kmStatementInterval = s
|
, kmStatementInterval = s
|
||||||
, kmNewCommits = hs
|
, kmNewCommits = hs
|
||||||
, kmConfigDir = f
|
, kmConfigDir = f
|
||||||
, kmTag = ts
|
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
bi = resolveBounds $ budgetInterval $ global c
|
bi = resolveBounds $ budgetInterval $ global c
|
||||||
|
|
|
@ -8,6 +8,7 @@ import Data.Hashable
|
||||||
import Database.Persist.Class
|
import Database.Persist.Class
|
||||||
import Database.Persist.Sql hiding (Single, Statement)
|
import Database.Persist.Sql hiding (Single, Statement)
|
||||||
import GHC.Utils.Misc hiding (split)
|
import GHC.Utils.Misc hiding (split)
|
||||||
|
import Internal.Database.Model
|
||||||
import Internal.Statement
|
import Internal.Statement
|
||||||
import Internal.Types hiding (sign)
|
import Internal.Types hiding (sign)
|
||||||
import Internal.Utils
|
import Internal.Utils
|
||||||
|
@ -150,8 +151,9 @@ fromShadow tx t@ShadowTransfer {stFrom, stTo, stDesc, stRatio} = do
|
||||||
BudgetTx
|
BudgetTx
|
||||||
{ btMeta = btMeta $ bttTx tx
|
{ btMeta = btMeta $ bttTx tx
|
||||||
, btWhen = btWhen $ bttTx tx
|
, btWhen = btWhen $ bttTx tx
|
||||||
, btFrom = stFrom
|
, -- TODO what are these supposed to do?
|
||||||
, btTo = stTo
|
btFrom = BudgetSplit stFrom Nothing
|
||||||
|
, btTo = BudgetSplit stTo Nothing
|
||||||
, btValue = dec2Rat stRatio * (btValue $ bttTx tx)
|
, btValue = dec2Rat stRatio * (btValue $ bttTx tx)
|
||||||
, btDesc = stDesc
|
, btDesc = stDesc
|
||||||
}
|
}
|
||||||
|
@ -163,8 +165,8 @@ shadowMatches ShadowMatch {smFrom, smTo, smDate, smVal} tx = do
|
||||||
-- TODO what does the amount do for each of the different types?
|
-- TODO what does the amount do for each of the different types?
|
||||||
valRes <- valMatches smVal (btValue tx_)
|
valRes <- valMatches smVal (btValue tx_)
|
||||||
return $
|
return $
|
||||||
memberMaybe (taAcnt $ btFrom tx_) smFrom
|
memberMaybe (bsAcnt $ btFrom tx_) smFrom
|
||||||
&& memberMaybe (taAcnt $ btTo tx_) smTo
|
&& memberMaybe (bsAcnt $ btTo tx_) smTo
|
||||||
&& maybe True (`dateMatches` (btWhen tx_)) smDate
|
&& maybe True (`dateMatches` (btWhen tx_)) smDate
|
||||||
&& valRes
|
&& valRes
|
||||||
where
|
where
|
||||||
|
@ -179,13 +181,13 @@ balanceTransfers ts = snd $ L.mapAccumR go initBals $ L.sortOn (btWhen . bttTx)
|
||||||
M.fromList $
|
M.fromList $
|
||||||
fmap (,0) $
|
fmap (,0) $
|
||||||
L.nub $
|
L.nub $
|
||||||
(fmap (btTo . bttTx) ts ++ fmap (btTo . bttTx) ts)
|
(fmap (bsAcnt . btTo . bttTx) ts ++ fmap (bsAcnt . btTo . bttTx) ts)
|
||||||
updateBal x = M.update (Just . (+ x))
|
updateBal x = M.update (Just . (+ x))
|
||||||
lookupBal = M.findWithDefault (error "this should not happen")
|
lookupBal = M.findWithDefault (error "this should not happen")
|
||||||
go bals btt =
|
go bals btt =
|
||||||
let tx = bttTx btt
|
let tx = bttTx btt
|
||||||
from = btFrom tx
|
from = bsAcnt $ btFrom tx
|
||||||
to = btTo tx
|
to = bsAcnt $ btTo tx
|
||||||
bal = lookupBal to bals
|
bal = lookupBal to bals
|
||||||
x = amtToMove bal (bttType btt) (btValue tx)
|
x = amtToMove bal (bttType btt) (btValue tx)
|
||||||
in (updateBal x to $ updateBal (-x) from bals, tx {btValue = x})
|
in (updateBal x to $ updateBal (-x) from bals, tx {btValue = x})
|
||||||
|
@ -196,6 +198,12 @@ balanceTransfers ts = snd $ L.mapAccumR go initBals $ L.sortOn (btWhen . bttTx)
|
||||||
amtToMove bal Percent x = -(x / 100 * bal)
|
amtToMove bal Percent x = -(x / 100 * bal)
|
||||||
amtToMove bal Target x = x - bal
|
amtToMove bal Target x = x - bal
|
||||||
|
|
||||||
|
-- TODO allow currency conversions here
|
||||||
|
data BudgetSplit b = BudgetSplit
|
||||||
|
{ bsAcnt :: !AcntID
|
||||||
|
, bsBucket :: !(Maybe b)
|
||||||
|
}
|
||||||
|
|
||||||
data BudgetMeta = BudgetMeta
|
data BudgetMeta = BudgetMeta
|
||||||
{ bmCommit :: !(Key CommitR)
|
{ bmCommit :: !(Key CommitR)
|
||||||
, bmCur :: !BudgetCurrency
|
, bmCur :: !BudgetCurrency
|
||||||
|
@ -205,8 +213,8 @@ data BudgetMeta = BudgetMeta
|
||||||
data BudgetTx = BudgetTx
|
data BudgetTx = BudgetTx
|
||||||
{ btMeta :: !BudgetMeta
|
{ btMeta :: !BudgetMeta
|
||||||
, btWhen :: !Day
|
, btWhen :: !Day
|
||||||
, btFrom :: !TaggedAcnt
|
, btFrom :: !(BudgetSplit IncomeBucket)
|
||||||
, btTo :: !TaggedAcnt
|
, btTo :: !(BudgetSplit ExpenseBucket)
|
||||||
, btValue :: !Rational
|
, btValue :: !Rational
|
||||||
, btDesc :: !T.Text
|
, btDesc :: !T.Text
|
||||||
}
|
}
|
||||||
|
@ -223,53 +231,55 @@ insertIncome
|
||||||
whenHash CTIncome i (Right []) $ \c -> do
|
whenHash CTIncome i (Right []) $ \c -> do
|
||||||
let meta = BudgetMeta c (NoX incCurrency) name
|
let meta = BudgetMeta c (NoX incCurrency) name
|
||||||
let balRes = balanceIncome i
|
let balRes = balanceIncome i
|
||||||
fromRes <- lift $ checkAcntType IncomeT $ taAcnt incFrom
|
fromRes <- lift $ checkAcntType IncomeT incFrom (\j -> BudgetSplit j . Just)
|
||||||
case concatEither2 balRes fromRes (,) of
|
toRes <- lift $ expandTarget incToBal
|
||||||
|
case concatEither3 balRes fromRes toRes (,,) of
|
||||||
Left es -> return $ Left es
|
Left es -> return $ Left es
|
||||||
-- TODO this hole seems sloppy...
|
Right (balance, fromFun, to) ->
|
||||||
Right (balance, _) ->
|
|
||||||
fmap (fmap (concat . concat)) $
|
fmap (fmap (concat . concat)) $
|
||||||
withDates incWhen $ \day -> do
|
withDates incWhen $ \day -> do
|
||||||
let fromAllos = fmap concat . mapM (lift . fromAllo day meta incFrom)
|
let fromAllos b =
|
||||||
pre <- fromAllos incPretax
|
fmap (fmap concat . concatEitherL)
|
||||||
|
. mapM (lift . fromAllo day meta (fromFun b))
|
||||||
|
pre <- fromAllos PreTax incPretax
|
||||||
tax <-
|
tax <-
|
||||||
concatEitherL
|
concatEitherL
|
||||||
<$> mapM (lift . fromTax day meta (taAcnt incFrom)) incTaxes
|
<$> mapM (lift . fromTax day meta (fromFun IntraTax)) incTaxes
|
||||||
post <- fromAllos incPosttax
|
post <- fromAllos PostTax incPosttax
|
||||||
let bal =
|
let bal =
|
||||||
BudgetTxType
|
BudgetTxType
|
||||||
{ bttTx =
|
{ bttTx =
|
||||||
BudgetTx
|
BudgetTx
|
||||||
{ btMeta = meta
|
{ btMeta = meta
|
||||||
, btWhen = day
|
, btWhen = day
|
||||||
, btFrom = incFrom
|
, btFrom = fromFun PostTax
|
||||||
, btTo = incToBal
|
, btTo = to
|
||||||
, btValue = balance
|
, btValue = balance
|
||||||
, btDesc = "balance after deductions"
|
, btDesc = "balance after deductions"
|
||||||
}
|
}
|
||||||
, bttType = FixedAmt
|
, bttType = FixedAmt
|
||||||
}
|
}
|
||||||
return $ concatEithersL [Right [bal], tax, Right pre, Right post]
|
return $ concatEithersL [Right [bal], tax, pre, post]
|
||||||
|
|
||||||
fromAllo
|
fromAllo
|
||||||
:: MonadFinance m
|
:: MonadFinance m
|
||||||
=> Day
|
=> Day
|
||||||
-> BudgetMeta
|
-> BudgetMeta
|
||||||
-> TaggedAcnt
|
-> BudgetSplit IncomeBucket
|
||||||
-> Allocation
|
-> Allocation
|
||||||
-> m [BudgetTxType]
|
-> m (EitherErr [BudgetTxType])
|
||||||
fromAllo day meta from Allocation {alloTo, alloAmts} = do
|
fromAllo day meta from Allocation {alloPath, alloAmts} = do
|
||||||
-- TODO this is going to be repeated a zillion times (might matter)
|
-- TODO this is going to be repeated a zillion times (might matter)
|
||||||
-- res <- expandTarget alloPath
|
res <- expandTarget alloPath
|
||||||
return $ fmap toBT alloAmts
|
return $ (\to -> fmap (toBT to) alloAmts) <$> res
|
||||||
where
|
where
|
||||||
toBT (Amount desc v) =
|
toBT to (Amount desc v) =
|
||||||
BudgetTxType
|
BudgetTxType
|
||||||
{ bttTx =
|
{ bttTx =
|
||||||
BudgetTx
|
BudgetTx
|
||||||
{ btFrom = from
|
{ btFrom = from
|
||||||
, btWhen = day
|
, btWhen = day
|
||||||
, btTo = alloTo
|
, btTo = to
|
||||||
, btValue = dec2Rat v
|
, btValue = dec2Rat v
|
||||||
, btDesc = desc
|
, btDesc = desc
|
||||||
, btMeta = meta
|
, btMeta = meta
|
||||||
|
@ -277,25 +287,22 @@ fromAllo day meta from Allocation {alloTo, alloAmts} = do
|
||||||
, bttType = FixedAmt
|
, bttType = FixedAmt
|
||||||
}
|
}
|
||||||
|
|
||||||
-- TODO maybe allow tags here?
|
|
||||||
fromTax
|
fromTax
|
||||||
:: MonadFinance m
|
:: MonadFinance m
|
||||||
=> Day
|
=> Day
|
||||||
-> BudgetMeta
|
-> BudgetMeta
|
||||||
-> AcntID
|
-> BudgetSplit IncomeBucket
|
||||||
-> Tax
|
-> Tax
|
||||||
-> m (EitherErr BudgetTxType)
|
-> m (EitherErr BudgetTxType)
|
||||||
fromTax day meta from Tax {taxAcnt = to, taxValue = v} = do
|
fromTax day meta from Tax {taxAcnt = to, taxValue = v} =
|
||||||
res <- checkAcntType ExpenseT to
|
-- TODO this is going to be repeated a zillion times (might matter)
|
||||||
return $ fmap go res
|
checkAcntType ExpenseT to $ \to_ ->
|
||||||
where
|
|
||||||
go to_ =
|
|
||||||
BudgetTxType
|
BudgetTxType
|
||||||
{ bttTx =
|
{ bttTx =
|
||||||
BudgetTx
|
BudgetTx
|
||||||
{ btFrom = TaggedAcnt from []
|
{ btFrom = from
|
||||||
, btWhen = day
|
, btWhen = day
|
||||||
, btTo = TaggedAcnt to_ []
|
, btTo = BudgetSplit to_ (Just Fixed)
|
||||||
, btValue = dec2Rat v
|
, btValue = dec2Rat v
|
||||||
, btDesc = ""
|
, btDesc = ""
|
||||||
, btMeta = meta
|
, btMeta = meta
|
||||||
|
@ -337,7 +344,11 @@ expandTransfer name t@Transfer {transAmounts, transTo, transCurrency, transFrom}
|
||||||
whenHash CTExpense t (Right []) $ \key ->
|
whenHash CTExpense t (Right []) $ \key ->
|
||||||
fmap (fmap concat . concatEithersL) $
|
fmap (fmap concat . concatEithersL) $
|
||||||
forM transAmounts $ \(TimeAmount (Amount desc v) atype pat) -> do
|
forM transAmounts $ \(TimeAmount (Amount desc v) atype pat) -> do
|
||||||
withDates pat $ \day ->
|
-- TODO this is going to be repeated a zillion times (might matter)
|
||||||
|
res <- lift $ expandTarget transTo
|
||||||
|
case res of
|
||||||
|
Left e -> return $ Left [e]
|
||||||
|
Right to -> withDates pat $ \day ->
|
||||||
let meta =
|
let meta =
|
||||||
BudgetMeta
|
BudgetMeta
|
||||||
{ bmCur = transCurrency
|
{ bmCur = transCurrency
|
||||||
|
@ -350,8 +361,8 @@ expandTransfer name t@Transfer {transAmounts, transTo, transCurrency, transFrom}
|
||||||
BudgetTx
|
BudgetTx
|
||||||
{ btMeta = meta
|
{ btMeta = meta
|
||||||
, btWhen = day
|
, btWhen = day
|
||||||
, btFrom = transFrom
|
, btFrom = BudgetSplit transFrom Nothing
|
||||||
, btTo = transTo
|
, btTo = to
|
||||||
, btValue = dec2Rat v
|
, btValue = dec2Rat v
|
||||||
, btDesc = desc
|
, btDesc = desc
|
||||||
}
|
}
|
||||||
|
@ -361,66 +372,87 @@ expandTransfer name t@Transfer {transAmounts, transTo, transCurrency, transFrom}
|
||||||
|
|
||||||
insertBudgetTx :: MonadFinance m => BudgetTx -> SqlPersistT m [InsertError]
|
insertBudgetTx :: MonadFinance m => BudgetTx -> SqlPersistT m [InsertError]
|
||||||
insertBudgetTx BudgetTx {btFrom, btTo, btMeta, btValue, btDesc, btWhen} = do
|
insertBudgetTx BudgetTx {btFrom, btTo, btMeta, btValue, btDesc, btWhen} = do
|
||||||
res <- lift $ splitPair btFrom btTo (bmCur btMeta) btValue
|
res <- lift $ splitPair (bsAcnt btFrom) (bsAcnt btTo) (bmCur btMeta) btValue
|
||||||
unlessLefts_ res $ \((sFrom, sTo), exchange) -> do
|
unlessLefts_ res $ \((sFrom, sTo), exchange) -> do
|
||||||
insertPair sFrom sTo
|
insertPair sFrom sTo
|
||||||
forM_ exchange $ \(xFrom, xTo) -> insertPair xFrom xTo
|
forM_ exchange $ \(xFrom, xTo) -> insertPair xFrom xTo
|
||||||
where
|
where
|
||||||
insertPair from to = do
|
insertPair from to = do
|
||||||
k <- insert $ TransactionR (bmCommit btMeta) btWhen btDesc
|
k <- insert $ TransactionR (bmCommit btMeta) btWhen btDesc
|
||||||
insertBudgetLabel k from
|
insertBudgetLabel name k IncomeBucketR from btFrom
|
||||||
insertBudgetLabel k to
|
insertBudgetLabel name k ExpenseBucketR to btTo
|
||||||
insertBudgetLabel k split = do
|
name = bmName btMeta
|
||||||
|
|
||||||
|
insertBudgetLabel
|
||||||
|
:: (MonadUnliftIO m, PersistRecordBackend record SqlBackend)
|
||||||
|
=> T.Text
|
||||||
|
-> Key TransactionR
|
||||||
|
-> (Key BudgetLabelR -> a -> record)
|
||||||
|
-> KeySplit
|
||||||
|
-> BudgetSplit a
|
||||||
|
-> SqlPersistT m ()
|
||||||
|
insertBudgetLabel name k bucketType split bs = do
|
||||||
sk <- insertSplit k split
|
sk <- insertSplit k split
|
||||||
insert_ $ BudgetLabelR sk $ bmName btMeta
|
bk <- insert $ BudgetLabelR sk name
|
||||||
|
forM_ (bsBucket bs) $ insert_ . bucketType bk
|
||||||
|
|
||||||
type SplitPair = (KeySplit, KeySplit)
|
type SplitPair = (KeySplit, KeySplit)
|
||||||
|
|
||||||
splitPair
|
splitPair
|
||||||
:: MonadFinance m
|
:: MonadFinance m
|
||||||
=> TaggedAcnt
|
=> AcntID
|
||||||
-> TaggedAcnt
|
-> AcntID
|
||||||
-> BudgetCurrency
|
-> BudgetCurrency
|
||||||
-> Rational
|
-> Rational
|
||||||
-> m (EitherErrs (SplitPair, Maybe SplitPair))
|
-> m (EitherErrs (SplitPair, Maybe SplitPair))
|
||||||
splitPair from to cur val = case cur of
|
splitPair from to cur val = case cur of
|
||||||
NoX curid -> fmap (fmap (,Nothing)) $ pair curid from to val
|
NoX curid -> fmap (fmap (,Nothing)) $ pair curid from to val
|
||||||
X (Exchange {xFromCur, xToCur, xAcnt, xRate}) -> do
|
X (Exchange {xFromCur, xToCur, xAcnt, xRate}) -> do
|
||||||
let middle = TaggedAcnt xAcnt []
|
res1 <- pair xFromCur from xAcnt val
|
||||||
res1 <- pair xFromCur from middle val
|
res2 <- pair xToCur xAcnt to (val * dec2Rat xRate)
|
||||||
res2 <- pair xToCur middle to (val * dec2Rat xRate)
|
|
||||||
return $ concatEithers2 res1 res2 $ \a b -> (a, Just b)
|
return $ concatEithers2 res1 res2 $ \a b -> (a, Just b)
|
||||||
where
|
where
|
||||||
pair curid from_ to_ v = do
|
pair curid from_ to_ v = do
|
||||||
s1 <- split curid from_ (-v)
|
s1 <- split curid from_ (-v)
|
||||||
s2 <- split curid to_ v
|
s2 <- split curid to_ v
|
||||||
return $ concatEithers2 s1 s2 (,)
|
return $ concatEithers2 s1 s2 (,)
|
||||||
split c TaggedAcnt {taAcnt, taTags} v =
|
split c a v =
|
||||||
resolveSplit $
|
resolveSplit $
|
||||||
Split
|
Split
|
||||||
{ sAcnt = taAcnt
|
{ sAcnt = a
|
||||||
, sValue = v
|
, sValue = v
|
||||||
, sComment = ""
|
, sComment = ""
|
||||||
, sCurrency = c
|
, sCurrency = c
|
||||||
, sTags = taTags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expandTarget
|
||||||
|
:: MonadFinance m
|
||||||
|
=> TransferTarget
|
||||||
|
-> m (EitherErr (BudgetSplit ExpenseBucket))
|
||||||
|
expandTarget t = case t of
|
||||||
|
ExpenseTarget i b -> checkAcntType ExpenseT i $ (`BudgetSplit` (Just b))
|
||||||
|
GenericTarget i ->
|
||||||
|
checkAcntTypes (LiabilityT :| [AssetT, EquityT]) i $
|
||||||
|
(`BudgetSplit` Nothing)
|
||||||
|
|
||||||
checkAcntType
|
checkAcntType
|
||||||
:: MonadFinance m
|
:: MonadFinance m
|
||||||
=> AcntType
|
=> AcntType
|
||||||
-> AcntID
|
-> AcntID
|
||||||
-> m (EitherErr AcntID)
|
-> (AcntID -> a)
|
||||||
|
-> m (EitherErr a)
|
||||||
checkAcntType t = checkAcntTypes (t :| [])
|
checkAcntType t = checkAcntTypes (t :| [])
|
||||||
|
|
||||||
checkAcntTypes
|
checkAcntTypes
|
||||||
:: MonadFinance m
|
:: MonadFinance m
|
||||||
=> NE.NonEmpty AcntType
|
=> NE.NonEmpty AcntType
|
||||||
-> AcntID
|
-> AcntID
|
||||||
-> m (EitherErr AcntID)
|
-> (AcntID -> a)
|
||||||
checkAcntTypes ts i = (go =<<) <$> lookupAccountType i
|
-> m (EitherErr a)
|
||||||
|
checkAcntTypes ts i f = (go =<<) <$> lookupAccountType i
|
||||||
where
|
where
|
||||||
go t
|
go t
|
||||||
| t `L.elem` ts = Right i
|
| t `L.elem` ts = Right $ f i
|
||||||
| otherwise = Left $ AccountError i ts
|
| otherwise = Left $ AccountError i ts
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
@ -473,7 +505,6 @@ insertImport i = whenHash CTImport i [] $ \c -> do
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- low-level transaction stuff
|
-- low-level transaction stuff
|
||||||
|
|
||||||
-- TODO tags here?
|
|
||||||
txPair
|
txPair
|
||||||
:: MonadFinance m
|
:: MonadFinance m
|
||||||
=> Day
|
=> Day
|
||||||
|
@ -485,7 +516,7 @@ txPair
|
||||||
-> m (EitherErrs KeyTx)
|
-> m (EitherErrs KeyTx)
|
||||||
txPair day from to cur val desc = resolveTx tx
|
txPair day from to cur val desc = resolveTx tx
|
||||||
where
|
where
|
||||||
split a v = Split {sAcnt = a, sValue = v, sComment = "", sCurrency = cur, sTags = []}
|
split a v = Split {sAcnt = a, sValue = v, sComment = "", sCurrency = cur}
|
||||||
tx =
|
tx =
|
||||||
Tx
|
Tx
|
||||||
{ txDescr = desc
|
{ txDescr = desc
|
||||||
|
@ -500,21 +531,17 @@ resolveTx t@Tx {txSplits = ss} = do
|
||||||
return $ fmap (\kss -> t {txSplits = kss}) res
|
return $ fmap (\kss -> t {txSplits = kss}) res
|
||||||
|
|
||||||
resolveSplit :: MonadFinance m => BalSplit -> m (EitherErrs KeySplit)
|
resolveSplit :: MonadFinance m => BalSplit -> m (EitherErrs KeySplit)
|
||||||
resolveSplit s@Split {sAcnt, sCurrency, sValue, sTags} = do
|
resolveSplit s@Split {sAcnt = p, sCurrency = c, sValue = v} = do
|
||||||
aid <- lookupAccountKey sAcnt
|
aid <- lookupAccountKey p
|
||||||
cid <- lookupCurrency sCurrency
|
cid <- lookupCurrency c
|
||||||
sign <- lookupAccountSign sAcnt
|
sign <- lookupAccountSign p
|
||||||
tags <- mapM lookupTag sTags
|
|
||||||
-- TODO correct sign here?
|
-- TODO correct sign here?
|
||||||
-- TODO lenses would be nice here
|
-- TODO lenses would be nice here
|
||||||
return $
|
return $ concatEither3 aid cid sign $ \aid_ cid_ sign_ ->
|
||||||
(concatEithers2 (concatEither3 aid cid sign (,,)) $ concatEitherL tags) $
|
|
||||||
\(aid_, cid_, sign_) tags_ ->
|
|
||||||
s
|
s
|
||||||
{ sAcnt = aid_
|
{ sAcnt = aid_
|
||||||
, sCurrency = cid_
|
, sCurrency = cid_
|
||||||
, sValue = sValue * fromIntegral (sign2Int sign_)
|
, sValue = v * fromIntegral (sign2Int sign_)
|
||||||
, sTags = tags_
|
|
||||||
}
|
}
|
||||||
|
|
||||||
insertTx :: MonadUnliftIO m => Key CommitR -> KeyTx -> SqlPersistT m ()
|
insertTx :: MonadUnliftIO m => Key CommitR -> KeyTx -> SqlPersistT m ()
|
||||||
|
@ -523,10 +550,8 @@ insertTx c Tx {txDate = d, txDescr = e, txSplits = ss} = do
|
||||||
mapM_ (insertSplit k) ss
|
mapM_ (insertSplit k) ss
|
||||||
|
|
||||||
insertSplit :: MonadUnliftIO m => Key TransactionR -> KeySplit -> SqlPersistT m (Key SplitR)
|
insertSplit :: MonadUnliftIO m => Key TransactionR -> KeySplit -> SqlPersistT m (Key SplitR)
|
||||||
insertSplit t Split {sAcnt, sCurrency, sValue, sComment, sTags} = do
|
insertSplit t Split {sAcnt = aid, sCurrency = cid, sValue = v, sComment = c} = do
|
||||||
k <- insert $ SplitR t sCurrency sAcnt sComment sValue
|
insert $ SplitR t cid aid c v
|
||||||
mapM_ (insert_ . TagRelationR k) sTags
|
|
||||||
return k
|
|
||||||
|
|
||||||
lookupAccount :: MonadFinance m => AcntID -> m (EitherErr (Key AccountR, AcntSign, AcntType))
|
lookupAccount :: MonadFinance m => AcntID -> m (EitherErr (Key AccountR, AcntSign, AcntType))
|
||||||
lookupAccount p = lookupErr (DBKey AcntField) p <$> (askDBState kmAccount)
|
lookupAccount p = lookupErr (DBKey AcntField) p <$> (askDBState kmAccount)
|
||||||
|
@ -543,9 +568,6 @@ lookupAccountType = fmap (fmap thdOf3) . lookupAccount
|
||||||
lookupCurrency :: MonadFinance m => T.Text -> m (EitherErr (Key CurrencyR))
|
lookupCurrency :: MonadFinance m => T.Text -> m (EitherErr (Key CurrencyR))
|
||||||
lookupCurrency c = lookupErr (DBKey CurField) c <$> (askDBState kmCurrency)
|
lookupCurrency c = lookupErr (DBKey CurField) c <$> (askDBState kmCurrency)
|
||||||
|
|
||||||
lookupTag :: MonadFinance m => TagID -> m (EitherErr (Key TagR))
|
|
||||||
lookupTag c = lookupErr (DBKey TagField) c <$> (askDBState kmTag)
|
|
||||||
|
|
||||||
-- TODO this hashes twice (not that it really matters)
|
-- TODO this hashes twice (not that it really matters)
|
||||||
whenHash
|
whenHash
|
||||||
:: (Hashable a, MonadFinance m)
|
:: (Hashable a, MonadFinance m)
|
||||||
|
|
|
@ -6,6 +6,7 @@ module Internal.Statement
|
||||||
where
|
where
|
||||||
|
|
||||||
import Data.Csv
|
import Data.Csv
|
||||||
|
import Internal.Database.Model
|
||||||
import Internal.Types
|
import Internal.Types
|
||||||
import Internal.Utils
|
import Internal.Utils
|
||||||
import RIO
|
import RIO
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
{-# LANGUAGE DeriveAnyClass #-}
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
{-# LANGUAGE QuasiQuotes #-}
|
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
{-# LANGUAGE UndecidableInstances #-}
|
|
||||||
|
|
||||||
module Internal.Types where
|
module Internal.Types where
|
||||||
|
|
||||||
|
@ -9,7 +7,6 @@ import Data.Fix (Fix (..), foldFix)
|
||||||
import Data.Functor.Foldable (embed)
|
import Data.Functor.Foldable (embed)
|
||||||
import qualified Data.Functor.Foldable.TH as TH
|
import qualified Data.Functor.Foldable.TH as TH
|
||||||
import Database.Persist.Sql hiding (Desc, In, Statement)
|
import Database.Persist.Sql hiding (Desc, In, Statement)
|
||||||
import Database.Persist.TH
|
|
||||||
import Dhall hiding (embed, maybe)
|
import Dhall hiding (embed, maybe)
|
||||||
import Dhall.TH
|
import Dhall.TH
|
||||||
import Language.Haskell.TH.Syntax (Lift)
|
import Language.Haskell.TH.Syntax (Lift)
|
||||||
|
@ -35,10 +32,12 @@ makeHaskellTypesWith
|
||||||
, MultipleConstructors "MatchYMD" "(./dhall/Types.dhall).MatchYMD"
|
, MultipleConstructors "MatchYMD" "(./dhall/Types.dhall).MatchYMD"
|
||||||
, MultipleConstructors "MatchDate" "(./dhall/Types.dhall).MatchDate"
|
, MultipleConstructors "MatchDate" "(./dhall/Types.dhall).MatchDate"
|
||||||
, MultipleConstructors "SplitNum" "(./dhall/Types.dhall).SplitNum"
|
, MultipleConstructors "SplitNum" "(./dhall/Types.dhall).SplitNum"
|
||||||
|
, MultipleConstructors "ExpenseBucket" "(./dhall/Types.dhall).ExpenseBucket"
|
||||||
|
, MultipleConstructors "IncomeBucket" "(./dhall/Types.dhall).IncomeBucket"
|
||||||
, MultipleConstructors "AmountType" "(./dhall/Types.dhall).AmountType"
|
, MultipleConstructors "AmountType" "(./dhall/Types.dhall).AmountType"
|
||||||
|
, MultipleConstructors "TransferTarget" "(./dhall/Types.dhall).TransferTarget"
|
||||||
, MultipleConstructors "BudgetCurrency" "(./dhall/Types.dhall).BudgetCurrency"
|
, MultipleConstructors "BudgetCurrency" "(./dhall/Types.dhall).BudgetCurrency"
|
||||||
, SingleConstructor "Currency" "Currency" "(./dhall/Types.dhall).Currency"
|
, SingleConstructor "Currency" "Currency" "(./dhall/Types.dhall).Currency"
|
||||||
, SingleConstructor "Tag" "Tag" "(./dhall/Types.dhall).Tag"
|
|
||||||
, SingleConstructor "Gregorian" "Gregorian" "(./dhall/Types.dhall).Gregorian"
|
, SingleConstructor "Gregorian" "Gregorian" "(./dhall/Types.dhall).Gregorian"
|
||||||
, SingleConstructor "GregorianM" "GregorianM" "(./dhall/Types.dhall).GregorianM"
|
, SingleConstructor "GregorianM" "GregorianM" "(./dhall/Types.dhall).GregorianM"
|
||||||
, SingleConstructor "Interval" "Interval" "(./dhall/Types.dhall).Interval"
|
, SingleConstructor "Interval" "Interval" "(./dhall/Types.dhall).Interval"
|
||||||
|
@ -57,14 +56,42 @@ makeHaskellTypesWith
|
||||||
, SingleConstructor "Budget" "Budget" "(./dhall/Types.dhall).Budget"
|
, SingleConstructor "Budget" "Budget" "(./dhall/Types.dhall).Budget"
|
||||||
, SingleConstructor "Transfer" "Transfer" "(./dhall/Types.dhall).Transfer"
|
, SingleConstructor "Transfer" "Transfer" "(./dhall/Types.dhall).Transfer"
|
||||||
, SingleConstructor "Exchange" "Exchange" "(./dhall/Types.dhall).Exchange"
|
, SingleConstructor "Exchange" "Exchange" "(./dhall/Types.dhall).Exchange"
|
||||||
, SingleConstructor "TaggedAcnt" "TaggedAcnt" "(./dhall/Types.dhall).TaggedAcnt"
|
|
||||||
, SingleConstructor "AcntSet" "AcntSet" "(./dhall/Types.dhall).AcntSet.Type"
|
, SingleConstructor "AcntSet" "AcntSet" "(./dhall/Types.dhall).AcntSet.Type"
|
||||||
, SingleConstructor "ShadowMatch" "ShadowMatch" "(./dhall/Types.dhall).ShadowMatch.Type"
|
, SingleConstructor "ShadowMatch" "ShadowMatch" "(./dhall/Types.dhall).ShadowMatch.Type"
|
||||||
, SingleConstructor "ShadowTransfer" "ShadowTransfer" "(./dhall/Types.dhall).ShadowTransfer"
|
, SingleConstructor "ShadowTransfer" "ShadowTransfer" "(./dhall/Types.dhall).ShadowTransfer"
|
||||||
]
|
]
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
-- lots of instances for dhall types
|
-- account tree
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
-- curencies
|
||||||
|
|
||||||
deriving instance Eq Currency
|
deriving instance Eq Currency
|
||||||
|
|
||||||
|
@ -72,11 +99,48 @@ deriving instance Lift Currency
|
||||||
|
|
||||||
deriving instance Hashable Currency
|
deriving instance Hashable Currency
|
||||||
|
|
||||||
deriving instance Eq Tag
|
type CurID = T.Text
|
||||||
|
|
||||||
deriving instance Lift Tag
|
-------------------------------------------------------------------------------
|
||||||
|
-- DHALL CONFIG
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
deriving instance Hashable Tag
|
data Config_ a = Config_
|
||||||
|
{ global :: !Global
|
||||||
|
, budget :: ![Budget]
|
||||||
|
, currencies :: ![Currency]
|
||||||
|
, statements :: ![Statement]
|
||||||
|
, accounts :: !a
|
||||||
|
, 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)
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
-- accounts
|
||||||
|
|
||||||
|
type AcntID = T.Text
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Time Patterns (for assigning when budget events will happen)
|
||||||
|
|
||||||
deriving instance Eq TimeUnit
|
deriving instance Eq TimeUnit
|
||||||
|
|
||||||
|
@ -170,16 +234,13 @@ deriving instance Show DatePat
|
||||||
|
|
||||||
deriving instance Hashable DatePat
|
deriving instance Hashable DatePat
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Budget (projecting into the future)
|
||||||
|
|
||||||
deriving instance Eq Budget
|
deriving instance Eq Budget
|
||||||
|
|
||||||
deriving instance Hashable Budget
|
deriving instance Hashable Budget
|
||||||
|
|
||||||
deriving instance Eq TaggedAcnt
|
|
||||||
|
|
||||||
deriving instance Hashable TaggedAcnt
|
|
||||||
|
|
||||||
deriving instance Ord TaggedAcnt
|
|
||||||
|
|
||||||
deriving instance Eq Income
|
deriving instance Eq Income
|
||||||
|
|
||||||
deriving instance Hashable Income
|
deriving instance Hashable Income
|
||||||
|
@ -204,6 +265,14 @@ deriving instance Eq Allocation
|
||||||
|
|
||||||
deriving instance Hashable Allocation
|
deriving instance Hashable Allocation
|
||||||
|
|
||||||
|
deriving instance Eq IncomeBucket
|
||||||
|
|
||||||
|
deriving instance Hashable IncomeBucket
|
||||||
|
|
||||||
|
deriving instance Show IncomeBucket
|
||||||
|
|
||||||
|
deriving instance Read IncomeBucket
|
||||||
|
|
||||||
toPersistText :: Show a => a -> PersistValue
|
toPersistText :: Show a => a -> PersistValue
|
||||||
toPersistText = PersistText . T.pack . show
|
toPersistText = PersistText . T.pack . show
|
||||||
|
|
||||||
|
@ -214,6 +283,30 @@ fromPersistText what (PersistText t) = case readMaybe $ T.unpack t of
|
||||||
fromPersistText what x =
|
fromPersistText what x =
|
||||||
Left $ T.unwords ["error when deserializing", what, "; got", T.pack (show x)]
|
Left $ T.unwords ["error when deserializing", what, "; got", T.pack (show x)]
|
||||||
|
|
||||||
|
instance PersistField IncomeBucket where
|
||||||
|
toPersistValue = toPersistText
|
||||||
|
|
||||||
|
fromPersistValue = fromPersistText "IncomeBucket"
|
||||||
|
|
||||||
|
instance PersistFieldSql IncomeBucket where
|
||||||
|
sqlType _ = SqlString
|
||||||
|
|
||||||
|
deriving instance Eq ExpenseBucket
|
||||||
|
|
||||||
|
deriving instance Hashable ExpenseBucket
|
||||||
|
|
||||||
|
deriving instance Show ExpenseBucket
|
||||||
|
|
||||||
|
deriving instance Read ExpenseBucket
|
||||||
|
|
||||||
|
instance PersistField ExpenseBucket where
|
||||||
|
toPersistValue = toPersistText
|
||||||
|
|
||||||
|
fromPersistValue = fromPersistText "ExpenseBucket"
|
||||||
|
|
||||||
|
instance PersistFieldSql ExpenseBucket where
|
||||||
|
sqlType _ = SqlString
|
||||||
|
|
||||||
deriving instance Eq AmountType
|
deriving instance Eq AmountType
|
||||||
|
|
||||||
deriving instance Hashable AmountType
|
deriving instance Hashable AmountType
|
||||||
|
@ -222,6 +315,10 @@ deriving instance Eq TimeAmount
|
||||||
|
|
||||||
deriving instance Hashable TimeAmount
|
deriving instance Hashable TimeAmount
|
||||||
|
|
||||||
|
deriving instance Eq TransferTarget
|
||||||
|
|
||||||
|
deriving instance Hashable TransferTarget
|
||||||
|
|
||||||
deriving instance Eq Transfer
|
deriving instance Eq Transfer
|
||||||
|
|
||||||
deriving instance Hashable Transfer
|
deriving instance Hashable Transfer
|
||||||
|
@ -238,149 +335,27 @@ deriving instance Eq ShadowMatch
|
||||||
|
|
||||||
deriving instance Hashable ShadowMatch
|
deriving instance Hashable ShadowMatch
|
||||||
|
|
||||||
deriving instance Eq MatchVal
|
|
||||||
|
|
||||||
deriving instance Hashable MatchVal
|
|
||||||
|
|
||||||
deriving instance Show MatchVal
|
|
||||||
|
|
||||||
deriving instance Eq MatchYMD
|
|
||||||
|
|
||||||
deriving instance Hashable MatchYMD
|
|
||||||
|
|
||||||
deriving instance Show MatchYMD
|
|
||||||
|
|
||||||
deriving instance Eq MatchDate
|
|
||||||
|
|
||||||
deriving instance Hashable MatchDate
|
|
||||||
|
|
||||||
deriving instance Show MatchDate
|
|
||||||
|
|
||||||
deriving instance Eq Decimal
|
|
||||||
|
|
||||||
deriving instance Hashable Decimal
|
|
||||||
|
|
||||||
deriving instance Show Decimal
|
|
||||||
|
|
||||||
-- TODO this just looks silly...but not sure how to simplify it
|
|
||||||
instance Ord MatchYMD where
|
|
||||||
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 MatchDate where
|
|
||||||
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 Eq SplitNum
|
|
||||||
|
|
||||||
deriving instance Hashable SplitNum
|
|
||||||
|
|
||||||
deriving instance Show SplitNum
|
|
||||||
|
|
||||||
deriving instance Eq Manual
|
|
||||||
|
|
||||||
deriving instance Hashable Manual
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
-- 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 :: !Global
|
|
||||||
, budget :: ![Budget]
|
|
||||||
, currencies :: ![Currency]
|
|
||||||
, statements :: ![Statement]
|
|
||||||
, accounts :: !a
|
|
||||||
, tags :: ![Tag]
|
|
||||||
, 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)
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- dhall type overrides (since dhall can't import types with parameters...yet)
|
-- Statements (data from the past)
|
||||||
|
|
||||||
-- TODO newtypes for these?
|
|
||||||
type AcntID = T.Text
|
|
||||||
|
|
||||||
type CurID = T.Text
|
|
||||||
|
|
||||||
type TagID = T.Text
|
|
||||||
|
|
||||||
data Statement
|
data Statement
|
||||||
= StmtManual !Manual
|
= StmtManual !Manual
|
||||||
| StmtImport !Import
|
| StmtImport !Import
|
||||||
deriving (Eq, Hashable, Generic, FromDhall)
|
deriving (Eq, Hashable, Generic, FromDhall)
|
||||||
|
|
||||||
data Split a v c t = Split
|
deriving instance Eq Manual
|
||||||
|
|
||||||
|
deriving instance Hashable Manual
|
||||||
|
|
||||||
|
data Split a v c = Split
|
||||||
{ sAcnt :: !a
|
{ sAcnt :: !a
|
||||||
, sValue :: !v
|
, sValue :: !v
|
||||||
, sCurrency :: !c
|
, sCurrency :: !c
|
||||||
, sComment :: !T.Text
|
, sComment :: !T.Text
|
||||||
, sTags :: ![t]
|
|
||||||
}
|
}
|
||||||
deriving (Eq, Generic, Hashable, Show)
|
deriving (Eq, Generic, Hashable, Show, FromDhall)
|
||||||
|
|
||||||
type ExpSplit = Split SplitAcnt (Maybe SplitNum) SplitCur TagID
|
type ExpSplit = Split SplitAcnt (Maybe SplitNum) SplitCur
|
||||||
|
|
||||||
instance FromDhall ExpSplit
|
|
||||||
|
|
||||||
data Tx s = Tx
|
data Tx s = Tx
|
||||||
{ txDescr :: !T.Text
|
{ txDescr :: !T.Text
|
||||||
|
@ -413,6 +388,52 @@ data Import = Import
|
||||||
}
|
}
|
||||||
deriving (Eq, Hashable, Generic, FromDhall)
|
deriving (Eq, Hashable, Generic, FromDhall)
|
||||||
|
|
||||||
|
deriving instance Eq MatchVal
|
||||||
|
|
||||||
|
deriving instance Hashable MatchVal
|
||||||
|
|
||||||
|
deriving instance Show MatchVal
|
||||||
|
|
||||||
|
deriving instance Eq MatchYMD
|
||||||
|
|
||||||
|
deriving instance Hashable MatchYMD
|
||||||
|
|
||||||
|
deriving instance Show MatchYMD
|
||||||
|
|
||||||
|
deriving instance Eq MatchDate
|
||||||
|
|
||||||
|
deriving instance Hashable MatchDate
|
||||||
|
|
||||||
|
deriving instance Show MatchDate
|
||||||
|
|
||||||
|
-- TODO this just looks silly...but not sure how to simplify it
|
||||||
|
instance Ord MatchYMD where
|
||||||
|
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 MatchDate where
|
||||||
|
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 Eq SplitNum
|
||||||
|
|
||||||
|
deriving instance Hashable SplitNum
|
||||||
|
|
||||||
|
deriving instance Show SplitNum
|
||||||
|
|
||||||
-- | the value of a field in split (text version)
|
-- | the value of a field in split (text version)
|
||||||
-- can either be a raw (constant) value, a lookup from the record, or a map
|
-- can either be a raw (constant) value, a lookup from the record, or a map
|
||||||
-- between the lookup and some other value
|
-- between the lookup and some other value
|
||||||
|
@ -466,54 +487,13 @@ data Match re = Match
|
||||||
deriving instance Show (Match T.Text)
|
deriving instance Show (Match T.Text)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- DATABASE MODEL
|
-- Specialized dhall types
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
share
|
deriving instance Eq Decimal
|
||||||
[mkPersist sqlSettings, mkMigrate "migrateAll"]
|
|
||||||
[persistLowerCase|
|
deriving instance Hashable Decimal
|
||||||
CommitR sql=commits
|
|
||||||
hash Int
|
deriving instance Show Decimal
|
||||||
type ConfigType
|
|
||||||
deriving Show Eq
|
|
||||||
CurrencyR sql=currencies
|
|
||||||
symbol T.Text
|
|
||||||
fullname T.Text
|
|
||||||
deriving Show Eq
|
|
||||||
TagR sql=tags
|
|
||||||
symbol T.Text
|
|
||||||
fullname T.Text
|
|
||||||
deriving Show Eq
|
|
||||||
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
|
|
||||||
TagRelationR sql=tag_relations
|
|
||||||
split SplitRId OnDeleteCascade
|
|
||||||
tag TagRId OnDeleteCascade
|
|
||||||
BudgetLabelR sql=budget_labels
|
|
||||||
split SplitRId OnDeleteCascade
|
|
||||||
budgetName T.Text
|
|
||||||
deriving Show Eq
|
|
||||||
|]
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- database cache types
|
-- database cache types
|
||||||
|
@ -539,60 +519,6 @@ instance PersistField ConfigType where
|
||||||
maybe (Left $ "could not parse: " <> v) Right $ readMaybe $ T.unpack v
|
maybe (Left $ "could not parse: " <> v) Right $ readMaybe $ T.unpack v
|
||||||
fromPersistValue _ = Left "wrong type"
|
fromPersistValue _ = Left "wrong type"
|
||||||
|
|
||||||
type AccountMap = M.Map AcntID (AccountRId, AcntSign, AcntType)
|
|
||||||
|
|
||||||
type CurrencyMap = M.Map CurID CurrencyRId
|
|
||||||
|
|
||||||
type TagMap = M.Map TagID TagRId
|
|
||||||
|
|
||||||
data DBState = DBState
|
|
||||||
{ kmCurrency :: !CurrencyMap
|
|
||||||
, kmAccount :: !AccountMap
|
|
||||||
, kmTag :: !TagMap
|
|
||||||
, kmBudgetInterval :: !Bounds
|
|
||||||
, kmStatementInterval :: !Bounds
|
|
||||||
, kmNewCommits :: ![Int]
|
|
||||||
, kmConfigDir :: !FilePath
|
|
||||||
}
|
|
||||||
|
|
||||||
type MappingT m = ReaderT DBState (SqlPersistT m)
|
|
||||||
|
|
||||||
type KeySplit = Split AccountRId Rational CurrencyRId TagRId
|
|
||||||
|
|
||||||
type KeyTx = Tx KeySplit
|
|
||||||
|
|
||||||
type TreeR = Tree ([T.Text], AccountRId)
|
|
||||||
|
|
||||||
type Balances = M.Map AccountRId Rational
|
|
||||||
|
|
||||||
type BalanceM m = ReaderT (MVar Balances) m
|
|
||||||
|
|
||||||
class MonadUnliftIO m => MonadFinance m where
|
|
||||||
askDBState :: (DBState -> a) -> m a
|
|
||||||
|
|
||||||
instance MonadUnliftIO m => MonadFinance (ReaderT DBState m) where
|
|
||||||
askDBState = asks
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
-- misc
|
-- misc
|
||||||
|
|
||||||
|
@ -628,6 +554,8 @@ data TxRecord = TxRecord
|
||||||
|
|
||||||
type Bounds = (Day, Natural)
|
type Bounds = (Day, Natural)
|
||||||
|
|
||||||
|
-- type MaybeBounds = (Maybe Day, Maybe Day)
|
||||||
|
|
||||||
data Keyed a = Keyed
|
data Keyed a = Keyed
|
||||||
{ kKey :: !Int64
|
{ kKey :: !Int64
|
||||||
, kVal :: !a
|
, kVal :: !a
|
||||||
|
@ -650,9 +578,9 @@ accountSign IncomeT = Credit
|
||||||
accountSign LiabilityT = Credit
|
accountSign LiabilityT = Credit
|
||||||
accountSign EquityT = Credit
|
accountSign EquityT = Credit
|
||||||
|
|
||||||
type RawSplit = Split AcntID (Maybe Rational) CurID TagID
|
type RawSplit = Split AcntID (Maybe Rational) CurID
|
||||||
|
|
||||||
type BalSplit = Split AcntID Rational CurID TagID
|
type BalSplit = Split AcntID Rational CurID
|
||||||
|
|
||||||
type RawTx = Tx RawSplit
|
type RawTx = Tx RawSplit
|
||||||
|
|
||||||
|
@ -660,14 +588,11 @@ type BalTx = Tx BalSplit
|
||||||
|
|
||||||
data MatchRes a = MatchPass !a | MatchFail | MatchSkip
|
data MatchRes a = MatchPass !a | MatchFail | MatchSkip
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- exception types
|
|
||||||
|
|
||||||
data BalanceType = TooFewSplits | NotOneBlank deriving (Show)
|
data BalanceType = TooFewSplits | NotOneBlank deriving (Show)
|
||||||
|
|
||||||
data MatchType = MatchNumeric | MatchText deriving (Show)
|
data MatchType = MatchNumeric | MatchText deriving (Show)
|
||||||
|
|
||||||
data SplitIDType = AcntField | CurField | TagField deriving (Show)
|
data SplitIDType = AcntField | CurField deriving (Show)
|
||||||
|
|
||||||
data LookupSuberr
|
data LookupSuberr
|
||||||
= SplitIDField !SplitIDType
|
= SplitIDField !SplitIDType
|
||||||
|
|
|
@ -170,7 +170,6 @@ toTx sc sa toSplits r@TxRecord {trAmount, trDate, trDesc} =
|
||||||
, sCurrency = c_
|
, sCurrency = c_
|
||||||
, sValue = Just trAmount
|
, sValue = Just trAmount
|
||||||
, sComment = ""
|
, sComment = ""
|
||||||
, sTags = [] -- TODO what goes here?
|
|
||||||
}
|
}
|
||||||
in Tx
|
in Tx
|
||||||
{ txTags = []
|
{ txTags = []
|
||||||
|
@ -358,10 +357,8 @@ showError other = (: []) $ case other of
|
||||||
SplitValField -> "split value"
|
SplitValField -> "split value"
|
||||||
MatchField mt -> T.unwords [matchName mt, "match"]
|
MatchField mt -> T.unwords [matchName mt, "match"]
|
||||||
DBKey st -> T.unwords ["database", idName st, "ID key"]
|
DBKey st -> T.unwords ["database", idName st, "ID key"]
|
||||||
-- TODO this should be its own function
|
|
||||||
idName AcntField = "account"
|
idName AcntField = "account"
|
||||||
idName CurField = "currency"
|
idName CurField = "currency"
|
||||||
idName TagField = "tag"
|
|
||||||
matchName MatchNumeric = "numeric"
|
matchName MatchNumeric = "numeric"
|
||||||
matchName MatchText = "text"
|
matchName MatchText = "text"
|
||||||
(IncomeError dp) ->
|
(IncomeError dp) ->
|
||||||
|
|
Loading…
Reference in New Issue