Skip to content

Commit

Permalink
Modify PostFinance PDF-Importer to support new transaction (#4422)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nirus2000 authored Dec 26, 2024
1 parent 5dbe199 commit 0143cc4
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
PDFBox Version: 1.8.17
Portfolio Performance Version: 0.72.2
-----------------------------------------
PostFinance AG
Sie werden betreut von
KmLaf STJiQ oTqDQh und Team Post CH AG
Telefon +41 20 486 17 12
www.postfinance.ch P.P. CH-4808 Zofingen A-PRIORITY
yObY
xCqJFfs rrSEgT WlfX BapaDg ICOS Pkoibzr
GweJpN gqJCiOWpctTNeseNL 69
6979 eQAbpo
Transaktion: Kauf Seite: 1 / 1
Krypto Datum: 02.12.2024
Kryptoportfolio 33-705809-6 Auftrag 37463178
Datum der 01.12.2024
Auftragserteilung
Ihr Auftrag wurde wie folgt am 01.12.2024 ausgeführt:
Position Anzahl Währung Kurs Betrag
Bitcoin BTC 0.006124 USD 97 376.126639
Kurswert in Handelswährung USD 596.33
Handelsgebühr USD 5.67
Total USD 602.00
Total in Kontowährung zum Kurs von USD/CHF 0.8949 538.73
Der Totalbetrag von CHF 538.73 wurde Ihrem Konto bD93 4072 6753 0009 6042 0 mit Valuta 01.12.2024 belastet.
Bitte prüfen Sie dieses Dokument und benachrichtigen Sie uns bei Unstimmigkeiten innert Monatsfrist.
Im Rahmen dieser Transaktion hat PostFinance als Kommissionärin gehandelt.
Freundliche Grüsse
PostFinance AG
0000011111888883333355555 DDDDDEEEEE 000000000000000000000000055555.....0000000000
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
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.hasFeed;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasFeedProperty;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasFees;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasForexGrossValue;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasGrossValue;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasIsin;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasName;
Expand Down Expand Up @@ -44,6 +47,7 @@
import name.abuchen.portfolio.datatransfer.actions.CheckCurrenciesAction;
import name.abuchen.portfolio.datatransfer.pdf.PDFInputFile;
import name.abuchen.portfolio.datatransfer.pdf.PostfinancePDFExtractor;
import name.abuchen.portfolio.datatransfer.pdf.TestCoinSearchProvider;
import name.abuchen.portfolio.model.Account;
import name.abuchen.portfolio.model.AccountTransaction;
import name.abuchen.portfolio.model.BuySellEntry;
Expand All @@ -54,10 +58,21 @@
import name.abuchen.portfolio.money.CurrencyUnit;
import name.abuchen.portfolio.money.Money;
import name.abuchen.portfolio.money.Values;
import name.abuchen.portfolio.online.SecuritySearchProvider;
import name.abuchen.portfolio.online.impl.CoinGeckoQuoteFeed;

@SuppressWarnings("nls")
public class PostfinancePDFExtractorTest
{
PostfinancePDFExtractor extractor = new PostfinancePDFExtractor(new Client())
{
@Override
protected List<SecuritySearchProvider> lookupCryptoProvider()
{
return TestCoinSearchProvider.cryptoProvider();
}
};

@Test
public void testWertpapierKauf01()
{
Expand Down Expand Up @@ -421,6 +436,38 @@ public void testWertpapierVerkauf01()
is(Money.of("CHF", Values.Amount.factorize(2.60))));
}

@Test
public void testCryptoKauf01()
{
List<Exception> errors = new ArrayList<>();

List<Item> results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "CryptoKauf01.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, "CHF");

// check security
assertThat(results, hasItem(security( //
hasIsin(null), hasWkn(null), hasTicker("BTC"), //
hasName("Bitcoin"), //
hasCurrencyCode("USD"), //
hasFeed(CoinGeckoQuoteFeed.ID), //
hasFeedProperty(CoinGeckoQuoteFeed.COINGECKO_COIN_ID, "bitcoin"))));

// check buy sell transaction
assertThat(results, hasItem(purchase( //
hasDate("2024-12-01T00:00"), hasShares(0.006124), //
hasSource("CryptoKauf01.txt"), //
hasNote("Auftrag 37463178"), //
hasAmount("CHF", 538.73), hasGrossValue("CHF", 533.66), //
hasForexGrossValue("USD", 596.33), //
hasTaxes("CHF", 0.00), hasFees("CHF", 5.07))));
}

@Test
public void testDividende01()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public PostfinancePDFExtractor(Client client)
addBankIdentifier("PostFinance");

addBuySellTransaction();
addBuySellCryptoTransaction();
addSettlementTransaction();
addDividendeTransaction();
addPaymentTransaction();
Expand Down Expand Up @@ -155,6 +156,87 @@ private void addBuySellTransaction()
addFeesSectionsTransaction(pdfTransaction, type);
}

private void addBuySellCryptoTransaction()
{
final DocumentType type = new DocumentType("Krypto");
this.addDocumentTyp(type);

Transaction<BuySellEntry> pdfTransaction = new Transaction<>();

Block firstRelevantLine = new Block("^Krypto .*$");
type.addBlock(firstRelevantLine);
firstRelevantLine.set(pdfTransaction);

pdfTransaction //

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

// @formatter:off
// Bitcoin BTC 0.006124 USD 97 376.126639
// @formatter:on
.section("name", "tickerSymbol", "currency") //
.match("^(?<name>.*) (?<tickerSymbol>[A-Z]+) [\\.'\\d]+ (?<currency>[\\w]{3}) [\\.'\\d]+ [\\.'\\d]+.*$") //
.assign((t, v) -> t.setSecurity(getOrCreateCryptoCurrency(v)))

// @formatter:off
// Bitcoin BTC 0.006124 USD 97 376.126639
// @formatter:on
.section("shares") //
.match("^.* [A-Z]+ (?<shares>[\\.'\\d]+) [\\w]{3} [\\.'\\d]+ [\\.'\\d]+.*$") //
.assign((t, v) -> t.setShares(asShares(v.get("shares"))))

// @formatter:off
// Ihr Auftrag wurde wie folgt am 01.12.2024 ausgeführt:
// @formatter:on
.section("date") //
.match("^Ihr Auftrag wurde wie folgt am[\\s]{1,}(?<date>[\\d]{2}\\.[\\d]{2}\\.[\\d]{4}) ausgef.hrt:.*$") //
.assign((t, v) -> t.setDate(asDate(v.get("date"))))

// @formatter:off
// Der Totalbetrag von CHF 538.73 wurde Ihrem Konto bD93 4072 6753 0009 6042 0 mit Valuta 01.12.2024 belastet.
// @formatter:on
.section("currency", "amount") //
.match("^Der Totalbetrag von[\\s]{1,}(?<currency>[\\w]{3}) (?<amount>[\\.'\\d]+) wurde Ihrem Konto .*$") //
.assign((t, v) -> {
t.setCurrencyCode(asCurrencyCode(v.get("currency")));
t.setAmount(asAmount(v.get("amount")));
})


// @formatter:off
// Kurswert in Handelswährung USD 596.33
// Total in Kontowährung zum Kurs von USD/CHF 0.8949 538.73
// @formatter:on
.section("fxGross", "termCurrency", "baseCurrency", "exchangeRate") //
.match("^Kurswert in Handelsw.hrung [\\w]{3} (?<fxGross>[\\.'\\d]+).*$") //
.match("^Total in Kontow.hrung zum Kurs von (?<baseCurrency>[\\w]{3})\\/(?<termCurrency>[\\w]{3}) (?<exchangeRate>[\\.'\\d]+) [\\.'\\d]+.*$") //
.assign((t, v) -> {
ExtrExchangeRate rate = asExchangeRate(v);
type.getCurrentContext().putType(rate);

Money fxGross = Money.of(rate.getBaseCurrency(), asAmount(v.get("fxGross")));
Money gross = rate.convert(rate.getTermCurrency(), fxGross);

checkAndSetGrossUnit(gross, fxGross, t, type.getCurrentContext());
})

// @formatter:off
// Kryptoportfolio 33-705809-6 Auftrag 37463178
// @formatter:on
.section("note").optional() //
.match("^Kryptoportfolio [\\d\\-]+ (?<note>Auftrag [\\d]+).*$") //
.assign((t, v) -> t.setNote(trim(v.get("note"))))

.wrap(BuySellEntryItem::new);

addTaxesSectionsTransaction(pdfTransaction, type);
addFeesSectionsTransaction(pdfTransaction, type);
}

private void addSettlementTransaction()
{
DocumentType type = new DocumentType("Transaktionsabrechnung: (Zeichnung|Fondssparplan)");
Expand Down Expand Up @@ -1085,6 +1167,13 @@ private <T extends Transaction<?>> void addFeesSectionsTransaction(T transaction
// @formatter:on
.section("currency", "fee").optional() //
.match("^B.rsengeb.hren und sonstige Spesen (?<currency>[\\w]{3}) (?<fee>[\\.'\\d\\s]+).*$") //
.assign((t, v) -> processFeeEntries(t, v, type))

// @formatter:off
// Handelsgebühr USD 5.67
// @formatter:on
.section("currency", "fee").optional() //
.match("^Handelsgeb.hr (?<currency>[\\w]{3}) (?<fee>[\\.'\\d\\s]+).*$") //
.assign((t, v) -> processFeeEntries(t, v, type));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -939,4 +939,5 @@ private boolean notEmpty(String s)
{
return s != null && s.length() > 0;
}

}

0 comments on commit 0143cc4

Please sign in to comment.