gnucash maint: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Tue Dec 13 16:15:55 EST 2022


Updated	 via  https://github.com/Gnucash/gnucash/commit/73a13473 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/598a6f1b (commit)
	 via  https://github.com/Gnucash/gnucash/commit/f6cc6eda (commit)
	 via  https://github.com/Gnucash/gnucash/commit/5e7bc1d6 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/86284838 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/6a9e1cb2 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/17eb739d (commit)
	 via  https://github.com/Gnucash/gnucash/commit/0bc2d692 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/5519a9d7 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/e116a4f1 (commit)
	from  https://github.com/Gnucash/gnucash/commit/b4bab92d (commit)



commit 73a134730f682e54e15f4813df6946abdb45db4a
Author: John Ralls <jralls at ceridwen.us>
Date:   Tue Dec 13 13:13:13 2022 -0800

    Bug 798680 - Not able to match a reverse transaction of a...
    
    previously matched transaction.
    
    Cloned transactions and copied splits shouldn't keep the online-id
    slot.

diff --git a/libgnucash/engine/Split.c b/libgnucash/engine/Split.c
index d8fc1b784..af9588982 100644
--- a/libgnucash/engine/Split.c
+++ b/libgnucash/engine/Split.c
@@ -624,6 +624,8 @@ void
 xaccSplitCopyKvp (const Split *from, Split *to)
 {
     qof_instance_copy_kvp (QOF_INSTANCE (to), QOF_INSTANCE (from));
+    /* But not the online-id */
+    qof_instance_set (QOF_INSTANCE (to), "online-id", NULL, NULL);
 }
 
 /*################## Added for Reg2 #################*/
diff --git a/libgnucash/engine/Transaction.c b/libgnucash/engine/Transaction.c
index ce4761b6b..d8255f14c 100644
--- a/libgnucash/engine/Transaction.c
+++ b/libgnucash/engine/Transaction.c
@@ -693,6 +693,9 @@ xaccTransClone (const Transaction *from)
     xaccTransBeginEdit (to);
     qof_instance_copy_kvp (QOF_INSTANCE (to), QOF_INSTANCE (from));
 
+    /* But not the online-id! */
+    qof_instance_set (QOF_INSTANCE (to), "online-id", NULL, NULL);
+
     for (GList* lfrom = from->splits, *lto = to->splits; lfrom && lto;
          lfrom = g_list_next (lfrom), lto = g_list_next (lto))
         xaccSplitCopyKvp (lfrom->data, lto->data);

commit 598a6f1b6b565e9eb335c43c278202d498ff1fc3
Author: John Ralls <jralls at ceridwen.us>
Date:   Tue Dec 13 12:03:03 2022 -0800

    [ofx import] Propose parent account unless last account is right commodity.

diff --git a/gnucash/import-export/ofx/gnc-ofx-import.c b/gnucash/import-export/ofx/gnc-ofx-import.c
index 542ed4f6c..516b5e209 100644
--- a/gnucash/import-export/ofx/gnc-ofx-import.c
+++ b/gnucash/import-export/ofx/gnc-ofx-import.c
@@ -649,13 +649,19 @@ static Account*
 choose_investment_account_helper(OfxTransactionData *data, ofx_info *info,
                                  InvestmentAcctData *inv_data)
 {
-    Account *investment_account =
+    Account *investment_account, *parent_account;
+
+    if (xaccAccountGetCommodity(info->last_investment_account) == inv_data->commodity)
+        parent_account = info->last_investment_account;
+    else
+        parent_account = ofx_parent_account;
+
+    investment_account =
         gnc_import_select_account(GTK_WIDGET(info->parent),
                                   inv_data->online_id,
                                   TRUE, inv_data->acct_text,
                                   inv_data->commodity, ACCT_TYPE_STOCK,
-                                  info->last_investment_account,
-                                  &inv_data->choosing);
+                                  parent_account, &inv_data->choosing);
     if (investment_account &&
         xaccAccountGetCommodity(investment_account) == inv_data->commodity)
     {

commit f6cc6eda4c11a204c9abe4e7e6d681fb019fe00e
Author: John Ralls <jralls at ceridwen.us>
Date:   Tue Dec 13 11:53:42 2022 -0800

    [ofx import] Make automatic account creation work.
    
    When Preferences>Import>Automatic commodity creation is enabled.
    
    Note that this behavior isn't indicated in the tooltip and is a bit
    clunky: the user has to cancel the manual account selection for it
    to fire.

diff --git a/gnucash/import-export/ofx/gnc-ofx-import.c b/gnucash/import-export/ofx/gnc-ofx-import.c
index 453213450..542ed4f6c 100644
--- a/gnucash/import-export/ofx/gnc-ofx-import.c
+++ b/gnucash/import-export/ofx/gnc-ofx-import.c
@@ -649,47 +649,34 @@ static Account*
 choose_investment_account_helper(OfxTransactionData *data, ofx_info *info,
                                  InvestmentAcctData *inv_data)
 {
-    Account *investment_account = NULL;
-    // No account with correct commodity automatically found.
-
-    // But are we in auto-create mode and already know a parent?
-    if (auto_create_commodity && ofx_parent_account)
-    {
-        // Yes, so use that as parent when auto-creating the new account below.
-        investment_account = ofx_parent_account;
-    }
-    else
-    {
-        // Let the user choose an account
-        investment_account =
-            gnc_import_select_account(GTK_WIDGET(info->parent), data->unique_id,
-                                      TRUE, inv_data->acct_text,
-                                      inv_data->commodity, ACCT_TYPE_STOCK,
-                                      info->last_investment_account,
-                                      &inv_data->choosing);
-        if (investment_account)
-            info->last_investment_account = investment_account;
-    }
-    // Does the chosen account have the right commodity?
+    Account *investment_account =
+        gnc_import_select_account(GTK_WIDGET(info->parent),
+                                  inv_data->online_id,
+                                  TRUE, inv_data->acct_text,
+                                  inv_data->commodity, ACCT_TYPE_STOCK,
+                                  info->last_investment_account,
+                                  &inv_data->choosing);
     if (investment_account &&
         xaccAccountGetCommodity(investment_account) == inv_data->commodity)
-        return investment_account;
+    {
+        Account *parent_account = gnc_account_get_parent(investment_account);
+
+        if (!ofx_parent_account && parent_account &&
+            !gnc_account_is_root(parent_account) &&
+            xaccAccountTypesCompatible(xaccAccountGetType(parent_account),
+                                       ACCT_TYPE_STOCK))
+            ofx_parent_account = parent_account;
 
-    if (!investment_account) // Nothing more to try
-        return NULL;
+        info->last_investment_account = investment_account;
+        return investment_account;
+    }
 
-    if (auto_create_commodity &&
-        xaccAccountTypesCompatible(xaccAccountGetType(investment_account),
-                                   ACCT_TYPE_STOCK))
+    /* That didn't work out. Create a subaccount if we can. */
+    if (auto_create_commodity && ofx_parent_account)
     {
-        /* We have an account but the commodity doesn't match. Since
-         * auto_create_commodity is on and the account has a
-         * compatible type we can just create a sub-account with the
-         * right commodity.
-         */
         investment_account =
             create_investment_subaccount(GTK_WINDOW(info->parent),
-                                                    investment_account,
+                                                    ofx_parent_account,
                                                     inv_data);
     }
     else
@@ -711,8 +698,8 @@ choose_investment_account(OfxTransactionData *data, ofx_info *info,
 {
     Account* investment_account = NULL;
     gnc_numeric gnc_amount, gnc_units;
-    InvestmentAcctData inv_data = {commodity, NULL, NULL, FALSE};
-    char *acct_online_id;
+    InvestmentAcctData inv_data = {commodity, NULL, NULL, TRUE};
+
      // As we now have the commodity, select the account with that commodity.
 
      /* Translators: This string is a default account name. It MUST
@@ -721,21 +708,9 @@ choose_investment_account(OfxTransactionData *data, ofx_info *info,
      inv_data.acct_text = g_strdup_printf(
           _("Stock account for security \"%s\""),
           sanitize_string (data->security_data_ptr->secname));
-     inv_data.online_id = data->unique_id;
-
-     acct_online_id = g_strdup_printf("%s%s", data->account_id, data->unique_id);
-     investment_account =
-         gnc_import_select_account(GTK_WIDGET(info->parent), acct_online_id,
-                                   TRUE, inv_data.acct_text,
-                                   inv_data.commodity, ACCT_TYPE_STOCK,
-                                   info->last_investment_account, NULL);
-     if (investment_account)
-          info->last_investment_account = investment_account;
-
-     // but use it only if that's really the right commodity
-     if (investment_account
-         && xaccAccountGetCommodity(investment_account) != inv_data.commodity)
-          investment_account = NULL;
+
+     inv_data.online_id =
+         g_strdup_printf("%s%s", data->account_id, data->unique_id);
 
      // Loop until we either have an account, or the user pressed Cancel
      while (!investment_account && inv_data.choosing)
@@ -746,7 +721,7 @@ choose_investment_account(OfxTransactionData *data, ofx_info *info,
           PERR("No investment account found for text: %s\n", inv_data.acct_text);
      }
      g_free (inv_data.acct_text);
-     g_free (acct_online_id);
+     g_free (inv_data.online_id);
 
      return investment_account;
 }

commit 5e7bc1d6c214d1faeab271215721e4348f7a9ef5
Author: John Ralls <jralls at ceridwen.us>
Date:   Mon Dec 12 18:02:42 2022 -0800

    [ofx import] Fix the parentage of the select account windows.
    
    The account list window is gone when these are used, use the importer parent.

diff --git a/gnucash/import-export/ofx/gnc-ofx-import.c b/gnucash/import-export/ofx/gnc-ofx-import.c
index c58463009..453213450 100644
--- a/gnucash/import-export/ofx/gnc-ofx-import.c
+++ b/gnucash/import-export/ofx/gnc-ofx-import.c
@@ -627,12 +627,12 @@ create_investment_subaccount(GtkWindow *parent, Account* parent_acct,
 }
 
 static gboolean
-continue_account_selection(GNCImportMainMatcher* matcher, Account* account,
+continue_account_selection(GtkWidget* parent, Account* account,
                            gnc_commodity* commodity)
 {
     gboolean keep_going =
         gnc_verify_dialog(
-            GTK_WINDOW (gnc_gen_trans_list_widget(matcher)), TRUE,
+            GTK_WINDOW (parent), TRUE,
             "The chosen account \"%s\" does not have the correct "
             "currency/security \"%s\" (it has \"%s\" instead). "
             "This account cannot be used. "
@@ -661,11 +661,12 @@ choose_investment_account_helper(OfxTransactionData *data, ofx_info *info,
     else
     {
         // Let the user choose an account
-        investment_account = gnc_import_select_account(
-            gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
-            data->unique_id, TRUE, inv_data->acct_text,
-            inv_data->commodity, ACCT_TYPE_STOCK,
-            info->last_investment_account, &inv_data->choosing);
+        investment_account =
+            gnc_import_select_account(GTK_WIDGET(info->parent), data->unique_id,
+                                      TRUE, inv_data->acct_text,
+                                      inv_data->commodity, ACCT_TYPE_STOCK,
+                                      info->last_investment_account,
+                                      &inv_data->choosing);
         if (investment_account)
             info->last_investment_account = investment_account;
     }
@@ -696,9 +697,8 @@ choose_investment_account_helper(OfxTransactionData *data, ofx_info *info,
         // No account with matching commodity. Ask the user
         // whether to continue or abort.
         inv_data->choosing =
-            continue_account_selection(info->gnc_ofx_importer_gui,
-                                       investment_account,
-                                       inv_data->commodity);
+            continue_account_selection(GTK_WIDGET(info->parent),
+                                       investment_account, inv_data->commodity);
         investment_account = NULL;
     }
 
@@ -724,10 +724,11 @@ choose_investment_account(OfxTransactionData *data, ofx_info *info,
      inv_data.online_id = data->unique_id;
 
      acct_online_id = g_strdup_printf("%s%s", data->account_id, data->unique_id);
-     investment_account = gnc_import_select_account(
-          gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
-          acct_online_id, TRUE, inv_data.acct_text, inv_data.commodity,
-          ACCT_TYPE_STOCK, info->last_investment_account, NULL);
+     investment_account =
+         gnc_import_select_account(GTK_WIDGET(info->parent), acct_online_id,
+                                   TRUE, inv_data.acct_text,
+                                   inv_data.commodity, ACCT_TYPE_STOCK,
+                                   info->last_investment_account, NULL);
      if (investment_account)
           info->last_investment_account = investment_account;
 
@@ -770,10 +771,11 @@ choose_income_account(Account* investment_account, Transaction *transaction,
         income_account_text = g_strdup_printf(
             _("Income account for security \"%s\""),
             sanitize_string (data->security_data_ptr->secname));
-        income_account = gnc_import_select_account(
-            gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
-            NULL, TRUE, income_account_text, currency,
-            ACCT_TYPE_INCOME, info->last_income_account, NULL);
+        income_account =
+            gnc_import_select_account(GTK_WIDGET(info->parent), NULL, TRUE,
+                                      income_account_text, currency,
+                                      ACCT_TYPE_INCOME,
+                                      info->last_income_account, NULL);
 
         if (income_account != NULL)
         {
@@ -934,7 +936,7 @@ int ofx_proc_transaction_cb(OfxTransactionData data, void *user_data)
     Transaction *transaction;
     ofx_info* info = (ofx_info*) user_data;
 
-    g_assert(info->gnc_ofx_importer_gui);
+    g_assert(info->parent);
 
     if (!data.amount_valid)
     {
@@ -950,7 +952,7 @@ int ofx_proc_transaction_cb(OfxTransactionData data, void *user_data)
 
     gnc_utf8_strip_invalid (data.account_id);
 
-    import_account = gnc_import_select_account(gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
+    import_account = gnc_import_select_account(GTK_WIDGET(info->parent),
                                         data.account_id,
 					0, NULL, NULL, ACCT_TYPE_NONE,
 					info->last_import_account, NULL);

commit 862848380c788a23a26500562722a57a40c5a717
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Dec 10 17:18:38 2022 -0800

    Bug 798681 - Previously imported investment income transactions may not be filtered.
    
    Resequence process_investment_transaction so that the first split is
    the primary asset account split (cash unless it's a reinvest) followed
    by the security asset account if it's not income and finally the income
    split for income or reinvest transactions.
    
    Note that there's also a sign change on the income splits for
    income and reinvest transactions: testing showed the signs to be
    backwards.

diff --git a/gnucash/import-export/ofx/gnc-ofx-import.c b/gnucash/import-export/ofx/gnc-ofx-import.c
index 688531266..c58463009 100644
--- a/gnucash/import-export/ofx/gnc-ofx-import.c
+++ b/gnucash/import-export/ofx/gnc-ofx-import.c
@@ -804,8 +804,9 @@ add_investment_split(Transaction* transaction, Account* account,
     xaccTransAppendSplit(transaction, split);
     xaccAccountInsertSplit(account, split);
 
-    gnc_amount = gnc_ofx_numeric_from_double_txn(ofx_get_investment_amount(data),
-                                                  transaction);
+    gnc_amount =
+        gnc_ofx_numeric_from_double_txn(ofx_get_investment_amount(data),
+                                        transaction);
     gnc_units = gnc_ofx_numeric_from_double (data->units, commodity);
     xaccSplitSetAmount(split, gnc_units);
     xaccSplitSetValue(split, gnc_amount);
@@ -854,77 +855,75 @@ add_currency_split(Transaction *transaction, Account* account,
         gnc_import_set_split_online_id (split, sanitize_string (data->fi_id));
 }
 
+/* ******** Process an investment transaction **********/
+/* Note that the ACCT_TYPE_STOCK account type
+   should be replaced with something derived from
+   data->invtranstype*/
+
 static void
 process_investment_transaction(Transaction *transaction, Account *import_account,
                                OfxTransactionData *data, ofx_info *info)
 {
-    Split *split;
-    gnc_numeric gnc_amount, gnc_units;
-    gnc_commodity *investment_commodity = NULL;
     Account *investment_account = NULL;
     Account *income_account = NULL;
-    QofBook *book = qof_instance_get_book(QOF_INSTANCE(transaction));
+    gnc_commodity *investment_commodity;
+    double amount = data->amount;
+
+    g_return_if_fail(data->invtransactiontype_valid);
 
     gnc_utf8_strip_invalid (data->unique_id);
 
-    /********* Process an investment transaction **********/
-    /* Note that the ACCT_TYPE_STOCK account type
-       should be replaced with something derived from
-       data->invtranstype*/
 
-    // We have an investment transaction. First select the correct commodity.
+    // Set the cash split unless it's a reinvestment, which doesn't have one.
+    if (data->invtransactiontype != OFX_REINVEST)
+    {
+        DEBUG("Adding investment cash split.");
+        add_currency_split(transaction, import_account,
+                           -ofx_get_investment_amount(data), data);
+    }
+
     investment_commodity = gnc_import_select_commodity(data->unique_id,
-                                                       FALSE,
-                                                       NULL,
-                                                       NULL);
-    if (investment_commodity != NULL)
-         investment_account = choose_investment_account(data, info,
-                                                        investment_commodity);
-    else
+                                                       FALSE, NULL, NULL);
+    if (!investment_commodity)
     {
         PERR("Commodity not found for the investment transaction");
+        return;
     }
+    investment_account = choose_investment_account(data, info,
+                                                   investment_commodity);
 
-     if (investment_account != NULL &&
-         data->unitprice_valid &&
-         data->units_valid &&
-         ( data->invtransactiontype != OFX_INCOME ) )
-         add_investment_split(transaction, investment_account, data);
-     else
-         PERR("The investment account, units or unitprice was not found for the investment transaction");
-
-    if (data->invtransactiontype_valid && investment_account)
+    if (!investment_account)
     {
-        double amount = data->amount;
-#ifdef HAVE_LIBOFX_VERSION_0_10
-        if (data->currency_ratio_valid && data->currency_ratio != 0)
-            amount *= data->currency_ratio;
-#endif
-        if (data->invtransactiontype == OFX_REINVEST
-            || data->invtransactiontype == OFX_INCOME)
-            income_account = choose_income_account(investment_account,
-                                                   transaction, data, info);
-        if (income_account != NULL &&
-            data->invtransactiontype == OFX_REINVEST)
-        {
-            DEBUG("Adding investment split; Money flows from the income account");
-            add_currency_split(transaction, income_account, -amount, data);
-        }
-        if (income_account != NULL &&
-            data->invtransactiontype == OFX_INCOME)
-        {
-            DEBUG("Adding investment split; Money flows to the income account");
-            add_currency_split(transaction, income_account, amount, data);
-        }
+        PERR("Failed to determine an investment asset account.");
+        return;
     }
 
-    if (data->invtransactiontype_valid
-        && data->invtransactiontype != OFX_REINVEST)
+    if (data->invtransactiontype != OFX_INCOME)
     {
-        DEBUG("Adding investment split; Money flows from or to the cash account");
-        add_currency_split(transaction, import_account,
-                           -ofx_get_investment_amount(data), data);
+        if (data->unitprice_valid && data->units_valid)
+            add_investment_split(transaction, investment_account, data);
+        else
+            PERR("Unable to add investment split, unit price or units were invalid.");
     }
+
+    if (!(data->invtransactiontype == OFX_REINVEST
+          || data->invtransactiontype == OFX_INCOME))
+        //Done
+        return;
+
+#ifdef HAVE_LIBOFX_VERSION_0_10
+    if (data->currency_ratio_valid && data->currency_ratio != 0)
+        amount *= data->currency_ratio;
+#endif
+    income_account = choose_income_account(investment_account,
+                                           transaction, data, info);
+    g_return_if_fail(income_account);
+
+    DEBUG("Adding investment income split.");
+    if (data->invtransactiontype == OFX_REINVEST)
+        add_currency_split(transaction, income_account, amount, data);
+    else
+        add_currency_split(transaction, income_account, -amount, data);
 }
 
 int ofx_proc_transaction_cb(OfxTransactionData data, void *user_data)

commit 6a9e1cb24976c0a1a1b78f62adda2b3234d8915c
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Dec 10 16:32:25 2022 -0800

    [ofx import] Don't set online-id on income accounts.

diff --git a/gnucash/import-export/ofx/gnc-ofx-import.c b/gnucash/import-export/ofx/gnc-ofx-import.c
index 67537b0e1..688531266 100644
--- a/gnucash/import-export/ofx/gnc-ofx-import.c
+++ b/gnucash/import-export/ofx/gnc-ofx-import.c
@@ -825,7 +825,9 @@ add_investment_split(Transaction* transaction, Account* account,
         xaccSplitSetMemo(split,
                          sanitize_string (data->security_data_ptr->memo));
     }
-    if (data->fi_id_valid)
+    if (data->fi_id_valid &&
+        xaccAccountTypesCompatible(xaccAccountGetType(account),
+                                   ACCT_TYPE_ASSET))
     {
         gnc_import_set_split_online_id(split,
                                        sanitize_string (data->fi_id));

commit 17eb739da3eef1166e7c42adb8400d031db2baf5
Author: John Ralls <jralls at ceridwen.us>
Date:   Thu Dec 8 13:38:16 2022 -0800

    [ofx import] Extract functions from ofx_proc_transaction_cb.
    
    set_transaction_dates, fill_transaction_description,
    fill_transaction_notes, process_bank_transaction,
    process_investment_transaction, choose_investment_account,
    choose_income_account, add_investment_split, add_currency_split,
    create_investment_subaccount, continue_account_selection,
    choose_investment_account_helper, choose_investment_account

diff --git a/gnucash/import-export/ofx/gnc-ofx-import.c b/gnucash/import-export/ofx/gnc-ofx-import.c
index f7781c568..67537b0e1 100644
--- a/gnucash/import-export/ofx/gnc-ofx-import.c
+++ b/gnucash/import-export/ofx/gnc-ofx-import.c
@@ -415,76 +415,29 @@ fix_ofx_bug_39 (time64 t)
     return t;
 }
 
-int ofx_proc_transaction_cb(OfxTransactionData data, void *user_data)
+static void
+set_transaction_dates(Transaction *transaction, OfxTransactionData *data)
 {
-    char dest_string[255];
-    time64 current_time = gnc_time (NULL);
-    Account *account;
-    Account *investment_account = NULL;
-    Account *income_account = NULL;
-    gchar *investment_account_text, *investment_account_onlineid;
-    gnc_commodity *currency = NULL;
-    gnc_commodity *investment_commodity = NULL;
-    gnc_numeric gnc_amount, gnc_units;
-    QofBook *book;
-    Transaction *transaction;
-    Split *split;
-    gchar *notes, *tmp;
-    ofx_info* info = (ofx_info*) user_data;
-    GtkWindow *parent = GTK_WINDOW (info->parent);
-
-    g_assert(info->gnc_ofx_importer_gui);
-
-    if (!data.account_id_valid)
-    {
-        PERR("account ID for this transaction is unavailable!");
-        return 0;
-    }
-    else
-	gnc_utf8_strip_invalid (data.account_id);
-
-    account = gnc_import_select_account(gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
-                                        data.account_id,
-					0, NULL, NULL, ACCT_TYPE_NONE,
-					info->last_import_account, NULL);
-    if (account == NULL)
-    {
-        PERR("Unable to find account for id %s", data.account_id);
-        return 0;
-    }
-    info->last_import_account = account;
-    /***** Validate the input strings to ensure utf8 *****/
-    if (data.name_valid)
-        gnc_utf8_strip_invalid(data.name);
-    if (data.memo_valid)
-        gnc_utf8_strip_invalid(data.memo);
-    if (data.check_number_valid)
-        gnc_utf8_strip_invalid(data.check_number);
-    if (data.reference_number_valid)
-        gnc_utf8_strip_invalid(data.reference_number);
-
-    /***** Create the transaction and setup transaction data *******/
-    book = gnc_account_get_book(account);
-    transaction = xaccMallocTransaction(book);
-    xaccTransBeginEdit(transaction);
-
-    /* Note: Unfortunately libofx <= 0.9.5 will not report a missing
+     /* Note: Unfortunately libofx <= 0.9.5 will not report a missing
      * date field as an invalid one. Instead, it will report it as
      * valid and return a completely bogus date. Starting with
      * libofx-0.9.6 (not yet released as of 2012-09-09), it will still
      * be reported as valid but at least the date integer itself is
      * just plain zero. */
-    if (data.date_posted_valid && (data.date_posted != 0))
+
+    time64 current_time = gnc_time (NULL);
+
+    if (data->date_posted_valid && (data->date_posted != 0))
     {
         /* The hopeful case: We have a posted_date */
-        data.date_posted = fix_ofx_bug_39 (data.date_posted);
-        xaccTransSetDatePostedSecsNormalized(transaction, data.date_posted);
+        data->date_posted = fix_ofx_bug_39 (data->date_posted);
+        xaccTransSetDatePostedSecsNormalized(transaction, data->date_posted);
     }
-    else if (data.date_initiated_valid && (data.date_initiated != 0))
+    else if (data->date_initiated_valid && (data->date_initiated != 0))
     {
         /* No posted date? Maybe we have an initiated_date */
-        data.date_initiated = fix_ofx_bug_39 (data.date_initiated);
-        xaccTransSetDatePostedSecsNormalized(transaction, data.date_initiated);
+        data->date_initiated = fix_ofx_bug_39 (data->date_initiated);
+        xaccTransSetDatePostedSecsNormalized(transaction, data->date_initiated);
     }
     else
     {
@@ -493,462 +446,587 @@ int ofx_proc_transaction_cb(OfxTransactionData data, void *user_data)
     }
 
     xaccTransSetDateEnteredSecs(transaction, current_time);
+}
 
+static void
+fill_transaction_description(Transaction *transaction, OfxTransactionData *data)
+{
     /* Put transaction name in Description, or memo if name unavailable */
-    if (data.name_valid)
+    if (data->name_valid)
     {
-        xaccTransSetDescription(transaction, data.name);
+        xaccTransSetDescription(transaction, data->name);
     }
-    else if (data.memo_valid)
+    else if (data->memo_valid)
     {
-        xaccTransSetDescription(transaction, data.memo);
+        xaccTransSetDescription(transaction, data->memo);
     }
+}
 
+static void
+fill_transaction_notes(Transaction *transaction, OfxTransactionData *data)
+{
     /* Put everything else in the Notes field */
-    notes = g_strdup_printf("OFX ext. info: ");
+    char *notes = g_strdup_printf("OFX ext. info: ");
 
-    if (data.transactiontype_valid)
+    if (data->transactiontype_valid)
     {
-        tmp = notes;
+        char *tmp = notes;
         notes = g_strdup_printf("%s%s%s", tmp, "|Trans type:",
-                                gnc_ofx_ttype_to_string(data.transactiontype));
+                                gnc_ofx_ttype_to_string(data->transactiontype));
         g_free(tmp);
     }
 
-    if (data.invtransactiontype_valid)
+    if (data->invtransactiontype_valid)
     {
-        tmp = notes;
+        char *tmp = notes;
         notes = g_strdup_printf("%s%s%s", tmp, "|Investment Trans type:",
-                                gnc_ofx_invttype_to_str(data.invtransactiontype));
+                                gnc_ofx_invttype_to_str(data->invtransactiontype));
         g_free(tmp);
     }
-    if (data.memo_valid && data.name_valid) /* Copy only if memo wasn't put in Description */
+    if (data->memo_valid && data->name_valid) /* Copy only if memo wasn't put in Description */
     {
-        tmp = notes;
-        notes = g_strdup_printf("%s%s%s", tmp, "|Memo:", data.memo);
+        char *tmp = notes;
+        notes = g_strdup_printf("%s%s%s", tmp, "|Memo:", data->memo);
         g_free(tmp);
     }
-    if (data.date_funds_available_valid)
+    if (data->date_funds_available_valid)
     {
-        time64 time = data.date_funds_available;
+        char dest_string[MAX_DATE_LENGTH];
+        time64 time = data->date_funds_available;
+        char *tmp = notes;
+
         gnc_time64_to_iso8601_buff (time, dest_string);
-        tmp = notes;
         notes = g_strdup_printf("%s%s%s", tmp,
 				"|Date funds available:", dest_string);
         g_free(tmp);
     }
-    if (data.server_transaction_id_valid)
+    if (data->server_transaction_id_valid)
     {
-        tmp = notes;
+        char *tmp = notes;
         notes = g_strdup_printf("%s%s%s", tmp,
 				"|Server trans ID (conf. number):",
-				sanitize_string (data.server_transaction_id));
+				sanitize_string (data->server_transaction_id));
         g_free(tmp);
     }
-    if (data.standard_industrial_code_valid)
+    if (data->standard_industrial_code_valid)
     {
-        tmp = notes;
+        char *tmp = notes;
         notes = g_strdup_printf("%s%s%ld", tmp,
 				"|Standard Industrial Code:",
-                                data.standard_industrial_code);
+                                data->standard_industrial_code);
         g_free(tmp);
 
     }
-    if (data.payee_id_valid)
+    if (data->payee_id_valid)
     {
-        tmp = notes;
+        char *tmp = notes;
         notes = g_strdup_printf("%s%s%s", tmp, "|Payee ID:",
-				sanitize_string (data.payee_id));
+				sanitize_string (data->payee_id));
         g_free(tmp);
     }
-
     //PERR("WRITEME: GnuCash ofx_proc_transaction():Add PAYEE and ADDRESS here once supported by libofx! Notes=%s\n", notes);
 
     /* Ideally, gnucash should process the corrected transactions */
-    if (data.fi_id_corrected_valid)
+    if (data->fi_id_corrected_valid)
     {
+        char *tmp = notes;
         PERR("WRITEME: GnuCash ofx_proc_transaction(): WARNING: This transaction corrected a previous transaction, but we created a new one instead!\n");
-        tmp = notes;
         notes = g_strdup_printf("%s%s%s%s", tmp,
 				"|This corrects transaction #",
-				sanitize_string (data.fi_id_corrected),
+				sanitize_string (data->fi_id_corrected),
 				"but GnuCash didn't process the correction!");
         g_free(tmp);
     }
     xaccTransSetNotes(transaction, notes);
     g_free(notes);
 
-    if (data.account_ptr && data.account_ptr->currency_valid)
+}
+
+static void
+process_bank_transaction(Transaction *transaction, Account *import_account,
+                         OfxTransactionData *data, ofx_info *info)
+{
+    Split *split;
+    gnc_numeric gnc_amount;
+    QofBook *book = qof_instance_get_book(QOF_INSTANCE(transaction));
+    double amount = data->amount;
+#ifdef HAVE_LIBOFX_VERSION_0_10
+    if (data->currency_ratio_valid && data->currency_ratio != 0)
+        amount *= data->currency_ratio;
+#endif
+    /***** Process a normal transaction ******/
+    DEBUG("Adding split; Ordinary banking transaction, money flows from or into the source account");
+    split = xaccMallocSplit(book);
+    xaccTransAppendSplit(transaction, split);
+    xaccAccountInsertSplit(import_account, split);
+    gnc_amount = gnc_ofx_numeric_from_double_txn(amount, transaction);
+    xaccSplitSetBaseValue(split, gnc_amount, xaccTransGetCurrency(transaction));
+
+    /* set tran-num and/or split-action per book option */
+    if (data->check_number_valid)
     {
-        DEBUG("Currency from libofx: %s", data.account_ptr->currency);
-        currency = gnc_commodity_table_lookup( gnc_get_current_commodities (),
-                                               GNC_COMMODITY_NS_CURRENCY,
-                                               data.account_ptr->currency);
+        /* SQL will correctly interpret the string "null", but
+         * the transaction num field is declared to be
+         * non-null so substitute the empty string.
+         */
+        const char *num_value =
+            strcasecmp (data->check_number, "null") == 0 ? "" :
+            data->check_number;
+        gnc_set_num_action(transaction, split, num_value, NULL);
+    }
+    else if (data->reference_number_valid)
+    {
+        const char *num_value =
+            strcasecmp (data->reference_number, "null") == 0 ? "" :
+            data->check_number;
+        gnc_set_num_action(transaction, split, num_value, NULL);
+    }
+    /* Also put the ofx transaction's memo in the
+     * split's memo field */
+    if (data->memo_valid)
+    {
+        xaccSplitSetMemo(split, data->memo);
+    }
+    if (data->fi_id_valid)
+    {
+        gnc_import_set_split_online_id(split,
+                                       sanitize_string (data->fi_id));
+    }
+}
+
+typedef struct
+{
+    gnc_commodity *commodity;
+    char *online_id;
+    char *acct_text;
+    gboolean choosing;
+} InvestmentAcctData;
+
+static Account*
+create_investment_subaccount(GtkWindow *parent, Account* parent_acct,
+                             InvestmentAcctData *inv_data)
+{
+
+    Account *investment_account =
+        gnc_ofx_new_account(parent,
+                            inv_data->acct_text,
+                            inv_data->commodity,
+                            parent_acct,
+                            ACCT_TYPE_STOCK);
+    if (investment_account)
+    {
+        gnc_import_set_acc_online_id(investment_account, inv_data->online_id);
+        inv_data->choosing = FALSE;
+        ofx_parent_account = parent_acct;
     }
     else
     {
-        DEBUG("Currency from libofx unavailable, defaulting to account's default");
-        currency = xaccAccountGetCommodity(account);
+        ofx_parent_account = NULL;
     }
+    return investment_account;
+}
 
-    xaccTransSetCurrency(transaction, currency);
-    if (data.amount_valid)
+static gboolean
+continue_account_selection(GNCImportMainMatcher* matcher, Account* account,
+                           gnc_commodity* commodity)
+{
+    gboolean keep_going =
+        gnc_verify_dialog(
+            GTK_WINDOW (gnc_gen_trans_list_widget(matcher)), TRUE,
+            "The chosen account \"%s\" does not have the correct "
+            "currency/security \"%s\" (it has \"%s\" instead). "
+            "This account cannot be used. "
+            "Do you want to choose again?",
+            xaccAccountGetName(account),
+            gnc_commodity_get_fullname(commodity),
+            gnc_commodity_get_fullname(xaccAccountGetCommodity(account)));
+    // We must also delete the online_id that was set in gnc_import_select_account()
+    gnc_import_set_acc_online_id(account, "");
+    return keep_going;
+}
+
+static Account*
+choose_investment_account_helper(OfxTransactionData *data, ofx_info *info,
+                                 InvestmentAcctData *inv_data)
+{
+    Account *investment_account = NULL;
+    // No account with correct commodity automatically found.
+
+    // But are we in auto-create mode and already know a parent?
+    if (auto_create_commodity && ofx_parent_account)
     {
-        if (!data.invtransactiontype_valid
-#ifdef HAVE_LIBOFX_VERSION_0_10
-            || data.invtransactiontype == OFX_INVBANKTRAN)
-#else
-             )
-#endif
+        // Yes, so use that as parent when auto-creating the new account below.
+        investment_account = ofx_parent_account;
+    }
+    else
+    {
+        // Let the user choose an account
+        investment_account = gnc_import_select_account(
+            gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
+            data->unique_id, TRUE, inv_data->acct_text,
+            inv_data->commodity, ACCT_TYPE_STOCK,
+            info->last_investment_account, &inv_data->choosing);
+        if (investment_account)
+            info->last_investment_account = investment_account;
+    }
+    // Does the chosen account have the right commodity?
+    if (investment_account &&
+        xaccAccountGetCommodity(investment_account) == inv_data->commodity)
+        return investment_account;
+
+    if (!investment_account) // Nothing more to try
+        return NULL;
+
+    if (auto_create_commodity &&
+        xaccAccountTypesCompatible(xaccAccountGetType(investment_account),
+                                   ACCT_TYPE_STOCK))
+    {
+        /* We have an account but the commodity doesn't match. Since
+         * auto_create_commodity is on and the account has a
+         * compatible type we can just create a sub-account with the
+         * right commodity.
+         */
+        investment_account =
+            create_investment_subaccount(GTK_WINDOW(info->parent),
+                                                    investment_account,
+                                                    inv_data);
+    }
+    else
+    {
+        // No account with matching commodity. Ask the user
+        // whether to continue or abort.
+        inv_data->choosing =
+            continue_account_selection(info->gnc_ofx_importer_gui,
+                                       investment_account,
+                                       inv_data->commodity);
+        investment_account = NULL;
+    }
+
+    return investment_account;
+}
+
+static Account*
+choose_investment_account(OfxTransactionData *data, ofx_info *info,
+                          gnc_commodity *commodity)
+{
+    Account* investment_account = NULL;
+    gnc_numeric gnc_amount, gnc_units;
+    InvestmentAcctData inv_data = {commodity, NULL, NULL, FALSE};
+    char *acct_online_id;
+     // As we now have the commodity, select the account with that commodity.
+
+     /* Translators: This string is a default account name. It MUST
+      * NOT contain the character ':' anywhere in it or in any
+      * translations.  */
+     inv_data.acct_text = g_strdup_printf(
+          _("Stock account for security \"%s\""),
+          sanitize_string (data->security_data_ptr->secname));
+     inv_data.online_id = data->unique_id;
+
+     acct_online_id = g_strdup_printf("%s%s", data->account_id, data->unique_id);
+     investment_account = gnc_import_select_account(
+          gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
+          acct_online_id, TRUE, inv_data.acct_text, inv_data.commodity,
+          ACCT_TYPE_STOCK, info->last_investment_account, NULL);
+     if (investment_account)
+          info->last_investment_account = investment_account;
+
+     // but use it only if that's really the right commodity
+     if (investment_account
+         && xaccAccountGetCommodity(investment_account) != inv_data.commodity)
+          investment_account = NULL;
+
+     // Loop until we either have an account, or the user pressed Cancel
+     while (!investment_account && inv_data.choosing)
+         investment_account = choose_investment_account_helper(data, info,
+                                                               &inv_data);
+     if (!investment_account)
+     {
+          PERR("No investment account found for text: %s\n", inv_data.acct_text);
+     }
+     g_free (inv_data.acct_text);
+     g_free (acct_online_id);
+
+     return investment_account;
+}
+
+static Account*
+choose_income_account(Account* investment_account, Transaction *transaction,
+                      OfxTransactionData *data, ofx_info *info)
+{
+    Account *income_account = NULL;
+    DEBUG("Now let's find an account for the destination split");
+    income_account =
+        get_associated_income_account(investment_account);
+
+    if (income_account == NULL)
+    {
+        char *income_account_text;
+        gnc_commodity *currency = xaccTransGetCurrency(transaction);
+        DEBUG("Couldn't find an associated income account");
+        /* Translators: This string is a default account
+         * name. It MUST NOT contain the character ':' anywhere
+         * in it or in any translations.  */
+        income_account_text = g_strdup_printf(
+            _("Income account for security \"%s\""),
+            sanitize_string (data->security_data_ptr->secname));
+        income_account = gnc_import_select_account(
+            gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
+            NULL, TRUE, income_account_text, currency,
+            ACCT_TYPE_INCOME, info->last_income_account, NULL);
+
+        if (income_account != NULL)
         {
-            double amount = data.amount;
-#ifdef HAVE_LIBOFX_VERSION_0_10
-            if (data.currency_ratio_valid && data.currency_ratio != 0)
-                amount *= data.currency_ratio;
-#endif
-            /***** Process a normal transaction ******/
-            DEBUG("Adding split; Ordinary banking transaction, money flows from or into the source account");
-            split = xaccMallocSplit(book);
-            xaccTransAppendSplit(transaction, split);
-            xaccAccountInsertSplit(account, split);
-            gnc_amount = gnc_ofx_numeric_from_double_txn(amount, transaction);
-            xaccSplitSetBaseValue(split, gnc_amount, xaccTransGetCurrency(transaction));
-
-            /* set tran-num and/or split-action per book option */
-            if (data.check_number_valid)
-            {
-                /* SQL will correctly interpret the string "null", but
-                 * the transaction num field is declared to be
-                 * non-null so substitute the empty string.
-                 */
-                const char *num_value =
-                    strcasecmp (data.check_number, "null") == 0 ? "" :
-                    data.check_number;
-                gnc_set_num_action(transaction, split, num_value, NULL);
-            }
-            else if (data.reference_number_valid)
-            {
-                const char *num_value =
-                    strcasecmp (data.reference_number, "null") == 0 ? "" :
-                    data.check_number;
-                gnc_set_num_action(transaction, split, num_value, NULL);
-            }
-            /* Also put the ofx transaction's memo in the
-             * split's memo field */
-            if (data.memo_valid)
-            {
-                xaccSplitSetMemo(split, data.memo);
-            }
-            if (data.fi_id_valid)
-            {
-                gnc_import_set_split_online_id(split,
-					       sanitize_string (data.fi_id));
-            }
+            info->last_income_account = income_account;
+            set_associated_income_account(investment_account,
+                                          income_account);
+            DEBUG("KVP written");
         }
+    }
+    else
+    {
+        DEBUG("Found at least one associated income account");
+    }
 
-        else if (data.unique_id_valid
-                 && data.security_data_valid
-                 && data.security_data_ptr != NULL
-                 && data.security_data_ptr->secname_valid)
-        {
-            gboolean choosing_account = TRUE;
-	    gnc_utf8_strip_invalid (data.unique_id);
-            /********* Process an investment transaction **********/
-            /* Note that the ACCT_TYPE_STOCK account type
-               should be replaced with something derived from
-               data.invtranstype*/
-
-            // We have an investment transaction. First select the correct commodity.
-            investment_commodity = gnc_import_select_commodity(data.unique_id,
-                                   FALSE,
-                                   NULL,
-                                   NULL);
-            if (investment_commodity != NULL)
-            {
-                // As we now have the commodity, select the account with that commodity.
-
-                /* Translators: This string is a default account name. It MUST
-                 * NOT contain the character ':' anywhere in it or in any
-                 * translations.  */
-                investment_account_text = g_strdup_printf(
-                                         _("Stock account for security \"%s\""),
-                             sanitize_string (data.security_data_ptr->secname));
-
-                investment_account_onlineid = g_strdup_printf( "%s%s",
-							       data.account_id,
-							       data.unique_id);
-                investment_account = gnc_import_select_account(
-                                     gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
-                                     investment_account_onlineid,
-                                     1,
-                                     investment_account_text,
-                                     investment_commodity,
-                                     ACCT_TYPE_STOCK,
-                                     info->last_investment_account,
-                                     NULL);
-                if (investment_account)
-                    info->last_investment_account = investment_account;
-
-                // but use it only if that's really the right commodity
-                if (investment_account
-                        && xaccAccountGetCommodity(investment_account) != investment_commodity)
-                    investment_account = NULL;
-
-                // Loop until we either have an account, or the user pressed Cancel
-                while (!investment_account && choosing_account)
-                {
-                    // No account with correct commodity automatically found.
-
-                    // But are we in auto-create mode and already know a parent?
-                    if (auto_create_commodity && ofx_parent_account)
-                    {
-                        // Yes, so use that as parent when auto-creating the new account below.
-                        investment_account = ofx_parent_account;
-                    }
-                    else
-                    {
-                        // Let the user choose an account
-                        investment_account = gnc_import_select_account(
-                                                 gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
-                                                 data.unique_id,
-                                                 TRUE,
-                                                 investment_account_text,
-                                                 investment_commodity,
-                                                 ACCT_TYPE_STOCK,
-                                                 info->last_investment_account,
-                                                 &choosing_account);
-                        if (investment_account)
-                            info->last_investment_account = investment_account;
-                    }
-                    // Does the chosen account have the right commodity?
-                    if (investment_account && xaccAccountGetCommodity(investment_account) != investment_commodity)
-                    {
-                        if (auto_create_commodity
-                                && xaccAccountTypesCompatible(xaccAccountGetType(investment_account),
-                                                              ACCT_TYPE_STOCK))
-                        {
-                            // The user chose an account, but it does
-                            // not have the right commodity. Also,
-                            // auto-creation is on. Hence, we create a
-                            // new child account of the selected one,
-                            // and this one will have the right
-                            // commodity.
-                            Account *parent_account = investment_account;
-                            investment_account =
-                                gnc_ofx_new_account(parent,
-                                                    investment_account_text,
-                                                    investment_commodity,
-                                                    parent_account,
-                                                    ACCT_TYPE_STOCK);
-                            if (investment_account)
-                            {
-                                gnc_import_set_acc_online_id(investment_account, data.unique_id);
-                                choosing_account = FALSE;
-                                ofx_parent_account = parent_account;
-                            }
-                            else
-                            {
-                                ofx_parent_account = NULL;
-                            }
-                        }
-                        else
-                        {
-                            // No account with matching commodity. Ask the user
-                            // whether to continue or abort.
-                            choosing_account =
-                                gnc_verify_dialog(
-                                    GTK_WINDOW (gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui)), TRUE,
-                                    "The chosen account \"%s\" does not have the correct "
-                                    "currency/security \"%s\" (it has \"%s\" instead). "
-                                    "This account cannot be used. "
-                                    "Do you want to choose again?",
-                                    xaccAccountGetName(investment_account),
-                                    gnc_commodity_get_fullname(investment_commodity),
-                                    gnc_commodity_get_fullname(xaccAccountGetCommodity(investment_account)));
-                            // We must also delete the online_id that was set in gnc_import_select_account()
-                            gnc_import_set_acc_online_id(investment_account, "");
-                            investment_account = NULL;
-                        }
-                    }
-                }
-                if (!investment_account)
-                {
-                    PERR("No investment account found for text: %s\n", investment_account_text);
-                }
-                g_free (investment_account_text);
-                g_free (investment_account_onlineid);
-                investment_account_text = NULL;
-
-                if (investment_account != NULL &&
-                        data.unitprice_valid &&
-                        data.units_valid &&
-                        ( data.invtransactiontype != OFX_INCOME ) )
-                {
-                    DEBUG("Adding investment split; Money flows from or into the stock account");
-                    split = xaccMallocSplit(book);
-                    xaccTransAppendSplit(transaction, split);
-                    xaccAccountInsertSplit(investment_account, split);
-
-                    gnc_amount = gnc_ofx_numeric_from_double_txn (ofx_get_investment_amount(&data),
-                                 transaction);
-                    gnc_units = gnc_ofx_numeric_from_double (data.units, investment_commodity);
-                    xaccSplitSetAmount(split, gnc_units);
-                    xaccSplitSetValue(split, gnc_amount);
-
-                    /* set tran-num and/or split-action per book option */
-                    if (data.check_number_valid)
-                    {
-                        gnc_set_num_action(transaction, split, data.check_number, NULL);
-                    }
-                    else if (data.reference_number_valid)
-                    {
-                        gnc_set_num_action(transaction, split,
-                                           data.reference_number, NULL);
-                    }
-                    if (data.security_data_ptr->memo_valid)
-                    {
-                        xaccSplitSetMemo(split,
-                                sanitize_string (data.security_data_ptr->memo));
-                    }
-                    if (data.fi_id_valid)
-                    {
-                        gnc_import_set_split_online_id(split,
-                                                 sanitize_string (data.fi_id));
-                    }
-                }
-                else
-                {
-                    if (investment_account)
-                        PERR("The investment account, units or unitprice was not found for the investment transaction");
-                }
-            }
-            else
-            {
-                PERR("Commodity not found for the investment transaction");
-            }
+    return income_account;
+}
 
-            if (data.invtransactiontype_valid && investment_account)
-            {
-                double amount = data.amount;
-#ifdef HAVE_LIBOFX_VERSION_0_10
-                if (data.currency_ratio_valid && data.currency_ratio != 0)
-                    amount *= data.currency_ratio;
-#endif
-                if (data.invtransactiontype == OFX_REINVEST
-                        || data.invtransactiontype == OFX_INCOME)
-                {
-                    DEBUG("Now let's find an account for the destination split");
-                    income_account =
-                        get_associated_income_account(investment_account);
-
-                    if (income_account == NULL)
-                    {
-                        DEBUG("Couldn't find an associated income account");
-                        /* Translators: This string is a default account
-                         * name. It MUST NOT contain the character ':' anywhere
-                         * in it or in any translations.  */
-                        investment_account_text = g_strdup_printf(
-                                                      _("Income account for security \"%s\""),
-                                                      sanitize_string (data.security_data_ptr->secname));
-                        income_account = gnc_import_select_account(
-                                             gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
-                                             NULL,
-                                             1,
-                                             investment_account_text,
-                                             currency,
-                                             ACCT_TYPE_INCOME,
-                                             info->last_income_account,
-                                             NULL);
-                        if (income_account != NULL)
-                        {
-                            info->last_income_account = income_account;
-                            set_associated_income_account(investment_account,
-                                                          income_account);
-                            DEBUG("KVP written");
-                        }
-
-                    }
-                    else
-                    {
-                        DEBUG("Found at least one associated income account");
-                    }
-                }
-                if (income_account != NULL &&
-                        data.invtransactiontype == OFX_REINVEST)
-                {
-                    DEBUG("Adding investment split; Money flows from the income account");
-                    split = xaccMallocSplit(book);
-                    xaccTransAppendSplit(transaction, split);
-                    xaccAccountInsertSplit(income_account, split);
-                    gnc_amount = gnc_ofx_numeric_from_double_txn(amount,
-                                                                 transaction);
-                    xaccSplitSetBaseValue(split, gnc_amount, xaccTransGetCurrency(transaction));
-
-                    // Set split memo from ofx transaction name or memo
-                    gnc_ofx_set_split_memo(&data, split);
-                    if (data.fi_id_valid)
-                        gnc_import_set_split_online_id (split, sanitize_string (data.fi_id));
-                }
-                if (income_account != NULL &&
-                        data.invtransactiontype == OFX_INCOME)
-                {
-                    DEBUG("Adding investment split; Money flows from the income account");
-                    split = xaccMallocSplit(book);
-                    xaccTransAppendSplit(transaction, split);
-                    xaccAccountInsertSplit(income_account, split);
-                    /*OFX_INCOME amounts come in as positive numbers*/
-                    gnc_amount = gnc_ofx_numeric_from_double_txn (-amount,
-                                                                  transaction);
-
-                    xaccSplitSetBaseValue(split, gnc_amount, xaccTransGetCurrency(transaction));
-
-                    // Set split memo from ofx transaction name or memo
-                    gnc_ofx_set_split_memo(&data, split);
-                    if (data.fi_id_valid)
-                        gnc_import_set_split_online_id (split, sanitize_string (data.fi_id));
-                }
-            }
+static void
+add_investment_split(Transaction* transaction, Account* account,
+                             OfxTransactionData *data)
+{
+    Split *split;
+    QofBook *book = gnc_account_get_book(account);
+    gnc_numeric gnc_amount, gnc_units;
+    gnc_commodity *commodity = xaccAccountGetCommodity(account);
+    DEBUG("Adding investment split; Money flows from or into the stock account");
+    split = xaccMallocSplit(book);
+    xaccTransAppendSplit(transaction, split);
+    xaccAccountInsertSplit(account, split);
+
+    gnc_amount = gnc_ofx_numeric_from_double_txn(ofx_get_investment_amount(data),
+                                                  transaction);
+    gnc_units = gnc_ofx_numeric_from_double (data->units, commodity);
+    xaccSplitSetAmount(split, gnc_units);
+    xaccSplitSetValue(split, gnc_amount);
+
+    /* set tran-num and/or split-action per book option */
+    if (data->check_number_valid)
+    {
+        gnc_set_num_action(transaction, split, data->check_number, NULL);
+    }
+    else if (data->reference_number_valid)
+    {
+        gnc_set_num_action(transaction, split,
+                           data->reference_number, NULL);
+    }
+    if (data->security_data_ptr->memo_valid)
+    {
+        xaccSplitSetMemo(split,
+                         sanitize_string (data->security_data_ptr->memo));
+    }
+    if (data->fi_id_valid)
+    {
+        gnc_import_set_split_online_id(split,
+                                       sanitize_string (data->fi_id));
+    }
+}
 
-            if (data.invtransactiontype_valid
-                    && data.invtransactiontype != OFX_REINVEST)
-            {
-                DEBUG("Adding investment split; Money flows from or to the cash account");
-                split = xaccMallocSplit(book);
-                xaccTransAppendSplit(transaction, split);
-                xaccAccountInsertSplit(account, split);
-
-                gnc_amount = gnc_ofx_numeric_from_double_txn(
-                                 -ofx_get_investment_amount(&data), transaction);
-                xaccSplitSetBaseValue(split, gnc_amount,
-                                      xaccTransGetCurrency(transaction));
-
-                // Set split memo from ofx transaction name or memo
-                gnc_ofx_set_split_memo(&data, split);
-                if (data.fi_id_valid)
-                    gnc_import_set_split_online_id (split, sanitize_string (data.fi_id));
-            }
-        }
+static void
+add_currency_split(Transaction *transaction, Account* account,
+                 double amount, OfxTransactionData *data)
+{
+    Split *split;
+    QofBook *book = gnc_account_get_book(account);
+    gnc_numeric gnc_amount;
+
+    split = xaccMallocSplit(book);
+    xaccTransAppendSplit(transaction, split);
+    xaccAccountInsertSplit(account, split);
+    gnc_amount = gnc_ofx_numeric_from_double_txn(amount, transaction);
+    xaccSplitSetBaseValue(split, gnc_amount, xaccTransGetCurrency(transaction));
+
+    // Set split memo from ofx transaction name or memo
+    gnc_ofx_set_split_memo(data, split);
+    if (data->fi_id_valid)
+        gnc_import_set_split_online_id (split, sanitize_string (data->fi_id));
+}
+
+static void
+process_investment_transaction(Transaction *transaction, Account *import_account,
+                               OfxTransactionData *data, ofx_info *info)
+{
+    Split *split;
+    gnc_numeric gnc_amount, gnc_units;
+    gnc_commodity *investment_commodity = NULL;
+    Account *investment_account = NULL;
+    Account *income_account = NULL;
+    QofBook *book = qof_instance_get_book(QOF_INSTANCE(transaction));
+
+    gnc_utf8_strip_invalid (data->unique_id);
+
+    /********* Process an investment transaction **********/
+    /* Note that the ACCT_TYPE_STOCK account type
+       should be replaced with something derived from
+       data->invtranstype*/
+
+    // We have an investment transaction. First select the correct commodity.
+    investment_commodity = gnc_import_select_commodity(data->unique_id,
+                                                       FALSE,
+                                                       NULL,
+                                                       NULL);
+    if (investment_commodity != NULL)
+         investment_account = choose_investment_account(data, info,
+                                                        investment_commodity);
+    else
+    {
+        PERR("Commodity not found for the investment transaction");
+    }
+
+     if (investment_account != NULL &&
+         data->unitprice_valid &&
+         data->units_valid &&
+         ( data->invtransactiontype != OFX_INCOME ) )
+         add_investment_split(transaction, investment_account, data);
+     else
+         PERR("The investment account, units or unitprice was not found for the investment transaction");
 
-        /* Send transaction to importer GUI. */
-        if (xaccTransCountSplits(transaction) > 0)
+    if (data->invtransactiontype_valid && investment_account)
+    {
+        double amount = data->amount;
+#ifdef HAVE_LIBOFX_VERSION_0_10
+        if (data->currency_ratio_valid && data->currency_ratio != 0)
+            amount *= data->currency_ratio;
+#endif
+        if (data->invtransactiontype == OFX_REINVEST
+            || data->invtransactiontype == OFX_INCOME)
+            income_account = choose_income_account(investment_account,
+                                                   transaction, data, info);
+        if (income_account != NULL &&
+            data->invtransactiontype == OFX_REINVEST)
         {
-            DEBUG("%d splits sent to the importer gui",
-                  xaccTransCountSplits(transaction));
-            info->trans_list = g_list_prepend (info->trans_list, transaction);
+            DEBUG("Adding investment split; Money flows from the income account");
+            add_currency_split(transaction, income_account, -amount, data);
         }
-        else
+        if (income_account != NULL &&
+            data->invtransactiontype == OFX_INCOME)
         {
-            PERR("No splits in transaction (missing account?), ignoring.");
-            xaccTransDestroy(transaction);
-            xaccTransCommitEdit(transaction);
+            DEBUG("Adding investment split; Money flows to the income account");
+            add_currency_split(transaction, income_account, amount, data);
         }
     }
-    else
+
+    if (data->invtransactiontype_valid
+        && data->invtransactiontype != OFX_REINVEST)
+    {
+        DEBUG("Adding investment split; Money flows from or to the cash account");
+        add_currency_split(transaction, import_account,
+                           -ofx_get_investment_amount(data), data);
+    }
+}
+
+int ofx_proc_transaction_cb(OfxTransactionData data, void *user_data)
+{
+    Account *import_account;
+    gnc_commodity *currency = NULL;
+    QofBook *book;
+    Transaction *transaction;
+    ofx_info* info = (ofx_info*) user_data;
+
+    g_assert(info->gnc_ofx_importer_gui);
+
+    if (!data.amount_valid)
     {
         PERR("The transaction doesn't have a valid amount");
+        return 0;
+    }
+
+    if (!data.account_id_valid)
+    {
+        PERR("account ID for this transaction is unavailable!");
+        return 0;
+    }
+
+    gnc_utf8_strip_invalid (data.account_id);
+
+    import_account = gnc_import_select_account(gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
+                                        data.account_id,
+					0, NULL, NULL, ACCT_TYPE_NONE,
+					info->last_import_account, NULL);
+    if (import_account == NULL)
+    {
+        PERR("Unable to find account for id %s", data.account_id);
+        return 0;
+    }
+    info->last_import_account = import_account;
+    /***** Validate the input strings to ensure utf8 *****/
+    if (data.name_valid)
+        gnc_utf8_strip_invalid(data.name);
+    if (data.memo_valid)
+        gnc_utf8_strip_invalid(data.memo);
+    if (data.check_number_valid)
+        gnc_utf8_strip_invalid(data.check_number);
+    if (data.reference_number_valid)
+        gnc_utf8_strip_invalid(data.reference_number);
+
+    /***** Create the transaction and setup transaction data *******/
+    book = gnc_account_get_book(import_account);
+    transaction = xaccMallocTransaction(book);
+    xaccTransBeginEdit(transaction);
+
+    set_transaction_dates(transaction, &data);
+    fill_transaction_description(transaction, &data);
+    fill_transaction_notes(transaction, &data);
+
+    if (data.account_ptr && data.account_ptr->currency_valid)
+    {
+        DEBUG("Currency from libofx: %s", data.account_ptr->currency);
+        currency = gnc_commodity_table_lookup( gnc_get_current_commodities (),
+                                               GNC_COMMODITY_NS_CURRENCY,
+                                               data.account_ptr->currency);
+    }
+    else
+    {
+        DEBUG("Currency from libofx unavailable, defaulting to account's default");
+        currency = xaccAccountGetCommodity(import_account);
+    }
+
+    xaccTransSetCurrency(transaction, currency);
+
+    if (!data.invtransactiontype_valid
+#ifdef HAVE_LIBOFX_VERSION_0_10
+        || data.invtransactiontype == OFX_INVBANKTRAN
+#endif
+        )
+        process_bank_transaction(transaction, import_account, &data, info);
+    else if (data.unique_id_valid
+             && data.security_data_valid
+             && data.security_data_ptr != NULL
+             && data.security_data_ptr->secname_valid)
+        process_investment_transaction(transaction, import_account,
+                                       &data, info);
+    else
+    {
+        PERR("Unsupported OFX transaction type.");
+        xaccTransDestroy(transaction);
+        xaccTransCommitEdit(transaction);
+        return 0;
+    }
+
+    /* Send transaction to importer GUI. */
+    if (xaccTransCountSplits(transaction) > 0)
+    {
+        DEBUG("%d splits sent to the importer gui",
+              xaccTransCountSplits(transaction));
+        info->trans_list = g_list_prepend (info->trans_list, transaction);
+    }
+    else
+    {
+        PERR("No splits in transaction (missing account?), ignoring.");
         xaccTransDestroy(transaction);
         xaccTransCommitEdit(transaction);
     }
+
     info->num_trans_processed += 1;
     return 0;
 }//end ofx_proc_transaction()

commit 0bc2d692c732048f6950c514ce8d126c82810dfa
Author: John Ralls <jralls at ceridwen.us>
Date:   Thu Dec 8 13:09:10 2022 -0800

    [ofx import] Typedef OfxTransactionData.
    
    So we don't have to say struct on every use.

diff --git a/gnucash/import-export/ofx/gnc-ofx-import.c b/gnucash/import-export/ofx/gnc-ofx-import.c
index c1f0bd205..f7781c568 100644
--- a/gnucash/import-export/ofx/gnc-ofx-import.c
+++ b/gnucash/import-export/ofx/gnc-ofx-import.c
@@ -65,6 +65,9 @@ static QofLogModule log_module = GNC_MOD_IMPORT;
 
 static gboolean auto_create_commodity = FALSE;
 static Account *ofx_parent_account = NULL;
+
+typedef struct OfxTransactionData OfxTransactionData;
+
 // Structure we use to gather information about statement balance/account etc.
 typedef struct _ofx_info
 {
@@ -126,9 +129,9 @@ set_associated_income_account(Account* investment_account,
 
 int ofx_proc_statement_cb (struct OfxStatementData data, void * statement_user_data);
 int ofx_proc_security_cb (const struct OfxSecurityData data, void * security_user_data);
-int ofx_proc_transaction_cb (struct OfxTransactionData data, void *user_data);
+int ofx_proc_transaction_cb (OfxTransactionData data, void *user_data);
 int ofx_proc_account_cb (struct OfxAccountData data, void * account_user_data);
-static double ofx_get_investment_amount (const struct OfxTransactionData* data);
+static double ofx_get_investment_amount (const OfxTransactionData* data);
 
 static const gchar *gnc_ofx_ttype_to_string(TransactionType t)
 {
@@ -314,7 +317,7 @@ int ofx_proc_security_cb(const struct OfxSecurityData data, void * security_user
     return 0;
 }
 
-static void gnc_ofx_set_split_memo(const struct OfxTransactionData* data, Split *split)
+static void gnc_ofx_set_split_memo(const OfxTransactionData* data, Split *split)
 {
     g_assert(data);
     g_assert(split);
@@ -412,7 +415,7 @@ fix_ofx_bug_39 (time64 t)
     return t;
 }
 
-int ofx_proc_transaction_cb(struct OfxTransactionData data, void *user_data)
+int ofx_proc_transaction_cb(OfxTransactionData data, void *user_data)
 {
     char dest_string[255];
     time64 current_time = gnc_time (NULL);
@@ -1079,7 +1082,7 @@ int ofx_proc_account_cb(struct OfxAccountData data, void * account_user_data)
     return 0;
 }
 
-double ofx_get_investment_amount(const struct OfxTransactionData* data)
+double ofx_get_investment_amount(const OfxTransactionData* data)
 {
     double amount = data->amount;
 #ifdef HAVE_LIBOFX_VERSION_0_10

commit 5519a9d788c1ec6439252daf956d16c299dcd651
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Dec 10 16:26:05 2022 -0800

    [import matcher] Make related functions adjacent
    
    gnc_gen_trans_list_add_trans just wraps
    gnc_gen_trans_list_add_trans_with_ref_id.

diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c
index bb4dfbe0d..ababddff6 100644
--- a/gnucash/import-export/import-main-matcher.c
+++ b/gnucash/import-export/import-main-matcher.c
@@ -2189,21 +2189,6 @@ refresh_model_row (GNCImportMainMatcher *gui,
     gtk_tree_selection_unselect_all (selection);
 }
 
-void
-gnc_gen_trans_list_add_trans (GNCImportMainMatcher *gui, Transaction *trans)
-{
-    Account* acc = NULL;
-    Split* split = NULL;
-    int i=0;
-
-    split = xaccTransGetSplit (trans, 0);
-    acc = xaccSplitGetAccount (split);
-    defer_bal_computation (gui, acc);
-
-    gnc_gen_trans_list_add_trans_with_ref_id (gui, trans, 0);
-    return;
-}/* end gnc_import_add_trans() */
-
 void
 gnc_gen_trans_list_show_reconcile_after_close_button (GNCImportMainMatcher *info,
                                                       gboolean reconcile_after_close,
@@ -2219,6 +2204,22 @@ gnc_gen_trans_list_get_reconcile_after_close_button (GNCImportMainMatcher *info)
     return info->reconcile_after_close;
 }
 
+
+void
+gnc_gen_trans_list_add_trans (GNCImportMainMatcher *gui, Transaction *trans)
+{
+    Account* acc = NULL;
+    Split* split = NULL;
+    int i=0;
+
+    split = xaccTransGetSplit (trans, 0);
+    acc = xaccSplitGetAccount (split);
+    defer_bal_computation (gui, acc);
+
+    gnc_gen_trans_list_add_trans_with_ref_id (gui, trans, 0);
+    return;
+}/* end gnc_import_add_trans() */
+
 void
 gnc_gen_trans_list_add_trans_with_ref_id (GNCImportMainMatcher *gui, Transaction *trans, guint32 ref_id)
 {

commit e116a4f195250b782c40e51a19a53409be73b480
Author: John Ralls <jralls at ceridwen.us>
Date:   Tue Dec 13 12:32:54 2022 -0800

    [import backend] Extract function hash_account_online_ids.

diff --git a/gnucash/import-export/import-backend.c b/gnucash/import-export/import-backend.c
index 14a1da107..b74333a16 100644
--- a/gnucash/import-export/import-backend.c
+++ b/gnucash/import-export/import-backend.c
@@ -1187,6 +1187,22 @@ static gint check_trans_online_id(Transaction *trans1, void *user_data)
     return retval;
 }
 
+static GHashTable*
+hash_account_online_ids (Account *account)
+{
+     GHashTable* acct_hash = g_hash_table_new_full
+          (g_str_hash, g_str_equal, g_free, NULL);
+     for (GList *n = xaccAccountGetSplitList (account) ; n; n = n->next)
+     {
+          if (gnc_import_split_has_online_id (n->data))
+          {
+               char *id = gnc_import_get_split_online_id (n->data);
+               g_hash_table_insert (acct_hash, (void*) id, GINT_TO_POINTER (1));
+          }
+     }
+     return acct_hash;
+}
+
 /** Checks whether the given transaction's online_id already exists in
   its parent account. */
 gboolean gnc_import_exists_online_id (Transaction *trans, GHashTable* acct_id_hash)
@@ -1210,19 +1226,8 @@ gboolean gnc_import_exists_online_id (Transaction *trans, GHashTable* acct_id_ha
     // test below will be fast if we have many transactions to import.
     dest_acct = xaccSplitGetAccount (source_split);
     if (!g_hash_table_contains (acct_id_hash, dest_acct))
-    {
-        GHashTable* new_hash = g_hash_table_new_full
-            (g_str_hash, g_str_equal, g_free, NULL);
-        g_hash_table_insert (acct_id_hash, dest_acct, new_hash);
-        for (GList *n = xaccAccountGetSplitList (dest_acct) ; n; n=n->next)
-        {
-            if (gnc_import_split_has_online_id (n->data))
-            {
-                char *id = gnc_import_get_split_online_id (n->data);
-                g_hash_table_insert (new_hash, (void*) id, GINT_TO_POINTER (1));
-            }
-        }
-    }
+         g_hash_table_insert (acct_id_hash, dest_acct,
+                              hash_account_online_ids (dest_acct));
     online_id_exists = g_hash_table_contains (g_hash_table_lookup (acct_id_hash, dest_acct),
                                               source_online_id);
     



Summary of changes:
 gnucash/import-export/import-backend.c      |  31 +-
 gnucash/import-export/import-main-matcher.c |  31 +-
 gnucash/import-export/ofx/gnc-ofx-import.c  | 947 +++++++++++++++-------------
 libgnucash/engine/Split.c                   |   2 +
 libgnucash/engine/Transaction.c             |   3 +
 5 files changed, 545 insertions(+), 469 deletions(-)



More information about the gnucash-changes mailing list