gnucash master: Multiple changes pushed

Christopher Lam clam at code.gnucash.org
Fri Dec 3 06:13:29 EST 2021


Updated	 via  https://github.com/Gnucash/gnucash/commit/9d1c73f9 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/91b1d291 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/972fd452 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/ab8277e9 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/329ca0e4 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/73822f97 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/99c2c82c (commit)
	 via  https://github.com/Gnucash/gnucash/commit/6070aecd (commit)
	 via  https://github.com/Gnucash/gnucash/commit/5dab3369 (commit)
	from  https://github.com/Gnucash/gnucash/commit/88ecf8dd (commit)



commit 9d1c73f91a62e1184565a198dfdd8ee4830ddc03
Merge: 88ecf8dd1 91b1d291a
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Fri Dec 3 17:41:33 2021 +0800

    Merge branch 'master-fix-budget-signs' PR #585


commit 91b1d291a54e3ea44b3bb82fa174c57cd0029c82
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Thu Dec 2 22:28:49 2021 +0800

    [3/3][gnc-ui-util.c] Remove unused functions
    
    These functions were used to (optionally) reverse amounts according to
    whether the feature GNC_FEATURE_BUDGET_UNREVERSED was set. They are
    now obsolete because code will now assume feature is set for all
    loaded datafiles.

diff --git a/libgnucash/app-utils/app-utils.i b/libgnucash/app-utils/app-utils.i
index 28730bc61..31d5066b2 100644
--- a/libgnucash/app-utils/app-utils.i
+++ b/libgnucash/app-utils/app-utils.i
@@ -108,8 +108,6 @@ const char * xaccPrintAmount (gnc_numeric val, GNCPrintAmountInfo info);
 gchar *number_to_words(gdouble val, gint64 denom);
 const gchar *printable_value (gdouble val, gint denom);
 
-gboolean gnc_using_unreversed_budgets (QofBook* book);
-gboolean gnc_reverse_budget_balance (const Account *account, gboolean unreversed);
 gboolean gnc_reverse_balance (const Account *account);
 
 gboolean gnc_is_euro_currency(const gnc_commodity * currency);
diff --git a/libgnucash/app-utils/gnc-ui-util.c b/libgnucash/app-utils/gnc-ui-util.c
index aea6bbba3..86ded4791 100644
--- a/libgnucash/app-utils/gnc-ui-util.c
+++ b/libgnucash/app-utils/gnc-ui-util.c
@@ -177,23 +177,6 @@ gnc_reverse_balance (const Account *account)
     return reverse_type[type];
 }
 
-gboolean gnc_using_unreversed_budgets (QofBook* book)
-{
-    return gnc_features_check_used (book, GNC_FEATURE_BUDGET_UNREVERSED);
-}
-
-/* similar to gnc_reverse_balance but also accepts a gboolean
-   unreversed which specifies the reversal strategy - FALSE = pre-4.x
-   always-assume-credit-accounts, TRUE = all amounts unreversed */
-gboolean
-gnc_reverse_budget_balance (const Account *account, gboolean unreversed)
-{
-    if (unreversed == gnc_using_unreversed_budgets(gnc_account_get_book(account)))
-        return gnc_reverse_balance (account);
-
-    return FALSE;
-}
-
 gboolean gnc_using_equity_type_opening_balance_account (QofBook* book)
 {
     return gnc_features_check_used (book, GNC_FEATURE_EQUITY_TYPE_OPENING_BALANCE);
diff --git a/libgnucash/app-utils/gnc-ui-util.h b/libgnucash/app-utils/gnc-ui-util.h
index b9bb385c6..b6627e09d 100644
--- a/libgnucash/app-utils/gnc-ui-util.h
+++ b/libgnucash/app-utils/gnc-ui-util.h
@@ -46,16 +46,6 @@ typedef QofSession * (*QofSessionCB) (void);
 gchar *gnc_normalize_account_separator (const gchar* separator);
 gboolean gnc_reverse_balance(const Account *account);
 
-/* Backward compatibility *******************************************
- * Return book's UNREVERSED_BUDGET feature check. */
-gboolean gnc_using_unreversed_budgets (QofBook* book);
-
-/* Backward compatibility *******************************************
- * Compare book's UNREVERSED_BUDGET with unreverse_check. If they
- * match, return account reversal according to global pref. If they
- * don't match, return FALSE. */
-gboolean gnc_reverse_budget_balance (const Account *account, gboolean unreversed);
-
 /* Backward compatibility *******************************************
  * Return that book's support opening balance accounts by equity type slot */
 void gnc_set_use_equity_type_opening_balance_account (QofBook* book);

commit 972fd452025f68e917bfe72549fae7086ee2dc14
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Thu Dec 2 22:31:29 2021 +0800

    [2/3][budget editor] remove backward-compatibility code
    
    Previously the budget editor would sign-reverse budget amounts for
    some account types prior to saving in datafile. This code is now
    obsolete. Sign reversal is now a display mechanic in the budget viewer
    only.

diff --git a/gnucash/gnome/gnc-budget-view.c b/gnucash/gnome/gnc-budget-view.c
index c9a1e4672..05a037771 100644
--- a/gnucash/gnome/gnc-budget-view.c
+++ b/gnucash/gnome/gnc-budget-view.c
@@ -1077,8 +1077,7 @@ gbv_get_accumulated_budget_amount (GncBudget *budget, Account *account, guint pe
     else
         info.total = gnc_budget_get_account_period_value (budget, account, period_num);
 
-    if (gnc_reverse_budget_balance (account, TRUE))
-        info.total = gnc_numeric_neg (info.total);
+    info.total = gnc_numeric_neg (info.total);
 
     return info.total;
 }
@@ -1141,8 +1140,7 @@ budget_col_source (Account *account, GtkTreeViewColumn *col,
             strcpy (amtbuff, "error");
         else
         {
-            if (gnc_reverse_budget_balance (account, TRUE))
-                numeric = gnc_numeric_neg (numeric);
+            numeric = gnc_numeric_neg (numeric);
 
             xaccSPrintAmount (amtbuff, numeric,
                               gnc_account_print_info (account, FALSE));
@@ -1192,8 +1190,7 @@ bgv_get_total_for_account (Account *account, GncBudget *budget, gnc_commodity *n
             {
                 numeric = gbv_get_accumulated_budget_amount (budget, account, period_num);
 
-                if (gnc_reverse_budget_balance (account, TRUE))
-                    numeric = gnc_numeric_neg (numeric);
+                numeric = gnc_numeric_neg (numeric);
 
                 if (new_currency)
                 {
@@ -1220,8 +1217,7 @@ bgv_get_total_for_account (Account *account, GncBudget *budget, gnc_commodity *n
         }
     }
 
-    if (gnc_reverse_budget_balance (account, TRUE))
-        total = gnc_numeric_neg (total);
+    total = gnc_numeric_neg (total);
 
     return total;
 }
@@ -1284,8 +1280,7 @@ budget_col_edited (Account *account, GtkTreeViewColumn *col,
         gnc_budget_unset_account_period_value (priv->budget, account, period_num);
     else
     {
-        if (gnc_reverse_budget_balance (account, TRUE))
-            numeric = gnc_numeric_neg (numeric);
+        numeric = gnc_numeric_neg (numeric);
         gnc_budget_set_account_period_value (priv->budget, account, period_num,
                                              numeric);
     }
@@ -1342,65 +1337,31 @@ totals_col_source (GtkTreeViewColumn *col, GtkCellRenderer *cell,
         currency = gnc_account_get_currency_or_parent (account);
         acctype = xaccAccountGetType (account);
 
-        if (gnc_using_unreversed_budgets (gnc_account_get_book (account)))
-        {  /* using book with unreversed-budgets feature. This will be
-              the default in 4.x after budget scrubbing*/
-            neg = gnc_reverse_balance (account);
-
-            switch (row_type)
-            {
-                case TOTALS_TYPE_ASSET_LIAB_EQ:
-                    if ((acctype != ACCT_TYPE_ASSET) &&
-                        (acctype != ACCT_TYPE_LIABILITY) &&
-                        (acctype != ACCT_TYPE_EQUITY))
-                        continue;
-                    neg = !neg;
-                    break;
-                case TOTALS_TYPE_EXPENSES:
-                    if (acctype != ACCT_TYPE_EXPENSE)
-                        continue;
-                    break;
-                case TOTALS_TYPE_INCOME:
-                    if (acctype != ACCT_TYPE_INCOME)
-                        continue;
-                    neg = !neg;
-                    break;
-                case TOTALS_TYPE_REMAINDER:
-                    neg = !neg;
-                    break;
-                default:
-                    continue;       /* don't count if unexpected total row type is passed in... */
-            }
-        }
-        else
-        {   /* this section is for backward compatibility, to be
-               removed when unreversed-budgets are mandatory */
-            neg = FALSE;
+        neg = gnc_reverse_balance (account);
 
-            switch (row_type)
-            {
-                case TOTALS_TYPE_ASSET_LIAB_EQ:
-                    if ((acctype != ACCT_TYPE_ASSET) &&
-                        (acctype != ACCT_TYPE_LIABILITY) &&
-                        (acctype != ACCT_TYPE_EQUITY))
-                        continue;
-                    neg = (acctype == ACCT_TYPE_ASSET);
-                    break;
-                case TOTALS_TYPE_EXPENSES:
-                    if (acctype != ACCT_TYPE_EXPENSE)
-                        continue;
-                    break;
-                case TOTALS_TYPE_INCOME:
-                    if (acctype != ACCT_TYPE_INCOME)
-                        continue;
-                    break;
-                case TOTALS_TYPE_REMAINDER:
-                    neg = ((acctype == ACCT_TYPE_ASSET) ||
-                           (acctype == ACCT_TYPE_EXPENSE));
-                    break;
-                default:
-                    continue;       /* don't count if unexpected total row type is passed in... */
-            }
+        switch (row_type)
+        {
+            case TOTALS_TYPE_ASSET_LIAB_EQ:
+                if ((acctype != ACCT_TYPE_ASSET) &&
+                    (acctype != ACCT_TYPE_LIABILITY) &&
+                    (acctype != ACCT_TYPE_EQUITY))
+                    continue;
+                neg = !neg;
+                break;
+            case TOTALS_TYPE_EXPENSES:
+                if (acctype != ACCT_TYPE_EXPENSE)
+                    continue;
+                break;
+            case TOTALS_TYPE_INCOME:
+                if (acctype != ACCT_TYPE_INCOME)
+                    continue;
+                neg = !neg;
+                break;
+            case TOTALS_TYPE_REMAINDER:
+                neg = !neg;
+                break;
+            default:
+                continue;       /* don't count if unexpected total row type is passed in... */
         }
         // find the total for this account
 
diff --git a/gnucash/gnome/gnc-plugin-page-budget.c b/gnucash/gnome/gnc-plugin-page-budget.c
index 67da7eba2..064761526 100644
--- a/gnucash/gnome/gnc-plugin-page-budget.c
+++ b/gnucash/gnome/gnc-plugin-page-budget.c
@@ -968,9 +968,6 @@ estimate_budget_helper (GtkTreeModel *model, GtkTreePath *path,
                                GNC_HOW_DENOM_SIGFIGS(priv->sigFigs) |
                                GNC_HOW_RND_ROUND_HALF_UP);
 
-        if (gnc_reverse_budget_balance (acct, FALSE))
-            num = gnc_numeric_neg (num);
-
         for (i = 0; i < num_periods; i++)
         {
             gnc_budget_set_account_period_value (priv->budget, acct, i, num);
@@ -986,9 +983,6 @@ estimate_budget_helper (GtkTreeModel *model, GtkTreePath *path,
 
             if (!gnc_numeric_check (num))
             {
-                if (gnc_reverse_budget_balance (acct, FALSE))
-                    num = gnc_numeric_neg (num);
-
                 num = gnc_numeric_convert (num, GNC_DENOM_AUTO,
                                            GNC_HOW_DENOM_SIGFIGS(priv->sigFigs) |
                                            GNC_HOW_RND_ROUND_HALF_UP);
@@ -1095,8 +1089,7 @@ allperiods_budget_helper (GtkTreeModel *model, GtkTreePath *path,
     acct = gnc_budget_view_get_account_from_path (priv->budget_view, path);
     num_periods = gnc_budget_get_num_periods (priv->budget);
     allvalue = priv->allValue;
-    if (gnc_reverse_budget_balance (acct, TRUE))
-        allvalue = gnc_numeric_neg (allvalue);
+    allvalue = gnc_numeric_neg (allvalue);
 
     for (i = 0; i < num_periods; i++)
     {

commit ab8277e9c415e267c4d47674c76e32c5cedb7153
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Thu Dec 2 22:31:16 2021 +0800

    [1/3][reports.scm] remove backward-compatibility code
    
    We can now assume that all budget amounts now use natural
    numbers. Remove handling old reversed-budget amounts.

diff --git a/gnucash/report/reports/standard/budget-balance-sheet.scm b/gnucash/report/reports/standard/budget-balance-sheet.scm
index c1dc21b9a..c7b6ed3b4 100644
--- a/gnucash/report/reports/standard/budget-balance-sheet.scm
+++ b/gnucash/report/reports/standard/budget-balance-sheet.scm
@@ -257,7 +257,7 @@
 
   (define (get-budget-account-budget-balance budget account)
     (let ((bal (gnc:budget-account-get-net budget account #f #f)))
-      (if (gnc-reverse-budget-balance account #t) (gnc:collector- bal) bal)))
+      (if (gnc-reverse-balance account) (gnc:collector- bal) bal)))
 
   (define (get-budget-account-initial-balance budget account)
     (gnc:budget-account-get-initial-balance budget account))
diff --git a/gnucash/report/reports/standard/budget-income-statement.scm b/gnucash/report/reports/standard/budget-income-statement.scm
index 5428b0197..c239c313c 100644
--- a/gnucash/report/reports/standard/budget-income-statement.scm
+++ b/gnucash/report/reports/standard/budget-income-statement.scm
@@ -308,7 +308,7 @@
 
   (define (get-budget-account-budget-balance budget account period-start period-end)
     (let ((bal (gnc:budget-account-get-net budget account period-start period-end)))
-      (if (gnc-reverse-budget-balance account #t) (gnc:collector- bal) bal)))
+      (if (gnc-reverse-balance account) (gnc:collector- bal) bal)))
 
   (gnc:report-starting reportname)
   
diff --git a/gnucash/report/reports/standard/budget.scm b/gnucash/report/reports/standard/budget.scm
index a960d50c7..cea15d449 100644
--- a/gnucash/report/reports/standard/budget.scm
+++ b/gnucash/report/reports/standard/budget.scm
@@ -348,8 +348,6 @@
       (let* ((comm (xaccAccountGetCommodity acct))
              (reverse-balance? (gnc-reverse-balance acct))
              (maybe-negate (lambda (amt) (if reverse-balance? (- amt) amt)))
-             (unreversed? (gnc-using-unreversed-budgets
-                           (gnc-get-current-book))) ;fwd-compatibility
              (allperiods (filter number? (gnc:list-flatten column-list)))
              (total-periods (if (and accumulate? (not (null? allperiods)))
                                 (iota (1+ (apply max allperiods)))
@@ -399,8 +397,7 @@
            ((null? column-list)
             #f)
 
-           ;; fwd-compatibility for unreversed budgets
-           ((and (eq? (car column-list) 'total) unreversed?)
+           ((eq? (car column-list) 'total)
             (let* ((bgt-total (maybe-negate
                                (gnc:get-account-periodlist-budget-value
                                 budget acct total-periods)))
@@ -412,21 +409,7 @@
                     (disp-cols "total-number-cell" current-col
                                bgt-total act-total dif-total #f))))
 
-           ((eq? (car column-list) 'total)
-            (let* ((bgt-total (gnc:get-account-periodlist-budget-value
-                               budget acct total-periods))
-                   (act-total (gnc:get-account-periodlist-actual-value
-                               budget acct total-periods))
-                   (act-total (if reverse-balance? (- act-total) act-total))
-                   (dif-total (if income-acct?
-                                  (- act-total bgt-total)
-                                  (- bgt-total act-total))))
-              (loop (cdr column-list)
-                    (disp-cols "total-number-cell" current-col
-                               bgt-total act-total dif-total #f))))
-
-           ;; fwd-compatibility for unreversed budgets
-           (unreversed?
+           (else
             (let* ((period-list (cond
                                  ((list? (car column-list)) (car column-list))
                                  (accumulate? (iota (1+ (car column-list))))
@@ -441,28 +424,6 @@
                              (gnc:get-account-periodlist-actual-value
                               budget acct period-list)))
                    (dif-val (- bgt-val act-val)))
-              (loop (cdr column-list)
-                    (disp-cols "number-cell" current-col
-                               bgt-val act-val dif-val note))))
-
-           (else
-            (let* ((period-list (cond
-                                 ((list? (car column-list)) (car column-list))
-                                 (accumulate? (iota (1+ (car column-list))))
-                                 (else (list (car column-list)))))
-                   (note (and (= 1 (length period-list))
-                              (gnc-budget-get-account-period-note
-                               budget acct (car period-list))))
-                   (bgt-val (gnc:get-account-periodlist-budget-value
-                             budget acct period-list))
-                   (act-abs (gnc:get-account-periodlist-actual-value
-                             budget acct period-list))
-                   (act-val (if reverse-balance?
-                                (- act-abs)
-                                act-abs))
-                   (dif-val (if income-acct?
-                                (- act-val bgt-val)
-                                (- bgt-val act-val))))
               (loop (cdr column-list)
                     (disp-cols "number-cell" current-col
                                bgt-val act-val dif-val note))))))))
diff --git a/gnucash/report/reports/standard/test/test-budget.scm b/gnucash/report/reports/standard/test/test-budget.scm
index fbd9fd36e..93adc3a3f 100644
--- a/gnucash/report/reports/standard/test/test-budget.scm
+++ b/gnucash/report/reports/standard/test/test-budget.scm
@@ -98,8 +98,8 @@
         (sxml->table-row-col sxml 1 5 #f))
       (test-equal "income"
         '("Income" "-$55.00 " "1" "-$55.00" "$0.00" "." "$0.00" "."
-          "-$65.00" "-$67.00" "-$2.00" "-$75.00" "-$77.00" "-$2.00" "."
-          "$0.00" "." "." "$0.00" "." "-$195.00" "-$199.00" "-$4.00")
+          "-$65.00" "-$67.00" "$2.00" "-$75.00" "-$77.00" "$2.00" "."
+          "$0.00" "." "." "$0.00" "." "-$195.00" "-$199.00" "$4.00")
         (sxml->table-row-col sxml 1 9 #f))
       (test-equal "expense"
         '("Expenses" "." "$20.00" "-$20.00" "$30.00 " "2" "$20.00"

commit 329ca0e4916df2f5042c19e8079f650d6aa83dad
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Fri Aug 27 20:37:58 2021 +0800

    [2/2][gnc-file.c] launch scrubbing and inform user after file-load
    
    Note the feature check and checking for budgets is done in
    ScrubBudget.c

diff --git a/gnucash/gnome-utils/gnc-file.c b/gnucash/gnome-utils/gnc-file.c
index 677125c1e..76af4de3f 100644
--- a/gnucash/gnome-utils/gnc-file.c
+++ b/gnucash/gnome-utils/gnc-file.c
@@ -46,6 +46,7 @@
 #include "gnc-plugin-file-history.h"
 #include "qof.h"
 #include "Scrub.h"
+#include "ScrubBudget.h"
 #include "TransLog.h"
 #include "gnc-session.h"
 #include "gnc-state.h"
@@ -701,6 +702,26 @@ gnc_file_query_save (GtkWindow *parent, gboolean can_cancel)
 #define RESPONSE_READONLY 4
 #define RESPONSE_FILE 5
 
+/* This function is called after loading datafile. It's meant to
+   collect all scrubbing routines. */
+static void
+run_post_load_scrubs (GtkWindow *parent, QofBook *book)
+{
+    qof_event_suspend();
+
+    /* If feature GNC_FEATURE_BUDGET_UNREVERSED is not set, and there
+       are budgets, fix signs */
+    if (gnc_maybe_scrub_all_budget_signs (book))
+        gnc_info_dialog (parent, "%s", _("This book has budgets. \
+The internal representation of amounts is now fixed. Please review \
+budgets and amend signs if necessary."));
+
+    // Fix account color slots being set to 'Not Set', should run once on a book
+    xaccAccountScrubColorNotSet (book);
+
+    qof_event_resume();
+}
+
 static gboolean
 gnc_post_file_open (GtkWindow *parent, const char * filename, gboolean is_readonly)
 {
@@ -1104,10 +1125,7 @@ RESTART:
         g_list_free_full (invalid_account_names, g_free);
     }
 
-    // Fix account color slots being set to 'Not Set', should run once on a book
-    qof_event_suspend();
-    xaccAccountScrubColorNotSet (gnc_get_current_book());
-    qof_event_resume();
+    run_post_load_scrubs (parent, new_book);
 
     return TRUE;
 }

commit 73822f97a958dd2b2a732e4453cc5747450e09f7
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Fri Aug 27 20:37:50 2021 +0800

    [1/2][Scrubbudget.c] use heuristics to scrub budget signs
    
    If book has budgets, and GNC_FEATURE_BUDGET_UNREVERSED isn't set,
    check and fix budget amount signs. Previously budgets were created
    with assumption that sign reversal was set to credit amounts. A
    heuristic approach is used:
    
    - if budgeted expense is negative, conclude sign reversal is
    "Income and Expense"
    
    - if budgeted liability is positive, conclude sign reversal is
    "None"
    
    - otherwise conclude sign reversal is "Credit Accounts"
    
    The above is calibrated to (hopefully) prefer sign reversal of Credit
    Accounts.

diff --git a/libgnucash/engine/CMakeLists.txt b/libgnucash/engine/CMakeLists.txt
index d860bd436..559d53919 100644
--- a/libgnucash/engine/CMakeLists.txt
+++ b/libgnucash/engine/CMakeLists.txt
@@ -37,6 +37,7 @@ set (engine_HEADERS
   Scrub2.h
   ScrubBusiness.h
   Scrub3.h
+  ScrubBudget.h
   Split.h
   TransLog.h
   Transaction.h
@@ -133,6 +134,7 @@ set (engine_SOURCES
   Scrub2.c
   Scrub3.c
   ScrubBusiness.c
+  ScrubBudget.c
   Split.c
   TransLog.c
   Transaction.c
diff --git a/libgnucash/engine/ScrubBudget.c b/libgnucash/engine/ScrubBudget.c
new file mode 100644
index 000000000..614f946f7
--- /dev/null
+++ b/libgnucash/engine/ScrubBudget.c
@@ -0,0 +1,211 @@
+/********************************************************************\
+ * ScrubBudget.c -- fix budget amount signs                         *
+ * Copyright (c) 2020 Christoher Lam                                *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#include <config.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "gnc-prefs.h"
+#include "gnc-budget.h"
+#include "gnc-features.h"
+#include "ScrubBudget.h"
+
+static QofLogModule log_module = "gnc.engine.scrub";
+
+typedef enum
+{
+    HEURISTICS_INC_EXP,
+    HEURISTICS_CREDIT_ACC,
+    HEURISTICS_NONE
+} SignReversals;
+
+typedef struct
+{
+    GncBudget* budget;
+    SignReversals policy;
+} ReversalType;
+
+typedef struct
+{
+    gint asset,liability,equity,income,expense;
+    gint num_periods;
+    GncBudget* budget;
+} ProcessData;
+
+static void
+process_heuristics_acct (Account * account, gpointer user_data)
+{
+    /* each account- check type. sum budget period amounts. if sum<0,
+       decrease type tally by 1. if sum>0, increase type tally by 1. */
+    ProcessData *heuristics = (ProcessData*) user_data;
+    gnc_numeric total = gnc_numeric_zero(), val;
+    gint sign;
+    gchar *totalstr;
+
+    for (gint i = 0; i < heuristics->num_periods; ++i)
+    {
+        if (!gnc_budget_is_account_period_value_set (heuristics->budget, account, i))
+            continue;
+        val = gnc_budget_get_account_period_value (heuristics->budget, account, i);
+        total = gnc_numeric_add_fixed (total, val);
+    }
+
+    sign = gnc_numeric_compare (total, gnc_numeric_zero ());
+    totalstr = gnc_numeric_to_string (total);
+    PINFO ("acct=%s, total=%s, sign=%d",
+           xaccAccountGetName (account), totalstr, sign);
+    g_free (totalstr);
+
+    switch (xaccAccountTypeGetFundamental (xaccAccountGetType (account)))
+    {
+    case ACCT_TYPE_ASSET:
+        heuristics->asset += sign;
+        break;
+    case ACCT_TYPE_LIABILITY:
+        heuristics->liability += sign;
+        break;
+    case ACCT_TYPE_EXPENSE:
+        heuristics->expense += sign;
+        break;
+    case ACCT_TYPE_INCOME:
+        heuristics->income += sign;
+        break;
+    case ACCT_TYPE_EQUITY:
+        heuristics->equity += sign;
+        break;
+    default:
+        break;
+    }
+}
+
+static SignReversals
+heuristics_on_budget (GncBudget * budget, Account *root)
+{
+    ProcessData heuristics = {0, 0, 0, 0, 0, gnc_budget_get_num_periods (budget),
+                              budget};
+    SignReversals result;
+
+    gnc_account_foreach_descendant (root, process_heuristics_acct, &heuristics);
+
+    result =
+        heuristics.expense < 0 ? HEURISTICS_INC_EXP :
+        heuristics.liability > 0 ? HEURISTICS_NONE :
+        HEURISTICS_CREDIT_ACC;
+
+    LEAVE ("heuristics_on_budget %s: A(%d) L(%d) Inc(%d) Exp(%d) Eq(%d) = %d",
+           gnc_budget_get_name (budget),
+           heuristics.asset, heuristics.liability, heuristics.income,
+           heuristics.expense, heuristics.equity, result);
+
+    return result;
+}
+
+static void
+fix_budget_acc_sign (Account *acc, gpointer user_data)
+{
+    ReversalType* reversal = (ReversalType*) user_data;
+    GncBudget* budget = reversal->budget;
+    guint numperiods = gnc_budget_get_num_periods (budget);
+    int type = xaccAccountTypeGetFundamental (xaccAccountGetType (acc));
+
+    ENTER ("budget account reversal [%s] starting", xaccAccountGetName(acc));
+
+    switch (reversal->policy)
+    {
+    case HEURISTICS_INC_EXP:
+        if ((type != ACCT_TYPE_INCOME) && (type != ACCT_TYPE_EXPENSE))
+            return;
+        PINFO ("budget account [%s] is inc/exp. reverse!",
+               xaccAccountGetName(acc));
+        break;
+    case HEURISTICS_CREDIT_ACC:
+        if ((type != ACCT_TYPE_LIABILITY) &&
+            (type != ACCT_TYPE_EQUITY) &&
+            (type != ACCT_TYPE_INCOME))
+            return;
+        PINFO ("budget account [%s] is credit-account. reverse!",
+               xaccAccountGetName(acc));
+        break;
+    default:
+        /* shouldn't happen. */
+        return;
+    }
+
+    for (guint i=0; i < numperiods; ++i)
+    {
+        gnc_numeric amt;
+        if (!gnc_budget_is_account_period_value_set (budget, acc, i))
+            continue;
+
+        amt = gnc_budget_get_account_period_value (budget, acc, i);
+        amt = gnc_numeric_neg (amt);
+        gnc_budget_set_account_period_value (budget, acc, i, amt);
+    }
+
+    LEAVE ("budget account reversal [%s] completed!", xaccAccountGetName(acc));
+}
+
+static void
+maybe_scrub_budget (QofInstance* data, gpointer user_data)
+{
+    GncBudget* budget = GNC_BUDGET(data);
+    Account *root = (Account*) user_data;
+    ReversalType reversal;
+
+    reversal.policy = heuristics_on_budget (budget, root);
+    if (reversal.policy == HEURISTICS_NONE)
+    {
+        PWARN ("budget [%s] doesn't need reversing", gnc_budget_get_name (budget));
+        return;
+    }
+
+    reversal.budget = budget;
+
+    ENTER ("processing budget [%s] for reversal", gnc_budget_get_name (budget));
+    gnc_account_foreach_descendant (root, fix_budget_acc_sign, &reversal);
+    LEAVE ("completed budget [%s] for reversal", gnc_budget_get_name (budget));
+}
+
+gboolean
+gnc_maybe_scrub_all_budget_signs (QofBook *book)
+{
+    Account* root = gnc_book_get_root_account (book);
+    gchar *retval = NULL;
+
+    if (gnc_features_check_used (book, GNC_FEATURE_BUDGET_UNREVERSED))
+        return FALSE;
+
+    if (!gnc_budget_get_default (book))
+        return FALSE;
+
+    qof_collection_foreach (qof_book_get_collection (book, GNC_ID_BUDGET),
+                            maybe_scrub_budget, root);
+
+    gnc_features_set_used (book, GNC_FEATURE_BUDGET_UNREVERSED);
+    return TRUE;
+}
+/* ==================== END OF FILE ==================== */
diff --git a/libgnucash/engine/ScrubBudget.h b/libgnucash/engine/ScrubBudget.h
new file mode 100644
index 000000000..9efc42b79
--- /dev/null
+++ b/libgnucash/engine/ScrubBudget.h
@@ -0,0 +1,43 @@
+/********************************************************************\
+ * ScrubBudget.h -- fix budget amount signs                         *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#ifndef _GNC_SCRUBBUDGET_H_
+#define _GNC_SCRUBBUDGET_H_
+
+#include <glib.h>
+#include <qofbook.h>
+
+/* ================================================================ */
+
+/** Fix budget signs
+ * For GnuCash 5.0 onwards - fix budget signs
+ * A feature is set if we have completed reversal.
+ *
+ * @param book The book to scrub
+ *
+ * returns TRUE if budgets were scrubbed
+ * returns FALSE if feature already set, or no budgets.
+ */
+gboolean gnc_maybe_scrub_all_budget_signs (QofBook *book);
+
+
+#endif // _GNC_SCRUBBUDGET_H_
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 52dd27261..da7ec590e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -673,6 +673,7 @@ libgnucash/engine/Recurrence.c
 libgnucash/engine/SchedXaction.c
 libgnucash/engine/Scrub2.c
 libgnucash/engine/Scrub3.c
+libgnucash/engine/ScrubBudget.c
 libgnucash/engine/ScrubBusiness.c
 libgnucash/engine/Scrub.c
 libgnucash/engine/Split.c

commit 99c2c82c98220fd1f95add33b0d1201cbf5191f0
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sat Mar 6 18:14:25 2021 +0800

    [3/3][gnc-plugin-budget.c] set/unset feature creating/deleting budget
    
    If creating budget, and unfeatured, set feature.
    If deleting budget, and num(budgets) == 0 afterwards, unset feature

diff --git a/gnucash/gnome/gnc-plugin-budget.c b/gnucash/gnome/gnc-plugin-budget.c
index 6632a8d11..351290195 100644
--- a/gnucash/gnome/gnc-plugin-budget.c
+++ b/gnucash/gnome/gnc-plugin-budget.c
@@ -32,6 +32,7 @@
 #include "gnc-tree-model-budget.h"
 
 #include "qof.h"
+#include "gnc-features.h"
 #include "gnc-ui-util.h"
 #include "gnc-ui.h"
 #include "gnc-component-manager.h"
@@ -158,9 +159,17 @@ gnc_plugin_budget_cmd_new_budget (GtkAction *action,
     GncBudget *budget;
     GncPluginPage *page;
     gchar *description, *date;
+    QofBook *book = gnc_get_current_book();
 
     g_return_if_fail (user_data != NULL);
 
+    if (!gnc_features_check_used (book, GNC_FEATURE_BUDGET_UNREVERSED))
+    {
+        gnc_features_set_used (book, GNC_FEATURE_BUDGET_UNREVERSED);
+        PWARN ("Setting feature BUDGET_UNREVERSED. This book now requires \
+GnuCash 3.8 or later.");
+    }
+
     budget = gnc_budget_new (gnc_get_current_book());
     page = gnc_plugin_page_budget_new (budget);
 
@@ -259,6 +268,14 @@ gnc_plugin_budget_cmd_delete_budget (GtkAction *action,
     if (!bgt) return;
 
     gnc_budget_gui_delete_budget (bgt);
+
+    if (qof_collection_count (qof_book_get_collection (book, GNC_ID_BUDGET)) == 0)
+    {
+        gnc_features_set_unused (book, GNC_FEATURE_BUDGET_UNREVERSED);
+        PWARN ("Removing feature BUDGET_UNREVERSED. No budgets left.");
+    }
+
+
 }
 
 /************************************************************

commit 6070aecd69785c8f51727aea4df1d7c8b5fcc3e3
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Thu Dec 2 22:12:29 2021 +0800

    [2/3][gnc-features.c] add & expose gnc_features_set_unused
    
    same as gnc_features_set_used but removes feature

diff --git a/libgnucash/engine/gnc-features.c b/libgnucash/engine/gnc-features.c
index f710677d2..e6622712c 100644
--- a/libgnucash/engine/gnc-features.c
+++ b/libgnucash/engine/gnc-features.c
@@ -152,6 +152,27 @@ void gnc_features_set_used (QofBook *book, const gchar *feature)
     qof_book_set_feature (book, feature, description);
 }
 
+
+void gnc_features_set_unused (QofBook *book, const gchar *feature)
+{
+    const gchar *description;
+
+    g_return_if_fail (book);
+    g_return_if_fail (feature);
+
+    gnc_features_init();
+
+    /* Can't unset an unknown feature */
+    description = g_hash_table_lookup (features_table, feature);
+    if (!description)
+    {
+        PWARN("Tried to set unknown feature as unused.");
+        return;
+    }
+
+    qof_book_unset_feature (book, feature, description);
+}
+
 struct CheckFeature
 {
     gchar const * checked_feature;
diff --git a/libgnucash/engine/gnc-features.h b/libgnucash/engine/gnc-features.h
index 030c02531..b5960ac7a 100644
--- a/libgnucash/engine/gnc-features.h
+++ b/libgnucash/engine/gnc-features.h
@@ -67,6 +67,13 @@ extern "C" {
  */
 gchar *gnc_features_test_unknown (QofBook *book);
 
+/**
+ * Indicate that the current book does not use the given feature. This
+ * will allow older versions of GnuCash that don't support this
+ * feature to load this book.
+ */
+void gnc_features_set_unused (QofBook *book, const gchar *feature);
+
 /**
  * Indicate that the current book uses the given feature. This will prevent
  * older versions of GnuCash that don't support this feature to refuse to load

commit 5dab33694fbbe82f4aa46ad148584ec105385372
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Thu Dec 2 22:12:12 2021 +0800

    [1/3][qofbook.cpp] add & expose qof_book_unset_feature
    
    same as qof_book_set_feature but removes feature item

diff --git a/libgnucash/engine/qofbook.cpp b/libgnucash/engine/qofbook.cpp
index ab2b01231..1cbc673eb 100644
--- a/libgnucash/engine/qofbook.cpp
+++ b/libgnucash/engine/qofbook.cpp
@@ -1255,6 +1255,27 @@ qof_book_set_feature (QofBook *book, const gchar *key, const gchar *descr)
     }
 }
 
+
+void
+qof_book_unset_feature (QofBook *book, const gchar *key, const gchar *descr)
+{
+    KvpFrame *frame = qof_instance_get_slots (QOF_INSTANCE (book));
+    KvpValue* feature = nullptr;
+    auto feature_slot = frame->get_slot({GNC_FEATURES});
+    if (feature_slot)
+    {
+        auto feature_frame = feature_slot->get<KvpFrame*>();
+        feature = feature_frame->get_slot({key});
+    }
+    if (feature == nullptr || g_strcmp0 (feature->get<const char*>(), descr))
+    {
+        qof_book_begin_edit (book);
+        delete frame->set_path({GNC_FEATURES, key}, nullptr);
+        qof_instance_set_dirty (QOF_INSTANCE (book));
+        qof_book_commit_edit (book);
+    }
+}
+
 void
 qof_book_load_options (QofBook *book, GNCOptionLoad load_cb, GNCOptionDB *odb)
 {
diff --git a/libgnucash/engine/qofbook.h b/libgnucash/engine/qofbook.h
index f48ed9400..a26229938 100644
--- a/libgnucash/engine/qofbook.h
+++ b/libgnucash/engine/qofbook.h
@@ -382,6 +382,7 @@ void qof_book_option_frame_delete (QofBook *book, const char* opt_name);
 /** Access functions for reading and setting the used-features on this book.
  */
 GHashTable *qof_book_get_features (QofBook *book);
+void qof_book_unset_feature (QofBook *book, const gchar *key, const gchar *descr);
 void qof_book_set_feature (QofBook *book, const gchar *key, const gchar *descr);
 
 void qof_book_begin_edit(QofBook *book);



Summary of changes:
 gnucash/gnome-utils/gnc-file.c                     |  26 ++-
 gnucash/gnome/gnc-budget-view.c                    |  97 +++-------
 gnucash/gnome/gnc-plugin-budget.c                  |  17 ++
 gnucash/gnome/gnc-plugin-page-budget.c             |   9 +-
 .../reports/standard/budget-balance-sheet.scm      |   2 +-
 .../reports/standard/budget-income-statement.scm   |   2 +-
 gnucash/report/reports/standard/budget.scm         |  43 +----
 .../report/reports/standard/test/test-budget.scm   |   4 +-
 libgnucash/app-utils/app-utils.i                   |   2 -
 libgnucash/app-utils/gnc-ui-util.c                 |  17 --
 libgnucash/app-utils/gnc-ui-util.h                 |  10 -
 libgnucash/engine/CMakeLists.txt                   |   2 +
 libgnucash/engine/ScrubBudget.c                    | 211 +++++++++++++++++++++
 .../engine/ScrubBudget.h                           |  42 ++--
 libgnucash/engine/gnc-features.c                   |  21 ++
 libgnucash/engine/gnc-features.h                   |   7 +
 libgnucash/engine/qofbook.cpp                      |  21 ++
 libgnucash/engine/qofbook.h                        |   1 +
 po/POTFILES.in                                     |   1 +
 19 files changed, 355 insertions(+), 180 deletions(-)
 create mode 100644 libgnucash/engine/ScrubBudget.c
 copy gnucash/register/register-core/table-control.c => libgnucash/engine/ScrubBudget.h (71%)



More information about the gnucash-changes mailing list