gnucash master: Multiple changes pushed

Geert Janssens gjanssens at code.gnucash.org
Sat Mar 19 09:42:55 EDT 2016


Updated	 via  https://github.com/Gnucash/gnucash/commit/323f8165 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/a0fa6d2f (commit)
	 via  https://github.com/Gnucash/gnucash/commit/dca13d62 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/6c2c2d73 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/d1e148ef (commit)
	 via  https://github.com/Gnucash/gnucash/commit/0f66e200 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/d45886f7 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/3109b6f6 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/d0e103be (commit)
	 via  https://github.com/Gnucash/gnucash/commit/c2ce2044 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/a27abf76 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/8117a7c1 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/1121cd07 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/ab35b571 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/ee70922c (commit)
	 via  https://github.com/Gnucash/gnucash/commit/7ef25689 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/c56a4d95 (commit)
	from  https://github.com/Gnucash/gnucash/commit/d1ab9e78 (commit)



commit 323f8165dfbba7221228601d91a5eef2990660e6
Merge: a0fa6d2 dca13d6
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Sat Mar 19 15:34:09 2016 +0100

    Merge branch 'maint'

diff --cc src/libqof/qof/qofbook.h
index 99404ba,35aa303..f15c0ad
--- a/src/libqof/qof/qofbook.h
+++ b/src/libqof/qof/qofbook.h
@@@ -343,10 -333,9 +343,10 @@@ gchar * qof_book_normalize_counter_form
  
  /** Get the format string to use for the named counter.
   *    The return value is NULL on error or the format string of the
-  *    counter. The string should not be freed.
+  *    counter. The returned string should be freed by the caller.
   */
 -gchar *qof_book_get_counter_format (const QofBook *book, const char *counter_name);
 +char *qof_book_get_counter_format (const QofBook *book,
 +                                   const char *counter_name);
  
  const char* qof_book_get_string_option(const QofBook* book, const char* opt_name);
  void qof_book_set_string_option(QofBook* book, const char* opt_name, const char* opt_val);

commit a0fa6d2fe762c44b869ea843b3ec44888a2ee35c
Merge: d1ab9e7 6c2c2d7
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Sat Mar 19 14:56:12 2016 +0100

    Merge branch 'maint'
    
    Resolved Conflicts:
    	src/engine/test/utest-Transaction.cpp
    	src/gnome-utils/ui/osx_accel_map
    	src/libqof/qof/qofbook.cpp

diff --cc src/engine/Transaction.c
index e903060,2425de5..d21f0d4
--- a/src/engine/Transaction.c
+++ b/src/engine/Transaction.c
@@@ -2531,21 -2466,27 +2589,29 @@@ xaccTransVoid(Transaction *trans, cons
  
      g_return_if_fail(trans && reason);
  
+     /* Prevent voiding transactions that are already marked
+      * read only, for example generated by the business features.
+      */
+     if (xaccTransGetReadOnly (trans))
+     {
+         PWARN ("Refusing to void a read-only transaction!");
+         return;
+     }
      xaccTransBeginEdit(trans);
 -    frame = trans->inst.kvp_data;
 -
 -    val = kvp_frame_get_slot(frame, trans_notes_str);
 -    kvp_frame_set_slot(frame, void_former_notes_str, val);
 +    qof_instance_get_kvp (QOF_INSTANCE (trans), trans_notes_str, &v);
 +    if (G_VALUE_HOLDS_STRING (&v))
 +        qof_instance_set_kvp (QOF_INSTANCE (trans), void_former_notes_str, &v);
 +    else
 +        g_value_init (&v, G_TYPE_STRING);
  
 -    kvp_frame_set_string(frame, trans_notes_str, _("Voided transaction"));
 -    kvp_frame_set_string(frame, void_reason_str, reason);
 +    g_value_set_string (&v, _("Voided transaction"));
 +    qof_instance_set_kvp (QOF_INSTANCE (trans), trans_notes_str, &v);
 +    g_value_set_string (&v, reason);
 +    qof_instance_set_kvp (QOF_INSTANCE (trans), void_reason_str, &v);
  
 -    now.tv_sec = gnc_time (NULL);
 -    now.tv_nsec = 0;
 -    gnc_timespec_to_iso8601_buff(now, iso8601_str);
 -    kvp_frame_set_string(frame, void_time_str, iso8601_str);
 +    gnc_timespec_to_iso8601_buff (timespec_now (), iso8601_str);
 +    g_value_set_string (&v, iso8601_str);
 +    qof_instance_set_kvp (QOF_INSTANCE (trans), void_time_str, &v);
  
      FOR_EACH_SPLIT(trans, xaccSplitVoid(s));
  
@@@ -2641,10 -2567,12 +2707,13 @@@ xaccTransReverse (Transaction *orig
      });
  
      /* Now update the original with a pointer to the new one */
 -    kvp_val = kvp_value_new_guid(xaccTransGetGUID(trans));
 -    kvp_frame_set_slot_nc(orig->inst.kvp_data, TRANS_REVERSED_BY, kvp_val);
 +    g_value_init (&v, GNC_TYPE_GUID);
 +    g_value_set_boxed (&v, xaccTransGetGUID(trans));
 +    qof_instance_set_kvp (QOF_INSTANCE (orig), TRANS_REVERSED_BY, &v);
  
+     /* Make sure the reverse transaction is not read-only */
+     xaccTransClearReadOnly(trans);
+ 
      qof_instance_set_dirty(QOF_INSTANCE(trans));
      xaccTransCommitEdit(trans);
      return trans;
diff --cc src/engine/test/utest-Transaction.cpp
index 98dadf4,50bb824..0b32bbe
--- a/src/engine/test/utest-Transaction.cpp
+++ b/src/engine/test/utest-Transaction.cpp
@@@ -931,18 -925,17 +931,17 @@@ test_xaccTransEqual (Fixture *fixture, 
      g_assert (!xaccTransEqual (clone, txn0, TRUE, TRUE, TRUE, TRUE));
      g_assert (xaccTransEqual (clone, txn0, FALSE, FALSE, FALSE, TRUE));
      g_assert_cmpint (check->hits, ==, 10);
 -    g_assert_cmpint (check2.hits, ==, 1);
 +    g_assert_cmpint (check2->hits, ==, 1);
  
      g_free (check->msg);
 -    g_free (check2.msg);
 -    check2.msg = g_strdup_printf (
 +    g_free (check2->msg);
-     check->msg = g_strdup("[xaccSplitEqual] amounts differ: 13333/1000 vs 100000/1000");
 +    check2->msg = g_strdup_printf (
                       "[xaccTransEqual] splits %s and %s differ", split_guid0, split_guid0);
      qof_instance_set_guid (split1, qof_instance_get_guid (split0));
      g_assert (!xaccTransEqual (clone, txn0, TRUE, TRUE, TRUE, TRUE));
      g_assert (xaccTransEqual (clone, txn0, TRUE, FALSE, FALSE, TRUE));
-     g_assert_cmpint (check->hits, ==, 11);
+     g_assert_cmpint (check->hits, ==, 10);
 -    g_assert_cmpint (check2.hits, ==, 2);
 +    g_assert_cmpint (check2->hits, ==, 2);
  
      qof_instance_set_guid (xaccTransGetSplit (txn1, 0),
                             qof_instance_get_guid (split0));
@@@ -954,19 -946,19 +952,19 @@@
          Split* split01 = xaccTransGetSplit (txn0, 1);
          Split* split10 = xaccTransGetSplit (txn1, 0);
          Split* split11 = xaccTransGetSplit (txn1, 1);
 -        gchar *bal00 = gnc_numeric_to_string (split00->balance);
 -        gchar *bal01 = gnc_numeric_to_string (split01->balance);
 -        gchar *bal10 = gnc_numeric_to_string (split10->balance);
 -        gchar *bal11 = gnc_numeric_to_string (split11->balance);
 +        auto bal00 = gnc_numeric_to_string (split00->balance);
 +        auto bal01 = gnc_numeric_to_string (split01->balance);
 +        auto bal10 = gnc_numeric_to_string (split10->balance);
 +        auto bal11 = gnc_numeric_to_string (split11->balance);
          check->msg = g_strdup_printf("[xaccSplitEqualCheckBal] balances differ: %s vs %s", bal10, bal00);
 -        check3.msg = g_strdup_printf("[xaccSplitEqualCheckBal] balances differ: %s vs %s", bal11, bal01);
 +        check3->msg = g_strdup_printf("[xaccSplitEqualCheckBal] balances differ: %s vs %s", bal11, bal01);
  
 -        test_add_error (&check3);
 +        test_add_error (check3);
          g_assert (!xaccTransEqual (txn1, txn0, TRUE, TRUE, TRUE, TRUE));
          g_assert (xaccTransEqual (txn1, txn0, TRUE, TRUE, FALSE, TRUE));
-         g_assert_cmpint (check->hits, ==, 12);
+         g_assert_cmpint (check->hits, ==, 11);
 -        g_assert_cmpint (check2.hits, ==, 3);
 -        g_assert_cmpint (check3.hits, ==, 0);
 +        g_assert_cmpint (check2->hits, ==, 3);
 +        g_assert_cmpint (check3->hits, ==, 0);
  
          split10->balance = split00->balance;
          split11->balance = split01->balance;
diff --cc src/gnome-utils/ui/osx_accel_map
index 2ebdd44,9182621..6ca6366
--- a/src/gnome-utils/ui/osx_accel_map
+++ b/src/gnome-utils/ui/osx_accel_map
@@@ -40,8 -40,8 +40,8 @@@
  ; (gtk_accel_path "<Actions>/gnc-plugin-log-replay-actions/LogReplayAction" "")
  ; (gtk_accel_path "<Actions>/gnc-plugin-business-actions/CustomerNewJobOpenAction" "")
  ; (gtk_accel_path "<Actions>/MenuAdditions/PayableAgingAction" "")
 -; (gtk_accel_path "<Actions>/MenuAdditions/GeneralLedgerAction" "")
 +; (gtk_accel_path "<Actions>/MenuAdditions/GeneralJournalAction" "")
- ; (gtk_accel_path "<Actions>/MainWindowActions/HelpTutorialAction" "")
+ (gtk_accel_path "<Actions>/MainWindowActions/HelpTutorialAction" "")
  ; (gtk_accel_path "<Actions>/gnc-plugin-basic-commands-actions/ActionsSinceLastRunAction" "")
  ; (gtk_accel_path "<Actions>/gnc-plugin-business-actions/BusinessTestReloadOwnerAction" "")
  ; (gtk_accel_path "<Actions>/MenuAdditions/ReceivableAgingAction" "")
diff --cc src/libqof/qof/qofbook.cpp
index 997f841,b8e5fac..98349be
--- a/src/libqof/qof/qofbook.cpp
+++ b/src/libqof/qof/qofbook.cpp
@@@ -44,11 -39,8 +44,12 @@@ extern "C
  #include <string.h>
  
  #include <glib.h>
+ #include <inttypes.h>
  
 +#ifdef __cplusplus
 +}
 +#endif
 +
  #include "qof.h"
  #include "qofevent-p.h"
  #include "qofbackend-p.h"
@@@ -717,16 -458,19 +719,19 @@@ qof_book_increment_and_format_counter (
      }
  
      /* Generate a string version of the counter */
-     return g_strdup_printf(format, counter);
+     result = g_strdup_printf(format, counter);
+     g_free (format);
+     return result;
  }
  
- const gchar *
 -gchar *
++char *
  qof_book_get_counter_format(const QofBook *book, const char *counter_name)
  {
      KvpFrame *kvp;
-     const char *format;
 -    gchar *user_format = NULL;
++    const char *user_format = NULL;
+     gchar *norm_format = NULL;
      KvpValue *value;
-     gchar *error;
+     gchar *error = NULL;
  
      if (!book)
      {
@@@ -749,19 -493,17 +754,17 @@@
          return NULL;
      }
  
-     format = NULL;
- 
      /* Get the format string */
 -    value = kvp_frame_get_slot_path (kvp, "counter_formats", counter_name, NULL);
 +    value = kvp->get_slot({"counter_formats", counter_name});
      if (value)
      {
-         format = value->get<const char*>();
-         error = qof_book_validate_counter_format(format);
-         if (error != NULL)
 -        user_format = kvp_value_get_string (value);
++        user_format = value->get<const char*>();
+         norm_format = qof_book_normalize_counter_format(user_format, &error);
+         if (!norm_format)
          {
-             PWARN("Invalid counter format string. Format string: '%s' Counter: '%s' Error: '%s')", format, counter_name, error);
+             PWARN("Invalid counter format string. Format string: '%s' Counter: '%s' Error: '%s')", user_format, counter_name, error);
              /* Invalid format string */
-             format = NULL;
+             user_format = NULL;
              g_free(error);
          }
      }
@@@ -883,92 -675,33 +936,97 @@@ qof_book_normalize_counter_format_inter
          p++;
      }
  
+     /* Add the suffix to our normalized string */
+     aux_str = normalized_str;
+     normalized_str = g_strconcat (aux_str, tmp, NULL);
+     g_free (aux_str);
+ 
      /* If we end up here, the string was valid, so return no error
       * message */
-     return NULL;
+     return normalized_str;
  }
  
 -/* Determine whether this book uses trading accounts */
 -gboolean
 -qof_book_use_trading_accounts (const QofBook *book)
 +/** Returns pointer to book-currency name for book, if one exists in the
 +  * KVP, or NULL; does not validate contents nor determine if there is a valid
 +  * default gain/loss policy, both of which are required, for the
 +  * 'book-currency' currency accounting method to apply. Use instead
 +  * 'gnc_book_get_book_currency' which does these validations. */
 +const gchar *
 +qof_book_get_book_currency (QofBook *book)
  {
 -    const char *opt;
 -    kvp_value *kvp_val;
 +    KvpFrame *kvp;
 +    KvpValue *value;
 +
 +    if (!book)
 +    {
 +        PWARN ("No book!!!");
 +        return NULL;
 +    }
 +
 +    /* Get the KVP from the current book */
 +    kvp = qof_instance_get_slots (QOF_INSTANCE (book));
 +
 +    if (!kvp)
 +    {
 +        PWARN ("Book has no KVP_Frame");
 +        return NULL;
 +    }
 +
 +    /* See if there is a book currency. */
 +    value = kvp->get_slot({KVP_OPTION_PATH, OPTION_SECTION_ACCOUNTS,
 +                           OPTION_NAME_BOOK_CURRENCY});
 +    if (!value) /* No book-currency */
 +        return nullptr;
 +
 +    return value->get<const char*>();
 +}
 +
 +/** Returns pointer to default gain/loss policy for book, if one exists in the
 +  * KVP, or NULL; does not validate contents nor determine if there is a valid
 +  * book-currency, both of which are required, for the 'book-currency'
 +  * currency accounting method to apply. Use instead
 +  * 'gnc_book_get_default_gains_policy' which does these validations. */
 +const gchar *
 +qof_book_get_default_gains_policy (QofBook *book)
 +{
 +    KvpFrame *kvp;
 +    KvpValue *value;
  
 -    kvp_val = kvp_frame_get_slot_path (qof_book_get_slots (book),
 -                                       KVP_OPTION_PATH,
 -                                       OPTION_SECTION_ACCOUNTS,
 -                                       OPTION_NAME_TRADING_ACCOUNTS,
 -                                       NULL);
 -    if (kvp_val == NULL)
 -        return FALSE;
 +    if (!book)
 +    {
 +        PWARN ("No book!!!");
 +        return NULL;
 +    }
  
 -    opt = kvp_value_get_string (kvp_val);
 +    /* Get the KVP from the current book */
 +    kvp = qof_instance_get_slots (QOF_INSTANCE (book));
  
 +    if (!kvp)
 +    {
 +        PWARN ("Book has no KVP_Frame");
 +        return NULL;
 +    }
 +
 +    /* See if there is a default gain/loss policy */
 +    value = kvp->get_slot({KVP_OPTION_PATH, OPTION_SECTION_ACCOUNTS,
 +                           OPTION_NAME_DEFAULT_GAINS_POLICY});
 +    if (!value)
 +    /* No default gain/loss policy, therefore not valid book-currency
 +       accounting method */
 +        return nullptr;
 +
 +    return g_strdup(value->get<const char*>());
 +}
 +
 +
 +/* Determine whether this book uses trading accounts */
 +gboolean
 +qof_book_use_trading_accounts (const QofBook *book)
 +{
 +    const char *opt = NULL;
 +    qof_instance_get (QOF_INSTANCE (book),
 +		      "trading-accts", &opt,
 +		      NULL);
      if (opt && opt[0] == 't' && opt[1] == 0)
          return TRUE;
      return FALSE;
diff --cc src/libqof/qof/qofbook.h
index d98c08f,ab9a0db..99404ba
--- a/src/libqof/qof/qofbook.h
+++ b/src/libqof/qof/qofbook.h
@@@ -342,8 -335,7 +345,8 @@@ gchar * qof_book_normalize_counter_form
   *    The return value is NULL on error or the format string of the
   *    counter. The string should not be freed.
   */
- const char *qof_book_get_counter_format (const QofBook *book,
- 					 const char *counter_name);
 -gchar *qof_book_get_counter_format (const QofBook *book, const char *counter_name);
++char *qof_book_get_counter_format (const QofBook *book,
++                                   const char *counter_name);
  
  const char* qof_book_get_string_option(const QofBook* book, const char* opt_name);
  void qof_book_set_string_option(QofBook* book, const char* opt_name, const char* opt_val);
diff --cc src/libqof/qof/test/test-qofbook.c
index b6ad804,697c2c6..cc31369
--- a/src/libqof/qof/test/test-qofbook.c
+++ b/src/libqof/qof/test/test-qofbook.c
@@@ -27,11 -22,8 +27,12 @@@ extern "C
  #include "config.h"
  #include <string.h>
  #include <glib.h>
+ #include <inttypes.h>
  #include <unittest-support.h>
 +#ifdef __cplusplus
 +}
 +#endif
 +
  #include "../qof.h"
  #include "../qofbook-p.h"
  #include "../qofbookslots.h"

commit dca13d6248a611c39e9b6f507f88212a9fc386b0
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Sat Mar 19 15:07:33 2016 +0100

    Fix doxigen comment after changed const behaviour

diff --git a/src/libqof/qof/qofbook.h b/src/libqof/qof/qofbook.h
index ab9a0db..35aa303 100644
--- a/src/libqof/qof/qofbook.h
+++ b/src/libqof/qof/qofbook.h
@@ -333,7 +333,7 @@ gchar * qof_book_normalize_counter_format(const gchar *format, gchar **err_msg);
 
 /** Get the format string to use for the named counter.
  *    The return value is NULL on error or the format string of the
- *    counter. The string should not be freed.
+ *    counter. The returned string should be freed by the caller.
  */
 gchar *qof_book_get_counter_format (const QofBook *book, const char *counter_name);
 

commit 6c2c2d73a35e48ff2d9f69f8e9bfe215fc8ceada
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Sat Mar 19 14:32:14 2016 +0100

    Bug 620281 - Adding reversing transaction to bill transactions creates undeleteable transactions

diff --git a/src/engine/Transaction.c b/src/engine/Transaction.c
index 7cc74ea..2425de5 100644
--- a/src/engine/Transaction.c
+++ b/src/engine/Transaction.c
@@ -2570,6 +2570,9 @@ xaccTransReverse (Transaction *orig)
     kvp_val = kvp_value_new_guid(xaccTransGetGUID(trans));
     kvp_frame_set_slot_nc(orig->inst.kvp_data, TRANS_REVERSED_BY, kvp_val);
 
+    /* Make sure the reverse transaction is not read-only */
+    xaccTransClearReadOnly(trans);
+
     qof_instance_set_dirty(QOF_INSTANCE(trans));
     xaccTransCommitEdit(trans);
     return trans;

commit d1e148ef87ee1ff553a1c4cdb3da884e68c97299
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Sat Mar 19 14:05:25 2016 +0100

    Refuse to void read-only transactions.
    
    Add test case to verify

diff --git a/src/engine/Transaction.c b/src/engine/Transaction.c
index 06b8016..7cc74ea 100644
--- a/src/engine/Transaction.c
+++ b/src/engine/Transaction.c
@@ -2466,6 +2466,14 @@ xaccTransVoid(Transaction *trans, const char *reason)
 
     g_return_if_fail(trans && reason);
 
+    /* Prevent voiding transactions that are already marked
+     * read only, for example generated by the business features.
+     */
+    if (xaccTransGetReadOnly (trans))
+    {
+        PWARN ("Refusing to void a read-only transaction!");
+        return;
+    }
     xaccTransBeginEdit(trans);
     frame = trans->inst.kvp_data;
 
diff --git a/src/engine/test/test-transaction-voiding.c b/src/engine/test/test-transaction-voiding.c
index 74b4c71..0528f81 100644
--- a/src/engine/test/test-transaction-voiding.c
+++ b/src/engine/test/test-transaction-voiding.c
@@ -154,7 +154,7 @@ run_test (void)
 
     if (xaccTransGetVoidStatus(transaction))
     {
-        failure("void status reports trus after restoring transaction");
+        failure("void status reports true after restoring transaction");
     }
 
     if (xaccTransGetVoidReason(transaction))
@@ -188,6 +188,21 @@ run_test (void)
         failure("former value (after restore) should be zero");
     }
 
+    xaccTransSetReadOnly (transaction, "Read-only void test");
+    xaccTransVoid(transaction, reason);
+
+    ts = xaccTransGetVoidTime (transaction);
+
+    if (xaccTransGetVoidStatus(transaction))
+    {
+        failure("void status reports true while read-only transaction can't be voided");
+    }
+
+    if (xaccTransGetVoidReason(transaction))
+    {
+        failure("void reason exists while read-only transaction can't be voided");
+    }
+
     return;
 }
 
diff --git a/src/gnome/gnc-plugin-page-register.c b/src/gnome/gnc-plugin-page-register.c
index c3e0240..8ddce64 100644
--- a/src/gnome/gnc-plugin-page-register.c
+++ b/src/gnome/gnc-plugin-page-register.c
@@ -2954,6 +2954,12 @@ gnc_plugin_page_register_cmd_void_transaction (GtkAction *action,
         gnc_error_dialog(NULL, "%s", _("You cannot void a transaction with reconciled or cleared splits."));
         return;
     }
+    reason = xaccTransGetReadOnly (trans);
+    if (reason)
+    {
+        gnc_error_dialog(NULL, _("This transaction is marked read-only with the comment: '%s'"), reason);
+        return;
+    }
 
     if (!gnc_plugin_page_register_finish_pending(GNC_PLUGIN_PAGE(page)))
         return;

commit 0f66e2000574477ee0a36a0cd5f4710d1da0f442
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Sat Mar 19 13:44:10 2016 +0100

    Bug 754209 - Bills can be posted multiple times from "find bill" search results - follow up
    
    This commit adds code to check & repair that removes the read only status of the bogus transactions so the user can go in the AP/AR account and delete these bad transactions.
    
    Translators: this commit introduces a new translatable string.

diff --git a/src/engine/ScrubBusiness.c b/src/engine/ScrubBusiness.c
index 9bc113d..f93619b 100644
--- a/src/engine/ScrubBusiness.c
+++ b/src/engine/ScrubBusiness.c
@@ -462,6 +462,50 @@ gncScrubBusinessLot (GNCLot *lot)
     return splits_deleted;
 }
 
+void
+gncScrubBusinessSplit (Split *split)
+{
+    const gchar *memo = _("Please delete this transaction. Explanation at http://wiki.gnucash.org/wiki/Business_Features_Issues#Double_Posting");
+    Transaction *txn;
+
+    if (!split) return;
+    ENTER ("(split=%p)", split);
+
+    txn = xaccSplitGetParent (split);
+    if (txn)
+    {
+        gchar txntype = xaccTransGetTxnType (txn);
+        const gchar *read_only = xaccTransGetReadOnly (txn);
+        gboolean is_void = xaccTransGetVoidStatus (txn);
+        GNCLot *lot = xaccSplitGetLot (split);
+
+        /* Look for transactions as a result of double posting an invoice or bill
+         * Refer to https://bugzilla.gnome.org/show_bug.cgi?id=754209
+         * to learn how this could have happened in the past.
+         * Characteristics of such transaction are:
+         * - read only
+         * - not voided (to ensure read only is set by the business functions)
+         * - transaction type is none (should be type invoice for proper post transactions)
+         * - assigned to a lot
+         */
+        if ((txntype == TXN_TYPE_NONE) && read_only && !is_void && lot)
+        {
+            gchar *txn_date = qof_print_date (xaccTransGetDateEntered (txn));
+            xaccTransClearReadOnly (txn);
+            xaccSplitSetMemo (split, memo);
+            gnc_lot_remove_split (lot, split);
+            PWARN("Cleared double post status of transaction \"%s\", dated %s. "
+                  "Please delete transaction and verify balance.",
+                  xaccTransGetDescription (txn),
+                  txn_date);
+            g_free (txn_date);
+        }
+
+    }
+
+    LEAVE ("(split=%p)", split);
+}
+
 /* ============================================================== */
 
 void
@@ -505,20 +549,72 @@ gncScrubBusinessAccountLots (Account *acc)
 
 /* ============================================================== */
 
+void
+gncScrubBusinessAccountSplits (Account *acc)
+{
+    SplitList *splits, *node;
+    gint split_count = 0;
+    gint curr_split_no = 1;
+    const gchar *str;
+
+    if (!acc) return;
+    if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
+
+    str = xaccAccountGetName(acc);
+    str = str ? str : "(null)";
+
+    ENTER ("(acc=%s)", str);
+    PINFO ("Cleaning up superfluous lot links in account %s \n", str);
+    xaccAccountBeginEdit(acc);
+
+    splits = xaccAccountGetSplitList(acc);
+    split_count = g_list_length (splits);
+    for (node = splits; node; node = node->next)
+    {
+        Split *split = node->data;
+
+        PINFO("Start processing split %d of %d",
+              curr_split_no, split_count);
+
+        if (split)
+            gncScrubBusinessSplit (split);
+
+        PINFO("Finished processing split %d of %d",
+              curr_split_no, split_count);
+        curr_split_no++;
+    }
+    xaccAccountCommitEdit(acc);
+    LEAVE ("(acc=%s)", str);
+}
+
+/* ============================================================== */
+
+void
+gncScrubBusinessAccount (Account *acc)
+{
+    if (!acc) return;
+    if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
+
+    gncScrubBusinessAccountLots (acc);
+    gncScrubBusinessAccountSplits (acc);
+}
+
+/* ============================================================== */
+
 static void
 lot_scrub_cb (Account *acc, gpointer data)
 {
     if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
-    gncScrubBusinessAccountLots (acc);
+    gncScrubBusinessAccount (acc);
 }
 
 void
-gncScrubBusinessAccountTreeLots (Account *acc)
+gncScrubBusinessAccountTree (Account *acc)
 {
     if (!acc) return;
 
     gnc_account_foreach_descendant(acc, lot_scrub_cb, NULL);
-    gncScrubBusinessAccountLots (acc);
+    gncScrubBusinessAccount (acc);
 }
 
 /* ========================== END OF FILE  ========================= */
diff --git a/src/engine/ScrubBusiness.h b/src/engine/ScrubBusiness.h
index 59af9f0..cbf31d5 100644
--- a/src/engine/ScrubBusiness.h
+++ b/src/engine/ScrubBusiness.h
@@ -54,6 +54,21 @@
  */
 gboolean gncScrubBusinessLot (GNCLot *lot);
 
+/** The gncScrubBusinessSplit() function will fix all issues found with
+ *    the given split.
+ *
+ *    Currently this function only does one thing: check if the split is
+ *    part of a transaction that was generated as the result of a doubly
+ *    posted invoice/bill/credit note. Refer to
+ *    https://bugzilla.gnome.org/show_bug.cgi?id=754209 to learn how this
+ *    could have happened in the past.
+ *    If such a transaction is found, its read-only status is removed and
+ *    a warning is written to the trace file. Considering the user may
+ *    already have added a correcting transaction we leave it up to the user
+ *    to decide whether to also delete the transaction or not.
+ */
+void gncScrubBusinessSplit (Split *split);
+
 /** The gncScrubBusinessAccountLots() function will call
  *    gncScrubBusinessLot() on each lot in the given account.
  *
@@ -63,15 +78,26 @@ gboolean gncScrubBusinessLot (GNCLot *lot);
  */
 void gncScrubBusinessAccountLots (Account *acc);
 
+/** The gncScrubBusinessAccountSplits() function will call
+ *    gncScrubBusinessSplit() on each split in the given account.
+ */
+void gncScrubBusinessAccountSplits (Account *acc);
+
+/** The gncScrubBusinessAccount() function will call
+ *    all scrub functions relevant for a given account
+ *    on condition the account is a business related account
+ *    (Accounts Receivable or Accounts Payable type).
+ *
+ *    This routine is the primary routine for fixing all
+ *    (known) issues in a business account.
+ */
+void gncScrubBusinessAccount (Account *acc);
+
 /** The gncScrubBusinessAccountTreeLots() function will call
- *    gncScrubBusinessAccountLots() on each lot in the given account
+ *    gncScrubBusinessAccount() on the given account
  *    and its sub accounts.
- *
- *    This routine is the primary routine for ensuring that the
- *    lot structure of every lot of a business account is in good
- *    order.
  */
-void gncScrubBusinessAccountTreeLots (Account *acc);
+void gncScrubBusinessAccountTree (Account *acc);
 
 /** @} */
 #endif /* GNC_SCRUBBUSINESS_H */
diff --git a/src/gnome/gnc-plugin-page-account-tree.c b/src/gnome/gnc-plugin-page-account-tree.c
index b126eb3..7864e57 100644
--- a/src/gnome/gnc-plugin-page-account-tree.c
+++ b/src/gnome/gnc-plugin-page-account-tree.c
@@ -1573,7 +1573,7 @@ gnc_plugin_page_account_tree_cmd_scrub (GtkAction *action, GncPluginPageAccountT
     if (g_getenv("GNC_AUTO_SCRUB_LOTS") != NULL)
         xaccAccountScrubLots(account);
 
-    gncScrubBusinessAccountLots(account);
+    gncScrubBusinessAccount(account);
 
 
     gnc_resume_gui_refresh ();
@@ -1595,7 +1595,7 @@ gnc_plugin_page_account_tree_cmd_scrub_sub (GtkAction *action, GncPluginPageAcco
     if (g_getenv("GNC_AUTO_SCRUB_LOTS") != NULL)
         xaccAccountTreeScrubLots(account);
 
-    gncScrubBusinessAccountTreeLots(account);
+    gncScrubBusinessAccountTree(account);
 
     gnc_resume_gui_refresh ();
 }
@@ -1613,7 +1613,7 @@ gnc_plugin_page_account_tree_cmd_scrub_all (GtkAction *action, GncPluginPageAcco
     if (g_getenv("GNC_AUTO_SCRUB_LOTS") != NULL)
         xaccAccountTreeScrubLots(root);
 
-    gncScrubBusinessAccountTreeLots(root);
+    gncScrubBusinessAccountTree(root);
 
     gnc_resume_gui_refresh ();
 }
diff --git a/src/gnome/gnc-plugin-page-register.c b/src/gnome/gnc-plugin-page-register.c
index 5ad77b0..c3e0240 100644
--- a/src/gnome/gnc-plugin-page-register.c
+++ b/src/gnome/gnc-plugin-page-register.c
@@ -3726,7 +3726,10 @@ gnc_plugin_page_register_cmd_scrub_current (GtkAction *action,
     split = gnc_split_register_get_current_split (reg);
     lot = xaccSplitGetLot (split);
     if (lot && xaccAccountIsAPARType (xaccAccountGetType (xaccSplitGetAccount (split))))
+    {
         gncScrubBusinessLot (lot);
+        gncScrubBusinessSplit (split);
+    }
     gnc_resume_gui_refresh();
     LEAVE(" ");
 }
@@ -3773,7 +3776,10 @@ gnc_plugin_page_register_cmd_scrub_all (GtkAction *action,
 
         lot = xaccSplitGetLot (split);
         if (lot && xaccAccountIsAPARType (xaccAccountGetType (xaccSplitGetAccount (split))))
+        {
             gncScrubBusinessLot (lot);
+            gncScrubBusinessSplit (split);
+        }
 
         PINFO("Finished processing split %d of %d",
               curr_split_no, split_count);

commit d45886f73b63ea1672e125d158c28506b8ea305f
Author: John Ralls <jralls at ceridwen.us>
Date:   Thu Mar 17 16:51:46 2016 -0700

    Bug 733164 - Command-H Invokes Help->Help Contents
    
    Override the Gtk-supplied accelerator for the help menu.

diff --git a/src/gnome-utils/ui/osx_accel_map b/src/gnome-utils/ui/osx_accel_map
index d564fa0..9182621 100644
--- a/src/gnome-utils/ui/osx_accel_map
+++ b/src/gnome-utils/ui/osx_accel_map
@@ -41,7 +41,7 @@
 ; (gtk_accel_path "<Actions>/gnc-plugin-business-actions/CustomerNewJobOpenAction" "")
 ; (gtk_accel_path "<Actions>/MenuAdditions/PayableAgingAction" "")
 ; (gtk_accel_path "<Actions>/MenuAdditions/GeneralLedgerAction" "")
-; (gtk_accel_path "<Actions>/MainWindowActions/HelpTutorialAction" "")
+(gtk_accel_path "<Actions>/MainWindowActions/HelpTutorialAction" "")
 ; (gtk_accel_path "<Actions>/gnc-plugin-basic-commands-actions/ActionsSinceLastRunAction" "")
 ; (gtk_accel_path "<Actions>/gnc-plugin-business-actions/BusinessTestReloadOwnerAction" "")
 ; (gtk_accel_path "<Actions>/MenuAdditions/ReceivableAgingAction" "")

commit 3109b6f6c3bbfa939bce0e97177f8d654d06c0ce
Author: John Ralls <jralls at ceridwen.us>
Date:   Thu Mar 17 15:41:51 2016 -0700

    Set the transaction currency during auto-completion.
    
    When auto-completing a transaction that was originally created in another
    account with a different currency the balancing code will try to apply
    conversions in the wrong direction if one edits the transaction. Explicitly
    setting the transaction currency to the current register's currency
    prevents the conversions being applied and allows the transaction to
    balance correctly.

diff --git a/src/register/ledger-core/split-register-control.c b/src/register/ledger-core/split-register-control.c
index 39565a4..493a67a 100644
--- a/src/register/ledger-core/split-register-control.c
+++ b/src/register/ledger-core/split-register-control.c
@@ -885,11 +885,15 @@ gnc_split_register_auto_completion (SplitRegister *reg,
 
         if (gnc_split_register_get_default_account (reg) != NULL)
         {
-            Account *default_account;
+            Account *default_account =
+                gnc_split_register_get_default_account (reg);
+            gnc_commodity *trans_cmdty = xaccTransGetCurrency(trans);
+            gnc_commodity *acct_cmdty = xaccAccountGetCommodity(default_account);
             Split *s;
             int i = 0;
-
-            default_account = gnc_split_register_get_default_account (reg);
+            if (gnc_commodity_is_currency(acct_cmdty) &&
+                !gnc_commodity_equal(trans_cmdty, acct_cmdty))
+                xaccTransSetCurrency(trans, acct_cmdty);
 
             while ((s = xaccTransGetSplit(trans, i)) != NULL)
             {

commit d0e103be086f65db04a006cefc09e6acf48f977a
Author: John Ralls <jralls at ceridwen.us>
Date:   Thu Mar 17 15:37:19 2016 -0700

    Correctly re-value splits when the transaction currency is changed.
    
    When a transaction with existing splits had its currency changed, the
    function would change the values to use the new currency's denominator
    without changing the actual value. The balancing code would then apply
    the price of the the new "other" split to the amount, changing it as
    well. Changing the transaction currency back would convert the value in
    the other split correctly so that it would equal the amount that the
    balancing code wouldn't change anything. I actually detected this bug
    when I wrote the test but didn't recognize it as a bug.
    
    The new code first calculates a new price and then applies it to each
    split so that the transaction balances correctly in the new transaction
    currency. This also round-trips correctly

diff --git a/src/engine/Transaction.c b/src/engine/Transaction.c
index eac94d6..06b8016 100644
--- a/src/engine/Transaction.c
+++ b/src/engine/Transaction.c
@@ -672,6 +672,8 @@ xaccTransCopyFromClipBoard(const Transaction *from_trans, Transaction *to_trans,
     xaccTransBeginEdit(to_trans);
 
     FOR_EACH_SPLIT(to_trans, xaccSplitDestroy(s));
+    g_list_free(to_trans->splits);
+    to_trans->splits = NULL;
 
     xaccTransSetCurrency(to_trans, xaccTransGetCurrency(from_trans));
     xaccTransSetDescription(to_trans, xaccTransGetDescription(from_trans));
@@ -1267,22 +1269,78 @@ xaccTransGetCurrency (const Transaction *trans)
     return trans ? trans->common_currency : NULL;
 }
 
+/* Helper functions for xaccTransSetCurrency */
+static gnc_numeric
+find_new_rate(Transaction *trans, gnc_commodity *curr)
+{
+    GList *node;
+    gnc_numeric rate = gnc_numeric_zero();
+    for (node = trans->splits; node != NULL; node = g_list_next (node))
+    {
+        Split *split = GNC_SPLIT(node->data);
+        gnc_commodity *split_com =
+            xaccAccountGetCommodity(xaccSplitGetAccount(split));
+        if (gnc_commodity_equal(curr, split_com))
+        {
+/* This looks backwards, but the amount of the balancing transaction
+ * that we're going to use it on is in the value's currency. */
+            rate = gnc_numeric_div(xaccSplitGetAmount(split),
+                                   xaccSplitGetValue(split),
+                                   GNC_DENOM_AUTO, GNC_HOW_RND_NEVER);
+            break;
+        }
+    }
+    return rate;
+}
+
+static void
+split_set_new_value(Split* split, gnc_commodity *curr, gnc_commodity *old_curr,
+                    gnc_numeric rate)
+{
+    gnc_commodity *split_com =
+        xaccAccountGetCommodity(xaccSplitGetAccount(split));
+    if (gnc_commodity_equal(curr, split_com))
+        xaccSplitSetValue(split, xaccSplitGetAmount(split));
+    else if (gnc_commodity_equal(old_curr, split_com))
+        xaccSplitSetSharePrice(split, rate);
+    else
+    {
+        gnc_numeric old_rate = gnc_numeric_div(xaccSplitGetValue(split),
+                                               xaccSplitGetAmount(split),
+                                               GNC_DENOM_AUTO,
+                                               GNC_HOW_RND_NEVER);
+        gnc_numeric new_rate = gnc_numeric_div(old_rate, rate, GNC_DENOM_AUTO,
+                                               GNC_HOW_RND_NEVER);
+        xaccSplitSetSharePrice(split, new_rate);
+    }
+}
+
+/**
+ * Set a new currency on a transaction.
+ * When we do that to a transaction with splits we need to re-value
+ * all of the splits in the new currency.
+ * @param trans: The transaction to change
+ * @param curr: The new currency to set.
+ */
 void
 xaccTransSetCurrency (Transaction *trans, gnc_commodity *curr)
 {
-    gint fraction, old_fraction;
-
+    gnc_commodity *old_curr = trans->common_currency;
     if (!trans || !curr || trans->common_currency == curr) return;
     xaccTransBeginEdit(trans);
 
-    old_fraction = gnc_commodity_get_fraction (trans->common_currency);
     trans->common_currency = curr;
-    fraction = gnc_commodity_get_fraction (curr);
-
-    /* avoid needless crud if fraction didn't change */
-    if (fraction != old_fraction)
+    if (old_curr != NULL && trans->splits != NULL)
     {
-        FOR_EACH_SPLIT(trans, xaccSplitSetValue(s, xaccSplitGetValue(s)));
+        gnc_numeric rate = find_new_rate(trans, curr);
+        if (!gnc_numeric_zero_p (rate))
+        {
+            FOR_EACH_SPLIT(trans, split_set_new_value(s, curr, old_curr, rate));
+        }
+        else
+        {
+            FOR_EACH_SPLIT(trans, xaccSplitSetValue(s, xaccSplitGetValue(s)));
+        }
     }
 
     qof_instance_set_dirty(QOF_INSTANCE(trans));
diff --git a/src/engine/test/utest-Transaction.c b/src/engine/test/utest-Transaction.c
index 77569f0..50bb824 100644
--- a/src/engine/test/utest-Transaction.c
+++ b/src/engine/test/utest-Transaction.c
@@ -929,20 +929,18 @@ test_xaccTransEqual (Fixture *fixture, gconstpointer pData)
 
     g_free (check->msg);
     g_free (check2.msg);
-    check->msg = g_strdup("[xaccSplitEqual] amounts differ: 13333/1000 vs 100000/1000");
     check2.msg = g_strdup_printf (
                      "[xaccTransEqual] splits %s and %s differ", split_guid0, split_guid0);
     qof_instance_set_guid (split1, qof_instance_get_guid (split0));
     g_assert (!xaccTransEqual (clone, txn0, TRUE, TRUE, TRUE, TRUE));
     g_assert (xaccTransEqual (clone, txn0, TRUE, FALSE, FALSE, TRUE));
-    g_assert_cmpint (check->hits, ==, 11);
+    g_assert_cmpint (check->hits, ==, 10);
     g_assert_cmpint (check2.hits, ==, 2);
 
     qof_instance_set_guid (xaccTransGetSplit (txn1, 0),
                            qof_instance_get_guid (split0));
     qof_instance_set_guid (xaccTransGetSplit (txn1, 1),
                            qof_instance_get_guid (xaccTransGetSplit (txn0, 1)));
-    g_free (check->msg);
     {
         Split* split00 = xaccTransGetSplit (txn0, 0);
         Split* split01 = xaccTransGetSplit (txn0, 1);
@@ -958,7 +956,7 @@ test_xaccTransEqual (Fixture *fixture, gconstpointer pData)
         test_add_error (&check3);
         g_assert (!xaccTransEqual (txn1, txn0, TRUE, TRUE, TRUE, TRUE));
         g_assert (xaccTransEqual (txn1, txn0, TRUE, TRUE, FALSE, TRUE));
-        g_assert_cmpint (check->hits, ==, 12);
+        g_assert_cmpint (check->hits, ==, 11);
         g_assert_cmpint (check2.hits, ==, 3);
         g_assert_cmpint (check3.hits, ==, 0);
 

commit c2ce20443453f8a9f1f1e9b1a26eabfb1a029122
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Thu Mar 17 23:06:52 2016 +0100

    Use PRIi64 instead of PRIx64
    
    We obviously want our business counters to use integral numbers instead of hexadecimal...

diff --git a/src/libqof/qof/qofbook.c b/src/libqof/qof/qofbook.c
index c789ada..b8e5fac 100644
--- a/src/libqof/qof/qofbook.c
+++ b/src/libqof/qof/qofbook.c
@@ -513,7 +513,7 @@ qof_book_get_counter_format(const QofBook *book, const char *counter_name)
     if (!norm_format)
     {
         /* Use the default format */
-        norm_format = g_strdup ("%.6" PRIx64);
+        norm_format = g_strdup ("%.6" PRIi64);
     }
     return norm_format;
 }
@@ -525,7 +525,7 @@ qof_book_normalize_counter_format(const gchar *p, gchar **err_msg)
             G_GINT64_FORMAT,
             "lli",
             "I64i",
-            PRIx64,
+            PRIi64,
             "li",
             NULL,
     };
@@ -645,7 +645,7 @@ qof_book_normalize_counter_format_internal(const gchar *p,
 
     /* Copy the string we have so far and add normalized format specifier for long int */
     aux_str = g_strndup (base, p - base);
-    normalized_str = g_strconcat (aux_str, PRIx64, NULL);
+    normalized_str = g_strconcat (aux_str, PRIi64, NULL);
     g_free (aux_str);
 
     /* Skip length modifier / conversion specifier */
diff --git a/src/libqof/qof/test/test-qofbook.c b/src/libqof/qof/test/test-qofbook.c
index c11876c..697c2c6 100644
--- a/src/libqof/qof/test/test-qofbook.c
+++ b/src/libqof/qof/test/test-qofbook.c
@@ -147,7 +147,7 @@ test_book_normalize_counter( void )
     {
         g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert_cmpstr( r, == , "Test - %" PRIx64);
+    g_assert_cmpstr( r, == , "Test - %" PRIi64);
     g_assert(err_msg == NULL);
     g_free(r);
 
@@ -157,7 +157,7 @@ test_book_normalize_counter( void )
     {
         g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert_cmpstr( r, == , "Test - %" PRIx64);
+    g_assert_cmpstr( r, == , "Test - %" PRIi64);
     g_assert(err_msg == NULL);
     g_free(r);
 
@@ -167,47 +167,47 @@ test_book_normalize_counter( void )
     {
         g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert_cmpstr( r, == , "Test - %" PRIx64);
+    g_assert_cmpstr( r, == , "Test - %" PRIi64);
     g_assert(err_msg == NULL);
     g_free(r);
 
-    /* Test the posix' PRIx64 */
-    r = qof_book_normalize_counter_format("Test - %" PRIx64, &err_msg);
+    /* Test the posix' PRIi64 */
+    r = qof_book_normalize_counter_format("Test - %" PRIi64, &err_msg);
     if (!r && g_test_verbose())
     {
         g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert_cmpstr( r, == , "Test - %" PRIx64);
+    g_assert_cmpstr( r, == , "Test - %" PRIi64);
     g_assert(err_msg == NULL);
     g_free(r);
 
-    /* Test the posix' PRIx64 with precision field */
-    r = qof_book_normalize_counter_format("Test - %.3" PRIx64, &err_msg);
+    /* Test the posix' PRIi64 with precision field */
+    r = qof_book_normalize_counter_format("Test - %.3" PRIi64, &err_msg);
     if (!r && g_test_verbose())
     {
         g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert_cmpstr( r, == , "Test - %.3" PRIx64);
+    g_assert_cmpstr( r, == , "Test - %.3" PRIi64);
     g_assert(err_msg == NULL);
     g_free(r);
 
-    /* Test the posix' PRIx64 with width field */
-    r = qof_book_normalize_counter_format("Test - %5" PRIx64, &err_msg);
+    /* Test the posix' PRIi64 with width field */
+    r = qof_book_normalize_counter_format("Test - %5" PRIi64, &err_msg);
     if (!r && g_test_verbose())
     {
         g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert_cmpstr( r, == , "Test - %5" PRIx64);
+    g_assert_cmpstr( r, == , "Test - %5" PRIi64);
     g_assert(err_msg == NULL);
     g_free(r);
 
-    /* Test the posix' PRIx64 with width and precision field */
-    r = qof_book_normalize_counter_format("Test - %5.4" PRIx64, &err_msg);
+    /* Test the posix' PRIi64 with width and precision field */
+    r = qof_book_normalize_counter_format("Test - %5.4" PRIi64, &err_msg);
     if (!r && g_test_verbose())
     {
         g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert_cmpstr( r, == , "Test - %5.4" PRIx64);
+    g_assert_cmpstr( r, == , "Test - %5.4" PRIi64);
     g_assert(err_msg == NULL);
     g_free(r);
 
@@ -217,7 +217,7 @@ test_book_normalize_counter( void )
     {
         g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert_cmpstr( r, == , "Test - %" PRIx64);
+    g_assert_cmpstr( r, == , "Test - %" PRIi64);
     g_assert(err_msg == NULL);
     g_free(r);
 
@@ -227,7 +227,7 @@ test_book_normalize_counter( void )
     {
         g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert_cmpstr( r, == , "Test - %" PRIx64);
+    g_assert_cmpstr( r, == , "Test - %" PRIi64);
     g_assert(err_msg == NULL);
     g_free(r);
 
@@ -366,11 +366,11 @@ test_book_get_counter_format ( Fixture *fixture, gconstpointer pData )
 
     g_test_message( "Testing counter format with existing counter" );
     r = qof_book_get_counter_format( fixture->book, counter_name );
-    g_assert_cmpstr( r, == , "%.6" PRIx64);
+    g_assert_cmpstr( r, == , "%.6" PRIi64);
 
     g_test_message( "Testing counter format for default value" );
     r = qof_book_get_counter_format( fixture->book, counter_name );
-    g_assert_cmpstr( r, == , "%.6" PRIx64);
+    g_assert_cmpstr( r, == , "%.6" PRIi64);
 }
 
 static void

commit a27abf766a1c0148a497d0f8000da92f0228398e
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Thu Mar 17 22:37:15 2016 +0100

    Bug 728722 - Setting number format details appear wrong in Help, section 10.3.4. Counters Book Options Tab
    
    This is a follow-up commit to fix the core of the issue.
    With this commit gnucash is more liberal at accepting
    counter formats. It will accept either li, lli, I64i and
    whatever is defined for G_GINT_64 or PRIx64 on the user's
    platform. Internally the code will always convert the
    specifier set by the user with PRIx64, which should always
    be the correct one on any platform.
    
    Additionally a few extra tests were added to stress the
    counter format code a bit more.

diff --git a/src/libqof/qof/qofbook-p.h b/src/libqof/qof/qofbook-p.h
index 4560c83..393b2b0 100644
--- a/src/libqof/qof/qofbook-p.h
+++ b/src/libqof/qof/qofbook-p.h
@@ -61,13 +61,16 @@ backends (when reading the GncGUID from the data source). */
 #define qof_book_set_guid(book,guid)    \
          qof_instance_set_guid(QOF_INSTANCE(book), guid)
 
-/** Validate a counter format string with the given
- *    G_GINT64_FORMAT. Returns an error message if the format string
- *    was invalid, or NULL if it is ok. The caller should free the
- *    error message with g_free.
+/** Validate a counter format string with a given format specifier.
+ *    If valid, returns a normalized format string,
+ *    that is whatever long int specifier was used will be replaced with the value of
+ *    the posix "PRIx64" macro.
+ *    If not valid returns NULL and optionally set an error message is a non-null
+ *    err_msg parameter was passed.
+ *    The caller should free the returned format string and  error message with g_free.
  */
-gchar *qof_book_validate_counter_format_internal(const gchar *p,
-        const gchar* gint64_format);
+gchar *qof_book_normalize_counter_format_internal(const gchar *p,
+        const gchar* gint64_format, gchar **err_msg);
 
 /** This debugging function can be used to traverse the book structure
  *    and all subsidiary structures, printing out which structures
diff --git a/src/libqof/qof/qofbook.c b/src/libqof/qof/qofbook.c
index e2b6aa2..c789ada 100644
--- a/src/libqof/qof/qofbook.c
+++ b/src/libqof/qof/qofbook.c
@@ -39,6 +39,7 @@
 #include <string.h>
 
 #include <glib.h>
+#include <inttypes.h>
 
 #include "qof.h"
 #include "qofevent-p.h"
@@ -407,6 +408,7 @@ qof_book_increment_and_format_counter (QofBook *book, const char *counter_name)
     KvpValue *value;
     gint64 counter;
     gchar* format;
+    gchar* result;
 
     if (!book)
     {
@@ -456,16 +458,19 @@ qof_book_increment_and_format_counter (QofBook *book, const char *counter_name)
     }
 
     /* Generate a string version of the counter */
-    return g_strdup_printf(format, counter);
+    result = g_strdup_printf(format, counter);
+    g_free (format);
+    return result;
 }
 
 gchar *
 qof_book_get_counter_format(const QofBook *book, const char *counter_name)
 {
     KvpFrame *kvp;
-    gchar *format;
+    gchar *user_format = NULL;
+    gchar *norm_format = NULL;
     KvpValue *value;
-    gchar *error;
+    gchar *error = NULL;
 
     if (!book)
     {
@@ -488,49 +493,75 @@ qof_book_get_counter_format(const QofBook *book, const char *counter_name)
         return NULL;
     }
 
-    format = NULL;
-
     /* Get the format string */
     value = kvp_frame_get_slot_path (kvp, "counter_formats", counter_name, NULL);
     if (value)
     {
-        format = kvp_value_get_string (value);
-        error = qof_book_validate_counter_format(format);
-        if (error != NULL)
+        user_format = kvp_value_get_string (value);
+        norm_format = qof_book_normalize_counter_format(user_format, &error);
+        if (!norm_format)
         {
-            PWARN("Invalid counter format string. Format string: '%s' Counter: '%s' Error: '%s')", format, counter_name, error);
+            PWARN("Invalid counter format string. Format string: '%s' Counter: '%s' Error: '%s')", user_format, counter_name, error);
             /* Invalid format string */
-            format = NULL;
+            user_format = NULL;
             g_free(error);
         }
     }
 
     /* If no (valid) format string was found, use the default format
      * string */
-    if (!format)
+    if (!norm_format)
     {
         /* Use the default format */
-        format = "%.6" G_GINT64_FORMAT;
+        norm_format = g_strdup ("%.6" PRIx64);
     }
-    return format;
+    return norm_format;
 }
 
 gchar *
-qof_book_validate_counter_format(const gchar *p)
-{
-    return qof_book_validate_counter_format_internal(p, G_GINT64_FORMAT);
+qof_book_normalize_counter_format(const gchar *p, gchar **err_msg)
+{
+    const gchar *valid_formats [] = {
+            G_GINT64_FORMAT,
+            "lli",
+            "I64i",
+            PRIx64,
+            "li",
+            NULL,
+    };
+    int i = 0;
+    gchar *normalized_spec = NULL;
+
+    while (valid_formats[i])
+    {
+
+        if (err_msg && *err_msg)
+        {
+            g_free (*err_msg);
+            *err_msg = NULL;
+        }
+
+        normalized_spec = qof_book_normalize_counter_format_internal(p, valid_formats[i], err_msg);
+        if (normalized_spec)
+            return normalized_spec;  /* Found a valid format specifier, return */
+        i++;
+    }
+
+    return NULL;
 }
 
 gchar *
-qof_book_validate_counter_format_internal(const gchar *p,
-        const gchar *gint64_format)
+qof_book_normalize_counter_format_internal(const gchar *p,
+        const gchar *gint64_format, gchar **err_msg)
 {
-    const gchar *conv_start, *tmp = NULL;
+    const gchar *conv_start, *base, *tmp = NULL;
+    gchar *normalized_str = NULL, *aux_str = NULL;
 
     /* Validate a counter format. This is a very simple "parser" that
      * simply checks for a single gint64 conversion specification,
      * allowing all modifiers and flags that printf(3) specifies (except
      * for the * width and precision, which need an extra argument). */
+    base = p;
 
     /* Skip a prefix of any character except % */
     while (*p)
@@ -551,7 +582,11 @@ qof_book_validate_counter_format_internal(const gchar *p,
     }
 
     if (!*p)
-        return g_strdup("Format string ended without any conversion specification");
+    {
+        if (err_msg)
+            *err_msg = g_strdup("Format string ended without any conversion specification");
+        return NULL;
+    }
 
     /* Store the start of the conversion for error messages */
     conv_start = p;
@@ -563,6 +598,13 @@ qof_book_validate_counter_format_internal(const gchar *p,
      * specification (e.g. "li" on Unix, "I64i" on Windows). */
     tmp = strstr(p, gint64_format);
 
+    if (!tmp)
+    {
+        if (err_msg)
+            *err_msg = g_strdup_printf("Format string doesn't contain requested format specifier: %s", gint64_format);
+        return NULL;
+    }
+
     /* Skip any number of flag characters */
     while (*p && (tmp != p) && strchr("#0- +'I", *p))
     {
@@ -570,39 +612,45 @@ qof_book_validate_counter_format_internal(const gchar *p,
         tmp = strstr(p, gint64_format);
     }
 
-    /* Skip any number of field width digits */
-    while (*p && (tmp != p) && strchr("0123456789", *p))
+    /* Skip any number of field width digits,
+     * and precision specifier digits (including the leading dot) */
+    while (*p && (tmp != p) && strchr("0123456789.", *p))
     {
         p++;
         tmp = strstr(p, gint64_format);
     }
 
-    /* A precision specifier always starts with a dot */
-    if (*p && *p == '.')
+    if (!*p)
     {
-        /* Skip the . */
-        p++;
-        /* Skip any number of precision digits */
-        while (*p && strchr("0123456789", *p)) p++;
+        if (err_msg)
+            *err_msg = g_strdup_printf("Format string ended during the conversion specification. Conversion seen so far: %s", conv_start);
+        return NULL;
     }
 
-    if (!*p)
-        return g_strdup_printf("Format string ended during the conversion specification. Conversion seen so far: %s", conv_start);
-
     /* See if the format string starts with the correct format
      * specification. */
     tmp = strstr(p, gint64_format);
     if (tmp == NULL)
     {
-        return g_strdup_printf("Invalid length modifier and/or conversion specifier ('%.4s'), it should be: %s", p, gint64_format);
+        if (err_msg)
+            *err_msg = g_strdup_printf("Invalid length modifier and/or conversion specifier ('%.4s'), it should be: %s", p, gint64_format);
+        return NULL;
     }
     else if (tmp != p)
     {
-        return g_strdup_printf("Garbage before length modifier and/or conversion specifier: '%*s'", (int)(tmp - p), p);
+        if (err_msg)
+            *err_msg = g_strdup_printf("Garbage before length modifier and/or conversion specifier: '%*s'", (int)(tmp - p), p);
+        return NULL;
     }
 
+    /* Copy the string we have so far and add normalized format specifier for long int */
+    aux_str = g_strndup (base, p - base);
+    normalized_str = g_strconcat (aux_str, PRIx64, NULL);
+    g_free (aux_str);
+
     /* Skip length modifier / conversion specifier */
     p += strlen(gint64_format);
+    tmp = p;
 
     /* Skip a suffix of any character except % */
     while (*p)
@@ -617,14 +665,24 @@ qof_book_validate_counter_format_internal(const gchar *p,
         /* Break on a single percent mark, which is the start of the
          * conversion specification */
         if (*p == '%')
-            return g_strdup_printf("Format string contains unescaped %% signs (or multiple conversion specifications) at '%s'", p);
+        {
+            if (err_msg)
+                *err_msg = g_strdup_printf("Format string contains unescaped %% signs (or multiple conversion specifications) at '%s'", p);
+            g_free (normalized_str);
+            return NULL;
+        }
         /* Skip all other characters */
         p++;
     }
 
+    /* Add the suffix to our normalized string */
+    aux_str = normalized_str;
+    normalized_str = g_strconcat (aux_str, tmp, NULL);
+    g_free (aux_str);
+
     /* If we end up here, the string was valid, so return no error
      * message */
-    return NULL;
+    return normalized_str;
 }
 
 /* Determine whether this book uses trading accounts */
diff --git a/src/libqof/qof/qofbook.h b/src/libqof/qof/qofbook.h
index fef6d92..ab9a0db 100644
--- a/src/libqof/qof/qofbook.h
+++ b/src/libqof/qof/qofbook.h
@@ -322,11 +322,14 @@ gint64 qof_book_get_counter (QofBook *book, const char *counter_name);
  */
 gchar *qof_book_increment_and_format_counter (QofBook *book, const char *counter_name);
 
-/** Validate a counter format string. Returns an error message if the
- *    format string was invalid, or NULL if it is ok. The caller should
- *    free the error message with g_free.
+/** Validate a counter format string. If valid, returns a normalized format string,
+ *    that is whatever long int specifier was used will be replaced with the value of
+ *    the posix "PRIx64" macro.
+ *    If not valid returns NULL and optionally set an error message is a non-null
+ *    err_msg parameter was passed.
+ *    The caller should free the returned format string and  error message with g_free.
  */
-gchar * qof_book_validate_counter_format(const gchar *format);
+gchar * qof_book_normalize_counter_format(const gchar *format, gchar **err_msg);
 
 /** Get the format string to use for the named counter.
  *    The return value is NULL on error or the format string of the
diff --git a/src/libqof/qof/test/test-qofbook.c b/src/libqof/qof/test/test-qofbook.c
index ae53b28..c11876c 100644
--- a/src/libqof/qof/test/test-qofbook.c
+++ b/src/libqof/qof/test/test-qofbook.c
@@ -22,6 +22,7 @@
 #include "config.h"
 #include <string.h>
 #include <glib.h>
+#include <inttypes.h>
 #include <unittest-support.h>
 #include "../qof.h"
 #include "../qofbook-p.h"
@@ -123,64 +124,133 @@ test_book_readonly( Fixture *fixture, gconstpointer pData )
     g_assert( qof_book_is_readonly( fixture->book ) );
 }
 static void
-test_book_validate_counter( void )
+test_book_normalize_counter( void )
 {
-    gchar *r;
+    gchar *r, *err_msg = NULL;
     g_test_bug("644036");
+    g_test_bug("728722");
 
     /* Test for detection of missing format conversion */
-    r = qof_book_validate_counter_format("This string is missing the conversion specifier");
-    g_assert(r);
-    if (r && g_test_verbose())
+    r = qof_book_normalize_counter_format("This string is missing the conversion specifier", &err_msg);
+    g_assert(!r);
+    g_assert(err_msg);
+    if (!r && g_test_verbose())
     {
-        g_test_message("Counter format validation correctly failed: %s", r);
+        g_test_message("Counter format normalization correctly failed: %s", err_msg);
     }
-    g_free(r);
+    g_free(err_msg);
+    err_msg = NULL;
 
     /* Test the usual Linux/Unix G_GINT64_FORMAT */
-    r = qof_book_validate_counter_format_internal("Test - %li", "li");
-    if (r && g_test_verbose())
+    r = qof_book_normalize_counter_format("Test - %li", &err_msg);
+    if (!r && g_test_verbose())
     {
-        g_test_message("Counter format validation erroneously failed: %s", r);
+        g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert(r == NULL);
+    g_assert_cmpstr( r, == , "Test - %" PRIx64);
+    g_assert(err_msg == NULL);
     g_free(r);
 
     /* Test the Windows G_GINT64_FORMAT */
-    r = qof_book_validate_counter_format_internal("Test - %I64i", "I64i");
-    if (r && g_test_verbose())
+    r = qof_book_normalize_counter_format("Test - %I64i", &err_msg);
+    if (!r && g_test_verbose())
     {
-        g_test_message("Counter format validation erroneously failed: %s", r);
+        g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert(r == NULL);
+    g_assert_cmpstr( r, == , "Test - %" PRIx64);
+    g_assert(err_msg == NULL);
     g_free(r);
 
-    /* Test the system's GINT64_FORMAT */
-    r = qof_book_validate_counter_format("Test - %" G_GINT64_FORMAT);
-    if (r && g_test_verbose())
+    /* Test the system's G_INT64_FORMAT */
+    r = qof_book_normalize_counter_format("Test - %" G_GINT64_FORMAT, &err_msg);
+    if (!r && g_test_verbose())
     {
-        g_test_message("Counter format validation erroneously failed: %s", r);
+        g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert(r == NULL);
+    g_assert_cmpstr( r, == , "Test - %" PRIx64);
+    g_assert(err_msg == NULL);
     g_free(r);
 
-    /* Test an erroneous Windows G_GINT64_FORMAT */
-    r = qof_book_validate_counter_format_internal("Test - %li", "I64i");
-    if (r && g_test_verbose())
+    /* Test the posix' PRIx64 */
+    r = qof_book_normalize_counter_format("Test - %" PRIx64, &err_msg);
+    if (!r && g_test_verbose())
     {
-        g_test_message("Counter format validation correctly failed: %s", r);
+        g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert(r);
+    g_assert_cmpstr( r, == , "Test - %" PRIx64);
+    g_assert(err_msg == NULL);
     g_free(r);
 
-    /* Test an erroneous Linux G_GINT64_FORMAT */
-    r = qof_book_validate_counter_format_internal("Test - %I64i", "li");
-    if (r && g_test_verbose())
+    /* Test the posix' PRIx64 with precision field */
+    r = qof_book_normalize_counter_format("Test - %.3" PRIx64, &err_msg);
+    if (!r && g_test_verbose())
+    {
+        g_test_message("Counter format normalization erroneously failed: %s", err_msg);
+    }
+    g_assert_cmpstr( r, == , "Test - %.3" PRIx64);
+    g_assert(err_msg == NULL);
+    g_free(r);
+
+    /* Test the posix' PRIx64 with width field */
+    r = qof_book_normalize_counter_format("Test - %5" PRIx64, &err_msg);
+    if (!r && g_test_verbose())
+    {
+        g_test_message("Counter format normalization erroneously failed: %s", err_msg);
+    }
+    g_assert_cmpstr( r, == , "Test - %5" PRIx64);
+    g_assert(err_msg == NULL);
+    g_free(r);
+
+    /* Test the posix' PRIx64 with width and precision field */
+    r = qof_book_normalize_counter_format("Test - %5.4" PRIx64, &err_msg);
+    if (!r && g_test_verbose())
+    {
+        g_test_message("Counter format normalization erroneously failed: %s", err_msg);
+    }
+    g_assert_cmpstr( r, == , "Test - %5.4" PRIx64);
+    g_assert(err_msg == NULL);
+    g_free(r);
+
+    /* Test the usual Linux/Unix G_GINT64_FORMAT */
+    r = qof_book_normalize_counter_format_internal("Test - %li", "li", &err_msg);
+    if (!r && g_test_verbose())
+    {
+        g_test_message("Counter format normalization erroneously failed: %s", err_msg);
+    }
+    g_assert_cmpstr( r, == , "Test - %" PRIx64);
+    g_assert(err_msg == NULL);
+    g_free(r);
+
+    /* Test the Windows G_GINT64_FORMAT */
+    r = qof_book_normalize_counter_format_internal("Test - %I64i", "I64i", &err_msg);
+    if (!r && g_test_verbose())
     {
-        g_test_message("Counter format validation correctly failed: %s", r);
+        g_test_message("Counter format normalization erroneously failed: %s", err_msg);
     }
-    g_assert(r);
+    g_assert_cmpstr( r, == , "Test - %" PRIx64);
+    g_assert(err_msg == NULL);
     g_free(r);
+
+    /* Test an erroneous Windows G_GINT64_FORMAT */
+    r = qof_book_normalize_counter_format_internal("Test - %li", "I64i", &err_msg);
+    g_assert(!r);
+    g_assert(err_msg);
+    if (!r && g_test_verbose())
+    {
+        g_test_message("Counter format normalization correctly failed: %s", err_msg);
+    }
+    g_free(err_msg);
+    err_msg = NULL;
+
+    /* Test an erroneous Linux G_GINT64_FORMAT */
+    r = qof_book_normalize_counter_format_internal("Test - %I64i", "li", &err_msg);
+    g_assert(!r);
+    g_assert(err_msg);
+    if (!r && g_test_verbose())
+    {
+        g_test_message("Counter format normalization correctly failed: %s", err_msg);
+    }
+    g_free(err_msg);
 }
 
 static void
@@ -289,18 +359,18 @@ test_book_get_counter_format ( Fixture *fixture, gconstpointer pData )
     g_free( test_struct.msg );
 
     g_test_message( "Testing counter format when counter name is empty string" );
-    r = qof_book_get_counter_format( fixture->book, NULL );
+    r = qof_book_get_counter_format( fixture->book, "" );
     g_assert_cmpstr( r, == , NULL );
     g_assert( g_strrstr( test_struct.msg, err_invalid_cnt ) != NULL );
     g_free( test_struct.msg );
 
     g_test_message( "Testing counter format with existing counter" );
     r = qof_book_get_counter_format( fixture->book, counter_name );
-    g_assert_cmpstr( r, == , "%.6" G_GINT64_FORMAT);
+    g_assert_cmpstr( r, == , "%.6" PRIx64);
 
     g_test_message( "Testing counter format for default value" );
     r = qof_book_get_counter_format( fixture->book, counter_name );
-    g_assert_cmpstr( r, == , "%.6" G_GINT64_FORMAT);
+    g_assert_cmpstr( r, == , "%.6" PRIx64);
 }
 
 static void
@@ -331,7 +401,7 @@ test_book_increment_and_format_counter ( Fixture *fixture, gconstpointer pData )
     g_free( test_struct.msg );
 
     g_test_message( "Testing increment and format when counter name is empty string" );
-    r = qof_book_increment_and_format_counter( fixture->book, NULL );
+    r = qof_book_increment_and_format_counter( fixture->book, "" );
     g_assert_cmpstr( r, == , NULL );
     g_free( r );
     g_assert( g_strrstr( test_struct.msg, err_invalid_cnt ) != NULL );
@@ -757,7 +827,7 @@ void
 test_suite_qofbook ( void )
 {
     GNC_TEST_ADD( suitename, "readonly", Fixture, NULL, setup, test_book_readonly, teardown );
-    GNC_TEST_ADD_FUNC( suitename, "validate counter", test_book_validate_counter );
+    GNC_TEST_ADD_FUNC( suitename, "validate counter", test_book_normalize_counter );
     GNC_TEST_ADD( suitename, "get string option", Fixture, NULL, setup, test_book_get_string_option, teardown );
     GNC_TEST_ADD( suitename, "set string option", Fixture, NULL, setup, test_book_set_string_option, teardown );
     GNC_TEST_ADD( suitename, "session not saved", Fixture, NULL, setup, test_book_session_not_saved, teardown );

commit 8117a7c17f5c993965a5cc2247a18b563af503d7
Author: Mike Evans <mikee at saxicola.co.uk>
Date:   Wed Mar 16 12:18:26 2016 +0000

    Bug 754209 - Bills can be posted from "find bill" search results
    even if bill is already posted and results in extra $ posted to A/P
    
    This adds a test in gncInvoice to return NULL it already posted.
    
    Adds checks in dialog-invoice to test for already posted invoices. Messages
    user and refuses to post entire selection if more than one selected.
    
    Translators: This adds a message string.

diff --git a/src/business/business-gnome/dialog-invoice.c b/src/business/business-gnome/dialog-invoice.c
index c5b2b6e..b522a94 100644
--- a/src/business/business-gnome/dialog-invoice.c
+++ b/src/business/business-gnome/dialog-invoice.c
@@ -2886,17 +2886,41 @@ static void post_one_invoice_cb(gpointer data, gpointer user_data)
     gnc_invoice_post(iw, post_params);
 }
 
+static void gnc_invoice_is_posted(gpointer inv, gpointer test_value)
+{
+    GncInvoice *invoice = inv;
+    gboolean *test = (gboolean*)test_value;
+      
+    if (gncInvoiceIsPosted (invoice))
+    {
+        *test = TRUE;
+    }
+}
+
+
 static void
 multi_post_invoice_cb (GList *invoice_list, gpointer user_data)
 {
     struct post_invoice_params post_params;
+    gboolean test;
     InvoiceWindow *iw;
 
     if (g_list_length(invoice_list) == 0)
         return;
-
     // Get the posting parameters for these invoices
     iw = gnc_ui_invoice_edit(invoice_list->data);
+    test = FALSE;
+    gnc_suspend_gui_refresh (); // Turn off GUI refresh for the duration.
+    // Check if any of the selected invoices have already been posted.
+    g_list_foreach(invoice_list, gnc_invoice_is_posted, &test);
+    gnc_resume_gui_refresh ();
+    if (test)
+    {
+        gnc_error_dialog (iw_get_window(iw), "%s",
+                          _("One or more selected invoices have already been posted.\nRe-check you selection."));
+        return;
+    }
+    
     if (!gnc_dialog_post_invoice(iw, _("Do you really want to post these invoices?"),
                                  &post_params.ddue, &post_params.postdate,
                                  &post_params.memo, &post_params.acc,
diff --git a/src/engine/gncInvoice.c b/src/engine/gncInvoice.c
index 9b9fed4..50f3373 100644
--- a/src/engine/gncInvoice.c
+++ b/src/engine/gncInvoice.c
@@ -1389,7 +1389,8 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc,
     const GncOwner *owner;
 
     if (!invoice || !acc) return NULL;
-
+    if (gncInvoiceIsPosted (invoice)) return NULL;
+    
     gncInvoiceBeginEdit (invoice);
     book = qof_instance_get_book(invoice);
 

commit 1121cd0795f56ae6439848a6a0c2940584e2b4a5
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Mon Mar 14 22:16:14 2016 +0100

    Bug 720934 - Barcharts with many data points have overlapping x-axis labels
    
    Depending on the available chart width x-axis labels will be pruned
    from the full list to ensure the labels that are printed will
    always be readable.

diff --git a/src/report/report-system/html-barchart.scm b/src/report/report-system/html-barchart.scm
index c1b62c8..2b24376 100644
--- a/src/report/report-system/html-barchart.scm
+++ b/src/report/report-system/html-barchart.scm
@@ -424,6 +424,14 @@
                       (loop (+ 1 col) 1)))))
 
 
+            (push "var all_ticks = [")
+            (for-each
+                (lambda (val)
+                    (push "\"")
+                    (push val)
+                    (push "\","))
+                (gnc:html-barchart-row-labels barchart))
+            (push "];\n")
             (push "var options = {
                    shadowAlpha: 0.07,
                    stackSeries: false,
@@ -493,28 +501,43 @@
                 (push "  options.axes.yaxis.label = \"")
                 (push y-label)
                 (push "\";\n")))
-            (if (and (string? row-labels) (> (string-length row-labels) 0))
-              (begin 
-                (push "  options.axes.xaxis.ticks = [")
-                (for-each (lambda (val)
-                        (push "\"")
-                        (push val)
-                        (push "\","))
-                    (gnc:html-barchart-row-labels barchart))
-                (push "];\n")))
+            (push "  options.axes.xaxis.ticks = all_ticks;\n")
 
 
             (push "$.jqplot.config.enablePlugins = true;\n")
-            (push "var plot = $.jqplot('")(push chart-id)(push"', data, options);
-
-  function formatTooltip(str, seriesIndex, pointIndex) {
-      if (options.axes.xaxis.ticks[pointIndex] !== undefined)
-          x = options.axes.xaxis.ticks[pointIndex];
-      else
-          x = pointIndex;
-      y = data[seriesIndex][pointIndex][1].toFixed(2);
-      return options.series[seriesIndex].label + '<br/>' + x + '<br/><b>' + y + '</b>';
-  }\n") 
+            (push "$(document).ready(function() {
+var plot = $.jqplot('")(push chart-id)(push"', data, options);
+var int_chart_width = document.getElementById(\"")(push chart-id)(push"\").getElementsByClassName(\"jqplot-zoom-canvas\")[0].width;
+plot.axes.xaxis.ticks = getVisualTicks(int_chart_width);
+plot.replot();
+});
+
+function formatTooltip(str, seriesIndex, pointIndex) {
+    if (options.axes.xaxis.ticks[pointIndex] !== undefined)
+        x = options.axes.xaxis.ticks[pointIndex];
+    else
+        x = pointIndex;
+    y = data[seriesIndex][pointIndex][1].toFixed(2);
+    return options.series[seriesIndex].label + '<br/>' + x + '<br/><b>' + y + '</b>';
+}
+
+function getVisualTicks(chart_width) {
+    var num_ticks = all_ticks.length;
+    var label_width = 25;
+    var num_labels = chart_width / label_width;
+    var show_every_nth_label = Math.ceil (num_ticks / num_labels);
+    var visual_ticks = [];
+
+    if (show_every_nth_label == 0)
+        show_every_nth_label = 1;
+    for (counter = 0; counter < all_ticks.length; counter++) {
+        if ((counter % show_every_nth_label) == 0)
+            visual_ticks.push (all_ticks[counter]);
+        else
+            visual_ticks.push (' ');
+    }
+    return visual_ticks;
+}\n")
 
             (push "});\n</script>")
 

commit ab35b571c3b75e2c402f8422b3fdc83fbc5b1011
Author: Chris Good <chris.good at ozemail.com.au>
Date:   Sun Feb 28 14:45:14 2016 +1100

    Add Tip Of The Day re using multiple windows to do comparisons - Bug 762800
    
    Note: this introduces an untranslated string to the stable series

diff --git a/doc/tip_of_the_day.list.in b/doc/tip_of_the_day.list.in
index 99584a9..964090c 100644
--- a/doc/tip_of_the_day.list.in
+++ b/doc/tip_of_the_day.list.in
@@ -102,3 +102,7 @@ Douglas Adams, \"The Restaurant at the End of the Universe\"")
  N_( "To search through all your transactions, start a search (Edit -> \
 Find...) from the main accounts hierarchy page. To limit your search \
 to a single account, start the search from that account's register.")
+
+ N_( "To visually compare on screen the contents of 2 tabs, \
+in one of the tabs, select Window -> New Window with Page \
+from the menu to duplicate that tab in a new window.")

commit ee70922c5bcc38f6ef9a09050c12219f1ad634e9
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Sun Mar 13 13:54:46 2016 +0100

    Fixup previous commit
    
    Forgot to add my changes before pushing...

diff --git a/src/gnome/gnc-budget-view.c b/src/gnome/gnc-budget-view.c
index b70ccb5..e691b5e 100644
--- a/src/gnome/gnc-budget-view.c
+++ b/src/gnome/gnc-budget-view.c
@@ -899,8 +899,14 @@ totals_col_source(GtkTreeViewColumn *col, GtkCellRenderer *cell,
     gchar amtbuff[100]; //FIXME: overkill, where's the #define?
     
     gint width; // FIXME: VARIABLE NOT NEEDED?
-    
+
     gint i;
+    gint num_top_accounts;
+
+    gnc_numeric totalincome = gnc_numeric_zero();
+    gnc_numeric totalexpenses = gnc_numeric_zero();
+    gnc_numeric totalassets = gnc_numeric_zero();
+    gnc_numeric totalliabilities = gnc_numeric_zero();
 
     view = GNC_BUDGET_VIEW(user_data);
     priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
@@ -910,17 +916,8 @@ totals_col_source(GtkTreeViewColumn *col, GtkCellRenderer *cell,
     period_num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(col),
                                  "period_num"));
 
-    gnc_numeric totalincome;
-    totalincome = gnc_numeric_zero();
-    gnc_numeric totalexpenses;
-    totalexpenses = gnc_numeric_zero();
-    gnc_numeric totalassets;
-    totalassets = gnc_numeric_zero();
-    gnc_numeric totalliabilities;
-    totalliabilities = gnc_numeric_zero();
-    
-    
-    gint num_top_accounts = gnc_account_n_children(priv->rootAcct);
+
+    num_top_accounts = gnc_account_n_children(priv->rootAcct);
     
     // step through each child account of the root, find the total income, expenses, liabilities, and assets.
     

commit 7ef256893907d7c8d2e61eaa91f583919954eeb5
Author: Matt G <matt_graham2001 at hotmail.com>
Date:   Mon Dec 21 13:03:44 2015 +1100

    Bug 742352 - Budget Summary section does not show expense totals
    
    Fixed buget add up issue.
    https://bugzilla.gnome.org/show_bug.cgi?id=742352

diff --git a/src/gnome/gnc-budget-view.c b/src/gnome/gnc-budget-view.c
index 49305c3..b70ccb5 100644
--- a/src/gnome/gnc-budget-view.c
+++ b/src/gnome/gnc-budget-view.c
@@ -154,6 +154,7 @@ struct GncBudgetViewPrivate
     Account* expenses;
     Account* assets;
     Account* liabilities;
+    Account* rootAcct;
 };
 
 #define GNC_BUDGET_VIEW_GET_PRIVATE(o)  \
@@ -216,9 +217,11 @@ gnc_budget_view_init(GncBudgetView *budget_view)
     ENTER("view %p", budget_view);
     priv = GNC_BUDGET_VIEW_GET_PRIVATE(budget_view);
 
-    /* Keep track of the top level asset, liability, income and expense accounts */
+    /* Keep track of the root and top level asset, liability, income and expense accounts */
     root = gnc_book_get_root_account(gnc_get_current_book());
     num_top_accounts = gnc_account_n_children(root);
+    
+    priv->rootAcct = root;
 
     for (i = 0; i < num_top_accounts; ++i)
     {
@@ -716,9 +719,9 @@ budget_accum_helper(Account* account, gpointer data)
     }
 }
 
-/** \brief Function to calculate the accumulated budget amount in a given account for a specified period.
+/** \brief Function to calculate the accumulated budget amount in a given account at a specified period number.
 
-This function uses the \ref budget_accum_helper to calculate the accumulated budget amount in a given budget account for a specified period. Specifically, it uses the function \ref gnc_account_foreach_child function passing through an instance of \ref budget_accum_helper.
+This function uses the \ref budget_accum_helper to calculate the accumulated budget amount in a given budget account for a specified period number. If the acocunt does not have children, then it simply returns the balance of the account.
 */
 static gnc_numeric
 gbv_get_accumulated_budget_amount(GncBudget* budget, Account* account, guint period_num)
@@ -728,9 +731,19 @@ gbv_get_accumulated_budget_amount(GncBudget* budget, Account* account, guint per
     info.total = gnc_numeric_zero();
     info.budget = budget;
     info.period_num = period_num;
-    gnc_account_foreach_child(account, budget_accum_helper, &info);
+    
 
-    return info.total;
+    
+    if (!gnc_budget_is_account_period_value_set(budget, account, period_num))
+    {
+    	gnc_account_foreach_child(account, budget_accum_helper, &info);
+    }
+    else
+    {
+    	info.total = gnc_budget_get_account_period_value(budget, account, period_num);
+    }
+    	return info.total;
+    
 }
 
 /** \brief Calculates and displays budget amount for a period in a defined account.
@@ -866,7 +879,10 @@ budget_col_edited(Account *account, GtkTreeViewColumn *col,
 
 /** \brief Function to find the total in a column of budget provided and display the info in the totals tree widget.
 
-This function looks at which type of account is in question, and then calls the function \ref gbv_get_accumulated_budget_amount on the root account (assuming that we are not at a totals column) or \ref bgv_get_total_for_account if we are. It then displays this information in the totals tree widget.
+This function is called on each row within the totals tree (i.e. assets, expenses, transfers, and totals) in order to 
+update the total values in the totals tree (grand totals at the bottom of the budget page). It looks at which type of account is currently being examined, and then calls the function
+\ref gbv_get_accumulated_budget_amount on all of the relevant children accounts of the root. It then sets the value and color of the cell based on this information in the totals tree widget.
+
 */
 static void
 totals_col_source(GtkTreeViewColumn *col, GtkCellRenderer *cell,
@@ -877,10 +893,14 @@ totals_col_source(GtkTreeViewColumn *col, GtkCellRenderer *cell,
     GncBudgetViewPrivate* priv;
     gint row_type;
     GncBudget *budget;
+    Account* account; // used to make things easier in the adding up processes
     gint period_num;
-    gnc_numeric value;
+    gnc_numeric value; // used to assist in adding and subtracting
     gchar amtbuff[100]; //FIXME: overkill, where's the #define?
-    gint width;
+    
+    gint width; // FIXME: VARIABLE NOT NEEDED?
+    
+    gint i;
 
     view = GNC_BUDGET_VIEW(user_data);
     priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
@@ -890,78 +910,92 @@ totals_col_source(GtkTreeViewColumn *col, GtkCellRenderer *cell,
     period_num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(col),
                                  "period_num"));
 
+    gnc_numeric totalincome;
+    totalincome = gnc_numeric_zero();
+    gnc_numeric totalexpenses;
+    totalexpenses = gnc_numeric_zero();
+    gnc_numeric totalassets;
+    totalassets = gnc_numeric_zero();
+    gnc_numeric totalliabilities;
+    totalliabilities = gnc_numeric_zero();
+    
+    
+    gint num_top_accounts = gnc_account_n_children(priv->rootAcct);
+    
+    // step through each child account of the root, find the total income, expenses, liabilities, and assets.
+    
+    for (i = 0; i < num_top_accounts; ++i)
+  	{
+    	account = gnc_account_nth_child(priv->rootAcct, i);
+    	
+    	// find the total for this account
+    	
+    	if (period_num < 0)
+    	{
+    		value = bgv_get_total_for_account(account, budget);
+    	}
+    	else
+    	{
+    		value = gbv_get_accumulated_budget_amount(budget, account, period_num);
+    	}
+
+		// test for what account type, and add 'value' to the appopriate total
+    	
+    	if (xaccAccountGetType(account) == ACCT_TYPE_INCOME)
+    	{
+    		totalincome = gnc_numeric_add(totalincome, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
+    	}
+    	else if (xaccAccountGetType(account) == ACCT_TYPE_EXPENSE)
+    	{
+    		totalexpenses = gnc_numeric_add(totalexpenses, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
+    	}
+    	else if (xaccAccountGetType(account) == ACCT_TYPE_ASSET)
+    	{
+    		totalassets = gnc_numeric_add(totalassets, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
+    	}
+    	else if (xaccAccountGetType(account) == ACCT_TYPE_LIABILITY)
+    	{
+    		totalliabilities = gnc_numeric_add(totalliabilities, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
+    	}
+    	else
+    	{
+    		// Do nothing because this account is not of interest
+    	}
+    	
+   	}
+
+   
+    
+    // at this point we should have variables holding the values for assets, liabilities, expenses and incomes.
+    
+    // Set the text to display, depending on which of the totals rows we are currently looking at	
+    	
     if (row_type == TOTALS_TYPE_INCOME)
     {
-        if (period_num >= 0)
-        {
-            value = gbv_get_accumulated_budget_amount(budget, priv->income, period_num);
-        }
-        else
-        {
-            value = bgv_get_total_for_account(priv->income, budget);
-        }
-        xaccSPrintAmount(amtbuff, value,
+    	// FIXME: There must be a better way to get the GncAccountPrintInfo object than this. Would prefer to depreciate the tracking of top level accounts.
+        xaccSPrintAmount(amtbuff, totalincome,
                          gnc_account_print_info(priv->income, FALSE));
         g_object_set(cell, "foreground", "black", NULL);
     }
     else if (row_type == TOTALS_TYPE_EXPENSES)
     {
-        if (period_num >= 0)
-        {
-            value = gbv_get_accumulated_budget_amount(budget, priv->expenses, period_num);
-        }
-        else
-        {
-            value = bgv_get_total_for_account(priv->expenses, budget);
-        }
-        xaccSPrintAmount(amtbuff, value,
+       
+        xaccSPrintAmount(amtbuff, totalexpenses,
                          gnc_account_print_info(priv->expenses, FALSE));
         g_object_set(cell, "foreground", "black", NULL);
     }
     else if (row_type == TOTALS_TYPE_TRANSFERS)
     {
-        gnc_numeric assets;
-        gnc_numeric liabilities;
-
-        if (period_num >= 0)
-        {
-            assets = gbv_get_accumulated_budget_amount(budget, priv->assets, period_num);
-            liabilities = gbv_get_accumulated_budget_amount(budget, priv->liabilities, period_num);
-        }
-        else
-        {
-            assets = bgv_get_total_for_account(priv->assets, budget);
-            liabilities = bgv_get_total_for_account(priv->liabilities, budget);
-        }
-        value = gnc_numeric_sub(assets, liabilities, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
-        xaccSPrintAmount(amtbuff, value,
+    	
+        xaccSPrintAmount(amtbuff, gnc_numeric_sub(totalassets, totalliabilities, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD),
                          gnc_account_print_info(priv->assets, FALSE));
         g_object_set(cell, "foreground", "black", NULL);
     }
     else if (row_type == TOTALS_TYPE_TOTAL)
     {
-        gnc_numeric income;
-        gnc_numeric expenses;
-        gnc_numeric assets;
-        gnc_numeric liabilities;
-
-        if (period_num >= 0)
-        {
-            income = gbv_get_accumulated_budget_amount(budget, priv->income, period_num);
-            expenses = gbv_get_accumulated_budget_amount(budget, priv->expenses, period_num);
-            assets = gbv_get_accumulated_budget_amount(budget, priv->assets, period_num);
-            liabilities = gbv_get_accumulated_budget_amount(budget, priv->liabilities, period_num);
-        }
-        else
-        {
-            income = bgv_get_total_for_account(priv->income, budget);
-            expenses = bgv_get_total_for_account(priv->expenses, budget);
-            assets = bgv_get_total_for_account(priv->assets, budget);
-            liabilities = bgv_get_total_for_account(priv->liabilities, budget);
-        }
-        value = gnc_numeric_sub(income, expenses, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
-        value = gnc_numeric_sub(value, assets, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
-        value = gnc_numeric_add(value, liabilities, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
+        value = gnc_numeric_sub(totalincome, totalexpenses, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
+        value = gnc_numeric_sub(value, totalassets, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
+        value = gnc_numeric_add(value, totalliabilities, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
         xaccSPrintAmount(amtbuff, value,
                          gnc_account_print_info(priv->assets, FALSE));
         if (gnc_numeric_negative_p(value))
@@ -975,6 +1009,7 @@ totals_col_source(GtkTreeViewColumn *col, GtkCellRenderer *cell,
     }
     else
     {
+    	// if it reaches here then the row type was not set correctly
         g_strlcpy(amtbuff, "error", sizeof(amtbuff));
     }
 

commit c56a4d959356b23bf28320255179bc907821d753
Author: Gilles Dartiguelongue <eva at gentoo.org>
Date:   Wed Nov 11 13:35:46 2015 +0100

    Bug 760015 - guile support is automagic
    
    Make guile support selectable

diff --git a/configure.ac b/configure.ac
index 20b4e61..82df52b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -452,26 +452,45 @@ AC_CHECK_FUNCS(gethostid link)
 ### --------------------------------------------------------------------------
 ### Guile version checks
 
+GUILE_EFFECTIVE_VERSION=0
 # - check minimum version
 # - determine GUILE_CFLAGS and GUILE_LIBS
-gnc_have_guile_2=no
-gnc_have_guile_www=no
-PKG_CHECK_MODULES(GUILE,
-                  [guile-2.0 >= 2.0.0],
-		  [gnc_have_guile_2=yes
-		  GUILE_EFFECTIVE_VERSION=2.0
-		  AC_PATH_PROG([GUILD], guild)],
-   [PKG_CHECK_MODULES(GUILE,
-                  [guile-1.8 >= 1.8.5],
-		  [GUILE_EFFECTIVE_VERSION=1.8],
-                  [AC_MSG_ERROR([
-    guile does not appear to be installed correctly, or is not in the
-    correct version range.  Perhaps you have not installed the guile
-    development packages?  Gnucash requires at least version 1.8.5 to build.
-  ])])
-])
 
-AM_CONDITIONAL(GNC_HAVE_GUILE_2, test "x${gnc_have_guile_2}" = xyes)
+AC_ARG_WITH([guile],
+    AS_HELP_STRING([--with-guile=1.8|2.0|auto],
+                   [which guile version to compile against @<:@default: auto@:>@]),
+    [],
+    [with_guile=auto]
+)
+
+AS_IF([test "$with_guile" = "2.0"],
+      [PKG_CHECK_MODULES(GUILE, [guile-2.0 >= 2.0.0],
+                         [GUILE_EFFECTIVE_VERSION=2.0
+                          AC_PATH_PROG([GUILD], guild)])],
+      [test "$with_guile" = "1.8"],
+      [PKG_CHECK_MODULES(GUILE, [guile-1.8 >= 1.8.5],
+                         [GUILE_EFFECTIVE_VERSION=1.8])],
+      [test "$with_guile" = "auto"],
+      [PKG_CHECK_MODULES(GUILE, [guile-2.0 >= 2.0.0],
+          [GUILE_EFFECTIVE_VERSION=2.0
+           AC_PATH_PROG([GUILD], guild)],
+          [PKG_CHECK_MODULES(GUILE, [guile-1.8 >= 1.8.5],
+                             [GUILE_EFFECTIVE_VERSION=1.8],
+                             [GUILE_EFFECTIVE_VERSION=0])
+          ])],
+      # else
+      [AC_MSG_ERROR([invalid guile version specified])]
+)
+
+AS_IF([test "$GUILE_EFFECTIVE_VERSION" = "0"],
+      [AC_MSG_ERROR([
+       guile does not appear to be installed correctly, or is not in the
+       correct version range.  Perhaps you have not installed the guile
+       development packages?  Gnucash requires at least version 1.8.5 to build.
+       ])]
+)
+
+AM_CONDITIONAL(GNC_HAVE_GUILE_2, [test "$GUILE_EFFECTIVE_VERSION" = "2.0"])
 AC_SUBST(GUILE_EFFECTIVE_VERSION)
 AC_SUBST(GUILE, [`pwd`/gnc-guile])
 
@@ -498,7 +517,7 @@ if test "${BUILDING_FROM_VCS}" = yes
 then
     AX_PKG_SWIG(2.0.10, [gnc_have_swig_2_0_10=yes], [gnc_have_swig_2_0_10=no])
 
-    if test "${gnc_have_guile_2}" = yes
+    if test "${GUILE_EFFECTIVE_VERSION}" = "2.0"
     then
         if test "${gnc_have_swig_2_0_10}" = no
         then



Summary of changes:
 configure.ac                                      |  55 +++++---
 doc/tip_of_the_day.list.in                        |   4 +
 src/business/business-gnome/dialog-invoice.c      |  26 +++-
 src/engine/ScrubBusiness.c                        | 102 +++++++++++++-
 src/engine/ScrubBusiness.h                        |  38 +++++-
 src/engine/Transaction.c                          |  85 ++++++++++--
 src/engine/gncInvoice.c                           |   3 +-
 src/engine/test/test-transaction-voiding.cpp      |  17 ++-
 src/engine/test/utest-Transaction.cpp             |   6 +-
 src/gnome-utils/ui/osx_accel_map                  |   2 +-
 src/gnome/gnc-budget-view.c                       | 158 +++++++++++++---------
 src/gnome/gnc-plugin-page-account-tree.c          |   6 +-
 src/gnome/gnc-plugin-page-register.c              |  12 ++
 src/libqof/qof/qofbook-p.h                        |  15 +-
 src/libqof/qof/qofbook.cpp                        | 130 +++++++++++++-----
 src/libqof/qof/qofbook.h                          |  17 ++-
 src/libqof/qof/test/test-qofbook.c                | 140 ++++++++++++++-----
 src/register/ledger-core/split-register-control.c |  10 +-
 src/report/report-system/html-barchart.scm        |  61 ++++++---
 19 files changed, 672 insertions(+), 215 deletions(-)



More information about the gnucash-changes mailing list