From dcb0212105fa195e8546437a7a8d471d8609d5c3 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 13 Apr 2023 20:08:45 -0700 Subject: [PATCH] Initial Commit --- .gitignore | 1 + Cargo.lock | 1193 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 13 + rustfmt.toml | 77 +++ src/asset_account.rs | 189 +++++++ src/books.rs | 249 ++++++++ src/external_account.rs | 67 +++ src/main.rs | 18 + src/oracle.rs | 43 ++ src/spreadsheet.rs | 283 ++++++++++ 10 files changed, 2133 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 rustfmt.toml create mode 100644 src/asset_account.rs create mode 100644 src/books.rs create mode 100644 src/external_account.rs create mode 100644 src/main.rs create mode 100644 src/oracle.rs create mode 100644 src/spreadsheet.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..79a5308 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1193 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "angle" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf965b6b142c8c68150e815ef527d17f0fe74e622b2c5287fd8626cb7c4a5fa" +dependencies = [ + "num-traits", + "serde", + "serde_derive", +] + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "borsh" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive", + "hashbrown 0.13.2", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "bytecheck" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13fe11640a23eb24562225322cd3e452b93a3d4091d62fab69c70542fcd17d1f" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31225543cb46f81a7e224762764f4a6a0f097b1db0b175f69e8065efaa42de5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytecount" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d6cda18d80350d6a6d15d1f2dd6f5c88b3d61882e08801e8d68f506434b808" +dependencies = [ + "angle", + "half", + "num-traits", + "serde", + "serde_derive", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.14", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.14", +] + +[[package]] +name = "displaydoc" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "icu_locid" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d7a98ecb812760b5f077e55a4763edeefa7ccc30d6eb5680a70841ede81928" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "ledger-parser" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edccb6e83d1a4d5dfd9c0bbca42ffe69d1d0f5bd5d26711041cdc6d3c5847161" +dependencies = [ + "chrono", + "nom", + "rust_decimal", +] + +[[package]] +name = "ledgerneo" +version = "0.1.0" +dependencies = [ + "chrono", + "icu_locid", + "ledger-parser", + "rust_decimal", + "spreadsheet-ods", +] + +[[package]] +name = "libc" +version = "0.2.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "litemap" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575d8a551c59104b4df91269921e5eab561aa1b77c618dac0414b5d44a4617de" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[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.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "mktemp" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bdc1f74dd7bb717d39f784f844e490d935b3aa7e383008006dbbf29c1f7820a" +dependencies = [ + "uuid", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[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 = "nom_locate" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e299bf5ea7b212e811e71174c5d1a5d065c4c0ad0c8691ecb1f97e3e66025e" +dependencies = [ + "bytecount", + "memchr", + "nom", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quick-xml" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +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 = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rend" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21499ed91807f07ae081880aabb2ccc0235e9d88011867d984525e9a4c3cfa3e" +dependencies = [ + "bytecheck", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1c672430eb41556291981f45ca900a0239ad007242d1cb4b4167af842db666" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust_decimal" +version = "1.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc" +dependencies = [ + "arrayvec", + "borsh", + "bytecheck", + "byteorder", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rust_decimal_macros" +version = "1.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e773fd3da1ed42472fdf3cfdb4972948a555bc3d73f5e0bdb99d17e7b54c687" +dependencies = [ + "quote", + "rust_decimal", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "serde" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" + +[[package]] +name = "serde_derive" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.14", +] + +[[package]] +name = "serde_json" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "spreadsheet-ods" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dedd2b14d76d7f9116905fd53adb0465d468070127f9bb8596a4cf7d24c2992" +dependencies = [ + "chrono", + "color-rs", + "icu_locid", + "lazy_static", + "mktemp", + "nom", + "nom_locate", + "quick-xml", + "rust_decimal", + "rust_decimal_macros", + "string_cache", + "time 0.3.20", + "unicode-ident", + "zip", +] + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "tinystr" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ac3f5b6856e931e15e07b478e98c8045239829a65f9156d4fa7e7788197a5ef" +dependencies = [ + "displaydoc", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "uuid" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "writeable" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d74a687e3b9a7a129db0a8c82b4d464eb9c36f5a66ca68572a7e5f1cfdb5bc" + +[[package]] +name = "zip" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", + "flate2", + "time 0.3.20", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e63888c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ledgerneo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ledger-parser = "5.1.1" +rust_decimal = "1.29.0" +spreadsheet-ods = "0.15.0" +chrono = "0.4.24" +icu_locid = "1.1.0" \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..da8d95f --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,77 @@ +max_width = 200 +hard_tabs = false +tab_spaces = 4 +newline_style = "Auto" +indent_style = "Block" +use_small_heuristics = "Default" +fn_call_width = 60 +attr_fn_like_width = 70 +struct_lit_width = 18 +struct_variant_width = 35 +array_width = 60 +chain_width = 60 +single_line_if_else_max_width = 50 +wrap_comments = false +format_code_in_doc_comments = false +doc_comment_code_block_width = 100 +comment_width = 80 +normalize_comments = false +normalize_doc_attributes = false +format_strings = false +format_macro_matchers = false +format_macro_bodies = true +skip_macro_invocations = [] +hex_literal_case = "Preserve" +empty_item_single_line = true +struct_lit_single_line = true +fn_single_line = false +where_single_line = false +imports_indent = "Block" +imports_layout = "Mixed" +imports_granularity = "Preserve" +group_imports = "Preserve" +reorder_imports = true +reorder_modules = true +reorder_impl_items = false +type_punctuation_density = "Wide" +space_before_colon = false +space_after_colon = true +spaces_around_ranges = false +binop_separator = "Front" +remove_nested_parens = true +combine_control_expr = true +short_array_element_width_threshold = 10 +overflow_delimited_expr = false +struct_field_align_threshold = 0 +enum_discrim_align_threshold = 0 +match_arm_blocks = true +match_arm_leading_pipes = "Never" +force_multiline_blocks = false +fn_params_layout = "Tall" +brace_style = "SameLineWhere" +control_brace_style = "AlwaysSameLine" +trailing_semicolon = true +trailing_comma = "Vertical" +match_block_trailing_comma = false +blank_lines_upper_bound = 1 +blank_lines_lower_bound = 0 +edition = "2015" +version = "One" +inline_attribute_width = 0 +format_generated_files = true +merge_derives = true +use_try_shorthand = false +use_field_init_shorthand = false +force_explicit_abi = true +condense_wildcard_suffixes = false +color = "Auto" +required_version = "1.5.2" +unstable_features = false +disable_all_formatting = false +skip_children = false +hide_parse_errors = false +error_on_line_overflow = false +error_on_unformatted = false +ignore = [] +emit_mode = "Files" +make_backup = false diff --git a/src/asset_account.rs b/src/asset_account.rs new file mode 100644 index 0000000..5309704 --- /dev/null +++ b/src/asset_account.rs @@ -0,0 +1,189 @@ +use std::borrow::Borrow; +use crate::CommoditiesPriceOracle; +use rust_decimal::Decimal; +use std::collections::HashMap; +use chrono::Month::December; +use rust_decimal::prelude::ToPrimitive; + +#[derive(Clone)] +pub struct AssetCommodity { + cost_basis: Decimal, + quantity: Decimal, + name: String, +} + +impl AssetCommodity { + pub fn cost_basis_total(&self) -> Decimal { + self.cost_basis * self.quantity + } +} + +#[derive(Clone)] +pub struct AssetAccount { + name: String, + commodities: HashMap>, + running_total: HashMap, + opening: HashMap, + pub(crate) realized_gains: Decimal, +} + +impl AssetAccount { + pub fn new(name: String) -> AssetAccount { + AssetAccount { + name, + commodities: HashMap::new(), + realized_gains: Decimal::new(0, 0), + running_total: HashMap::new(), + opening: HashMap::new(), + } + } + + pub fn unrealized_gains(&self, oracle: &CommoditiesPriceOracle, date: &String) -> Vec<(String, Decimal)> { + let mut totals = vec![]; + for (name, commodities) in self.commodities.iter() { + if name != "$" && name != "StickerSheets" { + let mut unrealized_gain = Decimal::new(0, 0); + + let historical_cost = commodities.iter().fold(Decimal::new(0,0), |accum, x| { + accum + (x.cost_basis * x.quantity) + }); + + let current_cost = commodities.iter().fold(Decimal::new(0,0), |accum, x| { + accum + (oracle.lookup(&x.name, date) * x.quantity) + }); + + unrealized_gain += current_cost - historical_cost; + totals.push((name.clone(), unrealized_gain)) + } + } + totals + } + + pub fn total_nominal(&self, oracle: &CommoditiesPriceOracle, date: &String) -> f64 { + let mut total = 0.0; + for (commodity_name, quantity) in self.running_total.iter() { + if commodity_name!= "$" && commodity_name != "StickerSheets" { + total += (oracle.lookup(commodity_name, date) * quantity.abs()).to_f64().unwrap(); + } else if commodity_name == "$" { + total += quantity.abs().to_f64().unwrap() + } + } + total + } + + pub fn opening_nominal(&self, oracle: &CommoditiesPriceOracle, date: &String) -> f64 { + let mut total = 0.0; + for (commodity_name, quantity) in self.opening.iter() { + if commodity_name!= "$" && commodity_name != "StickerSheets" { + total += (oracle.lookup(commodity_name, date) * quantity.abs()).to_f64().unwrap(); + } else if commodity_name == "$" { + total += quantity.abs().to_f64().unwrap() + } + } + total + } + + + pub fn withdraw(&mut self, name: String, cost: Decimal, quantity: Decimal, date: &String) { + let cost_basis = cost; + let asset_commodity = AssetCommodity { + cost_basis, + quantity, + name: name.clone(), + }; + + match self.commodities.contains_key(&name) { + false => { + self.commodities.insert(name.clone(), vec![]); + self.running_total.insert(name.clone(), Decimal::new(0,0)); + if date == "2022-02-11" { + println!("Opening Total: {} {} {}", quantity, name, cost); + self.opening.insert(name.clone(), quantity); + } + } + _ => { + } + } + + *self.running_total.get_mut(&name).unwrap() += quantity; + println!("Running Total: {} {} {} {}", quantity, name, cost, *self.running_total.get_mut(&name).unwrap() ); + + if self.name.starts_with("Assets") == false { + // we don't track cost-basis for liabilities + return; + } + + match self.commodities.get_mut(&name) { + None => { + panic!(); + } + Some(current_commodities) => { + + current_commodities.sort_by(|a,b| { + a.cost_basis.cmp(&b.cost_basis) + }); + current_commodities.reverse(); + + + let mut quantity_needed = quantity.abs(); + let mut base_cost = Decimal::new(0,0); + if name != "$" { + for commodity in current_commodities.iter_mut() { + if commodity.quantity <= quantity_needed { + // use all of this commodity.... + println!("Taking {} {} @@ {}", commodity.quantity, commodity.name, commodity.cost_basis); + base_cost += (commodity.quantity * commodity.cost_basis); + quantity_needed -= commodity.quantity; + commodity.quantity = Decimal::new(0, 0); + } else { + // only use what we need from this...and then we are done... + println!("Taking {} {} @@ {}", quantity_needed, commodity.name, commodity.cost_basis); + base_cost += (quantity_needed * commodity.cost_basis); + commodity.quantity -= quantity_needed; + break; + } + if quantity_needed.is_zero() { + break; + } + } + if (base_cost - (cost * quantity.abs())).is_zero() == false { + println!("Realized Gains: {} {} {} {} {} {}", self.name, (cost * quantity.abs()) - base_cost, base_cost, cost * quantity.abs(), date, cost); + } + self.realized_gains += (cost * quantity.abs()) - base_cost; + } else { + current_commodities.push(AssetCommodity{ + cost_basis: Decimal::new(1,0), + quantity: quantity, + name + }) + } + + } + } + } + + pub fn deposit(&mut self, name: String, cost: Decimal, quantity: Decimal, date: &String) { + let cost_basis = cost; + let asset_commodity = AssetCommodity { + cost_basis, + quantity, + name: name.clone(), + }; + + match self.commodities.get_mut(&name) { + None => { + self.commodities.insert(name.clone(), vec![asset_commodity]); + self.running_total.insert(name.clone(), Decimal::new(0,0)); + if date == "2022-02-11" { + println!("Opening: {} {} {}", quantity, name, cost); + self.opening.insert(name.clone(), quantity); + } + } + Some(account) => account.push(asset_commodity), + } + + + *self.running_total.get_mut(&name).unwrap() += quantity; + println!("Running Total: {} {} {} {}", quantity, name, cost, *self.running_total.get_mut(&name).unwrap() ); + } +} diff --git a/src/books.rs b/src/books.rs new file mode 100644 index 0000000..36313d8 --- /dev/null +++ b/src/books.rs @@ -0,0 +1,249 @@ +use crate::{AssetAccount, CommoditiesPriceOracle, ExternalAccount}; +use ledger_parser::{LedgerItem, PostingAmount}; +use rust_decimal::Decimal; +use std::collections::{HashMap, HashSet}; +use std::fs::read_to_string; +use std::str::FromStr; +use chrono::NaiveDate; +use rust_decimal::prelude::ToPrimitive; + +pub type TransactionReference = usize; + +pub struct Books { + pub(crate) asset_accounts: HashMap, + pub(crate) external_accounts: HashMap, + pub(crate) commodities_oracle: CommoditiesPriceOracle, + pub general_ledger: Vec<(TransactionReference, String, Vec<(String, String, Decimal)>)>, +} + +impl Books { + pub fn new() -> Books { + Books { + asset_accounts: HashMap::new(), + external_accounts: HashMap::new(), + commodities_oracle: CommoditiesPriceOracle::new(), + general_ledger: vec![], + } + } + + pub fn load_ledger(&mut self, ledger_file: &str, to_date: &String) { + let ast = ledger_parser::parse(&read_to_string(ledger_file).unwrap()).unwrap(); + for (id, li) in ast.items.iter().enumerate() { + match li { + LedgerItem::EmptyLine => {} + LedgerItem::LineComment(_) => {} + LedgerItem::Transaction(tx) => { + let mut final_line = None; + let mut total = Decimal::new(0, 0); + let mut commodities = HashSet::new(); + + let mut general_ledger_entry = vec![]; + + if tx.date > NaiveDate::from_str(&to_date).unwrap() { + break; + } + + if tx.postings.len() == 2 { + match (&tx.postings[0].amount, &tx.postings[1].amount) { + (Some(a), Some(b)) => { + if a.amount.commodity != b.amount.commodity { + let a_account = tx.postings[0].account.clone(); + let b_account = tx.postings[1].account.clone(); + if a.amount.quantity.is_sign_negative() { + // Trading a for b + let cost_basis = b.amount.quantity / a.amount.quantity.abs() ; + self.asset_accounts.get_mut(&a_account).unwrap().withdraw(a.amount.commodity.name.clone(), cost_basis, a.amount.quantity, &tx.date.to_string()); + self.asset_accounts.get_mut(&b_account).unwrap().deposit(b.amount.commodity.name.clone(), Decimal::new(1,0), b.amount.quantity, &tx.date.to_string()); + } else { + panic!("Trade order should have selling first {} {}",a,b); + } + + general_ledger_entry.push((a_account, a.amount.commodity.name.clone(), a.amount.quantity)); + general_ledger_entry.push((b_account, b.amount.commodity.name.clone(), b.amount.quantity)); + + self.general_ledger.push(( + id as TransactionReference, + tx.date.to_string(), + general_ledger_entry, + )); + continue + } + } + _ => { + + } + } + } + + for posting in &tx.postings { + match &posting.amount { + None => { + final_line = Some(posting.clone()); + } + Some(amount) => { + println!( + "{},{},{},{},{}", + id, tx.date, posting.account, amount.amount.commodity.name, amount.amount.quantity + ); + total += amount.amount.quantity; + commodities.insert(amount.amount.commodity.name.clone()); + + general_ledger_entry.push(( + posting.account.clone(), + amount.amount.commodity.name.clone(), + amount.amount.quantity.clone(), + )); + + self.update_account( + id as TransactionReference, + &posting.account, + &tx.date.to_string(), + &amount.amount.commodity.name.clone(), + amount.amount.quantity, + ); + } + } + } + match final_line { + Some(posting) => { + + if commodities.len() != 1 { + panic!("Trades should be handled separately") + } + + println!("{},{},{},{}", id, tx.date, posting.account, total); + let commodity: Vec<&String> = commodities.iter().collect(); + self.update_account( + id as TransactionReference, + &posting.account, + &tx.date.to_string(), + commodity[0], + -total, + ); + + + general_ledger_entry.push((posting.account.clone(), commodity[0].clone(), -total)); + } + _ => {} + } + + self.general_ledger.push(( + id as TransactionReference, + tx.date.to_string(), + general_ledger_entry, + )) + } + LedgerItem::CommodityPrice(cprice) => self.commodities_oracle.insert( + cprice.commodity_name.clone(), + cprice.datetime.date().to_string(), + cprice.amount.quantity, + ), + LedgerItem::Include(_) => {} + _ => {} + } + } + } + + pub fn update_account(&mut self, tx_ref: TransactionReference, account_name: &String, tx_date: &String, commodity_name: &String, commodity_quantity: Decimal) { + if account_name.starts_with("Assets:") || account_name.starts_with("Liability:"){ + match self.asset_accounts.contains_key(account_name) { + false => { + self.asset_accounts.insert( + account_name.clone(), + AssetAccount::new(account_name.clone()), + ); + } + _ => {} + } + let asset_account = self.asset_accounts.get_mut(account_name).unwrap(); + + if commodity_name != "$" && commodity_name != "StickerSheets" { + let cost_basis = self.commodities_oracle.lookup(commodity_name, tx_date); + + if commodity_quantity.is_sign_positive() { + asset_account.deposit(commodity_name.clone(), cost_basis, commodity_quantity, tx_date); + } else { + asset_account.withdraw( + commodity_name.clone(), + cost_basis, + commodity_quantity, + tx_date, + ); + } + } else { + if commodity_quantity.is_sign_positive() { + asset_account.deposit( + commodity_name.clone(), + Decimal::new(1, 0), + commodity_quantity, + tx_date + ); + } else { + asset_account.withdraw( + commodity_name.clone(), + Decimal::new(1, 0), + commodity_quantity, + tx_date, + ); + } + } + } else { + // This is an external account + match self.external_accounts.contains_key(account_name) { + false => { + let external_account = if account_name.starts_with("Income") { + ExternalAccount::new(account_name.clone(), true) + } else if account_name.starts_with("Expenses") { + ExternalAccount::new(account_name.clone(), false) + } else { + // panic!("Unable to track {}", account_name) + return; + }; + + self.external_accounts + .insert(account_name.clone(), external_account); + } + _ => {} + } + + let external_account = self.external_accounts.get_mut(account_name).unwrap(); + + if !external_account.income_account { + external_account.deposit(tx_ref, commodity_name.clone(), commodity_quantity, tx_date) + } else { + external_account.withdraw(tx_ref, commodity_name.clone(), commodity_quantity, tx_date) + } + } + } + + pub fn summarize_accounts(&self) { + for (account_name, account) in self.external_accounts.iter() { + println!("{}", account_name); + println!("-----"); + account.summarize_transactions(); + println!() + } + } + + pub fn calculate_gains(&self, date: &String) -> f64 { + let mut realized_gains = Decimal::new(0, 0); + let mut unrealized_gains = Decimal::new(0, 0); + for (name, asset_account) in self.asset_accounts.iter() { + println!("{} ", name); + realized_gains += asset_account.realized_gains; + println!(" Realized: {}", asset_account.realized_gains); + for (commodity, total) in asset_account.unrealized_gains(&self.commodities_oracle, &String::from(date)) { + println!(" Unrealized: {} {} ", commodity, total); + unrealized_gains += total; + } + } + + println!("Total Realized Gains: {}", realized_gains); + println!("Total Unrealized Gains: {}", unrealized_gains); + println!( + "Total Realized + Unrealized Gains: {}", + unrealized_gains + realized_gains + ); + return (unrealized_gains+realized_gains).to_f64().unwrap() + } +} diff --git a/src/external_account.rs b/src/external_account.rs new file mode 100644 index 0000000..136adf0 --- /dev/null +++ b/src/external_account.rs @@ -0,0 +1,67 @@ +use crate::books::TransactionReference; +use rust_decimal::Decimal; +use rust_decimal::prelude::ToPrimitive; +use crate::CommoditiesPriceOracle; + +// For tracking income and expenses... +// All Transactions are one way. +#[derive(Clone)] +pub struct ExternalAccount { + name: String, + pub(crate) income_account: bool, + pub(crate) transactions: Vec<(TransactionReference, String, String, Decimal)>, +} + +impl ExternalAccount { + pub fn new(name: String, income_account: bool) -> ExternalAccount { + ExternalAccount { + name, + transactions: vec![], + income_account, + } + } + + // We can only deposit commodities to an Income Account. + pub fn deposit(&mut self, txref: TransactionReference, commodity: String, quantity: Decimal, date: &String) { + if !self.income_account { + self.transactions + .push((txref, date.clone(), commodity, quantity)); + return; + } + panic!( + "cannot deposit to an income account...{} {} {} {}", + txref, commodity, quantity, date + ) + } + + // We can only withdraw commodities from an Income Account. + pub fn withdraw(&mut self, txref: TransactionReference, commodity: String, quantity: Decimal, date: &String) { + if self.income_account { + self.transactions + .push((txref, date.clone(), commodity, quantity)); + return; + } + panic!( + "cannot withdraw from an expense account...{} {} {} {} {}", + self.name, txref, commodity, quantity, date + ) + } + + pub fn total_nominal(&self, oracle: &CommoditiesPriceOracle) -> f64 { + let mut total = 0.0; + for (txid, date, commodity, quantity) in self.transactions.iter() { + if commodity != "$" && commodity != "StickerSheets" { + total += (oracle.lookup(commodity, date) * quantity.abs()).to_f64().unwrap(); + } else if commodity == "$" { + total += quantity.abs().to_f64().unwrap() + } + } + total + } + + pub fn summarize_transactions(&self) { + for (txid, date, commodity, quantity) in self.transactions.iter() { + println!("{}, {}, {}, {}", txid, date, commodity, quantity) + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d5a37c7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,18 @@ +use crate::asset_account::AssetAccount; +use crate::books::Books; +use crate::external_account::ExternalAccount; +use crate::oracle::CommoditiesPriceOracle; +use crate::spreadsheet::export_to_spreadsheet; + +pub mod asset_account; +pub mod books; +pub mod external_account; +pub mod oracle; +pub mod spreadsheet; + +fn main() { + let mut books = Books::new(); + let stop_date = format!("2023-02-10"); + books.load_ledger("2022-archive.dat", &stop_date); + export_to_spreadsheet("test.ods", &books, &format!("2022-02-11"), &stop_date); +} diff --git a/src/oracle.rs b/src/oracle.rs new file mode 100644 index 0000000..4af94fe --- /dev/null +++ b/src/oracle.rs @@ -0,0 +1,43 @@ +use rust_decimal::Decimal; +use std::collections::HashMap; + +pub struct CommoditiesPriceOracle { + commodities: HashMap>, +} + +impl CommoditiesPriceOracle { + pub fn new() -> CommoditiesPriceOracle { + CommoditiesPriceOracle { + commodities: HashMap::new(), + } + } + + pub fn insert(&mut self, commodity: String, date: String, price: Decimal) { + match self.commodities.contains_key(&commodity) { + false => { + self.commodities.insert(commodity.clone(), HashMap::new()); + } + _ => {} + } + + println!("inserting {} {} {}", commodity, date, price); + self.commodities + .get_mut(&commodity) + .unwrap() + .insert(date, price); + } + + pub fn lookup(&self, commodity: &String, date: &String) -> Decimal { + match self.commodities.get(commodity) { + None => { + panic!("no price history data for commodity: {}", commodity) + } + Some(price_history) => match price_history.get(date) { + None => { + panic!("no date history for {} on {}", commodity, date) + } + Some(price) => price.clone(), + }, + } + } +} diff --git a/src/spreadsheet.rs b/src/spreadsheet.rs new file mode 100644 index 0000000..18b9474 --- /dev/null +++ b/src/spreadsheet.rs @@ -0,0 +1,283 @@ +use std::cmp::Ordering; +use std::str::FromStr; +use chrono::{NaiveDate, NaiveTime}; +use rust_decimal::prelude::{ToPrimitive, Zero}; +use spreadsheet_ods::{CellStyle, format, Length, Sheet, Value, WorkBook}; +use crate::{AssetAccount, Books, ExternalAccount}; +use icu_locid::locale; +use rust_decimal::Decimal; +use spreadsheet_ods::style::units::TextAlign; +use spreadsheet_ods::Value::Currency; + + +pub fn export_to_spreadsheet(name: &str, books: &Books, opening_date: &String, close_date: &String) { + let mut wb =WorkBook::new(locale!("en_CA")); + + let mut header_style = CellStyle::new_empty(); + header_style.set_font_bold(); + header_style.set_text_align(TextAlign::Center); + let header_style_ref = wb.add_cellstyle(header_style); + + let currency_format = format::create_currency_prefix("cad", locale!("en_CA"), format!("$")); + let currency_style = wb.add_currency_format(currency_format); + + let mut general_text_style = CellStyle::new_empty(); + general_text_style.set_text_align(TextAlign::Right); + let general_text_style = wb.add_cellstyle(general_text_style); + + let mut currency_pretty_print = CellStyle::new_empty(); + currency_pretty_print.set_text_align(TextAlign::Right); + currency_pretty_print.set_decimal_places(2); + currency_pretty_print.set_value_format(¤cy_style); + let currency_pretty_print_style = wb.add_cellstyle(currency_pretty_print); + + + // Create a Balance Statement + let mut balance_statement = Sheet::new(format!("BalanceSheet")); + balance_statement.set_styled_value(0, 0, Value::Text(format!("Balance Sheet")), &header_style_ref); + balance_statement.set_col_width(0, Length::Pc(25.0)); + balance_statement.set_col_width(1, Length::Pc(25.0)); + balance_statement.set_col_width(2, Length::Pc(25.0)); + balance_statement.set_col_width(3, Length::Pc(25.0)); + balance_statement.set_col_width(4, Length::Pc(25.0)); + // We first need a list of all of our assets and liabilities + let mut asset_accounts : Vec<(String, AssetAccount)> = books.asset_accounts.iter().map(|(a,b)| (a.clone(), b.clone())).collect(); + asset_accounts.sort_by(|a,b| { + if a.0.starts_with("Asset") && b.0.starts_with("Liability") { + return Ordering::Less + } if a.0.starts_with("Liability") && b.0.starts_with("Asset") { + return Ordering::Greater + } else { + return b.1.total_nominal(&books.commodities_oracle, close_date).total_cmp(&a.1.total_nominal(&books.commodities_oracle, close_date)) + } + }); + + // Assets Section + balance_statement.set_styled_value(2, 0, Value::Text(format!("Assets")), &header_style_ref); + balance_statement.set_styled_value(2, 2, Value::Text(format!("Closing Value")), &header_style_ref); + balance_statement.set_styled_value(2, 3, Value::Text(format!("Opening Value")), &header_style_ref); + let mut row_index = 3; + let start = row_index; + for (account_name, account) in asset_accounts.iter() { + if account_name.starts_with("Asset") { + let total_nominal = account.total_nominal(&books.commodities_oracle, close_date); + let total_opening = account.opening_nominal(&books.commodities_oracle, opening_date); + balance_statement.set_styled_value(row_index, 1, Value::Text(account_name.clone().replace("Assets:", "")), &general_text_style); + balance_statement.set_styled_value(row_index, 2, Value::Currency(total_nominal, format!("CAD")), ¤cy_pretty_print_style); + balance_statement.set_styled_value(row_index, 3, Value::Currency(total_opening, format!("CAD")), ¤cy_pretty_print_style); + + row_index+=1; + } + } + balance_statement.set_styled_value(row_index, 0, format!("Total"), &header_style_ref); + balance_statement.set_formula(row_index, 2, format!("SUM(C{}:C{})", start+1, row_index)); + balance_statement.set_cellstyle(row_index,2, ¤cy_pretty_print_style); + balance_statement.set_formula(row_index, 3, format!("SUM(D{}:D{})", start+1, row_index)); + balance_statement.set_cellstyle(row_index,3, ¤cy_pretty_print_style); + let mut assets_total_row = row_index; + row_index+=2; + + balance_statement.set_styled_value(row_index, 0, Value::Text(format!("Liabilities")), &header_style_ref); + let start = row_index; + for (account_name, account) in asset_accounts.iter() { + if account_name.starts_with("Liability") { + let total_nominal = account.total_nominal(&books.commodities_oracle, close_date); + let total_opening = account.opening_nominal(&books.commodities_oracle, opening_date); + if total_opening.is_zero() && total_nominal.is_zero() { + continue + } + balance_statement.set_styled_value(row_index, 1, Value::Text(account_name.clone().replace("Liability:", "")), &general_text_style); + balance_statement.set_styled_value(row_index, 2, Value::Currency(total_nominal, format!("CAD")), ¤cy_pretty_print_style); + balance_statement.set_styled_value(row_index, 3, Value::Currency(total_opening, format!("CAD")), ¤cy_pretty_print_style); + + row_index+=1; + } + } + balance_statement.set_styled_value(row_index, 0, format!("Total"), &header_style_ref); + balance_statement.set_formula(row_index, 2, format!("SUM(C{}:C{})", start+1, row_index)); + balance_statement.set_cellstyle(row_index,2, ¤cy_pretty_print_style); + balance_statement.set_formula(row_index, 3, format!("SUM(D{}:D{})", start+1, row_index)); + balance_statement.set_cellstyle(row_index,3, ¤cy_pretty_print_style); + let mut liability_total_row = row_index; + row_index+=2; + + + balance_statement.set_styled_value(row_index, 0, format!("Net Assets"), &header_style_ref); + balance_statement.set_formula(row_index, 2, format!("C{} - C{}", assets_total_row+1, liability_total_row+1)); + balance_statement.set_cellstyle(row_index,2, ¤cy_pretty_print_style); + + + balance_statement.set_formula(row_index, 3, format!("D{} - D{}", assets_total_row+1, liability_total_row+1)); + balance_statement.set_cellstyle(row_index,3, ¤cy_pretty_print_style); + let balance_sheet_opening_assets_row = row_index; + + let balance_sheet_total_net_assets_row = row_index; + + // Create an Income Statement + let mut income_statement = Sheet::new(format!("IncomeStatement")); + income_statement.set_styled_value(0, 0, Value::Text(format!("Income Statement")), &header_style_ref); + income_statement.set_col_width(0, Length::Pc(25.0)); + income_statement.set_col_width(1, Length::Pc(25.0)); + + // We first need a list of all of our incomes and expenses, ordered by value... + let mut external_accounts : Vec<(String, ExternalAccount)> = books.external_accounts.iter().map(|(a,b)| (a.clone(), b.clone())).collect(); + external_accounts.sort_by(|a,b| { + if a.0.starts_with("Income") && b.0.starts_with("Expense") { + return Ordering::Less + } if a.0.starts_with("Expense") && b.0.starts_with("Income") { + return Ordering::Greater + } else { + return b.1.total_nominal(&books.commodities_oracle).total_cmp(&a.1.total_nominal(&books.commodities_oracle)) + } + }); + + // Revenue Section + income_statement.set_styled_value(2, 0, Value::Text(format!("Revenue")), &header_style_ref); + let mut row_index = 3; + let start = row_index; + for (account_name, account) in external_accounts.iter() { + if account_name.starts_with("Income") { + income_statement.set_styled_value(row_index, 0, Value::Text(account_name.clone().replace("Income:", "")), &general_text_style); + income_statement.set_styled_value(row_index, 1, Value::Currency(account.total_nominal(&books.commodities_oracle), format!("CAD")), ¤cy_pretty_print_style); + row_index+=1; + } + } + income_statement.set_styled_value(row_index, 0, format!("Total"), &header_style_ref); + income_statement.set_formula(row_index, 1, format!("SUM(B{}:B{})", start+1, row_index)); + income_statement.set_cellstyle(row_index,1, ¤cy_pretty_print_style); + let mut revenue_total_row = row_index; + row_index+=2; + + // Now to Expenses... + income_statement.set_styled_value(row_index, 0, Value::Text(format!("Expenses")), &general_text_style); + row_index+=1; + let start = row_index; + for (account_name, account) in external_accounts.iter() { + if account_name.starts_with("Expense") { + income_statement.set_styled_value(row_index, 0, Value::Text(account_name.clone().replace("Expenses:", "")), &general_text_style); + income_statement.set_styled_value(row_index, 1, Value::Currency(account.total_nominal(&books.commodities_oracle), format!("CAD")), ¤cy_pretty_print_style); + row_index+=1; + } + } + income_statement.set_styled_value(row_index, 0, format!("Total"), &header_style_ref); + income_statement.set_formula(row_index, 1, format!("SUM(B{}:B{})", start+1, row_index)); + income_statement.set_cellstyle(row_index,1, ¤cy_pretty_print_style); + let mut expense_total_row = row_index; + + row_index+=2; + income_statement.set_styled_value(row_index, 0, format!("Net Income"), &header_style_ref); + income_statement.set_formula(row_index, 1, format!("B{} - B{}", revenue_total_row+1, expense_total_row+1)); + income_statement.set_cellstyle(row_index,1, ¤cy_pretty_print_style); + + + let balance_sheet_summary_row = balance_sheet_total_net_assets_row + 2; + balance_statement.set_styled_value(balance_sheet_summary_row,0, format!("Income statement balance for the year"), &header_style_ref); + balance_statement.set_formula(balance_sheet_summary_row,1, format!("IncomeStatement.B{}", row_index+1)); + + balance_statement.set_styled_value(balance_sheet_summary_row+1,0, format!("Opening Assets + Income"), &header_style_ref); + balance_statement.set_formula(balance_sheet_summary_row+1,1, format!("B{}+D{}", balance_sheet_summary_row+1,balance_sheet_opening_assets_row+1)); + + let exchange_gains = books.calculate_gains(close_date); + balance_statement.set_styled_value(balance_sheet_summary_row+2,0, format!("Realized Gains/Losses"), &header_style_ref); + balance_statement.set_styled_value(balance_sheet_summary_row+2,1, Currency(exchange_gains, format!("CAD")), ¤cy_pretty_print_style); + + balance_statement.set_styled_value(balance_sheet_summary_row+3,0, format!("Balance"), &header_style_ref); + balance_statement.set_formula(balance_sheet_summary_row+3,1, format!("B{}+B{}", balance_sheet_summary_row+2,balance_sheet_summary_row+3)); + + + wb.push_sheet(balance_statement); + wb.push_sheet(income_statement); + + // Create a General Ledger + let mut general_ledger = Sheet::new(format!("GeneralLedger")); + general_ledger.set_styled_value(0, 0, Value::Text(format!("General Ledger")), &header_style_ref); + + let start_row = 1; + general_ledger.set_styled_value(start_row, 0, Value::Text(String::from("Ref")), &header_style_ref); + general_ledger.set_col_width(0, Length::Pc(15.0)); + + general_ledger.set_styled_value(start_row, 1, Value::Text(String::from("Date")), &header_style_ref); + general_ledger.set_col_width(1, Length::Pc(15.0)); + + general_ledger.set_styled_value(start_row, 2, Value::Text(String::from("Account")), &header_style_ref); + general_ledger.set_col_width(2, Length::Pc(30.0)); + + general_ledger.set_styled_value(start_row, 3, Value::Text(String::from("Recorded CAD Value")), &header_style_ref); + general_ledger.set_col_width(3, Length::Pc(15.0)); + + general_ledger.set_styled_value(start_row, 4, Value::Text(String::from("Recorded Commodity Value")), &header_style_ref); + general_ledger.set_col_width(4, Length::Pc(15.0)); + + general_ledger.set_styled_value(start_row, 5, Value::Text(String::from("Commodity")), &header_style_ref); + general_ledger.set_col_width(5, Length::Pc(15.0)); + general_ledger.set_header_rows(start_row,1); + + let mut row_index = 2; + // Create a sheet for each tracked account + for (txid, date, postings) in books.general_ledger.iter() { + general_ledger.set_styled_value(row_index, 0, Value::Number(*txid as f64), &header_style_ref); + let recorded_date = NaiveDate::from_str(date).unwrap(); + general_ledger.set_styled_value(row_index, 1, Value::DateTime(recorded_date.and_time(NaiveTime::default())), &header_style_ref); + row_index +=1; + for(account, commodity, quantity) in postings.iter() { + let nominal_value = if commodity != "$" && commodity != "StickerSheets" { + books.commodities_oracle.lookup(commodity, date) * quantity + } else { + Decimal::new(1,0 ) * quantity + }; + general_ledger.set_styled_value(row_index, 2, Value::Text(account.clone()), &general_text_style); + general_ledger.set_styled_value(row_index, 3, Value::Currency(nominal_value.to_f64().unwrap(), String::from("CAD")), ¤cy_pretty_print_style); + general_ledger.set_styled_value(row_index, 4, Value::Currency(quantity.to_f64().unwrap(), commodity.clone()),&general_text_style); + general_ledger.set_styled_value(row_index, 5, Value::Text(commodity.clone().replace("$", "CAD")), &general_text_style); + row_index +=1; + } + } + wb.push_sheet(general_ledger); + + + // Create a sheet for each tracked account + for (external_account, account) in external_accounts.iter() { + let mut sheet = Sheet::new(format!("{}", external_account.to_lowercase().replace(":", ""))); + + sheet.set_styled_value(0, 0, Value::Text(format!("Account: {}", external_account)), &header_style_ref); + + + let start_row = 1; + sheet.set_styled_value(start_row, 0, Value::Text(String::from("Ref")), &header_style_ref); + sheet.set_col_width(0, Length::Pc(15.0)); + + sheet.set_styled_value(start_row, 1, Value::Text(String::from("Date")), &header_style_ref); + sheet.set_col_width(1, Length::Pc(15.0)); + + sheet.set_styled_value(start_row, 2, Value::Text(String::from("Recorded CAD Value")), &header_style_ref); + sheet.set_col_width(2, Length::Pc(15.0)); + + sheet.set_styled_value(start_row, 3, Value::Text(String::from("Recorded Commodity Value")), &header_style_ref); + sheet.set_col_width(3, Length::Pc(15.0)); + + sheet.set_styled_value(start_row, 4, Value::Text(String::from("Commodity")), &header_style_ref); + sheet.set_col_width(4, Length::Pc(15.0)); + + + sheet.set_header_rows(start_row,1); + + let start_row = 2; + for(row, (txref, date, commodity, quantity)) in account.transactions.iter().enumerate() { + let index = start_row+(row as u32); + let recorded_date = NaiveDate::from_str(date).unwrap(); + let nominal_value = if commodity != "$" && commodity != "StickerSheets" { + books.commodities_oracle.lookup(commodity, date) * quantity + } else { + Decimal::new(1,0 ) * quantity + }; + sheet.set_styled_value(index, 0, Value::Number(*txref as f64), &header_style_ref); + sheet.set_styled_value(index, 1, Value::DateTime(recorded_date.and_time(NaiveTime::default())), &general_text_style); + sheet.set_styled_value(index, 2, Value::Currency(nominal_value.to_f64().unwrap(), String::from("CAD")), ¤cy_pretty_print_style); + sheet.set_styled_value(index, 3, Value::Currency(quantity.to_f64().unwrap(), commodity.clone()), &general_text_style); + sheet.set_styled_value(index, 4, Value::Text(commodity.clone().replace("$", "CAD")), &general_text_style); + } + wb.push_sheet(sheet); + } + + spreadsheet_ods::write_ods(&mut wb, name); +} \ No newline at end of file