From fd2954bc26a8c660c01eb234579d7df56eea00f5 Mon Sep 17 00:00:00 2001 From: Nirus2000 Date: Tue, 28 Mar 2023 07:14:45 +0200 Subject: [PATCH] Modify sbroker PDF-Importer to support new transaction https://forum.portfolio-performance.info/t/pdf-import-von-s-broker-sparkasse/5195/58 https://forum.portfolio-performance.info/t/pdf-import-von-s-broker-sparkasse/5195/60 --- .../datatransfer/pdf/sbroker/Dividende10.txt | 87 +++++++++ .../pdf/sbroker/DividendeStorno02.txt | 96 ++++++++++ .../datatransfer/pdf/sbroker/Kauf12.txt | 35 ++++ .../pdf/sbroker/SBrokerPDFExtractorTest.java | 170 ++++++++++++++++++ .../datatransfer/pdf/SBrokerPDFExtractor.java | 49 ++--- 5 files changed, 405 insertions(+), 32 deletions(-) create mode 100644 name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/Dividende10.txt create mode 100644 name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/DividendeStorno02.txt create mode 100644 name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/Kauf12.txt diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/Dividende10.txt b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/Dividende10.txt new file mode 100644 index 0000000000..f40f78e4ca --- /dev/null +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/Dividende10.txt @@ -0,0 +1,87 @@ +PDFBox Version: 1.8.17 +Portfolio Performance Version: 0.61.4 +----------------------------------------- +BELEGDRUCK=J +ORIGINAL=1 +FAXVERSAND=N +EMAILVERSAND=N +Erträgnisgutschrift aus Wertpapieren +DEPOTNUMMER=700/7940/000 +DEPOTUNTERBEZEICHNUNG= +VERSANDARTENSCHLUESSEL=0000 +ADRESSZEILE1=Herrn +Ausschüttung für +ADRESSZEILE2=Dr. Fritz Fox +01.05.2017 - 31.12.2017 +ADRESSZEILE3=Frau Fritzi Foxy +Depot-Nr. Abrechnungs-Nr. +Herrn ADRESSZEILE4=Fuchsstraße 1 +ADRESSZEILE5=88888 München +Dr. Fritz Fox +700/7940/000 61314054 +ADRESSZEILE6= +Frau Fritzi Foxy +BELEGNUMMER=22135 +Fuchsstraße 1 +SEITENNUMMER=1 +88888 München +STEUERERSTATTUNG=N +Depotinhaber +Dr. Fritz Fox oder Fritzi Foxy +Ertragsthesaurierung +Wiesbaden, 13.01.2018 +Gattungsbezeichnung ISIN +iShares MDAX UCITS ETF DE Inhaber-Anteile DE0005933923 +Nominal Ex-Tag Zahltag Ausschüttungsbetrag pro Stück +STK 0,254 02.01.2018 15.01.2018 EUR 2,693217 +Wert Konto-Nr. Betrag zu Ihren Lasten +15.01.2018 11/7940/001 EUR 0,01 +Ertrag für 2017 EUR 0,68 +Hinweise zur steuerlichen Verrechnung: vorher aktuell +inl. Dividendenanteil (Thesaurierung) EUR 0,68 +Aktienverlusttopf EUR 0,00 0,00 +allgemeiner Verlusttopf EUR 0,00 0,00 +Freistellungsauftrag (eingereicht: EUR 501,00) EUR 0,00 0,00 +Quellensteuertopf EUR 0,00 0,00 +Steuerliquidität EUR 0,19 +zu versteuern EUR 0,68 +einbehaltene Kapitalertragsteuer EUR 0,17 +einbehaltener Solidaritätszuschlag EUR 0,01 +einbehaltene Kirchensteuer EUR 0,01 +einbehaltene Kirchensteuer Ehegatte/Lebenspartner EUR 0,01 +im laufenden Jahr einbehaltene Kapitalertragsteuer EUR 40,10 +im laufenden Jahr einbehaltener Solidaritätszuschlag EUR 2,20 +im laufenden Jahr einbehaltene Kirchensteuer EUR 1,60 +im laufenden Jahr einbehaltene Kirchensteuer Ehegatte/Lebenspartner EUR 1,60 +Kapitalertragsteuer, Solidaritätszuschlag und ggf. Kirchensteuer nach gemeldetem Kirchensteuersatz verrechnet mit dem +Finanzamt Wiesbaden, Steuernummer 402/200/0073. +Es folgt Seite 2 +3.20/ABREEREIERTR/GDBERTRG/022135/130118/032659 +BELEGDRUCK=J +ORIGINAL=1 +FAXVERSAND=N +EMAILVERSAND=N +Depot-Nr. Abrechnungs-Nr. Seite-Nr. +DEPOTNUMMER=700/7940/000 +700/7940/000 61314054 2 +DEPOTUNTERBEZEICHNUNG= +VERSANDARTENSCHLUESSEL=0000 +ADRESSZEILE1=Herrn +ADRESSZEILE2=Dr. Fritz Fox +ADRESSZEILE3=Frau Fritzi Foxy +Steuerlicher Stichtag 31.12.2017 +ADRESSZEILE4=Fuchsstraße 1 +Die Kapitalverwaltungsgesellschaft (KVG) entnimmt KESt, SolZ und ggf. KiSt dem Sondervermögen und stellt den +ADRESSZEILE5=88888 München +auszahlenden Stellen diesen Betrag in Form der Steuerliquidität zur Verfügung. Abhängig von der tatsächlich +ADRESSZEILE6= +abzuführenden Steuer wird ein möglicher Differenzbetrag dem Kundenkonto erstattet. +BELEGNUMMER=22135 +Jahressteuerbescheinigung folgt +SEITENNUMMER=2 +Verwahrart: Girosammelverwahrung +STEUERERSTATTUNG=N +Mit freundlichen Grüßen +S Broker AG & Co. KG +Dieser Beleg wird maschinell erstellt und daher nicht unterschrieben. +3.20/ABREEREIERTR/GDBERTRG/022135/130118/032659 \ No newline at end of file diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/DividendeStorno02.txt b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/DividendeStorno02.txt new file mode 100644 index 0000000000..e852498740 --- /dev/null +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/DividendeStorno02.txt @@ -0,0 +1,96 @@ +PDFBox Version: 1.8.17 +Portfolio Performance Version: 0.61.4 +----------------------------------------- +BELEGDRUCK=J +ORIGINAL=1 +FAXVERSAND=N +EMAILVERSAND=N +Erträgnisgutschrift aus Wertpapieren +DEPOTNUMMER=000/0000/000 +DEPOTUNTERBEZEICHNUNG= +VERSANDARTENSCHLUESSEL=0000 +ADRESSZEILE1=Herrn +Ausschüttung für +ADRESSZEILE2=Dr. Fritz Fox +01.06.2017 - 31.12.2017 +ADRESSZEILE3=Frau Fritzi Foxy +Depot-Nr. Abrechnungs-Nr. +Herrn ADRESSZEILE4=Fuchsstraße 4 +ADRESSZEILE5=88888 München +Dr. Fritz Fox +000/0000/000 26495157 +ADRESSZEILE6= +Frau Fritzi Foxy +BELEGNUMMER=21157 +Fuchsstraße 4 +SEITENNUMMER=1 +88888 München +STEUERERSTATTUNG=N +Depotinhaber +Dr. Fritz Fox oder Fritzi Foxy +Storno - Ertragsthesaurierung +Wiesbaden, 27.01.2018 +Gattungsbezeichnung ISIN +iSh.EO ST.Sel.Div.30 U.ETF DE Inhaber-Anteile DE0002635281 +Nominal Ex-Tag Zahltag Ausschüttungsbetrag pro Stück +STK 195,419 02.01.2018 15.01.2018 EUR 0,106071 +Wert Konto-Nr. Betrag zu Ihren Gunsten +15.01.2018 00/0000/000 EUR 0,05 +Storno unserer Erträgnisgutschrift Nr. 80052726 vom 13.01.2018. +Ertrag für 2017 EUR 20,73 +Hinweise zur steuerlichen Verrechnung: vorher aktuell +ausl. Dividendenanteil (Thesaurierung) EUR 20,73 +anrechenbare Quellensteuer Fondseingangsseite EUR 0,07 +Aktienverlusttopf EUR 0,00 0,00 +allgemeiner Verlusttopf EUR 0,00 0,00 +Freistellungsauftrag (eingereicht: EUR 501,00) EUR 0,00 0,00 +Quellensteuertopf EUR 0,00 0,00 +Steuerliquidität EUR 5,73- +zu versteuern (negativ) EUR 20,73 +einbehaltene Kapitalertragsteuer EUR 5,08 +einbehaltener Solidaritätszuschlag EUR 0,28 +einbehaltene Kirchensteuer EUR 0,21 +einbehaltene Kirchensteuer Ehegatte/Lebenspartner EUR 0,21 +Steuerausgleich nach § 43a EStG: +Kirchensteuer EUR 0,01- +Kirchensteuer Ehegatte/Lebenspartner EUR 0,01- +Wert Konto-Nr. Abrechnungs-Nr. Betrag zu Ihren Lasten +29.01.2018 00/0000/000 45714457 EUR 0,02 +Es folgt Seite 2 +3.20/ABREEREIERTR/GDBSTERT/021157/300118/052924 +BELEGDRUCK=J +ORIGINAL=1 +FAXVERSAND=N +EMAILVERSAND=N +Depot-Nr. Abrechnungs-Nr. Seite-Nr. +DEPOTNUMMER=000/0000/000 +000/0000/000 26495157 2 +DEPOTUNTERBEZEICHNUNG= +VERSANDARTENSCHLUESSEL=0000 +ADRESSZEILE1=Herrn +ADRESSZEILE2=Dr. Fritz Fox +ADRESSZEILE3=Frau Fritzi Foxy +im laufenden Jahr einbehaltene Kapitalertragsteuer EUR 35,02 +ADRESSZEILE4=Fuchsstraße 4 +im laufenden Jahr einbehaltener Solidaritätszuschlag EUR 1,92 +ADRESSZEILE5=88888 München +im laufenden Jahr einbehaltene Kirchensteuer EUR 1,40 +ADRESSZEILE6= +im laufenden Jahr einbehaltene Kirchensteuer Ehegatte/Lebenspartner EUR 1,40 +BELEGNUMMER=21157 +SEITENNUMMER=2 +Kapitalertragsteuer, Solidaritätszuschlag und ggf. Kirchensteuer nach gemeldetem Kirchensteuersatz verrechnet mit dem +STEUERERSTATTUNG=N +Finanzamt Wiesbaden, Steuernummer 401/200/0073. +Steuerlicher Stichtag 31.12.2017 +Die Kapitalverwaltungsgesellschaft (KVG) entnimmt KESt, SolZ und ggf. KiSt dem Sondervermögen und stellt den +auszahlenden Stellen diesen Betrag in Form der Steuerliquidität zur Verfügung. Abhängig von der tatsächlich +abzuführenden Steuer wird ein möglicher Differenzbetrag dem Kundenkonto erstattet. +Jahressteuerbescheinigung folgt +Stornierung und Neuabrechnung aufgrund fehlender Berücksichtigung der anrechenbaren ausländischen Quellensteuer. Wir +bitten, das Versehen zu entschuldigen. +Verwahrart: Girosammelverwahrung +Mit freundlichen Grüßen +S Broker AG & Co. KG +Dieser Beleg wird maschinell erstellt und daher nicht unterschrieben. +3.20/ABREEREIERTR/GDBSTERT/021157/300118/052924 \ No newline at end of file diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/Kauf12.txt b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/Kauf12.txt new file mode 100644 index 0000000000..f7a2b214f7 --- /dev/null +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/Kauf12.txt @@ -0,0 +1,35 @@ +PDFBox Version: 1.8.17 +Portfolio Performance Version: 0.61.4 +----------------------------------------- +~$~|OR07|162994||||||||ORDER.D211105.AFP||||20211105||45650|0000|||||~$~ +S Broker AG & Co. KG ´ Postfach 17 29 ´ 65007 Wiesbaden +Depotnummer 7162990000 +Fritz Fox und Fritzi Foxy +Auftragsnummer 332865/91.00 +Datum 05.11.2021 +Herrn Rechnungsnummer W05500-0000054156/21 +Dr. Fritz Fox und Umsatzsteuer-ID DE213898425 +Frau Fritzi Foxy +Fuchsstraße 1 +88888 München +Wertpapier Abrechnung Ausgabe Investmentfonds +Nominale Wertpapierbezeichnung ISIN (WKN) +Stück 0,7769 ISH.DJ U.S.SELECT DIV.U.ETF DE DE000A0D8Q49 (A0D8Q4) +INHABER-ANTEILE +Handels-/Ausführungsplatz Auûerbörslich (gemäû Weisung) +Schlusstag 05.11.2021 +Ausführungskurs 73,63 EUR +Girosammelverw. mehrere Sammelurkunden - kein Stückeausdruck - +Kurswert 57,20- EUR +Provision 2,5000 % vom Kurswert 1,43- EUR +Ausmachender Betrag 58,63- EUR +maximal möglicher Rücknahmeabschlag 1,00 % +Ausgabeaufschlag pro Anteil 0,00 % +Die Wertpapiere schreiben wir Ihrem Depotkonto gut. +Sofern keine Umsatzsteuer ausgewiesen ist, handelt es sich um eine umsatzsteuerbefreite Finanzdienstleistung. +Ausführungsplatz gemäû Ausführungsgrundsätzen (Best Execution): Börse Tradegate +ABR. OHNE AUSGABEAUFSCHLAG +Ihr ETF-Sparplan Nr. 2 +Dieses Dokument wurde maschinell erstellt und wird nicht unterschrieben. +5500.11052055.0050579OR07 +SBRO_OR07_v2.7P.ORDER.D211105.AFP_45650 \ No newline at end of file diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/SBrokerPDFExtractorTest.java b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/SBrokerPDFExtractorTest.java index abc3e5082b..9a529cece1 100644 --- a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/SBrokerPDFExtractorTest.java +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/sbroker/SBrokerPDFExtractorTest.java @@ -53,6 +53,8 @@ public void testWertpapierKauf01() Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("DE000A0H0785")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("iS.EO G.B.C.1.5-10.5y.U.ETF DE Inhaber-Anteile")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -95,6 +97,8 @@ public void testWertpapierKauf02() Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("US5801351017")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("McDonald's Corp. Registered Shares DL-,01")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -138,6 +142,7 @@ public void testWertpapierKauf03() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("LU0171310443")); assertThat(security.getWkn(), is("A0BMAN")); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("BGF - WORLD TECHNOLOGY FUND ACT. NOM. CLASSE A2 EUR O.N.")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -180,6 +185,8 @@ public void testWertpapierKauf04() Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("DE000ETFL508")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("Deka MSCI World UCITS ETF Inhaber-Anteile")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -222,6 +229,8 @@ public void testWertpapierKauf05() Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("DE000ETFL342")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("Deka MSCI Em. Mkts. UCITS ETF Inhaber-Anteile")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -265,6 +274,7 @@ public void testWertpapierKauf06() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("US45781V1017")); assertThat(security.getWkn(), is("A2DGXH")); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("INNOVATIVE INDL PROPERTIES REGISTERED SHARES DL -,001")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -308,6 +318,7 @@ public void testWertpapierKauf07() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("US74144T1088")); assertThat(security.getWkn(), is("870967")); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("T. ROWE PRICE GROUP INC. REGISTERED SHARES DL -,20")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -351,6 +362,7 @@ public void testWertpapierKauf08() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("DE000A2YN900")); assertThat(security.getWkn(), is("A2YN90")); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("TEAMVIEWER AG INHABER-AKTIEN O.N.")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -394,6 +406,7 @@ public void testWertpapierKauf09() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("LU0552385295")); assertThat(security.getWkn(), is("A1H6XK")); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("MOR.ST.INV.-GLOBAL OPPORTUNITY ACTIONS NOMINATIVES A USD O.N.")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.USD)); @@ -489,6 +502,7 @@ public void testWertpapierKauf10() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("US1941621039")); assertThat(security.getWkn(), is("850667")); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("COLGATE-PALMOLIVE CO. SHARES REGISTERED SHARES DL 1")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -532,6 +546,7 @@ public void testWertpapierKauf11() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("DE000A0D8Q49")); assertThat(security.getWkn(), is("A0D8Q4")); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("ISH.DJ U.S.SELECT DIV.U.ETF DE INHABER-ANTEILE")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -557,6 +572,50 @@ public void testWertpapierKauf11() is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1.25)))); } + @Test + public void testWertpapierKauf12() + { + SBrokerPDFExtractor extractor = new SBrokerPDFExtractor(new Client()); + + List errors = new ArrayList<>(); + + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Kauf12.txt"), errors); + + assertThat(errors, empty()); + 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("DE000A0D8Q49")); + assertThat(security.getWkn(), is("A0D8Q4")); + assertNull(security.getTickerSymbol()); + assertThat(security.getName(), is("ISH.DJ U.S.SELECT DIV.U.ETF DE INHABER-ANTEILE")); + 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-11-05T00:00"))); + assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(0.7769))); + assertThat(entry.getSource(), is("Kauf12.txt")); + assertNull(entry.getNote()); + + assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), + is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(58.63)))); + assertThat(entry.getPortfolioTransaction().getGrossValue(), + is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(57.20)))); + 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(1.43)))); + } + @Test public void testWertpapierVerkauf01() { @@ -574,6 +633,8 @@ public void testWertpapierVerkauf01() Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("DE000A0H0785")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("iS.EO G.B.C.1.5-10.5y.U.ETF DE Inhaber-Anteile")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -636,6 +697,8 @@ public void testWertpapierVerkauf02() Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("DE000ETFL110")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("Deka iB.EO L.Sov.D.1-10 U.ETF Inhaber-Anteile")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -678,6 +741,8 @@ public void testWertpapierVerkauf03() Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("FR0013495298")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("Gaussin S.A. Actions au Port. EO 1")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -740,6 +805,8 @@ public void testDividende01() Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("DE000A0H0785")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("iS.EO G.B.C.1.5-10.5y.U.ETF DE Inhaber-Anteile")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -781,6 +848,8 @@ public void testDividende02() Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("US5801351017")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("McDonald's Corp. Registered Shares DL-,01")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.USD)); @@ -871,6 +940,8 @@ public void testDividende03() Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("US7427181091")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("Procter & Gamble Co., The Registered Shares o.N.")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.USD)); @@ -962,6 +1033,7 @@ public void testDividende04() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("US3765361080")); assertThat(security.getWkn(), is("260884")); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("GLADSTONE COMMERCIAL CORP. REGISTERED SHARES DL -,01")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.USD)); @@ -1054,6 +1126,7 @@ public void testDividende05() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("US5021751020")); assertThat(security.getWkn(), is("884625")); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("LTC PROPERTIES INC. REGISTERED SHARES DL -,01")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.USD)); @@ -1146,6 +1219,7 @@ public void testDividende06() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("US83012A1097")); assertThat(security.getWkn(), is("A2P60W")); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("SIXTH STREET SPECIALITY LEND. REGISTERED SHARES DL -,01")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.USD)); @@ -1238,6 +1312,7 @@ public void testDividende07() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("US02209S1033")); assertThat(security.getWkn(), is("200417")); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("ALTRIA GROUP INC. REGISTERED SHARES DL -,333")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.USD)); @@ -1330,6 +1405,7 @@ public void testDividende08() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("BMG9156K1018")); assertThat(security.getWkn(), is("A2PNW9")); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("2020 BULKERS LTD. REGISTERED SHARES DL 1")); assertThat(security.getCurrencyCode(), is("NOK")); @@ -1421,6 +1497,8 @@ public void testDividende09() Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("DE000A0D8Q49")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("iSh.DJ U.S.Select Div.U.ETF DE Inhaber-Anteile")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.USD)); @@ -1494,6 +1572,49 @@ public void testDividende09WithSecurityInEUR() assertThat(s, is(Status.OK_STATUS)); } + @Test + public void testDividende10() + { + SBrokerPDFExtractor extractor = new SBrokerPDFExtractor(new Client()); + + List errors = new ArrayList(); + + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Dividende10.txt"), errors); + + assertThat(errors, empty()); + 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("DE0005933923")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); + assertThat(security.getName(), is("iShares MDAX UCITS ETF DE Inhaber-Anteile")); + assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); + + // check dividends transaction + AccountTransaction transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance) + .findFirst().orElseThrow(IllegalArgumentException::new).getSubject(); + + assertThat(transaction.getType(), is(AccountTransaction.Type.TAXES)); + + assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2018-01-15T00:00"))); + assertThat(transaction.getShares(), is(Values.Share.factorize(0.254))); + assertThat(transaction.getSource(), is("Dividende10.txt")); + assertThat(transaction.getNote(), is("Ertragsthesaurierung für 2017 (0,68 EUR)")); + + assertThat(transaction.getMonetaryAmount(), + is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.01)))); + assertThat(transaction.getGrossValue(), + is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.01)))); + 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)))); + } + @Test public void testDividendeStorno01() { @@ -1511,6 +1632,8 @@ public void testDividendeStorno01() Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() .orElseThrow(IllegalArgumentException::new).getSecurity(); assertThat(security.getIsin(), is("DE000A0D8QZ7")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); assertThat(security.getName(), is("iSh.ST.Euro.Small 200 U.ETF DE Inhaber-Anteile")); assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); @@ -1520,6 +1643,7 @@ public void testDividendeStorno01() .filter(TransactionItem.class::isInstance) // .findFirst().orElseThrow(IllegalArgumentException::new); + assertThat(((AccountTransaction) cancellation.getSubject()).getType(), is(AccountTransaction.Type.DIVIDENDS)); assertThat(cancellation.getFailureMessage(), is(Messages.MsgErrorOrderCancellationUnsupported)); assertThat(((Transaction) cancellation.getSubject()).getDateTime(), is(LocalDateTime.parse("2016-06-15T00:00"))); @@ -1536,4 +1660,50 @@ public void testDividendeStorno01() assertThat(((Transaction) cancellation.getSubject()).getUnitSum(Unit.Type.FEE), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); } + + @Test + public void testDividendeStorno02() + { + SBrokerPDFExtractor extractor = new SBrokerPDFExtractor(new Client()); + + List errors = new ArrayList<>(); + + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "DividendeStorno02.txt"), errors); + + assertThat(errors, empty()); + 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("DE0002635281")); + assertNull(security.getWkn()); + assertNull(security.getTickerSymbol()); + assertThat(security.getName(), is("iSh.EO ST.Sel.Div.30 U.ETF DE Inhaber-Anteile")); + assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR)); + + // check cancellation (Storno) transaction + TransactionItem cancellation = (TransactionItem) results.stream() // + .filter(i -> i.isFailure()) // + .filter(TransactionItem.class::isInstance) // + .findFirst().orElseThrow(IllegalArgumentException::new); + + assertThat(((AccountTransaction) cancellation.getSubject()).getType(), is(AccountTransaction.Type.TAX_REFUND)); + assertThat(cancellation.getFailureMessage(), is(Messages.MsgErrorOrderCancellationUnsupported)); + + assertThat(((Transaction) cancellation.getSubject()).getDateTime(), is(LocalDateTime.parse("2018-01-15T00:00"))); + assertThat(((Transaction) cancellation.getSubject()).getShares(), is(Values.Share.factorize(195.419))); + assertThat(((Transaction) cancellation.getSubject()).getSource(), is("DividendeStorno02.txt")); + assertThat(((Transaction) cancellation.getSubject()).getNote(), is("Ertragsthesaurierung für 2017 (20,73 EUR)")); + + assertThat(((Transaction) cancellation.getSubject()).getMonetaryAmount(), + is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.05)))); + assertThat(((Transaction) cancellation.getSubject()).getGrossValue(), + is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.05)))); + assertThat(((Transaction) cancellation.getSubject()).getUnitSum(Unit.Type.TAX), + is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + assertThat(((Transaction) cancellation.getSubject()).getUnitSum(Unit.Type.FEE), + is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + } } diff --git a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/SBrokerPDFExtractor.java b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/SBrokerPDFExtractor.java index 1106094b4a..b366795dd0 100644 --- a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/SBrokerPDFExtractor.java +++ b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/SBrokerPDFExtractor.java @@ -235,12 +235,24 @@ private void addDividendTransaction() .assign((t, v) -> v.getTransactionContext().put(FAILURE, Messages.MsgErrorOrderCancellationUnsupported)) + // @formatter:off // If we have a positive amount and a gross reinvestment, // there is a tax refund. // If the amount is negative, then it is taxes. + // @formatter:on + + // @formatter:off + // Ertragsthesaurierung + // Wert Konto-Nr. Devisenkurs Betrag zu Ihren Lasten + // 15.01.2018 00/0000/000 EUR/USD 1,19265 EUR 0,65 + // + // Storno - Ertragsthesaurierung + // Wert Konto-Nr. Betrag zu Ihren Gunsten + // 15.01.2018 00/0000/000 EUR 0,05 + // @formatter:on .section("type", "sign").optional() - .match("^(?Ertragsthesaurierung)$") - .match("Wert Konto\\-Nr\\. Devisenkurs Betrag zu Ihren (?(Gunsten|Lasten))") + .match("^(Storno \\- )?(?Ertragsthesaurierung)$") + .match("^Wert Konto\\-Nr\\.( Devisenkurs)? Betrag zu Ihren (?(Gunsten|Lasten))$") .assign((t, v) -> { if (v.get("type").equals("Ertragsthesaurierung") && v.get("sign").equals("Gunsten")) t.setType(AccountTransaction.Type.TAX_REFUND); @@ -308,46 +320,19 @@ private void addDividendTransaction() ) .oneOf( - // @formatter:off - // This is for the "Ertragsthesaurierung" - // - // Ertragsthesaurierung - // Wert Konto-Nr. Devisenkurs Betrag zu Ihren Lasten - // 15.01.2018 00/0000/000 EUR/USD 1,19265 EUR 0,65 - // @formatter:on - section -> section - .attributes("currency", "amount") - .match("^Ertragsthesaurierung$") - .find("Wert Konto\\-Nr\\. Devisenkurs Betrag zu Ihren Lasten") - .match("^[\\d]{2}\\.[\\d]{2}\\.[\\d]{4} .* (?[\\w]{3}) (?[\\.,\\d]+)$") - .assign((t, v) -> { - t.setCurrencyCode(asCurrencyCode(v.get("currency"))); - t.setAmount(asAmount(v.get("amount"))); - }) - , // @formatter:off // Wert Konto-Nr. Betrag zu Ihren Gunsten // 17.11.2014 10/0000/000 EUR 12,70 // // Wert Konto-Nr. Betrag zu Ihren Lasten // 15.06.2016 00/0000/000 EUR 20,24 - // @formatter:on - section -> section - .attributes("currency", "amount") - .find("Wert Konto\\-Nr\\. Betrag zu Ihren (Gunsten|Lasten)") - .match("^[\\d]{2}\\.[\\d]{2}\\.[\\d]{4} .* (?[\\w]{3}) (?[\\.,\\d]+)$") - .assign((t, v) -> { - t.setAmount(asAmount(v.get("amount"))); - t.setCurrencyCode(asCurrencyCode(v.get("currency"))); - }) - , - // @formatter:off + // // Wert Konto-Nr. Devisenkurs Betrag zu Ihren Gunsten // 15.12.2014 12/3456/789 EUR/USD 1,24495 EUR 52,36 // @formatter:on section -> section .attributes("currency", "amount") - .find("Wert Konto\\-Nr\\. Devisenkurs Betrag zu Ihren (Gunsten|Lasten)") + .find("Wert Konto\\-Nr\\.( Devisenkurs)? Betrag zu Ihren (Gunsten|Lasten)") .match("^[\\d]{2}\\.[\\d]{2}\\.[\\d]{4} .* (?[\\w]{3}) (?[\\.,\\d]+)$") .assign((t, v) -> { t.setAmount(asAmount(v.get("amount"))); @@ -446,7 +431,7 @@ private void addDividendTransaction() // Ertrag für 2017 USD 54,16 // @formatter:on .section("note1", "note2", "note3", "note4").optional() - .match("^(?Ertragsthesaurierung)$") + .match("^(Storno \\- )?(?Ertragsthesaurierung)$") .match("^Ertrag (?f.r [\\d]{4}(\\/[\\d]{2})?) (?[\\w]{3}) (?[\\.,\\d]+)$") .assign((t, v) -> t.setNote(v.get("note1") + " " + v.get("note2") + " (" + v.get("note4") + " " + v.get("note3") + ")"))