From 28649c3254fe0f5e19547faaf1bd74ef6453130b Mon Sep 17 00:00:00 2001 From: Jacek Czaja Date: Sun, 17 Dec 2023 19:01:44 +0100 Subject: [PATCH] Initial support to revolut transactions from savings account (EUR and PLN) (#89) * - Added draft of CSV processing * - Added Results returning from parsing routines * - Buildable and some of UT works for csvparser * - added UT to test parse_incomes * - added unit tests to csvparser * - Refactor part 1 * - Unit tests worked * - Unit tests works * - implemented unit test for create detail revolut transactions * - added computation of taxes for Revolut and combined with sold * - Fixed UTs * Extended GUI file dialog filter to have CSV * - GUI prints revolut (savings) data * FMT --- Cargo.lock | 942 ++++++++++++++++++- Cargo.toml | 4 +- revolut_data/Revolut_21sie2023_27lis2023.csv | 199 ++++ revolut_data/Revolut_30cze2023_27lis2023.csv | 192 ++++ src/csvparser.rs | 424 +++++++++ src/de.rs | 7 +- src/gui.rs | 13 +- src/lib.rs | 240 ++++- src/main.rs | 79 +- src/pdfparser.rs | 23 +- src/pl.rs | 62 +- src/transactions.rs | 232 ++++- src/us.rs | 5 +- src/xlsxparser.rs | 18 +- 14 files changed, 2304 insertions(+), 136 deletions(-) create mode 100644 revolut_data/Revolut_21sie2023_27lis2023.csv create mode 100644 revolut_data/Revolut_30cze2023_27lis2023.csv create mode 100644 src/csvparser.rs diff --git a/Cargo.lock b/Cargo.lock index f7dabef..9326487 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,6 +54,19 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -63,6 +76,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -84,6 +103,46 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" +[[package]] +name = "argminmax" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202108b46429b765ef483f8a24d5c46f48c14acfdacc086dd4ab6dddf6bcdbd2" +dependencies = [ + "num-traits", +] + +[[package]] +name = "array-init-cursor" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7d0a018de4f6aa429b9d33d69edf69072b1c5b1cb8d3e4a5f7ef898fc3eb76" + +[[package]] +name = "arrow-format" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07884ea216994cdc32a2d5f8274a8bee979cfe90274b83f86f440866ee3132c7" +dependencies = [ + "planus", + "serde", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atoi_simd" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccfc14f5c3e34de57539a7ba9c18ecde3d9bbde48d232ea1da3e468adb307fd0" + [[package]] name = "atty" version = "0.2.14" @@ -171,6 +230,26 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -204,6 +283,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -280,6 +360,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "comfy-table" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" +dependencies = [ + "crossterm", + "strum", + "strum_macros", + "unicode-width", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -324,6 +416,40 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.16" @@ -333,6 +459,28 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.4.1", + "crossterm_winapi", + "libc", + "parking_lot", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "deflate" version = "0.9.1" @@ -366,6 +514,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dyn-clone" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" + [[package]] name = "either" version = "1.9.0" @@ -381,6 +535,24 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum_dispatch" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f33313078bb8d4d05a2733a94ac4c2d8a0df9a2b84424ebf4f33bfc224a890e" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.5" @@ -391,16 +563,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ethnum" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" + [[package]] name = "etradeTaxReturnHelper" -version = "0.3.3" +version = "0.4.0" dependencies = [ "calamine", "chrono", "clap", "fltk", "log", + "nom", "pdf", + "polars", "regex", "reqwest", "serde", @@ -408,6 +588,12 @@ dependencies = [ "wild", ] +[[package]] +name = "fast-float" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" + [[package]] name = "fastrand" version = "2.0.1" @@ -493,6 +679,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1b05cbd864bcaecbd3455d6d967862d446e4ebfc3c2e5e5b9841e53cba6673" + [[package]] name = "form_urlencoded" version = "1.2.0" @@ -560,6 +752,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + [[package]] name = "gimli" version = "0.28.0" @@ -584,7 +789,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -597,6 +802,23 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", + "rayon", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -612,6 +834,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "http" version = "0.2.9" @@ -723,7 +954,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", ] [[package]] @@ -767,6 +1008,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "jpeg-decoder" version = "0.1.22" @@ -794,18 +1044,54 @@ version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "linux-raw-sys" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lz4" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" +dependencies = [ + "libc", + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "md5" version = "0.7.0" @@ -818,12 +1104,36 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "memmap2" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -844,6 +1154,28 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "multiversion" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2c7b9d7fe61760ce5ea19532ead98541f6b4c495d87247aff9826445cf6872a" +dependencies = [ + "multiversion-macros", + "target-features", +] + +[[package]] +name = "multiversion-macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26a83d8500ed06d68877e9de1dde76c1dbb83885dcdbda4ef44ccbc3fbda2ac8" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "target-features", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -862,6 +1194,34 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "now" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89e9874397a1f0a52fc1f197a8effd9735223cb2390e9dcc83ac6cd02923d0" +dependencies = [ + "chrono", +] + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -869,6 +1229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -961,6 +1322,29 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91409674c628d07a6b4b79cc877c6b63ba5ccbfbadddd77ca822f55069ed1bd4" +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "paste" version = "1.0.14" @@ -1031,13 +1415,307 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] -name = "powerfmt" -version = "0.2.0" +name = "planus" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "fc1691dd09e82f428ce8d6310bd6d5da2557c82ff17694d2a32cad7242aea89f" +dependencies = [ + "array-init-cursor", +] [[package]] -name = "proc-macro2" +name = "polars" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8e52f9236eb722da0990a70bbb1216dcc7a77bcb00c63439d2d982823e90d5" +dependencies = [ + "getrandom", + "polars-core", + "polars-io", + "polars-lazy", + "polars-ops", + "polars-sql", + "polars-time", + "version_check", +] + +[[package]] +name = "polars-arrow" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd503430a6d9779b07915d858865fe998317ef3cfef8973881f578ac5d4baae7" +dependencies = [ + "ahash", + "arrow-format", + "atoi_simd", + "bytemuck", + "chrono", + "dyn-clone", + "either", + "ethnum", + "fast-float", + "foreign_vec", + "getrandom", + "hashbrown 0.14.3", + "itoa", + "lz4", + "multiversion", + "num-traits", + "polars-error", + "polars-utils", + "rustc_version", + "ryu", + "simdutf8", + "streaming-iterator", + "strength_reduce", + "zstd", +] + +[[package]] +name = "polars-core" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae73d5b8e55decde670caba1cc82b61f14bfb9a72503198f0997d657a98dcfd6" +dependencies = [ + "ahash", + "bitflags 2.4.1", + "bytemuck", + "chrono", + "comfy-table", + "either", + "hashbrown 0.14.3", + "indexmap 2.1.0", + "num-traits", + "once_cell", + "polars-arrow", + "polars-error", + "polars-row", + "polars-utils", + "rand", + "rand_distr", + "rayon", + "regex", + "smartstring", + "thiserror", + "version_check", + "xxhash-rust", +] + +[[package]] +name = "polars-error" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb0520d68eaa9993ae0c741409d1526beff5b8f48e1d73e4381616f8152cf488" +dependencies = [ + "arrow-format", + "regex", + "simdutf8", + "thiserror", +] + +[[package]] +name = "polars-io" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96e10a0745acd6009db64bef0ceb9e23a70b1c27b26a0a6517c91f3e6363bc06" +dependencies = [ + "ahash", + "atoi_simd", + "bytes", + "chrono", + "fast-float", + "home", + "itoa", + "memchr", + "memmap2", + "num-traits", + "once_cell", + "percent-encoding", + "polars-arrow", + "polars-core", + "polars-error", + "polars-time", + "polars-utils", + "rayon", + "regex", + "ryu", + "simdutf8", + "smartstring", +] + +[[package]] +name = "polars-lazy" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3555f759705be6dd0d3762d16a0b8787b2dc4da73b57465f3b2bf1a070ba8f20" +dependencies = [ + "ahash", + "bitflags 2.4.1", + "glob", + "once_cell", + "polars-arrow", + "polars-core", + "polars-io", + "polars-ops", + "polars-pipe", + "polars-plan", + "polars-time", + "polars-utils", + "rayon", + "smartstring", + "version_check", +] + +[[package]] +name = "polars-ops" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7eb218296aaa7f79945f08288ca32ca3cf25fa505649eeee689ec21eebf636" +dependencies = [ + "ahash", + "argminmax", + "bytemuck", + "either", + "hashbrown 0.14.3", + "indexmap 2.1.0", + "memchr", + "num-traits", + "polars-arrow", + "polars-core", + "polars-error", + "polars-utils", + "rayon", + "regex", + "smartstring", + "version_check", +] + +[[package]] +name = "polars-pipe" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66094e7df64c932a9a7bdfe7df0c65efdcb192096e11a6a765a9778f78b4bdec" +dependencies = [ + "crossbeam-channel", + "crossbeam-queue", + "enum_dispatch", + "hashbrown 0.14.3", + "num-traits", + "polars-arrow", + "polars-core", + "polars-io", + "polars-ops", + "polars-plan", + "polars-row", + "polars-utils", + "rayon", + "smartstring", + "version_check", +] + +[[package]] +name = "polars-plan" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10e32a0958ef854b132bad7f8369cb3237254635d5e864c99505bc0bc1035fbc" +dependencies = [ + "ahash", + "bytemuck", + "once_cell", + "percent-encoding", + "polars-arrow", + "polars-core", + "polars-io", + "polars-ops", + "polars-time", + "polars-utils", + "rayon", + "regex", + "smartstring", + "strum_macros", + "version_check", +] + +[[package]] +name = "polars-row" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135ab81cac2906ba74ea8984c7e6025d081ae5867615bcefb4d84dfdb456dac" +dependencies = [ + "polars-arrow", + "polars-error", + "polars-utils", +] + +[[package]] +name = "polars-sql" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dbd7786849a5e3ad1fde188bf38141632f626e3a57319b0bbf7a5f1d75519e" +dependencies = [ + "polars-arrow", + "polars-core", + "polars-error", + "polars-lazy", + "polars-plan", + "rand", + "serde", + "serde_json", + "sqlparser", +] + +[[package]] +name = "polars-time" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae56f79e9cedd617773c1c8f5ca84a31a8b1d593714959d5f799e7bdd98fe51" +dependencies = [ + "atoi", + "chrono", + "now", + "once_cell", + "polars-arrow", + "polars-core", + "polars-error", + "polars-ops", + "polars-utils", + "regex", + "smartstring", +] + +[[package]] +name = "polars-utils" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da6ce68169fe61d46958c8eab7447360f30f2f23f6e24a0ce703a14b0a3cfbfc" +dependencies = [ + "ahash", + "bytemuck", + "hashbrown 0.14.3", + "indexmap 2.1.0", + "num-traits", + "once_cell", + "polars-error", + "rayon", + "smartstring", + "sysinfo", + "version_check", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" @@ -1064,6 +1742,66 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1146,6 +1884,15 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.21" @@ -1159,6 +1906,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.15" @@ -1174,6 +1927,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "security-framework" version = "2.9.2" @@ -1197,6 +1956,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + [[package]] name = "serde" version = "1.0.190" @@ -1253,6 +2018,12 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "simple_logger" version = "4.2.0" @@ -1274,6 +2045,23 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + [[package]] name = "snafu" version = "0.6.10" @@ -1315,6 +2103,33 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "sqlparser" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743b4dc2cbde11890ccb254a8fc9d537fa41b36da00de2a1c5e9848c9bc42bd7" +dependencies = [ + "log", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "stringprep" version = "0.1.4" @@ -1332,6 +2147,25 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.38", +] + [[package]] name = "syn" version = "1.0.109" @@ -1354,6 +2188,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sysinfo" +version = "0.29.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "winapi", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -1375,6 +2223,12 @@ dependencies = [ "libc", ] +[[package]] +name = "target-features" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb5fa503293557c5158bd215fdc225695e567a77e453f5d4452a50a193969bd" + [[package]] name = "tempfile" version = "3.8.1" @@ -1397,6 +2251,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "time" version = "0.3.30" @@ -1852,6 +2726,32 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "xxhash-rust" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9828b178da53440fa9c766a3d2f73f7cf5d0ac1fe3980c1e5018d899fd19e07b" + +[[package]] +name = "zerocopy" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6f15f7ade05d2a4935e34a457b936c23dc70a05cc1d97133dc99e7a3fe0f0e" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbbad221e3f78500350ecbd7dfa4e63ef945c05f4c61cb7f4d3f84cd0bba649b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "zip" version = "0.6.6" @@ -1863,3 +2763,31 @@ dependencies = [ "crossbeam-utils", "flate2", ] + +[[package]] +name = "zstd" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 25c5b31..299cffc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "etradeTaxReturnHelper" -version = "0.3.3" +version = "0.4.0" edition = "2021" description = "Parses etrade financial documents for transaction details (income, tax paid, cost basis) and compute total income and total tax paid according to chosen tax residency (currency)" license = "BSD-3-Clause" @@ -26,3 +26,5 @@ regex = "1.3.3" calamine = "0.22.1" wild = "2.2.0" fltk = {version = "=1.3.24", features = ["fltk-bundled"], optional = true} +nom = "7.1.3" +polars = "0.35.4" diff --git a/revolut_data/Revolut_21sie2023_27lis2023.csv b/revolut_data/Revolut_21sie2023_27lis2023.csv new file mode 100644 index 0000000..e92de5f --- /dev/null +++ b/revolut_data/Revolut_21sie2023_27lis2023.csv @@ -0,0 +1,199 @@ +Completed Date,Product name,Description,Interest rate (p.a.),Money out,Money in,Balance +"21 Aug 2023","","Saldo z przeniesienia","","","","€0" +"23 Aug 2023","Instant Access - Aion Bank","Wpłata","","","+€2,000","€2,000" +"24 Aug 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/08/24","1.11%","","+€0.05","€2,000.05" +"25 Aug 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/08/25","1.11%","","+€0.07","€2,000.12" +"26 Aug 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/08/26","1.11%","","+€0.06","€2,000.18" +"27 Aug 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/08/27","1.11%","","+€0.06","€2,000.24" +"28 Aug 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/08/28","1.11%","","+€0.06","€2,000.30" +"29 Aug 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/08/29","1.11%","","+€0.06","€2,000.36" +"30 Aug 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/08/30","1.11%","","+€0.06","€2,000.42" +"31 Aug 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/08/31","1.11%","","+€0.06","€2,000.48" +"1 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/01","1.11%","","+€0.06","€2,000.54" +"2 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/02","1.11%","","+€0.06","€2,000.60" +"3 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/03","1.11%","","+€0.06","€2,000.66" +"4 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/04","1.11%","","+€0.06","€2,000.72" +"5 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/05","1.11%","","+€0.06","€2,000.78" +"6 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/06","1.11%","","+€0.06","€2,000.84" +"7 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/07","1.11%","","+€0.06","€2,000.90" +"8 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/08","1.11%","","+€0.06","€2,000.96" +"9 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/09","1.11%","","+€0.06","€2,001.02" +"10 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/10","1.11%","","+€0.06","€2,001.08" +"11 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/11","1.11%","","+€0.06","€2,001.14" +"12 Sep 2023","Instant Access - Aion Bank","Wpłata","","","+€6,000","€8,001.14" +"12 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/12","1.11%","","+€0.06","€8,001.20" +"13 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/13","1.11%","","+€0.24","€8,001.44" +"14 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/14","1.11%","","+€0.24","€8,001.68" +"15 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/15","1.11%","","+€0.24","€8,001.92" +"16 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/16","1.11%","","+€0.24","€8,002.16" +"17 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/17","1.11%","","+€0.25","€8,002.41" +"18 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/18","1.11%","","+€0.24","€8,002.65" +"19 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/19","1.11%","","+€0.24","€8,002.89" +"20 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/20","1.11%","","+€0.24","€8,003.13" +"21 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/21","1.11%","","+€0.24","€8,003.37" +"22 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/22","1.11%","","+€0.24","€8,003.61" +"23 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/23","1.11%","","+€0.24","€8,003.85" +"24 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/24","1.11%","","+€0.24","€8,004.09" +"25 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/25","1.11%","","+€0.25","€8,004.34" +"26 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/26","1.11%","","+€0.24","€8,004.58" +"27 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/27","1.11%","","+€0.24","€8,004.82" +"28 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/28","1.11%","","+€0.24","€8,005.06" +"29 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/29","1.11%","","+€0.24","€8,005.30" +"30 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/30","1.11%","","+€0.24","€8,005.54" +"1 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/01","1.11%","","+€0.24","€8,005.78" +"2 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/02","1.11%","","+€0.24","€8,006.02" +"3 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/03","1.11%","","+€0.25","€8,006.27" +"4 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/04","1.11%","","+€0.24","€8,006.51" +"5 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/05","1.11%","","+€0.24","€8,006.75" +"6 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/06","1.11%","","+€0.24","€8,006.99" +"7 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/07","1.11%","","+€0.24","€8,007.23" +"8 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/08","1.11%","","+€0.24","€8,007.47" +"9 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/09","1.11%","","+€0.24","€8,007.71" +"10 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/10","1.11%","","+€0.24","€8,007.95" +"11 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/11","1.11%","","+€0.25","€8,008.20" +"12 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/12","1.11%","","+€0.24","€8,008.44" +"13 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/13","1.11%","","+€0.24","€8,008.68" +"14 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/14","1.11%","","+€0.24","€8,008.92" +"15 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/15","1.11%","","+€0.24","€8,009.16" +"16 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/16","1.11%","","+€0.24","€8,009.40" +"17 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/17","1.11%","","+€0.24","€8,009.64" +"18 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/18","1.11%","","+€0.25","€8,009.89" +"19 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/19","1.11%","","+€0.24","€8,010.13" +"20 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/20","1.11%","","+€0.24","€8,010.37" +"21 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/21","1.11%","","+€0.24","€8,010.61" +"22 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/22","1.11%","","+€0.24","€8,010.85" +"23 Oct 2023","Instant Access - Aion Bank","Wpłata","","","+€200","€8,210.85" +"23 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/23","1.11%","","+€0.24","€8,211.09" +"24 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/24","1.11%","","+€0.25","€8,211.34" +"25 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/25","1.11%","","+€0.25","€8,211.59" +"26 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/26","1.11%","","+€0.25","€8,211.84" +"27 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/27","1.11%","","+€0.24","€8,212.08" +"28 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/28","1.11%","","+€0.25","€8,212.33" +"29 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/29","1.11%","","+€0.25","€8,212.58" +"30 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/30","1.11%","","+€0.25","€8,212.83" +"31 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/31","1.11%","","+€0.24","€8,213.07" +"1 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/01","1.11%","","+€0.25","€8,213.32" +"2 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/02","1.11%","","+€0.25","€8,213.57" +"3 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/03","1.11%","","+€0.25","€8,213.82" +"4 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/04","1.11%","","+€0.24","€8,214.06" +"5 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/05","1.11%","","+€0.25","€8,214.31" +"6 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/06","1.11%","","+€0.25","€8,214.56" +"7 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/07","1.11%","","+€0.25","€8,214.81" +"8 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/08","1.11%","","+€0.24","€8,215.05" +"9 Nov 2023","Instant Access - Aion Bank","Wpłata","","","+€600","€8,815.05" +"9 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/09","1.11%","","+€0.25","€8,815.30" +"10 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/10","1.11%","","+€0.27","€8,815.57" +"11 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/11","1.11%","","+€0.26","€8,815.83" +"12 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/12","1.11%","","+€0.27","€8,816.10" +"13 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/13","1.11%","","+€0.26","€8,816.36" +"14 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/14","1.11%","","+€0.27","€8,816.63" +"15 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/15","1.11%","","+€0.26","€8,816.89" +"16 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/16","1.11%","","+€0.27","€8,817.16" +"17 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/17","1.11%","","+€0.27","€8,817.43" +"18 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/18","1.11%","","+€0.26","€8,817.69" +"19 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/19","1.11%","","+€0.27","€8,817.96" +"20 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/20","1.11%","","+€0.26","€8,818.22" +"21 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/21","1.11%","","+€0.27","€8,818.49" +"22 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/22","1.11%","","+€0.26","€8,818.75" +"23 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/23","1.11%","","+€0.27","€8,819.02" +"24 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/24","1.11%","","+€0.27","€8,819.29" +"25 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/25","1.11%","","+€0.26","€8,819.55" +"26 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/26","1.11%","","+€0.27","€8,819.82" +"27 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/27","1.11%","","+€0.26","€8,820.08" +"27 Nov 2023","","Saldo do przeniesienia","","","","€8,820.08" diff --git a/revolut_data/Revolut_30cze2023_27lis2023.csv b/revolut_data/Revolut_30cze2023_27lis2023.csv new file mode 100644 index 0000000..b1871cd --- /dev/null +++ b/revolut_data/Revolut_30cze2023_27lis2023.csv @@ -0,0 +1,192 @@ +Completed Date,Product name,Description,Interest rate (p.a.),Money out,Money in,Balance +"30 Jun 2023","","Saldo z przeniesienia","","","","0 PLN" +"10 Jul 2023","Instant Access - Aion Bank","Wpłata","","","+20,000 PLN","20,000 PLN" +"10 Jul 2023","Instant Access - Aion Bank","Wypłata","","-20,000 PLN","","0 PLN" +"28 Aug 2023","Instant Access - Aion Bank","Wpłata","","","+4,000 PLN","4,000 PLN" +"29 Aug 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/08/29","4.05%","","+0.44 PLN","4,000.44 PLN" +"30 Aug 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/08/30","4.05%","","+0.45 PLN","4,000.89 PLN" +"31 Aug 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/08/31","4.05%","","+0.44 PLN","4,001.33 PLN" +"1 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/01","4.05%","","+0.45 PLN","4,001.78 PLN" +"2 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/02","4.05%","","+0.44 PLN","4,002.22 PLN" +"3 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/03","4.05%","","+0.44 PLN","4,002.66 PLN" +"4 Sep 2023","Instant Access - Aion Bank","Wpłata","","","+3,000 PLN","7,002.66 PLN" +"4 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/04","4.05%","","+0.45 PLN","7,003.11 PLN" +"5 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/05","4.05%","","+0.77 PLN","7,003.88 PLN" +"6 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/06","4.05%","","+0.78 PLN","7,004.66 PLN" +"7 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/07","4.05%","","+0.78 PLN","7,005.44 PLN" +"8 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/08","4.05%","","+0.78 PLN","7,006.22 PLN" +"9 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/09","4.05%","","+0.77 PLN","7,006.99 PLN" +"10 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/10","4.05%","","+0.78 PLN","7,007.77 PLN" +"11 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/11","4.05%","","+0.78 PLN","7,008.55 PLN" +"12 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/12","4.05%","","+0.78 PLN","7,009.33 PLN" +"13 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/13","4.05%","","+0.77 PLN","7,010.10 PLN" +"14 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/14","4.05%","","+0.78 PLN","7,010.88 PLN" +"15 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/15","4.05%","","+0.78 PLN","7,011.66 PLN" +"16 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/16","4.05%","","+0.78 PLN","7,012.44 PLN" +"17 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/17","4.05%","","+0.78 PLN","7,013.22 PLN" +"18 Sep 2023","Instant Access - Aion Bank","Wpłata","","","+2,000 PLN","9,013.22 PLN" +"18 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/18","4.05%","","+0.77 PLN","9,013.99 PLN" +"19 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/19","4.05%","","+1 PLN","9,014.99 PLN" +"20 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/20","4.05%","","+1.01 PLN","9,016 PLN" +"21 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/21","4.05%","","+1 PLN","9,017 PLN" +"22 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/22","4.05%","","+1 PLN","9,018 PLN" +"23 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/23","4.05%","","+1 PLN","9,019 PLN" +"24 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/24","4.05%","","+1 PLN","9,020 PLN" +"25 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/25","4.05%","","+1 PLN","9,021 PLN" +"26 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/26","4.05%","","+1 PLN","9,022 PLN" +"27 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/27","4.05%","","+1 PLN","9,023 PLN" +"28 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/28","4.05%","","+1 PLN","9,024 PLN" +"29 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/29","4.05%","","+1 PLN","9,025 PLN" +"30 Sep 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/09/30","4.05%","","+1 PLN","9,026 PLN" +"1 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/01","4.05%","","+1.01 PLN","9,027.01 PLN" +"2 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/02","4.05%","","+1 PLN","9,028.01 PLN" +"3 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/03","4.05%","","+1 PLN","9,029.01 PLN" +"3 Oct 2023","Instant Access - Aion Bank","Wpłata","","","+500 PLN","9,529.01 PLN" +"4 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/04","4.05%","","+1.06 PLN","9,530.07 PLN" +"5 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/05","4.05%","","+1.05 PLN","9,531.12 PLN" +"6 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/06","4.05%","","+1.06 PLN","9,532.18 PLN" +"7 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/07","4.05%","","+1.06 PLN","9,533.24 PLN" +"8 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/08","4.05%","","+1.06 PLN","9,534.30 PLN" +"9 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/09","4.05%","","+1.05 PLN","9,535.35 PLN" +"10 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/10","4.05%","","+1.06 PLN","9,536.41 PLN" +"11 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/11","4.05%","","+1.06 PLN","9,537.47 PLN" +"12 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/12","4.05%","","+1.06 PLN","9,538.53 PLN" +"13 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/13","4.05%","","+1.06 PLN","9,539.59 PLN" +"14 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/14","4.05%","","+1.06 PLN","9,540.65 PLN" +"15 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/15","4.05%","","+1.05 PLN","9,541.70 PLN" +"16 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/16","4.05%","","+1.06 PLN","9,542.76 PLN" +"17 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/17","4.05%","","+1.06 PLN","9,543.82 PLN" +"18 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/18","4.05%","","+1.06 PLN","9,544.88 PLN" +"19 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/19","4.05%","","+1.06 PLN","9,545.94 PLN" +"20 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/20","4.05%","","+1.06 PLN","9,547 PLN" +"21 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/21","4.05%","","+1.06 PLN","9,548.06 PLN" +"22 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/22","4.05%","","+1.06 PLN","9,549.12 PLN" +"23 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/23","4.05%","","+1.06 PLN","9,550.18 PLN" +"24 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/24","4.05%","","+1.06 PLN","9,551.24 PLN" +"25 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/25","4.05%","","+1.06 PLN","9,552.30 PLN" +"26 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/26","4.05%","","+1.06 PLN","9,553.36 PLN" +"27 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/27","4.05%","","+1.06 PLN","9,554.42 PLN" +"28 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/28","4.05%","","+1.06 PLN","9,555.48 PLN" +"29 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/29","4.05%","","+1.06 PLN","9,556.54 PLN" +"30 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/30","4.05%","","+1.06 PLN","9,557.60 PLN" +"31 Oct 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/10/31","4.05%","","+1.06 PLN","9,558.66 PLN" +"1 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/01","4.05%","","+1.06 PLN","9,559.72 PLN" +"2 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/02","4.05%","","+1.06 PLN","9,560.78 PLN" +"3 Nov 2023","Instant Access - Aion Bank","Wpłata","","","+500 PLN","10,060.78 PLN" +"3 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/03","4.05%","","+1.06 PLN","10,061.84 PLN" +"4 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/04","4.05%","","+1.12 PLN","10,062.96 PLN" +"5 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/05","4.05%","","+1.11 PLN","10,064.07 PLN" +"6 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/06","4.05%","","+1.12 PLN","10,065.19 PLN" +"7 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/07","4.05%","","+1.12 PLN","10,066.31 PLN" +"8 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/08","4.05%","","+1.11 PLN","10,067.42 PLN" +"9 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/09","4.05%","","+1.12 PLN","10,068.54 PLN" +"10 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/10","4.05%","","+1.12 PLN","10,069.66 PLN" +"11 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/11","4.05%","","+1.12 PLN","10,070.78 PLN" +"12 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/12","4.05%","","+1.11 PLN","10,071.89 PLN" +"13 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/13","4.05%","","+1.12 PLN","10,073.01 PLN" +"14 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/14","4.05%","","+1.12 PLN","10,074.13 PLN" +"15 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/15","4.05%","","+1.12 PLN","10,075.25 PLN" +"16 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/16","4.05%","","+1.11 PLN","10,076.36 PLN" +"17 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/17","4.05%","","+1.12 PLN","10,077.48 PLN" +"18 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/18","4.05%","","+1.12 PLN","10,078.60 PLN" +"19 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/19","4.05%","","+1.12 PLN","10,079.72 PLN" +"20 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/20","4.05%","","+1.12 PLN","10,080.84 PLN" +"21 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/21","4.05%","","+1.12 PLN","10,081.96 PLN" +"22 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/22","3.05%","","+0.82 PLN","10,082.78 PLN" +"23 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/23","3.05%","","+0.83 PLN","10,083.61 PLN" +"24 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/24","3.05%","","+0.83 PLN","10,084.44 PLN" +"25 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/25","3.05%","","+0.83 PLN","10,085.27 PLN" +"26 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/26","3.05%","","+0.83 PLN","10,086.10 PLN" +"27 Nov 2023","Instant Access - Aion Bank","Odsetki brutto +Wypłacone dnia 2023/11/27","3.05%","","+0.83 PLN","10,086.93 PLN" +"27 Nov 2023","","Saldo do przeniesienia","","","","10,086.93 PLN" diff --git a/src/csvparser.rs b/src/csvparser.rs new file mode 100644 index 0000000..e12da31 --- /dev/null +++ b/src/csvparser.rs @@ -0,0 +1,424 @@ +pub use crate::logging::ResultExt; +use nom::{ + branch::alt, + bytes::complete::is_a, + bytes::complete::tag, + bytes::complete::take, + bytes::complete::take_till, + bytes::complete::take_until, + bytes::complete::take_while, + character::{complete::alphanumeric1, is_digit}, + combinator::peek, + error::Error, + number::complete::double, + sequence::delimited, + sequence::tuple, + IResult, +}; +use polars::prelude::*; + +fn extract_cash(cashline: &str) -> Result { + // We need to erase "," before processing it by parser + log::info!("Entry moneyin line: {cashline}"); + let cashline_string: String = cashline.to_string().replace(",", ""); + log::info!("Processed moneyin line: {cashline_string}"); + let mut euro_parser = tuple((tag("+€"), double::<&str, Error<_>>)); + let mut pln_parser = tuple((tag("+"), double::<&str, Error<_>>, take(1usize), tag("PLN"))); + + match euro_parser(cashline_string.as_str()) { + Ok((_, (_, value))) => return Ok(crate::Currency::EUR(value)), + Err(_) => match pln_parser(cashline_string.as_str()) { + Ok((_, (_, value, _, _))) => return Ok(crate::Currency::PLN(value)), + Err(_) => return Err("Error converting: {cashline_string}"), + }, + } +} + +fn extract_intrest_rate_transactions(df: &DataFrame) -> Result { + // 1. Get rows with transactions + let mut df_transactions = df + .select(&["Completed Date", "Description", "Money in"]) + .map_err(|_| "Error: Unable to select description")?; + + let intrest_rate = df_transactions + .column("Description") + .map_err(|_| "Error: Unable to get Description")? + .iter() + .map(|x| { + let m = match x { + AnyValue::Utf8(x) => { + if x.contains("Odsetki brutto") { + Some("odsetki") + } else { + None + } + } + _ => None, + }; + m + }) + .collect::>(); + + // cols: "Completed Date", "Description" , "Money In" + let new_desc = Series::new("Description", intrest_rate); + df_transactions + .with_column(new_desc) + .expect("Unable to replace Description column"); + let intrest_rate_mask = df_transactions + .column("Description") + .map_err(|_| "Error: Unable to get Description")? + .equal("odsetki") + .expect("Error creating mask"); + + let filtred_df = df.filter(&intrest_rate_mask).expect("Error filtering"); + // I need to get (Currecy, Transaction Data and amount) + + Ok(filtred_df) +} + +fn parse_transaction_dates(df: &DataFrame) -> Result, &'static str> { + let completed_date = df + .column("Completed Date") + .map_err(|_| "Error: Unable to select Complete Date")?; + let mut dates: Vec = vec![]; + let possible_dates = completed_date + .utf8() + .map_err(|_| "Error: Unable to convert to utf8")?; + possible_dates.into_iter().try_for_each(|x| { + if let Some(d) = x { + let cd = chrono::NaiveDate::parse_from_str(&d, "%e %b %Y") + .map_err(|_| "Error converting cell to NaiveDate")? + .format("%m/%d/%y") + .to_string(); + dates.push(cd); + } + Ok::<(), &str>(()) + })?; + + Ok(dates) +} + +fn parse_incomes(df: DataFrame) -> Result, &'static str> { + let mut incomes: Vec = vec![]; + let moneyin = df + .column("Money in") + .map_err(|_| "Error: Unable to select Money In")?; + let possible_incomes = moneyin + .utf8() + .map_err(|_| "Error: Unable to convert to utf8")?; + possible_incomes.into_iter().try_for_each(|x| { + if let Some(d) = x { + incomes.push(extract_cash(d)?); + } + Ok::<(), &str>(()) + })?; + Ok(incomes) +} + +pub fn parse_revolut_transactions( + csvtoparse: &str, +) -> Result, &str> { + let df = CsvReader::from_path(csvtoparse) + .map_err(|_| "Error: opening CSV")? + .has_header(true) + .finish() + .map_err(|_| "Error: opening CSV")?; + + log::info!("CSV DataFrame: {df}"); + + let filtred_df = extract_intrest_rate_transactions(&df)?; + + log::info!("DF: {filtred_df}"); + + let dates = parse_transaction_dates(&filtred_df)?; + log::info!("Dates: {:?}", dates); + + let incomes = parse_incomes(filtred_df)?; + log::info!("Incomes: {:?}", incomes); + + let mut transactions: Vec<(String, crate::Currency)> = vec![]; + let iter = std::iter::zip(dates, incomes); + iter.for_each(|(d, m)| { + transactions.push((d, m)); + }); + + Ok(transactions) +} + +mod tests { + use super::*; + + #[test] + fn test_extract_cash() -> Result<(), String> { + assert_eq!(extract_cash("+€0.07"), Ok(crate::Currency::EUR(0.07))); + assert_eq!(extract_cash("+€6,000"), Ok(crate::Currency::EUR(6000.00))); + assert_eq!(extract_cash("+€600"), Ok(crate::Currency::EUR(600.00))); + assert_eq!( + extract_cash("+€6,000.45"), + Ok(crate::Currency::EUR(6000.45)) + ); + + assert_eq!(extract_cash("+1.06 PLN"), Ok(crate::Currency::PLN(1.06))); + assert_eq!( + extract_cash("+4,000 PLN"), + Ok(crate::Currency::PLN(4000.00)) + ); + assert_eq!(extract_cash("+500 PLN"), Ok(crate::Currency::PLN(500.00))); + assert_eq!( + extract_cash("+4,000.32 PLN"), + Ok(crate::Currency::PLN(4000.32)) + ); + + Ok(()) + } + + #[test] + fn test_parse_incomes() -> Result<(), String> { + let moneyin = Series::new("Money in", vec!["+€6,000", "+€3,000"]); + let description = Series::new("Description", vec!["odsetki", "odsetki"]); + + let df = + DataFrame::new(vec![description, moneyin]).map_err(|_| "Error creating DataFrame")?; + + assert_eq!( + parse_incomes(df), + Ok(vec![ + crate::Currency::EUR(6000.00), + crate::Currency::EUR(3000.00) + ]) + ); + + Ok(()) + } + + #[test] + fn test_parse_transaction_dates() -> Result<(), String> { + let completed_dates = Series::new("Completed Date", vec!["25 Aug 2023", "1 Sep 2023"]); + let description = Series::new("Description", vec!["odsetki", "odsetki"]); + + let df = DataFrame::new(vec![description, completed_dates]) + .map_err(|_| "Error creating DataFrame")?; + + let expected_first_date = "08/25/23".to_owned(); + let expected_second_date = "09/01/23".to_owned(); + + assert_eq!( + parse_transaction_dates(&df), + Ok(vec![expected_first_date, expected_second_date]) + ); + + Ok(()) + } + + #[test] + fn test_parse_revolut_transactions_eur() -> Result<(), String> { + let expected_result = Ok(vec![ + ("08/24/23".to_owned(), crate::Currency::EUR(0.05)), + ("08/25/23".to_owned(), crate::Currency::EUR(0.07)), + ("08/26/23".to_owned(), crate::Currency::EUR(0.06)), + ("08/27/23".to_owned(), crate::Currency::EUR(0.06)), + ("08/28/23".to_owned(), crate::Currency::EUR(0.06)), + ("08/29/23".to_owned(), crate::Currency::EUR(0.06)), + ("08/30/23".to_owned(), crate::Currency::EUR(0.06)), + ("08/31/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/01/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/02/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/03/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/04/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/05/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/06/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/07/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/08/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/09/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/10/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/11/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/12/23".to_owned(), crate::Currency::EUR(0.06)), + ("09/13/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/14/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/15/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/16/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/17/23".to_owned(), crate::Currency::EUR(0.25)), + ("09/18/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/19/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/20/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/21/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/22/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/23/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/24/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/25/23".to_owned(), crate::Currency::EUR(0.25)), + ("09/26/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/27/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/28/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/29/23".to_owned(), crate::Currency::EUR(0.24)), + ("09/30/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/01/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/02/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/03/23".to_owned(), crate::Currency::EUR(0.25)), + ("10/04/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/05/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/06/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/07/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/08/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/09/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/10/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/11/23".to_owned(), crate::Currency::EUR(0.25)), + ("10/12/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/13/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/14/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/15/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/16/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/17/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/18/23".to_owned(), crate::Currency::EUR(0.25)), + ("10/19/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/20/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/21/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/22/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/23/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/24/23".to_owned(), crate::Currency::EUR(0.25)), + ("10/25/23".to_owned(), crate::Currency::EUR(0.25)), + ("10/26/23".to_owned(), crate::Currency::EUR(0.25)), + ("10/27/23".to_owned(), crate::Currency::EUR(0.24)), + ("10/28/23".to_owned(), crate::Currency::EUR(0.25)), + ("10/29/23".to_owned(), crate::Currency::EUR(0.25)), + ("10/30/23".to_owned(), crate::Currency::EUR(0.25)), + ("10/31/23".to_owned(), crate::Currency::EUR(0.24)), + ("11/01/23".to_owned(), crate::Currency::EUR(0.25)), + ("11/02/23".to_owned(), crate::Currency::EUR(0.25)), + ("11/03/23".to_owned(), crate::Currency::EUR(0.25)), + ("11/04/23".to_owned(), crate::Currency::EUR(0.24)), + ("11/05/23".to_owned(), crate::Currency::EUR(0.25)), + ("11/06/23".to_owned(), crate::Currency::EUR(0.25)), + ("11/07/23".to_owned(), crate::Currency::EUR(0.25)), + ("11/08/23".to_owned(), crate::Currency::EUR(0.24)), + ("11/09/23".to_owned(), crate::Currency::EUR(0.25)), + ("11/10/23".to_owned(), crate::Currency::EUR(0.27)), + ("11/11/23".to_owned(), crate::Currency::EUR(0.26)), + ("11/12/23".to_owned(), crate::Currency::EUR(0.27)), + ("11/13/23".to_owned(), crate::Currency::EUR(0.26)), + ("11/14/23".to_owned(), crate::Currency::EUR(0.27)), + ("11/15/23".to_owned(), crate::Currency::EUR(0.26)), + ("11/16/23".to_owned(), crate::Currency::EUR(0.27)), + ("11/17/23".to_owned(), crate::Currency::EUR(0.27)), + ("11/18/23".to_owned(), crate::Currency::EUR(0.26)), + ("11/19/23".to_owned(), crate::Currency::EUR(0.27)), + ("11/20/23".to_owned(), crate::Currency::EUR(0.26)), + ("11/21/23".to_owned(), crate::Currency::EUR(0.27)), + ("11/22/23".to_owned(), crate::Currency::EUR(0.26)), + ("11/23/23".to_owned(), crate::Currency::EUR(0.27)), + ("11/24/23".to_owned(), crate::Currency::EUR(0.27)), + ("11/25/23".to_owned(), crate::Currency::EUR(0.26)), + ("11/26/23".to_owned(), crate::Currency::EUR(0.27)), + ("11/27/23".to_owned(), crate::Currency::EUR(0.26)), + ]); + + assert_eq!( + parse_revolut_transactions("revolut_data/Revolut_21sie2023_27lis2023.csv"), + expected_result + ); + + Ok(()) + } + + #[test] + fn test_parse_revolut_transactions_pln() -> Result<(), String> { + let expected_result = Ok(vec![ + ("08/29/23".to_owned(), crate::Currency::PLN(0.44)), + ("08/30/23".to_owned(), crate::Currency::PLN(0.45)), + ("08/31/23".to_owned(), crate::Currency::PLN(0.44)), + ("09/01/23".to_owned(), crate::Currency::PLN(0.45)), + ("09/02/23".to_owned(), crate::Currency::PLN(0.44)), + ("09/03/23".to_owned(), crate::Currency::PLN(0.44)), + ("09/04/23".to_owned(), crate::Currency::PLN(0.45)), + ("09/05/23".to_owned(), crate::Currency::PLN(0.77)), + ("09/06/23".to_owned(), crate::Currency::PLN(0.78)), + ("09/07/23".to_owned(), crate::Currency::PLN(0.78)), + ("09/08/23".to_owned(), crate::Currency::PLN(0.78)), + ("09/09/23".to_owned(), crate::Currency::PLN(0.77)), + ("09/10/23".to_owned(), crate::Currency::PLN(0.78)), + ("09/11/23".to_owned(), crate::Currency::PLN(0.78)), + ("09/12/23".to_owned(), crate::Currency::PLN(0.78)), + ("09/13/23".to_owned(), crate::Currency::PLN(0.77)), + ("09/14/23".to_owned(), crate::Currency::PLN(0.78)), + ("09/15/23".to_owned(), crate::Currency::PLN(0.78)), + ("09/16/23".to_owned(), crate::Currency::PLN(0.78)), + ("09/17/23".to_owned(), crate::Currency::PLN(0.78)), + ("09/18/23".to_owned(), crate::Currency::PLN(0.77)), + ("09/19/23".to_owned(), crate::Currency::PLN(1.0)), + ("09/20/23".to_owned(), crate::Currency::PLN(1.01)), + ("09/21/23".to_owned(), crate::Currency::PLN(1.0)), + ("09/22/23".to_owned(), crate::Currency::PLN(1.0)), + ("09/23/23".to_owned(), crate::Currency::PLN(1.0)), + ("09/24/23".to_owned(), crate::Currency::PLN(1.0)), + ("09/25/23".to_owned(), crate::Currency::PLN(1.0)), + ("09/26/23".to_owned(), crate::Currency::PLN(1.0)), + ("09/27/23".to_owned(), crate::Currency::PLN(1.0)), + ("09/28/23".to_owned(), crate::Currency::PLN(1.0)), + ("09/29/23".to_owned(), crate::Currency::PLN(1.0)), + ("09/30/23".to_owned(), crate::Currency::PLN(1.0)), + ("10/01/23".to_owned(), crate::Currency::PLN(1.01)), + ("10/02/23".to_owned(), crate::Currency::PLN(1.0)), + ("10/03/23".to_owned(), crate::Currency::PLN(1.0)), + ("10/04/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/05/23".to_owned(), crate::Currency::PLN(1.05)), + ("10/06/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/07/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/08/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/09/23".to_owned(), crate::Currency::PLN(1.05)), + ("10/10/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/11/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/12/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/13/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/14/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/15/23".to_owned(), crate::Currency::PLN(1.05)), + ("10/16/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/17/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/18/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/19/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/20/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/21/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/22/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/23/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/24/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/25/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/26/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/27/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/28/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/29/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/30/23".to_owned(), crate::Currency::PLN(1.06)), + ("10/31/23".to_owned(), crate::Currency::PLN(1.06)), + ("11/01/23".to_owned(), crate::Currency::PLN(1.06)), + ("11/02/23".to_owned(), crate::Currency::PLN(1.06)), + ("11/03/23".to_owned(), crate::Currency::PLN(1.06)), + ("11/04/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/05/23".to_owned(), crate::Currency::PLN(1.11)), + ("11/06/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/07/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/08/23".to_owned(), crate::Currency::PLN(1.11)), + ("11/09/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/10/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/11/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/12/23".to_owned(), crate::Currency::PLN(1.11)), + ("11/13/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/14/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/15/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/16/23".to_owned(), crate::Currency::PLN(1.11)), + ("11/17/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/18/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/19/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/20/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/21/23".to_owned(), crate::Currency::PLN(1.12)), + ("11/22/23".to_owned(), crate::Currency::PLN(0.82)), + ("11/23/23".to_owned(), crate::Currency::PLN(0.83)), + ("11/24/23".to_owned(), crate::Currency::PLN(0.83)), + ("11/25/23".to_owned(), crate::Currency::PLN(0.83)), + ("11/26/23".to_owned(), crate::Currency::PLN(0.83)), + ("11/27/23".to_owned(), crate::Currency::PLN(0.83)), + ]); + assert_eq!( + parse_revolut_transactions("revolut_data/Revolut_30cze2023_27lis2023.csv"), + expected_result + ); + + Ok(()) + } +} diff --git a/src/de.rs b/src/de.rs index 7fba6bc..63ed44a 100644 --- a/src/de.rs +++ b/src/de.rs @@ -5,9 +5,12 @@ pub struct DE {} impl etradeTaxReturnHelper::Residency for DE { fn get_exchange_rates( &self, - dates: &mut std::collections::HashMap>, + dates: &mut std::collections::HashMap< + etradeTaxReturnHelper::Exchange, + Option<(String, f32)>, + >, ) -> Result<(), String> { - self.get_currency_exchange_rates(dates, "USD", "EUR") + self.get_currency_exchange_rates(dates, "EUR") } fn parse_exchange_rates(&self, body: &str) -> Result<(f32, String), String> { diff --git a/src/gui.rs b/src/gui.rs index 0c907c2..c9001b5 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -117,11 +117,11 @@ pub mod gui { tbuffer.set_text(""); nbuffer.set_text("Running..."); let rd: Box = Box::new(PL {}); - let (gross_div, tax_div, gross_sold, cost_sold, div_transactions, sold_transactions) = + let (gross_div, tax_div, gross_sold, cost_sold, div_transactions, revolut_transactions, sold_transactions) = match run_taxation(&rd, file_names) { - Ok((gd, td, gs, cs, dts, sts)) => { + Ok((gd, td, gs, cs, dts, rts, sts)) => { nbuffer.set_text("Finished.\n\n (Double check if generated tax data (Summary) makes sense and then copy it to your tax form)"); - (gd, td, gs, cs, dts, sts) + (gd, td, gs, cs, dts, rts, sts) } Err(err) => { nbuffer.set_text(&err); @@ -136,7 +136,10 @@ pub mod gui { let mut transactions_strings: Vec = vec![]; div_transactions .iter() - .for_each(|x| transactions_strings.push(x.format_to_print())); + .for_each(|x| transactions_strings.push(x.format_to_print("DIV").expect_and_log("Error: Formatting DIV transaction failed"))); + revolut_transactions + .iter() + .for_each(|x| transactions_strings.push(x.format_to_print("REVOLUT ").expect_and_log("Error: Formatting DIV transaction failed"))); sold_transactions .iter() .for_each(|x| transactions_strings.push(x.format_to_print())); @@ -154,7 +157,7 @@ pub mod gui { load_button.set_callback(move |_| { let mut chooser = dialog::FileChooser::new( ".", - "*.{pdf,xlsx}", + "*.{pdf,xlsx,csv}", dialog::FileChooserType::Multi, "Choose e-trade documents with transactions (PDF and/or XLSX)", ); diff --git a/src/lib.rs b/src/lib.rs index e525650..8d2d674 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +mod csvparser; mod logging; mod pdfparser; mod transactions; @@ -9,26 +10,89 @@ type ReqwestClient = reqwest::blocking::Client; pub use logging::ResultExt; use transactions::{ - create_detailed_div_transactions, create_detailed_sold_transactions, - reconstruct_sold_transactions, verify_dividends_transactions, + create_detailed_div_transactions, create_detailed_revolut_transactions, + create_detailed_sold_transactions, reconstruct_sold_transactions, + verify_dividends_transactions, }; +#[derive(Debug, PartialEq, PartialOrd, Copy, Clone)] +pub enum Currency { + PLN(f64), + EUR(f64), + USD(f64), +} + +impl Currency { + fn value(&self) -> f64 { + match self { + Currency::EUR(val) => *val, + Currency::PLN(val) => *val, + Currency::USD(val) => *val, + } + } + fn derive(&self, val: f64) -> Currency { + match self { + Currency::EUR(_) => Currency::EUR(val), + Currency::PLN(_) => Currency::PLN(val), + Currency::USD(_) => Currency::USD(val), + } + } + + pub fn derive_exchange(&self, date: String) -> Exchange { + match self { + Currency::EUR(_) => Exchange::EUR(date), + Currency::PLN(_) => Exchange::PLN(date), + Currency::USD(_) => Exchange::USD(date), + } + } +} + +/// +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum Exchange { + EUR(String), + PLN(String), + USD(String), +} + #[derive(Debug, PartialEq, PartialOrd)] pub struct Transaction { pub transaction_date: String, - pub gross_us: f32, - pub tax_us: f32, + pub gross: Currency, + pub tax_paid: Currency, pub exchange_rate_date: String, pub exchange_rate: f32, } impl Transaction { - pub fn format_to_print(&self) -> String { - format!( - " DIV TRANSACTION date: {}, gross: ${}, tax_us: ${}, exchange_rate: {} , exchange_rate_date: {}", - chrono::NaiveDate::parse_from_str(&self.transaction_date, "%m/%d/%y").unwrap().format("%Y-%m-%d"), &self.gross_us, &self.tax_us, &self.exchange_rate, &self.exchange_rate_date + pub fn format_to_print(&self, prefix: &str) -> Result { + let msg = match (&self.gross,&self.tax_paid) { + (Currency::PLN(gross),Currency::PLN(tax_paid)) => { + + format!("{prefix} TRANSACTION date: {}, gross: {gross} PLN, tax paid: {tax_paid} PLN", + chrono::NaiveDate::parse_from_str(&self.transaction_date, "%m/%d/%y").map_err(|_| "Error: unable to format date")?.format("%Y-%m-%d") + ) + .to_owned() + }, + (Currency::USD(gross),Currency::USD(tax_paid)) => { + + format!("{prefix} TRANSACTION date: {}, gross: ${gross}, tax paid: ${tax_paid}, exchange_rate: {} , exchange_rate_date: {}", + chrono::NaiveDate::parse_from_str(&self.transaction_date, "%m/%d/%y").map_err(|_| "Error: unable to format date")?.format("%Y-%m-%d"), &self.exchange_rate,&self.exchange_rate_date ) .to_owned() + }, + + (Currency::EUR(gross),Currency::EUR(tax_paid)) => { + + format!("{prefix} TRANSACTION date: {}, gross: €{gross}, tax paid: €{tax_paid}, exchange_rate: {} , exchange_rate_date: {}", + chrono::NaiveDate::parse_from_str(&self.transaction_date, "%m/%d/%y").map_err(|_| "Error: unable to format date")?.format("%Y-%m-%d"), &self.exchange_rate,&self.exchange_rate_date + ) + .to_owned() + }, + (_,_) => return Err("Error: Gross and Tax paid currency does not match!"), + }; + + Ok(msg) } } @@ -63,7 +127,6 @@ impl SoldTransaction { } pub trait Residency { - // fn get_exchange_rate(&self, transaction_date: &str) -> Result<(String, f32), String>; fn present_result( &self, gross_div: f32, @@ -73,7 +136,7 @@ pub trait Residency { ) -> (Vec, Option); fn get_exchange_rates( &self, - dates: &mut std::collections::HashMap>, + dates: &mut std::collections::HashMap>, ) -> Result<(), String>; // Default parser (not to be used) @@ -83,8 +146,7 @@ pub trait Residency { fn get_currency_exchange_rates( &self, - dates: &mut std::collections::HashMap>, - from: &str, + dates: &mut std::collections::HashMap>, to: &str, ) -> Result<(), String> { // proxies are taken from env vars: http_proxy and https_proxy @@ -109,7 +171,13 @@ pub trait Residency { let base_exchange_rate_url = "https://www.exchange-rates.org/Rate/"; - dates.iter_mut().try_for_each(|(date, val)| { + dates.iter_mut().try_for_each(|(exchange, val)| { + let (from, date) = match exchange { + Exchange::USD(date) => ("usd", date), + Exchange::EUR(date) => ("eur", date), + Exchange::PLN(date) => ("pln", date), + }; + let mut converted_date = chrono::NaiveDate::parse_from_str(&date, "%m/%d/%y") .map_err(|x| format!("Unable to convert date {x}"))?; @@ -117,9 +185,9 @@ pub trait Residency { .checked_sub_signed(chrono::Duration::days(1)) .ok_or("Error traversing date")?; - let exchange_rate_url: String = base_exchange_rate_url.to_string() - + &format!("{}/{}/{}", from, to, converted_date.format("%m-%d-%Y")) - + "/?format=json"; + let fms = + format!("{}/{}/{}", from, to, converted_date.format("%m-%d-%Y")) + "/?format=json"; + let exchange_rate_url: String = base_exchange_rate_url.to_string() + fms.as_str(); let body = client.get(&(exchange_rate_url)).send(); let actual_body = body.map_err(|_| { @@ -155,12 +223,12 @@ fn compute_div_taxation(transactions: &Vec) -> (f32, f32) { // Gross income from dividends in target currency (PLN, EUR etc.) let gross_us_pl: f32 = transactions .iter() - .map(|x| x.exchange_rate * x.gross_us) + .map(|x| x.exchange_rate * x.gross.value() as f32) .sum(); // Tax paid in US in PLN let tax_us_pl: f32 = transactions .iter() - .map(|x| x.exchange_rate * x.tax_us) + .map(|x| x.exchange_rate * x.tax_paid.value() as f32) .sum(); (gross_us_pl, tax_us_pl) } @@ -184,23 +252,38 @@ pub fn format_sold_transactions_to_string() {} pub fn run_taxation( rd: &Box, names: Vec, -) -> Result<(f32, f32, f32, f32, Vec, Vec), String> { +) -> Result< + ( + f32, + f32, + f32, + f32, + Vec, + Vec, + Vec, + ), + String, +> { let mut parsed_div_transactions: Vec<(String, f32, f32)> = vec![]; let mut parsed_sold_transactions: Vec<(String, String, i32, f32, f32)> = vec![]; let mut parsed_gain_and_losses: Vec<(String, String, f32, f32, f32)> = vec![]; + let mut parsed_revolut_transactions: Vec<(String, Currency)> = vec![]; - // 1. Parse PDF and XLSX documents to get list of transactions - names.iter().for_each(|x| { + // 1. Parse PDF,XLSX and CSV documents to get list of transactions + names.iter().try_for_each(|x| { // If name contains .pdf then parse as pdf // if name contains .xlsx then parse as spreadsheet if x.contains(".pdf") { - let (mut div_t, mut sold_t, _) = pdfparser::parse_brokerage_statement(x); + let (mut div_t, mut sold_t, _) = pdfparser::parse_brokerage_statement(x)?; parsed_div_transactions.append(&mut div_t); parsed_sold_transactions.append(&mut sold_t); + } else if x.contains(".xlsx") { + parsed_gain_and_losses.append(&mut xlsxparser::parse_gains_and_losses(x)?); } else { - parsed_gain_and_losses.append(&mut xlsxparser::parse_gains_and_losses(x)); + parsed_revolut_transactions.append(&mut csvparser::parse_revolut_transactions(x)?); } - }); + Ok::<(), &str>(()) + })?; // 2. Verify Transactions verify_dividends_transactions(&parsed_div_transactions)?; log::info!("Dividends transactions are consistent"); @@ -213,43 +296,63 @@ pub fn run_taxation( // Gather all trade , settlement and transaction dates into hash map to be passed to // get_exchange_rate // Hash map : Key(event date) -> (preceeding date, exchange_rate) - let mut dates: std::collections::HashMap> = + let mut dates: std::collections::HashMap> = std::collections::HashMap::new(); parsed_div_transactions .iter() .for_each(|(trade_date, _, _)| { - if dates.contains_key(trade_date) == false { - dates.insert(trade_date.clone(), None); + let ex = Exchange::USD(trade_date.clone()); + if dates.contains_key(&ex) == false { + dates.insert(ex, None); } }); detailed_sold_transactions.iter().for_each( |(trade_date, settlement_date, acquisition_date, _, _)| { - if dates.contains_key(trade_date) == false { - dates.insert(trade_date.clone(), None); + let ex = Exchange::USD(trade_date.clone()); + if dates.contains_key(&ex) == false { + dates.insert(ex, None); } - if dates.contains_key(settlement_date) == false { - dates.insert(settlement_date.clone(), None); + let ex = Exchange::USD(settlement_date.clone()); + if dates.contains_key(&ex) == false { + dates.insert(ex, None); } - if dates.contains_key(acquisition_date) == false { - dates.insert(acquisition_date.clone(), None); + let ex = Exchange::USD(acquisition_date.clone()); + if dates.contains_key(&ex) == false { + dates.insert(ex, None); } }, ); + parsed_revolut_transactions + .iter() + .for_each(|(trade_date, currency)| { + let ex = match currency { + Currency::EUR(_) => Exchange::EUR(trade_date.clone()), + Currency::PLN(_) => Exchange::PLN(trade_date.clone()), + Currency::USD(_) => Exchange::USD(trade_date.clone()), + }; + if dates.contains_key(&ex) == false { + dates.insert(ex, None); + } + }); - rd.get_exchange_rates(&mut dates).map_err(|x| "Error: unable to get exchange rates. Please check your internet connection or proxy settings\n\nDetails:".to_string()+&x)?; + rd.get_exchange_rates(&mut dates).map_err(|x| "Error: unable to get exchange rates. Please check your internet connection or proxy settings\n\nDetails:".to_string()+x.as_str())?; // Make a detailed_div_transactions - let transactions = create_detailed_div_transactions(parsed_div_transactions, &dates); - let sold_transactions = create_detailed_sold_transactions(detailed_sold_transactions, &dates); + let transactions = create_detailed_div_transactions(parsed_div_transactions, &dates)?; + let sold_transactions = create_detailed_sold_transactions(detailed_sold_transactions, &dates)?; + let revolut_transactions = + create_detailed_revolut_transactions(parsed_revolut_transactions, &dates)?; let (gross_div, tax_div) = compute_div_taxation(&transactions); let (gross_sold, cost_sold) = compute_sold_taxation(&sold_transactions); + let (gross_revolut, cost_revolut) = compute_div_taxation(&revolut_transactions); Ok(( gross_div, tax_div, - gross_sold, - cost_sold, + gross_sold + gross_revolut, // We put sold and savings income into the same column + cost_sold + cost_revolut, transactions, + revolut_transactions, sold_transactions, )) } @@ -262,8 +365,8 @@ mod tests { // Init Transactions let transactions: Vec = vec![Transaction { transaction_date: "N/A".to_string(), - gross_us: 100.0, - tax_us: 25.0, + gross: crate::Currency::USD(100.0), + tax_paid: crate::Currency::USD(25.0), exchange_rate_date: "N/A".to_string(), exchange_rate: 4.0, }]; @@ -277,15 +380,15 @@ mod tests { let transactions: Vec = vec![ Transaction { transaction_date: "N/A".to_string(), - gross_us: 100.0, - tax_us: 25.0, + gross: crate::Currency::USD(100.0), + tax_paid: crate::Currency::USD(25.0), exchange_rate_date: "N/A".to_string(), exchange_rate: 4.0, }, Transaction { transaction_date: "N/A".to_string(), - gross_us: 126.0, - tax_us: 10.0, + gross: crate::Currency::USD(126.0), + tax_paid: crate::Currency::USD(10.0), exchange_rate_date: "N/A".to_string(), exchange_rate: 3.5, }, @@ -296,6 +399,55 @@ mod tests { ); Ok(()) } + #[test] + fn test_revolut_savings_taxation_pln() -> Result<(), String> { + let transactions: Vec = vec![ + Transaction { + transaction_date: "03/01/21".to_string(), + gross: crate::Currency::PLN(0.44), + tax_paid: crate::Currency::PLN(0.0), + exchange_rate_date: "N/A".to_string(), + exchange_rate: 1.0, + }, + Transaction { + transaction_date: "04/11/21".to_string(), + gross: crate::Currency::PLN(0.45), + tax_paid: crate::Currency::PLN(0.0), + exchange_rate_date: "N/A".to_string(), + exchange_rate: 1.0, + }, + ]; + assert_eq!( + compute_div_taxation(&transactions), + (0.44 * 1.0 + 0.45 * 1.0, 0.0) + ); + Ok(()) + } + + #[test] + fn test_revolut_savings_taxation_eur() -> Result<(), String> { + let transactions: Vec = vec![ + Transaction { + transaction_date: "03/01/21".to_string(), + gross: crate::Currency::EUR(0.44), + tax_paid: crate::Currency::EUR(0.0), + exchange_rate_date: "02/28/21".to_string(), + exchange_rate: 2.0, + }, + Transaction { + transaction_date: "04/11/21".to_string(), + gross: crate::Currency::EUR(0.45), + tax_paid: crate::Currency::EUR(0.0), + exchange_rate_date: "04/10/21".to_string(), + exchange_rate: 3.0, + }, + ]; + assert_eq!( + compute_div_taxation(&transactions), + (0.44 * 2.0 + 0.45 * 3.0, 0.0) + ); + Ok(()) + } #[test] fn test_simple_sold_taxation() -> Result<(), String> { diff --git a/src/main.rs b/src/main.rs index 86b5e92..c4f5840 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,8 +11,13 @@ mod gui; use etradeTaxReturnHelper::run_taxation; use logging::ResultExt; +// TODO: investemnt info of revolut +// TODO: async to get currency +// TODO: parse_gain_and_losses expect -> ? // TODO: GUI : choosing residency // TODO: Drag&Drop to work on MultiBrowser field +// TODO: Change run_taxation for_Each into try_for_Ech if possible +// TODO: taxation of EUR instruments in US fn create_cmd_line_pattern<'a, 'b>(myapp: App<'a, 'b>) -> App<'a, 'b> { myapp @@ -71,7 +76,7 @@ fn main() { let pdfnames: Vec = pdfnames.map(|x| x.to_string()).collect(); let (gross_div, tax_div, gross_sold, cost_sold) = match run_taxation(&rd, pdfnames) { - Ok((gross_div, tax_div, gross_sold, cost_sold, _, _)) => { + Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _)) => { (gross_div, tax_div, gross_sold, cost_sold) } Err(msg) => panic!("\nError: Unable to compute taxes. \n\nDetails: {msg}"), @@ -94,12 +99,23 @@ mod tests { fn test_exchange_rate_de() -> Result<(), String> { let rd: Box = Box::new(de::DE {}); - let mut dates: std::collections::HashMap> = - std::collections::HashMap::new(); - dates.insert("02/21/23".to_owned(), None); - rd.get_exchange_rates(&mut dates).unwrap(); + let mut dates: std::collections::HashMap< + etradeTaxReturnHelper::Exchange, + Option<(String, f32)>, + > = std::collections::HashMap::new(); + + dates.insert( + etradeTaxReturnHelper::Exchange::USD("02/21/23".to_owned()), + None, + ); + + rd.get_exchange_rates(&mut dates)?; + + let (exchange_rate_date, exchange_rate) = dates + [&etradeTaxReturnHelper::Exchange::USD("02/21/23".to_owned())] + .clone() + .unwrap(); - let (exchange_rate_date, exchange_rate) = dates.remove("02/21/23").unwrap().unwrap(); assert_eq!( (exchange_rate_date, exchange_rate), ("2023-02-20".to_owned(), 0.93561) @@ -111,11 +127,23 @@ mod tests { fn test_exchange_rate_pl() -> Result<(), String> { let rd: Box = Box::new(pl::PL {}); - let mut dates: std::collections::HashMap> = - std::collections::HashMap::new(); - dates.insert("03/01/21".to_owned(), None); - rd.get_exchange_rates(&mut dates).unwrap(); - let (exchange_rate_date, exchange_rate) = dates.remove("03/01/21").unwrap().unwrap(); + let mut dates: std::collections::HashMap< + etradeTaxReturnHelper::Exchange, + Option<(String, f32)>, + > = std::collections::HashMap::new(); + + dates.insert( + etradeTaxReturnHelper::Exchange::USD("03/01/21".to_owned()), + None, + ); + + rd.get_exchange_rates(&mut dates)?; + + let (exchange_rate_date, exchange_rate) = dates + [&etradeTaxReturnHelper::Exchange::USD("03/01/21".to_owned())] + .clone() + .unwrap(); + assert_eq!( (exchange_rate_date, exchange_rate), ("2021-02-26".to_owned(), 3.7247) @@ -126,11 +154,24 @@ mod tests { #[test] fn test_exchange_rate_us() -> Result<(), String> { let rd: Box = Box::new(us::US {}); - let mut dates: std::collections::HashMap> = - std::collections::HashMap::new(); - dates.insert("03/01/21".to_owned(), None); - rd.get_exchange_rates(&mut dates).unwrap(); - let (exchange_rate_date, exchange_rate) = dates.remove("03/01/21").unwrap().unwrap(); + + let mut dates: std::collections::HashMap< + etradeTaxReturnHelper::Exchange, + Option<(String, f32)>, + > = std::collections::HashMap::new(); + + dates.insert( + etradeTaxReturnHelper::Exchange::USD("03/01/21".to_owned()), + None, + ); + + rd.get_exchange_rates(&mut dates)?; + + let (exchange_rate_date, exchange_rate) = dates + [&etradeTaxReturnHelper::Exchange::USD("03/01/21".to_owned())] + .clone() + .unwrap(); + assert_eq!((exchange_rate_date, exchange_rate), ("N/A".to_owned(), 1.0)); Ok(()) } @@ -243,7 +284,7 @@ mod tests { let pdfnames: Vec = pdfnames.map(|x| x.to_string()).collect(); match etradeTaxReturnHelper::run_taxation(&rd, pdfnames) { - Ok((gross_div, tax_div, gross_sold, cost_sold, _, _)) => { + Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _)) => { assert_eq!( (gross_div, tax_div, gross_sold, cost_sold), (14062.57, 2109.3772, 395.45355, 91.156715) @@ -274,7 +315,7 @@ mod tests { let pdfnames: Vec = pdfnames.map(|x| x.to_string()).collect(); match etradeTaxReturnHelper::run_taxation(&rd, pdfnames) { - Ok((gross_div, tax_div, gross_sold, cost_sold, _, _)) => { + Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _)) => { assert_eq!( (gross_div, tax_div, gross_sold, cost_sold), (2930.206, 439.54138, 395.45355, 91.156715) @@ -301,7 +342,7 @@ mod tests { let pdfnames: Vec = pdfnames.map(|x| x.to_string()).collect(); match etradeTaxReturnHelper::run_taxation(&rd, pdfnames) { - Ok((gross_div, tax_div, gross_sold, cost_sold, _, _)) => { + Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _)) => { assert_eq!( (gross_div, tax_div, gross_sold, cost_sold), (3272.3125, 490.82773, 0.0, 0.0), diff --git a/src/pdfparser.rs b/src/pdfparser.rs index 286f74a..acab0d3 100644 --- a/src/pdfparser.rs +++ b/src/pdfparser.rs @@ -266,11 +266,14 @@ fn yield_sold_transaction( /// (trade_date, settlement_date, quantity, price, amount_sold) pub fn parse_brokerage_statement( pdftoparse: &str, -) -> ( - Vec<(String, f32, f32)>, - Vec<(String, String, i32, f32, f32)>, - Vec<(String, String, i32, f32, f32, f32, f32, f32)>, -) { +) -> Result< + ( + Vec<(String, f32, f32)>, + Vec<(String, String, i32, f32, f32)>, + Vec<(String, String, i32, f32, f32, f32, f32, f32)>, + ), + &str, +> { //2. parsing each pdf let mypdffile = File::>::open(pdftoparse) .expect_and_log(&format!("Error opening and parsing file: {}", pdftoparse)); @@ -434,7 +437,7 @@ pub fn parse_brokerage_statement( } } } - (div_transactions, sold_transactions, trades) + Ok((div_transactions, sold_transactions, trades)) } #[cfg(test)] @@ -515,15 +518,15 @@ mod tests { fn test_parse_brokerage_statement() -> Result<(), String> { assert_eq!( parse_brokerage_statement("data/example-divs.pdf"), - ( + (Ok(( vec![("03/01/22".to_owned(), 698.25, 104.74)], vec![], vec![] - ) + ))) ); assert_eq!( parse_brokerage_statement("data/example-sold-wire.pdf"), - ( + Ok(( vec![], vec![( "05/02/22".to_owned(), @@ -533,7 +536,7 @@ mod tests { 43.67 )], vec![] - ) + )) ); //TODO(jczaja): Renable reinvest dividends case as soon as you get some PDFs diff --git a/src/pl.rs b/src/pl.rs index 2a97252..fcbc723 100644 --- a/src/pl.rs +++ b/src/pl.rs @@ -32,7 +32,10 @@ struct ExchangeRate { impl etradeTaxReturnHelper::Residency for PL { fn get_exchange_rates( &self, - dates: &mut std::collections::HashMap>, + dates: &mut std::collections::HashMap< + etradeTaxReturnHelper::Exchange, + Option<(String, f32)>, + >, ) -> Result<(), String> { // proxies are taken from env vars: http_proxy and https_proxy let http_proxy = std::env::var("http_proxy"); @@ -60,7 +63,16 @@ impl etradeTaxReturnHelper::Residency for PL { let base_exchange_rate_url = "https://api.nbp.pl/api/exchangerates/rates/a/"; - dates.iter_mut().try_for_each(|(date, val)| { + dates.iter_mut().try_for_each(|(exchange, val)| { + let (from, date) = match exchange { + etradeTaxReturnHelper::Exchange::USD(date) => ("usd", date), + etradeTaxReturnHelper::Exchange::EUR(date) => ("eur", date), + etradeTaxReturnHelper::Exchange::PLN(_) => { + *val = Some(("N/A".to_owned(), 1.0)); + return Ok::<(), String>(()); + } // For PLN to PLN follow fast path + }; + let mut converted_date = chrono::NaiveDate::parse_from_str(&date, "%m/%d/%y").unwrap(); // Try to get exchange rate going backwards with dates till success @@ -71,7 +83,7 @@ impl etradeTaxReturnHelper::Residency for PL { .ok_or("Error traversing date")?; let exchange_rate_url: String = base_exchange_rate_url.to_string() - + &format!("usd/{}", converted_date.format("%Y-%m-%d")) + + format!("{}/{}", from, converted_date.format("%Y-%m-%d")).as_str() + "/?format=json"; let body = client.get(&(exchange_rate_url)).send(); @@ -166,6 +178,50 @@ mod tests { Ok(()) } + #[test] + fn test_get_exchange_rates_pl() -> Result<(), String> { + let mut dates: std::collections::HashMap< + etradeTaxReturnHelper::Exchange, + Option<(String, f32)>, + > = std::collections::HashMap::new(); + dates.insert( + etradeTaxReturnHelper::Exchange::PLN("07/14/81".to_owned()), + None, + ); + dates.insert( + etradeTaxReturnHelper::Exchange::PLN("08/14/81".to_owned()), + None, + ); + dates.insert( + etradeTaxReturnHelper::Exchange::PLN("09/14/81".to_owned()), + None, + ); + + let rd: Box = Box::new(crate::pl::PL {}); + rd.get_exchange_rates(&mut dates).map_err(|x| "Error: unable to get exchange rates. Please check your internet connection or proxy settings\n\nDetails:".to_string()+x.as_str())?; + + let mut expected_result: std::collections::HashMap< + etradeTaxReturnHelper::Exchange, + Option<(String, f32)>, + > = std::collections::HashMap::new(); + expected_result.insert( + etradeTaxReturnHelper::Exchange::PLN("07/14/81".to_owned()), + Some(("N/A".to_owned(), 1.0)), + ); + expected_result.insert( + etradeTaxReturnHelper::Exchange::PLN("08/14/81".to_owned()), + Some(("N/A".to_owned(), 1.0)), + ); + expected_result.insert( + etradeTaxReturnHelper::Exchange::PLN("09/14/81".to_owned()), + Some(("N/A".to_owned(), 1.0)), + ); + + assert_eq!(dates, expected_result); + + Ok(()) + } + #[test] fn test_present_result_double_taxation_warning_pl() -> Result<(), String> { let rd: Box = Box::new(PL {}); diff --git a/src/transactions.rs b/src/transactions.rs index 234ca1d..72aa882 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -81,31 +81,69 @@ pub fn reconstruct_sold_transactions( Ok(detailed_sold_transactions) } +pub fn create_detailed_revolut_transactions( + transactions: Vec<(String, crate::Currency)>, + dates: &std::collections::HashMap>, +) -> Result, &str> { + let mut detailed_transactions: Vec = Vec::new(); + + transactions + .iter() + .try_for_each(|(transaction_date, gross)| { + let (exchange_rate_date, exchange_rate) = dates + //[&crate::Exchange::USD(transaction_date.clone())] + [&gross.derive_exchange(transaction_date.clone())] + .clone() + .unwrap(); + + let transaction = Transaction { + transaction_date: transaction_date.clone(), + gross: *gross, + //Revolut does not take taxes in savings account + tax_paid: gross.derive(0.0), + exchange_rate_date, + exchange_rate, + }; + + let msg = transaction.format_to_print("REVOLUT")?; + + println!("{}", msg); + log::info!("{}", msg); + detailed_transactions.push(transaction); + Ok::<(), &str>(()) + })?; + Ok(detailed_transactions) +} + pub fn create_detailed_div_transactions( transactions: Vec<(String, f32, f32)>, - dates: &std::collections::HashMap>, -) -> Vec { + dates: &std::collections::HashMap>, +) -> Result, &str> { let mut detailed_transactions: Vec = Vec::new(); transactions .iter() - .for_each(|(transaction_date, gross_us, tax_us)| { - let (exchange_rate_date, exchange_rate) = dates[transaction_date].clone().unwrap(); + .try_for_each(|(transaction_date, gross_us, tax_us)| { + let (exchange_rate_date, exchange_rate) = dates + [&crate::Exchange::USD(transaction_date.clone())] + .clone() + .unwrap(); let transaction = Transaction { transaction_date: transaction_date.clone(), - gross_us: gross_us.clone(), - tax_us: tax_us.clone(), + gross: crate::Currency::USD(*gross_us as f64), + tax_paid: crate::Currency::USD(*tax_us as f64), exchange_rate_date, exchange_rate, }; - let msg = transaction.format_to_print(); + let msg = transaction.format_to_print("DIV")?; println!("{}", msg); log::info!("{}", msg); detailed_transactions.push(transaction); - }); - detailed_transactions + Ok::<(), &str>(()) + })?; + Ok(detailed_transactions) } // pub trade_date: String, @@ -119,15 +157,19 @@ pub fn create_detailed_div_transactions( // pub exchange_rate_acquisition: f32, pub fn create_detailed_sold_transactions( transactions: Vec<(String, String, String, f32, f32)>, - dates: &std::collections::HashMap>, -) -> Vec { + dates: &std::collections::HashMap>, +) -> Result, &str> { let mut detailed_transactions: Vec = Vec::new(); transactions.iter().for_each( |(trade_date, settlement_date, acquisition_date, income, cost_basis)| { - let (exchange_rate_settlement_date, exchange_rate_settlement) = - dates[settlement_date].clone().unwrap(); - let (exchange_rate_acquisition_date, exchange_rate_acquisition) = - dates[acquisition_date].clone().unwrap(); + let (exchange_rate_settlement_date, exchange_rate_settlement) = dates + [&crate::Exchange::USD(settlement_date.clone())] + .clone() + .unwrap(); + let (exchange_rate_acquisition_date, exchange_rate_acquisition) = dates + [&crate::Exchange::USD(acquisition_date.clone())] + .clone() + .unwrap(); let transaction = SoldTransaction { settlement_date: settlement_date.clone(), @@ -149,7 +191,7 @@ pub fn create_detailed_sold_transactions( detailed_transactions.push(transaction); }, ); - detailed_transactions + Ok(detailed_transactions) } #[cfg(test)] @@ -166,6 +208,92 @@ mod tests { verify_dividends_transactions(&transactions) } + #[test] + fn test_create_detailed_revolut_transactions_eur() -> Result<(), String> { + let parsed_transactions = vec![ + ("03/01/21".to_owned(), crate::Currency::EUR(0.05)), + ("04/11/21".to_owned(), crate::Currency::EUR(0.07)), + ]; + + let mut dates: std::collections::HashMap> = + std::collections::HashMap::new(); + + dates.insert( + crate::Exchange::EUR("03/01/21".to_owned()), + Some(("02/28/21".to_owned(), 2.0)), + ); + dates.insert( + crate::Exchange::EUR("04/11/21".to_owned()), + Some(("04/10/21".to_owned(), 3.0)), + ); + + let transactions = create_detailed_revolut_transactions(parsed_transactions, &dates); + + assert_eq!( + transactions, + Ok(vec![ + Transaction { + transaction_date: "03/01/21".to_string(), + gross: crate::Currency::EUR(0.05), + tax_paid: crate::Currency::EUR(0.0), + exchange_rate_date: "02/28/21".to_string(), + exchange_rate: 2.0, + }, + Transaction { + transaction_date: "04/11/21".to_string(), + gross: crate::Currency::EUR(0.07), + tax_paid: crate::Currency::EUR(0.0), + exchange_rate_date: "04/10/21".to_string(), + exchange_rate: 3.0, + }, + ]) + ); + Ok(()) + } + + #[test] + fn test_create_detailed_revolut_transactions_pln() -> Result<(), String> { + let parsed_transactions = vec![ + ("03/01/21".to_owned(), crate::Currency::PLN(0.44)), + ("04/11/21".to_owned(), crate::Currency::PLN(0.45)), + ]; + + let mut dates: std::collections::HashMap> = + std::collections::HashMap::new(); + + dates.insert( + crate::Exchange::PLN("03/01/21".to_owned()), + Some(("N/A".to_owned(), 1.0)), + ); + dates.insert( + crate::Exchange::PLN("04/11/21".to_owned()), + Some(("N/A".to_owned(), 1.0)), + ); + + let transactions = create_detailed_revolut_transactions(parsed_transactions, &dates); + + assert_eq!( + transactions, + Ok(vec![ + Transaction { + transaction_date: "03/01/21".to_string(), + gross: crate::Currency::PLN(0.44), + tax_paid: crate::Currency::PLN(0.0), + exchange_rate_date: "N/A".to_string(), + exchange_rate: 1.0, + }, + Transaction { + transaction_date: "04/11/21".to_string(), + gross: crate::Currency::PLN(0.45), + tax_paid: crate::Currency::PLN(0.0), + exchange_rate_date: "N/A".to_string(), + exchange_rate: 1.0, + }, + ]) + ); + Ok(()) + } + #[test] fn test_create_detailed_div_transactions() -> Result<(), String> { let parsed_transactions: Vec<(String, f32, f32)> = vec![ @@ -173,31 +301,38 @@ mod tests { ("03/01/21".to_string(), 126.0, 10.0), ]; - let mut dates: std::collections::HashMap> = + let mut dates: std::collections::HashMap> = std::collections::HashMap::new(); - dates.insert("03/01/21".to_owned(), Some(("02/28/21".to_owned(), 2.0))); - dates.insert("04/11/21".to_owned(), Some(("04/10/21".to_owned(), 3.0))); + + dates.insert( + crate::Exchange::USD("03/01/21".to_owned()), + Some(("02/28/21".to_owned(), 2.0)), + ); + dates.insert( + crate::Exchange::USD("04/11/21".to_owned()), + Some(("04/10/21".to_owned(), 3.0)), + ); let transactions = create_detailed_div_transactions(parsed_transactions, &dates); assert_eq!( transactions, - vec![ + Ok(vec![ Transaction { transaction_date: "04/11/21".to_string(), - gross_us: 100.0, - tax_us: 25.0, + gross: crate::Currency::USD(100.0), + tax_paid: crate::Currency::USD(25.0), exchange_rate_date: "04/10/21".to_string(), exchange_rate: 3.0, }, Transaction { transaction_date: "03/01/21".to_string(), - gross_us: 126.0, - tax_us: 10.0, + gross: crate::Currency::USD(126.0), + tax_paid: crate::Currency::USD(10.0), exchange_rate_date: "02/28/21".to_string(), exchange_rate: 2.0, }, - ] + ]) ); Ok(()) } @@ -221,22 +356,47 @@ mod tests { ), ]; - let mut dates: std::collections::HashMap> = + let mut dates: std::collections::HashMap> = std::collections::HashMap::new(); - dates.insert("01/01/21".to_owned(), Some(("12/30/20".to_owned(), 1.0))); - dates.insert("03/01/21".to_owned(), Some(("02/28/21".to_owned(), 2.0))); - dates.insert("03/03/21".to_owned(), Some(("03/02/21".to_owned(), 2.5))); - dates.insert("06/01/21".to_owned(), Some(("06/03/21".to_owned(), 3.0))); - dates.insert("06/03/21".to_owned(), Some(("06/05/21".to_owned(), 4.0))); - dates.insert("01/01/21".to_owned(), Some(("02/28/21".to_owned(), 5.0))); - dates.insert("01/01/19".to_owned(), Some(("12/30/18".to_owned(), 6.0))); - dates.insert("04/11/21".to_owned(), Some(("04/10/21".to_owned(), 7.0))); + + dates.insert( + crate::Exchange::USD("01/01/21".to_owned()), + Some(("12/30/20".to_owned(), 1.0)), + ); + dates.insert( + crate::Exchange::USD("03/01/21".to_owned()), + Some(("02/28/21".to_owned(), 2.0)), + ); + dates.insert( + crate::Exchange::USD("03/03/21".to_owned()), + Some(("03/02/21".to_owned(), 2.5)), + ); + dates.insert( + crate::Exchange::USD("06/01/21".to_owned()), + Some(("06/03/21".to_owned(), 3.0)), + ); + dates.insert( + crate::Exchange::USD("06/03/21".to_owned()), + Some(("06/05/21".to_owned(), 4.0)), + ); + dates.insert( + crate::Exchange::USD("01/01/21".to_owned()), + Some(("02/28/21".to_owned(), 5.0)), + ); + dates.insert( + crate::Exchange::USD("01/01/19".to_owned()), + Some(("12/30/18".to_owned(), 6.0)), + ); + dates.insert( + crate::Exchange::USD("04/11/21".to_owned()), + Some(("04/10/21".to_owned(), 7.0)), + ); let transactions = create_detailed_sold_transactions(parsed_transactions, &dates); assert_eq!( transactions, - vec![ + Ok(vec![ SoldTransaction { trade_date: "03/01/21".to_string(), settlement_date: "03/03/21".to_string(), @@ -259,7 +419,7 @@ mod tests { exchange_rate_acquisition_date: "12/30/18".to_string(), exchange_rate_acquisition: 6.0, }, - ] + ]) ); Ok(()) } diff --git a/src/us.rs b/src/us.rs index 0dcb571..552e29c 100644 --- a/src/us.rs +++ b/src/us.rs @@ -2,7 +2,10 @@ pub struct US {} impl etradeTaxReturnHelper::Residency for US { fn get_exchange_rates( &self, - dates: &mut std::collections::HashMap>, + dates: &mut std::collections::HashMap< + etradeTaxReturnHelper::Exchange, + Option<(String, f32)>, + >, ) -> Result<(), String> { dates.iter_mut().for_each(|(_date, val)| { *val = Some(("N/A".to_owned(), 1.0)); diff --git a/src/xlsxparser.rs b/src/xlsxparser.rs index b385b1b..12934ef 100644 --- a/src/xlsxparser.rs +++ b/src/xlsxparser.rs @@ -9,9 +9,11 @@ pub use crate::logging::ResultExt; /// aqusition cost of sold stock (aquisition_cost) /// adjusted aquisition cost of sold stock (cost_basis) /// income from sold stock (total_proceeds) -pub fn parse_gains_and_losses(xlsxtoparse: &str) -> Vec<(String, String, f32, f32, f32)> { - let mut excel: Xlsx<_> = open_workbook(xlsxtoparse) - .expect_and_log(&format!("Error opening XLSX file: {}", xlsxtoparse)); +pub fn parse_gains_and_losses( + xlsxtoparse: &str, +) -> Result, &str> { + let mut excel: Xlsx<_> = + open_workbook(xlsxtoparse).map_err(|_| "Error opening XLSX file: {}")?; let name = excel .sheet_names() .first() @@ -88,7 +90,7 @@ pub fn parse_gains_and_losses(xlsxtoparse: &str) -> Vec<(String, String, f32, f3 } } log::info!("G&L Transactions: {:#?}", transactions); - transactions + Ok(transactions) } #[cfg(test)] @@ -99,7 +101,7 @@ mod tests { fn test_parse_gain_and_losses() -> Result<(), String> { assert_eq!( parse_gains_and_losses("data/G&L_Collapsed.xlsx"), - (vec![ + Ok((vec![ ( "04/24/2013".to_owned(), "04/11/2022".to_owned(), @@ -114,11 +116,11 @@ mod tests { 29.28195, 43.67 ) - ]) + ])) ); assert_eq!( parse_gains_and_losses("data/G&L_Expanded.xlsx"), - (vec![ + Ok((vec![ ( "04/24/2013".to_owned(), "04/11/2022".to_owned(), @@ -133,7 +135,7 @@ mod tests { 29.28195, 43.67 ) - ]) + ])) ); Ok(())