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. *