gnucash master: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Fri Dec 30 19:12:33 EST 2022


Updated	 via  https://github.com/Gnucash/gnucash/commit/31d79e30 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/7536fcaf (commit)
	 via  https://github.com/Gnucash/gnucash/commit/3be25ff0 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/a15c6f81 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/841b0694 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/60ab654d (commit)
	 via  https://github.com/Gnucash/gnucash/commit/5108c52e (commit)
	 via  https://github.com/Gnucash/gnucash/commit/fb84cf7d (commit)
	 via  https://github.com/Gnucash/gnucash/commit/518ecfe0 (commit)
	from  https://github.com/Gnucash/gnucash/commit/a7abb82d (commit)



commit 31d79e30042fdb843e4b11a24c2aef53c7fc691a
Merge: a7abb82d3 7536fcaf2
Author: John Ralls <jralls at ceridwen.us>
Date:   Fri Dec 30 16:00:12 2022 -0800

    Merge Bob Fewell's 'bug798673' into master.


commit 7536fcaf2f1521f65b9f1bbf041d172cc66cadfe
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Fri Dec 16 15:17:49 2022 +0000

    Change the balance limit icon if both limits are zero
    
    When higher and lower limits are zero, when used for a a suspense
    account, change the icon to 'dialog-warning'. This type of use is to
    indicate that a non zero balance exists.

diff --git a/gnucash/gnome-utils/gnc-tree-model-account.c b/gnucash/gnome-utils/gnc-tree-model-account.c
index 7f083c434..4da356212 100644
--- a/gnucash/gnome-utils/gnc-tree-model-account.c
+++ b/gnucash/gnome-utils/gnc-tree-model-account.c
@@ -833,12 +833,7 @@ gnc_tree_model_account_get_value (GtkTreeModel *tree_model,
 
     case GNC_TREE_MODEL_ACCOUNT_COL_BALANCE_LIMIT:
         g_value_init (value, G_TYPE_STRING);
-        if (gnc_ui_account_is_higher_balance_limit_reached (account))
-            string = "go-top";
-        else if (gnc_ui_account_is_lower_balance_limit_reached (account))
-            string = "go-bottom";
-        else
-            string = "";
+        string = gnc_ui_account_get_balance_limit_icon_name (account);
         g_value_set_string (value, string);
         break;
 
diff --git a/libgnucash/app-utils/gnc-ui-balances.c b/libgnucash/app-utils/gnc-ui-balances.c
index 05539e7a8..3893c04e1 100644
--- a/libgnucash/app-utils/gnc-ui-balances.c
+++ b/libgnucash/app-utils/gnc-ui-balances.c
@@ -272,7 +272,8 @@ account_balance_limit_reached (const Account *account, gnc_numeric balance_limit
 }
 
 gboolean
-gnc_ui_account_is_higher_balance_limit_reached (const Account *account)
+gnc_ui_account_is_higher_balance_limit_reached (const Account *account,
+                                                gboolean *is_zero)
 {
     gnc_numeric balance_limit;
     gboolean limit_valid = FALSE;
@@ -289,6 +290,9 @@ gnc_ui_account_is_higher_balance_limit_reached (const Account *account)
     if (!limit_valid)
         return retval;
 
+    if (gnc_numeric_zero_p (balance_limit))
+        *is_zero = TRUE;
+
     if (account_balance_limit_reached (account, balance_limit) == 1)
         retval = TRUE;
 
@@ -296,7 +300,8 @@ gnc_ui_account_is_higher_balance_limit_reached (const Account *account)
 }
 
 gboolean
-gnc_ui_account_is_lower_balance_limit_reached (const Account *account)
+gnc_ui_account_is_lower_balance_limit_reached (const Account *account,
+                                               gboolean *is_zero)
 {
     gnc_numeric balance_limit;
     gboolean limit_valid = FALSE;
@@ -313,12 +318,44 @@ gnc_ui_account_is_lower_balance_limit_reached (const Account *account)
     if (!limit_valid)
         return retval;
 
+    if (gnc_numeric_zero_p (balance_limit))
+        *is_zero = TRUE;
+
     if (account_balance_limit_reached (account, balance_limit) == -1)
         retval = TRUE;
 
     return retval;
 }
 
+gchar *
+gnc_ui_account_get_balance_limit_icon_name (const Account *account)
+{
+    gboolean lower_limit_reached, higher_limit_reached;
+    gboolean lower_is_zero = FALSE;
+    gboolean higher_is_zero = FALSE;
+
+    g_return_val_if_fail (GNC_IS_ACCOUNT(account), g_strdup (""));
+
+    higher_limit_reached = gnc_ui_account_is_higher_balance_limit_reached (account, &higher_is_zero);
+
+    // assume the higher value will be set mostly so test that first
+    if (higher_limit_reached && !higher_is_zero)
+        return g_strdup ("go-top");
+
+    lower_limit_reached = gnc_ui_account_is_lower_balance_limit_reached (account, &lower_is_zero);
+
+    if (lower_limit_reached && (!lower_is_zero || !higher_is_zero))
+        return g_strdup ("go-bottom");
+
+    if (higher_limit_reached && !lower_is_zero)
+        return g_strdup ("go-top");
+
+    if ((lower_limit_reached || higher_limit_reached ) && lower_is_zero && higher_is_zero)
+        return g_strdup ("dialog-warning");
+
+    return g_strdup ("");
+}
+
 /********************************************************************
  * Balance calculations related to owners
  ********************************************************************/
diff --git a/libgnucash/app-utils/gnc-ui-balances.h b/libgnucash/app-utils/gnc-ui-balances.h
index 2cf0506de..f0b4d5516 100644
--- a/libgnucash/app-utils/gnc-ui-balances.h
+++ b/libgnucash/app-utils/gnc-ui-balances.h
@@ -160,18 +160,30 @@ GList * gnc_account_get_autoclear_splits (Account *account, gnc_numeric toclear_
  *
  *  @param account A pointer to the account.
  *
+ *  @param is_zero A return value, set to TRUE if limit is zero
+ *
  *  @return TRUE if account balance has passed limit.
  */
-gboolean gnc_ui_account_is_higher_balance_limit_reached (const Account *account);
+gboolean gnc_ui_account_is_higher_balance_limit_reached (const Account *account, gboolean *is_zero);
 
 /** Test the account balance as of today for it passing the
  *  lower limit if set.
  *
  *  @param account A pointer to the account.
  *
+ *  @param is_zero A return value, set to TRUE if limit is zero
+ *
  *  @return TRUE if account balance has passed limit.
  */
-gboolean gnc_ui_account_is_lower_balance_limit_reached (const Account *account);
+gboolean gnc_ui_account_is_lower_balance_limit_reached (const Account *account, gboolean *is_zero);
 
+/** Test the account balance as of today for it passing the
+ *  lower and higher limits if set.
+ *
+ *  @param account A pointer to the account.
+ *
+ *  @return The icon name to be displayed.
+ */
+gchar * gnc_ui_account_get_balance_limit_icon_name (const Account *account);
 
 #endif /* GNC_UI_BALANCES_H_ */

commit 3be25ff0a9c53878eeffa2d065c6da06999833e5
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Tue Dec 13 11:48:05 2022 +0000

    Add some tests for the account functions

diff --git a/libgnucash/engine/test/utest-Account.cpp b/libgnucash/engine/test/utest-Account.cpp
index 093bc60a9..60fb3888b 100644
--- a/libgnucash/engine/test/utest-Account.cpp
+++ b/libgnucash/engine/test/utest-Account.cpp
@@ -1089,6 +1089,9 @@ test_gnc_account_kvp_setters_getters (Fixture *fixture, gconstpointer pData)
 {
     Account *account = xaccMallocAccount (gnc_account_get_book (fixture->acct));
     xaccAccountSetType (account, ACCT_TYPE_EQUITY);
+    gnc_numeric h_balance = gnc_numeric_create (1700, 100);
+    gnc_numeric l_balance = gnc_numeric_create (1000, 100);
+    gnc_numeric balance_limit;
 
     // equity_type getter/setter
     g_assert (xaccAccountGetIsOpeningBalance (account) == FALSE);
@@ -1231,6 +1234,32 @@ test_gnc_account_kvp_setters_getters (Fixture *fixture, gconstpointer pData)
     xaccAccountSetNotes (account, nullptr);
     g_assert_cmpstr (xaccAccountGetNotes (account), ==, nullptr);
 
+    // Balance Limits getter/setter
+    g_assert (xaccAccountGetHigherBalanceLimit (account, &balance_limit) == false);
+    g_assert (xaccAccountGetLowerBalanceLimit (account, &balance_limit) == false);
+    g_assert (xaccAccountGetIncludeSubAccountBalances (account) == false);
+
+    xaccAccountSetHigherBalanceLimit (account, h_balance);
+    xaccAccountSetLowerBalanceLimit (account, l_balance);
+    xaccAccountSetIncludeSubAccountBalances (account, true);
+
+    g_assert (xaccAccountGetHigherBalanceLimit (account, &balance_limit) == true);
+    g_assert_cmpint (gnc_numeric_compare (h_balance, balance_limit), ==, 0);
+
+    g_assert (xaccAccountGetLowerBalanceLimit (account, &balance_limit) == true);
+    g_assert_cmpint (gnc_numeric_compare (l_balance, balance_limit), ==, 0);
+
+    g_assert (xaccAccountGetIncludeSubAccountBalances (account) == true);
+
+    xaccAccountSetIncludeSubAccountBalances (account, false);
+
+    xaccAccountClearHigherBalanceLimit (account);
+    xaccAccountClearLowerBalanceLimit (account);
+
+    g_assert (xaccAccountGetHigherBalanceLimit (account, &balance_limit) == false);
+    g_assert (xaccAccountGetLowerBalanceLimit (account, &balance_limit) == false);
+    g_assert (xaccAccountGetIncludeSubAccountBalances (account) == false);
+
     // STOCK_ACCOUNT tests from now on
     xaccAccountSetType (account, ACCT_TYPE_STOCK);
 

commit a15c6f81b34cc84cecd09500d1d12839aad1ba41
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Tue Dec 13 11:47:30 2022 +0000

    Allow the balances of sub accounts to be used in limits

diff --git a/gnucash/gnome-utils/dialog-account.c b/gnucash/gnome-utils/dialog-account.c
index be426f1b9..4043bc650 100644
--- a/gnucash/gnome-utils/dialog-account.c
+++ b/gnucash/gnome-utils/dialog-account.c
@@ -111,6 +111,7 @@ typedef struct _AccountWindow
     GtkWidget *balance_grid;
     GtkWidget *higher_balance_limit_edit;
     GtkWidget *lower_balance_limit_edit;
+    GtkWidget *include_balance_sub_accts;
     gboolean   balance_is_reversed;
 
     GtkWidget *opening_balance_button;
@@ -336,6 +337,11 @@ gnc_account_to_ui (AccountWindow *aw)
 
     aw->balance_is_reversed = gnc_reverse_balance (account);
 
+    flag = xaccAccountGetIncludeSubAccountBalances (account);
+
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(aw->include_balance_sub_accts),
+                                  flag);
+
     balance_limit_valid = xaccAccountGetHigherBalanceLimit (account, &balance_limit);
     if (balance_limit_valid)
     {
@@ -433,7 +439,8 @@ gnc_ui_to_account (AccountWindow *aw)
     gboolean flag;
     gnc_numeric balance;
     gnc_numeric balance_limit;
-    gint balance_limit_valid;
+    gint higher_balance_limit_valid;
+    gint lower_balance_limit_valid;
     gboolean use_equity, nonstd;
     time64 date;
     gint index, old_scu, new_scu;
@@ -546,10 +553,16 @@ gnc_ui_to_account (AccountWindow *aw)
     if (parent_account != gnc_account_get_parent (account))
         gnc_account_append_child (parent_account, account);
 
-    balance_limit_valid = gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(aw->higher_balance_limit_edit),
-                                                         &balance_limit, TRUE, NULL);
+    flag = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(
+                                         aw->include_balance_sub_accts));
+
+    xaccAccountSetIncludeSubAccountBalances (account, flag);
+
+    higher_balance_limit_valid = gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(
+                                                                aw->higher_balance_limit_edit),
+                                                                &balance_limit, TRUE, NULL);
 
-    if (balance_limit_valid == 0)
+    if (higher_balance_limit_valid == 0)
     {
         if (aw->balance_is_reversed)
         {
@@ -560,7 +573,7 @@ gnc_ui_to_account (AccountWindow *aw)
             xaccAccountSetHigherBalanceLimit (account, balance_limit);
     }
 
-    if (balance_limit_valid == -1)
+    if (higher_balance_limit_valid == -1)
     {
         if (aw->balance_is_reversed)
             xaccAccountClearLowerBalanceLimit (account);
@@ -568,10 +581,11 @@ gnc_ui_to_account (AccountWindow *aw)
             xaccAccountClearHigherBalanceLimit (account);
     }
 
-    balance_limit_valid = gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(aw->lower_balance_limit_edit),
-                                                         &balance_limit, TRUE, NULL);
+    lower_balance_limit_valid = gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(
+                                                               aw->lower_balance_limit_edit),
+                                                               &balance_limit, TRUE, NULL);
 
-    if (balance_limit_valid == 0)
+    if (lower_balance_limit_valid == 0)
     {
         if (aw->balance_is_reversed)
         {
@@ -582,7 +596,7 @@ gnc_ui_to_account (AccountWindow *aw)
             xaccAccountSetLowerBalanceLimit (account, balance_limit);
     }
 
-    if (balance_limit_valid == -1)
+    if (lower_balance_limit_valid == -1)
     {
         if (aw->balance_is_reversed)
             xaccAccountClearHigherBalanceLimit (account);
@@ -590,6 +604,9 @@ gnc_ui_to_account (AccountWindow *aw)
             xaccAccountClearLowerBalanceLimit (account);
     }
 
+    if ((higher_balance_limit_valid == -1) && (lower_balance_limit_valid == -1))
+        xaccAccountSetIncludeSubAccountBalances (account, FALSE);
+
     xaccAccountCommitEdit (account);
 
     balance = gnc_amount_edit_get_amount (GNC_AMOUNT_EDIT(aw->opening_balance_edit));
@@ -1618,6 +1635,8 @@ gnc_account_window_create (GtkWindow *parent, AccountWindow *aw)
     gnc_amount_edit_show_warning_symbol (GNC_AMOUNT_EDIT(aw->lower_balance_limit_edit), TRUE);
     gtk_widget_show (aw->lower_balance_limit_edit);
 
+    aw->include_balance_sub_accts = GTK_WIDGET(gtk_builder_get_object (builder, "include_sub_accts_tb"));
+
     aw->more_properties_page =
         gtk_notebook_get_nth_page (GTK_NOTEBOOK(aw->notebook), 1);
 
@@ -2103,14 +2122,6 @@ gnc_ui_edit_account_window (GtkWindow *parent, Account *account)
     if (xaccAccountGetSplitList (account) != NULL)
         gtk_widget_hide (aw->opening_balance_page);
 
-    // Note: we are hiding the notebook page here, when other items
-    // are added this will need adjusting.
-    if (gnc_account_n_children (account) > 0)
-    {
-        gtk_widget_hide (GTK_WIDGET(aw->balance_grid));
-        gtk_widget_hide (GTK_WIDGET(aw->more_properties_page));
-    }
-
     parent_acct = gnc_account_get_parent (account);
     if (parent_acct == NULL)
         parent_acct = account; // must be at the root
diff --git a/gnucash/gtkbuilder/dialog-account.glade b/gnucash/gtkbuilder/dialog-account.glade
index e81dc5c99..cf6f773ee 100644
--- a/gnucash/gtkbuilder/dialog-account.glade
+++ b/gnucash/gtkbuilder/dialog-account.glade
@@ -1691,7 +1691,7 @@
                 <property name="orientation">vertical</property>
                 <property name="spacing">18</property>
                 <child>
-                  <!-- n-columns=2 n-rows=3 -->
+                  <!-- n-columns=2 n-rows=4 -->
                   <object class="GtkGrid" id="balance_grid">
                     <property name="visible">True</property>
                     <property name="can-focus">False</property>
@@ -1783,6 +1783,23 @@ Clear the entry to have no warning.</property>
                         <property name="width">2</property>
                       </packing>
                     </child>
+                    <child>
+                      <object class="GtkCheckButton" id="include_sub_accts_tb">
+                        <property name="label" translatable="yes">_Include sub accounts</property>
+                        <property name="visible">True</property>
+                        <property name="can-focus">True</property>
+                        <property name="receives-default">False</property>
+                        <property name="use-underline">True</property>
+                        <property name="draw-indicator">True</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
                   </object>
                   <packing>
                     <property name="expand">False</property>
diff --git a/libgnucash/app-utils/gnc-ui-balances.c b/libgnucash/app-utils/gnc-ui-balances.c
index 222ba658a..05539e7a8 100644
--- a/libgnucash/app-utils/gnc-ui-balances.c
+++ b/libgnucash/app-utils/gnc-ui-balances.c
@@ -253,9 +253,13 @@ gnc_ui_account_get_reconciled_balance_as_of_date (Account *account,
 static gint
 account_balance_limit_reached (const Account *account, gnc_numeric balance_limit)
 {
+    gboolean inc_sub = xaccAccountGetIncludeSubAccountBalances (account);
+
     // we use today's date because account may have future dated splits
-    gnc_numeric balance = xaccAccountGetBalanceAsOfDate ((Account*)account,
-                              gnc_time64_get_day_end (gnc_time (NULL)));
+    gnc_numeric balance = gnc_ui_account_get_balance_as_of_date (
+                              (Account*)account,
+                              gnc_time64_get_day_end (gnc_time (NULL)),
+                              inc_sub);
 
     if (gnc_numeric_zero_p (balance))
         return 0;
diff --git a/libgnucash/engine/Account.cpp b/libgnucash/engine/Account.cpp
index f6863a0ce..aad4791d2 100644
--- a/libgnucash/engine/Account.cpp
+++ b/libgnucash/engine/Account.cpp
@@ -76,6 +76,7 @@ static const std::string AB_TRANS_RETRIEVAL("trans-retrieval");
 static const std::string KEY_BALANCE_LIMIT("balance-limit");
 static const std::string KEY_BALANCE_HIGHER_LIMIT_VALUE("higher-value");
 static const std::string KEY_BALANCE_LOWER_LIMIT_VALUE("lower-value");
+static const std::string KEY_BALANCE_INCLUDE_SUB_ACCTS("inlude-sub-accts");
 
 static gnc_numeric GetBalanceAsOfDate (Account *acc, time64 date, gboolean ignclosing);
 
@@ -336,6 +337,7 @@ gnc_account_init(Account* acc)
     priv->higher_balance_cached = false;
     priv->lower_balance_limit = gnc_numeric_create (1,0);
     priv->lower_balance_cached = false;
+    priv->include_sub_account_balances = TriState::Unset;
 
     priv->last_num = (char*) is_unset;
     priv->tax_us_code = (char*) is_unset;
@@ -5141,6 +5143,47 @@ xaccAccountClearLowerBalanceLimit (Account *acc)
     clear_balance_limits (acc, false);
 }
 
+gboolean
+xaccAccountGetIncludeSubAccountBalances (const Account *acc)
+{
+    g_return_val_if_fail (GNC_IS_ACCOUNT(acc), false);
+
+    if (GET_PRIVATE(acc)->include_sub_account_balances == TriState::Unset)
+    {
+        gboolean inc_sub = boolean_from_key (acc, {KEY_BALANCE_LIMIT,
+                                                   KEY_BALANCE_INCLUDE_SUB_ACCTS});
+
+        GET_PRIVATE(acc)->include_sub_account_balances = inc_sub ? TriState::True
+                                                                 : TriState::False;
+    }
+    return GET_PRIVATE(acc)->include_sub_account_balances == TriState::True;
+}
+
+void
+xaccAccountSetIncludeSubAccountBalances (Account *acc, gboolean inc_sub)
+{
+    g_return_if_fail (GNC_IS_ACCOUNT(acc));
+
+    if (inc_sub != xaccAccountGetIncludeSubAccountBalances (acc))
+    {
+        GValue v = G_VALUE_INIT;
+        g_value_init (&v, G_TYPE_BOOLEAN);
+        g_value_set_boolean (&v, inc_sub);
+        std::vector<std::string> path {KEY_BALANCE_LIMIT,
+                                       KEY_BALANCE_INCLUDE_SUB_ACCTS};
+        xaccAccountBeginEdit (acc);
+        if (inc_sub)
+            qof_instance_set_path_kvp (QOF_INSTANCE(acc), &v, path);
+        else
+            qof_instance_set_path_kvp (QOF_INSTANCE(acc), nullptr, path);
+        GET_PRIVATE(acc)->include_sub_account_balances =
+                              inc_sub ? TriState::True : TriState::False;
+        mark_account (acc);
+        xaccAccountCommitEdit (acc);
+        g_value_unset (&v);
+    }
+}
+
 /********************************************************************\
 \********************************************************************/
 
diff --git a/libgnucash/engine/Account.h b/libgnucash/engine/Account.h
index d84176d47..cc4fc6bd9 100644
--- a/libgnucash/engine/Account.h
+++ b/libgnucash/engine/Account.h
@@ -1233,6 +1233,22 @@ typedef enum
      *  @param account The account to clear the limit on
      */
     void xaccAccountClearLowerBalanceLimit (Account *account);
+
+    /** Get whether to include balances of sub accounts.
+     *
+     *  @param account The account to get setting on
+     *
+     *  @return TRUE to include, default is FALSE
+     */
+    gboolean xaccAccountGetIncludeSubAccountBalances (const Account *account);
+
+    /** Set whether to include balances of sub accounts.
+     *
+     *  @param account The account to set the setting on
+     *
+     *  @param include Set to TRUE for including sub account balances
+     */
+    void xaccAccountSetIncludeSubAccountBalances (Account *account, gboolean include);
     /** @} */
 
     /** DOCUMENT ME! */
diff --git a/libgnucash/engine/AccountP.h b/libgnucash/engine/AccountP.h
index 68017ba9c..9de148482 100644
--- a/libgnucash/engine/AccountP.h
+++ b/libgnucash/engine/AccountP.h
@@ -125,7 +125,8 @@ typedef struct AccountPrivate
     gboolean    higher_balance_cached;
     gnc_numeric lower_balance_limit;
     gboolean    lower_balance_cached;
-
+    TriState    include_sub_account_balances;
+ 
     gboolean balance_dirty;     /* balances in splits incorrect */
 
     GList *splits;              /* list of split pointers */

commit 841b06940ea846c34653735b2d66ed7e19a4795e
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Tue Dec 13 11:43:25 2022 +0000

    Use new tree view function to display icon in COA

diff --git a/gnucash/gnome-utils/gnc-tree-model-account.c b/gnucash/gnome-utils/gnc-tree-model-account.c
index 9a9cdb870..7f083c434 100644
--- a/gnucash/gnome-utils/gnc-tree-model-account.c
+++ b/gnucash/gnome-utils/gnc-tree-model-account.c
@@ -394,6 +394,7 @@ gnc_tree_model_account_get_column_type (GtkTreeModel *tree_model, int index)
     case GNC_TREE_MODEL_ACCOUNT_COL_BALANCE:
     case GNC_TREE_MODEL_ACCOUNT_COL_BALANCE_REPORT:
     case GNC_TREE_MODEL_ACCOUNT_COL_BALANCE_PERIOD:
+    case GNC_TREE_MODEL_ACCOUNT_COL_BALANCE_LIMIT:
     case GNC_TREE_MODEL_ACCOUNT_COL_CLEARED:
     case GNC_TREE_MODEL_ACCOUNT_COL_CLEARED_REPORT:
     case GNC_TREE_MODEL_ACCOUNT_COL_RECONCILED:
@@ -830,6 +831,17 @@ gnc_tree_model_account_get_value (GtkTreeModel *tree_model,
         g_free (string);
         break;
 
+    case GNC_TREE_MODEL_ACCOUNT_COL_BALANCE_LIMIT:
+        g_value_init (value, G_TYPE_STRING);
+        if (gnc_ui_account_is_higher_balance_limit_reached (account))
+            string = "go-top";
+        else if (gnc_ui_account_is_lower_balance_limit_reached (account))
+            string = "go-bottom";
+        else
+            string = "";
+        g_value_set_string (value, string);
+        break;
+
     case GNC_TREE_MODEL_ACCOUNT_COL_CLEARED:
         g_value_init (value, G_TYPE_STRING);
         string = gnc_ui_account_get_print_balance (xaccAccountGetClearedBalanceInCurrency,
diff --git a/gnucash/gnome-utils/gnc-tree-model-account.h b/gnucash/gnome-utils/gnc-tree-model-account.h
index 177587305..5fcd42836 100644
--- a/gnucash/gnome-utils/gnc-tree-model-account.h
+++ b/gnucash/gnome-utils/gnc-tree-model-account.h
@@ -66,6 +66,7 @@ typedef enum
     GNC_TREE_MODEL_ACCOUNT_COL_BALANCE,
     GNC_TREE_MODEL_ACCOUNT_COL_BALANCE_REPORT,
     GNC_TREE_MODEL_ACCOUNT_COL_BALANCE_PERIOD,
+    GNC_TREE_MODEL_ACCOUNT_COL_BALANCE_LIMIT,
     GNC_TREE_MODEL_ACCOUNT_COL_CLEARED,
     GNC_TREE_MODEL_ACCOUNT_COL_CLEARED_REPORT,
     GNC_TREE_MODEL_ACCOUNT_COL_RECONCILED,
diff --git a/gnucash/gnome-utils/gnc-tree-view-account.c b/gnucash/gnome-utils/gnc-tree-view-account.c
index b5b106f78..dc39c034f 100644
--- a/gnucash/gnome-utils/gnc-tree-view-account.c
+++ b/gnucash/gnome-utils/gnc-tree-view-account.c
@@ -763,7 +763,7 @@ gnc_tree_view_account_new_with_root (Account *root, gboolean show_root)
     GtkTreePath *virtual_root_path = NULL;
     const gchar *sample_type, *sample_commodity;
     GncTreeViewAccountPrivate *priv;
-    GtkTreeViewColumn *tax_info_column, *acc_color_column;
+    GtkTreeViewColumn *tax_info_column, *acc_color_column, *acc_balance_limit_column;
     GtkCellRenderer *renderer;
     GList *col_list = NULL, *node = NULL;
 
@@ -957,6 +957,22 @@ gnc_tree_view_account_new_with_root (Account *root, gboolean show_root)
     /* Also add the full title to the column header as a tooltip */
     gtk_widget_set_tooltip_text (gtk_tree_view_column_get_button (acc_color_column), _("Account Color"));
 
+    acc_balance_limit_column
+        = gnc_tree_view_add_pix_column (view,
+                                        C_("Column header for 'Balance Limit'", "L"),
+                                        "account-balance-limit",
+                                        "xx",
+                                        GNC_TREE_MODEL_ACCOUNT_COL_BALANCE_LIMIT,
+                                        GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS,
+                                        NULL);
+
+    /* Add the full title to the object for menu creation */
+    g_object_set_data_full(G_OBJECT(acc_balance_limit_column), REAL_TITLE,
+                           g_strdup(_("Balance Limit")), g_free);
+
+    /* Also add the full title to the column header as a tooltip */
+    gtk_widget_set_tooltip_text (gtk_tree_view_column_get_button (acc_balance_limit_column), _("Balance Limit"));
+
     priv->notes_column
         = gnc_tree_view_add_text_view_column(view, _("Notes"), "notes", NULL,
                                         "Sample account notes.",

commit 60ab654d2ba0021407a92c5fb877251278da910d
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Tue Dec 13 11:42:40 2022 +0000

    Add account balance test functions to gnc-ui-balance

diff --git a/libgnucash/app-utils/gnc-ui-balances.c b/libgnucash/app-utils/gnc-ui-balances.c
index 9227aefb2..222ba658a 100644
--- a/libgnucash/app-utils/gnc-ui-balances.c
+++ b/libgnucash/app-utils/gnc-ui-balances.c
@@ -250,6 +250,70 @@ gnc_ui_account_get_reconciled_balance_as_of_date (Account *account,
                                            xaccAccountGetReconciledBalanceAsOfDate);
 }
 
+static gint
+account_balance_limit_reached (const Account *account, gnc_numeric balance_limit)
+{
+    // we use today's date because account may have future dated splits
+    gnc_numeric balance = xaccAccountGetBalanceAsOfDate ((Account*)account,
+                              gnc_time64_get_day_end (gnc_time (NULL)));
+
+    if (gnc_numeric_zero_p (balance))
+        return 0;
+
+    if (gnc_reverse_balance (account))
+        balance_limit = gnc_numeric_neg (balance_limit);
+
+    // Returns 1 if a>b, -1 if b>a, 0 if a == b
+    return gnc_numeric_compare (balance, balance_limit);
+}
+
+gboolean
+gnc_ui_account_is_higher_balance_limit_reached (const Account *account)
+{
+    gnc_numeric balance_limit;
+    gboolean limit_valid = FALSE;
+    gboolean retval = FALSE;
+    gint compare_result;
+
+    g_return_val_if_fail (GNC_IS_ACCOUNT(account), FALSE);
+
+    if (gnc_reverse_balance (account))
+        limit_valid = xaccAccountGetLowerBalanceLimit (account, &balance_limit);
+    else
+        limit_valid = xaccAccountGetHigherBalanceLimit (account, &balance_limit);
+
+    if (!limit_valid)
+        return retval;
+
+    if (account_balance_limit_reached (account, balance_limit) == 1)
+        retval = TRUE;
+
+    return retval;
+}
+
+gboolean
+gnc_ui_account_is_lower_balance_limit_reached (const Account *account)
+{
+    gnc_numeric balance_limit;
+    gboolean limit_valid = FALSE;
+    gboolean retval = FALSE;
+    gint compare_result;
+
+    g_return_val_if_fail (GNC_IS_ACCOUNT(account), FALSE);
+
+    if (gnc_reverse_balance (account))
+        limit_valid = xaccAccountGetHigherBalanceLimit (account, &balance_limit);
+    else
+        limit_valid = xaccAccountGetLowerBalanceLimit (account, &balance_limit);
+
+    if (!limit_valid)
+        return retval;
+
+    if (account_balance_limit_reached (account, balance_limit) == -1)
+        retval = TRUE;
+
+    return retval;
+}
 
 /********************************************************************
  * Balance calculations related to owners
diff --git a/libgnucash/app-utils/gnc-ui-balances.h b/libgnucash/app-utils/gnc-ui-balances.h
index 67d5f05e0..2cf0506de 100644
--- a/libgnucash/app-utils/gnc-ui-balances.h
+++ b/libgnucash/app-utils/gnc-ui-balances.h
@@ -154,4 +154,24 @@ gchar * gnc_ui_owner_get_print_report_balance (GncOwner *owner,
  */
 GList * gnc_account_get_autoclear_splits (Account *account, gnc_numeric toclear_value,
                                           gchar **errmsg);
+
+/** Test the account balance as of today for it passing the
+ *  higher limit if set.
+ *
+ *  @param account A pointer to the account.
+ *
+ *  @return TRUE if account balance has passed limit.
+ */
+gboolean gnc_ui_account_is_higher_balance_limit_reached (const Account *account);
+
+/** Test the account balance as of today for it passing the
+ *  lower limit if set.
+ *
+ *  @param account A pointer to the account.
+ *
+ *  @return TRUE if account balance has passed limit.
+ */
+gboolean gnc_ui_account_is_lower_balance_limit_reached (const Account *account);
+
+
 #endif /* GNC_UI_BALANCES_H_ */

commit 5108c52ea40a0cf92bac4304405756db140e71ba
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Tue Dec 13 11:40:18 2022 +0000

    Add new function to gnc-tree-view to display icon column

diff --git a/gnucash/gnome-utils/gnc-tree-view.c b/gnucash/gnome-utils/gnc-tree-view.c
index 201d770e2..994dbdf34 100644
--- a/gnucash/gnome-utils/gnc-tree-view.c
+++ b/gnucash/gnome-utils/gnc-tree-view.c
@@ -1957,6 +1957,63 @@ gnc_tree_view_add_text_view_column (GncTreeView *view,
                                     column_sort_fn);
 }
 
+/** This function adds a new pixbuf view column to a GncTreeView base view.
+ *  It takes all the parameters necessary to hook a GtkTreeModel
+ *  column to a GtkTreeViewColumn.  If the tree has a state section
+ *  associated with it, this function also wires up the column so that
+ *  its visibility and width are remembered.
+ *
+ *  Parameters are defined in gnc-tree-view.h
+ */
+GtkTreeViewColumn *
+gnc_tree_view_add_pix_column (GncTreeView *view,
+                              const gchar *column_title,
+                              const gchar *pref_name,
+                              const gchar *sizing_text,
+                              gint model_data_column,
+                              gint model_visibility_column,
+                              GtkTreeIterCompareFunc column_sort_fn)
+{
+    GtkTreeViewColumn *column;
+    PangoLayout* layout;
+    int default_width, title_width;
+    GtkCellRenderer *renderer;
+
+    g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
+
+    renderer = gtk_cell_renderer_pixbuf_new ();
+
+    column = gtk_tree_view_column_new ();
+    gtk_tree_view_column_set_title (column, column_title);
+
+    /* Set up a text renderer and attributes */
+    gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+    /* Set renderer attributes controlled by the model */
+    if (model_data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
+        gtk_tree_view_column_add_attribute (column, renderer,
+                                            "icon-name", model_data_column);
+    if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
+        gtk_tree_view_column_add_attribute (column, renderer,
+                                            "visible", model_visibility_column);
+
+    /* Default size is the larger of the column title and the sizing text */
+    layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), column_title);
+    pango_layout_get_pixel_size (layout, &title_width, NULL);
+    g_object_unref (layout);
+    layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), sizing_text);
+    pango_layout_get_pixel_size (layout, &default_width, NULL);
+    g_object_unref (layout);
+    default_width = MAX(default_width, title_width);
+    if (default_width)
+        default_width += 10; /* padding on either side */
+    gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
+                                     default_width, TRUE, column_sort_fn);
+
+    gnc_tree_view_append_column (view, column);
+    return column;
+}
+
 
 /** This function adds a new date column to a GncTreeView base view.
  *  It takes all the parameters necessary to hook a GtkTreeModel
diff --git a/gnucash/gnome-utils/gnc-tree-view.h b/gnucash/gnome-utils/gnc-tree-view.h
index ee2d1eaa3..9c8928064 100644
--- a/gnucash/gnome-utils/gnc-tree-view.h
+++ b/gnucash/gnome-utils/gnc-tree-view.h
@@ -246,6 +246,47 @@ gnc_tree_view_add_text_view_column (GncTreeView *view,
                                gint model_visibility_column,
                                GtkTreeIterCompareFunc column_sort_fn);
 
+/** This function adds a pixbuf view column to a GncTreeView base view.
+ *  It takes all the parameters necessary to hook a GtkTreeModel
+ *  column to a GtkTreeViewColumn.
+ *
+ *  @param view A pointer to a generic GncTreeView.
+ *
+ *  @param column_title The title for this column.
+ *
+ *  @param pref_name The internal name of this column.  This name is
+ *  used in several functions to look up the column, and it is also
+ *  used when saving/restoring the view's state.
+ *
+ *  @param sizing_text A string used to compute the default width of
+ *  the column.  This text is never displayed.
+ *
+ *  @param model_data_column The index of the GtkTreeModel data column
+ *  used to determine the data that will be displayed in this column
+ *  for each row in the view.  Use GNC_TREE_VIEW_COLUMN_DATA_NONE if
+ *  you plan on using a non-model data source for this column.  This
+ *  index is connected to the "icon-name" attribute of the cell renderer.
+ *
+ *  @param model_visibility_column The index of the GtkTreeModel data
+ *  column used to determine whether or not a checkbox for each row
+ *  will be displayed at all.  Use GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS
+ *  if the checkbox should be displayed in all rows.
+ *
+ *  @param column_sort_fn The function that GtkTreeModelSort
+ *  will call to compare two rows to determine their displayed
+ *  order.
+ *
+ *  @return The newly created GtkTreeViewColumn.
+ */
+GtkTreeViewColumn *
+gnc_tree_view_add_pix_column (GncTreeView *view,
+                              const gchar *column_title,
+                              const gchar *pref_name,
+                              const gchar *sizing_text,
+                              gint model_data_column,
+                              gint model_visibility_column,
+                              GtkTreeIterCompareFunc column_sort_fn);
+
 /** This function adds a new combobox column to a GncTreeView base
  *  view.  The parameters it takes in common with
  *  gnc_tree_view_add_text_column() behave the same as there.  In

commit fb84cf7d1668c0574fc9ec82f31269fb2b3984d6
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Tue Dec 13 11:39:47 2022 +0000

    Add entry to dialog-account to set account balance limits

diff --git a/gnucash/gnome-utils/dialog-account.c b/gnucash/gnome-utils/dialog-account.c
index 63ab3808a..be426f1b9 100644
--- a/gnucash/gnome-utils/dialog-account.c
+++ b/gnucash/gnome-utils/dialog-account.c
@@ -106,6 +106,13 @@ typedef struct _AccountWindow
     GtkTreeView   *parent_tree;
     GtkWidget     *parent_scroll;
 
+    GtkWidget *more_properties_page;
+
+    GtkWidget *balance_grid;
+    GtkWidget *higher_balance_limit_edit;
+    GtkWidget *lower_balance_limit_edit;
+    gboolean   balance_is_reversed;
+
     GtkWidget *opening_balance_button;
     GtkWidget *opening_balance_edit;
     GtkWidget *opening_balance_date_edit;
@@ -250,6 +257,8 @@ gnc_account_to_ui (AccountWindow *aw)
     GdkRGBA color;
     gboolean flag, nonstd_scu;
     gint index;
+    gnc_numeric balance_limit;
+    gboolean    balance_limit_valid;
 
     ENTER("%p", aw);
     account = aw_get_account (aw);
@@ -325,6 +334,36 @@ gnc_account_to_ui (AccountWindow *aw)
     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(aw->hidden_button),
                                   flag);
 
+    aw->balance_is_reversed = gnc_reverse_balance (account);
+
+    balance_limit_valid = xaccAccountGetHigherBalanceLimit (account, &balance_limit);
+    if (balance_limit_valid)
+    {
+        if (aw->balance_is_reversed)
+        {
+            balance_limit = gnc_numeric_neg (balance_limit);
+            gnc_amount_edit_set_amount (GNC_AMOUNT_EDIT(aw->lower_balance_limit_edit),
+                                                        balance_limit);
+        }
+        else
+            gnc_amount_edit_set_amount (GNC_AMOUNT_EDIT(aw->higher_balance_limit_edit),
+                                                        balance_limit);
+    }
+
+    balance_limit_valid = xaccAccountGetLowerBalanceLimit (account, &balance_limit);
+    if (balance_limit_valid)
+    {
+        if (aw->balance_is_reversed)
+        {
+            balance_limit = gnc_numeric_neg (balance_limit);
+            gnc_amount_edit_set_amount (GNC_AMOUNT_EDIT(aw->higher_balance_limit_edit),
+                                                        balance_limit);
+        }
+        else
+            gnc_amount_edit_set_amount (GNC_AMOUNT_EDIT(aw->lower_balance_limit_edit),
+                                                        balance_limit);
+    }
+
     set_auto_interest_box (aw);
     LEAVE(" ");
 }
@@ -393,6 +432,8 @@ gnc_ui_to_account (AccountWindow *aw)
     GdkRGBA color;
     gboolean flag;
     gnc_numeric balance;
+    gnc_numeric balance_limit;
+    gint balance_limit_valid;
     gboolean use_equity, nonstd;
     time64 date;
     gint index, old_scu, new_scu;
@@ -505,6 +546,50 @@ gnc_ui_to_account (AccountWindow *aw)
     if (parent_account != gnc_account_get_parent (account))
         gnc_account_append_child (parent_account, account);
 
+    balance_limit_valid = gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(aw->higher_balance_limit_edit),
+                                                         &balance_limit, TRUE, NULL);
+
+    if (balance_limit_valid == 0)
+    {
+        if (aw->balance_is_reversed)
+        {
+            balance_limit = gnc_numeric_neg (balance_limit);
+            xaccAccountSetLowerBalanceLimit (account, balance_limit);
+        }
+        else
+            xaccAccountSetHigherBalanceLimit (account, balance_limit);
+    }
+
+    if (balance_limit_valid == -1)
+    {
+        if (aw->balance_is_reversed)
+            xaccAccountClearLowerBalanceLimit (account);
+        else
+            xaccAccountClearHigherBalanceLimit (account);
+    }
+
+    balance_limit_valid = gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(aw->lower_balance_limit_edit),
+                                                         &balance_limit, TRUE, NULL);
+
+    if (balance_limit_valid == 0)
+    {
+        if (aw->balance_is_reversed)
+        {
+            balance_limit = gnc_numeric_neg (balance_limit);
+            xaccAccountSetHigherBalanceLimit (account, balance_limit);
+        }
+        else
+            xaccAccountSetLowerBalanceLimit (account, balance_limit);
+    }
+
+    if (balance_limit_valid == -1)
+    {
+        if (aw->balance_is_reversed)
+            xaccAccountClearHigherBalanceLimit (account);
+        else
+            xaccAccountClearLowerBalanceLimit (account);
+    }
+
     xaccAccountCommitEdit (account);
 
     balance = gnc_amount_edit_get_amount (GNC_AMOUNT_EDIT(aw->opening_balance_edit));
@@ -811,6 +896,10 @@ gnc_common_ok (AccountWindow *aw)
     gnc_commodity * commodity;
     gchar *fullname, *fullname_parent;
     const gchar *name, *separator;
+    gboolean higher_limit_valid;
+    gnc_numeric higher_balance_limit;
+    gboolean lower_limit_valid;
+    gnc_numeric lower_balance_limit;
 
     ENTER("aw %p", aw);
     root = gnc_book_get_root_account (aw->book);
@@ -892,6 +981,34 @@ gnc_common_ok (AccountWindow *aw)
         return FALSE;
     }
 
+    /* check for higher balance limit greater than lower */
+    higher_limit_valid = gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(aw->higher_balance_limit_edit),
+                                                        &higher_balance_limit, TRUE, NULL);
+
+    lower_limit_valid = gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(aw->lower_balance_limit_edit),
+                                                       &lower_balance_limit, TRUE, NULL);
+
+    if ((lower_limit_valid == 0) && (higher_limit_valid == 0))
+    {
+        gint compare = gnc_numeric_compare (higher_balance_limit,
+                                            lower_balance_limit);
+
+        if ((compare == 0) && (!gnc_numeric_zero_p (higher_balance_limit)))
+        {
+            const char *message = _("Balance limits must be different unless they are both zero.");
+            gnc_error_dialog (GTK_WINDOW(aw->dialog), "%s", message);
+            LEAVE("invalid balance limit, both the same but not zero");
+            return FALSE;
+        }
+        else if (compare == -1)
+        {
+            const char *message = _("The lower balance limit must be less than the higher limit.");
+            gnc_error_dialog (GTK_WINDOW(aw->dialog), "%s", message);
+            LEAVE("invalid balance limit, lower limit not less than upper");
+            return FALSE;
+        }
+    }
+
     LEAVE("passed");
     return TRUE;
 }
@@ -1483,6 +1600,27 @@ gnc_account_window_create (GtkWindow *parent, AccountWindow *aw)
     g_signal_connect (G_OBJECT(selection), "changed",
                       G_CALLBACK(gnc_account_parent_changed_cb), aw);
 
+    aw->balance_grid = GTK_WIDGET(gtk_builder_get_object (builder, "balance_grid"));
+
+    box  = GTK_WIDGET(gtk_builder_get_object (builder, "higher_balance_limit_hbox"));
+    aw->higher_balance_limit_edit = gnc_amount_edit_new ();
+    gtk_box_pack_start (GTK_BOX(box), aw->higher_balance_limit_edit, TRUE, TRUE, 0);
+    gnc_amount_edit_set_evaluate_on_enter (GNC_AMOUNT_EDIT(aw->higher_balance_limit_edit), TRUE);
+    gnc_amount_edit_set_validate_on_change (GNC_AMOUNT_EDIT(aw->higher_balance_limit_edit), TRUE);
+    gnc_amount_edit_show_warning_symbol (GNC_AMOUNT_EDIT(aw->higher_balance_limit_edit), TRUE);
+    gtk_widget_show (aw->higher_balance_limit_edit);
+
+    box  = GTK_WIDGET(gtk_builder_get_object (builder, "lower_balance_limit_hbox"));
+    aw->lower_balance_limit_edit = gnc_amount_edit_new ();
+    gtk_box_pack_start (GTK_BOX(box), aw->lower_balance_limit_edit, TRUE, TRUE, 0);
+    gnc_amount_edit_set_evaluate_on_enter (GNC_AMOUNT_EDIT(aw->lower_balance_limit_edit), TRUE);
+    gnc_amount_edit_set_validate_on_change (GNC_AMOUNT_EDIT(aw->lower_balance_limit_edit), TRUE);
+    gnc_amount_edit_show_warning_symbol (GNC_AMOUNT_EDIT(aw->lower_balance_limit_edit), TRUE);
+    gtk_widget_show (aw->lower_balance_limit_edit);
+
+    aw->more_properties_page =
+        gtk_notebook_get_nth_page (GTK_NOTEBOOK(aw->notebook), 1);
+
     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"));
@@ -1510,7 +1648,7 @@ gnc_account_window_create (GtkWindow *parent, AccountWindow *aw)
     gtk_widget_show (date_edit);
 
     aw->opening_balance_page =
-        gtk_notebook_get_nth_page (GTK_NOTEBOOK(aw->notebook), 1);
+        gtk_notebook_get_nth_page (GTK_NOTEBOOK(aw->notebook), 2);
 
     aw->opening_equity_radio = GTK_WIDGET(gtk_builder_get_object (builder,
                                           "opening_equity_radio"));
@@ -1965,6 +2103,14 @@ gnc_ui_edit_account_window (GtkWindow *parent, Account *account)
     if (xaccAccountGetSplitList (account) != NULL)
         gtk_widget_hide (aw->opening_balance_page);
 
+    // Note: we are hiding the notebook page here, when other items
+    // are added this will need adjusting.
+    if (gnc_account_n_children (account) > 0)
+    {
+        gtk_widget_hide (GTK_WIDGET(aw->balance_grid));
+        gtk_widget_hide (GTK_WIDGET(aw->more_properties_page));
+    }
+
     parent_acct = gnc_account_get_parent (account);
     if (parent_acct == NULL)
         parent_acct = account; // must be at the root
diff --git a/gnucash/gtkbuilder/dialog-account.glade b/gnucash/gtkbuilder/dialog-account.glade
index 3447f545e..e81dc5c99 100644
--- a/gnucash/gtkbuilder/dialog-account.glade
+++ b/gnucash/gtkbuilder/dialog-account.glade
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.38.2 -->
+<!-- Generated with glade 3.40.0 -->
 <interface>
   <requires lib="gtk+" version="3.22"/>
   <object class="GtkDialog" id="account_cascade_dialog">
@@ -1682,7 +1682,138 @@
               </packing>
             </child>
             <child>
-              <object class="GtkBox" id="vbox302">
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
+                <property name="border-width">6</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">18</property>
+                <child>
+                  <!-- n-columns=2 n-rows=3 -->
+                  <object class="GtkGrid" id="balance_grid">
+                    <property name="visible">True</property>
+                    <property name="can-focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="row-spacing">3</property>
+                    <property name="column-spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="higher_balance_label">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="halign">end</property>
+                        <property name="label" translatable="yes">_Higher Balance Limit</property>
+                        <property name="use-underline">True</property>
+                        <property name="mnemonic-widget">higher_balance_limit_hbox</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="higher_balance_limit_hbox">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="tooltip-text" translatable="yes">If a value is present, an indication can be shown in the chart of accounts when today's balance is more than this value.
+
+i.e.
+Today's balance of -90 will show icon if limit is set to -100
+Today's balance of 100 will show icon if limit is set to 90
+
+Clear the entry to have no warning.</property>
+                        <property name="hexpand">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="lower_balance_limit_hbox">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="tooltip-text" translatable="yes">If a value is present, an indication can be shown in the chart of accounts when today's balance is less than this value.
+
+i.e.
+Today's balance of -100 will show icon if limit is set to -90
+Today's balance of 90 will show icon if limit is set to 100
+
+Clear the entry to have no warning.</property>
+                        <property name="hexpand">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="lower_balance_label">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="halign">end</property>
+                        <property name="label" translatable="yes">_Lower Balance Limit</property>
+                        <property name="use-underline">True</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes">Balance Limit</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">0</property>
+                        <property name="width">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <style>
+                  <class name="gnc-class-account"/>
+                </style>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child type="tab">
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="label" translatable="yes">More Properties</property>
+              </object>
+              <packing>
+                <property name="position">1</property>
+                <property name="tab-fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="vbox1">
                 <property name="visible">True</property>
                 <property name="can-focus">False</property>
                 <property name="border-width">6</property>
@@ -1857,7 +1988,7 @@
                 </style>
               </object>
               <packing>
-                <property name="position">1</property>
+                <property name="position">2</property>
               </packing>
             </child>
             <child type="tab">
@@ -1868,7 +1999,7 @@
                 <property name="justify">center</property>
               </object>
               <packing>
-                <property name="position">1</property>
+                <property name="position">2</property>
                 <property name="tab-fill">False</property>
               </packing>
             </child>

commit 518ecfe04146ed4b60a1463d520bcd2bc20dc567
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Fri Dec 9 10:10:59 2022 +0000

    Add some functions that would allow adding a balance limit to accounts

diff --git a/libgnucash/engine/Account.cpp b/libgnucash/engine/Account.cpp
index d00e1aa52..f6863a0ce 100644
--- a/libgnucash/engine/Account.cpp
+++ b/libgnucash/engine/Account.cpp
@@ -73,6 +73,10 @@ static const std::string AB_ACCOUNT_UID("account-uid");
 static const std::string AB_BANK_CODE("bank-code");
 static const std::string AB_TRANS_RETRIEVAL("trans-retrieval");
 
+static const std::string KEY_BALANCE_LIMIT("balance-limit");
+static const std::string KEY_BALANCE_HIGHER_LIMIT_VALUE("higher-value");
+static const std::string KEY_BALANCE_LOWER_LIMIT_VALUE("lower-value");
+
 static gnc_numeric GetBalanceAsOfDate (Account *acc, time64 date, gboolean ignclosing);
 
 using FinalProbabilityVec=std::vector<std::pair<std::string, int32_t>>;
@@ -328,6 +332,11 @@ gnc_account_init(Account* acc)
     priv->starting_reconciled_balance = gnc_numeric_zero();
     priv->balance_dirty = FALSE;
 
+    priv->higher_balance_limit = gnc_numeric_create (1,0);
+    priv->higher_balance_cached = false;
+    priv->lower_balance_limit = gnc_numeric_create (1,0);
+    priv->lower_balance_cached = false;
+
     priv->last_num = (char*) is_unset;
     priv->tax_us_code = (char*) is_unset;
     priv->tax_us_pns = (char*) is_unset;
@@ -4930,6 +4939,211 @@ xaccAccountSetLastNum (Account *acc, const char *num)
     set_kvp_string_tag (acc, "last-num", priv->last_num);
 }
 
+
+/********************************************************************\
+\********************************************************************/
+
+gboolean
+xaccAccountGetHigherBalanceLimit (const Account *acc,
+                                  gnc_numeric *balance)
+{
+    g_return_val_if_fail (GNC_IS_ACCOUNT(acc), false);
+
+    if (GET_PRIVATE(acc)->higher_balance_cached)
+    {
+        *balance = GET_PRIVATE(acc)->higher_balance_limit;
+
+        if (gnc_numeric_check (*balance) == 0)
+            return true;
+        else
+            return false;
+    }
+    else
+    {
+        gnc_numeric bal = gnc_numeric_create (1,0);
+        GValue v = G_VALUE_INIT;
+        gboolean retval = false;
+
+        qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v, {KEY_BALANCE_LIMIT,
+                                                           KEY_BALANCE_HIGHER_LIMIT_VALUE});
+        if (G_VALUE_HOLDS_BOXED(&v))
+        {
+            bal = *(gnc_numeric*)g_value_get_boxed (&v);
+            if (bal.denom)
+            {
+                if (balance)
+                   *balance = bal;
+                retval = true;
+            }
+        }
+        g_value_unset (&v);
+
+        GET_PRIVATE(acc)->higher_balance_limit = bal;
+        GET_PRIVATE(acc)->higher_balance_cached = true;
+        return retval;
+    }
+}
+
+gboolean
+xaccAccountGetLowerBalanceLimit (const Account *acc,
+                                 gnc_numeric *balance)
+{
+    g_return_val_if_fail (GNC_IS_ACCOUNT(acc), false);
+
+    if (GET_PRIVATE(acc)->lower_balance_cached)
+    {
+        *balance = GET_PRIVATE(acc)->lower_balance_limit;
+
+        if (gnc_numeric_check (*balance) == 0)
+            return true;
+        else
+            return false;
+    }
+    else
+    {
+        gnc_numeric bal = gnc_numeric_create (1,0);
+        GValue v = G_VALUE_INIT;
+        gboolean retval = false;
+
+        qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v, {KEY_BALANCE_LIMIT,
+                                                           KEY_BALANCE_LOWER_LIMIT_VALUE});
+        if (G_VALUE_HOLDS_BOXED(&v))
+        {
+            bal = *(gnc_numeric*)g_value_get_boxed (&v);
+            if (bal.denom)
+            {
+                if (balance)
+                   *balance = bal;
+                retval = true;
+            }
+        }
+        g_value_unset (&v);
+
+        GET_PRIVATE(acc)->lower_balance_limit = bal;
+        GET_PRIVATE(acc)->lower_balance_cached = true;
+        return retval;
+    }
+}
+
+
+static void
+set_balance_limits (Account *acc, gnc_numeric balance, gboolean higher)
+{
+    gnc_numeric balance_limit;
+    gboolean balance_limit_valid;
+    std::vector<std::string> path {KEY_BALANCE_LIMIT};
+
+    if (higher)
+    {
+        path.push_back (KEY_BALANCE_HIGHER_LIMIT_VALUE);
+        balance_limit_valid = xaccAccountGetHigherBalanceLimit (acc, &balance_limit);
+    }
+    else
+    {
+        path.push_back (KEY_BALANCE_LOWER_LIMIT_VALUE);
+        balance_limit_valid = xaccAccountGetLowerBalanceLimit (acc, &balance_limit);
+    }
+
+    if (!balance_limit_valid  || gnc_numeric_compare (balance, balance_limit) != 0)
+    {
+        GValue v = G_VALUE_INIT;
+        g_value_init (&v, GNC_TYPE_NUMERIC);
+        g_value_set_boxed (&v, &balance);
+        xaccAccountBeginEdit (acc);
+
+        qof_instance_set_path_kvp (QOF_INSTANCE(acc), &v, path);
+        if (higher)
+        {
+            GET_PRIVATE(acc)->higher_balance_limit.denom = balance.denom;
+            GET_PRIVATE(acc)->higher_balance_limit.num = balance.num;
+            GET_PRIVATE(acc)->higher_balance_cached = true;
+        }
+        else
+        {
+            GET_PRIVATE(acc)->lower_balance_limit.denom = balance.denom;
+            GET_PRIVATE(acc)->lower_balance_limit.num = balance.num;
+            GET_PRIVATE(acc)->lower_balance_cached = true;
+        }
+        mark_account (acc);
+        xaccAccountCommitEdit (acc);
+        g_value_unset (&v);
+    }
+}
+
+void
+xaccAccountSetHigherBalanceLimit (Account *acc, gnc_numeric balance)
+{
+    g_return_if_fail (GNC_IS_ACCOUNT(acc));
+
+    if (gnc_numeric_check (balance) != 0)
+        return;
+
+    set_balance_limits (acc, balance, true);
+}
+
+void
+xaccAccountSetLowerBalanceLimit (Account *acc, gnc_numeric balance)
+{
+    g_return_if_fail (GNC_IS_ACCOUNT(acc));
+
+    if (gnc_numeric_check (balance) != 0)
+        return;
+
+    set_balance_limits (acc, balance, false);
+}
+
+
+static void
+clear_balance_limits (Account *acc, gboolean higher)
+{
+    gnc_numeric balance_limit;
+    gboolean balance_limit_valid;
+    std::vector<std::string> path {KEY_BALANCE_LIMIT};
+
+    if (higher)
+    {
+        path.push_back (KEY_BALANCE_HIGHER_LIMIT_VALUE);
+        balance_limit_valid = xaccAccountGetHigherBalanceLimit (acc, &balance_limit);
+    }
+    else
+    {
+        path.push_back (KEY_BALANCE_LOWER_LIMIT_VALUE);
+        balance_limit_valid = xaccAccountGetLowerBalanceLimit (acc, &balance_limit);
+    }
+
+    if (balance_limit_valid)
+    {
+        xaccAccountBeginEdit (acc);
+        qof_instance_set_path_kvp (QOF_INSTANCE(acc), nullptr, path);
+        qof_instance_slot_path_delete_if_empty (QOF_INSTANCE(acc), {KEY_BALANCE_LIMIT});
+        if (higher)
+            GET_PRIVATE(acc)->higher_balance_cached = false;
+        else
+            GET_PRIVATE(acc)->lower_balance_cached = false;
+        mark_account (acc);
+        xaccAccountCommitEdit (acc);
+    }
+}
+
+void
+xaccAccountClearHigherBalanceLimit (Account *acc)
+{
+    g_return_if_fail (GNC_IS_ACCOUNT(acc));
+
+    clear_balance_limits (acc, true);
+}
+
+void
+xaccAccountClearLowerBalanceLimit (Account *acc)
+{
+    g_return_if_fail (GNC_IS_ACCOUNT(acc));
+
+    clear_balance_limits (acc, false);
+}
+
+/********************************************************************\
+\********************************************************************/
+
 static Account *
 GetOrMakeOrphanAccount (Account *root, gnc_commodity * currency)
 {
diff --git a/libgnucash/engine/Account.h b/libgnucash/engine/Account.h
index 3fac9bdbb..d84176d47 100644
--- a/libgnucash/engine/Account.h
+++ b/libgnucash/engine/Account.h
@@ -1182,6 +1182,58 @@ typedef enum
     void xaccAccountClearReconcilePostpone (Account *account);
     /** @} */
 
+    /** @name Account Balance Limits
+     @{
+    */
+
+    /** Get the higher balance limit for the account.
+     *
+     *  @param account The account whose higher limit is to be retrieved
+     *
+     *  @param balance The placeholder to store the retrieved balance
+     *
+     *  @return True if the limit is valid. */
+    gboolean xaccAccountGetHigherBalanceLimit (const Account *account,
+                                               gnc_numeric *balance);
+
+    /** Set the higher balance limit for the account.
+     *
+     *  @param account The account whose higher limit is to be saved
+     *
+     *  @param balance The balance to be saved
+     */
+    void xaccAccountSetHigherBalanceLimit (Account *account, gnc_numeric balance);
+
+    /** Clear the higher balance limit for the account.
+     *
+     *  @param account The account to clear the limit on
+     */
+    void xaccAccountClearHigherBalanceLimit (Account *account);
+
+    /** Get the lower balance limit for the account.
+     *
+     *  @param account The account whose lower limit is to be retrieved
+     *
+     *  @param balance The placeholder to store the retrieved balance
+     *
+     *  @return True if the limit is valid. */
+    gboolean xaccAccountGetLowerBalanceLimit (const Account *account,
+                                              gnc_numeric *balance);
+
+    /** Set the lower balance limit for the account.
+     *
+     *  @param account The account whose lower limit is to be saved
+     *
+     *  @param balance The balance to be saved
+     */
+    void xaccAccountSetLowerBalanceLimit (Account *account, gnc_numeric balance);
+
+    /** Clear the lower balance limit for the account.
+     *
+     *  @param account The account to clear the limit on
+     */
+    void xaccAccountClearLowerBalanceLimit (Account *account);
+    /** @} */
 
     /** DOCUMENT ME! */
     typedef enum
diff --git a/libgnucash/engine/AccountP.h b/libgnucash/engine/AccountP.h
index 14faeb6c0..68017ba9c 100644
--- a/libgnucash/engine/AccountP.h
+++ b/libgnucash/engine/AccountP.h
@@ -121,6 +121,11 @@ typedef struct AccountPrivate
     gnc_numeric cleared_balance;
     gnc_numeric reconciled_balance;
 
+    gnc_numeric higher_balance_limit;
+    gboolean    higher_balance_cached;
+    gnc_numeric lower_balance_limit;
+    gboolean    lower_balance_cached;
+
     gboolean balance_dirty;     /* balances in splits incorrect */
 
     GList *splits;              /* list of split pointers */



Summary of changes:
 gnucash/gnome-utils/dialog-account.c         | 159 ++++++++++++++++-
 gnucash/gnome-utils/gnc-tree-model-account.c |   7 +
 gnucash/gnome-utils/gnc-tree-model-account.h |   1 +
 gnucash/gnome-utils/gnc-tree-view-account.c  |  18 +-
 gnucash/gnome-utils/gnc-tree-view.c          |  57 ++++++
 gnucash/gnome-utils/gnc-tree-view.h          |  41 +++++
 gnucash/gtkbuilder/dialog-account.glade      | 156 +++++++++++++++-
 libgnucash/app-utils/gnc-ui-balances.c       | 105 +++++++++++
 libgnucash/app-utils/gnc-ui-balances.h       |  32 ++++
 libgnucash/engine/Account.cpp                | 257 +++++++++++++++++++++++++++
 libgnucash/engine/Account.h                  |  68 +++++++
 libgnucash/engine/AccountP.h                 |   6 +
 libgnucash/engine/test/utest-Account.cpp     |  29 +++
 13 files changed, 930 insertions(+), 6 deletions(-)



More information about the gnucash-changes mailing list