Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify PostFinance AG PDF-Importer to support new transaction #3477

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,32 @@
PDFBox Version: 1.8.17
Portfolio Performance Version: 0.64.5
-----------------------------------------
PostFinance AG
Sie werden betreut von
Iris Soldat und Team Post CH AG
Telefon +41 848 888 700
www.postfinance.ch P.P. CH-4808 Zofingen A-PRIORITY
Herr
Max Mustermann
Mustermann Max Musterstrasse 75 C
Musterhausen 3857 Musterhausen
Transaktionsabrechnung: Fondssparplan Seite: 1 / 2
Selfservice Fonds Datum: 31.07.2023
Depot 97-133982-4 Ort der Ausführung Over-the-Counter
Auftrag 15186945
Gemäss Ihrem Auftrag haben wir folgende Transaktion vorgenommen:
Position Anteile Währung Kurs
PF - Global Fund A 1.216 CHF 162.830
ISIN CH0014933193
Kurswert in Handelswährung CHF 198.02
Kommission CHF 1.98
Total CHF 200.00
Der Totalbetrag von CHF 200.00 wurde Ihrem Konto CH81 0900 1234 8952 2587 6 mit Valuta 02.08.2023 belastet.
01521 DE 000035.00
Transaktionsabrechnung: Fondssparplan
Depot 97-123456-4 Datum: 31.07.2023 Seite: 2 / 2
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
01521 DE 000035.00
@@ -1,10 +1,5 @@
package name.abuchen.portfolio.datatransfer.pdf.postfinance;

import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.deposit;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasAmount;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasDate;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasNote;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasSource;
import static name.abuchen.portfolio.datatransfer.ExtractorTestUtilities.countAccountTransactions;
import static name.abuchen.portfolio.datatransfer.ExtractorTestUtilities.countBuySell;
import static name.abuchen.portfolio.datatransfer.ExtractorTestUtilities.countSecurities;
Expand All @@ -14,6 +9,23 @@
import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.junit.Assert.assertNull;

import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.deposit;
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 java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Iterator;
Expand Down Expand Up @@ -331,6 +343,35 @@ public void testWertpapierKauf04WithSecurityInCHF()
assertThat(s, is(Status.OK_STATUS));
}

@Test
public void testWertpapierKauf05()
{
PostfinancePDFExtractor extractor = new PostfinancePDFExtractor(new Client());

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

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

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("CH0014933193"), hasWkn(null), hasTicker(null), //
hasName("PF - Global Fund A"), //
hasCurrencyCode("CHF"))));

// check buy sell transaction
assertThat(results, hasItem(purchase( //
hasDate("2023-07-31T00:00"), hasShares(1.216), //
hasSource("Kauf05.txt"), hasNote("Auftrag 15186945"), //
hasAmount("CHF", 200.00), hasGrossValue("CHF", 198.02), //
hasTaxes("CHF", 0.00), hasFees("CHF", 1.98))));
}

@Test
public void testWertpapierVerkauf01()
{
Expand Down
@@ -1,6 +1,7 @@
package name.abuchen.portfolio.datatransfer.pdf;

import static name.abuchen.portfolio.datatransfer.ExtractorUtils.checkAndSetGrossUnit;

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

Expand Down Expand Up @@ -120,20 +121,18 @@ private void addBuySellTransaction()
// @formatter:off
// 55 49.76 EUR 2'736.80
// Wechselkurs 1.08279
// Zu Ihren Lasten CHF 2'968.50
// @formatter:on
.section("fxCurrency", "fxGross", "exchangeRate", "currency").optional()
.match("^[\\.,'\\d\\s]+ [\\.,'\\d\\s]+ (?<fxCurrency>[\\w]{3}) (?<fxGross>[\\.,'\\d\\s]+).*$")
.section("baseCurrency", "fxGross", "exchangeRate", "termCurrency").optional()
.match("^[\\.,'\\d\\s]+ [\\.,'\\d\\s]+ (?<baseCurrency>[\\w]{3}) (?<fxGross>[\\.,'\\d\\s]+).*$")
.match("^Wechselkurs (?<exchangeRate>[\\.,'\\d\\s]+).*$")
.match("^Zu Ihren (Lasten|Gunsten) (?<currency>[\\w]{3}) [\\.,'\\d\\s]+.*$")
.match("^Zu Ihren (Lasten|Gunsten) (?<termCurrency>[\\w]{3}) [\\.,'\\d\\s]+.*$")
.assign((t, v) -> {
v.put("baseCurrency", asCurrencyCode(v.get("fxCurrency")));
v.put("termCurrency", asCurrencyCode(v.get("currency")));

ExtrExchangeRate rate = asExchangeRate(v);
type.getCurrentContext().putType(rate);

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

checkAndSetGrossUnit(gross, fxGross, t, type.getCurrentContext());
})
Expand All @@ -155,7 +154,7 @@ private void addBuySellTransaction()

private void addSettlementTransaction()
{
DocumentType type = new DocumentType("Transaktionsabrechnung: Zeichnung");
DocumentType type = new DocumentType("Transaktionsabrechnung: (Zeichnung|Fondssparplan)");
this.addDocumentTyp(type);

Transaction<BuySellEntry> pdfTransaction = new Transaction<>();
Expand All @@ -165,39 +164,73 @@ private void addSettlementTransaction()
return entry;
});

Block firstRelevantLine = new Block("^Transaktionsabrechnung: Zeichnung Seite: .*$");
Block firstRelevantLine = new Block("^Transaktionsabrechnung: (Zeichnung|Fondssparplan) Seite: .*$");
type.addBlock(firstRelevantLine);
firstRelevantLine.set(pdfTransaction);

pdfTransaction
// @formatter:off
// Pictet - Japan Index - I JPY 1.441 JPY 23 608.200
// ISIN LU0188802960
// @formatter:on
.section("name", "currency", "isin")
.match("^(?<name>.*) [\\w]{3} [\\.,'\\d\\s]+ (?<currency>[\\w]{3}) [\\.,'\\d\\s]+.*$")
.match("^ISIN (?<isin>[A-Z]{2}[A-Z0-9]{9}[0-9]).*$")
.assign((t, v) -> t.setSecurity(getOrCreateSecurity(v)))
.oneOf(
// @formatter:off
// Position Anteile Währung Kurs
// Pictet - Japan Index - I JPY 1.441 JPY 23 608.200
// ISIN LU0188802960
// @formatter:on
section -> section
.attributes("isin", "name", "currency")
.find("Position Anteile W.hrung Kurs.*")
.match("^(?<name>.*) [\\w]{3} [\\.,'\\d\\s]+ (?<currency>[\\w]{3}) [\\.,'\\d\\s]+.*$")
.match("^ISIN (?<isin>[A-Z]{2}[A-Z0-9]{9}[0-9]).*$")
.assign((t, v) -> t.setSecurity(getOrCreateSecurity(v)))
,
// @formatter:off
// Position Anteile Währung Kurs
// PF - Global Fund A 1.216 CHF 162.830
// ISIN CH0014933193
// @formatter:on
section -> section
.attributes("name", "isin", "currency")
.find("Position Anteile W.hrung Kurs.*")
.match("^(?<name>.*) [\\.,'\\d\\s]+ (?<currency>[\\w]{3}) [\\.,'\\d\\s]+.*$")
.match("^ISIN (?<isin>[A-Z]{2}[A-Z0-9]{9}[0-9]).*$")
.assign((t, v) -> t.setSecurity(getOrCreateSecurity(v)))
)

// @formatter:off
// Pictet - Japan Index - I JPY 1.441 JPY 23 608.200
// @formatter:on
.section("shares")
.match("^(?<name>.*) [\\w]{3} (?<shares>[\\.,'\\d\\s]+) [\\w]{3} [\\.,'\\d\\s]+.*$")
.assign((t, v) -> t.setShares(asShares(v.get("shares"))))
.oneOf(
// @formatter:off
// Position Anteile Währung Kurs
// Pictet - Japan Index - I JPY 1.441 JPY 23 608.200
// @formatter:on
section -> section
.attributes("shares")
.find("Position Anteile W.hrung Kurs.*")
.match("^.* [\\w]{3} (?<shares>[\\.,'\\d\\s]+) [\\w]{3} [\\.,'\\d\\s]+.*$")
.assign((t, v) -> t.setShares(asShares(v.get("shares"))))
,
// @formatter:off
// Position Anteile Währung Kurs
// PF - Global Fund A 1.216 CHF 162.830
// @formatter:on
section -> section
.attributes("shares")
.find("Position Anteile W.hrung Kurs.*")
.match("^.* (?<shares>[\\.,'\\d\\s]+) [\\w]{3} [\\.,'\\d\\s]+.*$")
.assign((t, v) -> t.setShares(asShares(v.get("shares"))))
)

// @formatter:off
// E-Vermögensverwaltung Datum: 20.12.2021
// Selfservice Fonds Datum: 31.07.2023
// @formatter:on
.section("date")
.match("^E\\-Verm.gensverwaltung Datum: (?<date>[\\d]{2}\\.[\\d]{2}\\.[\\d]{4}).*$")
.match("^(E\\-Verm.gensverwaltung|Selfservice Fonds) Datum: (?<date>[\\d]{2}\\.[\\d]{2}\\.[\\d]{4}).*$")
.assign((t, v) -> t.setDate(asDate(v.get("date"))))

// @formatter:off
// Der Totalbetrag von CHF 280.91 wurde Ihrem Konto CH11 0100 0000 1111 1111 1 mit Valuta 21.12.2021 belastet.
// Der Totalbetrag von CHF 200.00 wurde Ihrem Konto CH81 0900 1234 8952 2587 6 mit Valuta 02.08.2023 belastet.
// @formatter:on
.section("currency", "amount")
.match("^Der Totalbetrag von (?<currency>[\\w]{3}) (?<amount>[\\.,'\\d\\s]+) .*$")
.match("^Der Totalbetrag von ([\\s]+)?(?<currency>[\\w]{3}) (?<amount>[\\.,'\\d\\s]+) .*$")
.assign((t, v) -> {
t.setAmount(asAmount(v.get("amount")));
t.setCurrencyCode(asCurrencyCode(v.get("currency")));
Expand All @@ -207,15 +240,15 @@ private void addSettlementTransaction()
// Kurswert in Handelswährung JPY 34 019.00
// Total in Kontowährung zum Kurs von JPY/CHF 0.0082450 CHF 280.91
// @formatter:on
.section("fxCurrency", "fxGross", "termCurrency", "baseCurrency", "exchangeRate", "currency").optional()
.match("^Kurswert in Handelsw.hrung (?<fxCurrency>[\\w]{3}) (?<fxGross>[\\.,'\\d\\s]+).*$")
.match("^Total in Kontow.hrung zum Kurs von (?<baseCurrency>[\\w]{3})\\/(?<termCurrency>[\\w]{3}) (?<exchangeRate>[\\.,'\\d\\s]+) (?<currency>[\\w]{3}) [\\.,'\\d\\s]+.*$")
.section("fxGross", "baseCurrency", "termCurrency", "exchangeRate").optional()
.match("^Kurswert in Handelsw.hrung [\\w]{3} (?<fxGross>[\\.,'\\d\\s]+).*$")
.match("^Total in Kontow.hrung zum Kurs von (?<baseCurrency>[\\w]{3})\\/(?<termCurrency>[\\w]{3}) (?<exchangeRate>[\\.,'\\d\\s]+) [\\w]{3} [\\.,'\\d\\s]+.*$")
.assign((t, v) -> {
ExtrExchangeRate rate = asExchangeRate(v);
type.getCurrentContext().putType(rate);

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

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