REF rearrange stuff
This commit is contained in:
parent
bae847d9f3
commit
8c9dc1e970
|
@ -180,7 +180,6 @@ runSync c = do
|
||||||
hSs' <- mapErrorsIO (readHistStmt root) hSs
|
hSs' <- mapErrorsIO (readHistStmt root) hSs
|
||||||
hTs' <- liftIOExceptT $ mapErrors readHistTransfer hTs
|
hTs' <- liftIOExceptT $ mapErrors readHistTransfer hTs
|
||||||
bTs <- liftIOExceptT $ mapErrors readBudget $ budget config
|
bTs <- liftIOExceptT $ mapErrors readBudget $ budget config
|
||||||
-- lift $ print hTs'
|
|
||||||
return $ second concat $ partitionEithers $ hSs' ++ hTs' ++ bTs
|
return $ second concat $ partitionEithers $ hSs' ++ hTs' ++ bTs
|
||||||
|
|
||||||
-- Update the DB.
|
-- Update the DB.
|
||||||
|
|
|
@ -8,6 +8,7 @@ where
|
||||||
import Control.Monad.Except
|
import Control.Monad.Except
|
||||||
import Data.Csv
|
import Data.Csv
|
||||||
import Data.Foldable
|
import Data.Foldable
|
||||||
|
import GHC.Real
|
||||||
import Internal.Database
|
import Internal.Database
|
||||||
import Internal.Types.Main
|
import Internal.Types.Main
|
||||||
import Internal.Utils
|
import Internal.Utils
|
||||||
|
@ -19,6 +20,8 @@ import qualified RIO.Map as M
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import RIO.Time
|
import RIO.Time
|
||||||
import qualified RIO.Vector as V
|
import qualified RIO.Vector as V
|
||||||
|
import Text.Regex.TDFA hiding (matchAll)
|
||||||
|
import Text.Regex.TDFA.Text
|
||||||
|
|
||||||
-- NOTE keep statement and transfer readers separate because the former needs
|
-- NOTE keep statement and transfer readers separate because the former needs
|
||||||
-- the IO monad, and thus will throw IO errors rather than using the ExceptT
|
-- the IO monad, and thus will throw IO errors rather than using the ExceptT
|
||||||
|
@ -261,3 +264,241 @@ matchNonDates ms = go ([], [], initZipper ms)
|
||||||
MatchSkip -> (Nothing : matched, unmatched)
|
MatchSkip -> (Nothing : matched, unmatched)
|
||||||
MatchFail -> (matched, r : unmatched)
|
MatchFail -> (matched, r : unmatched)
|
||||||
in go (m, u, resetZipper z') rs
|
in go (m, u, resetZipper z') rs
|
||||||
|
|
||||||
|
matches :: MonadFinance m => MatchRe -> TxRecord -> InsertExceptT m (MatchRes (Tx ()))
|
||||||
|
matches
|
||||||
|
StatementParser {spTx, spOther, spVal, spDate, spDesc}
|
||||||
|
r@TxRecord {trDate, trAmount, trDesc, trOther} = do
|
||||||
|
res <- liftInner $
|
||||||
|
combineError3 val other desc $
|
||||||
|
\x y z -> x && y && z && date
|
||||||
|
if res
|
||||||
|
then maybe (return MatchSkip) convert spTx
|
||||||
|
else return MatchFail
|
||||||
|
where
|
||||||
|
val = valMatches spVal trAmount
|
||||||
|
date = maybe True (`dateMatches` trDate) spDate
|
||||||
|
other = foldM (\a o -> (a &&) <$> otherMatches trOther o) True spOther
|
||||||
|
desc = maybe (return True) (matchMaybe trDesc . snd) spDesc
|
||||||
|
convert tg = MatchPass <$> toTx tg r
|
||||||
|
|
||||||
|
toTx :: MonadFinance m => TxGetter -> TxRecord -> InsertExceptT m (Tx ())
|
||||||
|
toTx
|
||||||
|
TxGetter
|
||||||
|
{ tgFrom
|
||||||
|
, tgTo
|
||||||
|
, tgCurrency
|
||||||
|
, tgOtherEntries
|
||||||
|
, tgScale
|
||||||
|
}
|
||||||
|
r@TxRecord {trAmount, trDate, trDesc} = do
|
||||||
|
combineError curRes subRes $ \(cur, f, t) ss ->
|
||||||
|
Tx
|
||||||
|
{ txDate = trDate
|
||||||
|
, txDescr = trDesc
|
||||||
|
, txCommit = ()
|
||||||
|
, txPrimary =
|
||||||
|
Left $
|
||||||
|
EntrySet
|
||||||
|
{ esTotalValue = roundPrecisionCur cur $ tgScale * fromRational trAmount
|
||||||
|
, esCurrency = cur
|
||||||
|
, esFrom = f
|
||||||
|
, esTo = t
|
||||||
|
}
|
||||||
|
, txOther = fmap Left ss
|
||||||
|
}
|
||||||
|
where
|
||||||
|
curRes = do
|
||||||
|
m <- askDBState kmCurrency
|
||||||
|
cur <- liftInner $ resolveCurrency m r tgCurrency
|
||||||
|
let fromRes = liftInner $ resolveHalfEntry resolveFromValue cur r () tgFrom
|
||||||
|
let toRes = liftInner $ resolveHalfEntry resolveToValue cur r () tgTo
|
||||||
|
combineError fromRes toRes (cur,,)
|
||||||
|
subRes = mapErrors (resolveSubGetter r) tgOtherEntries
|
||||||
|
|
||||||
|
resolveSubGetter
|
||||||
|
:: MonadFinance m
|
||||||
|
=> TxRecord
|
||||||
|
-> TxSubGetter
|
||||||
|
-> InsertExceptT m SecondayEntrySet
|
||||||
|
resolveSubGetter r TxSubGetter {tsgFrom, tsgTo, tsgValue, tsgCurrency} = do
|
||||||
|
m <- askDBState kmCurrency
|
||||||
|
cur <- liftInner $ resolveCurrency m r tsgCurrency
|
||||||
|
let toRes = resolveHalfEntry resolveToValue cur r () tsgTo
|
||||||
|
let valRes = liftInner $ fmap (roundPrecisionCur cur) <$> resolveValue r tsgValue
|
||||||
|
liftInner $ combineErrorM toRes valRes $ \t v -> do
|
||||||
|
f <- resolveHalfEntry resolveFromValue cur r v tsgFrom
|
||||||
|
return $
|
||||||
|
EntrySet
|
||||||
|
{ esTotalValue = ()
|
||||||
|
, esCurrency = cur
|
||||||
|
, esFrom = f
|
||||||
|
, esTo = t
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveHalfEntry
|
||||||
|
:: Traversable f
|
||||||
|
=> (TxRecord -> n -> InsertExcept (f Double))
|
||||||
|
-> CurrencyPrec
|
||||||
|
-> TxRecord
|
||||||
|
-> v
|
||||||
|
-> TxHalfGetter (EntryGetter n)
|
||||||
|
-> InsertExcept (HalfEntrySet v (f Rational))
|
||||||
|
resolveHalfEntry f cur r v TxHalfGetter {thgAcnt, thgComment, thgTags, thgEntries} =
|
||||||
|
combineError acntRes esRes $ \a es ->
|
||||||
|
HalfEntrySet
|
||||||
|
{ hesPrimary =
|
||||||
|
Entry
|
||||||
|
{ eAcnt = a
|
||||||
|
, eValue = v
|
||||||
|
, eComment = thgComment
|
||||||
|
, eTags = thgTags
|
||||||
|
}
|
||||||
|
, hesOther = es
|
||||||
|
}
|
||||||
|
where
|
||||||
|
acntRes = resolveAcnt r thgAcnt
|
||||||
|
esRes = mapErrors (resolveEntry f cur r) thgEntries
|
||||||
|
|
||||||
|
otherMatches :: M.Map T.Text T.Text -> FieldMatcherRe -> InsertExcept Bool
|
||||||
|
otherMatches dict m = case m of
|
||||||
|
Val (Field n mv) -> valMatches mv =<< (readRational =<< lookup_ MatchNumeric n)
|
||||||
|
Desc (Field n (_, md)) -> (`matchMaybe` md) =<< lookup_ MatchText n
|
||||||
|
where
|
||||||
|
lookup_ t n = lookupErr (MatchField t) n dict
|
||||||
|
|
||||||
|
resolveEntry
|
||||||
|
:: Traversable f
|
||||||
|
=> (TxRecord -> n -> InsertExcept (f Double))
|
||||||
|
-> CurrencyPrec
|
||||||
|
-> TxRecord
|
||||||
|
-> EntryGetter n
|
||||||
|
-> InsertExcept (Entry AcntID (f Rational) TagID)
|
||||||
|
resolveEntry f cur r s@Entry {eAcnt, eValue} = do
|
||||||
|
combineError acntRes valRes $ \a v ->
|
||||||
|
s {eAcnt = a, eValue = roundPrecisionCur cur <$> v}
|
||||||
|
where
|
||||||
|
acntRes = resolveAcnt r eAcnt
|
||||||
|
valRes = f r eValue
|
||||||
|
|
||||||
|
resolveFromValue :: TxRecord -> EntryNumGetter -> InsertExcept (EntryValue Double)
|
||||||
|
resolveFromValue = resolveValue
|
||||||
|
|
||||||
|
resolveToValue :: TxRecord -> LinkedEntryNumGetter -> InsertExcept (LinkDeferred Double)
|
||||||
|
resolveToValue _ (Linked l) = return $ LinkIndex l
|
||||||
|
resolveToValue r (Getter g) = LinkDeferred <$> resolveValue r g
|
||||||
|
|
||||||
|
resolveValue :: TxRecord -> EntryNumGetter -> InsertExcept (EntryValue Double)
|
||||||
|
resolveValue TxRecord {trOther, trAmount} s = case s of
|
||||||
|
(LookupN t) -> EntryValue TFixed <$> (readDouble =<< lookupErr EntryValField t trOther)
|
||||||
|
(ConstN c) -> return $ EntryValue TFixed c
|
||||||
|
AmountN m -> return $ EntryValue TFixed $ m * fromRational trAmount
|
||||||
|
BalanceN x -> return $ EntryValue TBalance x
|
||||||
|
PercentN x -> return $ EntryValue TPercent x
|
||||||
|
|
||||||
|
resolveAcnt :: TxRecord -> EntryAcnt -> InsertExcept T.Text
|
||||||
|
resolveAcnt = resolveEntryField AcntField
|
||||||
|
|
||||||
|
resolveCurrency :: CurrencyMap -> TxRecord -> EntryCur -> InsertExcept CurrencyPrec
|
||||||
|
resolveCurrency m r c = do
|
||||||
|
i <- resolveEntryField CurField r c
|
||||||
|
case M.lookup i m of
|
||||||
|
Just k -> return k
|
||||||
|
-- TODO this should be its own error (I think)
|
||||||
|
Nothing -> throwError $ InsertException [LookupError (EntryIDField CurField) undefined]
|
||||||
|
|
||||||
|
resolveEntryField :: EntryIDType -> TxRecord -> EntryAcnt -> InsertExcept T.Text
|
||||||
|
resolveEntryField t TxRecord {trOther = o} s = case s of
|
||||||
|
ConstT p -> return p
|
||||||
|
LookupT f -> lookup_ f o
|
||||||
|
MapT (Field f m) -> do
|
||||||
|
k <- lookup_ f o
|
||||||
|
lookup_ k m
|
||||||
|
Map2T (Field (f1, f2) m) -> do
|
||||||
|
(k1, k2) <- combineError (lookup_ f1 o) (lookup_ f2 o) (,)
|
||||||
|
lookup_ (k1, k2) m
|
||||||
|
where
|
||||||
|
lookup_ :: (Ord k, Show k) => k -> M.Map k v -> InsertExcept v
|
||||||
|
lookup_ = lookupErr (EntryIDField t)
|
||||||
|
|
||||||
|
readDouble :: T.Text -> InsertExcept Double
|
||||||
|
readDouble s = case readMaybe $ T.unpack s of
|
||||||
|
Just x -> return x
|
||||||
|
Nothing -> throwError $ InsertException [ConversionError s]
|
||||||
|
|
||||||
|
readRational :: T.Text -> InsertExcept Rational
|
||||||
|
readRational s = case T.split (== '.') s of
|
||||||
|
[x] -> maybe err (return . fromInteger) $ readT x
|
||||||
|
[x, y] -> case (readT x, readT y) of
|
||||||
|
(Just x', Just y') ->
|
||||||
|
let p = 10 ^ T.length y
|
||||||
|
k = if x' >= 0 then 1 else -1
|
||||||
|
in return $ fromInteger x' + k * y' % p
|
||||||
|
_ -> err
|
||||||
|
_ -> err
|
||||||
|
where
|
||||||
|
readT = readMaybe . T.unpack
|
||||||
|
err = throwError $ InsertException [ConversionError s]
|
||||||
|
|
||||||
|
compileOptions :: TxOpts T.Text -> InsertExcept TxOptsRe
|
||||||
|
compileOptions o@TxOpts {toAmountFmt = pat} = do
|
||||||
|
re <- compileRegex True pat
|
||||||
|
return $ o {toAmountFmt = re}
|
||||||
|
|
||||||
|
compileMatch :: StatementParser T.Text -> InsertExcept MatchRe
|
||||||
|
compileMatch m@StatementParser {spDesc, spOther} = do
|
||||||
|
combineError dres ores $ \d os -> m {spDesc = d, spOther = os}
|
||||||
|
where
|
||||||
|
go = compileRegex False
|
||||||
|
dres = mapM go spDesc
|
||||||
|
ores = combineErrors $ fmap (mapM go) spOther
|
||||||
|
|
||||||
|
compileRegex :: Bool -> T.Text -> InsertExcept (Text, Regex)
|
||||||
|
compileRegex groups pat = case res of
|
||||||
|
Right re -> return (pat, re)
|
||||||
|
Left _ -> throwError $ InsertException [RegexError pat]
|
||||||
|
where
|
||||||
|
res =
|
||||||
|
compile
|
||||||
|
(blankCompOpt {newSyntax = True})
|
||||||
|
(blankExecOpt {captureGroups = groups})
|
||||||
|
pat
|
||||||
|
|
||||||
|
matchMaybe :: T.Text -> Regex -> InsertExcept Bool
|
||||||
|
matchMaybe q re = case execute re q of
|
||||||
|
Right res -> return $ isJust res
|
||||||
|
Left _ -> throwError $ InsertException [RegexError "this should not happen"]
|
||||||
|
|
||||||
|
matchGroupsMaybe :: T.Text -> Regex -> [T.Text]
|
||||||
|
matchGroupsMaybe q re = case regexec re q of
|
||||||
|
Right Nothing -> []
|
||||||
|
Right (Just (_, _, _, xs)) -> xs
|
||||||
|
-- this should never fail as regexec always returns Right
|
||||||
|
Left _ -> []
|
||||||
|
|
||||||
|
parseRational :: MonadFail m => (T.Text, Regex) -> T.Text -> m Rational
|
||||||
|
parseRational (pat, re) s = case matchGroupsMaybe s re of
|
||||||
|
[sign, x, ""] -> uncurry (*) <$> readWhole sign x
|
||||||
|
[sign, x, y] -> do
|
||||||
|
d <- readT "decimal" y
|
||||||
|
let p = 10 ^ T.length y
|
||||||
|
(k, w) <- readWhole sign x
|
||||||
|
return $ k * (w + d % p)
|
||||||
|
_ -> msg "malformed decimal"
|
||||||
|
where
|
||||||
|
readT what t = case readMaybe $ T.unpack t of
|
||||||
|
Just d -> return $ fromInteger d
|
||||||
|
_ -> msg $ T.unwords ["could not parse", what, singleQuote t]
|
||||||
|
msg :: MonadFail m => T.Text -> m a
|
||||||
|
msg m =
|
||||||
|
fail $
|
||||||
|
T.unpack $
|
||||||
|
T.unwords [m, "-", keyVals [("pattern", pat), ("query", s)]]
|
||||||
|
readSign x
|
||||||
|
| x == "-" = return (-1)
|
||||||
|
| x == "+" || x == "" = return 1
|
||||||
|
| otherwise = msg $ T.append "invalid sign: " x
|
||||||
|
readWhole sign x = do
|
||||||
|
w <- readT "whole number" x
|
||||||
|
k <- readSign sign
|
||||||
|
return (k, w)
|
||||||
|
|
|
@ -64,6 +64,7 @@ EntryR sql=entries
|
||||||
TagRelationR sql=tag_relations
|
TagRelationR sql=tag_relations
|
||||||
entry EntryRId OnDeleteCascade
|
entry EntryRId OnDeleteCascade
|
||||||
tag TagRId OnDeleteCascade
|
tag TagRId OnDeleteCascade
|
||||||
|
deriving Show Eq
|
||||||
BudgetLabelR sql=budget_labels
|
BudgetLabelR sql=budget_labels
|
||||||
entry EntryRId OnDeleteCascade
|
entry EntryRId OnDeleteCascade
|
||||||
budgetName T.Text
|
budgetName T.Text
|
||||||
|
|
|
@ -76,6 +76,7 @@ data ReadEntry = ReadEntry
|
||||||
, reValue :: !Rational
|
, reValue :: !Rational
|
||||||
, reDate :: !Day
|
, reDate :: !Day
|
||||||
}
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
data UpdateEntry i v = UpdateEntry
|
data UpdateEntry i v = UpdateEntry
|
||||||
{ ueID :: !i
|
{ ueID :: !i
|
||||||
|
@ -83,21 +84,22 @@ data UpdateEntry i v = UpdateEntry
|
||||||
, ueValue :: !v
|
, ueValue :: !v
|
||||||
, ueIndex :: !Int
|
, ueIndex :: !Int
|
||||||
}
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
data CurrencyRound = CurrencyRound CurID Natural
|
data CurrencyRound = CurrencyRound CurID Natural
|
||||||
|
|
||||||
deriving instance Functor (UpdateEntry i)
|
deriving instance Functor (UpdateEntry i)
|
||||||
|
|
||||||
newtype LinkScale = LinkScale {unLinkScale :: Rational}
|
newtype LinkScale = LinkScale {unLinkScale :: Rational}
|
||||||
deriving newtype (Num)
|
deriving newtype (Num, Show)
|
||||||
|
|
||||||
-- newtype BalanceTarget = BalanceTarget {unBalanceTarget :: Rational}
|
-- newtype BalanceTarget = BalanceTarget {unBalanceTarget :: Rational}
|
||||||
-- deriving newtype (Num)
|
-- deriving newtype (Num)
|
||||||
|
|
||||||
newtype StaticValue = StaticValue {unStaticValue :: Rational}
|
newtype StaticValue = StaticValue {unStaticValue :: Rational}
|
||||||
deriving newtype (Num)
|
deriving newtype (Num, Show)
|
||||||
|
|
||||||
data EntryValueUnk = EVBalance Rational | EVPercent Rational
|
data EntryValueUnk = EVBalance Rational | EVPercent Rational deriving (Show)
|
||||||
|
|
||||||
type UEUnk = UpdateEntry EntryRId EntryValueUnk
|
type UEUnk = UpdateEntry EntryRId EntryValueUnk
|
||||||
|
|
||||||
|
@ -120,6 +122,7 @@ data UpdateEntrySet f t = UpdateEntrySet
|
||||||
, utDate :: !Day
|
, utDate :: !Day
|
||||||
, utTotalValue :: !t
|
, utTotalValue :: !t
|
||||||
}
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
type TotalUpdateEntrySet = UpdateEntrySet (UEBlank, [UELink]) Rational
|
type TotalUpdateEntrySet = UpdateEntrySet (UEBlank, [UELink]) Rational
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ module Internal.Utils
|
||||||
, fromWeekday
|
, fromWeekday
|
||||||
, inDaySpan
|
, inDaySpan
|
||||||
, fmtRational
|
, fmtRational
|
||||||
, matches
|
|
||||||
, fromGregorian'
|
, fromGregorian'
|
||||||
, resolveDaySpan
|
, resolveDaySpan
|
||||||
, resolveDaySpan_
|
, resolveDaySpan_
|
||||||
|
@ -28,12 +27,7 @@ module Internal.Utils
|
||||||
, combineErrorIOM3
|
, combineErrorIOM3
|
||||||
, collectErrorsIO
|
, collectErrorsIO
|
||||||
, mapErrorsIO
|
, mapErrorsIO
|
||||||
, parseRational
|
|
||||||
, showError
|
, showError
|
||||||
, unlessLeft_
|
|
||||||
, unlessLefts_
|
|
||||||
, unlessLeft
|
|
||||||
, unlessLefts
|
|
||||||
, acntPath2Text
|
, acntPath2Text
|
||||||
, showT
|
, showT
|
||||||
, lookupErr
|
, lookupErr
|
||||||
|
@ -43,8 +37,6 @@ module Internal.Utils
|
||||||
, sndOf3
|
, sndOf3
|
||||||
, thdOf3
|
, thdOf3
|
||||||
, xGregToDay
|
, xGregToDay
|
||||||
, compileMatch
|
|
||||||
, compileOptions
|
|
||||||
, dateMatches
|
, dateMatches
|
||||||
, valMatches
|
, valMatches
|
||||||
, roundPrecision
|
, roundPrecision
|
||||||
|
@ -64,6 +56,8 @@ module Internal.Utils
|
||||||
, expandTransfers
|
, expandTransfers
|
||||||
, expandTransfer
|
, expandTransfer
|
||||||
, entryPair
|
, entryPair
|
||||||
|
, singleQuote
|
||||||
|
, keyVals
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
@ -80,8 +74,6 @@ import RIO.State
|
||||||
import qualified RIO.Text as T
|
import qualified RIO.Text as T
|
||||||
import RIO.Time
|
import RIO.Time
|
||||||
import qualified RIO.Vector as V
|
import qualified RIO.Vector as V
|
||||||
import Text.Regex.TDFA
|
|
||||||
import Text.Regex.TDFA.Text
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- intervals
|
-- intervals
|
||||||
|
@ -300,101 +292,6 @@ toDaySpan (a, b) = (a, fromIntegral $ diffDays b a - 1)
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- matching
|
-- matching
|
||||||
|
|
||||||
matches :: MonadFinance m => MatchRe -> TxRecord -> InsertExceptT m (MatchRes (Tx ()))
|
|
||||||
matches
|
|
||||||
StatementParser {spTx, spOther, spVal, spDate, spDesc}
|
|
||||||
r@TxRecord {trDate, trAmount, trDesc, trOther} = do
|
|
||||||
res <- liftInner $
|
|
||||||
combineError3 val other desc $
|
|
||||||
\x y z -> x && y && z && date
|
|
||||||
if res
|
|
||||||
then maybe (return MatchSkip) convert spTx
|
|
||||||
else return MatchFail
|
|
||||||
where
|
|
||||||
val = valMatches spVal trAmount
|
|
||||||
date = maybe True (`dateMatches` trDate) spDate
|
|
||||||
other = foldM (\a o -> (a &&) <$> otherMatches trOther o) True spOther
|
|
||||||
desc = maybe (return True) (matchMaybe trDesc . snd) spDesc
|
|
||||||
convert tg = MatchPass <$> toTx tg r
|
|
||||||
|
|
||||||
toTx :: MonadFinance m => TxGetter -> TxRecord -> InsertExceptT m (Tx ())
|
|
||||||
toTx
|
|
||||||
TxGetter
|
|
||||||
{ tgFrom
|
|
||||||
, tgTo
|
|
||||||
, tgCurrency
|
|
||||||
, tgOtherEntries
|
|
||||||
, tgScale
|
|
||||||
}
|
|
||||||
r@TxRecord {trAmount, trDate, trDesc} = do
|
|
||||||
combineError curRes subRes $ \(cur, f, t) ss ->
|
|
||||||
Tx
|
|
||||||
{ txDate = trDate
|
|
||||||
, txDescr = trDesc
|
|
||||||
, txCommit = ()
|
|
||||||
, txPrimary =
|
|
||||||
Left $
|
|
||||||
EntrySet
|
|
||||||
{ esTotalValue = roundPrecisionCur cur $ tgScale * fromRational trAmount
|
|
||||||
, esCurrency = cur
|
|
||||||
, esFrom = f
|
|
||||||
, esTo = t
|
|
||||||
}
|
|
||||||
, txOther = fmap Left ss
|
|
||||||
}
|
|
||||||
where
|
|
||||||
curRes = do
|
|
||||||
m <- askDBState kmCurrency
|
|
||||||
cur <- liftInner $ resolveCurrency m r tgCurrency
|
|
||||||
let fromRes = liftInner $ resolveHalfEntry resolveFromValue cur r () tgFrom
|
|
||||||
let toRes = liftInner $ resolveHalfEntry resolveToValue cur r () tgTo
|
|
||||||
combineError fromRes toRes (cur,,)
|
|
||||||
subRes = mapErrors (resolveSubGetter r) tgOtherEntries
|
|
||||||
|
|
||||||
resolveSubGetter
|
|
||||||
:: MonadFinance m
|
|
||||||
=> TxRecord
|
|
||||||
-> TxSubGetter
|
|
||||||
-> InsertExceptT m SecondayEntrySet
|
|
||||||
resolveSubGetter r TxSubGetter {tsgFrom, tsgTo, tsgValue, tsgCurrency} = do
|
|
||||||
m <- askDBState kmCurrency
|
|
||||||
cur <- liftInner $ resolveCurrency m r tsgCurrency
|
|
||||||
let toRes = resolveHalfEntry resolveToValue cur r () tsgTo
|
|
||||||
let valRes = liftInner $ fmap (roundPrecisionCur cur) <$> resolveValue r tsgValue
|
|
||||||
liftInner $ combineErrorM toRes valRes $ \t v -> do
|
|
||||||
f <- resolveHalfEntry resolveFromValue cur r v tsgFrom
|
|
||||||
return $
|
|
||||||
EntrySet
|
|
||||||
{ esTotalValue = ()
|
|
||||||
, esCurrency = cur
|
|
||||||
, esFrom = f
|
|
||||||
, esTo = t
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveHalfEntry
|
|
||||||
:: Traversable f
|
|
||||||
=> (TxRecord -> n -> InsertExcept (f Double))
|
|
||||||
-> CurrencyPrec
|
|
||||||
-> TxRecord
|
|
||||||
-> v
|
|
||||||
-> TxHalfGetter (EntryGetter n)
|
|
||||||
-> InsertExcept (HalfEntrySet v (f Rational))
|
|
||||||
resolveHalfEntry f cur r v TxHalfGetter {thgAcnt, thgComment, thgTags, thgEntries} =
|
|
||||||
combineError acntRes esRes $ \a es ->
|
|
||||||
HalfEntrySet
|
|
||||||
{ hesPrimary =
|
|
||||||
Entry
|
|
||||||
{ eAcnt = a
|
|
||||||
, eValue = v
|
|
||||||
, eComment = thgComment
|
|
||||||
, eTags = thgTags
|
|
||||||
}
|
|
||||||
, hesOther = es
|
|
||||||
}
|
|
||||||
where
|
|
||||||
acntRes = resolveAcnt r thgAcnt
|
|
||||||
esRes = mapErrors (resolveEntry f cur r) thgEntries
|
|
||||||
|
|
||||||
valMatches :: ValMatcher -> Rational -> InsertExcept Bool
|
valMatches :: ValMatcher -> Rational -> InsertExcept Bool
|
||||||
valMatches ValMatcher {vmDen, vmSign, vmNum, vmPrec} x
|
valMatches ValMatcher {vmDen, vmSign, vmNum, vmPrec} x
|
||||||
| Just d_ <- vmDen, d_ >= p = throwError $ InsertException [MatchValPrecisionError d_ p]
|
| Just d_ <- vmDen, d_ >= p = throwError $ InsertException [MatchValPrecisionError d_ p]
|
||||||
|
@ -412,27 +309,6 @@ valMatches ValMatcher {vmDen, vmSign, vmNum, vmPrec} x
|
||||||
dateMatches :: DateMatcher -> Day -> Bool
|
dateMatches :: DateMatcher -> Day -> Bool
|
||||||
dateMatches md = (EQ ==) . compareDate md
|
dateMatches md = (EQ ==) . compareDate md
|
||||||
|
|
||||||
otherMatches :: M.Map T.Text T.Text -> FieldMatcherRe -> InsertExcept Bool
|
|
||||||
otherMatches dict m = case m of
|
|
||||||
Val (Field n mv) -> valMatches mv =<< (readRational =<< lookup_ MatchNumeric n)
|
|
||||||
Desc (Field n (_, md)) -> (`matchMaybe` md) =<< lookup_ MatchText n
|
|
||||||
where
|
|
||||||
lookup_ t n = lookupErr (MatchField t) n dict
|
|
||||||
|
|
||||||
resolveEntry
|
|
||||||
:: Traversable f
|
|
||||||
=> (TxRecord -> n -> InsertExcept (f Double))
|
|
||||||
-> CurrencyPrec
|
|
||||||
-> TxRecord
|
|
||||||
-> EntryGetter n
|
|
||||||
-> InsertExcept (Entry AcntID (f Rational) TagID)
|
|
||||||
resolveEntry f cur r s@Entry {eAcnt, eValue} = do
|
|
||||||
combineError acntRes valRes $ \a v ->
|
|
||||||
s {eAcnt = a, eValue = roundPrecisionCur cur <$> v}
|
|
||||||
where
|
|
||||||
acntRes = resolveAcnt r eAcnt
|
|
||||||
valRes = f r eValue
|
|
||||||
|
|
||||||
liftInner :: Monad m => ExceptT e Identity a -> ExceptT e m a
|
liftInner :: Monad m => ExceptT e Identity a -> ExceptT e m a
|
||||||
liftInner = mapExceptT (return . runIdentity)
|
liftInner = mapExceptT (return . runIdentity)
|
||||||
|
|
||||||
|
@ -442,9 +318,6 @@ liftExceptT x = runExceptT x >>= either throwError return
|
||||||
liftExcept :: MonadError e m => Except e a -> m a
|
liftExcept :: MonadError e m => Except e a -> m a
|
||||||
liftExcept = either throwError return . runExcept
|
liftExcept = either throwError return . runExcept
|
||||||
|
|
||||||
-- tryError :: MonadError e m => m a -> m (Either e a)
|
|
||||||
-- tryError action = (Right <$> action) `catchError` (pure . Left)
|
|
||||||
|
|
||||||
liftIOExceptT :: MonadIO m => InsertExceptT m a -> m a
|
liftIOExceptT :: MonadIO m => InsertExceptT m a -> m a
|
||||||
liftIOExceptT = fromEither <=< runExceptT
|
liftIOExceptT = fromEither <=< runExceptT
|
||||||
|
|
||||||
|
@ -526,101 +399,11 @@ mapErrorsIO f xs = mapM go $ enumTraversable xs
|
||||||
collectErrorsIO :: (Traversable t, MonadUnliftIO m) => t (m a) -> m (t a)
|
collectErrorsIO :: (Traversable t, MonadUnliftIO m) => t (m a) -> m (t a)
|
||||||
collectErrorsIO = mapErrorsIO id
|
collectErrorsIO = mapErrorsIO id
|
||||||
|
|
||||||
resolveFromValue :: TxRecord -> EntryNumGetter -> InsertExcept (EntryValue Double)
|
|
||||||
resolveFromValue = resolveValue
|
|
||||||
|
|
||||||
resolveToValue :: TxRecord -> LinkedEntryNumGetter -> InsertExcept (LinkDeferred Double)
|
|
||||||
resolveToValue _ (Linked l) = return $ LinkIndex l
|
|
||||||
resolveToValue r (Getter g) = LinkDeferred <$> resolveValue r g
|
|
||||||
|
|
||||||
resolveValue :: TxRecord -> EntryNumGetter -> InsertExcept (EntryValue Double)
|
|
||||||
resolveValue TxRecord {trOther, trAmount} s = case s of
|
|
||||||
(LookupN t) -> EntryValue TFixed <$> (readDouble =<< lookupErr EntryValField t trOther)
|
|
||||||
(ConstN c) -> return $ EntryValue TFixed c
|
|
||||||
AmountN m -> return $ EntryValue TFixed $ m * fromRational trAmount
|
|
||||||
BalanceN x -> return $ EntryValue TBalance x
|
|
||||||
PercentN x -> return $ EntryValue TPercent x
|
|
||||||
|
|
||||||
resolveAcnt :: TxRecord -> EntryAcnt -> InsertExcept T.Text
|
|
||||||
resolveAcnt = resolveEntryField AcntField
|
|
||||||
|
|
||||||
resolveCurrency :: CurrencyMap -> TxRecord -> EntryCur -> InsertExcept CurrencyPrec
|
|
||||||
resolveCurrency m r c = do
|
|
||||||
i <- resolveEntryField CurField r c
|
|
||||||
case M.lookup i m of
|
|
||||||
Just k -> return k
|
|
||||||
-- TODO this should be its own error (I think)
|
|
||||||
Nothing -> throwError $ InsertException [LookupError (EntryIDField CurField) undefined]
|
|
||||||
|
|
||||||
resolveEntryField :: EntryIDType -> TxRecord -> EntryAcnt -> InsertExcept T.Text
|
|
||||||
resolveEntryField t TxRecord {trOther = o} s = case s of
|
|
||||||
ConstT p -> return p
|
|
||||||
LookupT f -> lookup_ f o
|
|
||||||
MapT (Field f m) -> do
|
|
||||||
k <- lookup_ f o
|
|
||||||
lookup_ k m
|
|
||||||
Map2T (Field (f1, f2) m) -> do
|
|
||||||
(k1, k2) <- combineError (lookup_ f1 o) (lookup_ f2 o) (,)
|
|
||||||
lookup_ (k1, k2) m
|
|
||||||
where
|
|
||||||
lookup_ :: (Ord k, Show k) => k -> M.Map k v -> InsertExcept v
|
|
||||||
lookup_ = lookupErr (EntryIDField t)
|
|
||||||
|
|
||||||
lookupErr :: (Ord k, Show k) => LookupSuberr -> k -> M.Map k v -> InsertExcept v
|
lookupErr :: (Ord k, Show k) => LookupSuberr -> k -> M.Map k v -> InsertExcept v
|
||||||
lookupErr what k m = case M.lookup k m of
|
lookupErr what k m = case M.lookup k m of
|
||||||
Just x -> return x
|
Just x -> return x
|
||||||
_ -> throwError $ InsertException [LookupError what $ showT k]
|
_ -> throwError $ InsertException [LookupError what $ showT k]
|
||||||
|
|
||||||
parseRational :: MonadFail m => (T.Text, Regex) -> T.Text -> m Rational
|
|
||||||
parseRational (pat, re) s = case matchGroupsMaybe s re of
|
|
||||||
[sign, x, ""] -> uncurry (*) <$> readWhole sign x
|
|
||||||
[sign, x, y] -> do
|
|
||||||
d <- readT "decimal" y
|
|
||||||
let p = 10 ^ T.length y
|
|
||||||
(k, w) <- readWhole sign x
|
|
||||||
return $ k * (w + d % p)
|
|
||||||
_ -> msg "malformed decimal"
|
|
||||||
where
|
|
||||||
readT what t = case readMaybe $ T.unpack t of
|
|
||||||
Just d -> return $ fromInteger d
|
|
||||||
_ -> msg $ T.unwords ["could not parse", what, singleQuote t]
|
|
||||||
msg :: MonadFail m => T.Text -> m a
|
|
||||||
msg m =
|
|
||||||
fail $
|
|
||||||
T.unpack $
|
|
||||||
T.unwords [m, "-", keyVals [("pattern", pat), ("query", s)]]
|
|
||||||
readSign x
|
|
||||||
| x == "-" = return (-1)
|
|
||||||
| x == "+" || x == "" = return 1
|
|
||||||
| otherwise = msg $ T.append "invalid sign: " x
|
|
||||||
readWhole sign x = do
|
|
||||||
w <- readT "whole number" x
|
|
||||||
k <- readSign sign
|
|
||||||
return (k, w)
|
|
||||||
|
|
||||||
readDouble :: T.Text -> InsertExcept Double
|
|
||||||
readDouble s = case readMaybe $ T.unpack s of
|
|
||||||
Just x -> return x
|
|
||||||
Nothing -> throwError $ InsertException [ConversionError s]
|
|
||||||
|
|
||||||
readRational :: T.Text -> InsertExcept Rational
|
|
||||||
readRational s = case T.split (== '.') s of
|
|
||||||
[x] -> maybe err (return . fromInteger) $ readT x
|
|
||||||
[x, y] -> case (readT x, readT y) of
|
|
||||||
(Just x', Just y') ->
|
|
||||||
let p = 10 ^ T.length y
|
|
||||||
k = if x' >= 0 then 1 else -1
|
|
||||||
in return $ fromInteger x' + k * y' % p
|
|
||||||
_ -> err
|
|
||||||
_ -> err
|
|
||||||
where
|
|
||||||
readT = readMaybe . T.unpack
|
|
||||||
err = throwError $ InsertException [ConversionError s]
|
|
||||||
|
|
||||||
-- TODO smells like a lens
|
|
||||||
-- mapTxSplits :: (a -> b) -> Tx a -> Tx b
|
|
||||||
-- mapTxSplits f t@Tx {txSplits = ss} = t {txSplits = fmap f ss}
|
|
||||||
|
|
||||||
fmtRational :: Natural -> Rational -> T.Text
|
fmtRational :: Natural -> Rational -> T.Text
|
||||||
fmtRational precision x = T.concat [s, txt n', ".", pad 2 "0" $ txt d']
|
fmtRational precision x = T.concat [s, txt n', ".", pad 2 "0" $ txt d']
|
||||||
where
|
where
|
||||||
|
@ -834,87 +617,9 @@ keyVals = T.intercalate "; " . fmap (uncurry keyVal)
|
||||||
showT :: Show a => a -> T.Text
|
showT :: Show a => a -> T.Text
|
||||||
showT = T.pack . show
|
showT = T.pack . show
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- pure error processing
|
|
||||||
|
|
||||||
-- concatEither2 :: Either x a -> Either x b -> (a -> b -> c) -> Either [x] c
|
|
||||||
-- concatEither2 a b fun = case (a, b) of
|
|
||||||
-- (Right a_, Right b_) -> Right $ fun a_ b_
|
|
||||||
-- _ -> Left $ catMaybes [leftToMaybe a, leftToMaybe b]
|
|
||||||
|
|
||||||
-- concatEither2M :: Monad m => Either x a -> Either x b -> (a -> b -> m c) -> m (Either [x] c)
|
|
||||||
-- concatEither2M a b fun = case (a, b) of
|
|
||||||
-- (Right a_, Right b_) -> Right <$> fun a_ b_
|
|
||||||
-- _ -> return $ Left $ catMaybes [leftToMaybe a, leftToMaybe b]
|
|
||||||
|
|
||||||
-- concatEither3 :: Either x a -> Either x b -> Either x c -> (a -> b -> c -> d) -> Either [x] d
|
|
||||||
-- concatEither3 a b c fun = case (a, b, c) of
|
|
||||||
-- (Right a_, Right b_, Right c_) -> Right $ fun a_ b_ c_
|
|
||||||
-- _ -> Left $ catMaybes [leftToMaybe a, leftToMaybe b, leftToMaybe c]
|
|
||||||
|
|
||||||
-- concatEithers2 :: Either [x] a -> Either [x] b -> (a -> b -> c) -> Either [x] c
|
|
||||||
-- concatEithers2 a b = merge . concatEither2 a b
|
|
||||||
|
|
||||||
-- concatEithers2M
|
|
||||||
-- :: Monad m
|
|
||||||
-- => Either [x] a
|
|
||||||
-- -> Either [x] b
|
|
||||||
-- -> (a -> b -> m c)
|
|
||||||
-- -> m (Either [x] c)
|
|
||||||
-- concatEithers2M a b = fmap merge . concatEither2M a b
|
|
||||||
|
|
||||||
-- concatEithers3
|
|
||||||
-- :: Either [x] a
|
|
||||||
-- -> Either [x] b
|
|
||||||
-- -> Either [x] c
|
|
||||||
-- -> (a -> b -> c -> d)
|
|
||||||
-- -> Either [x] d
|
|
||||||
-- concatEithers3 a b c = merge . concatEither3 a b c
|
|
||||||
|
|
||||||
-- concatEitherL :: [Either x a] -> Either [x] [a]
|
|
||||||
-- concatEitherL as = case partitionEithers as of
|
|
||||||
-- ([], bs) -> Right bs
|
|
||||||
-- (es, _) -> Left es
|
|
||||||
|
|
||||||
-- concatEithersL :: [Either [x] a] -> Either [x] [a]
|
|
||||||
-- concatEithersL = merge . concatEitherL
|
|
||||||
|
|
||||||
-- leftToMaybe :: Either a b -> Maybe a
|
|
||||||
-- leftToMaybe (Left a) = Just a
|
|
||||||
-- leftToMaybe _ = Nothing
|
|
||||||
|
|
||||||
unlessLeft :: (Monad m, MonadPlus n) => Either a b -> (b -> m (n a)) -> m (n a)
|
|
||||||
unlessLeft (Left es) _ = return (return es)
|
|
||||||
unlessLeft (Right rs) f = f rs
|
|
||||||
|
|
||||||
unlessLefts :: (Monad m) => Either (n a) b -> (b -> m (n a)) -> m (n a)
|
|
||||||
unlessLefts (Left es) _ = return es
|
|
||||||
unlessLefts (Right rs) f = f rs
|
|
||||||
|
|
||||||
unlessLeft_ :: (Monad m, MonadPlus n) => Either a b -> (b -> m ()) -> m (n a)
|
|
||||||
unlessLeft_ e f = unlessLeft e (\x -> void (f x) >> return mzero)
|
|
||||||
|
|
||||||
unlessLefts_ :: (Monad m, MonadPlus n) => Either (n a) b -> (b -> m ()) -> m (n a)
|
|
||||||
unlessLefts_ e f = unlessLefts e (\x -> void (f x) >> return mzero)
|
|
||||||
|
|
||||||
-- plural :: Either a b -> Either [a] b
|
|
||||||
-- plural = first (: [])
|
|
||||||
|
|
||||||
-- merge :: Either [[a]] b -> Either [a] b
|
|
||||||
-- merge = first concat
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- random functions
|
-- random functions
|
||||||
|
|
||||||
-- when bifunctor fails...
|
|
||||||
-- thrice :: (a -> d) -> (b -> e) -> (c -> f) -> (a, b, c) -> (d, e, f)
|
|
||||||
-- thrice f1 f2 f3 (x, y, z) = (f1 x, f2 y, f3 z)
|
|
||||||
|
|
||||||
-- groupKey :: Ord c => (a -> c) -> [(a, b)] -> [(a, [b])]
|
|
||||||
-- groupKey f = fmap go . NE.groupAllWith (f . fst)
|
|
||||||
-- where
|
|
||||||
-- go xs@((c, _) :| _) = (c, NE.toList $ fmap snd xs)
|
|
||||||
|
|
||||||
groupKey :: Ord c => (a -> c) -> [(a, b)] -> [(a, NonEmpty b)]
|
groupKey :: Ord c => (a -> c) -> [(a, b)] -> [(a, NonEmpty b)]
|
||||||
groupKey f = fmap go . NE.groupAllWith (f . fst)
|
groupKey f = fmap go . NE.groupAllWith (f . fst)
|
||||||
where
|
where
|
||||||
|
@ -940,65 +645,6 @@ sndOf3 (_, b, _) = b
|
||||||
thdOf3 :: (a, b, c) -> c
|
thdOf3 :: (a, b, c) -> c
|
||||||
thdOf3 (_, _, c) = c
|
thdOf3 (_, _, c) = c
|
||||||
|
|
||||||
-- lpad :: a -> Int -> [a] -> [a]
|
|
||||||
-- lpad c n s = replicate (n - length s) c ++ s
|
|
||||||
|
|
||||||
-- rpad :: a -> Int -> [a] -> [a]
|
|
||||||
-- rpad c n s = s ++ replicate (n - length s) c
|
|
||||||
|
|
||||||
-- lpadT :: Char -> Int -> T.Text -> T.Text
|
|
||||||
-- lpadT c n s = T.append (T.replicate (n - T.length s) (T.singleton c)) s
|
|
||||||
|
|
||||||
-- TODO this regular expression appears to be compiled each time, which is
|
|
||||||
-- super slow
|
|
||||||
-- NOTE: see https://github.com/haskell-hvr/regex-tdfa/issues/9 - performance
|
|
||||||
-- is likely not going to be optimal for text
|
|
||||||
-- matchMaybe :: T.Text -> T.Text -> EitherErr Bool
|
|
||||||
-- matchMaybe q pat = case compres of
|
|
||||||
-- Right re -> case execute re q of
|
|
||||||
-- Right res -> Right $ isJust res
|
|
||||||
-- Left _ -> Left $ RegexError "this should not happen"
|
|
||||||
-- Left _ -> Left $ RegexError pat
|
|
||||||
-- where
|
|
||||||
-- -- these options barely do anything in terms of performance
|
|
||||||
-- compres = compile (blankCompOpt {newSyntax = True}) (blankExecOpt {captureGroups = False}) pat
|
|
||||||
|
|
||||||
compileOptions :: TxOpts T.Text -> InsertExcept TxOptsRe
|
|
||||||
compileOptions o@TxOpts {toAmountFmt = pat} = do
|
|
||||||
re <- compileRegex True pat
|
|
||||||
return $ o {toAmountFmt = re}
|
|
||||||
|
|
||||||
compileMatch :: StatementParser T.Text -> InsertExcept MatchRe
|
|
||||||
compileMatch m@StatementParser {spDesc, spOther} = do
|
|
||||||
combineError dres ores $ \d os -> m {spDesc = d, spOther = os}
|
|
||||||
where
|
|
||||||
go = compileRegex False
|
|
||||||
dres = mapM go spDesc
|
|
||||||
ores = combineErrors $ fmap (mapM go) spOther
|
|
||||||
|
|
||||||
compileRegex :: Bool -> T.Text -> InsertExcept (Text, Regex)
|
|
||||||
compileRegex groups pat = case res of
|
|
||||||
Right re -> return (pat, re)
|
|
||||||
Left _ -> throwError $ InsertException [RegexError pat]
|
|
||||||
where
|
|
||||||
res =
|
|
||||||
compile
|
|
||||||
(blankCompOpt {newSyntax = True})
|
|
||||||
(blankExecOpt {captureGroups = groups})
|
|
||||||
pat
|
|
||||||
|
|
||||||
matchMaybe :: T.Text -> Regex -> InsertExcept Bool
|
|
||||||
matchMaybe q re = case execute re q of
|
|
||||||
Right res -> return $ isJust res
|
|
||||||
Left _ -> throwError $ InsertException [RegexError "this should not happen"]
|
|
||||||
|
|
||||||
matchGroupsMaybe :: T.Text -> Regex -> [T.Text]
|
|
||||||
matchGroupsMaybe q re = case regexec re q of
|
|
||||||
Right Nothing -> []
|
|
||||||
Right (Just (_, _, _, xs)) -> xs
|
|
||||||
-- this should never fail as regexec always returns Right
|
|
||||||
Left _ -> []
|
|
||||||
|
|
||||||
lookupAccount :: (MonadInsertError m, MonadFinance m) => AcntID -> m (AccountRId, AcntSign, AcntType)
|
lookupAccount :: (MonadInsertError m, MonadFinance m) => AcntID -> m (AccountRId, AcntSign, AcntType)
|
||||||
lookupAccount = lookupFinance AcntField kmAccount
|
lookupAccount = lookupFinance AcntField kmAccount
|
||||||
|
|
||||||
|
@ -1339,7 +985,7 @@ balanceLinked from curID precision acntID lg = case lg of
|
||||||
Nothing -> throwError undefined
|
Nothing -> throwError undefined
|
||||||
(LinkDeferred d) -> liftInnerS $ balanceDeferred curID acntID d
|
(LinkDeferred d) -> liftInnerS $ balanceDeferred curID acntID d
|
||||||
where
|
where
|
||||||
go s = roundPrecision precision . (* s) . fromRational
|
go s = negate . roundPrecision precision . (* s) . fromRational
|
||||||
|
|
||||||
balanceDeferred
|
balanceDeferred
|
||||||
:: CurrencyRId
|
:: CurrencyRId
|
||||||
|
|
Loading…
Reference in New Issue