Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/Types/Transaction/TransactionBuySell.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import Types.Transaction.GenericTransaction
)
import Types.UtilTypes
( parseInt,
parseMoney,
parseMoneyDefault0,
parseSvMoney,
parseSvMoneyDefault0,
)
import Util (SortableByDate (..))

Expand All @@ -35,11 +35,11 @@ transformToBuySell (GenericTransaction date account action company quantity rate
"Sälj" -> Just Sell
_ -> Nothing
quantity' <- parseInt quantity
rate' <- parseMoney rate
amount' <- parseMoney amount
rate' <- parseSvMoney rate
amount' <- parseSvMoney amount
-- I've noticed that courtage = 0 is sometimes indicated with "-" rather than
-- "0", therefor the default
let courtage' = parseMoneyDefault0 courtage
let courtage' = parseSvMoneyDefault0 courtage

return $
GenericTransaction
Expand Down
10 changes: 5 additions & 5 deletions src/Types/Transaction/TransactionDividend.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import Types.Transaction.GenericTransaction
)
import Types.UtilTypes
( parseInt,
parseMoney,
parseMoneyDefault0,
parseSvMoney,
parseSvMoneyDefault0,
)

type TransactionDividend = GenericTransaction Action Int Money
Expand All @@ -29,11 +29,11 @@ transformToDividend (GenericTransaction date account action company quantity rat
"Utdelning" -> Just Dividend
_ -> Nothing
quantity' <- parseInt quantity
rate' <- parseMoney rate
amount' <- parseMoney amount
rate' <- parseSvMoney rate
amount' <- parseSvMoney amount
-- I've noticed that courtage = 0 is sometimes indicated with "-" rather than
-- "0", therefor the default
let courtage' = parseMoneyDefault0 courtage
let courtage' = parseSvMoneyDefault0 courtage

return $
GenericTransaction
Expand Down
17 changes: 12 additions & 5 deletions src/Types/UtilTypes.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module Types.UtilTypes
normalizeSvNumber,
parseSvDouble,
parseSvMoney,
parseSvMoneyDefault0,
SortedByDateList (..),
)
where
Expand Down Expand Up @@ -44,11 +45,12 @@ parseMoneyDefault0 t = case parseMoney t of
Just m -> m
Nothing -> Money 0.0

-- Avanza's "consolidated holdings" export formats numbers in the Swedish locale:
-- a comma as the decimal separator and (for large values) spaces as thousands
-- separators, e.g. "1 234 567,89". This normalizes such a value into something
-- 'parseDouble' understands by dropping the (possibly non-breaking) spaces and
-- swapping the decimal comma for a dot.
-- Avanza's CSV exports format numbers in the Swedish locale: a comma as the
-- decimal separator and (for large values) spaces as thousands separators, e.g.
-- "1 234 567,89". 'parseDouble' (Data.Text.Read) only understands a dot and
-- stops at the comma, silently truncating "114,3" to 114. This normalizes such a
-- value into something 'parseDouble' reads in full by dropping the (possibly
-- non-breaking) spaces and swapping the decimal comma for a dot.
normalizeSvNumber :: Text -> Text
normalizeSvNumber =
T.replace (T.pack ",") (T.pack ".")
Expand All @@ -60,6 +62,11 @@ parseSvDouble = parseDouble . normalizeSvNumber
parseSvMoney :: Text -> Maybe Money
parseSvMoney t = Money <$> parseSvDouble t

parseSvMoneyDefault0 :: Text -> Money
parseSvMoneyDefault0 t = case parseSvMoney t of
Just m -> m
Nothing -> Money 0.0

newtype SortedByDateList a = SortedByDateList {getSortedByDateList :: [a]}
deriving (Show, Eq, Ord, Functor, Foldable)

Expand Down
23 changes: 22 additions & 1 deletion test/ParseSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@ import Test.Hspec
import TestUtils (shouldThrowErrorContaining)
import Types.Money (Money (..))
import Types.Position (Position (..))
import Types.Transaction.GenericTransaction (GenericTransaction (..))
import Types.Transaction.GenericTransaction
( GenericTransaction (..),
getAmount,
getCourtage,
getRate,
)
import Types.Transaction.TransactionBuySell (transformToBuySell)

spec :: Spec
spec = do
describe "test buy transaction" testParseBuy
describe "test basic parsing" testBasicParsing
describe "test position parsing" testParsePosition
describe "test swedish decimal parsing" testSwedishDecimals

testBasicParsing :: Spec
testBasicParsing = do
Expand Down Expand Up @@ -126,6 +132,21 @@ testParsePosition = do
evaluate (P.parsePositionCsvData csv)
`shouldThrowErrorContaining` "Missing: Kontonummer"

testSwedishDecimals :: Spec
testSwedishDecimals = do
it "parses comma-decimal rate/amount/courtage without truncating" $ do
-- Real Avanza exports format money with a decimal comma, e.g. "114,3".
-- The values below must survive in full rather than being cut at the comma.
let withBuyRow =
stringsToCsvByteString
[ csvHeader2025,
"2024-01-01;1111111;Köp;Company A;17;114,3;-1947,96;SEK;4,86;;SEK;some-isin;"
]
let buySell = transformToBuySell . head $ P.parseCsvData withBuyRow
fmap getRate buySell `shouldBe` Just (Money 114.3)
fmap getAmount buySell `shouldBe` Just (Money (-1947.96))
fmap getCourtage buySell `shouldBe` Just (Money 4.86)

positionHeader :: String
positionHeader = "Kontonummer;Namn;Kortnamn;Volym;Marknadsvärde;GAV (SEK);GAV;Valuta;Land;ISIN;Marknad;Typ"

Expand Down
Loading