Skip to content

Commit

Permalink
Added Scalable Capital importer (#4410)
Browse files Browse the repository at this point in the history
Apparently, Scalable Capital is not using the Baader Bank anymore.
From now on, there are two importers for Scalable: the Baader Bank one
for older document, and the Scalable Capital one for newer documents.
  • Loading branch information
buchen authored Dec 22, 2024
1 parent 28b7532 commit d7d212f
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
PDFBox Version: 1.8.17
Portfolio Performance Version: 0.72.2
-----------------------------------------
Scalable Capital GmbH • Seitzstraße 8e • 80538 München • Deutschland
gMbdE BSBVSo
PIUWUpOPvMUGNZ OXxLwz 66
80481 YHbanuVrhilpy Datum 12.12.2024
Deutschland Seite 1 / 1
Wertpapierabrechnung
für Kundenauftrag
Typ LIMIT Order SCALsin78vS5CYz
Ausführung 12.12.2024 13:12:51 Geschäft 36581526
Ausführungsplatz EIX Lagerland Luxemburg
Depot 1970027444 Verwahrart Wertpapierrechnung
Wertpapierabrechnung
Typ Wertpapier Anzahl Kurs Betrag
Kauf Vngrd Fds-ESG Dv.As-Pc Al ETF 3,00 Stk. 6,168 EUR 18,50 EUR
IE0008T6IUX0
Ordergebühren +0,99 EUR
Total 19,49 EUR
Der Betrag wird mit dem Verrechnungskonto DE168836967200035482353 (Valuta: 16.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
@@ -0,0 +1,70 @@
package name.abuchen.portfolio.datatransfer.pdf.scalablecapital;

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.purchase;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.security;
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 java.util.ArrayList;
import java.util.List;

import org.junit.Test;

import name.abuchen.portfolio.datatransfer.Extractor.Item;
import name.abuchen.portfolio.datatransfer.actions.AssertImportActions;
import name.abuchen.portfolio.datatransfer.pdf.PDFInputFile;
import name.abuchen.portfolio.datatransfer.pdf.ScalableCapitalPDFExtractor;
import name.abuchen.portfolio.model.Client;
import name.abuchen.portfolio.money.CurrencyUnit;

@SuppressWarnings("nls")
public class ScalableCapitalPDFExtractorTest
{
@Test
public void testKauf01()
{
ScalableCapitalPDFExtractor extractor = new ScalableCapitalPDFExtractor(new Client());

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

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
assertThat(results, hasItem(security( //
hasIsin("IE0008T6IUX0"), //
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), //
hasSource("Kauf01.txt"), //
hasNote(null), //
hasAmount("EUR", 19.49), hasGrossValue("EUR", 18.50), //
hasTaxes("EUR", 0.00), hasFees("EUR", 0.99))));

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public PDFImportAssistant(Client client, List<File> files)
extractors.add(new SaxoBankPDFExtractor(client));
extractors.add(new SberbankEuropeAGPDFExtractor(client));
extractors.add(new SBrokerPDFExtractor(client));
extractors.add(new ScalableCapitalPDFExtractor(client));
extractors.add(new ScorePriorityIncPDFExtractor(client));
extractors.add(new SelfWealthPDFExtractor(client));
extractors.add(new SimpelPDFExtractor(client));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package name.abuchen.portfolio.datatransfer.pdf;

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
{
public ScalableCapitalPDFExtractor(Client client)
{
super(client);

addBankIdentifier("Scalable Capital GmbH");

addPurchaseTransaction();
}

@Override
public String getLabel()
{
return "Scalable Capital";
}

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>()

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

.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("^(?<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})")
.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();

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

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

}

0 comments on commit d7d212f

Please sign in to comment.