From 248a8504b7b110a8b6a0795d2bad03872d814386 Mon Sep 17 00:00:00 2001 From: jean <27791933+jeanlaroche@users.noreply.github.com> Date: Thu, 9 Sep 2021 10:52:19 -0700 Subject: [PATCH 1/2] Add code to handle ofx files that include transfers between accounts --- gnucash/import-export/ofx/gnc-ofx-import.c | 125 ++++++++++++++------- 1 file changed, 82 insertions(+), 43 deletions(-) diff --git a/gnucash/import-export/ofx/gnc-ofx-import.c b/gnucash/import-export/ofx/gnc-ofx-import.c index 2c98665e67..d03c543d5a 100644 --- a/gnucash/import-export/ofx/gnc-ofx-import.c +++ b/gnucash/import-export/ofx/gnc-ofx-import.c @@ -76,8 +76,11 @@ typedef struct _ofx_info struct OfxStatementData* statement; // Statement, if any gboolean run_reconcile; // If TRUE the reconcile window is opened after matching. GSList* file_list; // List of OFX files to import + GList* trans_list; } ofx_info ; +static void runMatcher(ofx_info* info, char * selected_filename, gboolean go_to_next_file); + /* int ofx_proc_status_cb(struct OfxStatusData data) { @@ -900,7 +903,7 @@ int ofx_proc_transaction_cb(struct OfxTransactionData data, void *user_data) if (xaccTransCountSplits(transaction) > 0) { DEBUG("%d splits sent to the importer gui", xaccTransCountSplits(transaction)); - gnc_gen_trans_list_add_trans (info->gnc_ofx_importer_gui, transaction); + info->trans_list = g_list_prepend(info->trans_list,transaction); } else { @@ -1088,7 +1091,7 @@ gnc_ofx_process_next_file (GtkDialog *dialog, gpointer user_data) info->statement = NULL; // Done with the previous OFX file, process the next one if any. - info->file_list = g_slist_delete_link(info->file_list, info->file_list); + info->file_list = g_slist_delete_link (info->file_list, info->file_list); if (info->file_list) gnc_file_ofx_import_process_file (info); else @@ -1098,14 +1101,21 @@ gnc_ofx_process_next_file (GtkDialog *dialog, gpointer user_data) } } - -// This callback is called when the user is done matching transactions. static void -gnc_ofx_match_done (GtkDialog *dialog, gint response_id, gpointer user_data) +gnc_ofx_match_done (GtkDialog *dialog, gpointer user_data) { ofx_info* info = (ofx_info*) user_data; - - if (response_id == GTK_RESPONSE_OK && info->run_reconcile && info->statement) + + if (info->trans_list && g_list_length (info->trans_list)) + { + // Re-run the match dialog if there are transactions remaining in our list (happens if several accounts + // exist in the same ofx). + info->gnc_ofx_importer_gui = gnc_gen_trans_list_new (GTK_WIDGET (info->parent), NULL, FALSE, 42, FALSE); + runMatcher (info, NULL, true); + return; + } + + if (info->run_reconcile && info->statement) { // Open a reconcile window. Account* account = gnc_import_select_account (gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui), @@ -1121,17 +1131,12 @@ gnc_ofx_match_done (GtkDialog *dialog, gint response_id, gpointer user_data) info->statement->ledger_balance_date); // Connect to destroy, at which point we'll process the next OFX file.. - g_signal_connect (G_OBJECT(gnc_ui_reconcile_window_get_window (rec_window)), "destroy", - G_CALLBACK(gnc_ofx_process_next_file), info); + g_signal_connect (G_OBJECT (gnc_ui_reconcile_window_get_window (rec_window)), "destroy", + G_CALLBACK (gnc_ofx_process_next_file), info); + return; } } - else if (response_id == GTK_RESPONSE_HELP) - return; - else - { - gtk_widget_hide (GTK_WIDGET(dialog)); - gnc_ofx_process_next_file (dialog, info); - } + gnc_ofx_process_next_file (NULL, info); } // This callback is triggered when the user checks or unchecks the reconcile after match @@ -1142,6 +1147,65 @@ reconcile_when_close_toggled_cb (GtkToggleButton *togglebutton, ofx_info* info) info->run_reconcile = gtk_toggle_button_get_active (togglebutton); } +static void +runMatcher(ofx_info* info, char * selected_filename, gboolean go_to_next_file) +{ + GtkWindow *parent = info->parent; + GList* trans_list; + GList* trans_list_remain = NULL; + Account* first_account = NULL; + + // If we have multiple accounts in the ofx file, we need to process transactions one account at a time, + // in case there are transfers between accounts. + trans_list = info->trans_list; + info->num_trans_processed = 0; + for(;trans_list;trans_list=trans_list->next) + { + Transaction* trans = trans_list->data; + Split* split = xaccTransGetSplit (trans, 0); + if (first_account == NULL) first_account = xaccSplitGetAccount (split); + if (xaccSplitGetAccount (split) == first_account) + { + gnc_gen_trans_list_add_trans (info->gnc_ofx_importer_gui, trans_list->data); + info->num_trans_processed ++; + } + else trans_list_remain = g_list_prepend (trans_list_remain, trans_list->data); + } + g_list_free (info->trans_list); + info->trans_list = trans_list_remain; + + // See whether the view has anything in it and warn the user if not. + if (gnc_gen_trans_list_empty (info->gnc_ofx_importer_gui)) + { + gnc_gen_trans_list_delete (info->gnc_ofx_importer_gui); + if (info->num_trans_processed) + { + gchar* acct_name = gnc_get_account_name_for_register (first_account); + gnc_info_dialog (parent, _("OFX file '%s', imported transactions for account '%s'\n%d transactions processed, no transactions to match"), + selected_filename, acct_name, info->num_trans_processed); + g_free (acct_name); + gnc_ofx_match_done (NULL,info); + return; + } + } + else + { + /* Show the match dialog and connect to the "destroy" signal so we can trigger a reconcile when + the user clicks OK when done matching transactions if required. */ + g_signal_connect (G_OBJECT (gnc_gen_trans_list_widget (info->gnc_ofx_importer_gui)), "destroy", + G_CALLBACK (gnc_ofx_match_done), info); + + gnc_gen_trans_list_show_all (info->gnc_ofx_importer_gui); + + // Show or hide the check box for reconciling after match, depending on whether a statement was received. + gnc_gen_trans_list_show_reconcile_after_close_button (info->gnc_ofx_importer_gui, info->statement != NULL, info->run_reconcile); + + // Finally connect to the reconcile after match check box so we can be notified if the user wants/does not want to reconcile. + g_signal_connect (G_OBJECT (gnc_gen_trans_list_get_reconcile_after_close_button (info->gnc_ofx_importer_gui)), "toggled", + G_CALLBACK (reconcile_when_close_toggled_cb), info); + } +} + // Aux function to process the OFX file in info->file_list static void gnc_file_ofx_import_process_file (ofx_info* info) @@ -1182,33 +1246,7 @@ gnc_file_ofx_import_process_file (ofx_info* info) // Free the libofx context before recursing to process the next file libofx_free_context(libofx_context); - - // See whether the view has anything in it and warn the user if not. - if(gnc_gen_trans_list_empty (info->gnc_ofx_importer_gui)) - { - gnc_gen_trans_list_delete (info->gnc_ofx_importer_gui); - if(info->num_trans_processed) - gnc_info_dialog (parent, _("OFX file '%s' imported, %d transactions processed, no transactions to match"), - selected_filename, info->num_trans_processed); - // Process the next OFX file if any. - gnc_ofx_process_next_file (NULL, info); - } - else - { - /* Show the match dialog and connect to the "response" signal so we can trigger a reconcile when - the user clicks OK when done matching transactions if required. */ - g_signal_connect (G_OBJECT(gnc_gen_trans_list_widget (info->gnc_ofx_importer_gui)), "response", - G_CALLBACK (gnc_ofx_match_done), info); - - gnc_gen_trans_list_show_all (info->gnc_ofx_importer_gui); - - // Show or hide the check box for reconciling after match, depending on whether a statement was received. - gnc_gen_trans_list_show_reconcile_after_close_button (info->gnc_ofx_importer_gui, info->statement != NULL, info->run_reconcile); - - // Finally connect to the reconcile after match check box so we can be notified if the user wants/does not want to reconcile. - g_signal_connect (G_OBJECT(gnc_gen_trans_list_get_reconcile_after_close_button (info->gnc_ofx_importer_gui)), "toggled", - G_CALLBACK(reconcile_when_close_toggled_cb), info); - } + runMatcher(info, selected_filename,true); g_free(selected_filename); } @@ -1272,6 +1310,7 @@ void gnc_file_ofx_import (GtkWindow *parent) info->parent = parent; info->run_reconcile = FALSE; info->file_list = selected_filenames; + info->trans_list = NULL; // Call the aux import function. gnc_file_ofx_import_process_file (info); } From 34bd7b49b2747c02310443f723b140a4e619c553 Mon Sep 17 00:00:00 2001 From: jean <27791933+jeanlaroche@users.noreply.github.com> Date: Thu, 9 Sep 2021 21:38:47 -0700 Subject: [PATCH 2/2] Restore previous behavior when the user cancels --- gnucash/import-export/ofx/gnc-ofx-import.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/gnucash/import-export/ofx/gnc-ofx-import.c b/gnucash/import-export/ofx/gnc-ofx-import.c index d03c543d5a..f1116c9143 100644 --- a/gnucash/import-export/ofx/gnc-ofx-import.c +++ b/gnucash/import-export/ofx/gnc-ofx-import.c @@ -76,7 +76,8 @@ typedef struct _ofx_info struct OfxStatementData* statement; // Statement, if any gboolean run_reconcile; // If TRUE the reconcile window is opened after matching. GSList* file_list; // List of OFX files to import - GList* trans_list; + GList* trans_list; // We store the processed ofx transactions here + gint response; // Response sent by the match gui } ofx_info ; static void runMatcher(ofx_info* info, char * selected_filename, gboolean go_to_next_file); @@ -1101,11 +1102,22 @@ gnc_ofx_process_next_file (GtkDialog *dialog, gpointer user_data) } } +static void +gnc_ofx_on_match_click (GtkDialog *dialog, gint response_id, gpointer user_data) +{ + // Record the response of the user. If cancel we won't go to the next file, etc. + ofx_info* info = (ofx_info*) user_data; + info->response = response_id; +} + static void gnc_ofx_match_done (GtkDialog *dialog, gpointer user_data) { ofx_info* info = (ofx_info*) user_data; + // The the user did not click OK, don't process the rest of the transaction, don't go to the next of xfile. + if (info->response != GTK_RESPONSE_OK) return; + if (info->trans_list && g_list_length (info->trans_list)) { // Re-run the match dialog if there are transactions remaining in our list (happens if several accounts @@ -1191,10 +1203,15 @@ runMatcher(ofx_info* info, char * selected_filename, gboolean go_to_next_file) else { /* Show the match dialog and connect to the "destroy" signal so we can trigger a reconcile when - the user clicks OK when done matching transactions if required. */ + the user clicks OK when done matching transactions if required. Connecting to response isn't enough + because only when the matcher is destroyed do imported transactions get recorded */ g_signal_connect (G_OBJECT (gnc_gen_trans_list_widget (info->gnc_ofx_importer_gui)), "destroy", G_CALLBACK (gnc_ofx_match_done), info); + // Connect to response so we know if the user pressed "cancel". + g_signal_connect (G_OBJECT (gnc_gen_trans_list_widget (info->gnc_ofx_importer_gui)), "response", + G_CALLBACK (gnc_ofx_on_match_click), info); + gnc_gen_trans_list_show_all (info->gnc_ofx_importer_gui); // Show or hide the check box for reconciling after match, depending on whether a statement was received. @@ -1311,6 +1328,7 @@ void gnc_file_ofx_import (GtkWindow *parent) info->run_reconcile = FALSE; info->file_list = selected_filenames; info->trans_list = NULL; + info->response = 0; // Call the aux import function. gnc_file_ofx_import_process_file (info); }