From 5ca873f0caa366acc0bc033a231ae8e5b5d56e1b Mon Sep 17 00:00:00 2001 From: Nirus2000 Date: Fri, 29 Dec 2023 19:39:38 +0100 Subject: [PATCH] Modify Fidelity International PDF-Importer to support new transaction https://forum.portfolio-performance.info/t/pdf-import-from-fidelity-international/26191/2 https://forum.portfolio-performance.info/t/pdf-import-from-fidelity-international/26191/3 --- ...FidelityInternationalPDFExtractorTest.java | 62 +++++++++++ .../fidelityinternational/SecurityBuy01.txt | 38 +++++++ .../fidelityinternational/SecuritySale02.txt | 29 +++++ .../FidelityInternationalPDFExtractor.java | 101 ++++++++++++------ 4 files changed, 199 insertions(+), 31 deletions(-) create mode 100644 name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/fidelityinternational/SecurityBuy01.txt create mode 100644 name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/fidelityinternational/SecuritySale02.txt diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/fidelityinternational/FidelityInternationalPDFExtractorTest.java b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/fidelityinternational/FidelityInternationalPDFExtractorTest.java index 644adc5268..41db95efbc 100644 --- a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/fidelityinternational/FidelityInternationalPDFExtractorTest.java +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/fidelityinternational/FidelityInternationalPDFExtractorTest.java @@ -38,6 +38,37 @@ @SuppressWarnings("nls") public class FidelityInternationalPDFExtractorTest { + @Test + public void testSecurityBuy01() + { + FidelityInternationalPDFExtractor extractor = new FidelityInternationalPDFExtractor(new Client()); + + List errors = new ArrayList<>(); + + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "SecurityBuy01.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, "USD"); + + // check security + assertThat(results, hasItem(security( // + hasIsin(null), hasWkn("31620M106"), hasTicker("FIS"), // + hasName("FIDELITY NATL"), // + hasCurrencyCode("USD")))); + + // check buy sell transaction + assertThat(results, hasItem(purchase( // + hasDate("2023-12-08T00:00"), hasShares(7.5146), // + hasSource("SecurityBuy01.txt"), // + hasNote("Ref. No. 23342-0D6SVL"), // + hasAmount("USD", 442.83), hasGrossValue("USD", 442.83), // + hasTaxes("USD", 0.00), hasFees("USD", 0.00)))); + } + @Test public void testSecuritySale01() { @@ -69,6 +100,37 @@ public void testSecuritySale01() hasTaxes("USD", 0.00), hasFees("USD", 0.13)))); } + @Test + public void testSecuritySale02() + { + FidelityInternationalPDFExtractor extractor = new FidelityInternationalPDFExtractor(new Client()); + + List errors = new ArrayList<>(); + + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "SecuritySale02.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, "USD"); + + // check security + assertThat(results, hasItem(security( // + hasIsin(null), hasWkn("31620M106"), hasTicker("FIS"), // + hasName("FIDELITY NATL"), // + hasCurrencyCode("USD")))); + + // check buy sell transaction + assertThat(results, hasItem(sale( // + hasDate("2022-07-19T00:00"), hasShares(30), // + hasSource("SecuritySale02.txt"), // + hasNote("Ref. No. 33107-zz91lf"), // + hasAmount("USD", 2887.43), hasGrossValue("USD", 2887.50), // + hasTaxes("USD", 0.00), hasFees("USD", 0.07)))); + } + @Test public void testTradeConfirmation01() { diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/fidelityinternational/SecurityBuy01.txt b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/fidelityinternational/SecurityBuy01.txt new file mode 100644 index 0000000000..0a1ed80624 --- /dev/null +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/fidelityinternational/SecurityBuy01.txt @@ -0,0 +1,38 @@ +PDFBox Version: 1.8.17 +Portfolio Performance Version: 0.66.2 +----------------------------------------- +FIDELITY NATIONAL INFORMATION +SERVICES +PARTICIPANT NO. +U98299884 +90035485 + NIK HEMMERYCKX + XjlaqpRajgteel 88 FIDELITY STOCK PLAN SERVICES, LLC + ANTWERPEN P.O. BOX 770001 + BELGIUM 2000 CINCINNATI, OH 45277-0003 +TELEPHONE NUMBER:(800) 544-0275 +REF # 23342-0D6SVL +CUSTOMER NO. PARTICIPANT ID. TYPE REG.REP. TRADE DATE SETTLEMENT DATE TRANS NO. CUSIP NO. ORIG. +U98299884 1 000 12-08-23 12-12-23 0D6SVL 31620M106 +Confirmation of purchase made through your +FIS ESPP on DEC/08/2023. PURCHASE INFORMATION +Offering period: JAN/01/2023 - DEC/31/2023 Market Value at Purchase¹ $442.83 +Accumulated Contributions* $442.83 +YOU PURCHASED 7.5146 AT $58.9290 PURCHASE PRICE Gain² $0.00 +SECURITY DESCRIPTION SYMBOL: FIS EXPLANATION OF PROCEEDS +FIDELITY NATL +The shares you have acquired are subject to a "Restriction Period" in accordance with Total Gain $0.00 +your company's stock plan rules. Please see your plan documents for more +information. Provided below are the dates for when the restriction on these shares will +be removed. Share Proceeds** 7.5146 +Sale Availability: 06/05/2024 +Transfer Availability: 06/05/2024 +PLEASE RETAIN THIS STATEMENT FOR YOUR RECORDS +* Accumulated Contributions are the amount of money contributed to your Plan that is +used toward the purchase of company stock. +** Shares will be held by Fidelity Stock Plan Services on your behalf in the SPS +Participant Trust. +¹ Market Value is determined under your Plan rules. +² Please consult your tax advisor regarding tax treatment in your jurisdiction. +PAGE 1 OF 1 Recordkeeping and administrative services for your company's equity compensation plans are +provided by your company and its service providers. \ No newline at end of file diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/fidelityinternational/SecuritySale02.txt b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/fidelityinternational/SecuritySale02.txt new file mode 100644 index 0000000000..548155ab59 --- /dev/null +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/fidelityinternational/SecuritySale02.txt @@ -0,0 +1,29 @@ +PDFBox Version: 1.8.17 +Portfolio Performance Version: 0.66.2 +----------------------------------------- +PARTICIPANT NO. +U98299884 +90017770 + NIK HEMMERYCKX + flBJHBJAntgwOx 84 + ANTWERPEN + BELGIUM 2000 FIDELITY STOCK PLAN SERVICES, LLC +P.O. BOX 770001 +CINCINNATI, OH 45277-0003 +TELEPHONE NUMBER:(800) 544-0275 +REF # 33107-zz91lf +CUSTOMER NO. PARTICIPANT ID. TYPE REG.REP. TRADE DATE SETTLEMENT DATE TRANS NO. CUSIP NO. ORIG. +U98299884 1 WI# 07-19-22 07-21-22 KS51HX 31620M106 +YOU SOLD 30 AT 96.2500 + EXPLANATION OF PROCEEDS +SECURITY DESCRIPTION SYMBOL: FIS Sale Proceeds $2,887.50 +FIDELITY NATL +DETAILS: Total Fees $0.07 +Sale Date: JUL/19/2022 +Proceeds Available: JUL/21/2022 +Plan Type: COMPANY STOCK PLAN +Net Cash Proceeds¹ -$2,887.43 +PLEASE RETAIN THIS STATEMENT FOR YOUR RECORDS +¹ Your proceeds will be held by Fidelity in your trust. Please contact Fidelity Stock Plan Services to request a distribution. +PAGE 1 OF 1 Recordkeeping and administrative services for your company's equity compensation plans are +provided by your company and its service providers. \ No newline at end of file diff --git a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/FidelityInternationalPDFExtractor.java b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/FidelityInternationalPDFExtractor.java index 8d71bba391..6e118fe76f 100644 --- a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/FidelityInternationalPDFExtractor.java +++ b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/FidelityInternationalPDFExtractor.java @@ -43,7 +43,7 @@ public String getLabel() private void addBuySellTransaction() { - DocumentType type = new DocumentType("YOU SOLD"); + DocumentType type = new DocumentType("YOU (PURCHASED|SOLD)"); this.addDocumentTyp(type); Transaction pdfTransaction = new Transaction<>(); @@ -62,52 +62,91 @@ private void addBuySellTransaction() // Is type --> "SOLD" change from BUY to SELL .section("type").optional() // - .match("^YOU (?SOLD) [\\.,\\d]+.*$") // + .match("^YOU (?(PURCHASED|SOLD)) [\\.,\\d]+.*$") // .assign((t, v) -> { if ("SOLD".equals(v.get("type"))) t.setType(PortfolioTransaction.Type.SELL); }) - // @formatter:off - // I00123456 1 WI# 12-12-23 12-14-23 K9T1Q9 11135F101 - // SECURITY DESCRIPTION SYMBOL: AAPL Sale Proceeds $2,423.54 - // APPLE INC - // @formatter:off - .section("wkn", "tickerSymbol", "currency", "name") // - .match("^.* [\\d]{2}\\-[\\d]{2}\\-[\\d]{2} [\\d]{2}\\-[\\d]{2}\\-[\\d]{2} [\\w]+ (?[A-Z0-9]+)$") // - .match("^SECURITY DESCRIPTION SYMBOL: (?[A-Z]{2,}) .* (?\\p{Sc})[\\.,\\d]+$") // - .match("^(?.*)$") // - .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))) + .oneOf( // + // @formatter:off + // U98299884 1 000 12-08-23 12-12-23 0D6SVL 31620M106 + // YOU PURCHASED 7.5146 AT $58.9290 PURCHASE PRICE Gain² $0.00 + // SECURITY DESCRIPTION SYMBOL: FIS EXPLANATION OF PROCEEDS + // FIDELITY NATL + // @formatter:on + section -> section // + .attributes("wkn", "tickerSymbol", "currency", "name") // + .match("^.* [\\d]{2}\\-[\\d]{2}\\-[\\d]{2} [\\d]{2}\\-[\\d]{2}\\-[\\d]{2} [\\w]+ (?[A-Z0-9]+)$") // + .match("^YOU PURCHASED [\\.,\\d]+ AT (?\\p{Sc})[\\.,\\d]+.*$") // + .match("^SECURITY DESCRIPTION SYMBOL: (?[A-Z]{2,}) .*$") // + .match("^(?.*)$") // + .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))), + // @formatter:off + // I00123456 1 WI# 12-12-23 12-14-23 K9T1Q9 11135F101 + // YOU SOLD 14 AT 173.1100 + // SECURITY DESCRIPTION SYMBOL: AAPL Sale Proceeds $2,423.54 + // APPLE INC + // @formatter:on + section -> section // + .attributes("wkn", "tickerSymbol", "currency", "name") // + .match("^.* [\\d]{2}\\-[\\d]{2}\\-[\\d]{2} [\\d]{2}\\-[\\d]{2}\\-[\\d]{2} [\\w]+ (?[A-Z0-9]+)$") // + .match("^YOU SOLD [\\.,\\d]+.*$") // + .match("^SECURITY DESCRIPTION SYMBOL: (?[A-Z]{2,}) .* (?\\p{Sc})[\\.,\\d]+$") // + .match("^(?.*)$") // + .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v)))) - // @formatter:off - // Sale Date: DEC/12/2023 - // @formatter:on - .section("date") // - .match("^Sale Date: (?[\\w]{3}\\/[\\d]{2}\\/[\\d]{4})$") // - .assign((t, v) -> t.setDate(asDate(v.get("date")))) + .oneOf( // + // @formatter:off + // Sale Date: DEC/12/2023 + // @formatter:on + section -> section // + .attributes("date") // + .match("^Sale Date: (?[\\w]{3}\\/[\\d]{2}\\/[\\d]{4})$") // + .assign((t, v) -> t.setDate(asDate(v.get("date")))), + // @formatter:off + // FIS ESPP on DEC/08/2023. PURCHASE INFORMATION + // @formatter:on + section -> section // + .attributes("date") // + .match("^.* (?[\\w]{3}\\/[\\d]{2}\\/[\\d]{4})\\. PURCHASE INFORMATION$") // + .assign((t, v) -> t.setDate(asDate(v.get("date"))))) // @formatter:off // YOU SOLD 14 AT 173.1100 + // YOU PURCHASED 7.5146 AT $58.9290 PURCHASE PRICE Gain² $0.00 // @formatter:on .section("shares") // - .match("^YOU SOLD (?[\\.,\\d]+).*$") // + .match("^YOU (PURCHASED|SOLD) (?[\\.,\\d]+).*$") // .assign((t, v) -> t.setShares(asShares(v.get("shares")))) - // @formatter:off - // Net Cash Proceeds¹ -$2,423.41 - // @formatter:on - .section("currency", "amount") // - .match("^Net Cash Proceeds.* \\-(?\\p{Sc})(?[\\.,\\d]+)$") // - .assign((t, v) -> { - t.setCurrencyCode(asCurrencyCode(v.get("currency"))); - t.setAmount(asAmount(v.get("amount"))); - }) + .oneOf( // + // @formatter:off + // Net Cash Proceeds¹ -$2,423.41 + // @formatter:on + section -> section // + .attributes("currency", "amount") // + .match("^Net Cash Proceeds.* \\-(?\\p{Sc})(?[\\.,\\d]+)$") // + .assign((t, v) -> { + t.setCurrencyCode(asCurrencyCode(v.get("currency"))); + t.setAmount(asAmount(v.get("amount"))); + }), + // @formatter:off + // Accumulated Contributions* $442.83 + // @formatter:on + section -> section // + .attributes("currency", "amount") // + .match("^Accumulated Contributions.* (?\\p{Sc})(?[\\.,\\d]+)$") // + .assign((t, v) -> { + t.setCurrencyCode(asCurrencyCode(v.get("currency"))); + t.setAmount(asAmount(v.get("amount"))); + })) // @formatter:off // REF # 23346-K9T1Q9 // @formatter:on .section("note").optional() // - .match("^REF # (?[A-Z0-9]+\\-[A-Z0-9]+)$") // + .match("^REF # (?[\\w]+\\-[\\w]+)$") // .assign((t, v) -> t.setNote("Ref. No. " + v.get("note"))) .wrap(BuySellEntryItem::new); @@ -227,14 +266,14 @@ private void addSummaryStatementBuySellTransaction() // 20123-1XXXXX 1* WK# 01-04-21 01-06-21 46428Q109 20123-XXXXX // @formatter:on .section("note").optional() // - .match("^(?[A-Z0-9]+\\-[A-Z0-9]+) .*$") // + .match("^(?[\\w]+\\-[\\w]+) .*$") // .assign((t, v) -> t.setNote("Ref. No. " + v.get("note"))) // @formatter:off // 20123-1XXXXX 1* WK# 01-04-21 01-06-21 46428Q109 20123-XXXXX // @formatter:on .section("note").optional() // - .match("^[A-Z0-9]+\\-[A-Z0-9]+ .* (?[A-Z0-9]+\\-[A-Z0-9]+)$") // + .match("^[\\w]+\\-[\\w]+ .* (?[\\w]+\\-[\\w]+)$") // .assign((t, v) -> t.setNote(concatenate(t.getNote(), v.get("note"), " | Ord. No. "))) .wrap(BuySellEntryItem::new);