Skip to content

Commit

Permalink
Fix and Modify Sunrise PDF-Importer
Browse files Browse the repository at this point in the history
https://forum.portfolio-performance.info/t/pdf-import-von-sunrise/26633
https://forum.portfolio-performance.info/t/pdf-import-von-sunrise/26633/5

The Sunrise PDF importer was implemented in #3731.

If the dividends and the taxes neutralize each other, then negative dividends were previously posted (EUR 0.00 minus taxes). This is incorrect, as a negative dividend is then displayed under "Payments".
The transactions are now recorded separately in this case.

A test case for the sale of securities does not exist. This has therefore been removed from the importer. (Validation)

The test cases have been updated.

Format source to standard eclipse format

Improve regulare expressions
Add missing currency detection
Remove obsolet source
  • Loading branch information
Nirus2000 committed Mar 27, 2024
1 parent b68ddc4 commit 447ca01
Show file tree
Hide file tree
Showing 5 changed files with 456 additions and 127 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
PDFBox Version: 1.8.17
Portfolio Performance Version: 0.67.1
-----------------------------------------
Depotführung: Ein Service von:
Für Rückfragen wenden Sie sich bitte an:
[email protected]
Herr
Fondsabrechnung
TOGSoj SozSi
FcvmR ydOdxqCbtQB 7/0 Wien, 20.10.2022
2908 vLwD Depotinhaber: frQDJw AedNd
ukFulZrAOV Depotnummer: 5406992396
Hiermit bestätigen wir Ihnen folgende Transaktion für Sie ausgeführt zu haben:
Transaktion Fondsname Betrag Preis Anteile
WKN/ISIN Preisdatum Bestand neu
Verwahrart/Lagerland
Kauf Standortfonds Österreich 50.00 € 115.40 € 0.433
AT0000A1QA38 18.10.2022 4.740
Girosammelverwahrung/AT
Abrechnungsbetrag: 50.00 €
Ausgabeaufschlag/Provision: 0,00%
Auftrags-Nummer: 747448052066562190749732135729
KESt-Neubestand mit Anschaffungskosten nach dem gleitenden Durchschnittsverfahren § 27a Abs. 4 Zi 3 EStG:
0.433 Anteile.
Steuerlicher Anschaffungswert: 49.97 €
Verrechnung Kapitalertragssteuer*
• Vor dieser Transaktion nicht ausgeglichene Gewinne:
• Nach dieser Transaktion nicht ausgeglichene Gewinne:
• Vor dieser Transaktion nicht ausgeglichene Verluste:
• Nach dieser Transaktion nicht ausgeglichene Verluste:
* Innerhalb eines Kalenderjahres werden realisierte Verluste mit realisierten Gewinnen ausgeglichen. Dabei werden alle
von Ihnen bei uns gehaltenen Depots zusammen betrachtet. Es gibt zwei Fälle: Im ersten Fall haben Sie in der
Vergangenheit Gewinne realisiert, mit der aktuellen Transaktion machen Sie aber einen Verlust. In diesem Fall wird Ihre
bereits bezahlte Kapitalertragsteuer mit dieser Auszahlung rückerstattet. Im zweiten Fall haben Sie in der Vergangenheit
Verluste realisiert, machen aber mit dieser Auszahlung einen Gewinn. Dann wurde der Verlust aus der Vergangenheit bei
der Berechnung des Kapitalertragsteuerabzugs für diesen Verkauf berücksichtigt.
Anlagen in Investmentfonds können erst nach Kenntnisnahme der gesetzlichen Verkaufsunterlagen (Prospekt, Kunden-
informationsdokumente, Halb-/Jahresbericht) erfolgen. Diese Unterlagen haben Sie direkt über die Sunrise Kanäle
erhalten bzw. vor Auftragserteilung dort eingesehen.
Hinweis: Einwendungen gegen diese Fondsabrechnung wegen Unrichtigkeit oder Unvollständigkeit richten Sie bitte
schriftlich spätestens einen Monat nach Zugang an: [email protected]
Simpel S.A., 33, Boulevard Prince Henri, L-1724 Luxemburg; Sitz: Luxemburg; Umsatzsteuer-lD-Nr. LU32888126; Vorsitzender des Verwaltungsrats:
Thomas Niss; Conducting Officers: Martin Foussek, Christian McFadden
Simpel Zweigniederlassung Österreich, 1040 Wien, Gußhausstraße 3/2a; Handelsgericht Wien, FN 562615a ; Niederlassungsleiter:
Christian McFadden, Martin Foussek, Ständiger Vertreter: Thomas Niss
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
PDFBox Version: 1.8.17
Portfolio Performance Version: 0.67.1
-----------------------------------------
Ein Service von:
Für Rückfragen wenden Sie sich
bitte an: [email protected]
Herr
bbMArl kpKGn Fondsabrechnung
kyUDc IXQfXfvVuZi 6/0
2952 Ytaj Datum: 29.12.2023
TqZadDlMZh Depotnummer: 6782756237
Hiermit bestätigen wir Ihnen folgende Transaktion für Sie ausgeführt zu haben:
Transaktion Fondsname Betrag Preis Anteile
ISIN Preisdatum Bestand neu
Verwahrart/Lagerland
Verwahrort
Kauf Standortfonds Österreich 10.08 € 140.03 € 0.072
AT0000A1QA38 28.12.2023 13.548
Girosammelverwahrung/AT
Raiffeisen Bank International AG
Abrechnungsbetrag: 10.08 €
Ausgabeaufschlag/Provision: 0,00%
Auftrags-Nummer: 751175802407452854275862438475
Anlagen in Investmentfonds können erst nach Kenntnisnahme der gesetzlichen Verkaufsunterlagen
(Basisinformationsblatt) erfolgen. Diese Unterlagen haben Sie direkt über die Sunrise Kanäle erhalten bzw. vor
Auftragserteilung dort eingesehen.
Wichtiger Hinweis: Einwendungen gegen diese Fondsabrechnung wegen Unrichtigkeit oder Unvollständigkeit
richten Sie bitte schriftlich spätestens einen Monat nach Zugang an: [email protected].
Mit besten Grüßen
Ihr Sunrise Team
Sunrise Securities GmbH, Gußhausstraße 3/2, A-1040 Wien; UID-Nummer: ATU 68616777, Firmenbuchnummer: FN 410750w
Sunrise Securities GmbH nach österreichischem Recht Zweigniederlassung Deutschland, Rahel-Hirsch-Straße 10, D-10557 Berlin; Handelsregister: HRB 258310 B
Version_2023_01_K
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
PDFBox Version: 1.8.17
Portfolio Performance Version: 0.67.3
-----------------------------------------
Ein Service von:
Für Rückfragen wenden Sie sich
bitte an: [email protected]
Herr
gifSwG BNiVTeGR Fondsabrechnung
zaeldasd-emd-MqKbfe 81
39610 pTfvIfZgatMvWz Datum: 05.01.2024
Deutschland Depotnummer: 5190828770
Hiermit bestätigen wir Ihnen folgende Transaktion für Sie ausgeführt zu haben:
Transaktion Fondsname Betrag Preis Anteile
ISIN Preisdatum Bestand neu
Verwahrart/Lagerland
Verwahrort
Kauf Standortfonds Deutschland 1003.84 € 133.81 € 7.502
AT0000A1Z882 03.01.2024 275.444
Girosammelverwahrung/AT
Raiffeisen Bank International AG
Abrechnungsbetrag: 1003.84 €
Ausgabeaufschlag/Provision: 0,00%
Auftrags-Nummer: 413093083849495599565316731081
Anlagen in Investmentfonds können erst nach Kenntnisnahme der gesetzlichen Verkaufsunterlagen
(Basisinformationsblatt) erfolgen. Diese Unterlagen haben Sie direkt über die Sunrise Kanäle erhalten bzw. vor
Auftragserteilung dort eingesehen.
Wichtiger Hinweis: Einwendungen gegen diese Fondsabrechnung wegen Unrichtigkeit oder Unvollständigkeit
richten Sie bitte schriftlich spätestens einen Monat nach Zugang an: [email protected].
Mit besten Grüßen
Ihr Sunrise Team
Sunrise Securities GmbH, Gußhausstraße 3/2, A-1040 Wien; UID-Nummer: ATU 68616777, Firmenbuchnummer: FN 410750w
Sunrise Securities GmbH nach österreichischem Recht Zweigniederlassung Deutschland, Rahel-Hirsch-Straße 10, D-10557 Berlin; Handelsregister: HRB 258310 B
Version_2023_01_K
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
package name.abuchen.portfolio.datatransfer.pdf.sunrise;

import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.dividend;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasAmount;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasCurrencyCode;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasDate;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasFees;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasGrossValue;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasIsin;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasName;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasNote;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasShares;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasSource;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasTaxes;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasTicker;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasWkn;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.purchase;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.security;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.taxes;
import static name.abuchen.portfolio.datatransfer.ExtractorTestUtilities.countAccountTransactions;
import static name.abuchen.portfolio.datatransfer.ExtractorTestUtilities.countBuySell;
import static name.abuchen.portfolio.datatransfer.ExtractorTestUtilities.countSecurities;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.junit.Assert.assertNull;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

import name.abuchen.portfolio.datatransfer.Extractor.BuySellEntryItem;
import name.abuchen.portfolio.datatransfer.Extractor.Item;
import name.abuchen.portfolio.datatransfer.Extractor.SecurityItem;
import name.abuchen.portfolio.datatransfer.Extractor.TransactionItem;
import name.abuchen.portfolio.datatransfer.actions.AssertImportActions;
import name.abuchen.portfolio.datatransfer.pdf.PDFInputFile;
import name.abuchen.portfolio.datatransfer.pdf.SunrisePDFExtractor;
import name.abuchen.portfolio.model.AccountTransaction;
import name.abuchen.portfolio.model.BuySellEntry;
import name.abuchen.portfolio.model.Client;
import name.abuchen.portfolio.model.PortfolioTransaction;
import name.abuchen.portfolio.model.Security;
import name.abuchen.portfolio.model.Transaction.Unit;
import name.abuchen.portfolio.money.CurrencyUnit;
import name.abuchen.portfolio.money.Money;
import name.abuchen.portfolio.money.Values;

@SuppressWarnings("nls")
public class SunrisePDFExtractorTest
Expand All @@ -41,80 +50,156 @@ public void testWertpapierKauf01()
List<Item> results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Kauf01.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("AT0000A1QA38"));
assertNull(security.getWkn());
assertNull(security.getTickerSymbol());
assertThat(security.getName(), is("Standortfonds Österreich"));
assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR));
assertThat(results, hasItem(security( //
hasIsin("AT0000A1QA38"), hasWkn(null), hasTicker(null), //
hasName("Standortfonds Österreich"), //
hasCurrencyCode("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("2024-01-03T00:00")));
assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(0.215)));
assertThat(entry.getSource(), is("Kauf01.txt"));
assertThat(entry.getNote(), is("Auftrags-Nummer: 345834056535324784670985082345"));

assertThat(entry.getPortfolioTransaction().getMonetaryAmount(),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(30.12))));
assertThat(entry.getPortfolioTransaction().getGrossValue(),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(30.12))));
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))));
assertThat(results, hasItem(purchase( //
hasDate("2024-01-03T00:00"), hasShares(0.215), //
hasSource("Kauf01.txt"), //
hasNote("Auftrags-Nummer: 345834056535324784670985082345"), //
hasAmount("EUR", 30.12), hasGrossValue("EUR", 30.12), //
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))));
}

@Test
public void testDividende01()
public void testWertpapierKauf02()
{
Client client = new Client();
SunrisePDFExtractor extractor = new SunrisePDFExtractor(new Client());

List<Exception> errors = new ArrayList<>();

List<Item> results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Kauf02.txt"), errors);

SunrisePDFExtractor extractor = new SunrisePDFExtractor(client);
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("AT0000A1QA38"), hasWkn(null), hasTicker(null), //
hasName("Standortfonds Österreich"), //
hasCurrencyCode("EUR"))));

// check buy sell transaction
assertThat(results, hasItem(purchase( //
hasDate("2022-10-18T00:00"), hasShares(0.433), //
hasSource("Kauf02.txt"), //
hasNote("Auftrags-Nummer: 747448052066562190749732135729"), //
hasAmount("EUR", 50.00), hasGrossValue("EUR", 50.00), //
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))));
}

@Test
public void testWertpapierKauf03()
{
SunrisePDFExtractor extractor = new SunrisePDFExtractor(new Client());

List<Exception> errors = new ArrayList<>();

List<Item> results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Dividende01.txt"), errors);
List<Item> results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Kauf03.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("AT0000A1QA38"));
assertNull(security.getWkn());
assertNull(security.getTickerSymbol());
assertThat(security.getName(), is("Standortfonds Österreich"));
assertThat(security.getCurrencyCode(), is(CurrencyUnit.EUR));
assertThat(results, hasItem(security( //
hasIsin("AT0000A1QA38"), hasWkn(null), hasTicker(null), //
hasName("Standortfonds Österreich"), //
hasCurrencyCode("EUR"))));

// check buy sell transaction
assertThat(results, hasItem(purchase( //
hasDate("2023-12-28T00:00"), hasShares(0.072), //
hasSource("Kauf03.txt"), //
hasNote("Auftrags-Nummer: 751175802407452854275862438475"), //
hasAmount("EUR", 10.08), hasGrossValue("EUR", 10.08), //
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))));
}

@Test
public void testWertpapierKauf04()
{
SunrisePDFExtractor extractor = new SunrisePDFExtractor(new Client());

List<Exception> errors = new ArrayList<>();

List<Item> results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Kauf04.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("AT0000A1Z882"), hasWkn(null), hasTicker(null), //
hasName("Standortfonds Deutschland"), //
hasCurrencyCode("EUR"))));

// check buy sell transaction
assertThat(results, hasItem(purchase( //
hasDate("2024-01-03T00:00"), hasShares(7.502), //
hasSource("Kauf04.txt"), //
hasNote("Auftrags-Nummer: 413093083849495599565316731081"), //
hasAmount("EUR", 1003.84), hasGrossValue("EUR", 1003.84), //
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))));
}

@Test
public void testDividende22()
{
SunrisePDFExtractor extractor = new SunrisePDFExtractor(new Client());

List<Exception> errors = new ArrayList<>();

List<Item> results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Dividende01.txt"), errors);

assertThat(errors, empty());
assertThat(countSecurities(results), is(1L));
assertThat(countBuySell(results), is(0L));
assertThat(countAccountTransactions(results), is(2L));
assertThat(results.size(), is(3));
new AssertImportActions().check(results, CurrencyUnit.EUR);

// check security
assertThat(results, hasItem(security( //
hasIsin("AT0000A1QA38"), hasWkn(null), hasTicker(null), //
hasName("Standortfonds Österreich"), //
hasCurrencyCode("EUR"))));

// check dividends transaction
AccountTransaction transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance)
.findFirst().orElseThrow(IllegalArgumentException::new).getSubject();

assertThat(transaction.getType(), is(AccountTransaction.Type.DIVIDENDS));

assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2023-12-15T00:00")));
assertThat(transaction.getShares(), is(Values.Share.factorize(10.29 / 0.49)));
assertThat(transaction.getSource(), is("Dividende01.txt"));
assertThat(transaction.getNote(), is("Turnus: jährlich"));

assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00))));
assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(10.29))));
assertThat(transaction.getUnitSum(Unit.Type.TAX),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(10.29))));
assertThat(transaction.getUnitSum(Unit.Type.FEE),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00))));
assertThat(results, hasItem(dividend( //
hasDate("2023-12-15T00:00"), hasShares(10.29 / 0.49), //
hasSource("Dividende01.txt"), //
hasNote("Turnus: jährlich"), //
hasAmount("EUR", 10.29), hasGrossValue("EUR", 10.29), //
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))));

// check taxes transaction
assertThat(results, hasItem(taxes( //
hasDate("2023-12-15T00:00"), hasShares(10.29 / 0.49), //
hasSource("Dividende01.txt"), //
hasNote(null), //
hasAmount("EUR", 10.29), hasGrossValue("EUR", 10.29), //
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))));
}
}
Loading

0 comments on commit 447ca01

Please sign in to comment.