From f5085822662b34cdff8cc6eaa45e36b90b571953 Mon Sep 17 00:00:00 2001 From: Nirus2000 Date: Sun, 24 Mar 2024 10:02:40 +0100 Subject: [PATCH 01/40] Modify Trade Republic PDF-Importer to support new transaction https://forum.portfolio-performance.info/t/pdf-import-von-trade-republic/5107/489 Merge/Remove addLiquidationTransaction() Merge/Remove addCaptialReductionTransaction() Merge/Remove addCaptialReductionTransaction() Remove addDeliveryInOutBoundTransaction() Remove addTaxStatementTransaction(); Add addNonImportableTransaction() Add notes Fix testWertpapierVerkauf08() Rename test file USQuellensteuer01.txt to Steuerkorrektur01.txt There were previously some imports that were not solved correctly. For example, a SPIN-OFF was posted as a posting at 0.00. Also inbound/outbound deliveries which have no currency or amount are to be marked as NonImportable. The same applies to fiscal exchanges. Once without tax collection but delivery or derecognition has taken place without the currency of the security and taxes may have been offset. All in all, these are all special cases which we cannot import correctly and clearly. --- .../datatransfer/pdf/traderepublic/Kauf13.txt | 28 + ...llensteuer01.txt => Steuerkorrektur01.txt} | 0 .../TradeRepublicPDFExtractorTest.java | 750 ++++---- .../pdf/TradeRepublicPDFExtractor.java | 1658 ++++++++--------- 4 files changed, 1128 insertions(+), 1308 deletions(-) create mode 100644 name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/Kauf13.txt rename name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/traderepublic/{USQuellensteuer01.txt => Steuerkorrektur01.txt} (100%) 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 (?