Skip to content

Commit

Permalink
Modify Scalable Capita PDF-Importer to support new transaction (#4438)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nirus2000 authored Jan 2, 2025
1 parent 1e1a9b9 commit 68bff77
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
PDFBox Version: 1.8.17
Portfolio Performance Version: 0.73.0
System: macosx | aarch64 | 21.0.5+11-LTS | Azul Systems, Inc.
-----------------------------------------
Scalable Capital GmbH • Seitzstraße 8e • 80538 München • Deutschland
rxJrK ThosSK
BwnmeQxDygxpHJ kiWlnE 33
52347 YRyvGwLrLFzVO Datum 27.12.2024
Deutschland Seite 1 / 1
Wertpapierabrechnung
für Sparplanausführung
Typ LIMIT Order SCALx2qhYduKQJf
Ausführung 27.12.2024 11:46:01 Geschäft 4086GZHYi7421537
Ausführungsplatz EIX Lagerland Deutschland
Depot 7684756394 Verwahrart Girosammelverwahrung
Wertpapierabrechnung
Typ Wertpapier Anzahl Kurs Betrag
Kauf American Water Works 0,016515 Stk. 121,10 EUR 2,00 EUR
US0304201033
Total 2,00 EUR
Der Betrag wird mit dem Verrechnungskonto DEghin3u4DSCVJ09 (Valuta: 31.12.2024) verrechnet.
Bitte überprüfen Sie die Informationen auf Richtigkeit und melden Sie etwaige Einwände unverzüglich bei uns.
Verwenden Sie dafür den Menüpunkt Support im Kundenbereich.
Scalable Capital GmbH HRB 217778 Geschäftsführer: Aufsichtsrat: Seite
Seitzstraße 8e Amtsgericht München Erik Podzuweit, Florian Prucker Patrick Olson (Vorsitzender)
80538 München USt.-Id. Nr.: DE300434774 Martin Krebs, Dirk Franzmeyer, Dirk
Urmoneit 1 / 1
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
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.sale;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.security;
import static name.abuchen.portfolio.datatransfer.ExtractorTestUtilities.countAccountTransactions;
import static name.abuchen.portfolio.datatransfer.ExtractorTestUtilities.countBuySell;
Expand Down Expand Up @@ -54,17 +57,81 @@ public void testKauf01()

// check security
assertThat(results, hasItem(security( //
hasIsin("IE0008T6IUX0"), //
hasIsin("IE0008T6IUX0"), hasWkn(null), hasTicker(null), //
hasName("Vngrd Fds-ESG Dv.As-Pc Al ETF"), //
hasCurrencyCode("EUR"))));

// check dividends transaction
assertThat(results, hasItem(purchase( //
hasDate("2024-12-12T13:12:51"), hasShares(3), //
hasDate("2024-12-12T13:12:51"), hasShares(3.00), //
hasSource("Kauf01.txt"), //
hasNote(null), //
hasNote("Ord.-Nr.: SCALsin78vS5CYz"), //
hasAmount("EUR", 19.49), hasGrossValue("EUR", 18.50), //
hasTaxes("EUR", 0.00), hasFees("EUR", 0.99))));

}

@Test
public void testKauf02()
{
ScalableCapitalPDFExtractor extractor = new ScalableCapitalPDFExtractor(new Client());

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

List<Item> results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Kauf02.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("US0304201033"), hasWkn(null), hasTicker(null), //
hasName("American Water Works"), //
hasCurrencyCode("EUR"))));

// check dividends transaction
assertThat(results, hasItem(purchase( //
hasDate("2024-12-27T11:46:01"), hasShares(0.016515), //
hasSource("Kauf02.txt"), //
hasNote("Ord.-Nr.: SCALx2qhYduKQJf"), //
hasAmount("EUR", 2.00), hasGrossValue("EUR", 2.00), //
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))));

}

@Test
public void testVerkauf01()
{
ScalableCapitalPDFExtractor extractor = new ScalableCapitalPDFExtractor(new Client());

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

List<Item> results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Verkauf01.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("LU2903252349"), hasWkn(null), hasTicker(null), //
hasName("Scalable MSCI AC World Xtrackers (Acc)"), //
hasCurrencyCode("EUR"))));

// check dividends transaction
assertThat(results, hasItem(sale( //
hasDate("2024-12-30T13:39:12"), hasShares(1.00), //
hasSource("Verkauf01.txt"), //
hasNote("Ord.-Nr.: SCALRnomPwYQrc5"), //
hasAmount("EUR", 8.60), hasGrossValue("EUR", 9.59), //
hasTaxes("EUR", 0.00), hasFees("EUR", 0.99))));

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
PDFBox Version: 1.8.17
Portfolio Performance Version: 0.73.0
System: macosx | aarch64 | 21.0.5+11-LTS | Azul Systems, Inc.
-----------------------------------------
Scalable Capital GmbH • Seitzstraße 8e • 80538 München • Deutschland
xYXVd TlPyTs
OZleTFvjpBihGc HEJOhU 91
91919 JdNDkTwZDNPEq Datum 30.12.2024
Deutschland Seite 1 / 2
Wertpapierabrechnung
für Kundenauftrag
Typ MARKET Order SCALRnomPwYQrc5
Ausführung 30.12.2024 13:39:12 Geschäft 3050eskoFJ914634
Ausführungsplatz EIX Lagerland Luxemburg
Depot 026394660 Verwahrart Wertpapierrechnung
Wertpapierabrechnung
Typ Wertpapier Anzahl Kurs Betrag
Verkauf Scalable MSCI AC World Xtrackers (Acc) 1,00 Stk. 9,585 EUR 9,59 EUR
LU2903252349
Ordergebühren -0,99 EUR
Total 8,60 EUR
Der Betrag wird mit dem Verrechnungskonto DE2634edjfbvdcgD27 (Valuta: 02.01.2025) verrechnet.
Bitte überprüfen Sie die Informationen auf Richtigkeit und melden Sie etwaige Einwände unverzüglich bei uns.
Verwenden Sie dafür den Menüpunkt Support im Kundenbereich.
Scalable Capital GmbH HRB 217778 Geschäftsführer: Aufsichtsrat: Seite
Seitzstraße 8e Amtsgericht München Erik Podzuweit, Florian Prucker Patrick Olson (Vorsitzender)
80538 München USt.-Id. Nr.: DE300434774 Martin Krebs, Dirk Franzmeyer, Dirk
Urmoneit 1 / 2
Wertpapierabrechnung
Verkauf 1,00 Stk. Scalable MSCI AC World Xtrackers
(Acc)
Ermittlung steuerrelevanter Erträge
Typ Anmerkung Betrag
Gewinn oder Verlust -0,08 EUR
Berücksichtigte Ordergebühren -1,98 EUR
Teilfreistellung 30 % +0,62 EUR
Verlusttopf allgemein 2,24 EUR → 3,68 EUR +1,44 EUR
Zu versteuern 0,00 EUR
Scalable Capital GmbH HRB 217778 Geschäftsführer: Aufsichtsrat: Seite
Seitzstraße 8e Amtsgericht München Erik Podzuweit, Florian Prucker Patrick Olson (Vorsitzender)
80538 München USt.-Id. Nr.: DE300434774 Martin Krebs, Dirk Franzmeyer, Dirk
Urmoneit 2 / 2
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package name.abuchen.portfolio.datatransfer.pdf;

import static name.abuchen.portfolio.util.TextUtil.trim;

import name.abuchen.portfolio.datatransfer.pdf.PDFParser.Block;
import name.abuchen.portfolio.datatransfer.pdf.PDFParser.DocumentType;
import name.abuchen.portfolio.datatransfer.pdf.PDFParser.Transaction;
import name.abuchen.portfolio.model.BuySellEntry;
import name.abuchen.portfolio.model.Client;
import name.abuchen.portfolio.model.PortfolioTransaction;
import name.abuchen.portfolio.model.Transaction.Unit;
import name.abuchen.portfolio.money.Money;

@SuppressWarnings("nls")
public class ScalableCapitalPDFExtractor extends AbstractPDFExtractor
Expand All @@ -24,63 +24,93 @@ public ScalableCapitalPDFExtractor(Client client)
@Override
public String getLabel()
{
return "Scalable Capital";
return "Scalable Capital GmbH";
}

private void addPurchaseTransaction()
{
final DocumentType type = new DocumentType("Wertpapierabrechnung");

this.addDocumentTyp(type);

Block purchase = new Block(".* Kundenauftrag.*");
type.addBlock(purchase);
purchase.set(new Transaction<BuySellEntry>()
Transaction<BuySellEntry> pdfTransaction = new Transaction<>();

Block firstRelevantLine = new Block("^f.r (Kundenauftrag|Sparplanausf.hrung) .*$");
type.addBlock(firstRelevantLine);
firstRelevantLine.set(pdfTransaction);

pdfTransaction //

.subject(() -> {
BuySellEntry portfolioTransaction = new BuySellEntry();
portfolioTransaction.setType(PortfolioTransaction.Type.BUY);
return portfolioTransaction;
})

// Is type --> "Verkauf" change from BUY to SELL
.section("type").optional() //
.match("^(?<type>(Kauf|Verkauf)) .* [\\.,\\d]+ Stk\\. .*$") //
.assign((t, v) -> {
if ("Verkauf".equals(v.get("type"))) //
t.setType(PortfolioTransaction.Type.SELL);
})

// @formatter:off
// Kauf Vngrd Fds-ESG Dv.As-Pc Al ETF 3,00 Stk. 6,168 EUR 18,50 EUR
// Verkauf Scalable MSCI AC World Xtrackers (Acc) 1,00 Stk. 9,585 EUR 9,59 EUR
// IE0008T6IUX0
// @formatter:on
.section("name", "currency", "isin") //
.find("Typ Wertpapier Anzahl Kurs Betrag") //
.match("^Kauf (?<name>.*) " //
+ "[.,\\d]+ Stk. " //
+ "[.,\\d]+ [A-Z]{3} " //
+ "[.,\\d]+ (?<currency>[A-Z]{3})")
.match("^(Kauf|Verkauf) (?<name>.*) [\\.,\\d]+ Stk\\. [\\.,\\d]+ (?<currency>[\\w]{3}) [\\.,\\d]+ [\\w]{3}$") //
.match("^(?<isin>[A-Z]{2}[A-Z0-9]{9}[0-9])$") //
.assign((t, v) -> t.setSecurity(getOrCreateSecurity(v)))

.section("shares", "amount", "currency") //
.find("Typ Wertpapier Anzahl Kurs Betrag") //
.match("^Kauf .* " //
+ "(?<shares>[.,\\d]+) Stk. " //
+ "[.,\\d]+ [A-Z]{3} " //
+ "(?<amount>[.,\\d]+) (?<currency>[A-Z]{3})")
// @formatter:off
// Kauf Vngrd Fds-ESG Dv.As-Pc Al ETF 3,00 Stk. 6,168 EUR 18,50 EUR
// Verkauf Scalable MSCI AC World Xtrackers (Acc) 1,00 Stk. 9,585 EUR 9,59 EUR
// @formatter:on
.section("shares") //
.match("^(Kauf|Verkauf) .* (?<shares>[\\.,\\d]+) Stk\\. [\\.,\\d]+ [\\w]{3} [\\.,\\d]+ [\\w]{3}$") //
.assign((t, v) -> t.setShares(asShares(v.get("shares"))))

// @formatter:off
// Ausführung 12.12.2024 13:12:51 Geschäft 36581526
// @formatter:on
.section("date", "time") //
.match("^Ausf.hrung (?<date>[\\d]{2}\\.[\\w]{2}\\.[\\d]{4}) (?<time>[\\d]{2}:[\\d]{2}:[\\d]{2}).*$")
.assign((t, v) -> t.setDate(asDate(v.get("date"), v.get("time"))))

// @formatter:off
// Total 19,49 EUR
// @formatter:on
.section("currency", "amount") //
.match("^Total (?<amount>[\\.,\\d]+) (?<currency>[\\w]{3})$") //
.assign((t, v) -> {
t.setCurrencyCode(asCurrencyCode(v.get("currency")));
t.setAmount(asAmount(v.get("amount")));
t.setShares(asShares(v.get("shares")));
})

.section("date", "time") //
.match("^Ausführung (?<date>[\\d]{2}\\.[\\w]{2}\\.[\\d]{4}) " //
+ "(?<time>[\\d]{2}:[\\d]{2}:[\\d]{2}) .*$")
.assign((t, v) -> t.setDate(asDate(v.get("date"), v.get("time"))))

.section("fee", "currency") //
.optional() //
.match("^Ordergebühren \\+(?<fee>[.,\\d]+) (?<currency>[A-Z]{3})$").assign((t, v) -> {
var currency = asCurrencyCode(v.get("currency"));
var fee = asAmount(v.get("fee"));
var tx = t.getPortfolioTransaction();
// @formatter:off
// Typ LIMIT Order SCALsin78vS5CYz
// @formatter:on
.section("note").optional() //
.match("^.* Order (?<note>.*)$") //
.assign((t, v) -> t.setNote("Ord.-Nr.: " + trim(v.get("note"))))

tx.addUnit(new Unit(Unit.Type.FEE, Money.of(currency, fee)));
t.setAmount(tx.getAmount() + fee);
})
.wrap(BuySellEntryItem::new);

.wrap(e -> e.getPortfolioTransaction().getSecurity() == null ? null : new BuySellEntryItem(e)));
addFeesSectionsTransaction(pdfTransaction, type);
}

private <T extends Transaction<?>> void addFeesSectionsTransaction(T transaction, DocumentType type)
{
transaction //

// @formatter:off
// Ordergebühren +0,99 EUR
// Ordergebühren -0,99 EUR
// @formatter:on
.section("currency", "fee").optional() //
.match("^Ordergeb.hren [\\-|\\+](?<fee>[\\.,\\d]+) (?<currency>[\\w]{3})$") //
.assign((t, v) -> processFeeEntries(t, v, type));
}
}

0 comments on commit 68bff77

Please sign in to comment.