Tarkista että maksut ovat tarpeeksi suuria
This commit is contained in:
parent
72e432cc82
commit
8bd1753539
|
@ -1,5 +1,6 @@
|
||||||
{-# LANGUAGE DuplicateRecordFields #-}
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
|
||||||
module Laskutin where
|
module Laskutin where
|
||||||
|
@ -7,9 +8,9 @@ module Laskutin where
|
||||||
import Control.Concurrent (threadDelay)
|
import Control.Concurrent (threadDelay)
|
||||||
import Control.Monad (forM_, forM)
|
import Control.Monad (forM_, forM)
|
||||||
import Data.Csv (encodeByName)
|
import Data.Csv (encodeByName)
|
||||||
import Data.List.NonEmpty (nonEmpty)
|
import Data.List.NonEmpty (nonEmpty, singleton)
|
||||||
import Data.MIME (Address, renderAddresses)
|
import Data.MIME (Address, renderAddresses)
|
||||||
import Data.Maybe (catMaybes, mapMaybe)
|
import Data.Maybe (fromMaybe, mapMaybe)
|
||||||
import Data.Text.Encoding (decodeUtf8)
|
import Data.Text.Encoding (decodeUtf8)
|
||||||
import System.Exit (exitFailure)
|
import System.Exit (exitFailure)
|
||||||
import System.IO (stderr, hPutStrLn)
|
import System.IO (stderr, hPutStrLn)
|
||||||
|
@ -31,9 +32,9 @@ main = do
|
||||||
|
|
||||||
sendInvoicesMain :: FilePath -> SendOptions -> IO ()
|
sendInvoicesMain :: FilePath -> SendOptions -> IO ()
|
||||||
sendInvoicesMain csvPath options@SendOptions {email, sendmail, reminders} = do
|
sendInvoicesMain csvPath options@SendOptions {email, sendmail, reminders} = do
|
||||||
invoices <- filter (\(_, Invoice {isPaid}) -> not isPaid) <$> readInvoices csvPath options
|
invoices <- mapMaybe (\(address, invoice) -> (address,) <$> renderEmail reminders [email] address invoice) <$> readInvoices csvPath options
|
||||||
forM_ invoices $ \invoice@(address, _) -> do
|
forM_ invoices $ \(address, invoiceMail) -> do
|
||||||
sendEmail sendmail $ uncurry (renderEmail reminders [email]) invoice
|
sendEmail sendmail invoiceMail
|
||||||
putStr "Lähetetty osoitteeseen: "
|
putStr "Lähetetty osoitteeseen: "
|
||||||
T.putStrLn $ decodeUtf8 $ renderAddresses address
|
T.putStrLn $ decodeUtf8 $ renderAddresses address
|
||||||
threadDelay (3 * 1000 * 1000)
|
threadDelay (3 * 1000 * 1000)
|
||||||
|
@ -46,17 +47,21 @@ updateTableMain invoiceCsv transactionCsv = do
|
||||||
LBS.writeFile invoiceCsv $ encodeByName headers newInvoices
|
LBS.writeFile invoiceCsv $ encodeByName headers newInvoices
|
||||||
|
|
||||||
updateInvoices :: [CsvTransaction] -> CsvInvoice -> CsvInvoice
|
updateInvoices :: [CsvTransaction] -> CsvInvoice -> CsvInvoice
|
||||||
updateInvoices transactions invoice@CsvInvoice {isPaid, reference, ..}
|
updateInvoices transactions invoice@CsvInvoice {reference, ..}
|
||||||
| isPaid = invoice
|
| csvIsPaid invoice = invoice
|
||||||
| otherwise = CsvInvoice {isPaid = reference `elem` transactionReferences, ..}
|
| otherwise = CsvInvoice {paid = Just transactionsSum, ..}
|
||||||
where transactionReferences = catMaybes $ transactionReference <$> transactions
|
where transactionsSum = sum $ map amount $ filter hasReference transactions
|
||||||
transactionReference CsvTransaction {referenceOrMessage = Message _} = Nothing
|
hasReference CsvTransaction {referenceOrMessage = Reference ref} = reference == ref
|
||||||
transactionReference CsvTransaction {referenceOrMessage = Reference ref} = Just ref
|
hasReference CsvTransaction {referenceOrMessage = Message _} = False
|
||||||
|
|
||||||
readInvoices :: FilePath -> SendOptions -> IO [([Address], Invoice)]
|
readInvoices :: FilePath -> SendOptions -> IO [([Address], Invoice)]
|
||||||
readInvoices csv SendOptions {account, recipient, subject, message, due} = do
|
readInvoices csv SendOptions {account, recipient, subject, message, due} = do
|
||||||
(_, csvInvoices) <- parseCsvFile csv
|
(_, csvInvoices) <- parseCsvFile csv
|
||||||
forM csvInvoices $ \CsvInvoice {..} -> do
|
forM csvInvoices $ \csvInvoice@CsvInvoice {..} -> do
|
||||||
invoiceRows <- maybe (hPutStrLn stderr ("No invoice rows in invoice") >> exitFailure) pure $
|
invoiceRows' <- maybe (hPutStrLn stderr ("No invoice rows in invoice") >> exitFailure) pure $
|
||||||
nonEmpty $ mapMaybe invoiceRowFromCsv rows
|
nonEmpty $ mapMaybe invoiceRowFromCsv rows
|
||||||
|
let isPaid = fromMaybe 0 paid >= csvInvoiceSum csvInvoice
|
||||||
|
invoiceRows = case paid of
|
||||||
|
Nothing -> invoiceRows'
|
||||||
|
Just euro -> invoiceRows' <> singleton InvoiceRow {name = "Aiemmin maksettu", amount = 1, price = negate euro}
|
||||||
pure ([invoiceRecipient], Invoice { rows = invoiceRows, ..})
|
pure ([invoiceRecipient], Invoice { rows = invoiceRows, ..})
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{-# LANGUAGE DisambiguateRecordFields #-}
|
{-# LANGUAGE DisambiguateRecordFields #-}
|
||||||
{-# LANGUAGE DuplicateRecordFields #-}
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
|
||||||
module Laskutin.CSV (CsvInvoice(..), CsvTransaction(..), TransactionMessage(..), parseCsvFile, invoiceRowFromCsv) where
|
module Laskutin.CSV (CsvInvoice(..), CsvTransaction(..), TransactionMessage(..), csvInvoiceSum, csvInvoiceRowSum, csvIsPaid, parseCsvFile, invoiceRowFromCsv) where
|
||||||
|
|
||||||
import Data.Bifunctor (bimap, second)
|
import Data.Bifunctor (bimap, second)
|
||||||
import Control.Monad ((>=>), guard)
|
import Control.Monad ((>=>), guard)
|
||||||
|
@ -29,7 +29,7 @@ import Laskutin.Types
|
||||||
data CsvInvoice = CsvInvoice
|
data CsvInvoice = CsvInvoice
|
||||||
{ invoiceRecipient :: Address
|
{ invoiceRecipient :: Address
|
||||||
, reference :: Reference
|
, reference :: Reference
|
||||||
, isPaid :: Bool
|
, paid :: Maybe Euro
|
||||||
, rows :: [CsvInvoiceRow]
|
, rows :: [CsvInvoiceRow]
|
||||||
} deriving Show
|
} deriving Show
|
||||||
|
|
||||||
|
@ -47,6 +47,16 @@ data CsvTransaction = CsvTransaction
|
||||||
, amount :: Euro
|
, amount :: Euro
|
||||||
} deriving Show
|
} deriving Show
|
||||||
|
|
||||||
|
csvIsPaid :: CsvInvoice -> Bool
|
||||||
|
csvIsPaid CsvInvoice {paid = Nothing} = False
|
||||||
|
csvIsPaid invoice@CsvInvoice {paid = Just paid} = paid >= csvInvoiceSum invoice
|
||||||
|
|
||||||
|
csvInvoiceSum :: CsvInvoice -> Euro
|
||||||
|
csvInvoiceSum CsvInvoice {rows} = sum $ csvInvoiceRowSum <$> rows
|
||||||
|
|
||||||
|
csvInvoiceRowSum :: CsvInvoiceRow -> Euro
|
||||||
|
csvInvoiceRowSum CsvInvoiceRow {amount, price} = maybe 0 ((* price) . fromIntegral) amount
|
||||||
|
|
||||||
data TransactionMessage = Message Text
|
data TransactionMessage = Message Text
|
||||||
| Reference Reference
|
| Reference Reference
|
||||||
deriving Show
|
deriving Show
|
||||||
|
@ -57,14 +67,6 @@ instance FromField Address where
|
||||||
instance ToField Address where
|
instance ToField Address where
|
||||||
toField = renderAddress
|
toField = renderAddress
|
||||||
|
|
||||||
instance FromField Bool where
|
|
||||||
parseField "x" = pure True
|
|
||||||
parseField _ = pure False
|
|
||||||
|
|
||||||
instance ToField Bool where
|
|
||||||
toField True = "x"
|
|
||||||
toField False = ""
|
|
||||||
|
|
||||||
instance FromField Day where
|
instance FromField Day where
|
||||||
parseField = iso8601ParseM . T.unpack . decodeUtf8
|
parseField = iso8601ParseM . T.unpack . decodeUtf8
|
||||||
|
|
||||||
|
@ -83,14 +85,14 @@ instance FromNamedRecord CsvInvoice where
|
||||||
<*> namedRecordToRows (foldr delete row [emailField, referenceField, paidField])
|
<*> namedRecordToRows (foldr delete row [emailField, referenceField, paidField])
|
||||||
|
|
||||||
instance ToNamedRecord CsvInvoice where
|
instance ToNamedRecord CsvInvoice where
|
||||||
toNamedRecord CsvInvoice {invoiceRecipient, reference, isPaid, rows} = namedRecord $
|
toNamedRecord CsvInvoice {invoiceRecipient, reference, paid, rows} = namedRecord $
|
||||||
[ emailField .= invoiceRecipient
|
[ emailField .= invoiceRecipient
|
||||||
, referenceField .= reference
|
, referenceField .= reference
|
||||||
, paidField .= isPaid
|
, paidField .= paid
|
||||||
] <> (invoiceRowToField <$> rows)
|
] <> (invoiceRowToField <$> rows)
|
||||||
|
|
||||||
invoiceRowToField :: CsvInvoiceRow -> (BS.ByteString, BS.ByteString)
|
invoiceRowToField :: CsvInvoiceRow -> (BS.ByteString, BS.ByteString)
|
||||||
invoiceRowToField CsvInvoiceRow {amount, name} = encodeUtf8 name .= amount
|
invoiceRowToField CsvInvoiceRow {amount, price, name} = encodeUtf8 (name <> ":" <> renderEuro price) .= amount
|
||||||
|
|
||||||
instance FromNamedRecord CsvTransaction where
|
instance FromNamedRecord CsvTransaction where
|
||||||
parseNamedRecord row = CsvTransaction
|
parseNamedRecord row = CsvTransaction
|
||||||
|
@ -111,7 +113,7 @@ namedRecordToRows = mapM (parseInvoiceRow . bimap decodeUtf8 decodeUtf8) . HM.to
|
||||||
guard $ T.all isDigit x
|
guard $ T.all isDigit x
|
||||||
pure $ read $ T.unpack x
|
pure $ read $ T.unpack x
|
||||||
readHeader x = do
|
readHeader x = do
|
||||||
[name, price'] <- pure $ T.strip <$> T.splitOn ":" x
|
[name, price'] <- pure $ T.splitOn ":" x
|
||||||
price <- readEuro price'
|
price <- readEuro price'
|
||||||
pure (name, price)
|
pure (name, price)
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,11 @@ import qualified Data.ByteString.Lazy as LBS
|
||||||
|
|
||||||
import Laskutin.Types
|
import Laskutin.Types
|
||||||
|
|
||||||
renderEmail :: Bool -> [Address] -> [Address] -> Invoice -> ByteString
|
renderEmail :: Bool -> [Address] -> [Address] -> Invoice -> Maybe ByteString
|
||||||
renderEmail isReminder senders recipients = renderMessage
|
renderEmail isReminder senders recipients = fmap (renderMessage
|
||||||
. set (headerBCC defaultCharsets) senders
|
. set (headerBCC defaultCharsets) senders
|
||||||
. set (headerTo defaultCharsets) recipients
|
. set (headerTo defaultCharsets) recipients
|
||||||
. set (headerFrom defaultCharsets) senders
|
. set (headerFrom defaultCharsets) senders)
|
||||||
. invoiceEmail isReminder
|
. invoiceEmail isReminder
|
||||||
|
|
||||||
sendEmail :: FilePath -> ByteString -> IO ()
|
sendEmail :: FilePath -> ByteString -> IO ()
|
||||||
|
@ -36,8 +36,9 @@ sendEmail sendmail email = do
|
||||||
ExitSuccess -> pure ()
|
ExitSuccess -> pure ()
|
||||||
ExitFailure code -> throwIO $ ErrorCall ("sendmail exited with error code " <> show code)
|
ExitFailure code -> throwIO $ ErrorCall ("sendmail exited with error code " <> show code)
|
||||||
|
|
||||||
invoiceEmail :: Bool -> Invoice -> MIMEMessage
|
invoiceEmail :: Bool -> Invoice -> Maybe MIMEMessage
|
||||||
invoiceEmail isReminder Invoice {rows, reference, account, recipient, due, subject, message} =
|
invoiceEmail _ Invoice {isPaid = True} = Nothing
|
||||||
|
invoiceEmail isReminder Invoice {rows, reference, account, recipient, due, subject, message, isPaid = False} = Just $
|
||||||
set (headerSubject defaultCharsets) (Just $ subjectPrefix <> " – " <> subject ) $ createTextPlainMessage $
|
set (headerSubject defaultCharsets) (Just $ subjectPrefix <> " – " <> subject ) $ createTextPlainMessage $
|
||||||
(if isReminder then "Muistutus alla olevan laskun maksamisesta. Jos olet juuri maksanut kyseisen laskun, tämä muistutus on aiheeton.\n\n" else mempty)
|
(if isReminder then "Muistutus alla olevan laskun maksamisesta. Jos olet juuri maksanut kyseisen laskun, tämä muistutus on aiheeton.\n\n" else mempty)
|
||||||
<> (if T.null message then mempty else (message <> "\n\n\n"))
|
<> (if T.null message then mempty else (message <> "\n\n\n"))
|
||||||
|
|
Loading…
Reference in New Issue