From d75c8e9e29318000744be628392f4e945f965e36 Mon Sep 17 00:00:00 2001 From: Alexander Ott Date: Fri, 3 Jan 2025 14:57:13 +0100 Subject: [PATCH] Modify Directa SIM PDF-Importer to support new transaction Closes #4445 --- .../datatransfer/pdf/directasim/Buy04.txt | 30 ++++ .../DirectaSimPDFExtractorTest.java | 80 +++++++++- .../pdf/DirectaSimPDFExtractor.java | 146 ++++++++++++++---- 3 files changed, 223 insertions(+), 33 deletions(-) create mode 100644 name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/directasim/Buy04.txt diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/directasim/Buy04.txt b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/directasim/Buy04.txt new file mode 100644 index 0000000000..1aec4fe70e --- /dev/null +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/directasim/Buy04.txt @@ -0,0 +1,30 @@ +PDFBox Version: 1.8.17 +Portfolio Performance Version: 0.73.0 +System: linux | x86_64 | 23 | Arch Linux +----------------------------------------- +Signor +NySCUtY IyWjXm +WTmNCh QIjIafB f BBszIwhEpy 7 +51961 kQjxQ btJ OukDTd' +Goszrd +PErSD O2036 +Nota Informativa per l'ordine c6954896449059 +Vi Confermiamo il Vs. ordine sopra riportato del 23.09.2023 Data scadenza 23.10.2023 +per l'acquisto di: 9 CROCS INC ISIN US2270461096 +sottostante cod. mult. 1 +eseguito per: 9 +tramite l'intermediario ViewTrade +Tipo di Operazione: Acquisto Mercato di esecuzione: Borsa - NASDAQ + Quantita' USD Euro Prezzo Valuta +24.09.2023 19:02:34 Richiesta Immissione 9 780,3000 732,88* 86,70 +25.09.2023 14:30:10 Inoltro +25.09.2023 14:30:15 In negoziazione +25.09.2023 15:30:14 Eseguito (09:30:14) 9 769,7700 722,99* 85,53 27.09.2023 + Controvalore: 769,7700 722,99* 85,53 + Commissioni: 9,0000 8,45* + Totale a Vs. Debito : 778,7700 731,44 +Gli effetti fiscali saranno disponibili a data valuta nel menu Info - 2b.Capital Gain +=C/$= 1,06470 +* - cambio provvisorio +Torino, 25.09.2023 + DIRECTA SIM diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/directasim/DirectaSimPDFExtractorTest.java b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/directasim/DirectaSimPDFExtractorTest.java index 29c2108422..c41637422f 100644 --- a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/directasim/DirectaSimPDFExtractorTest.java +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/directasim/DirectaSimPDFExtractorTest.java @@ -1,9 +1,11 @@ package name.abuchen.portfolio.datatransfer.pdf.directasim; +import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.check; 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.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; @@ -29,10 +31,15 @@ import org.junit.Test; import name.abuchen.portfolio.datatransfer.Extractor.Item; +import name.abuchen.portfolio.datatransfer.ImportAction.Status; import name.abuchen.portfolio.datatransfer.actions.AssertImportActions; +import name.abuchen.portfolio.datatransfer.actions.CheckCurrenciesAction; import name.abuchen.portfolio.datatransfer.pdf.DirectaSimPDFExtractor; import name.abuchen.portfolio.datatransfer.pdf.PDFInputFile; import name.abuchen.portfolio.model.Client; +import name.abuchen.portfolio.model.Portfolio; +import name.abuchen.portfolio.model.PortfolioTransaction; +import name.abuchen.portfolio.model.Security; import name.abuchen.portfolio.money.CurrencyUnit; @SuppressWarnings("nls") @@ -62,7 +69,7 @@ public void testSecurityBuy01() // check buy sell transaction assertThat(results, hasItem(purchase( // - hasDate("2024-01-05T14:02:36"), hasShares(29), // + hasDate("2024-01-05T14:02:36"), hasShares(29.00), // hasSource("Buy01.txt"), // hasNote("Ordine T1673620593440"), // hasAmount("EUR", 3079.29), hasGrossValue("EUR", 3074.29), // @@ -93,7 +100,7 @@ public void testSecurityBuy02() // check buy sell transaction assertThat(results, hasItem(purchase( // - hasDate("2024-01-09T11:57:34"), hasShares(2900), // + hasDate("2024-01-09T11:57:34"), hasShares(2900.00), // hasSource("Buy02.txt"), // hasNote("Ordine X4171246514720"), // hasAmount("EUR", 1511.58), hasGrossValue("EUR", 1502.08), // @@ -124,11 +131,78 @@ public void testSecurityBuy03() // check buy sell transaction assertThat(results, hasItem(purchase( // - hasDate("2024-04-12T09:04:21"), hasShares(7), // + hasDate("2024-04-12T09:04:21"), hasShares(7.00), // hasSource("Buy03.txt"), // hasNote("Ordine P9417565891845"), // hasAmount("EUR", 829.15), hasGrossValue("EUR", 829.15), // hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))); + } + + @Test + public void testSecurityBuy04() + { + DirectaSimPDFExtractor extractor = new DirectaSimPDFExtractor(new Client()); + List errors = new ArrayList<>(); + + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Buy04.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("US2270461096"), hasWkn(null), hasTicker(null), // + hasName("CROCS INC"), // + hasCurrencyCode("USD")))); + + // check buy sell transaction + assertThat(results, hasItem(purchase( // + hasDate("2023-09-25T15:30:14"), hasShares(9.00), // + hasSource("Buy04.txt"), // + hasNote("Ordine c6954896449059"), // + hasAmount("EUR", 731.44), hasGrossValue("EUR", 722.99), // + hasForexGrossValue("USD", 769.77), // + hasTaxes("EUR", 0.00), hasFees("EUR", 8.45)))); + } + + @Test + public void testSecurityBuy04WithSecurityInEUR() + { + Security security = new Security("CROCS INC", CurrencyUnit.EUR); + security.setIsin("US2270461096"); + + Client client = new Client(); + client.addSecurity(security); + + DirectaSimPDFExtractor extractor = new DirectaSimPDFExtractor(client); + + List errors = new ArrayList<>(); + + List results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Buy04.txt"), errors); + + assertThat(errors, empty()); + assertThat(countSecurities(results), is(0L)); + assertThat(countBuySell(results), is(1L)); + assertThat(countAccountTransactions(results), is(0L)); + assertThat(results.size(), is(1)); + new AssertImportActions().check(results, CurrencyUnit.EUR); + + // check buy sell transaction + assertThat(results, hasItem(purchase( // + hasDate("2023-09-25T15:30:14"), hasShares(9.00), // + hasSource("Buy04.txt"), // + hasNote("Ordine c6954896449059"), // + hasAmount("EUR", 731.44), hasGrossValue("EUR", 722.99), // + hasTaxes("EUR", 0.00), hasFees("EUR", 8.45), // + check(tx -> { + CheckCurrenciesAction c = new CheckCurrenciesAction(); + Status s = c.process((PortfolioTransaction) tx, new Portfolio()); + assertThat(s, is(Status.OK_STATUS)); + })))); } } diff --git a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/DirectaSimPDFExtractor.java b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/DirectaSimPDFExtractor.java index 1e59f5f6cf..037da55915 100644 --- a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/DirectaSimPDFExtractor.java +++ b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/DirectaSimPDFExtractor.java @@ -1,7 +1,10 @@ package name.abuchen.portfolio.datatransfer.pdf; +import static name.abuchen.portfolio.datatransfer.ExtractorUtils.checkAndSetGrossUnit; import static name.abuchen.portfolio.util.TextUtil.trim; +import name.abuchen.portfolio.datatransfer.ExtrExchangeRate; +import name.abuchen.portfolio.datatransfer.ExtractorUtils; import name.abuchen.portfolio.datatransfer.pdf.PDFParser.Block; import name.abuchen.portfolio.datatransfer.pdf.PDFParser.DocumentType; import name.abuchen.portfolio.datatransfer.pdf.PDFParser.Transaction; @@ -9,15 +12,7 @@ import name.abuchen.portfolio.model.Client; import name.abuchen.portfolio.model.PortfolioTransaction; import name.abuchen.portfolio.money.CurrencyUnit; - -/** - * @formatter:off - * @implNote Directa SIM is a pioneer in Italian online trading - * The currency is EUR --> €. - * - * @implSpec All security currencies are EUR --> €. - * @formatter:on - */ +import name.abuchen.portfolio.money.Money; @SuppressWarnings("nls") public class DirectaSimPDFExtractor extends AbstractPDFExtractor @@ -39,7 +34,24 @@ public String getLabel() private void addBuySellTransaction() { - DocumentType type = new DocumentType("acquisto di"); + final DocumentType type = new DocumentType("acquisto di", // + documentContext -> documentContext // + .oneOf( // + // @formatter:off + // Quantita' Euro Prezzo Valuta + // @formatter:on + section -> section // + .attributes("currency") // + .match("^[\\s]{1,}Quantita.[\\s]{1,}(?(Euro|USD))[\\s]{1,}Prezzo.*$") // + .assign((ctx, v) -> ctx.put("currency", normalizeCurrency(v.get("currency")))), + // @formatter:off + // Quantita' USD Euro Prezzo Valuta + // @formatter:on + section -> section // + .attributes("currency") // + .match("^[\\s]{1,}Quantita.[\\s]{1,}(Euro|USD)[\\s]{1,}(?(Euro|USD))[\\s]{1,}Prezzo.*$") // + .assign((ctx, v) -> ctx.put("currency", normalizeCurrency(v.get("currency")))))); + this.addDocumentTyp(type); Transaction pdfTransaction = new Transaction<>(); @@ -56,36 +68,92 @@ private void addBuySellTransaction() return portfolioTransaction; }) - // @formatter:off - // per l'acquisto di: 29 VANGUARD FTSE ALL-WORLD UCITS ISIN IE00BK5BQT80 - // @formatter:on - .section("name", "isin") // - .match("^.*: [\\.,\\d]+[\\s]{1,}(?.*) ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9]).*$") // - .assign((t, v) -> { - t.setCurrencyCode(CurrencyUnit.EUR); - t.setSecurity(getOrCreateSecurity(v)); - }) + .oneOf( // + // @formatter:off + // per l'acquisto di: 29 VANGUARD FTSE ALL-WORLD UCITS ISIN IE00BK5BQT80 + // Quantita' Euro Prezzo Valuta + // @formatter:on + section -> section // + .attributes("name", "isin", "currency") // + .match("^.*: [\\.,\\d]+[\\s]{1,}(?.*) ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9]).*$") // + .match("^[\\s]{1,}Quantita.[\\s]{1,}(?(Euro|USD))[\\s]{1,}Prezzo.*$") // + .assign((t, v) -> { + v.put("currency", normalizeCurrency(v.get("currency"))); + t.setSecurity(getOrCreateSecurity(v)); + }), + // @formatter:off + // EUR 1.000,00 15,00 % 97,28 % + // EUR 208.000,00 Bundesrep.Deutschland 100,00 % + // @formatter:on + section -> section // + .attributes("name", "isin", "currency") // + .match("^.*: [\\.,\\d]+[\\s]{1,}(?.*) ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9]).*$") // + .match("^[\\s]{1,}Quantita.[\\s]{1,}(?(Euro|USD))[\\s]{1,}(Euro|USD)[\\s]{1,}Prezzo.*$") // + .assign((t, v) -> { + v.put("currency", normalizeCurrency(v.get("currency"))); + t.setSecurity(getOrCreateSecurity(v)); + })) // @formatter:off - // per l'acquisto di: 29 VANGUARD FTSE ALL-WORLD UCITS ISIN IE00BK5BQT80 + // per l'acquisto di: 9 CROCS INC ISIN US2270461096 // @formatter:on .section("shares") // - .match("^.*: (?[\\.,\\d]+).* ISIN [A-Z]{2}[A-Z0-9]{9}[0-9].*$") // + .match("^.*: (?[\\.,\\d]+)[\\s]{1,}.* ISIN [A-Z]{2}[A-Z0-9]{9}[0-9].*$") // .assign((t, v) -> t.setShares(asShares(v.get("shares")))) // @formatter:off // 5.01.2024 14:02:36 Eseguito 29 3.074,29 106,0100 09.01.2024 + // 12.04.2024 09:03:49 Richiesta Immissione 7 118,5000 // @formatter:on .section("date", "time") // - .match("^(\\s)*(?[\\d]{1,2}\\.[\\d]{2}\\.[\\d]{4})[\\s]{1,}(?