snelstart-import 1.0.1 → 1.1.0
raw patch · 6 files changed
+62/−21 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ SnelstartImport.SepaDirectCoreScheme: SepaDirectCoreResults :: [SepaDirectCoreScheme] -> SepaGlobals -> SepaDirectCoreResults
+ SnelstartImport.SepaDirectCoreScheme: SepaGlobals :: UTCTime -> Text -> SepaGlobals
+ SnelstartImport.SepaDirectCoreScheme: [cdtrAcct] :: SepaGlobals -> Text
+ SnelstartImport.SepaDirectCoreScheme: [creDtTm] :: SepaGlobals -> UTCTime
+ SnelstartImport.SepaDirectCoreScheme: [rmtInf] :: SepaDirectCoreScheme -> Text
+ SnelstartImport.SepaDirectCoreScheme: [sdcrGlob] :: SepaDirectCoreResults -> SepaGlobals
+ SnelstartImport.SepaDirectCoreScheme: [sdcrRows] :: SepaDirectCoreResults -> [SepaDirectCoreScheme]
+ SnelstartImport.SepaDirectCoreScheme: data SepaDirectCoreResults
+ SnelstartImport.SepaDirectCoreScheme: data SepaGlobals
- SnelstartImport.Convert: sepaDirectCoreSchemeToING :: Text -> SepaDirectCoreScheme -> ING
+ SnelstartImport.Convert: sepaDirectCoreSchemeToING :: SepaGlobals -> SepaDirectCoreScheme -> ING
- SnelstartImport.SepaDirectCoreScheme: SepaDirectCoreScheme :: Text -> Text -> Text -> Currency -> Day -> SepaDirectCoreScheme
+ SnelstartImport.SepaDirectCoreScheme: SepaDirectCoreScheme :: Text -> Text -> Text -> Currency -> Day -> Text -> SepaDirectCoreScheme
- SnelstartImport.SepaDirectCoreScheme: readSepaDirectCoreScheme :: ByteString -> Either SepaParseErrors [SepaDirectCoreScheme]
+ SnelstartImport.SepaDirectCoreScheme: readSepaDirectCoreScheme :: ByteString -> Either SepaParseErrors SepaDirectCoreResults
Files
- Changelog.md +7/−0
- Readme.md +2/−2
- snelstart-import.cabal +1/−1
- src/SnelstartImport/Convert.hs +7/−8
- src/SnelstartImport/SepaDirectCoreScheme.hs +40/−6
- src/SnelstartImport/Web/Handler.hs +5/−4
Changelog.md view
@@ -1,5 +1,12 @@ # Change log for template project +## Version 1.1.0++ strip whitespace from bank input++ sepa direct only should add ++ use credbttm as date++ credacct is used for bank number++ add invoice number in description (rmtinf)+ ## Version 1.0.1 + Add language files
Readme.md view
@@ -1,7 +1,7 @@ [](https://jappieklooster.nl/tag/haskell.html)-[](https://github.com/jappeace/haskell-template-project/actions)+[](https://github.com/jappeace/snelstart-import/actions) [](https://discord.gg/Hp4agqy)-[](https://hackage.haskell.org/package/template) +[](https://hackage.haskell.org/package/snelstart-import) > In lyk man is in ryk man
snelstart-import.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: snelstart-import-version: 1.0.1+version: 1.1.0 homepage: https://github.com/jappeace/snelstart-import#readme bug-reports: https://github.com/jappeace/snelstart-import/issues description: Import to snelstart from various formats such as sepa direct debit or n26 bank format. Converts the format to ING csv. Has a server for an easy UI and a cli for quick conversions.
src/SnelstartImport/Convert.hs view
@@ -11,19 +11,18 @@ import SnelstartImport.ING import SnelstartImport.N26 import Data.Text(Text)-import SnelstartImport.SepaDirectCoreScheme (SepaDirectCoreScheme(..))-import Data.Time+import SnelstartImport.SepaDirectCoreScheme -sepaDirectCoreSchemeToING :: Text -> SepaDirectCoreScheme -> ING-sepaDirectCoreSchemeToING ownAccoun SepaDirectCoreScheme{..} = ING{- datum = UTCTime{ utctDay = dtOfSgntr, utctDayTime = 0},+sepaDirectCoreSchemeToING :: SepaGlobals -> SepaDirectCoreScheme -> ING+sepaDirectCoreSchemeToING SepaGlobals{..} SepaDirectCoreScheme{..} = ING{+ datum = creDtTm , naamBescrhijving = dbtr,- rekening = ownAccoun,+ rekening = cdtrAcct, tegenRekening = dbtrAcct, mutatieSoort = Overschijving, -- TODO how can we figure this out?- bijAf = Af, -- TODO looks like it only deducts from the account, is this right?+ bijAf = Bij, -- appaerantly they ony use it for invoices so they add money bedragEur = instdAmt ,- mededeling = ""+ mededeling = rmtInf } n26ToING :: Text -> N26 -> ING
src/SnelstartImport/SepaDirectCoreScheme.hs view
@@ -1,10 +1,13 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-} -- | https://www.europeanpaymentscouncil.eu/sites/default/files/kb/file/2022-06/EPC130-08%20SDD%20Core%20C2PSP%20IG%202023%20V1.0.pdf -- this is some xml format the accountents asked support for module SnelstartImport.SepaDirectCoreScheme ( SepaDirectCoreScheme(..)+ , SepaDirectCoreResults(..)+ , SepaGlobals(..) , readSepaDirectCoreScheme ) where@@ -18,7 +21,9 @@ import Data.List import Data.Bifunctor(first) import Text.Read(readMaybe)-import Data.Time(Day, parseTimeM, defaultTimeLocale)+import Data.Time(UTCTime, Day, parseTimeM, defaultTimeLocale)+import Data.Time.Format.ISO8601+import Data.Time.LocalTime(zonedTimeToUTC) data SepaDirectCoreScheme = SepaDirectCoreScheme { -- -- | Unambiguous identification of the account of the@@ -26,12 +31,23 @@ -- -- result of the payment transaction. -- cdtrAcct :: Text, endToEndId :: Text,- dbtrAcct :: Text,- dbtr :: Text,+ dbtrAcct :: Text, -- | bank number+ dbtr :: Text, -- | name of person sending instdAmt :: Currency,- dtOfSgntr :: Day+ dtOfSgntr :: Day, -- | this is not the actual transaction date+ rmtInf :: Text -- | invoice number } deriving Show +data SepaGlobals = SepaGlobals {+ creDtTm :: UTCTime,+ cdtrAcct :: Text+ }++data SepaDirectCoreResults = SepaDirectCoreResults {+ sdcrRows :: [SepaDirectCoreScheme],+ sdcrGlob :: SepaGlobals+ }+ name_ :: Node -> Text name_ = Text.toLower . decodeUtf8 . name @@ -42,18 +58,30 @@ | SepaParseIssues SepaIssues deriving Show -readSepaDirectCoreScheme :: ByteString -> Either SepaParseErrors [SepaDirectCoreScheme]+readSepaDirectCoreScheme :: ByteString -> Either SepaParseErrors SepaDirectCoreResults readSepaDirectCoreScheme contents = do nodeRes <- first ParseXmlError $ parse contents - traverse (first SepaParseIssues . parseSepa) (((dig "DrctDbtTxInf")) =<< ((dig "PmtInf") =<< ((dig "CstmrDrctDbtInitn") =<< dig "document" nodeRes)))+ mainNode :: Node <- first SepaParseIssues $ assertOne "CstmrDrctDbtInitn" ((dig "CstmrDrctDbtInitn") =<< dig "document" nodeRes)++ sdcrGlob <- first SepaParseIssues $ parseGlobals mainNode++ sdcrRows <- traverse (first SepaParseIssues . parseSepa) $ ((dig "DrctDbtTxInf")) =<< (dig "PmtInf" mainNode )++ pure $ SepaDirectCoreResults {..} -- data SepaIssues = ExpectedOne [Node] Text | ExpectedNumber Node Text | ExpectedDate Node Text+ | ExpectedTime Node Text deriving Show +parseGlobals :: Node -> Either SepaIssues SepaGlobals+parseGlobals node = do+ creDtTm <- parseTime =<< assertOne "CreDtTm" (dig "CreDtTm" =<< dig "GrpHdr" node)+ cdtrAcct <- inner_ <$> assertOne "CdtrAcct" (dig "IBAN" =<< dig "Id" =<< dig "CdtrAcct" =<< dig "PmtInf" node)+ pure $ SepaGlobals {..} assertOne :: Text -> [Node] -> Either SepaIssues Node assertOne label nodes =@@ -74,6 +102,11 @@ Nothing -> Left $ ExpectedDate node (inner_ node) Just day -> Right day +parseTime :: Node -> Either SepaIssues UTCTime+parseTime node = case zonedTimeToUTC <$> iso8601ParseM (Text.unpack (inner_ node)) of+ Nothing -> Left $ ExpectedTime node (inner_ node)+ Just day -> Right day+ parseSepa :: Node -> Either SepaIssues SepaDirectCoreScheme parseSepa node = do dbtr <- inner_ <$> assertOne "dbtr" (dig "nm" =<< dig "dbtr" node)@@ -81,6 +114,7 @@ endToEndId <- inner_ <$> assertOne "endToEndId" (dig "EndToEndId" =<< dig "PmtId" node) instdAmt <- parseCurrency =<< assertOne "instdAmt" (dig "instdAmt" node) dtOfSgntr <- parseDay =<< assertOne "dtOfSgntr" (dig "DtOfSgntr" =<< dig "MndtRltdInf" =<< dig "DrctDbtTx" node)+ rmtInf <- inner_ <$> assertOne "RmtInf" (dig "Ustrd" =<< dig "RmtInf" node) Right $ SepaDirectCoreScheme { .. }
src/SnelstartImport/Web/Handler.hs view
@@ -24,12 +24,13 @@ import Data.ByteString.Base64 import Data.Base64.Types(extractBase64) import qualified Data.Text as Text-import SnelstartImport.SepaDirectCoreScheme(readSepaDirectCoreScheme)+import SnelstartImport.SepaDirectCoreScheme(readSepaDirectCoreScheme, sdcrRows, sdcrGlob ) import SnelstartImport.Web.Layout(layout) import Yesod.Core(lucius) import SnelstartImport.Web.Message import Data.Time import Control.Monad.IO.Class(liftIO)+import Data.Maybe(fromMaybe) type Form a = Html -> MForm Handler (FormResult a, Widget)@@ -42,7 +43,7 @@ inputFileForm :: Form InputFileForm inputFileForm csrf = do- (bankRes, bankView) <- mreq textField "own bank account" Nothing+ (bankRes, bankView) <- mopt textField "own bank account" Nothing (inputRes, inputView) <- mreq fileField "xml file" Nothing let view = do@@ -66,7 +67,7 @@ <div> <button type=submit >_{MsgConvert} |]- pure $ (InputFileForm <$> bankRes <*> inputRes, view)+ pure $ (InputFileForm <$> (Text.replace " " "" . Text.strip . fromMaybe "" <$> bankRes) <*> inputRes, view) getRootR :: Handler Html getRootR = do@@ -104,7 +105,7 @@ if Text.isSuffixOf "xml" filename then case readSepaDirectCoreScheme contents of Left err -> layout $ inputForm [pack $ show err] enctype form- Right res' -> renderDownload formRes (sepaDirectCoreSchemeToING (ifBank formRes) <$> res')+ Right res' -> renderDownload formRes (sepaDirectCoreSchemeToING (sdcrGlob res') <$> sdcrRows res') else case readN26BS $ LBS.fromStrict contents of Left err -> layout $ inputForm [pack err] enctype form Right n26 -> renderDownload formRes (n26ToING (ifBank formRes) <$> toList n26)