From 35ba4ec92fab052e72e08c86dad0a3d0b2fe2335 Mon Sep 17 00:00:00 2001 From: Geert Janssens Date: Sun, 11 Dec 2016 19:16:33 +0100 Subject: [PATCH] Extend csv importer to be able to import multi-currency and stock transactions --- src/import-export/csv-imp/gnc-trans-props.cpp | 122 ++++++++++++++++-- src/import-export/csv-imp/gnc-trans-props.hpp | 5 + 2 files changed, 113 insertions(+), 14 deletions(-) diff --git a/src/import-export/csv-imp/gnc-trans-props.cpp b/src/import-export/csv-imp/gnc-trans-props.cpp index 8eb6895fe2..3f354b059b 100644 --- a/src/import-export/csv-imp/gnc-trans-props.cpp +++ b/src/import-export/csv-imp/gnc-trans-props.cpp @@ -26,6 +26,7 @@ extern "C" { #include #endif +#include #include #include "engine-helpers.h" @@ -33,6 +34,7 @@ extern "C" { #include "gnc-ui-util.h" #include "Account.h" #include "Transaction.h" +#include "gnc-pricedb.h" } @@ -51,12 +53,14 @@ std::map gnc_csv_col_type_strs = { { GncTransPropType::NUM, N_("Num") }, { GncTransPropType::DESCRIPTION, N_("Description") }, { GncTransPropType::NOTES, N_("Notes") }, + { GncTransPropType::COMMODITY, N_("Transaction Commodity") }, { GncTransPropType::VOID_REASON, N_("Void Reason") }, { GncTransPropType::ACTION, N_("Action") }, { GncTransPropType::ACCOUNT, N_("Account") }, { GncTransPropType::DEPOSIT, N_("Deposit") }, { GncTransPropType::WITHDRAWAL, N_("Withdrawal") }, { GncTransPropType::BALANCE, N_("Balance") }, + { GncTransPropType::PRICE, N_("Price") }, { GncTransPropType::MEMO, N_("Memo") }, { GncTransPropType::REC_STATE, N_("Reconciled") }, { GncTransPropType::REC_DATE, N_("Reconcile Date") }, @@ -229,10 +233,57 @@ static char parse_reconciled (const std::string& reconcile) throw std::invalid_argument ("String can't be parsed into a valid reconcile state."); } +static gnc_commodity* parse_commodity (const std::string& comm_str) +{ + if (comm_str.empty()) + return nullptr; + + auto table = gnc_commodity_table_get_table (gnc_get_current_book()); + gnc_commodity* comm = nullptr; + + /* First try commodity as a unique name. */ + if (comm_str.find("::")) + comm = gnc_commodity_table_lookup_unique (table, comm_str.c_str()); + + /* Then try mnemonic in the currency namespace */ + if (!comm) + comm = gnc_commodity_table_lookup (table, + GNC_COMMODITY_NS_CURRENCY, comm_str.c_str()); + + if (!comm) + { + /* If that fails try mnemonic in all other namespaces */ + auto namespaces = gnc_commodity_table_get_namespaces(table); + for (auto ns = namespaces; ns; ns = ns->next) + { + gchar* ns_str = (gchar*)ns->data; + if (g_utf8_collate(ns_str, GNC_COMMODITY_NS_CURRENCY) == 0) + continue; + + comm = gnc_commodity_table_lookup (table, + ns_str, comm_str.c_str()); + if (comm) + break; + } + } + + if (!comm) + throw std::invalid_argument ("String can't be parsed into a valid commodity."); + else + return comm; +} + void GncPreTrans::set_property (GncTransPropType prop_type, const std::string& value) { switch (prop_type) { + case GncTransPropType::UNIQUE_ID: + if (!value.empty()) + m_differ = value; + else + m_differ = boost::none; + break; + case GncTransPropType::DATE: m_date = parse_date (value, m_date_format); // Throws if parsing fails break; @@ -258,11 +309,8 @@ void GncPreTrans::set_property (GncTransPropType prop_type, const std::string& v m_notes = boost::none; break; - case GncTransPropType::UNIQUE_ID: - if (!value.empty()) - m_differ = value; - else - m_differ = boost::none; + case GncTransPropType::COMMODITY: + m_commodity = parse_commodity (value); // Throws if parsing fails break; case GncTransPropType::VOID_REASON: @@ -306,8 +354,7 @@ Transaction* GncPreTrans::create_trans (QofBook* book, gnc_commodity* currency) auto trans = xaccMallocTransaction (book); xaccTransBeginEdit (trans); - xaccTransSetCurrency (trans, currency); - + xaccTransSetCurrency (trans, m_commodity ? *m_commodity : currency); xaccTransSetDatePostedSecsNormalized (trans, *m_date); if (m_num) @@ -319,6 +366,7 @@ Transaction* GncPreTrans::create_trans (QofBook* book, gnc_commodity* currency) if (m_notes) xaccTransSetNotes (trans, m_notes->c_str()); + created = true; return trans; } @@ -328,10 +376,13 @@ bool GncPreTrans::is_part_of (std::shared_ptr parent) if (!parent) return false; - return (!m_date || m_date == parent->m_date) && + return (!m_differ || m_differ == parent->m_differ) && + (!m_date || m_date == parent->m_date) && + (!m_num || m_num == parent->m_num) && (!m_desc || m_desc == parent->m_desc) && (!m_notes || m_notes == parent->m_notes) && - (!m_differ || m_differ == parent->m_differ); + (!m_commodity || m_commodity == parent->m_commodity) && + (!m_void_reason || m_void_reason == parent->m_void_reason); } void GncPreSplit::set_property (GncTransPropType prop_type, const std::string& value) @@ -393,6 +444,10 @@ void GncPreSplit::set_property (GncTransPropType prop_type, const std::string& v m_withdrawal = parse_amount (value, m_currency_format); // Will throw if parsing fails break; + case GncTransPropType::PRICE: + m_price = parse_amount (value, m_currency_format); // Will throw if parsing fails + break; + case GncTransPropType::REC_STATE: m_rec_state = parse_reconciled (value); // Throws if parsing fails break; @@ -452,14 +507,48 @@ static void trans_add_split (Transaction* trans, Account* account, gnc_numeric a const boost::optional& action, const boost::optional& memo, const boost::optional& rec_state, - const boost::optional rec_date) + const boost::optional rec_date, + const boost::optional price) { - auto book = xaccTransGetBook (trans); + QofBook* book = xaccTransGetBook (trans); auto split = xaccMallocSplit (book); xaccSplitSetAccount (split, account); xaccSplitSetParent (split, trans); xaccSplitSetAmount (split, amount); - xaccSplitSetValue (split, amount); + auto trans_curr = xaccTransGetCurrency(trans); + auto acct_comm = xaccAccountGetCommodity(account); + if (gnc_commodity_equiv(trans_curr, acct_comm)) + xaccSplitSetValue (split, amount); + else if (price) + { + gnc_numeric value = gnc_numeric_mul (amount, *price, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND); + xaccSplitSetValue (split, value); + } + else + { + auto tts = xaccTransRetDatePostedTS (trans); + /* Import data didn't specify price, let's lookup the nearest in time */ + auto nprice = gnc_pricedb_lookup_nearest_in_time(gnc_pricedb_get_db(book), + acct_comm, trans_curr, tts); + if (!nprice) + { + PWARN("No price found, using a price of 1."); + xaccSplitSetValue (split, amount); + } + else + { + /* Found a usable price. Let's check if the conversion direction is right */ + gnc_numeric rate = {0, 1}; + if (gnc_commodity_equiv(gnc_price_get_currency(nprice), trans_curr)) + rate = gnc_price_get_value(nprice); + else + rate = gnc_numeric_invert(gnc_price_get_value(nprice)); + + gnc_numeric value = gnc_numeric_mul (amount, rate, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND); + xaccSplitSetValue (split, value); + } + } + if (memo) xaccSplitSetMemo (split, memo->c_str()); /* Note, this function assumes the num/action switch is done at a higher level @@ -516,13 +605,18 @@ boost::optional GncPreSplit::create_split (Transaction* trans) GNC_HOW_RND_ROUND_HALF_UP); /* Add a split with the cumulative amount value. */ - trans_add_split (trans, account, amount, m_action, m_memo, m_rec_state, m_rec_date); + trans_add_split (trans, account, amount, m_action, m_memo, m_rec_state, m_rec_date, m_price); if (taccount) + { /* Note: the current importer assumes at most 2 splits. This means the second split amount * will be the negative of the the first split amount. */ - trans_add_split (trans, taccount, gnc_numeric_neg(amount), m_taction, m_tmemo, m_trec_state, m_trec_date); + auto inv_price = m_price; + if (inv_price) + inv_price = gnc_numeric_invert(*inv_price); + trans_add_split (trans, taccount, gnc_numeric_neg(amount), m_taction, m_tmemo, m_trec_state, m_trec_date, inv_price); + } created = true; diff --git a/src/import-export/csv-imp/gnc-trans-props.hpp b/src/import-export/csv-imp/gnc-trans-props.hpp index 169470aad9..99d5f39dbf 100644 --- a/src/import-export/csv-imp/gnc-trans-props.hpp +++ b/src/import-export/csv-imp/gnc-trans-props.hpp @@ -34,6 +34,7 @@ extern "C" { #include "Account.h" #include "Transaction.h" +#include "gnc-commodity.h" } #include @@ -52,6 +53,7 @@ enum class GncTransPropType { NUM, DESCRIPTION, NOTES, + COMMODITY, VOID_REASON, TRANS_PROPS = VOID_REASON, @@ -60,6 +62,7 @@ enum class GncTransPropType { DEPOSIT, WITHDRAWAL, BALANCE, + PRICE, MEMO, REC_STATE, REC_DATE, @@ -127,6 +130,7 @@ private: boost::optional m_num; boost::optional m_desc; boost::optional m_notes; + boost::optional m_commodity; boost::optional m_void_reason; bool created = false; }; @@ -151,6 +155,7 @@ private: boost::optional m_deposit; boost::optional m_withdrawal; boost::optional m_balance; + boost::optional m_price; boost::optional m_memo; boost::optional m_rec_state; boost::optional m_rec_date;