hledger/hledger-web/Handler/AddR.hs
2018-06-24 14:13:03 +02:00

87 lines
3.3 KiB
Haskell

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Handler.AddR
( postAddR
) where
import Import
import Control.Monad.State.Strict (evalStateT)
import Data.List (sortBy)
import qualified Data.Text as T
import Data.Void (Void)
import Safe (headMay)
import Text.Megaparsec
import Text.Megaparsec.Char
import Handler.AddForm (AddForm(..), addForm)
import Handler.Common (showErrors)
import Hledger
import Hledger.Cli.Commands.Add (appendToJournalFileOrStdout)
postAddR :: Handler ()
postAddR = do
VD{today, j} <- getViewData
-- 1. process the fixed fields with yesod-form
runInputPostResult (addForm today j) >>= \case
FormMissing -> bail ["there is no form data"]
FormFailure errs -> bail errs
FormSuccess form -> do
let journalfile = maybe (journalFilePath j) T.unpack $ addFormJournalFile form
-- 2. the fixed fields look good; now process the posting fields adhocly,
-- getting either errors or a balanced transaction
(params,_) <- runRequestBody
let acctparams = parseNumberedParameters "account" params
amtparams = parseNumberedParameters "amount" params
pnum = length acctparams
when (pnum == 0) (bail ["at least one posting must be entered"])
when (map fst acctparams /= [1..pnum] || map fst amtparams `elem` [[1..pnum], [1..pnum-1]])
(bail ["the posting parameters are malformed"])
let eaccts = runParser (accountnamep <* eof) "" . textstrip . snd <$> acctparams
eamts = runParser (evalStateT (amountp <* eof) mempty) "" . textstrip . snd <$> amtparams
(acctErrs, accts) = partitionEithers eaccts
(amtErrs, amts') = partitionEithers eamts
amts | length amts' == pnum = amts'
| otherwise = amts' ++ [missingamt]
errs = T.pack . parseErrorPretty <$> acctErrs ++ amtErrs
unless (null errs) (bail errs)
let etxn = balanceTransaction Nothing $ nulltransaction
{ tdate = addFormDate form
, tdescription = fromMaybe "" $ addFormDescription form
, tpostings = (\(ac, am) -> nullposting {paccount = ac, pamount = Mixed [am]}) <$> zip accts amts
}
case etxn of
Left errs' -> bail (fmap T.pack . maybeToList . headMay $ lines errs')
Right t -> do
-- 3. all fields look good and form a balanced transaction; append it to the file
liftIO (appendTransaction journalfile t)
setMessage [shamlet|<span>Transaction added.|]
redirect JournalR
where
bail :: [Text] -> Handler ()
bail xs = showErrors xs >> redirect (JournalR, [("add","1")])
parseNumberedParameters :: Text -> [(Text, Text)] -> [(Int, Text)]
parseNumberedParameters s =
reverse . dropWhile (T.null . snd) . sortBy (flip compare) . mapMaybe parseNum
where
parseNum :: (Text, Text) -> Maybe (Int, Text)
parseNum (k, v) = case parsewith paramnamep k of
Left (_ :: ParseError Char Void) -> Nothing
Right k' -> Just (k', v)
paramnamep = string s *> (read <$> some digitChar) <* eof
-- XXX move into balanceTransaction
appendTransaction :: FilePath -> Transaction -> IO ()
appendTransaction journalfile t = do
ensureJournalFileExists journalfile
appendToJournalFileOrStdout journalfile $
showTransaction (txnTieKnot t)