diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/zuercherkantonalbank/Kauf05.txt b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/zuercherkantonalbank/Kauf05.txt new file mode 100644 index 0000000000..b8fd81518f --- /dev/null +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/zuercherkantonalbank/Kauf05.txt @@ -0,0 +1,48 @@ +PDFBox Version: 1.8.17 +Portfolio Performance Version: 0.67.3 +----------------------------------------- +Telefon 0844 850 860 +Adresse Bahnhofstrasse 9 +8001 Zürich +Briefadresse Postfach, 8010 Zürich +Clearing-Nr. 700 +BIC (SWIFT) ZKBKCHZZ80A +Depot-Nr. x-xxxx-xxxxxxx +Produkt frankly Sparen 3 +Wertschriftendepot +Lautend auf von Manfred Mustermann +Konto-Nr. xxxx-xxxx.xxx +IBAN CHxx xxxx xxxx xxxx xxxx x +Produkt frankly Sparen 3 Konto +Rubrik frankly +Zürich, 29. Februar 2020 Lautend auf von Manfred Mustermann +Abrechnung von Wertschriften +Ausgabe +Abschluss per: 28.12.2023 / Buchungstag: 29.12.2023 / Börsenplatz: Primärmarkt +Abwicklungs-Nr. 123 +Auftrags-Nr. 123 +Anteile - Units -NT CHF- Swisscanto (CH) IPF III (IPF III) - Swc (CH) IPF III Vorsorge Fonds 95 Passiv +Valor 51215778 / ISIN CH0512157782 +Stück CHF CHF +0.645 zu 154.9731 99.96 +Net Asset Value (NAV) 154.8362 +Total zu Ihren Lasten Valuta 04.01.2024 99.96 +Der Unterschied des abgerechneten Preises zum NAV erklärt sich mit Spesen, die gemäss Prospekt zum Schutz der +bestehenden Anleger zu Gunsten des Fondsvermögens erhoben werden. +Automatische Transaktion gemäss Ihrem gewählten Investitionsmodus. +Wir bitten Sie, diese Abrechnung zu kontrollieren und uns allfällige Fehler innert Monatsfrist mitzuteilen. Nach +Ablauf dieser Frist gilt die Abrechnung als genehmigt. +Freundliche Grüsse +Vorsorgestiftung Sparen 3 +der Zürcher Kantonalbank +Ohne manuelle Ergänzung +gültig ohne Unterschrift +ANAMHKENFKCIBLAJGKAJCOELENBJGNGNEIGK +AJCJFKEBEOKPNBNMFKAIPGGMIIFOMMGLHCLK +AMJOIBNGKKMHLLLGDKAOADJBJLHIGANLCMOK OY5361 Vorsorgestiftung Sparen 3 der +ACLCKFKGGDGKLMOABKAAMAALNGPEPALBLJHK +ALJKJDAKICIKBKICBKALIDBCBCBDAKACJCBK - Zürcher Kantonalbank +ACAJOCMECBBCMMNDPKAGLLKMILIFIACKPBHK +ACLPLPFEOFEICILMMKAMOOJMGOJMGLFEPMIK +AGLDJFLKLPBGGGHDGKACBMCENECNDOODDHFK 1/1 +AIKAACIGMEEGAAMIKKAGOEIAGCMKCOCGAMGK \ No newline at end of file diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/zuercherkantonalbank/Kauf06.txt b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/zuercherkantonalbank/Kauf06.txt new file mode 100644 index 0000000000..ae125445b3 --- /dev/null +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/zuercherkantonalbank/Kauf06.txt @@ -0,0 +1,32 @@ +PDFBox Version: 1.8.17 +Portfolio Performance Version: 0.67.3 +----------------------------------------- +Ph^0Tr7E538%8{4201069824} +Kontakt frankly Support +Telefon 0800 320 000 +Briefadresse Postfach, 8010 Zürich +P.P. A Postfach 715, 8010 Zürich Post CH AG +Herr +Manfred Mustermann +Musterstrasse 16 +1111 City +Baden, 31. Mai 2021 +Abrechnung von Wertschriften +Konto: xxxx-xxxx.xxx, lautend auf: Manfred Mustermann +Wir haben die folgende Transaktion für Sie getätigt: +Zeichnungen +Valor Bezeichnung Anzahl Kurs CHF Datum Total CHF +51215778 SWC (CH) IPF III VF 95 Passiv NT 0.633 157.95 28.05.2021 99.98 +Total Belastung Zeichnungen 99.98 +Im Kurs sind Ausgabespesen zu Gunsten Fonds (bei Zeichnungen) resp. Rücknahmespesen zu Gunsten Fonds +enthalten. Diese Spesen werden zum Schutz der bestehenden Investoren dem Fondsvermögen gutgeschrieben. +Wir bitten Sie, diese Abrechnung zu kontrollieren und uns allfällige Fehler innert Monatsfrist mitzuteilen. Nach +Ablauf dieser Frist gilt die Abrechnung als genehmigt. +Freundliche Grüsse +Vorsorgestiftung Sparen 3 +der Zürcher Kantonalbank +Ohne manuelle Ergänzung +Gültig ohne Unterschrift +Vorsorgestiftung Sparen 3 +der Zürcher Kantonalbank +1/1 \ No newline at end of file diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/zuercherkantonalbank/ZuercherKantonalbankPDFExtractorTest.java b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/zuercherkantonalbank/ZuercherKantonalbankPDFExtractorTest.java index 3334865292..7fb0d157ed 100644 --- a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/zuercherkantonalbank/ZuercherKantonalbankPDFExtractorTest.java +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/zuercherkantonalbank/ZuercherKantonalbankPDFExtractorTest.java @@ -286,6 +286,68 @@ public void testWertpapierKauf04WithSecurityInCHF() })))); } + @Test + public void testWertpapierKauf05() + { + ZuercherKantonalbankPDFExtractor extractor = new ZuercherKantonalbankPDFExtractor(new Client()); + + List errors = new ArrayList<>(); + + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Kauf05.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("CH0512157782"), hasWkn("51215778"), hasTicker(null), // + hasName("Swisscanto (CH) IPF III (IPF III)"), // + hasCurrencyCode("CHF")))); + + // check buy sell transaction + assertThat(results, hasItem(purchase( // + hasDate("2023-12-28T00:00"), hasShares(0.645), // + hasSource("Kauf05.txt"), // + hasNote("Abwicklungs-Nr. 123 | Auftrags-Nr. 123"), // + hasAmount("CHF", 99.96), hasGrossValue("CHF", 99.96), // + hasTaxes("CHF", 0.00), hasFees("CHF", 0.00)))); + } + + @Test + public void testWertpapierKauf06() + { + ZuercherKantonalbankPDFExtractor extractor = new ZuercherKantonalbankPDFExtractor(new Client()); + + List errors = new ArrayList<>(); + + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Kauf06.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("51215778"), hasTicker(null), // + hasName("SWC (CH) IPF III VF 95 Passiv NT"), // + hasCurrencyCode("CHF")))); + + // check buy sell transaction + assertThat(results, hasItem(purchase( // + hasDate("2021-05-28T00:00"), hasShares(0.633), // + hasSource("Kauf06.txt"), // + hasNote(null), // + hasAmount("CHF", 99.98), hasGrossValue("CHF", 99.98), // + hasTaxes("CHF", 0.00), hasFees("CHF", 0.00)))); + } + @Test public void testWertpapierVerkauf01() { diff --git a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/ZuercherKantonalbankPDFExtractor.java b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/ZuercherKantonalbankPDFExtractor.java index be9730b9cc..c2810d6399 100644 --- a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/ZuercherKantonalbankPDFExtractor.java +++ b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/ZuercherKantonalbankPDFExtractor.java @@ -49,12 +49,12 @@ public String getLabel() private void addBuySellTransaction() { - final DocumentType type = new DocumentType("Ihr (Kauf|Verkauf)"); + final DocumentType type = new DocumentType("(Ihr (Kauf|Verkauf)|Abrechnung von Wertschriften)"); this.addDocumentTyp(type); Transaction pdfTransaction = new Transaction<>(); - Block firstRelevantLine = new Block("^Gesch.fts\\-Nr\\..*$"); + Block firstRelevantLine = new Block("^(Gesch.fts\\-Nr\\..*|Ausgabe|Zeichnungen)$"); type.addBlock(firstRelevantLine); firstRelevantLine.set(pdfTransaction); @@ -74,34 +74,74 @@ private void addBuySellTransaction() t.setType(PortfolioTransaction.Type.SELL); }) - // @formatter:off - // Abwicklungs-Nr. 592473551 / Auftrags-Nr. ONBA-0005128022772021 - // Registered Shs Glencore PLC - // Valor 12964057 / ISIN JE00B4T3BW64 - // Stück GBP GBP - // @formatter:on - .section("name", "wkn", "isin", "currency") - .find("Abwicklungs\\-Nr\\..*") - .match("^(?.*)$") - .match("Valor (?[A-Z0-9]{5,9}) \\/ ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9])$") - .match("^St.ck (?[\\w]{3}) [\\w]{3}$") - .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))) + .oneOf( // + // @formatter:off + // Zeichnungen + // Valor Bezeichnung Anzahl Kurs CHF Datum Total CHF + // 51215778 SWC (CH) IPF III VF 95 Passiv NT 0.633 157.95 28.05.2021 99.98 + // @formatter:on + section -> section // + .attributes("currency", "wkn", "name") // + .match("^Valor Bezeichnung Anzahl Kurs (?[\\w]{3}) Datum Total [\\w]{3}$") + .match("^(?[A-Z0-9]{5,9}) (?.*) [\\.'\\d]+ [\\.'\\d]+ [\\d]{2}\\.[\\d]{2}\\.[\\d]{4} [\\.'\\d]+$") + .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))), + // @formatter:off + // Anteile - Units -NT CHF- Swisscanto (CH) IPF III (IPF III) - Swc (CH) IPF III Vorsorge Fonds 95 Passiv + // Valor 51215778 / ISIN CH0512157782 + // Stück GBP GBP + // @formatter:on + section -> section // + .attributes("currency", "name", "wkn", "isin") // + .match("^Anteile .* (?[\\w]{3})\\- (?.*) \\-.*$") + .match("Valor (?[A-Z0-9]{5,9}) \\/ ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9])$") + .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))), + // @formatter:off + // Abwicklungs-Nr. 592473551 / Auftrags-Nr. ONBA-0005128022772021 + // Registered Shs Glencore PLC + // Valor 12964057 / ISIN JE00B4T3BW64 + // Stück GBP GBP + // @formatter:on + section -> section // + .attributes("name", "wkn", "isin", "currency") // + .find("Abwicklungs\\-Nr\\..*") + .match("^(?.*)$") + .match("Valor (?[A-Z0-9]{5,9}) \\/ ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9])$") + .match("^St.ck (?[\\w]{3}) [\\w]{3}$") + .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v)))) - // @formatter:off - // Stück GBP GBP - // 1'000 zu 3.545 3'545.00 - // @formatter:on - .section("shares") // - .find("St.ck [\\w]{3} [\\w]{3}") // - .match("^(?[\\.'\\d]+) zu [\\.'\\d]+ [\\.'\\d]+$") // - .assign((t, v) -> t.setShares(asShares(v.get("shares")))) + .oneOf( // + // @formatter:off + // 51215778 SWC (CH) IPF III VF 95 Passiv NT 0.633 157.95 28.05.2021 99.98 + // @formatter:on + section -> section // + .attributes("shares") // + .match("^[A-Z0-9]{5,9} .* (?[\\.'\\d]+) [\\.'\\d]+ [\\d]{2}\\.[\\d]{2}\\.[\\d]{4} [\\.'\\d]+$") // + .assign((t, v) -> t.setShares(asShares(v.get("shares")))), + // @formatter:off + // Stück GBP GBP + // 1'000 zu 3.545 3'545.00 + // @formatter:on + section -> section // + .attributes("shares") // + .find("St.ck [\\w]{3} [\\w]{3}") // + .match("^(?[\\.'\\d]+) zu [\\.'\\d]+ [\\.'\\d]+$") // + .assign((t, v) -> t.setShares(asShares(v.get("shares"))))) - // @formatter:off - // Abschluss per: 04.10.2021 / Buchungstag: 04.10.2021 / Börsenplatz: LSE UK 1 CUR - // @formatter:on - .section("date") // - .match("^Abschluss per: (?[\\d]{2}\\.[\\d]{2}\\.[\\d]{4}).*$") // - .assign((t, v) -> t.setDate(asDate(v.get("date")))) + .oneOf( // + // @formatter:off + // Abschluss per: 04.10.2021 / Buchungstag: 04.10.2021 / Börsenplatz: LSE UK 1 CUR + // @formatter:on + section -> section // + .attributes("date") // + .match("^Abschluss per: (?[\\d]{2}\\.[\\d]{2}\\.[\\d]{4}).*$") // + .assign((t, v) -> t.setDate(asDate(v.get("date")))), + // @formatter:off + // 51215778 SWC (CH) IPF III VF 95 Passiv NT 0.633 157.95 28.05.2021 99.98 + // @formatter:on + section -> section // + .attributes("date") // + .match("^[A-Z0-9]{5,9} .* [\\.'\\d]+ [\\.'\\d]+ (?[\\d]{2}\\.[\\d]{2}\\.[\\d]{4}) [\\.'\\d]+$") // + .assign((t, v) -> t.setDate(asDate(v.get("date"))))) .oneOf( // // @formatter:off @@ -117,6 +157,18 @@ private void addBuySellTransaction() t.setCurrencyCode(asCurrencyCode(v.get("currency"))); }), // @formatter:off + // Valor Bezeichnung Anzahl Kurs CHF Datum Total CHF + // Total Belastung Zeichnungen 99.98 + // @formatter:on + section -> section // + .attributes("currency", "amount") // + .match("^Valor Bezeichnung Anzahl Kurs [\\w]{3} Datum Total (?[\\w]{3})$") + .match("^Total Belastung Zeichnungen (?[\\.'\\d]+)$") // + .assign((t, v) -> { + t.setAmount(asAmount(v.get("amount"))); + t.setCurrencyCode(asCurrencyCode(v.get("currency"))); + }), + // @formatter:off // Stück CHF CHF // Total zu Ihren Lasten Valuta 10.06.2022 7'294.30 // Total zu Ihren Gunsten Valuta 28.02.2023 8'512.11 @@ -157,19 +209,37 @@ private void addBuySellTransaction() type.getCurrentContext().put("fxCurrency", rate.getBaseCurrency()); }) - // @formatter:off - // Abwicklungs-Nr. 592473551 / Auftrags-Nr. ONBA-0005128022772021 - // @formatter:on - .section("note").optional() // - .match("^(?Abwicklungs\\-Nr\\..*) \\/.*$") // - .assign((t, v) -> t.setNote(trim(v.get("note")))) + .optionalOneOf( // + // @formatter:off + // Abwicklungs-Nr. 592473551 / Auftrags-Nr. ONBA-0005128022772021 + // @formatter:on + section -> section // + .attributes("note") // + .match("^(?Abwicklungs\\-Nr\\..*) \\/.*$") // + .assign((t, v) -> t.setNote(trim(v.get("note")))), + // @formatter:off + // Abwicklungs-Nr. 123 + // @formatter:on + section -> section // + .attributes("note") // + .match("^(?Abwicklungs\\-Nr\\..*)$") // + .assign((t, v) -> t.setNote(trim(v.get("note"))))) - // @formatter:off - // Abwicklungs-Nr. 592473551 / Auftrags-Nr. ONBA-0005128022772021 - // @formatter:on - .section("note").optional() // - .match("^.* \\/ (?Auftrags\\-Nr\\..*)$") // - .assign((t, v) -> t.setNote(concatenate(t.getNote(), trim(v.get("note")), " | "))) + .optionalOneOf( // + // @formatter:off + // Abwicklungs-Nr. 592473551 / Auftrags-Nr. ONBA-0005128022772021 + // @formatter:on + section -> section // + .attributes("note") // + .match("^.* \\/ (?Auftrags\\-Nr\\..*)$") // + .assign((t, v) -> t.setNote(concatenate(t.getNote(), trim(v.get("note")), " | "))), + // @formatter:off + // Abwicklungs-Nr. 123 + // @formatter:on + section -> section // + .attributes("note") // + .match("^(?Auftrags\\-Nr\\..*)$") // + .assign((t, v) -> t.setNote(concatenate(t.getNote(), trim(v.get("note")), " | ")))) .conclude(ExtractorUtils.fixGrossValueBuySell())