From 437119b978e8ca9bba62268af35484c5f186e834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Aubrecht?= Date: Fri, 6 Mar 2020 20:36:42 +0100 Subject: [PATCH] Added support for parsing of dividends from Revolut. --- .vscode/launch.json | 4 +- .../StatementParser/Models/Broker.cs | 2 +- .../PdfModels/ActivityDividendModel.cs | 22 ++++++ .../Revolut/PdfModels/StatementModel.cs | 15 ++++ .../Brokers/Revolut/RevolutStatementParser.cs | 71 +++++++++++++++++++ .../StatementParser/TransactionParser.cs | 2 + 6 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 StatementParser/StatementParser/Parsers/Brokers/Revolut/PdfModels/ActivityDividendModel.cs create mode 100644 StatementParser/StatementParser/Parsers/Brokers/Revolut/PdfModels/StatementModel.cs create mode 100644 StatementParser/StatementParser/Parsers/Brokers/Revolut/RevolutStatementParser.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index b611f3d..1ca86f7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,8 +12,8 @@ // If you have changed target frameworks, make sure to update the program path. "program": "${workspaceFolder}/StatementParser/TaxReporterCLI/bin/Debug/netcoreapp3.0/TaxReporterCLI.dll", //"args": ["-x", "/Users/vladimiraubrecht/Documents/Taxes/2019/Report2019.xlsx", "/Users/vladimiraubrecht/Documents/Taxes/2019/Statements"], - //"args": ["/Users/vladimiraubrecht/Downloads/03-31-stat.pdf"], - "args": ["-x", "/Users/vladimiraubrecht/Documents/Taxes/2019/test.xlsx", "/Users/vladimiraubrecht/Documents/Taxes/2019/Statements/"], + "args": ["/Users/vladimiraubrecht/Library/Mobile Documents/com~apple~CloudDocs/Downloads/d6fe7c6b-56a2-41e1-9118-b079e607ace7.pdf"], + //"args": ["-x", "/Users/vladimiraubrecht/Documents/Taxes/2019/test.xlsx", "/Users/vladimiraubrecht/Documents/Taxes/2019/Statements/"], "cwd": "${workspaceFolder}/StatementParser/TaxReporterCLI", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console "console": "internalConsole", diff --git a/StatementParser/StatementParser/Models/Broker.cs b/StatementParser/StatementParser/Models/Broker.cs index a6a86a1..16f8cac 100644 --- a/StatementParser/StatementParser/Models/Broker.cs +++ b/StatementParser/StatementParser/Models/Broker.cs @@ -1,4 +1,4 @@ namespace StatementParser.Models { - public enum Broker { MorganStanley, Fidelity, FxChoice, Degiro, Lynx } + public enum Broker { MorganStanley, Fidelity, FxChoice, Degiro, Lynx, Revolut } } diff --git a/StatementParser/StatementParser/Parsers/Brokers/Revolut/PdfModels/ActivityDividendModel.cs b/StatementParser/StatementParser/Parsers/Brokers/Revolut/PdfModels/ActivityDividendModel.cs new file mode 100644 index 0000000..24548dd --- /dev/null +++ b/StatementParser/StatementParser/Parsers/Brokers/Revolut/PdfModels/ActivityDividendModel.cs @@ -0,0 +1,22 @@ +using System; +using ASoft.TextDeserializer.Attributes; + +namespace StatementParser.Parsers.Brokers.Revolut.PdfModels +{ + [DeserializeByRegex("(?[0-9]{2}/[0-9]{2}/[0-9]{4})(?[0-9]{2}/[0-9]{2}/[0-9]{4})(?[A-Z]{3})(?DIVNRA|DIV)(?.+?)00.00(?\\(?\\d+\\.\\d{2}\\)?)")] + internal class ActivityDividendModel + { + public DateTime TradeDate { get; set; } + + public DateTime SettleDate { get; set; } + + public string Currency { get; set; } + + public string ActivityType { get; set; } + + public string Description { get; set; } + + public decimal Amount => decimal.Parse(AmountRaw.Replace('(', '-').TrimEnd(')')); + public string AmountRaw { get; set; } + } +} diff --git a/StatementParser/StatementParser/Parsers/Brokers/Revolut/PdfModels/StatementModel.cs b/StatementParser/StatementParser/Parsers/Brokers/Revolut/PdfModels/StatementModel.cs new file mode 100644 index 0000000..5a71182 --- /dev/null +++ b/StatementParser/StatementParser/Parsers/Brokers/Revolut/PdfModels/StatementModel.cs @@ -0,0 +1,15 @@ +using ASoft.TextDeserializer.Attributes; + +namespace StatementParser.Parsers.Brokers.Revolut.PdfModels +{ + [DeserializeByRegex(" (?[^ ]+)Account")] + internal class StatementModel + { + public string AccountNumber { get; set; } + + [DeserializeCollectionByRegex( + "([0-9]{2}/[0-9]{2}/[0-9]{4}[0-9]{2}/[0-9]{2}/[0-9]{4})", + "HOLDINGSACTIVITYTrade DateSettle DateCurrencyActivity TypeSymbol / DescriptionQuantityPriceAmount(.+)Page \\d+ of \\d+")] + public ActivityDividendModel[] ActivityDividend { get; set; } + } +} diff --git a/StatementParser/StatementParser/Parsers/Brokers/Revolut/RevolutStatementParser.cs b/StatementParser/StatementParser/Parsers/Brokers/Revolut/RevolutStatementParser.cs new file mode 100644 index 0000000..b36c3cf --- /dev/null +++ b/StatementParser/StatementParser/Parsers/Brokers/Revolut/RevolutStatementParser.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ASoft.TextDeserializer; +using ASoft.TextDeserializer.Exceptions; +using StatementParser.Models; +using StatementParser.Parsers.Brokers.Revolut.PdfModels; + +namespace StatementParser.Parsers.Brokers.Revolut +{ + internal class RevolutStatementParser : ITransactionParser + { + private bool CanParse(string statementFilePath) + { + return File.Exists(statementFilePath) && Path.GetExtension(statementFilePath).ToLowerInvariant() == ".pdf"; + } + + public IList Parse(string statementFilePath) + { + if (!CanParse(statementFilePath)) + { + return null; + } + + var transactions = new List(); + + using (var textSource = new TextSource(statementFilePath)) + { + try + { + var parsedDocument = new TextDocumentParser().Parse(textSource); + + foreach (var transaction in parsedDocument.ActivityDividend) + { + if (transaction.ActivityType == "DIV") + { + transactions.Add(CreateDividendTransaction(transaction, parsedDocument.ActivityDividend)); + } + } + + } + catch (TextException) + { + return null; + } + } + + return transactions; + } + + private DividendTransaction CreateDividendTransaction(ActivityDividendModel activityDividendRow, ActivityDividendModel[] activities) + { + decimal tax = SearchForTax(activityDividendRow, activities); + var currency = (Currency)Enum.Parse(typeof(Currency), activityDividendRow.Currency); + return new DividendTransaction(Broker.Revolut, activityDividendRow.SettleDate, activityDividendRow.Description, activityDividendRow.Amount, tax, currency); + } + + private decimal SearchForTax(ActivityDividendModel dividendTransactionRow, ActivityDividendModel[] activities) + { + var transactionRow = activities.FirstOrDefault(i => + i.ActivityType == "DIVNRA" && + i.TradeDate == dividendTransactionRow.TradeDate && + i.SettleDate == dividendTransactionRow.SettleDate && + i.Description.Substring(0, i.Description.IndexOf(" - DIV")) == dividendTransactionRow.Description.Substring(0, dividendTransactionRow.Description.IndexOf(" - DIV")) + ); + + return transactionRow?.Amount ?? 0; + } + } +} diff --git a/StatementParser/StatementParser/TransactionParser.cs b/StatementParser/StatementParser/TransactionParser.cs index 6402d3f..a35ce47 100644 --- a/StatementParser/StatementParser/TransactionParser.cs +++ b/StatementParser/StatementParser/TransactionParser.cs @@ -7,6 +7,7 @@ using StatementParser.Parsers.Brokers.FxChoice; using StatementParser.Parsers.Brokers.Lynx; using StatementParser.Parsers.Brokers.MorganStanley; +using StatementParser.Parsers.Brokers.Revolut; namespace StatementParser { @@ -22,6 +23,7 @@ public TransactionParser() parsers.Add(new FxChoiceStatementParser()); parsers.Add(new LynxCsvParser()); parsers.Add(new DegiroParser()); + parsers.Add(new RevolutStatementParser()); } public Task> ParseAsync(string statementFilePath)