gnucash maint: Multiple changes pushed

Geert Janssens gjanssens at code.gnucash.org
Sun Feb 22 14:13:40 EST 2015


Updated	 via  https://github.com/Gnucash/gnucash/commit/208cf514 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/8136d7ba (commit)
	from  https://github.com/Gnucash/gnucash/commit/01170e66 (commit)



commit 208cf514f34f2749b5a5b494b35a2dd75e277ae3
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Sun Feb 22 12:30:52 2015 +0100

    Add scrubbing function to recover dangling lot links and payments

diff --git a/src/engine/ScrubBusiness.c b/src/engine/ScrubBusiness.c
index e830a3f..b99a1ee 100644
--- a/src/engine/ScrubBusiness.c
+++ b/src/engine/ScrubBusiness.c
@@ -190,7 +190,7 @@ scrub_start:
             if (sl_split == ll_txn_split)
                 continue; // next lot link transaction split
 
-            // Only splits of opposite sign can be scrubbed
+            // Only splits of opposite signed values can be scrubbed
             if (gnc_numeric_positive_p (xaccSplitGetValue (sl_split)) ==
                 gnc_numeric_positive_p (xaccSplitGetValue (ll_txn_split)))
                 continue; // next lot link transaction split
@@ -256,11 +256,160 @@ scrub_start:
     return modified;
 }
 
+// Note this is a recursive function. It presumes the number of splits
+// in avail_splits is relatively low. With many splits the performance will
+// quickly degrade.
+// Careful: this function assumes all splits in avail_splits to be valid
+// and with values of opposite sign of target_value
+// Ignoring this can cause unexpected results!
+static SplitList *
+gncSLFindOffsSplits (SplitList *avail_splits, gnc_numeric target_value)
+{
+    gint curr_recurse_level = 0;
+    gint max_recurse_level = g_list_length (avail_splits) - 1;
+
+    if (!avail_splits)
+        return NULL;
+
+    for (curr_recurse_level = 0;
+         curr_recurse_level <= max_recurse_level;
+         curr_recurse_level++)
+    {
+        SplitList *split_iter = NULL;
+        for (split_iter = avail_splits; split_iter; split_iter = split_iter->next)
+        {
+            Split *split = split_iter->data;
+            SplitList *match_splits = NULL;
+            gnc_numeric split_value, remaining_value;
+
+            split_value = xaccSplitGetValue (split);
+            // Attention: target_value and split_value are of opposite sign
+            // So to get the remaining target value, they should be *added*
+            remaining_value = gnc_numeric_add (target_value, split_value,
+                                               GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
+
+            if (curr_recurse_level == 0)
+            {
+                if (gnc_numeric_zero_p (remaining_value))
+                    match_splits = g_list_prepend (NULL, split);
+            }
+            else
+            {
+                if (gnc_numeric_positive_p (target_value) ==
+                    gnc_numeric_positive_p (remaining_value))
+                    match_splits = gncSLFindOffsSplits (split_iter->next,
+                                                        remaining_value);
+            }
+
+            if (match_splits)
+                return g_list_prepend (match_splits, split);
+        }
+    }
+
+    return NULL;
+}
+
+
+static gboolean
+gncScrubLotDanglingPayments (GNCLot *lot)
+{
+    SplitList * split_list, *filtered_list = NULL, *match_list = NULL, *node;
+    Split *ll_split = gnc_lot_get_earliest_split (lot);
+    Transaction *ll_trans = xaccSplitGetParent (ll_split);
+    gnc_numeric ll_val = xaccSplitGetValue (ll_split);
+    time64 ll_date = xaccTransGetDate (ll_trans);
+    const char *ll_desc = xaccTransGetDescription (ll_trans);
+
+    // look for free splits (i.e. not in any lot) which,
+    // compared to the lot link split
+    // - have the same date
+    // - have the same description
+    // - have an opposite sign amount
+    // - free split's abs value is less than or equal to ll split's abs value
+    split_list = xaccAccountGetSplitList(gnc_lot_get_account (lot));
+    for (node = split_list; node; node = node->next)
+    {
+        Split *free_split = node->data;
+        Transaction *free_trans;
+        gnc_numeric free_val;
+
+        if (NULL != xaccSplitGetLot(free_split))
+            continue;
+
+        free_trans = xaccSplitGetParent (free_split);
+        if (ll_date != xaccTransGetDate (free_trans))
+            continue;
+
+        if (0 != g_strcmp0 (ll_desc, xaccTransGetDescription (free_trans)))
+            continue;
+
+        free_val = xaccSplitGetValue (free_split);
+        if (gnc_numeric_positive_p (ll_val) ==
+            gnc_numeric_positive_p (free_val))
+            continue;
+
+        if (gnc_numeric_compare (gnc_numeric_abs (free_val), gnc_numeric_abs (ll_val)) > 0)
+            continue;
+
+        filtered_list = g_list_append(filtered_list, free_split);
+    }
+
+    match_list = gncSLFindOffsSplits (filtered_list, ll_val);
+    g_list_free (filtered_list);
+
+    for (node = match_list; node; node = node->next)
+    {
+        Split *match_split = node->data;
+        gnc_lot_add_split (lot, match_split);
+    }
+
+    if (match_list)
+    {
+        g_list_free (match_list);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+static gboolean
+gncScrubLotIsSingleLotLinkSplit (GNCLot *lot)
+{
+    Split *split = NULL;
+    Transaction *trans = NULL;
+
+    // Lots with a single split which is also a lot link transaction split
+    // may be sign of a dangling payment. Let's try to fix that
+
+    // Only works for single split lots...
+    if (1 != gnc_lot_count_splits (lot))
+        return FALSE;
+
+    split = gnc_lot_get_earliest_split (lot);
+    trans = xaccSplitGetParent (split);
+
+    if (!trans)
+    {
+        // Ooops - the split doesn't belong to any transaction !
+        // This is not expected so issue a warning and continue with next split
+        PWARN("Encountered a split in a business lot that's not part of any transaction. "
+              "This is unexpected! Skipping split %p.", split);
+        return FALSE;
+    }
+
+    // Only works if single split belongs to a lot link transaction...
+    if (xaccTransGetTxnType (trans) != TXN_TYPE_LINK)
+        return FALSE;
+
+    return TRUE;
+}
 
 gboolean
 gncScrubBusinessLot (GNCLot *lot)
 {
     gboolean splits_deleted = FALSE;
+    gboolean dangling_payments = FALSE;
+    gboolean dangling_lot_link = FALSE;
     Account *acc;
     gchar *lotname=NULL;
 
@@ -277,6 +426,21 @@ gncScrubBusinessLot (GNCLot *lot)
     xaccScrubMergeLotSubSplits (lot, FALSE);
     splits_deleted = gncScrubLotLinks (lot);
 
+    // Look for dangling payments and repair if found
+    dangling_lot_link = gncScrubLotIsSingleLotLinkSplit (lot);
+    if (dangling_lot_link)
+    {
+        dangling_payments = gncScrubLotDanglingPayments (lot);
+        if (dangling_payments)
+            splits_deleted |= gncScrubLotLinks (lot);
+        else
+        {
+            Split *split = gnc_lot_get_earliest_split (lot);
+            Transaction *trans = xaccSplitGetParent (split);
+            xaccTransDestroy (trans);
+        }
+    }
+
     // If lot is empty now, delete it
     if (0 == gnc_lot_count_splits (lot))
     {
@@ -287,7 +451,9 @@ gncScrubBusinessLot (GNCLot *lot)
     if (acc)
         xaccAccountCommitEdit(acc);
 
-    LEAVE ("(lot=%s, deleted=%d)", lotname ? lotname : "(no lotname)", splits_deleted);
+    LEAVE ("(lot=%s, deleted=%d, dangling lot link=%d, dangling_payments=%d)",
+            lotname ? lotname : "(no lotname)", splits_deleted, dangling_lot_link,
+            dangling_payments);
     g_free (lotname);
 
     return splits_deleted;

commit 8136d7ba3febe635712daf006fdfc70b2614bbcd
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Sat Feb 21 14:27:29 2015 +0100

    Fix potential infinite loop in business lot scrubbing

diff --git a/src/engine/ScrubBusiness.c b/src/engine/ScrubBusiness.c
index 9c7b308..e830a3f 100644
--- a/src/engine/ScrubBusiness.c
+++ b/src/engine/ScrubBusiness.c
@@ -82,31 +82,33 @@ scrub_other_link (GNCLot *from_lot, Split *ll_from_split,
                   GNCLot *to_lot,   Split *ll_to_split)
 {
     Split *real_from_split; // This refers to the split in the payment lot representing the payment itself
-    gnc_numeric from_val, real_from_val, to_val;
     gboolean modified = FALSE;
+    gnc_numeric real_from_val;
+    gnc_numeric from_val = xaccSplitGetValue (ll_from_split);
+    gnc_numeric to_val = xaccSplitGetValue (ll_to_split);
     Transaction *ll_txn = xaccSplitGetParent (ll_to_split);
 
-    // Per iteration we can only scrub at most max (val-doc-split, val-pay-split)
-    // So split the bigger one in two if needed and continue with the equal valued splits only
-    // The remainder is added to the lot link transaction and the lot to keep everything balanced
-    // and will be processed in a future iteration
-    modified = reduce_biggest_split (ll_from_split, ll_to_split);
+    // Per iteration we can only scrub at most min (val-doc-split, val-pay-split)
+    // So set the ceiling for finding a potential offsetting split in the lot
+    if (gnc_numeric_compare (gnc_numeric_abs (from_val), gnc_numeric_abs (to_val)) >= 0)
+        from_val = gnc_numeric_neg (to_val);
 
     // Next we have to find the original payment split so we can
     // add (part of) it to the document lot
-    real_from_split = gncOwnerFindOffsettingSplit (from_lot, xaccSplitGetValue (ll_from_split));
+    real_from_split = gncOwnerFindOffsettingSplit (from_lot, from_val);
     if (!real_from_split)
-        return modified; // No usable split in the payment lot
+        return FALSE; // No usable split in the payment lot
 
-    // Here again per iteration we can only scrub at most max (val-other-pay-split, val-pay-split)
-    // So split the bigger one in two if needed and continue with the equal valued splits only
+    // We now have found 3 splits involved in the scrub action:
+    // 2 lot link splits which we want to reduce
+    // 1 other split to move into the original lot instead of the lot link split
+    // As said only value of the split can be offset.
+    // So split the bigger ones in two if needed and continue with equal valued splits only
     // The remainder is added to the lot link transaction and the lot to keep everything balanced
     // and will be processed in a future iteration
-    modified = reduce_biggest_split (real_from_split, ll_from_split);
-
-    // Once more check for max (val-doc-split, val-pay-split), and reduce if necessary.
-    // It may have changed while looking for the real payment split
     modified = reduce_biggest_split (ll_from_split, ll_to_split);
+    modified |= reduce_biggest_split (real_from_split, ll_from_split);
+    modified |= reduce_biggest_split (ll_from_split, ll_to_split);
 
     // At this point ll_to_split and real_from_split should have the same value
     // If not, flag a warning and skip to the next iteration
@@ -116,8 +118,11 @@ scrub_other_link (GNCLot *from_lot, Split *ll_from_split,
     if (!gnc_numeric_equal (real_from_val, to_val))
     {
         // This is unexpected - write a warning message and skip this split
-        PWARN("real_from_val and to_val differ. "
-              "This is unexpected! Skip scrubbing of real_from_split %p against ll_to_split %p.", real_from_split, ll_to_split);
+        PWARN("real_from_val (%s) and to_val (%s) differ. "
+              "This is unexpected! Skip scrubbing of real_from_split %p against ll_to_split %p.",
+              gnc_numeric_to_string (real_from_val), // gnc_numeric_denom (real_from_val),
+              gnc_numeric_to_string (to_val), // gnc_numeric_denom (to_val),
+              real_from_split, ll_to_split);
         return modified;
     }
 



Summary of changes:
 src/engine/ScrubBusiness.c | 207 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 189 insertions(+), 18 deletions(-)



More information about the gnucash-changes mailing list