From 8ad29feaba539f6bd90f558ac76d50671f808820 Mon Sep 17 00:00:00 2001 From: goodvibes2 Date: Thu, 30 Sep 2021 15:28:28 +1000 Subject: [PATCH] Bug797678 OFX import should append not replace, existing Notes & Desc Add an "Append" checkbox to the bottom of the "Generic import transaction matcher" window to the left of the "Reconcile after match" checkbox. When ticked, this causes the imported Description/Notes to be appended to the matched transaction Description/Notes respectively. The selected ticked/unticked state of the "Append" checkbox is saved in a key value pair for the import account, so the next import for that account will automatically default it to the saved state. As these mods are limited to the code for the matcher window, this should work for all the imports that use it - ie ofx & csv file imports (both tested) & aqbanking (cannot test). --- gnucash/gtkbuilder/dialog-import.glade | 22 ++++- gnucash/import-export/import-backend.c | 91 +++++++++++++++++++-- gnucash/import-export/import-backend.h | 5 ++ gnucash/import-export/import-main-matcher.c | 44 +++++++++- gnucash/import-export/import-main-matcher.h | 7 ++ libgnucash/engine/Account.cpp | 30 +++++++ libgnucash/engine/Account.h | 24 ++++++ 7 files changed, 212 insertions(+), 11 deletions(-) diff --git a/gnucash/gtkbuilder/dialog-import.glade b/gnucash/gtkbuilder/dialog-import.glade index b32a5f842f..696a5fa605 100644 --- a/gnucash/gtkbuilder/dialog-import.glade +++ b/gnucash/gtkbuilder/dialog-import.glade @@ -960,7 +960,7 @@ - Show _matched information + Show matched _information True True False @@ -973,9 +973,25 @@ 2 + + + A_ppend + True + True + False + When Updating and Clearing a matched transaction, append the imported Description and Notes to the matched Description and Notes instead of replacing them. + True + True + + + False + True + 3 + + - Reconcile after match + _Reconcile after match True False True @@ -985,7 +1001,7 @@ False True - 3 + 4 diff --git a/gnucash/import-export/import-backend.c b/gnucash/import-export/import-backend.c index d9f86f6cce..c17487a227 100644 --- a/gnucash/import-export/import-backend.c +++ b/gnucash/import-export/import-backend.c @@ -95,6 +95,9 @@ struct _transactioninfo /* Reference id to link gnc transaction to external object. E.g. aqbanking job id. */ guint32 ref_id; + + /* When updating a matched transaction, append Description and Notes instead of replacing */ + gboolean append_text; }; /* Some simple getters and setters for the above data types. */ @@ -240,6 +243,15 @@ gnc_import_TransInfo_set_ref_id (GNCImportTransInfo *info, } +void +gnc_import_TransInfo_set_append_text (GNCImportTransInfo *info, + gboolean append_text) +{ + g_assert (info); + info->append_text = append_text; +} + + Split * gnc_import_MatchInfo_get_split (const GNCImportMatchInfo * info) { @@ -827,6 +839,77 @@ void split_find_match (GNCImportTransInfo * trans_info, /*********************************************************************** */ +/* append the imported transaction description to the matched transaction description */ +static void +desc_append (Transaction* selected_match_trans, Transaction* imp_trans) +{ + gchar* tmp = g_strconcat( xaccTransGetDescription (selected_match_trans), + "|", + xaccTransGetDescription (imp_trans), + NULL); + xaccTransSetDescription (selected_match_trans, tmp); + g_free (tmp); +} + +/* append the imported transaction notes to the matched transaction notes */ +static void +notes_append (Transaction* selected_match_trans, Transaction* imp_trans) +{ + gchar* tmp = g_strconcat (xaccTransGetNotes (selected_match_trans), + "|", + xaccTransGetNotes (imp_trans), + NULL); + xaccTransSetNotes (selected_match_trans, tmp); + g_free (tmp); +} + +/* Append or replace transaction description and notes + * depending on the Append checkbox + */ +static void +update_desc_and_notes (const GNCImportTransInfo* trans_info) +{ + GNCImportMatchInfo* selected_match = + gnc_import_TransInfo_get_selected_match (trans_info); + Transaction* imp_trans = gnc_import_TransInfo_get_trans (trans_info); + + if (trans_info->append_text) + { + gchar* desc_imported = g_utf8_normalize (xaccTransGetDescription ( + imp_trans), -1, G_NORMALIZE_ALL); + gchar* desc_matched = g_utf8_normalize (xaccTransGetDescription ( + selected_match->trans), -1, G_NORMALIZE_ALL); + gchar* note_imported = g_utf8_normalize (xaccTransGetNotes ( + imp_trans), -1, G_NORMALIZE_ALL); + gchar* note_matched = g_utf8_normalize (xaccTransGetNotes ( + selected_match->trans), -1, G_NORMALIZE_ALL); + + // Append if desc_imported not already in desc_matched + if (g_utf8_strlen (desc_imported, -1) > g_utf8_strlen (desc_matched, -1) || + !strstr (desc_matched, desc_imported)) + desc_append (selected_match->trans, imp_trans); + + // Append if note_imported not already in note_matched + if (g_utf8_strlen (note_imported, -1) > g_utf8_strlen (note_matched, -1) || + !strstr (note_matched, note_imported)) + notes_append (selected_match->trans, imp_trans); + + g_free(desc_imported); + g_free(desc_matched); + g_free(note_imported); + g_free(note_matched); + } + else + { + // replace the matched transaction description with the imported transaction description + xaccTransSetDescription (selected_match->trans, + xaccTransGetDescription (imp_trans)); + // replace the matched transaction notes with the imported transaction notes + xaccTransSetNotes (selected_match->trans, + xaccTransGetNotes (imp_trans)); + } +} + /** /brief -- Processes one match according to its selected action. */ gboolean @@ -944,13 +1027,7 @@ gnc_import_process_trans_item (GncImportMatchMap *matchmap, to balance the transaction */ } - xaccTransSetDescription(selected_match->trans, - xaccTransGetDescription( - gnc_import_TransInfo_get_trans(trans_info))); - - xaccTransSetNotes(selected_match->trans, - xaccTransGetNotes( - gnc_import_TransInfo_get_trans(trans_info))); + update_desc_and_notes( trans_info); if (xaccSplitGetReconcile(selected_match->split) == NREC) { diff --git a/gnucash/import-export/import-backend.h b/gnucash/import-export/import-backend.h index b524d18359..cb9c726631 100644 --- a/gnucash/import-export/import-backend.h +++ b/gnucash/import-export/import-backend.h @@ -238,6 +238,11 @@ void gnc_import_TransInfo_set_ref_id (GNCImportTransInfo *info, guint32 ref_id); +/** Set the append_text for this TransInfo. */ +void +gnc_import_TransInfo_set_append_text (GNCImportTransInfo *info, + gboolean append_text); + /**@}*/ /** @name Getters/Setters for GNCImportMatchInfo */ diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index e6c9df3611..c0f11ae33d 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -72,6 +72,7 @@ struct _main_matcher_info GtkTreeViewColumn *memo_column; GtkWidget *show_account_column; GtkWidget *show_matched_info; + GtkWidget *append_text; // Update+Clear: Append import Desc/Notes to matched Desc/Notes GtkWidget *reconcile_after_close; gboolean add_toggled; // flag to indicate that add has been toggled to stop selection gint id; @@ -444,7 +445,22 @@ resolve_conflicts (GNCImportMainMatcher *info) void gnc_gen_trans_list_show_all (GNCImportMainMatcher *info) { + GNCImportTransInfo* trans_info; + Account* account; + Split* first_split; + GSList* temp_trans_list; + g_assert (info); + + // Set initial state of Append checkbox to same as last import for this account. + // Get the import account from the first split in first transaction. + temp_trans_list = info->temp_trans_list; + trans_info = temp_trans_list->data; + first_split = gnc_import_TransInfo_get_fsplit (trans_info); + account = xaccSplitGetAccount(first_split); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (info->append_text), + xaccAccountGetAppendText(account)); + gnc_gen_trans_list_create_matches (info); resolve_conflicts (info); gtk_widget_show_all (GTK_WIDGET(info->main_widget)); @@ -457,6 +473,9 @@ on_matcher_ok_clicked (GtkButton *button, GNCImportMainMatcher *info) GtkTreeModel *model; GtkTreeIter iter; GNCImportTransInfo *trans_info; + gboolean append_text = gtk_toggle_button_get_active ((GtkToggleButton*) info->append_text); + gboolean first_tran = TRUE; + gpointer user_data = info->user_data; g_assert (info); @@ -479,6 +498,20 @@ on_matcher_ok_clicked (GtkButton *button, GNCImportMainMatcher *info) DOWNLOADED_COL_DATA, &trans_info, -1); + // Allow the backend to know if the Append checkbox is ticked or unticked + // by propagating info->append_text to every trans_info->append_text + gnc_import_TransInfo_set_append_text( trans_info, append_text); + + // When processing the first transaction, + // save the state of the Append checkbox to an account kvp so the same state can be + // defaulted next time this account is imported. + // Get the import account from the first split. + if (first_tran) + { + Split* first_split = gnc_import_TransInfo_get_fsplit (trans_info); + xaccAccountSetAppendText (xaccSplitGetAccount(first_split), append_text); + first_tran = FALSE; + } // Note: if there's only 1 split (unbalanced) one will be created with the unbalanced account, // and for that account the defer balance will not be set. So things will be slow. @@ -1222,7 +1255,9 @@ gnc_gen_trans_common_setup (GNCImportMainMatcher *info, g_signal_connect (G_OBJECT(info->show_matched_info), "toggled", G_CALLBACK(show_matched_info_toggled_cb), info); - // Create the checkbox, but do not show it by default. + info->append_text = GTK_WIDGET(gtk_builder_get_object (builder, "append_desc_notes_button")); + + // Create the checkbox, but do not show it unless there are transactions info->reconcile_after_close = GTK_WIDGET(gtk_builder_get_object (builder, "reconcile_after_close_button")); show_update = gnc_import_Settings_get_action_update_enabled (info->user_settings); @@ -1905,6 +1940,13 @@ gnc_gen_trans_list_widget (GNCImportMainMatcher *info) return info->main_widget; } +GtkWidget * +gnc_gen_trans_list_append_text_widget (GNCImportMainMatcher *info) +{ + g_assert (info); + return info->append_text; +} + gboolean query_tooltip_tree_view_cb (GtkWidget *widget, gint x, gint y, gboolean keyboard_tip, diff --git a/gnucash/import-export/import-main-matcher.h b/gnucash/import-export/import-main-matcher.h index 6b20c3d4f3..490d54c449 100644 --- a/gnucash/import-export/import-main-matcher.h +++ b/gnucash/import-export/import-main-matcher.h @@ -189,6 +189,13 @@ gboolean gnc_gen_trans_list_run (GNCImportMainMatcher *info); */ GtkWidget *gnc_gen_trans_list_widget (GNCImportMainMatcher *info); +/** Returns the append_text widget of this dialog. + * @param info A pointer to a the GNCImportMainMatcher structure. + * @return A GtkWidget pointer to the append_text widget. + */ +GtkWidget * +gnc_gen_trans_list_append_text_widget (GNCImportMainMatcher *info); + /** Checks whether there are no transactions to match. * @param info A pointer to a the GNCImportMainMatcher structure. * @return A boolean indicating whether the transaction list is empty. diff --git a/libgnucash/engine/Account.cpp b/libgnucash/engine/Account.cpp index 03e461d591..ee375d3046 100644 --- a/libgnucash/engine/Account.cpp +++ b/libgnucash/engine/Account.cpp @@ -65,6 +65,7 @@ static const std::string KEY_INCLUDE_CHILDREN("include-children"); static const std::string KEY_POSTPONE("postpone"); static const std::string KEY_LOT_MGMT("lot-mgmt"); static const std::string KEY_ONLINE_ID("online_id"); +static const std::string KEY_IMP_APPEND_TEXT("import-append-text"); static const std::string AB_KEY("hbci"); static const std::string AB_ACCOUNT_ID("account-id"); static const std::string AB_ACCOUNT_UID("account-uid"); @@ -116,6 +117,7 @@ enum PROP_LOT_NEXT_ID, /* KVP */ PROP_ONLINE_ACCOUNT, /* KVP */ + PROP_IMP_APPEND_TEXT, /* KVP */ PROP_IS_OPENING_BALANCE, /* KVP */ PROP_OFX_INCOME_ACCOUNT, /* KVP */ PROP_AB_ACCOUNT_ID, /* KVP */ @@ -484,6 +486,9 @@ gnc_account_get_property (GObject *object, case PROP_ONLINE_ACCOUNT: qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {KEY_ONLINE_ID}); break; + case PROP_IMP_APPEND_TEXT: + g_value_set_boolean(value, xaccAccountGetAppendText(account)); + break; case PROP_OFX_INCOME_ACCOUNT: qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {KEY_ASSOC_INCOME_ACCOUNT}); break; @@ -613,6 +618,9 @@ gnc_account_set_property (GObject *object, case PROP_ONLINE_ACCOUNT: qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {KEY_ONLINE_ID}); break; + case PROP_IMP_APPEND_TEXT: + xaccAccountSetAppendText(account, g_value_get_boolean(value)); + break; case PROP_OFX_INCOME_ACCOUNT: qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {KEY_ASSOC_INCOME_ACCOUNT}); break; @@ -1062,6 +1070,16 @@ gnc_account_class_init (AccountClass *klass) NULL, static_cast(G_PARAM_READWRITE))); + g_object_class_install_property + (gobject_class, + PROP_IMP_APPEND_TEXT, + g_param_spec_boolean ("import-append-text", + "Import Append Text", + "Saved state of Append checkbox for setting initial " + "value next time this account is imported.", + FALSE, + static_cast(G_PARAM_READWRITE))); + g_object_class_install_property( gobject_class, PROP_OFX_INCOME_ACCOUNT, @@ -4228,6 +4246,18 @@ xaccAccountSetPlaceholder (Account *acc, gboolean val) set_boolean_key(acc, {"placeholder"}, val); } +gboolean +xaccAccountGetAppendText (const Account *acc) +{ + return boolean_from_key(acc, {"import-append-text"}); +} + +void +xaccAccountSetAppendText (Account *acc, gboolean val) +{ + set_boolean_key(acc, {"import-append-text"}, val); +} + gboolean xaccAccountGetIsOpeningBalance (const Account *acc) { diff --git a/libgnucash/engine/Account.h b/libgnucash/engine/Account.h index 6822bfd655..19093036c1 100644 --- a/libgnucash/engine/Account.h +++ b/libgnucash/engine/Account.h @@ -1217,6 +1217,30 @@ gboolean xaccAccountGetPlaceholder (const Account *account); * @param val The new state for the account's "placeholder" flag. */ void xaccAccountSetPlaceholder (Account *account, gboolean val); +/** @name Account Append Text flag + @{ +*/ + +/** Get the "import-append-text" flag for an account. This is the saved + * state of the Append checkbox in the "Generic import transaction matcher" + * used to set the initial state of the Append checkbox next time this + * account is imported. + * + * @param account The account whose flag should be retrieved. + * + * @return The current state of the account's "import-append-text" flag. */ +gboolean xaccAccountGetAppendText (const Account *account); + +/** Set the "import-append-text" flag for an account. This is the saved + * state of the Append checkbox in the "Generic import transaction matcher" + * used to set the initial state of the Append checkbox next time this + * account is imported. + * + * @param account The account whose flag should be retrieved. + * + * @param val The new state for the account's "import-append-text" flag. */ +void xaccAccountSetAppendText (Account *account, gboolean val); + /** Get the "opening-balance" flag for an account. If this flag is set * then the account is used for opening balance transactions. *