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