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
This commit is contained in:
Ralf Habacker 2020-09-19 10:53:08 +02:00
parent 573f7aaa0b
commit f8dcd23023
9 changed files with 217 additions and 3 deletions

View File

@ -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"));

View File

@ -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;

View File

@ -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,

View File

@ -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)

View File

@ -1504,6 +1504,23 @@
<property name="top_attach">7</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="opening_balance_button">
<property name="label" translatable="yes">Opening balance</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">This account holds opening balance transactions. Only one account per commodity can hold opening balance transactions.</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">11</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>

View File

@ -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);

View File

@ -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<GParamFlags>(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<GParamFlags>(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,

View File

@ -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"

View File

@ -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);