ENH allow multiple budgets

This commit is contained in:
Nathan Dwarshuis 2023-02-05 18:45:56 -05:00
parent b9a389454f
commit 1e5f40d730
6 changed files with 54 additions and 30 deletions

View File

@ -163,7 +163,7 @@ runSync c = do
Left es -> throwIO $ InsertException es Left es -> throwIO $ InsertException es
Right s -> do Right s -> do
flip runReaderT (s $ takeDirectory c) $ do flip runReaderT (s $ takeDirectory c) $ do
es1 <- insertBudget $ budget config es1 <- concat <$> mapM insertBudget (budget config)
es2 <- insertStatements config es2 <- insertStatements config
let es = es1 ++ es2 let es = es1 ++ es2
unless (null es) $ throwIO $ InsertException es unless (null es) $ throwIO $ InsertException es

View File

@ -210,7 +210,8 @@ let Transfer =
, transCurrency : CurID , transCurrency : CurID
} }
let Budget = { name : Text, income : List Income, transfers : List Transfer } let Budget =
{ budgetLabel : Text, income : List Income, transfers : List Transfer }
in { CurID in { CurID
, AcntID , AcntID

View File

@ -4,7 +4,7 @@ let List/map =
let T = let T =
./Types.dhall ./Types.dhall
sha256:b29c40e8680e3bd2992180b1165a42f6cde1198cdc7efa4fd115e9e25eef50ea sha256:27c65b31fe1b0dd58ff03ec0a37648f586914c81b81e5f33d3eac5a465475f2b
let nullSplit = let nullSplit =
\(a : T.SplitAcnt) -> \(a : T.SplitAcnt) ->

View File

@ -102,14 +102,16 @@ nukeTables = do
hashConfig :: Config -> [Int] hashConfig :: Config -> [Int]
hashConfig hashConfig
Config_ Config_
{ budget = Budget {transfers = xs, income = is} { budget = bs
, statements = ss , statements = ss
} = } =
(hash <$> xs) ++ (hash <$> is) ++ (hash <$> ms) ++ (hash <$> ps) concatMap budgetHashes bs ++ (hash <$> ms) ++ (hash <$> ps)
where where
(ms, ps) = partitionEithers $ fmap go ss (ms, ps) = partitionEithers $ fmap go ss
go (StmtManual x) = Left x go (StmtManual x) = Left x
go (StmtImport x) = Right x go (StmtImport x) = Right x
budgetHashes Budget {transfers = xs, income = is} =
(hash <$> xs) ++ (hash <$> is)
setDiff :: Eq a => [a] -> [a] -> ([a], [a]) setDiff :: Eq a => [a] -> [a] -> ([a], [a])
-- setDiff = setDiff' (==) -- setDiff = setDiff' (==)

View File

@ -103,9 +103,9 @@ withDates dp f = do
-- budget -- budget
insertBudget :: MonadUnliftIO m => Budget -> MappingT m [InsertError] insertBudget :: MonadUnliftIO m => Budget -> MappingT m [InsertError]
insertBudget Budget {income = is, transfers = es} = do insertBudget Budget {budgetLabel = name, income = is, transfers = es} = do
es1 <- mapM insertIncome is es1 <- mapM (insertIncome name) is
es2 <- mapM insertTransfer es es2 <- mapM (insertTransfer name) es
return $ concat $ es1 ++ es2 return $ concat $ es1 ++ es2
-- TODO this hashes twice (not that it really matters) -- TODO this hashes twice (not that it really matters)
@ -131,6 +131,7 @@ data BudgetMeta = BudgetMeta
{ bmCommit :: !(Key CommitR) { bmCommit :: !(Key CommitR)
, bmWhen :: !Day , bmWhen :: !Day
, bmCur :: !CurID , bmCur :: !CurID
, bmName :: !T.Text
} }
data BudgetTx = BudgetTx data BudgetTx = BudgetTx
@ -138,15 +139,15 @@ data BudgetTx = BudgetTx
, btFrom :: !(BudgetSplit IncomeBucket) , btFrom :: !(BudgetSplit IncomeBucket)
, btTo :: !(BudgetSplit ExpenseBucket) , btTo :: !(BudgetSplit ExpenseBucket)
, btValue :: !Rational , btValue :: !Rational
, btName :: !T.Text , btDesc :: !T.Text
} }
insertIncome :: MonadUnliftIO m => Income -> MappingT m [InsertError] insertIncome :: MonadUnliftIO m => T.Text -> Income -> MappingT m [InsertError]
insertIncome i@Income {..} = insertIncome name i@Income {..} =
whenHash CTIncome i [] $ \c -> whenHash CTIncome i [] $ \c ->
unlessLeft (balanceIncome i) $ \balance -> do unlessLeft (balanceIncome i) $ \balance -> do
res <- withDates incWhen $ \day -> do res <- withDates incWhen $ \day -> do
let meta = BudgetMeta c day incCurrency let meta = BudgetMeta c day incCurrency name
let fromAllos b = concatMap (fromAllo meta incFrom (Just b)) let fromAllos b = concatMap (fromAllo meta incFrom (Just b))
let pre = fromAllos PreTax incPretax let pre = fromAllos PreTax incPretax
let tax = fmap (fromTax meta incFrom) incTaxes let tax = fmap (fromTax meta incFrom) incTaxes
@ -157,7 +158,7 @@ insertIncome i@Income {..} =
, btFrom = BudgetSplit incFrom $ Just PostTax , btFrom = BudgetSplit incFrom $ Just PostTax
, btTo = BudgetSplit incToBal Nothing , btTo = BudgetSplit incToBal Nothing
, btValue = balance , btValue = balance
, btName = "balance after deductions" , btDesc = "balance after deductions"
} }
fmap concat $ mapM insertBudgetTx $ bal : (pre ++ tax ++ post) fmap concat $ mapM insertBudgetTx $ bal : (pre ++ tax ++ post)
unlessLefts res $ return . concat unlessLefts res $ return . concat
@ -175,7 +176,7 @@ fromAllo meta from ib Allocation {..} = fmap (toBT alloPath) alloAmts
{ btFrom = BudgetSplit from ib { btFrom = BudgetSplit from ib
, btTo = BudgetSplit to $ Just alloBucket , btTo = BudgetSplit to $ Just alloBucket
, btValue = dec2Rat v , btValue = dec2Rat v
, btName = desc , btDesc = desc
, btMeta = meta , btMeta = meta
} }
@ -185,7 +186,7 @@ fromTax meta from Tax {taxAcnt = to, taxValue = v} =
{ btFrom = BudgetSplit from (Just IntraTax) { btFrom = BudgetSplit from (Just IntraTax)
, btTo = BudgetSplit to (Just Fixed) , btTo = BudgetSplit to (Just Fixed)
, btValue = dec2Rat v , btValue = dec2Rat v
, btName = "" , btDesc = ""
, btMeta = meta , btMeta = meta
} }
@ -209,36 +210,51 @@ sumAllocation = sum . fmap (dec2Rat . amtValue) . alloAmts
sumTaxes :: [Tax] -> Rational sumTaxes :: [Tax] -> Rational
sumTaxes = sum . fmap (dec2Rat . taxValue) sumTaxes = sum . fmap (dec2Rat . taxValue)
insertTransfer :: MonadUnliftIO m => Transfer -> MappingT m [InsertError] insertTransfer :: MonadUnliftIO m => T.Text -> Transfer -> MappingT m [InsertError]
insertTransfer t@Transfer {..} = insertTransfer name t@Transfer {..} =
fmap concat $ whenHash CTExpense t [] $ \key -> do fmap concat $ whenHash CTExpense t [] $ \key -> do
forM transAmounts $ \(TimeAmount amt pat) -> do forM transAmounts $ \(TimeAmount amt pat) -> do
res <- withDates pat $ \day -> insertBudgetTx $ budgetTx amt day key res <- withDates pat $ \day -> insertBudgetTx $ budgetTx amt day key
unlessLefts res $ return . concat unlessLefts res $ return . concat
where where
meta d c = BudgetMeta {bmWhen = d, bmCur = transCurrency, bmCommit = c} meta d c =
BudgetMeta
{ bmWhen = d
, bmCur = transCurrency
, bmCommit = c
, bmName = name
}
budgetTx (Amount desc v) d c = budgetTx (Amount desc v) d c =
BudgetTx BudgetTx
{ btMeta = meta d c { btMeta = meta d c
, btFrom = BudgetSplit transFrom Nothing , btFrom = BudgetSplit transFrom Nothing
, btTo = BudgetSplit transTo Nothing , btTo = BudgetSplit transTo Nothing
, btValue = dec2Rat v , btValue = dec2Rat v
, btName = desc , btDesc = desc
} }
insertBudgetTx :: MonadUnliftIO m => BudgetTx -> MappingT m [InsertError] insertBudgetTx :: MonadUnliftIO m => BudgetTx -> MappingT m [InsertError]
insertBudgetTx BudgetTx {..} = do insertBudgetTx BudgetTx {..} = do
res <- splitPair (bsAcnt btFrom) (bsAcnt btTo) (bmCur btMeta) btValue res <- splitPair (bsAcnt btFrom) (bsAcnt btTo) (bmCur btMeta) btValue
unlessLefts_ res $ \(sFrom, sTo) -> lift $ do unlessLefts_ res $ \(sFrom, sTo) -> lift $ do
k <- insert $ TransactionR (bmCommit btMeta) (bmWhen btMeta) btName k <- insert $ TransactionR (bmCommit btMeta) (bmWhen btMeta) btDesc
skFrom <- insertSplit k sFrom insertBudgetLabel name k IncomeBucketR sFrom btFrom
bFrom <- insert $ BudgetLabelR skFrom btName insertBudgetLabel name k ExpenseBucketR sTo btTo
forM_ (bsBucket btFrom) $ \b -> where
insert_ $ IncomeBucketR bFrom b name = bmName btMeta
skTo <- insertSplit k sTo
bTo <- insert $ BudgetLabelR skTo btName insertBudgetLabel
forM_ (bsBucket btTo) $ \b -> :: (MonadUnliftIO m, PersistRecordBackend record SqlBackend)
insert_ $ ExpenseBucketR bTo b => T.Text
-> Key TransactionR
-> (Key BudgetLabelR -> a -> record)
-> KeySplit
-> BudgetSplit a
-> SqlPersistT m ()
insertBudgetLabel name k bucketType split bs = do
sk <- insertSplit k split
bk <- insert $ BudgetLabelR sk name
forM_ (bsBucket bs) $ insert_ . bucketType bk
splitPair splitPair
:: MonadUnliftIO m :: MonadUnliftIO m

View File

@ -71,6 +71,7 @@ makeHaskellTypesWith
data AccountTree data AccountTree
= Placeholder T.Text T.Text [AccountTree] = Placeholder T.Text T.Text [AccountTree]
| Account T.Text T.Text | Account T.Text T.Text
deriving (Eq, Generic, Hashable)
TH.makeBaseFunctor ''AccountTree TH.makeBaseFunctor ''AccountTree
@ -110,7 +111,7 @@ type CurID = T.Text
data Config_ a = Config_ data Config_ a = Config_
{ global :: !Global { global :: !Global
, budget :: !Budget , budget :: ![Budget]
, currencies :: ![Currency] , currencies :: ![Currency]
, statements :: ![Statement] , statements :: ![Statement]
, accounts :: !a , accounts :: !a
@ -240,6 +241,10 @@ deriving instance Hashable DatePat
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Budget (projecting into the future) -- Budget (projecting into the future)
deriving instance Eq Budget
deriving instance Hashable Budget
deriving instance Eq Income deriving instance Eq Income
deriving instance Hashable Income deriving instance Hashable Income
@ -312,7 +317,7 @@ deriving instance Hashable Transfer
data Statement data Statement
= StmtManual !Manual = StmtManual !Manual
| StmtImport !Import | StmtImport !Import
deriving (Generic, FromDhall) deriving (Eq, Hashable, Generic, FromDhall)
deriving instance Eq Manual deriving instance Eq Manual