diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/Kauf13.txt b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/Kauf13.txt new file mode 100644 index 0000000000..391e094c1b --- /dev/null +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/Kauf13.txt @@ -0,0 +1,28 @@ +PDFBox Version: 1.8.17 +Portfolio Performance Version: 0.67.3 +----------------------------------------- +TRADE REPUBLIC BANK GMBH BRUNNENSTRASSE 19-21 10119 BERLIN +tygkI HBjkt mdySPWKNNou SEITE 1 von 1 +MZAKzhzG. 08B DATUM 05.03.2024 +23537 DCrFCrYea AUSFÜHRUNG 5437-f7f5 +SAVEBACK B2C4-n64q +DEPOT 5652696149 +WERTPAPIERABRECHNUNG SAVEBACK +ÜBERSICHT +Ausführung von Saveback am 04.03.2024 an der Lang & Schwarz Exchange. +Der Kontrahent der Transaktion ist Lang & Schwarz TradeCenter AG & Co. KG. +POSITION ANZAHL DURCHSCHNITTSKURS BETRAG +MUL Amundi MSCI AC World 0,032743 Stk. 420,85 EUR 13,78 EUR +UCITS ETF Inh.Anteile Acc +ISIN: LU1829220216 +GESAMT 13,78 EUR +BUCHUNG +VERRECHNUNGSKONTO WERTSTELLUNG BETRAG +DE2123456789012345678 06.03.2024 -13,78 EUR +MUL Amundi MSCI AC World UCITS ETF Inh.Anteile Acc in Girosammelverwahrung in Deutschland. +Diese Abrechnung wird maschinell erstellt und daher nicht unterschrieben. +Sofern keine Umsatzsteuer ausgewiesen ist, handelt es sich gem. § 4 Nr. 8 UStG um eine umsatzsteuerfreie Leistung. +Trade Republic Bank GmbH www.traderepublic.com Sitz der Gesellschaft: Berlin Geschäftsführer +Brunnenstraße 19-21 service@traderepublic.com AG Charlottenburg HRB 244347 B Andreas Torner +10119 Berlin USt-ID DE307510626 Gernot Mittendorfer +ABRE / 05.03.2024 / 32549613 / DR37-6Z5F \ No newline at end of file diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/USQuellensteuer01.txt b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/Steuerkorrektur01.txt similarity index 100% rename from name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/USQuellensteuer01.txt rename to name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/Steuerkorrektur01.txt diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/TradeRepublicPDFExtractorTest.java b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/TradeRepublicPDFExtractorTest.java index eeaff49b62..4eb2a8eea0 100644 --- a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/TradeRepublicPDFExtractorTest.java +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/TradeRepublicPDFExtractorTest.java @@ -21,6 +21,7 @@ import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasWkn; import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.inboundDelivery; import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.interest; +import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.outboundDelivery; import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.purchase; import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.removal; import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.sale; @@ -60,6 +61,7 @@ import name.abuchen.portfolio.model.AccountTransaction; import name.abuchen.portfolio.model.BuySellEntry; import name.abuchen.portfolio.model.Client; +import name.abuchen.portfolio.model.Portfolio; import name.abuchen.portfolio.model.PortfolioTransaction; import name.abuchen.portfolio.model.Security; import name.abuchen.portfolio.model.Transaction; @@ -123,7 +125,7 @@ public void testWertpapierKauf01() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2019-05-13T12:14"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(1))); assertThat(entry.getSource(), is("Kauf01.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Order: dead-beef | Ausführung: ab12-c3de")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(25.05)))); @@ -167,7 +169,7 @@ public void testWertpapierKauf02() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2019-05-13T13:59"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(1))); assertThat(entry.getSource(), is("Kauf02.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Order: 1234-abcd | Ausführung: a1b2-3456")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(59.19)))); @@ -211,7 +213,7 @@ public void testWertpapierKauf03() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2019-06-17T12:27"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(1))); assertThat(entry.getSource(), is("Kauf03.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Order: fd98-0283 | Ausführung: 51cb-50a8")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(193.08)))); @@ -255,7 +257,7 @@ public void testWertpapierKauf04() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2019-11-05T15:16"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(4))); assertThat(entry.getSource(), is("Kauf04.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Order: a1b2-3456 | Ausführung: 1234-56bb")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(485.80)))); @@ -299,7 +301,7 @@ public void testWertpapierKauf05() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2019-11-21T15:57"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(20))); assertThat(entry.getSource(), is("Kauf05.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Order: xxxx-xxxx | Ausführung: xxxx-xxxx")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1396.60)))); @@ -387,7 +389,7 @@ public void testWertpapierKauf07() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2022-05-02T21:26"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(2))); assertThat(entry.getSource(), is("Kauf07.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Order: 01f6-b7cc | Ausführung: a952-e304")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(95.69)))); @@ -469,7 +471,7 @@ public void testWertpapierKauf09() assertThat(results, hasItem(purchase( // hasDate("2023-11-27T16:17"), hasShares(1000.00), // hasSource("Kauf09.txt"), // - hasNote(null), // + hasNote("Order: 39f0-5191 | Ausführung: f728-6a96"), // hasAmount("EUR", 941.00), hasGrossValue("EUR", 940.00), // hasTaxes("EUR", 0.00), hasFees("EUR", 1.00)))); } @@ -500,7 +502,7 @@ public void testWertpapierKauf10() assertThat(results, hasItem(purchase( // hasDate("2023-11-27T14:05"), hasShares(1000.00), // hasSource("Kauf10.txt"), // - hasNote(null), // + hasNote("Order: 64b4-82d5 | Ausführung: c4f5-afae"), // hasAmount("EUR", 941.00), hasGrossValue("EUR", 940.00), // hasTaxes("EUR", 0.00), hasFees("EUR", 1.00)))); } @@ -531,7 +533,7 @@ public void testWertpapierKauf11() assertThat(results, hasItem(purchase( // hasDate("2023-12-12T19:41"), hasShares(1000.00), // hasSource("Kauf11.txt"), // - hasNote(null), // + hasNote("Order: c141-b5b3 | Ausführung: 4019-2100"), // hasAmount("EUR", 1611.00), hasGrossValue("EUR", 1610.00), // hasTaxes("EUR", 0.00), hasFees("EUR", 1.00)))); } @@ -562,11 +564,42 @@ public void testWertpapierKauf12() assertThat(results, hasItem(purchase( // hasDate("2024-02-09T00:00"), hasShares(0.09351), // hasSource("Kauf12.txt"), // - hasNote(null), // + hasNote("Ausführung: 8f44-fdf5 | Round Up: 42c2-50a7"), // hasAmount("EUR", 2.50), hasGrossValue("EUR", 2.50), // hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))); } + @Test + public void testWertpapierKauf13() + { + TradeRepublicPDFExtractor extractor = new TradeRepublicPDFExtractor(new Client()); + + List errors = new ArrayList<>(); + + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Kauf13.txt"), errors); + + assertThat(errors, empty()); + assertThat(countSecurities(results), is(1L)); + assertThat(countBuySell(results), is(1L)); + assertThat(countAccountTransactions(results), is(0L)); + assertThat(results.size(), is(2)); + new AssertImportActions().check(results, CurrencyUnit.EUR); + + // check security + assertThat(results, hasItem(security( // + hasIsin("LU1829220216"), hasWkn(null), hasTicker(null), // + hasName("MUL Amundi MSCI AC World UCITS ETF Inh.Anteile Acc"), // + hasCurrencyCode("EUR")))); + + // check buy sell transaction + assertThat(results, hasItem(purchase( // + hasDate("2024-03-04T00:00"), hasShares(0.032743), // + hasSource("Kauf13.txt"), // + hasNote("Ausführung: 5437-f7f5 | Saveback: B2C4-n64q"), // + hasAmount("EUR", 13.78), hasGrossValue("EUR", 13.78), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))); + } + @Test public void testBuy01() { @@ -593,7 +626,7 @@ public void testBuy01() assertThat(results, hasItem(purchase( // hasDate("2023-04-28T11:13"), hasShares(100.00), // hasSource("Buy01.txt"), // - hasNote(null), // + hasNote("Order: 10VG-16T0 | Execution: 4A66-g597"), // hasAmount("EUR", 87.40), hasGrossValue("EUR", 86.40), // hasTaxes("EUR", 0.00), hasFees("EUR", 1.00)))); } @@ -624,7 +657,7 @@ public void testAcquisto01() assertThat(results, hasItem(purchase( // hasDate("2023-06-01T10:46"), hasShares(125.00), // hasSource("Acquisto01.txt"), // - hasNote(null), // + hasNote("Ordine: cY43-6m6l | Esecuzione: V711-7789"), // hasAmount("EUR", 3719.75), hasGrossValue("EUR", 3718.75), // hasTaxes("EUR", 0.00), hasFees("EUR", 1.00)))); } @@ -686,7 +719,7 @@ public void testCryptoKauf02() assertThat(results, hasItem(purchase( // hasDate("2023-05-16T00:00"), hasShares(0.000983), // hasSource("CryptoKauf02.txt"), // - hasNote("Sparplan: y646-a753 | Ausführung: K7Y2-2e37"), // + hasNote("Ausführung: K7Y2-2e37 | Sparplan: y646-a753"), // hasAmount("EUR", 24.99), hasGrossValue("EUR", 24.99), // hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))); } @@ -845,7 +878,7 @@ public void testAchat01() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2022-01-17T00:00"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(0.3773))); assertThat(entry.getSource(), is("Achat01.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Exécution : cee1-2d00 | Programmé : eea2-4c8b")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(20.00)))); @@ -1126,13 +1159,13 @@ public void testSteuerabrechnung01() } @Test - public void testUSQuellensteuer01() + public void testSteuerkorrektur01() { TradeRepublicPDFExtractor extractor = new TradeRepublicPDFExtractor(new Client()); List errors = new ArrayList<>(); - List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "USQuellensteuer01.txt"), errors); + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Steuerkorrektur01.txt"), errors); assertThat(errors, empty()); assertThat(results.size(), is(2)); @@ -1155,7 +1188,7 @@ public void testUSQuellensteuer01() assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2023-11-02T00:00"))); assertThat(transaction.getShares(), is(Values.Share.factorize(38.4597))); - assertThat(transaction.getSource(), is("USQuellensteuer01.txt")); + assertThat(transaction.getSource(), is("Steuerkorrektur01.txt")); assertNull(transaction.getNote()); assertThat(transaction.getMonetaryAmount(), @@ -1200,7 +1233,7 @@ public void testWertpapierVerkauf01() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2019-06-18T17:50"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(80))); assertThat(entry.getSource(), is("Verkauf01.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Order: 55a8-39ad | Ausführung: 051a-e65e")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1792.29)))); @@ -1244,7 +1277,7 @@ public void testWertpapierVerkauf02() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2020-06-10T11:42"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(500))); assertThat(entry.getSource(), is("Verkauf02.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Order: 1234-1234 | Ausführung: 1234-1234")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(3594.00)))); @@ -1308,7 +1341,7 @@ public void testWertpapierVerkauf03() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2020-07-21T09:30"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(30))); assertThat(entry.getSource(), is("Verkauf03.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Order: c17d-baea | Ausführung: 6415-fd77")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1199.00)))); @@ -1372,7 +1405,7 @@ public void testWertpapierVerkauf04() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2020-09-12T12:19"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(0.0068))); assertThat(entry.getSource(), is("Verkauf04.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Order: 36de-8883 | Ausführung: f439-3735")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.29)))); @@ -1524,7 +1557,7 @@ public void testWertpapierVerkauf07() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2020-05-23T21:10"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(9))); assertThat(entry.getSource(), is("Verkauf07.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Order: 5a93-03d1 | Ausführung: b11c-12c4")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(623.60)))); @@ -1588,7 +1621,7 @@ public void testWertpapierVerkauf08() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2021-08-03T16:49"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(2))); assertThat(entry.getSource(), is("Verkauf08.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Order: 886a-ffff | Ausführung: dcd2-ffff")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(301.88)))); @@ -1605,10 +1638,10 @@ public void testWertpapierVerkauf08() assertThat(transaction.getType(), is(AccountTransaction.Type.TAX_REFUND)); - assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2021-08-03T16:49"))); - assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(2))); - assertThat(entry.getSource(), is("Verkauf08.txt")); - assertNull(entry.getNote()); + assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2021-08-03T16:49"))); + assertThat(transaction.getShares(), is(Values.Share.factorize(2))); + assertThat(transaction.getSource(), is("Verkauf08.txt")); + assertNull(transaction.getNote()); assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(12.65 + 0.69)))); @@ -1757,8 +1790,8 @@ public void testDividendeStorno01() hasDate("2020-02-26T00:00"), hasShares(200), // hasSource("DividendeStorno01.txt"), // hasNote(null), // - hasAmount("EUR", 29.90), hasGrossValue("EUR", 40.34), // - hasForexGrossValue("USD", 44.80), // + hasAmount("EUR", 30.17), hasGrossValue("EUR", 40.61), // + hasForexGrossValue("USD", 45.10), // hasTaxes("EUR", ((6.81 / 1.1105) + 4.09 + 0.22)), hasFees("EUR", 0.00))))); // check tax refund transaction @@ -1768,8 +1801,8 @@ public void testDividendeStorno01() hasDate("2020-02-26T00:00"), hasShares(200), // hasSource("DividendeStorno01.txt"), // hasNote(null), // - hasAmount("EUR", 0.27), hasGrossValue("EUR", 0.27), // - hasForexGrossValue("USD", 0.30), // + hasAmount("EUR", 0.36), hasGrossValue("EUR", 0.36), // + hasForexGrossValue("USD", 0.40), // hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); } @@ -1802,7 +1835,7 @@ public void testDividendeStorno01WithSecurityInEUR() hasDate("2020-02-26T00:00"), hasShares(200), // hasSource("DividendeStorno01.txt"), // hasNote(null), // - hasAmount("EUR", 29.90), hasGrossValue("EUR", 40.34), // + hasAmount("EUR", 30.17), hasGrossValue("EUR", 40.61), // hasTaxes("EUR", ((6.81 / 1.1105) + 4.09 + 0.22)), hasFees("EUR", 0.00), // check(tx -> { CheckCurrenciesAction c = new CheckCurrenciesAction(); @@ -1819,7 +1852,7 @@ public void testDividendeStorno01WithSecurityInEUR() hasDate("2020-02-26T00:00"), hasShares(200), // hasSource("DividendeStorno01.txt"), // hasNote(null), // - hasAmount("EUR", 0.27), hasGrossValue("EUR", 0.27), // + hasAmount("EUR", 0.36), hasGrossValue("EUR", 0.36), // hasTaxes("EUR", 0.00), hasFees("EUR", 0.00), // check(tx -> { CheckCurrenciesAction c = new CheckCurrenciesAction(); @@ -3329,41 +3362,30 @@ public void testKapitalerhoehungGegenBar01() List errors = new ArrayList<>(); - List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "KapitalerhoehungGegenBar01.txt"), - errors); + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "KapitalerhoehungGegenBar01.txt"), errors); assertThat(errors, empty()); + assertThat(countSecurities(results), is(1L)); + assertThat(countBuySell(results), is(0L)); + assertThat(countAccountTransactions(results), is(1L)); assertThat(results.size(), is(2)); new AssertImportActions().check(results, CurrencyUnit.EUR); // check security - Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security.getIsin(), is("DE000A3E5CX4")); - assertNull(security.getWkn()); - assertNull(security.getTickerSymbol()); - assertThat(security.getName(), is("Nordex SE Inhaber-Aktien o.N.")); - assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); - - // check delivery inbound (Einlieferung) transaction - PortfolioTransaction entry = (PortfolioTransaction) results.stream().filter(TransactionItem.class::isInstance) - .findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); - - assertThat(entry.getType(), is(PortfolioTransaction.Type.DELIVERY_INBOUND)); - - assertThat(entry.getDateTime(), is(LocalDateTime.parse("2021-07-02T00:00"))); - assertThat(entry.getShares(), is(Values.Share.factorize(130))); - assertThat(entry.getSource(), is("KapitalerhoehungGegenBar01.txt")); - assertNull(entry.getNote()); + assertThat(results, hasItem(security( // + hasIsin("DE000A0D6554"), hasWkn(null), hasTicker(null), // + hasName("Nordex SE Inhaber-Aktien o.N."), // + hasCurrencyCode("EUR")))); - assertThat(entry.getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + inboundDelivery( // + hasDate("2021-07-02T00:00"), hasShares(130.00), // + hasSource("KapitalerhoehungGegenBar01.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); } @Test @@ -3373,41 +3395,30 @@ public void testDepotuebertragEingehend() List errors = new ArrayList<>(); - List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "DepotuebertragEingehend.txt"), - errors); + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "DepotuebertragEingehend.txt"), errors); assertThat(errors, empty()); + assertThat(countSecurities(results), is(1L)); + assertThat(countBuySell(results), is(0L)); + assertThat(countAccountTransactions(results), is(1L)); assertThat(results.size(), is(2)); new AssertImportActions().check(results, CurrencyUnit.EUR); // check security - Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security.getIsin(), is("DE000KBX1006")); - assertNull(security.getWkn()); - assertNull(security.getTickerSymbol()); - assertThat(security.getName(), is("Knorr-Bremse AG Inhaber-Aktien o.N.")); - assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); - - // check delivery inbound (Einlieferung) transaction - PortfolioTransaction entry = (PortfolioTransaction) results.stream().filter(TransactionItem.class::isInstance) - .findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); - - assertThat(entry.getType(), is(PortfolioTransaction.Type.DELIVERY_INBOUND)); - - assertThat(entry.getDateTime(), is(LocalDateTime.parse("2022-11-08T00:00"))); - assertThat(entry.getShares(), is(Values.Share.factorize(13))); - assertThat(entry.getSource(), is("DepotuebertragEingehend.txt")); - assertNull(entry.getNote()); + assertThat(results, hasItem(security( // + hasIsin("DE000KBX1006"), hasWkn(null), hasTicker(null), // + hasName("Knorr-Bremse AG Inhaber-Aktien o.N."), // + hasCurrencyCode("EUR")))); - assertThat(entry.getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + inboundDelivery( // + hasDate("2022-11-08T00:00"), hasShares(13.00), // + hasSource("DepotuebertragEingehend.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); } @Test @@ -3417,41 +3428,30 @@ public void testSpinOff01() List errors = new ArrayList<>(); - List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "SpinOff01.txt"), - errors); + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "SpinOff01.txt"), errors); assertThat(errors, empty()); + assertThat(countSecurities(results), is(1L)); + assertThat(countBuySell(results), is(0L)); + assertThat(countAccountTransactions(results), is(1L)); assertThat(results.size(), is(2)); new AssertImportActions().check(results, CurrencyUnit.EUR); // check security - Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security.getIsin(), is("US64110Y1082")); - assertNull(security.getWkn()); - assertNull(security.getTickerSymbol()); - assertThat(security.getName(), is("Net Lease Office Properties Registered Shares DL -,001")); - assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); - - // check delivery inbound (Einlieferung) transaction - PortfolioTransaction entry = (PortfolioTransaction) results.stream().filter(TransactionItem.class::isInstance) - .findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); - - assertThat(entry.getType(), is(PortfolioTransaction.Type.DELIVERY_INBOUND)); - - assertThat(entry.getDateTime(), is(LocalDateTime.parse("2023-11-03T00:00"))); - assertThat(entry.getShares(), is(Values.Share.factorize(2.564))); - assertThat(entry.getSource(), is("SpinOff01.txt")); - assertNull(entry.getNote()); + assertThat(results, hasItem(security( // + hasIsin("US64110Y1082"), hasWkn(null), hasTicker(null), // + hasName("Net Lease Office Properties Registered Shares DL -,001"), // + hasCurrencyCode("EUR")))); - assertThat(entry.getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + inboundDelivery( // + hasDate("2023-11-03T00:00"), hasShares(2.564), // + hasSource("SpinOff01.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); } @Test @@ -3464,37 +3464,27 @@ public void testUmtausch01() List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Umtausch01.txt"), errors); assertThat(errors, empty()); + assertThat(countSecurities(results), is(1L)); + assertThat(countBuySell(results), is(0L)); + assertThat(countAccountTransactions(results), is(1L)); assertThat(results.size(), is(2)); new AssertImportActions().check(results, CurrencyUnit.EUR); // check security - Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security.getIsin(), is("DE000A3E5CX4")); - assertNull(security.getWkn()); - assertNull(security.getTickerSymbol()); - assertThat(security.getName(), is("Nordex SE Inhaber-Bezugsrechte")); - assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); - - // check delivery outbound (Auslieferung) transaction - PortfolioTransaction entry = (PortfolioTransaction) results.stream().filter(TransactionItem.class::isInstance) - .findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); - - assertThat(entry.getType(), is(PortfolioTransaction.Type.DELIVERY_OUTBOUND)); - - assertThat(entry.getDateTime(), is(LocalDateTime.parse("2021-07-20T00:00"))); - assertThat(entry.getShares(), is(Values.Share.factorize(129.25))); - assertThat(entry.getSource(), is("Umtausch01.txt")); - assertNull(entry.getNote()); + assertThat(results, hasItem(security( // + hasIsin("DE000A3E5CX4"), hasWkn(null), hasTicker(null), // + hasName("Nordex SE Inhaber-Bezugsrechte"), // + hasCurrencyCode("EUR")))); - assertThat(entry.getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + inboundDelivery( // + hasDate("2021-07-20T00:00"), hasShares(129.25), // + hasSource("Umtausch01.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); } @Test @@ -3507,38 +3497,27 @@ public void testUmtausch02() List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Umtausch02.txt"), errors); assertThat(errors, empty()); + assertThat(countSecurities(results), is(1L)); + assertThat(countBuySell(results), is(0L)); + assertThat(countAccountTransactions(results), is(1L)); assertThat(results.size(), is(2)); new AssertImportActions().check(results, CurrencyUnit.EUR); // check security - Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security.getIsin(), is("DE000A0D6554")); - assertNull(security.getWkn()); - assertNull(security.getTickerSymbol()); - assertThat(security.getName(), is("Nordex SE Inhaber-Aktien o.N.")); - assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); - - // check buy sell transaction - BuySellEntry entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSubject(); - - assertThat(entry.getPortfolioTransaction().getType(), is(PortfolioTransaction.Type.BUY)); - assertThat(entry.getAccountTransaction().getType(), is(AccountTransaction.Type.BUY)); - - assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2021-07-15T00:00"))); - assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(47))); - assertThat(entry.getSource(), is("Umtausch02.txt")); - assertNull(entry.getNote()); + assertThat(results, hasItem(security( // + hasIsin("DE000A0D6554"), hasWkn(null), hasTicker(null), // + hasName("Nordex SE Inhaber-Aktien o.N."), // + hasCurrencyCode("EUR")))); - assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(648.90)))); - assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(643.90)))); - assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(5.00)))); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + inboundDelivery( // + hasDate("2021-07-20T00:00"), hasShares(47.00), // + hasSource("Umtausch02.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); } @Test @@ -3649,7 +3628,7 @@ public void testSparplan01() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2019-11-18T00:00"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(0.4534))); assertThat(entry.getSource(), is("Sparplan01.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Ausführung: ff2f-1254")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(25.00)))); @@ -3693,7 +3672,7 @@ public void testSparplan02() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2020-05-04T00:00"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(4.9261))); assertThat(entry.getSource(), is("Sparplan02.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Ausführung: xxxx-xxxx | Sparplan: xxxx-xxxx")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(100.00)))); @@ -3737,7 +3716,7 @@ public void testSparplan03() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2020-05-18T00:00"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(5.5520))); assertThat(entry.getSource(), is("Sparplan03.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Ausführung: 8eac-25ab | Sparplan: 77c8-1b0c")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(150.00)))); @@ -3781,7 +3760,7 @@ public void testSparplan04() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2021-01-18T00:00"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(2.0028))); assertThat(entry.getSource(), is("Sparplan04.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Ausführung: 3609-6874 | Sparplan: 98a0-1d15")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1003.00)))); @@ -3825,7 +3804,7 @@ public void testSparplan05() assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2022-04-19T00:00"))); assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(0.0358))); assertThat(entry.getSource(), is("Sparplan05.txt")); - assertNull(entry.getNote()); + assertThat(entry.getNote(), is("Ausführung: fc69-6fc3 | Sparplan: af97-c4b1")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(19.96)))); @@ -3863,7 +3842,7 @@ public void testSparplan06() assertThat(results, hasItem(purchase( // hasDate("2023-05-16T00:00"), hasShares(0.3367), // hasSource("Sparplan06.txt"), // - hasNote(null), // + hasNote("Execution: e083-506a | Savings plan: 7687-2574"), // hasAmount("EUR", 100.00), hasGrossValue("EUR", 100.00), // hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))); } @@ -3894,7 +3873,7 @@ public void testSparplan07() assertThat(results, hasItem(purchase( // hasDate("2023-06-02T00:00"), hasShares(0.081967), // hasSource("Sparplan07.txt"), // - hasNote(null), // + hasNote("Execution: d008-0f58 | Savings plan: dbc9-ad4d"), // hasAmount("EUR", 25.00), hasGrossValue("EUR", 25.00), // hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))); } @@ -3925,7 +3904,7 @@ public void testSparplan08() assertThat(results, hasItem(purchase( // hasDate("2023-06-02T00:00"), hasShares(0.336491), // hasSource("Sparplan08.txt"), // - hasNote(null), // + hasNote("Execution: 8c26-15e1 | Savings plan: 6af7-5be3"), // hasAmount("EUR", 25.00), hasGrossValue("EUR", 25.00), // hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))); } @@ -3940,64 +3919,42 @@ public void testFusion01() List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Fusion01.txt"), errors); assertThat(errors, empty()); + assertThat(countSecurities(results), is(2L)); + assertThat(countBuySell(results), is(0L)); + assertThat(countAccountTransactions(results), is(2L)); assertThat(results.size(), is(4)); new AssertImportActions().check(results, CurrencyUnit.EUR); // check security - Security security1 = results.stream().filter(SecurityItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security1.getIsin(), is("US79466L3024")); - assertNull(security1.getWkn()); - assertNull(security1.getTickerSymbol()); - assertThat(security1.getName(), is("salesforce.com Inc. Registered Shares DL -,001")); - assertThat(security1.getCurrencyCode(), is(CurrencyUnit.EUR)); - - Security security2 = results.stream().filter(SecurityItem.class::isInstance).skip(1).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security2.getIsin(), is("US83088V1026")); - assertThat(security2.getName(), is("Slack Technologies Inc. Registered Shs Cl.A o.N.")); - assertThat(security2.getCurrencyCode(), is(CurrencyUnit.EUR)); - - // check buy sell transaction - BuySellEntry entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSubject(); - - assertThat(entry.getPortfolioTransaction().getType(), is(PortfolioTransaction.Type.SELL)); - assertThat(entry.getAccountTransaction().getType(), is(AccountTransaction.Type.SELL)); - - assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2021-07-22T00:00"))); - assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(10))); - assertThat(entry.getSource(), is("Fusion01.txt")); - assertNull(entry.getNote()); - - assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(163.68)))); - assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(227.32)))); - assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(55.58 + 3.06 + 5.00)))); - assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - - // check delivery inbound (Einlieferung) transaction - PortfolioTransaction entry2 = (PortfolioTransaction) results.stream().filter(TransactionItem.class::isInstance) - .findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); + assertThat(results, hasItem(security( // + hasIsin("US83088V1026"), hasWkn(null), hasTicker(null), // + hasName("Slack Technologies Inc. Registered Shs Cl.A o.N."), // + hasCurrencyCode("EUR")))); - assertThat(entry2.getType(), is(PortfolioTransaction.Type.DELIVERY_INBOUND)); + assertThat(results, hasItem(security( // + hasIsin("US79466L3024"), hasWkn(null), hasTicker(null), // + hasName("salesforce.com Inc. Registered Shares DL -,001"), // + hasCurrencyCode("EUR")))); - assertThat(entry2.getDateTime(), is(LocalDateTime.parse("2021-07-27T00:00"))); - assertThat(entry2.getShares(), is(Values.Share.factorize(0.776))); - assertThat(entry2.getSource(), is("Fusion01.txt")); - assertNull(entry2.getNote()); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + outboundDelivery( // + hasDate("2021-07-27T00:00"), hasShares(10.00), // + hasSource("Fusion01.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); - assertThat(entry2.getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry2.getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry2.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry2.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + inboundDelivery( // + hasDate("2021-07-27T00:00"), hasShares(0.776), // + hasSource("Fusion01.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); } @Test @@ -4043,38 +4000,62 @@ public void testZwangsuebernahme01() List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Zwangsuebernahme01.txt"), errors); assertThat(errors, empty()); + assertThat(countSecurities(results), is(1L)); + assertThat(countBuySell(results), is(1L)); + assertThat(countAccountTransactions(results), is(0L)); assertThat(results.size(), is(2)); new AssertImportActions().check(results, CurrencyUnit.EUR); // check security - Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security.getIsin(), is("NO0010785967")); - assertNull(security.getWkn()); - assertNull(security.getTickerSymbol()); - assertThat(security.getName(), is("Quantafuel AS Navne-Aksjer NK -,01")); - assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); + assertThat(results, hasItem(security( // + hasIsin("NO0010785967"), hasWkn(null), hasTicker(null), // + hasName("Quantafuel AS Navne-Aksjer NK -,01"), // + hasCurrencyCode("NOK")))); // check buy sell transaction - BuySellEntry entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSubject(); + assertThat(results, hasItem(sale( // + hasDate("2024-02-12T00:00"), hasShares(17.00), // + hasSource("Zwangsuebernahme01.txt"), // + hasNote(null), // + hasAmount("EUR", 9.54), hasGrossValue("EUR", 9.54), // + hasForexGrossValue("NOK", 108.47), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))); + } - assertThat(entry.getPortfolioTransaction().getType(), is(PortfolioTransaction.Type.SELL)); - assertThat(entry.getAccountTransaction().getType(), is(AccountTransaction.Type.SELL)); + @Test + public void testZwangsuebernahme01WithSecurityInEUR() + { + Security security = new Security("Quantafuel AS Navne-Aksjer NK -,01", CurrencyUnit.EUR); + security.setIsin("NO0010785967"); - assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2024-02-12T00:00"))); - assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(17.00))); - assertThat(entry.getSource(), is("Zwangsuebernahme01.txt")); - assertNull(entry.getNote()); + Client client = new Client(); + client.addSecurity(security); - assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(9.54)))); - assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(9.54)))); - assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + TradeRepublicPDFExtractor extractor = new TradeRepublicPDFExtractor(client); + + List errors = new ArrayList<>(); + + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Zwangsuebernahme01.txt"), errors); + + assertThat(errors, empty()); + assertThat(countSecurities(results), is(0L)); + assertThat(countBuySell(results), is(1L)); + assertThat(countAccountTransactions(results), is(0L)); + assertThat(results.size(), is(1)); + new AssertImportActions().check(results, CurrencyUnit.EUR); + + // check buy sell transaction + assertThat(results, hasItem(sale( // + hasDate("2024-02-12T00:00"), hasShares(17.00), // + hasSource("Zwangsuebernahme01.txt"), // + hasNote(null), // + hasAmount("EUR", 9.54), hasGrossValue("EUR", 9.54), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00), // + check(tx -> { + CheckCurrenciesAction c = new CheckCurrenciesAction(); + Status s = c.process((PortfolioTransaction) tx, new Portfolio()); + assertThat(s, is(Status.OK_STATUS)); + })))); } @Test @@ -4087,55 +4068,27 @@ public void testSteuerlicherUmtausch01() List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "SteuerlicherUmtausch01.txt"), errors); assertThat(errors, empty()); - assertThat(results.size(), is(3)); + assertThat(countSecurities(results), is(1L)); + assertThat(countBuySell(results), is(0L)); + assertThat(countAccountTransactions(results), is(1L)); + assertThat(results.size(), is(2)); new AssertImportActions().check(results, CurrencyUnit.EUR); // check security - Security security1 = results.stream().filter(SecurityItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security1.getIsin(), is("FR0010754135")); - assertNull(security1.getWkn()); - assertNull(security1.getTickerSymbol()); - assertThat(security1.getName(), is("AMUN.GOV.BD EO BR.IG 1-3 U.ETF Actions au Porteur o.N.")); - assertThat(security1.getCurrencyCode(), is(CurrencyUnit.EUR)); - - // check delivery outbound (Auslieferung) transaction - PortfolioTransaction entry = (PortfolioTransaction) results.stream().filter(TransactionItem.class::isInstance) - .findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); - - assertThat(entry.getType(), is(PortfolioTransaction.Type.DELIVERY_OUTBOUND)); - assertThat(entry.getDateTime(), is(LocalDateTime.parse("2023-12-07T00:00"))); - assertThat(entry.getShares(), is(Values.Share.factorize(50))); - assertThat(entry.getSource(), is("SteuerlicherUmtausch01.txt")); - assertNull(entry.getNote()); - - assertThat(entry.getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - - // check transaction - AccountTransaction transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance).skip(1) - .findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); - - assertThat(transaction.getType(), is(AccountTransaction.Type.TAXES)); - assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2023-12-01T00:00"))); - assertThat(transaction.getShares(), is(Values.Share.factorize(50))); - assertThat(transaction.getSource(), is("SteuerlicherUmtausch01.txt")); - assertNull(transaction.getNote()); + assertThat(results, hasItem(security( // + hasIsin("FR0010754135"), hasWkn(null), hasTicker(null), // + hasName("AMUN.GOV.BD EO BR.IG 1-3 U.ETF Actions au Porteur o.N."), // + hasCurrencyCode("EUR")))); - assertThat(transaction.getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(23.07)))); - assertThat(transaction.getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(23.07)))); - assertThat(transaction.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(transaction.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + outboundDelivery( // + hasDate("2023-12-07T00:00"), hasShares(50.00), // + hasSource("SteuerlicherUmtausch01.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); } @Test @@ -4148,36 +4101,27 @@ public void testSteuerlicherUmtausch02() List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "SteuerlicherUmtausch02.txt"), errors); assertThat(errors, empty()); + assertThat(countSecurities(results), is(1L)); + assertThat(countBuySell(results), is(0L)); + assertThat(countAccountTransactions(results), is(1L)); assertThat(results.size(), is(2)); new AssertImportActions().check(results, CurrencyUnit.EUR); // check security - Security security1 = results.stream().filter(SecurityItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security1.getIsin(), is("LU1650487413")); - assertNull(security1.getWkn()); - assertNull(security1.getTickerSymbol()); - assertThat(security1.getName(), is("MUL-LYX.EO Gov.Bd 1-3Y(DR)U.E. Nam.-An. Acc o.N.")); - assertThat(security1.getCurrencyCode(), is(CurrencyUnit.EUR)); - - // check delivery inbound transaction - PortfolioTransaction entry = (PortfolioTransaction) results.stream().filter(TransactionItem.class::isInstance) - .findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); - - assertThat(entry.getType(), is(PortfolioTransaction.Type.DELIVERY_INBOUND)); - assertThat(entry.getDateTime(), is(LocalDateTime.parse("2023-12-07T00:00"))); - assertThat(entry.getShares(), is(Values.Share.factorize(67.541))); - assertThat(entry.getSource(), is("SteuerlicherUmtausch02.txt")); - assertThat(entry.getNote(), is("Einstandskurs fehlt")); + assertThat(results, hasItem(security( // + hasIsin("LU1650487413"), hasWkn(null), hasTicker(null), // + hasName("MUL-LYX.EO Gov.Bd 1-3Y(DR)U.E. Nam.-An. Acc o.N."), // + hasCurrencyCode("EUR")))); - assertThat(entry.getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + inboundDelivery( // + hasDate("2023-12-07T00:00"), hasShares(67.541), // + hasSource("SteuerlicherUmtausch02.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); } @Test @@ -4190,65 +4134,42 @@ public void testVergleichsverfahren01() List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Vergleichsverfahren01.txt"), errors); assertThat(errors, empty()); + assertThat(countSecurities(results), is(2L)); + assertThat(countBuySell(results), is(0L)); + assertThat(countAccountTransactions(results), is(2L)); assertThat(results.size(), is(4)); new AssertImportActions().check(results, CurrencyUnit.EUR); // check security - Security security1 = results.stream().filter(SecurityItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security1.getIsin(), is("GB00BH0P3Z91")); - assertNull(security1.getWkn()); - assertNull(security1.getTickerSymbol()); - assertThat(security1.getName(), is("BHP Group PLC Registered Shares DL -,50")); - assertThat(security1.getCurrencyCode(), is(CurrencyUnit.EUR)); - - Security security2 = results.stream().filter(SecurityItem.class::isInstance).skip(1).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security2.getIsin(), is("AU000000BHP4")); - assertNull(security2.getWkn()); - assertNull(security2.getTickerSymbol()); - assertThat(security2.getName(), is("BHP Group Ltd. Registered Shares DL -,50")); - assertThat(security2.getCurrencyCode(), is(CurrencyUnit.EUR)); - - // check delivery inbound (Einlieferung) transaction - PortfolioTransaction entry = (PortfolioTransaction) results.stream().filter(TransactionItem.class::isInstance) - .findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); - - assertThat(entry.getType(), is(PortfolioTransaction.Type.DELIVERY_OUTBOUND)); - - assertThat(entry.getDateTime(), is(LocalDateTime.parse("2022-01-27T00:00"))); - assertThat(entry.getShares(), is(Values.Share.factorize(8))); - assertThat(entry.getSource(), is("Vergleichsverfahren01.txt")); - assertNull(entry.getNote()); - - assertThat(entry.getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - - // check delivery inbound (Einlieferung) transaction - entry = (PortfolioTransaction) results.stream().filter(TransactionItem.class::isInstance) - .skip(1).findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); + assertThat(results, hasItem(security( // + hasIsin("GB00BH0P3Z91"), hasWkn(null), hasTicker(null), // + hasName("BHP Group PLC Registered Shares DL -,50"), // + hasCurrencyCode("EUR")))); - assertThat(entry.getType(), is(PortfolioTransaction.Type.DELIVERY_INBOUND)); + assertThat(results, hasItem(security( // + hasIsin("AU000000BHP4"), hasWkn(null), hasTicker(null), // + hasName("BHP Group Ltd. Registered Shares DL -,50"), // + hasCurrencyCode("EUR")))); - assertThat(entry.getDateTime(), is(LocalDateTime.parse("2022-01-27T00:00"))); - assertThat(entry.getShares(), is(Values.Share.factorize(8))); - assertThat(entry.getSource(), is("Vergleichsverfahren01.txt")); - assertNull(entry.getNote()); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + outboundDelivery( // + hasDate("2022-01-27T00:00"), hasShares(8.00), // + hasSource("Vergleichsverfahren01.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); - assertThat(entry.getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + inboundDelivery( // + hasDate("2022-01-27T00:00"), hasShares(8.00), // + hasSource("Vergleichsverfahren01.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); } @Test @@ -4261,65 +4182,42 @@ public void testTitelumtausch01() List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Titelumtausch01.txt"), errors); assertThat(errors, empty()); + assertThat(countSecurities(results), is(2L)); + assertThat(countBuySell(results), is(0L)); + assertThat(countAccountTransactions(results), is(2L)); assertThat(results.size(), is(4)); new AssertImportActions().check(results, CurrencyUnit.EUR); // check security - Security security1 = results.stream().filter(SecurityItem.class::isInstance).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security1.getIsin(), is("GB00B03MLX29")); - assertNull(security1.getWkn()); - assertNull(security1.getTickerSymbol()); - assertThat(security1.getName(), is("Shell PLC Reg. Shares Class A EO -,07")); - assertThat(security1.getCurrencyCode(), is(CurrencyUnit.EUR)); - - Security security2 = results.stream().filter(SecurityItem.class::isInstance).skip(1).findFirst() - .orElseThrow(IllegalArgumentException::new).getSecurity(); - assertThat(security2.getIsin(), is("GB00BP6MXD84")); - assertNull(security2.getWkn()); - assertNull(security2.getTickerSymbol()); - assertThat(security2.getName(), is("Shell PLC Reg. Shares Class EO -,07")); - assertThat(security2.getCurrencyCode(), is(CurrencyUnit.EUR)); - - // check delivery inbound (Einlieferung) transaction - PortfolioTransaction entry = (PortfolioTransaction) results.stream().filter(TransactionItem.class::isInstance) - .findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); - - assertThat(entry.getType(), is(PortfolioTransaction.Type.DELIVERY_OUTBOUND)); - - assertThat(entry.getDateTime(), is(LocalDateTime.parse("2022-02-01T00:00"))); - assertThat(entry.getShares(), is(Values.Share.factorize(25))); - assertThat(entry.getSource(), is("Titelumtausch01.txt")); - assertNull(entry.getNote()); - - assertThat(entry.getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - - // check delivery inbound (Einlieferung) transaction - entry = (PortfolioTransaction) results.stream().filter(TransactionItem.class::isInstance) - .skip(1).findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); + assertThat(results, hasItem(security( // + hasIsin("GB00B03MLX29"), hasWkn(null), hasTicker(null), // + hasName("Shell PLC Reg. Shares Class A EO -,07"), // + hasCurrencyCode("EUR")))); - assertThat(entry.getType(), is(PortfolioTransaction.Type.DELIVERY_INBOUND)); + assertThat(results, hasItem(security( // + hasIsin("GB00BP6MXD84"), hasWkn(null), hasTicker(null), // + hasName("Shell PLC Reg. Shares Class EO -,07"), // + hasCurrencyCode("EUR")))); - assertThat(entry.getDateTime(), is(LocalDateTime.parse("2022-02-01T00:00"))); - assertThat(entry.getShares(), is(Values.Share.factorize(25))); - assertThat(entry.getSource(), is("Titelumtausch01.txt")); - assertNull(entry.getNote()); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + outboundDelivery( // + hasDate("2022-02-01T00:00"), hasShares(25.00), // + hasSource("Titelumtausch01.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); - assertThat(entry.getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); - assertThat(entry.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + // check unsupported transaction + assertThat(results, hasItem(withFailureMessage( // + Messages.MsgErrorTransactionTypeNotSupported, // + inboundDelivery( // + hasDate("2022-02-01T00:00"), hasShares(25.00), // + hasSource("Titelumtausch01.txt"), // + hasNote(null), // + hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), // + hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))))); } @Test diff --git a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/TradeRepublicPDFExtractor.java b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/TradeRepublicPDFExtractor.java index ecb23d4a9c..a25cdf82d4 100644 --- a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/TradeRepublicPDFExtractor.java +++ b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/TradeRepublicPDFExtractor.java @@ -4,9 +4,6 @@ import static name.abuchen.portfolio.util.TextUtil.trim; import java.math.BigDecimal; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import name.abuchen.portfolio.Messages; import name.abuchen.portfolio.datatransfer.ExtrExchangeRate; @@ -31,19 +28,16 @@ public TradeRepublicPDFExtractor(Client client) addBankIdentifier("TRADE REPUBLIC"); addBuySellTransaction(); - addBuySellCryptoTransaction(); addSellWithNegativeAmountTransaction(); - addLiquidationTransaction(); + addBuySellCryptoTransaction(); addDividendeTransaction(); + addAdvanceTaxTransaction(); addAccountStatementTransaction(); - addTaxStatementTransaction(); - addUSTaxStatementTransaction(); + addTaxesStatementTransaction(); + addTaxesCorrectionStatementTransaction(); addDepositStatementTransaction(); addInterestStatementTransaction(); - addAdvanceTaxTransaction(); - addCaptialReductionTransaction(); - addDeliveryInOutBoundTransaction(); - addTaxInOutBoundTransaction(); + addNonImportableTransaction(); } @Override @@ -57,11 +51,14 @@ private void addBuySellTransaction() DocumentType type = new DocumentType("(WERTPAPIERABRECHNUNG" // + "|WERTPAPIERABRECHNUNG SPARPLAN" // + "|WERTPAPIERABRECHNUNG ROUND UP" // + + "|WERTPAPIERABRECHNUNG SAVEBACK" // + "|SECURITIES SETTLEMENT SAVINGS PLAN" // + "|SECURITIES SETTLEMENT" // + "|REINVESTIERUNG" // + "|INVESTISSEMENT" - + "|REGOLAMENTO TITOLI)", // + + "|REGOLAMENTO TITOLI" + + "|ZWANGS.BERNAHME" + + "|TILGUNG)", // "(ABRECHNUNG CRYPTOGESCH.FT|CRYPTO SPARPLAN)"); this.addDocumentTyp(type); @@ -89,10 +86,14 @@ private void addBuySellTransaction() + "|Sparplanausf.hrung" // + "|SAVINGS PLAN" // + "|Ex.cution de l.investissement programm." // - + "|REINVESTIERUNG))" // - + " .*$") // + + "|REINVESTIERUNG" // + + "|ZWANGS.BERNAHME" + + "|TILGUNG))" // + + ".*$") // .assign((t, v) -> { - if ("Verkauf".equals(v.get("type"))) + if ("Verkauf".equals(v.get("type")) // + || "ZWANGSÜBERNAHME".equals(v.get("type")) // + || "TILGUNG".equals(v.get("type"))) t.setType(PortfolioTransaction.Type.SELL); }) @@ -110,6 +111,24 @@ private void addBuySellTransaction() .match("^(ISIN([\\s])?:([\\s])?)?(?[A-Z]{2}[A-Z0-9]{9}[0-9])$") // .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))), // @formatter:off + // 1 Ausbuchung Quantafuel AS 17 Stk. + // Navne-Aksjer NK -,01 + // NO0010785967 + // 1 Barausgleich 108,46 NOK + // + // 1 Tilgung HSBC Trinkaus & Burkhardt AG 700 Stk. + // TurboC O.End Linde + // DE000TT22GS8 + // 1 Kurswert 0,70 EUR + // @formatter:on + section -> section // + .attributes("name", "nameContinued", "isin", "currency") // + .match("^[\\d] (Ausbuchung|Tilgung) (?.*) [\\.,\\d]+ Stk\\.$") + .match("^(?.*)$") + .match("^(?[A-Z]{2}[A-Z0-9]{9}[0-9])$") + .match("^[\\d] (Barausgleich|Kurswert) [\\.,\\d]+ (?[\\w]{3})$") // + .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))), + // @formatter:off // This is for the reinvestment of dividends // We pick the second // @@ -147,6 +166,14 @@ private void addBuySellTransaction() .match("^.* (?[\\.,\\d]+) Pcs\\. .*$") // .assign((t, v) -> t.setShares(asShares(v.get("shares"), "en", "US"))), // @formatter:off + // 1 Ausbuchung Quantafuel AS 17 Stk. + // 1 Tilgung HSBC Trinkaus & Burkhardt AG 700 Stk. + // @formatter:on + section -> section // + .attributes("shares") // + .match("^[\\d] (Ausbuchung|Tilgung) .* (?[\\.,\\d]+) Stk\\.$") // + .assign((t, v) -> t.setShares(asShares(v.get("shares")))), + // @formatter:off // 1 Reinvestierung Vodafone Group PLC 699 Stk. // 2 Reinvestierung Vodafone Group PLC 22 Stk. // @formatter:on @@ -184,14 +211,14 @@ private void addBuySellTransaction() .attributes("date") // .match("^(Sparplanausf.hrung|Savings plan) .* (?([\\d]{2}\\.[\\d]{2}\\.[\\d]{4}|[\\d]{4}\\-[\\d]{2}\\-[\\d]{2})) .*$") // .assign((t, v) -> t.setDate(asDate(v.get("date")))), - // @formatter:off // Ausführung von Round up am 09.02.2024 an der Lang & Schwarz Exchange. + // Ausführung von Saveback am 04.03.2024 an der Lang & Schwarz Exchange. // @formatter:on - section -> section.attributes("date") // - .match("^Ausf.hrung von Round up .* (?([\\d]{2}\\.[\\d]{2}\\.[\\d]{4}|[\\d]{4}\\-[\\d]{2}\\-[\\d]{2})) .*$") // + section -> section // + .attributes("date") // + .match("^Ausf.hrung von (Round up|Saveback) .* (?([\\d]{2}\\.[\\d]{2}\\.[\\d]{4}|[\\d]{4}\\-[\\d]{2}\\-[\\d]{2})) .*$") // .assign((t, v) -> t.setDate(asDate(v.get("date")))), - // @formatter:off // This is for the reinvestment of dividends // @@ -244,6 +271,18 @@ private void addBuySellTransaction() t.setCurrencyCode(asCurrencyCode(v.get("currency"))); } }), + // @formatter:off + // SUMME 0,70 EUR + // SUMME 33,19 EUR + // @formatter:on + section -> section // + .attributes("amount", "currency") // + .match("^SUMME (?[\\.,\\d]+) (?[\\w]{3})$") // + .match("^SUMME [\\.,\\d]+ [\\w]{3}$") // + .assign((t, v) -> { + t.setAmount(asAmount(v.get("amount"))); + t.setCurrencyCode(asCurrencyCode(v.get("currency"))); + }), // In case there is no tax, // only one line with "GESAMT" // exists and we need to grab data from @@ -255,87 +294,199 @@ private void addBuySellTransaction() // @formatter:on section -> section // .attributes("amount", "currency") // - .match("^(GESAMT|TOTAL|TOTALE) (\\-)?(?[\\.,\\d]+) (?[\\w]{3})$") // + .match("^(GESAMT|TOTAL|TOTALE|) (\\-)?(?[\\.,\\d]+) (?[\\w]{3})$") // .assign((t, v) -> { t.setCurrencyCode(asCurrencyCode(v.get("currency"))); t.setAmount(asAmount(v.get("amount"))); })) - // @formatter:off - // This is for the reinvestment of dividends. - // We subtract the second amount from the first amount and set this. - // - // 1 Bruttoertrag 26,80 GBP - // 2 Barausgleich 0,37 GBP - // Zwischensumme 0,85267 EUR/GBP 0,44 EUR - // @formatter:on - .section("gross", "currency", "cashCompensation", "cashCompensationCurrency", "exchangeRate", "baseCurrency", "termCurrency").optional() // - .match("^[\\d] Bruttoertrag (?[\\.,\\d]+) (?[\\w]{3})$") // - .match("^[\\d] Barausgleich (?[\\.,\\d]+) (?[\\w]{3})$") // - .match("^Zwischensumme (?[\\.,\\d]+) (?[\\w]{3})\\/(?[\\w]{3}) [\\.,\\d]+ [\\w]{3}$") // - .assign((t, v) -> { - Money grossValueBasis = Money.of(asCurrencyCode(v.get("currency")), asAmount(v.get("gross"))); - Money cashCompensationValue = Money.of(asCurrencyCode(v.get("cashCompensationCurrency")), asAmount(v.get("cashCompensation"))); - - ExtrExchangeRate rate = asExchangeRate(v); - type.getCurrentContext().putType(rate); - - Money fxGross = grossValueBasis.subtract(cashCompensationValue); - Money gross = rate.convert(rate.getBaseCurrency(), fxGross); + .optionalOneOf( // + // @formatter:off + // This is for the reinvestment of dividends. + // We subtract the second amount from the first amount and set this. + // + // 1 Bruttoertrag 26,80 GBP + // 2 Barausgleich 0,37 GBP + // Zwischensumme 0,85267 EUR/GBP 0,44 EUR + // @formatter:on + section -> section + .attributes("gross", "currency", "cashCompensation", "cashCompensationCurrency", "exchangeRate", "baseCurrency", "termCurrency") + .match("^[\\d] Bruttoertrag (?[\\.,\\d]+) (?[\\w]{3})$") // + .match("^[\\d] Barausgleich (?[\\.,\\d]+) (?[\\w]{3})$") // + .match("^Zwischensumme (?[\\.,\\d]+) (?[\\w]{3})\\/(?[\\w]{3}) [\\.,\\d]+ [\\w]{3}$") // + .assign((t, v) -> { + Money grossValueBasis = Money.of(asCurrencyCode(v.get("currency")), asAmount(v.get("gross"))); + Money cashCompensationValue = Money.of(asCurrencyCode(v.get("cashCompensationCurrency")), asAmount(v.get("cashCompensation"))); + + ExtrExchangeRate rate = asExchangeRate(v); + type.getCurrentContext().putType(rate); + + Money fxGross = grossValueBasis.subtract(cashCompensationValue); + Money gross = rate.convert(rate.getBaseCurrency(), fxGross); + + t.setAmount(gross.getAmount()); + t.setCurrencyCode(asCurrencyCode(gross.getCurrencyCode())); + + checkAndSetGrossUnit(gross, fxGross, t, type.getCurrentContext()); + }), + // @formatter:off + // 1 Barausgleich 108,46 NOK + // Zwischensumme 11,370137 EUR/NOK 9,54 EUR + // @formatter:on + section -> section + .attributes("exchangeRate", "baseCurrency", "termCurrency", "gross") + .match("^Zwischensumme (?[\\.,\\d]+) (?[\\w]{3})\\/(?[\\w]{3}) (?[\\.,\\d]+) [\\w]{3}$") + .assign((t, v) -> { + ExtrExchangeRate rate = asExchangeRate(v); + type.getCurrentContext().putType(rate); - t.setAmount(gross.getAmount()); - t.setCurrencyCode(asCurrencyCode(gross.getCurrencyCode())); + Money gross = Money.of(rate.getBaseCurrency(), asAmount(v.get("gross"))); + Money fxGross = rate.convert(rate.getTermCurrency(), gross); - checkAndSetGrossUnit(gross, fxGross, t, type.getCurrentContext()); - }) + checkAndSetGrossUnit(gross, fxGross, t, type.getCurrentContext()); + })) + // @formatter:off // If the tax is optimized, this is a tax refund - // transaction and we subtract this from the amount and - // reset this. + // transaction and we subtract this from the amount and reset this. + // @formatter:on // @formatter:off // Kapitalertragssteuer Optimierung 20,50 EUR // Kapitalertragsteuer Optimierung 4,56 EUR // @formatter:on - .section("sign", "amount", "currency").optional() // - .match("^Kapitalertrags(s)?teuer Optimierung(?[\\s\\-]{1,2})(?[\\.,\\d]+) (?[\\w]{3})$") // + .section("amount", "currency").optional() // + .match("^([\\d] )?Kapitalertrags(s)?teuer Optimierung (?[\\.,\\d]+) (?[\\w]{3})$") // + .match("^(GESAMT|TOTAL|TOTALE|) (\\-)?[\\.,\\d]+ [\\w]{3}$") // .assign((t, v) -> { Money tax = Money.of(asCurrencyCode(v.get("currency")), asAmount(v.get("amount"))); - - if ("-".equals(trim(v.get("sign")))) - t.setMonetaryAmount(t.getPortfolioTransaction().getMonetaryAmount().add(tax)); - else - t.setMonetaryAmount(t.getPortfolioTransaction().getMonetaryAmount().subtract(tax)); + t.setMonetaryAmount(t.getPortfolioTransaction().getMonetaryAmount().subtract(tax)); }) // @formatter:off // Solidaritätszuschlag Optimierung 1,13 EUR // @formatter:on - .section("sign", "amount", "currency").optional() // - .match("^Solidarit.tszuschlag Optimierung(?[\\s\\-]{1,2})(?[\\.,\\d]+) (?[\\w]{3})$") // + .section("amount", "currency").optional() // + .match("^([\\d] )?Solidarit.tszuschlag Optimierung (?[\\.,\\d]+) (?[\\w]{3})$") // + .match("^(GESAMT|TOTAL|TOTALE|) (\\-)?[\\.,\\d]+ [\\w]{3}$") // .assign((t, v) -> { Money tax = Money.of(asCurrencyCode(v.get("currency")), asAmount(v.get("amount"))); - - if ("-".equals(trim(v.get("sign")))) - t.setMonetaryAmount(t.getPortfolioTransaction().getMonetaryAmount().add(tax)); - else - t.setMonetaryAmount(t.getPortfolioTransaction().getMonetaryAmount().subtract(tax)); + t.setMonetaryAmount(t.getPortfolioTransaction().getMonetaryAmount().subtract(tax)); }) // @formatter:off // Kirchensteuer Optimierung 9,84 EUR // @formatter:on - .section("sign", "amount", "currency").optional() // - .match("^Kirchensteuer Optimierung(?[\\s\\-]{1,2})(?[\\.,\\d]+) (?[\\w]{3})$") // + .section("amount", "currency").optional() // + .match("^([\\d] )?Kirchensteuer Optimierung (?[\\.,\\d]+) (?[\\w]{3})$") // + .match("^(GESAMT|TOTAL|TOTALE|) (\\-)?[\\.,\\d]+ [\\w]{3}$") // .assign((t, v) -> { Money tax = Money.of(asCurrencyCode(v.get("currency")), asAmount(v.get("amount"))); - - if ("-".equals(trim(v.get("sign")))) - t.setMonetaryAmount(t.getPortfolioTransaction().getMonetaryAmount().add(tax)); - else - t.setMonetaryAmount(t.getPortfolioTransaction().getMonetaryAmount().subtract(tax)); + t.setMonetaryAmount(t.getPortfolioTransaction().getMonetaryAmount().subtract(tax)); }) + .optionalOneOf( // + // @formatter:off + // D 12345 Stadt ORDER dead-beef + // @formatter:on + section -> section // + .attributes("note") // + .match("^.*ORDER (?.*\\-.*)$") // + .assign((t, v) -> t.setNote("Order: " + trim(v.get("note")))), + // @formatter:off + // 23537 DCrFCrYea AUSFÜHRUNG 5437-f7f5 + // @formatter:on + section -> section // + .attributes("note") // + .match("^.*AUSF.HRUNG (?.*\\-.*)$") // + .assign((t, v) -> t.setNote("Ausführung: " + trim(v.get("note")))), + // @formatter:off + // [ZIP CODE] [CITY] EXÉCUTION cee1-2d00 + // @formatter:on + section -> section // + .attributes("note") // + .match("^.*EXÉCUTION (?.*\\-.*)$") // + .assign((t, v) -> t.setNote("Exécution : " + trim(v.get("note")))), + // @formatter:off + // 131 56 rwMMPGwX EXECUTION d008-0f58 + // @formatter:on + section -> section // + .attributes("note") // + .match("^.*EXECUTION (?.*\\-.*)$") // + .assign((t, v) -> t.setNote("Execution: " + trim(v.get("note")))), + // @formatter:off + // 51670 cyuzKxpHr ORDINE cY43-6m6l + // @formatter:on + section -> section // + .attributes("note") // + .match("^.*ORDINE (?.*\\-.*)$") // + .assign((t, v) -> t.setNote("Ordine: " + trim(v.get("note"))))) + + .optionalOneOf( // + // @formatter:off + // SAVEBACK B2C4-n64q + // @formatter:on + section -> section // + .attributes("note") // + .match("^SAVEBACK (?.*\\-.*)$") // + .assign((t, v) -> t.setNote(concatenate(t.getNote(), trim(v.get("note")), " | Saveback: "))), + // @formatter:off + // SPARPLAN y646-a753 + // @formatter:on + section -> section // + .attributes("note") // + .match("^SPARPLAN (?.*\\-.*)$") // + .assign((t, v) -> t.setNote(concatenate(t.getNote(), trim(v.get("note")), " | Sparplan: "))), + // @formatter:off + // SAVINGS PLAN 6af7-5be3 + // @formatter:on + section -> section // + .attributes("note") // + .match("^SAVINGS PLAN (?.*\\-.*)$") // + .assign((t, v) -> t.setNote(concatenate(t.getNote(), trim(v.get("note")), " | Savings plan: "))), + // @formatter:off + // ROUND UP 42c2-50a7 + // @formatter:on + section -> section // + .attributes("note") // + .match("^ROUND UP (?.*\\-.*)$") // + .assign((t, v) -> t.setNote(concatenate(t.getNote(), trim(v.get("note")), " | Round Up: "))), + // @formatter:off + // PROGRAMMÉ eea2-4c8b + // @formatter:on + section -> section // + .attributes("note") // + .match("^PROGRAMM. (?.*\\-.*)$") // + .assign((t, v) -> t.setNote(concatenate(t.getNote(), trim(v.get("note")), " | Programmé : "))), + // @formatter:off + // EXÉCUTION 4A66-g597 + // @formatter:on + section -> section // + .attributes("note") // + .match("^EXÉCUTION (?.*\\-.*)$") // + .assign((t, v) -> t.setNote(concatenate(t.getNote(), trim(v.get("note")), " | Exécution : "))), + // @formatter:off + // EXECUTION 4A66-g597 + // @formatter:on + section -> section // + .attributes("note") // + .match("^EXECUTION (?.*\\-.*)$") // + .assign((t, v) -> t.setNote(concatenate(t.getNote(), trim(v.get("note")), " | Execution: "))), + // @formatter:off + // ESECUZIONE V711-7789 + // @formatter:on + section -> section // + .attributes("note") // + .match("^ESECUZIONE (?.*\\-.*)$") // + .assign((t, v) -> t.setNote(concatenate(t.getNote(), trim(v.get("note")), " | Esecuzione: "))), + // @formatter:off + // AUSFÜHRUNG 4019-2100 + // @formatter:on + section -> section // + .attributes("note") // + .match("^AUSF.HRUNG (?.*\\-.*)$") // + .assign((t, v) -> t.setNote(concatenate(t.getNote(), trim(v.get("note")), " | Ausführung: ")))) + .wrap(t -> { // If we have multiple entries in the document, // then the "negative" flag must be removed. @@ -349,6 +500,103 @@ private void addBuySellTransaction() addBuySellTaxReturnBlock(type); } + private void addSellWithNegativeAmountTransaction() + { + DocumentType type = new DocumentType("(WERTPAPIERABRECHNUNG" // + + "|WERTPAPIERABRECHNUNG SPARPLAN" // + + "|SECURITIES SETTLEMENT SAVINGS PLAN" // + + "|SECURITIES SETTLEMENT" // + + "|REINVESTIERUNG" // + + "|INVESTISSEMENT)", // + "(ABRECHNUNG CRYPTOGESCH.FT|CRYPTO SPARPLAN)"); + this.addDocumentTyp(type); + + Transaction pdfTransaction = new Transaction<>(); + + Block firstRelevantLine = new Block("^(.*\\-Order )?Verkauf.*$"); + type.addBlock(firstRelevantLine); + firstRelevantLine.set(pdfTransaction); + + pdfTransaction // + + .subject(() -> { + AccountTransaction accountTransaction = new AccountTransaction(); + accountTransaction.setType(AccountTransaction.Type.FEES); + return accountTransaction; + }) + + // @formatter:off + // Clinuvel Pharmaceuticals Ltd. 80 Stk. 22,82 EUR 1.825,60 EUR + // Registered Shares o.N. + // AU000000CUV3 + // ISIN: DE000A3H23V7 + // @formatter:on + .section("name", "currency", "nameContinued", "isin") // + .match("^(?.*) [\\.,\\d]+ Stk\\. [\\.,\\d]+ (?[\\w]{3}) [\\.,\\d]+ [\\w]{3}$") // + .match("^(?.*)$") // + .match("^(ISIN: )?(?[A-Z]{2}[A-Z0-9]{9}[0-9])$") // + .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))) + + // @formatter:off + // Clinuvel Pharmaceuticals Ltd. 80 Stk. 22,82 EUR 1.825,60 EUR + // @formatter:on + .section("shares") // + .match("^.* (?[\\.,\\d]+) Stk\\. [\\.,\\d]+ [\\w]{3} [\\.,\\d]+ [\\w]{3}$") // + .assign((t, v) -> t.setShares(asShares(v.get("shares")))) + + // @formatter:off + // Market-Order Verkauf am 18.06.2019, um 17:50 Uhr an der Lang & Schwarz Exchange. + // Stop-Market-Order Verkauf am 10.06.2020, um 11:42 Uhr. + // Limit-Order Verkauf am 21.07.2020, um 09:30 Uhr an der Lang & Schwarz Exchange. + // Verkauf am 26.02.2021, um 11:44 Uhr. + // @formatter:on + .section("date", "time") // + .match("^((Limit|Stop-Market|Market)-Order )?(Kauf|Verkauf) .* (?([\\d]{2}\\.[\\d]{2}\\.[\\d]{4}|[\\d]{4}\\-[\\d]{2}\\-[\\d]{2})), um (?