From f8dcd2302366a3d0032038b81f5fb5252fd09f87 Mon Sep 17 00:00:00 2001 From: Ralf Habacker Date: Sat, 19 Sep 2020 10:53:08 +0200 Subject: [PATCH] Add support for the opening balance accounts flag Up to now, opening balance accounts have been identified by means of fixed names and their translations, which in some cases is not appropriate. With this commit, therefore, opening balance accounts can now be identified by a special slot, which should solve the above problem. in gnc_find_or_create_equity_account(), when querying the EQUITY_OPENING_BALANCE type, the system now first searches for an account with an existing 'equity-type' slot having the value 'opening-balance' and returns it as an opening balance account if one exists. If no corresponding account is found, the search is continued as before. An account found in the process is automatically given the status of an opening balance account (it is given an 'equity-type' slot with value 'opening-balance') to simplify the future search. The opening balance status of an account is visualized in the account settings dialog with a check box. If a Gnucash file does not yet contain an opening balance account, one can be selected in the account settings dialog. https://bugs.gnucash.org/show_bug.cgi?id=797836 --- gnucash/gnome-utils/dialog-account.c | 62 ++++++++++++++++++++ gnucash/gnome-utils/gnc-tree-model-account.c | 6 ++ gnucash/gnome-utils/gnc-tree-model-account.h | 3 +- gnucash/gnome-utils/gnc-tree-view-account.c | 31 ++++++++++ gnucash/gtkbuilder/dialog-account.glade | 17 ++++++ libgnucash/app-utils/gnc-ui-util.c | 20 ++++++- libgnucash/engine/Account.cpp | 52 ++++++++++++++++ libgnucash/engine/Account.h | 25 ++++++++ libgnucash/engine/test/utest-Account.cpp | 4 +- 9 files changed, 217 insertions(+), 3 deletions(-) diff --git a/gnucash/gnome-utils/dialog-account.c b/gnucash/gnome-utils/dialog-account.c index f72debba11..3bf1d21ec9 100644 --- a/gnucash/gnome-utils/dialog-account.c +++ b/gnucash/gnome-utils/dialog-account.c @@ -106,6 +106,7 @@ typedef struct _AccountWindow GtkTreeView * parent_tree; GtkWidget * parent_scroll; + GtkWidget * opening_balance_button; GtkWidget * opening_balance_edit; GtkWidget * opening_balance_date_edit; GtkWidget * opening_balance_page; @@ -205,6 +206,32 @@ gnc_account_commodity_from_type (AccountWindow * aw, gboolean update) aw->commodity_mode = new_mode; } +static void +gnc_account_opening_balance_button_update (AccountWindow *aw, gnc_commodity *commodity) +{ + Account *account = aw_get_account (aw); + Account *ob_account = gnc_account_lookup_by_opening_balance (gnc_book_get_root_account (aw->book), commodity); + gboolean has_splits = xaccAccountCountSplits (account, FALSE) > 0; + + if (xaccAccountGetType (account) != ACCT_TYPE_EQUITY) + { + gtk_widget_set_sensitive (aw->opening_balance_button, FALSE); + return; + } + /* The opening balance flag can be edited, if there is no opening balance account + * or we are editing the only opening balance account and it has no splits assigned. + */ + switch(aw->dialog_type) + { + case EDIT_ACCOUNT: + gtk_widget_set_sensitive (aw->opening_balance_button, (ob_account == NULL || ob_account == account) && has_splits == 0); + break; + case NEW_ACCOUNT: + gtk_widget_set_sensitive (aw->opening_balance_button, ob_account == NULL); + break; + } +} + /* Copy the account values to the GUI widgets */ static void gnc_account_to_ui(AccountWindow *aw) @@ -268,6 +295,12 @@ gnc_account_to_ui(AccountWindow *aw) gtk_text_buffer_set_text (aw->notes_text_buffer, string, strlen(string)); + gnc_account_opening_balance_button_update (aw, commodity); + + flag = xaccAccountGetIsOpeningBalance (account); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (aw->opening_balance_button), + flag); + flag = xaccAccountGetTaxRelated (account); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (aw->tax_related_button), flag); @@ -434,6 +467,11 @@ gnc_ui_to_account(AccountWindow *aw) if (null_strcmp (string, old_string) != 0) xaccAccountSetNotes (account, string); + flag = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (aw->opening_balance_button)); + if (xaccAccountGetIsOpeningBalance (account) != flag) + xaccAccountSetIsOpeningBalance (account, flag); + flag = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (aw->tax_related_button)); if (xaccAccountGetTaxRelated (account) != flag) @@ -1271,11 +1309,33 @@ commodity_changed_cb (GNCGeneralSelect *gsl, gpointer data) AccountWindow *aw = data; gnc_commodity *currency; GtkTreeSelection *selection; + Account *account = aw_get_account (aw); currency = (gnc_commodity *) gnc_general_select_get_selected (gsl); if (!currency) return; + if (xaccAccountGetIsOpeningBalance (account)) + { + Account *ob_account = gnc_account_lookup_by_opening_balance (gnc_book_get_root_account (aw->book), currency); + if (ob_account != account) + { + gchar *dialog_msg = _("An account with opening balance already exists for the desired currency."); + gchar *dialog_title = _("Cannot change currency"); + GtkWidget *dialog = gtk_message_dialog_new (gnc_ui_get_main_window (NULL), + 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", dialog_title); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog), + "%s", dialog_msg); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + gnc_general_select_set_selected (gsl, xaccAccountGetCommodity (account)); + return; + } + } + gnc_amount_edit_set_fraction (GNC_AMOUNT_EDIT (aw->opening_balance_edit), gnc_commodity_get_fraction (currency)); gnc_amount_edit_set_print_info (GNC_AMOUNT_EDIT (aw->opening_balance_edit), @@ -1283,6 +1343,7 @@ commodity_changed_cb (GNCGeneralSelect *gsl, gpointer data) selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (aw->transfer_tree)); gtk_tree_selection_unselect_all (selection); + gnc_account_opening_balance_button_update (aw, currency); } static gboolean @@ -1420,6 +1481,7 @@ gnc_account_window_create(GtkWindow *parent, AccountWindow *aw) g_signal_connect (G_OBJECT (selection), "changed", G_CALLBACK (gnc_account_parent_changed_cb), aw); + aw->opening_balance_button = GTK_WIDGET(gtk_builder_get_object (builder, "opening_balance_button")); aw->tax_related_button = GTK_WIDGET(gtk_builder_get_object (builder, "tax_related_button")); aw->placeholder_button = GTK_WIDGET(gtk_builder_get_object (builder, "placeholder_button")); aw->hidden_button = GTK_WIDGET(gtk_builder_get_object (builder, "hidden_button")); diff --git a/gnucash/gnome-utils/gnc-tree-model-account.c b/gnucash/gnome-utils/gnc-tree-model-account.c index 0a93faf4da..5c3160e5ac 100644 --- a/gnucash/gnome-utils/gnc-tree-model-account.c +++ b/gnucash/gnome-utils/gnc-tree-model-account.c @@ -420,6 +420,7 @@ gnc_tree_model_account_get_column_type (GtkTreeModel *tree_model, int index) case GNC_TREE_MODEL_ACCOUNT_COL_HIDDEN: case GNC_TREE_MODEL_ACCOUNT_COL_PLACEHOLDER: + case GNC_TREE_MODEL_ACCOUNT_COL_OPENING_BALANCE: return G_TYPE_BOOLEAN; default: @@ -956,6 +957,11 @@ gnc_tree_model_account_get_value (GtkTreeModel *tree_model, g_value_set_boolean (value, xaccAccountGetPlaceholder (account)); break; + case GNC_TREE_MODEL_ACCOUNT_COL_OPENING_BALANCE: + g_value_init (value, G_TYPE_BOOLEAN); + g_value_set_boolean (value, xaccAccountGetIsOpeningBalance (account)); + break; + default: g_assert_not_reached (); break; diff --git a/gnucash/gnome-utils/gnc-tree-model-account.h b/gnucash/gnome-utils/gnc-tree-model-account.h index 17fd76d53f..177587305e 100644 --- a/gnucash/gnome-utils/gnc-tree-model-account.h +++ b/gnucash/gnome-utils/gnc-tree-model-account.h @@ -81,8 +81,9 @@ typedef enum GNC_TREE_MODEL_ACCOUNT_COL_TAX_INFO_SUB_ACCT, GNC_TREE_MODEL_ACCOUNT_COL_HIDDEN, GNC_TREE_MODEL_ACCOUNT_COL_PLACEHOLDER, + GNC_TREE_MODEL_ACCOUNT_COL_OPENING_BALANCE, - GNC_TREE_MODEL_ACCOUNT_COL_LAST_VISIBLE = GNC_TREE_MODEL_ACCOUNT_COL_PLACEHOLDER, + GNC_TREE_MODEL_ACCOUNT_COL_LAST_VISIBLE = GNC_TREE_MODEL_ACCOUNT_COL_OPENING_BALANCE, /* internal hidden columns */ GNC_TREE_MODEL_ACCOUNT_COL_COLOR_PRESENT, diff --git a/gnucash/gnome-utils/gnc-tree-view-account.c b/gnucash/gnome-utils/gnc-tree-view-account.c index f4abff1872..0da39e1171 100644 --- a/gnucash/gnome-utils/gnc-tree-view-account.c +++ b/gnucash/gnome-utils/gnc-tree-view-account.c @@ -494,6 +494,29 @@ sort_by_placeholder (GtkTreeModel *f_model, return xaccAccountOrder(account_a, account_b); } +static gint +sort_by_opening_balance (GtkTreeModel *f_model, + GtkTreeIter *f_iter_a, + GtkTreeIter *f_iter_b, + gpointer user_data) +{ + const Account *account_a, *account_b; + gboolean flag_a, flag_b; + + /* Find the accounts */ + sort_cb_setup (f_model, f_iter_a, f_iter_b, &account_a, &account_b); + + /* Get the opening balance flags. */ + flag_a = xaccAccountGetIsOpeningBalance (account_a); + flag_b = xaccAccountGetIsOpeningBalance (account_b); + + if (flag_a > flag_b) + return -1; + else if (flag_a < flag_b) + return 1; + return xaccAccountOrder(account_a, account_b); +} + static gint sort_by_xxx_period_value (GtkTreeModel *f_model, GtkTreeIter *f_iter_a, @@ -970,6 +993,14 @@ gnc_tree_view_account_new_with_root (Account *root, gboolean show_root) sort_by_placeholder, gnc_tree_view_account_placeholder_toggled); + gnc_tree_view_add_toggle_column(view, _("Opening Balance"), + C_("Column header for 'Opening Balance'", "O"), + "opening-balance", + GNC_TREE_MODEL_ACCOUNT_COL_OPENING_BALANCE, + GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS, + sort_by_opening_balance, + NULL); + /* Add function to each column that optionally sets a background color for accounts */ col_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view)); for (node = col_list; node; node = node->next) diff --git a/gnucash/gtkbuilder/dialog-account.glade b/gnucash/gtkbuilder/dialog-account.glade index 18f5d9d70a..dd63585fdb 100644 --- a/gnucash/gtkbuilder/dialog-account.glade +++ b/gnucash/gtkbuilder/dialog-account.glade @@ -1504,6 +1504,23 @@ 7 + + + Opening balance + True + True + False + This account holds opening balance transactions. Only one account per commodity can hold opening balance transactions. + True + + + 1 + 11 + + + + + diff --git a/libgnucash/app-utils/gnc-ui-util.c b/libgnucash/app-utils/gnc-ui-util.c index 02f487f5b8..86b61b296f 100644 --- a/libgnucash/app-utils/gnc-ui-util.c +++ b/libgnucash/app-utils/gnc-ui-util.c @@ -955,7 +955,7 @@ gnc_find_or_create_equity_account (Account *root, gnc_commodity *currency) { Account *parent; - Account *account; + Account *account = NULL; gboolean name_exists; gboolean base_name_exists; const char *base_name; @@ -966,6 +966,13 @@ gnc_find_or_create_equity_account (Account *root, g_return_val_if_fail (currency != NULL, NULL); g_return_val_if_fail (root != NULL, NULL); + if (equity_type == EQUITY_OPENING_BALANCE) + { + account = gnc_account_lookup_by_opening_balance (root, currency); + if (account) + return account; + } + base_name = equity_base_name (equity_type); account = gnc_account_lookup_by_name(root, base_name); @@ -985,7 +992,11 @@ gnc_find_or_create_equity_account (Account *root, if (account && gnc_commodity_equiv (currency, xaccAccountGetCommodity (account))) + { + if (equity_type == EQUITY_OPENING_BALANCE) + xaccAccountSetIsOpeningBalance (account, TRUE); return account; + } name = g_strconcat (base_name, " - ", gnc_commodity_get_mnemonic (currency), NULL); @@ -997,7 +1008,11 @@ gnc_find_or_create_equity_account (Account *root, if (account && gnc_commodity_equiv (currency, xaccAccountGetCommodity (account))) + { + if (equity_type == EQUITY_OPENING_BALANCE) + xaccAccountSetIsOpeningBalance (account, TRUE); return account; + } /* Couldn't find one, so create it */ if (name_exists && base_name_exists) @@ -1027,6 +1042,9 @@ gnc_find_or_create_equity_account (Account *root, xaccAccountSetType (account, ACCT_TYPE_EQUITY); xaccAccountSetCommodity (account, currency); + if (equity_type == EQUITY_OPENING_BALANCE) + xaccAccountSetIsOpeningBalance (account, TRUE); + xaccAccountBeginEdit (parent); gnc_account_append_child (parent, account); xaccAccountCommitEdit (parent); diff --git a/libgnucash/engine/Account.cpp b/libgnucash/engine/Account.cpp index cdba3c20f0..5bcfe1a6c3 100644 --- a/libgnucash/engine/Account.cpp +++ b/libgnucash/engine/Account.cpp @@ -116,6 +116,7 @@ enum PROP_LOT_NEXT_ID, /* KVP */ PROP_ONLINE_ACCOUNT, /* KVP */ + PROP_IS_OPENING_BALANCE, /* KVP */ PROP_OFX_INCOME_ACCOUNT, /* KVP */ PROP_AB_ACCOUNT_ID, /* KVP */ PROP_AB_ACCOUNT_UID, /* KVP */ @@ -467,6 +468,9 @@ gnc_account_get_property (GObject *object, case PROP_AUTO_INTEREST: g_value_set_boolean (value, xaccAccountGetAutoInterest (account)); break; + case PROP_IS_OPENING_BALANCE: + g_value_set_boolean(value, xaccAccountGetIsOpeningBalance(account)); + break; case PROP_PLACEHOLDER: g_value_set_boolean(value, xaccAccountGetPlaceholder(account)); break; @@ -595,6 +599,9 @@ gnc_account_set_property (GObject *object, case PROP_AUTO_INTEREST: xaccAccountSetAutoInterest (account, g_value_get_boolean (value)); break; + case PROP_IS_OPENING_BALANCE: + xaccAccountSetIsOpeningBalance (account, g_value_get_boolean (value)); + break; case PROP_PLACEHOLDER: xaccAccountSetPlaceholder(account, g_value_get_boolean(value)); break; @@ -940,6 +947,15 @@ gnc_account_class_init (AccountClass *klass) FALSE, static_cast(G_PARAM_READWRITE))); + g_object_class_install_property + (gobject_class, + PROP_IS_OPENING_BALANCE, + g_param_spec_boolean ("opening-balance", + "Opening Balance", + "Whether the account holds opening balances", + FALSE, + static_cast(G_PARAM_READWRITE))); + g_object_class_install_property (gobject_class, PROP_TAX_CODE, @@ -3018,6 +3034,21 @@ gnc_account_lookup_by_code (const Account *parent, const char * code) return NULL; } +static gpointer +is_opening_balance_account (Account* account, gpointer data) +{ + gnc_commodity* commodity = GNC_COMMODITY(data); + if (xaccAccountGetIsOpeningBalance(account) && gnc_commodity_equiv(commodity, xaccAccountGetCommodity(account))) + return account; + return nullptr; +} + +Account* +gnc_account_lookup_by_opening_balance (Account* account, gnc_commodity* commodity) +{ + return (Account *)gnc_account_foreach_descendant_until (account, is_opening_balance_account, commodity); +} + /********************************************************************\ * Fetch an account, given its full name * \********************************************************************/ @@ -4128,6 +4159,22 @@ xaccAccountSetPlaceholder (Account *acc, gboolean val) set_boolean_key(acc, {"placeholder"}, val); } +gboolean +xaccAccountGetIsOpeningBalance (const Account *acc) +{ + if (GET_PRIVATE(acc)->type != ACCT_TYPE_EQUITY) + return false; + return g_strcmp0(get_kvp_string_tag(acc, "equity-type"), "opening-balance") == 0; +} + +void +xaccAccountSetIsOpeningBalance (Account *acc, gboolean val) +{ + if (GET_PRIVATE(acc)->type != ACCT_TYPE_EQUITY) + return; + set_kvp_string_tag(acc, "equity-type", val ? "opening-balance" : ""); +} + GNCPlaceholderType xaccAccountGetDescendantPlaceholder (const Account *acc) { @@ -6034,6 +6081,11 @@ gboolean xaccAccountRegister (void) (QofAccessFunc) xaccAccountGetTaxRelated, (QofSetterFunc) xaccAccountSetTaxRelated }, + { + ACCOUNT_OPENING_BALANCE_, QOF_TYPE_BOOLEAN, + (QofAccessFunc) xaccAccountGetIsOpeningBalance, + (QofSetterFunc) xaccAccountSetIsOpeningBalance + }, { ACCOUNT_SCU, QOF_TYPE_INT32, (QofAccessFunc) xaccAccountGetCommoditySCU, diff --git a/libgnucash/engine/Account.h b/libgnucash/engine/Account.h index 2afe184d9b..cb93c64a5b 100644 --- a/libgnucash/engine/Account.h +++ b/libgnucash/engine/Account.h @@ -930,6 +930,14 @@ Account *gnc_account_lookup_by_full_name (const Account *any_account, Account *gnc_account_lookup_by_code (const Account *parent, const char *code); +/** Find the opening balance account for the currency. + * + * @param account The account of which the sought-for account is a descendant. + * @param commodity The commodity in which the account should be denominated + * @return The descendant account of EQUITY_TYPE_OPENING_BALANCE or NULL if one doesn't exist. + */ +Account *gnc_account_lookup_by_opening_balance (Account *account, gnc_commodity *commodity); + /** @} */ /* ------------------ */ @@ -1191,6 +1199,22 @@ gboolean xaccAccountGetPlaceholder (const Account *account); * @param val The new state for the account's "placeholder" flag. */ void xaccAccountSetPlaceholder (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. + * + * @param account The account whose flag should be retrieved. + * + * @return The current state of the account's "opening-balance" flag. */ +gboolean xaccAccountGetIsOpeningBalance (const Account *account); + +/** Set the "opening-balance" flag for an account. If this flag is set + * then the account is used for opening balance transactions. + * + * @param account The account whose flag should be set. + * + * @param val The new state for the account's "opening-balance" flag. */ +void xaccAccountSetIsOpeningBalance (Account *account, gboolean val); + /** Returns PLACEHOLDER_NONE if account is NULL or neither account nor * any descendant of account is a placeholder. If account is a * placeholder, returns PLACEHOLDER_THIS. Otherwise, if any @@ -1568,6 +1592,7 @@ const char * dxaccAccountGetQuoteTZ (const Account *account); #define ACCOUNT_NOTES_ "notes" #define ACCOUNT_BALANCE_ "balance" #define ACCOUNT_NOCLOSING_ "noclosing" +#define ACCOUNT_OPENING_BALANCE_ "opening-balance" #define ACCOUNT_CLEARED_ "cleared" #define ACCOUNT_RECONCILED_ "reconciled" #define ACCOUNT_PRESENT_ "present" diff --git a/libgnucash/engine/test/utest-Account.cpp b/libgnucash/engine/test/utest-Account.cpp index 7c146153d7..eab36c3253 100644 --- a/libgnucash/engine/test/utest-Account.cpp +++ b/libgnucash/engine/test/utest-Account.cpp @@ -546,7 +546,7 @@ test_gnc_account_create_and_destroy (void) GNCAccountType type; gnc_commodity *commo; gint commo_scu, mark; - gboolean non_std_scu, sort_dirty, bal_dirty, tax_rel, hide, hold; + gboolean non_std_scu, sort_dirty, bal_dirty, opening_balance, tax_rel, hide, hold; gint64 copy_num; gnc_numeric *start_bal, *start_clr_bal, *start_rec_bal; gnc_numeric *end_bal, *end_clr_bal, *end_rec_bal; @@ -571,6 +571,7 @@ test_gnc_account_create_and_destroy (void) "end-balance", &end_bal, "end-cleared-balance", &end_clr_bal, "end-reconciled-balance", &end_rec_bal, + "opening-balance", &opening_balance, "policy", &pol, "acct-mark", &mark, "tax-related", &tax_rel, @@ -599,6 +600,7 @@ test_gnc_account_create_and_destroy (void) g_assert (gnc_numeric_zero_p (*start_bal)); g_assert (gnc_numeric_zero_p (*start_clr_bal)); g_assert (gnc_numeric_zero_p (*start_rec_bal)); + g_assert (!opening_balance); g_assert (pol == xaccGetFIFOPolicy ()); g_assert (!mark); g_assert (!tax_rel);