mirror of
https://github.com/Gnucash/gnucash.git
synced 2024-12-01 13:09:41 -06:00
754 lines
28 KiB
C
754 lines
28 KiB
C
/********************************************************************\
|
|
* ScrubBusiness.h -- Cleanup functions for the business objects. *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as *
|
|
* published by the Free Software Foundation; either version 2 of *
|
|
* the License, or (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License*
|
|
* along with this program; if not, contact: *
|
|
* *
|
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
|
\********************************************************************/
|
|
|
|
/** @file ScrubBusiness.h
|
|
* @brief Cleanup functions for business objects
|
|
* @author Created by Geert Janssens August 2014
|
|
* @author Copyright (c) 2014 Geert Janssens <geert@kobaltwit.be>
|
|
*
|
|
* Provides the high-level API for checking and repairing ('scrubbing
|
|
* clean') the various data objects used by the business functions.*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "gnc-engine.h"
|
|
#include "gnc-lot.h"
|
|
#include "policy-p.h"
|
|
#include "Account.h"
|
|
#include "gncInvoice.h"
|
|
#include "gncInvoiceP.h"
|
|
#include "Scrub.h"
|
|
#include "Scrub2.h"
|
|
#include "ScrubBusiness.h"
|
|
#include "Transaction.h"
|
|
|
|
#undef G_LOG_DOMAIN
|
|
#define G_LOG_DOMAIN "gnc.engine.scrub"
|
|
|
|
static QofLogModule log_module = G_LOG_DOMAIN;
|
|
|
|
static void
|
|
gncScrubInvoiceState (GNCLot *lot)
|
|
{
|
|
SplitList *ls_iter = NULL;
|
|
GncInvoice *invoice = NULL;
|
|
GncInvoice *lot_invoice = gncInvoiceGetInvoiceFromLot (lot);
|
|
|
|
for (ls_iter = gnc_lot_get_split_list (lot); ls_iter; ls_iter = ls_iter->next)
|
|
{
|
|
Split *split = ls_iter->data;
|
|
Transaction *txn = NULL; // ll_txn = "Lot Link Transaction"
|
|
|
|
if (!split)
|
|
continue; // next scrub lot split
|
|
|
|
txn = xaccSplitGetParent (split);
|
|
invoice = gncInvoiceGetInvoiceFromTxn (txn);
|
|
if (invoice)
|
|
break;
|
|
|
|
}
|
|
|
|
if (invoice != lot_invoice)
|
|
{
|
|
PINFO("Correcting lot invoice associaton. Old invoice: %p, new invoice %p", lot_invoice, invoice);
|
|
gncInvoiceDetachFromLot(lot);
|
|
|
|
if (invoice)
|
|
gncInvoiceAttachToLot (invoice, lot);
|
|
else
|
|
gncOwnerAttachToLot (gncInvoiceGetOwner(lot_invoice), lot);
|
|
}
|
|
}
|
|
|
|
// A helper function that takes two splits. If the splits are of opposite sign
|
|
// it reduces the biggest split to have the same value (but with opposite sign)
|
|
// of the smaller split.
|
|
// To make sure everything still continues to balance in addition a "remainder" split
|
|
// will be created that will be added to the same lot and transaction as the biggest
|
|
// split.
|
|
// The opposite sign restriction is because that's the only scenario that makes sense
|
|
// in the context of scrubbing business lots below.
|
|
// If we created new splits, return TRUE, otherwise FALSE
|
|
static gboolean reduce_biggest_split (Split *splitA, Split *splitB)
|
|
{
|
|
gnc_numeric valA = xaccSplitGetValue (splitA);
|
|
gnc_numeric valB = xaccSplitGetValue (splitB);
|
|
|
|
if (gnc_numeric_compare (gnc_numeric_abs (valA), gnc_numeric_abs (valB)) >= 0)
|
|
return gncOwnerReduceSplitTo (splitA, gnc_numeric_neg (valB));
|
|
else
|
|
return gncOwnerReduceSplitTo (splitB, gnc_numeric_neg (valA));
|
|
}
|
|
|
|
// Attempt to eliminate or reduce the lot link splits (ll_*_split)
|
|
// between from_lot and to_lot. To do so this function will attempt
|
|
// to move a payment split from from_lot to to_lot in order to
|
|
// balance the lot link split that will be deleted.
|
|
// To ensure everything remains balanced at most
|
|
// min (val-ll-*-split, val-pay-split) (in absolute values) can be moved.
|
|
// If any split involved has a larger value, it will be split in two
|
|
// and only the part matching the other splits' value will be used.
|
|
// The leftover splits are kept in the respective transactions/lots.
|
|
// A future scrub action can still act on those if needed.
|
|
//
|
|
// Note that this function assumes that ll_from_split and ll_to_split are
|
|
// of opposite sign. The calling function should check this.
|
|
|
|
static gboolean
|
|
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
|
|
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 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, from_val);
|
|
if (!real_from_split)
|
|
return FALSE; // No usable split in the payment lot
|
|
|
|
// 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 (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
|
|
to_val = xaccSplitGetValue (ll_to_split);
|
|
real_from_val = xaccSplitGetValue (real_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 (%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;
|
|
}
|
|
|
|
// Now do the actual split dance
|
|
// - move real payment split to doc lot
|
|
// - delete both lot link splits from the lot link transaction
|
|
gnc_lot_add_split (to_lot, real_from_split);
|
|
xaccTransBeginEdit (ll_txn);
|
|
xaccSplitDestroy (ll_to_split);
|
|
xaccSplitDestroy (ll_from_split);
|
|
xaccTransCommitEdit (ll_txn);
|
|
|
|
// Cleanup the lots
|
|
xaccScrubMergeLotSubSplits (to_lot, FALSE);
|
|
xaccScrubMergeLotSubSplits (from_lot, FALSE);
|
|
|
|
return TRUE; // We did change splits/transactions/lots...
|
|
}
|
|
|
|
static gboolean
|
|
gncScrubLotLinks (GNCLot *scrub_lot)
|
|
{
|
|
gboolean modified = FALSE, restart_needed = FALSE;
|
|
SplitList *sls_iter = NULL;
|
|
|
|
scrub_start:
|
|
restart_needed = FALSE;
|
|
|
|
// Iterate over all splits in the lot
|
|
for (sls_iter = gnc_lot_get_split_list (scrub_lot); sls_iter; sls_iter = sls_iter->next)
|
|
{
|
|
Split *sl_split = sls_iter->data;
|
|
Transaction *ll_txn = NULL; // ll_txn = "Lot Link Transaction"
|
|
SplitList *lts_iter = NULL;
|
|
|
|
if (!sl_split)
|
|
continue; // next scrub lot split
|
|
|
|
ll_txn = xaccSplitGetParent (sl_split);
|
|
|
|
if (!ll_txn)
|
|
{
|
|
// 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.", sl_split);
|
|
continue;
|
|
}
|
|
|
|
// Don't scrub invoice type transactions
|
|
if (xaccTransGetTxnType (ll_txn) == TXN_TYPE_INVOICE)
|
|
continue; // next scrub lot split
|
|
|
|
// Empty splits can be removed immediately
|
|
if (gnc_numeric_zero_p (xaccSplitGetValue (sl_split)) ||
|
|
gnc_numeric_zero_p(xaccSplitGetValue (sl_split)))
|
|
{
|
|
xaccSplitDestroy (sl_split);
|
|
modified = TRUE;
|
|
goto scrub_start;
|
|
}
|
|
|
|
// Iterate over all splits in the lot link transaction
|
|
for (lts_iter = xaccTransGetSplitList (ll_txn); lts_iter; lts_iter = lts_iter->next)
|
|
{
|
|
Split *ll_txn_split = lts_iter->data; // These all refer to splits in the lot link transaction
|
|
GNCLot *remote_lot = NULL; // lot at the other end of the lot link transaction
|
|
gboolean sl_is_doc_lot, rl_is_doc_lot;
|
|
|
|
if (!ll_txn_split)
|
|
continue; // next lot link transaction split
|
|
|
|
// Skip the split in the lot we're currently scrubbing
|
|
if (sl_split == ll_txn_split)
|
|
continue; // next lot link transaction split
|
|
|
|
// Skip empty other splits. They'll be scrubbed in the outer for loop later
|
|
if (gnc_numeric_zero_p (xaccSplitGetValue (ll_txn_split)) ||
|
|
gnc_numeric_zero_p(xaccSplitGetValue (ll_txn_split)))
|
|
continue;
|
|
|
|
// 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
|
|
|
|
// We can only scrub if the other split is in a lot as well
|
|
// Link transactions always have their other split in another lot
|
|
// however ordinary payment transactions may not
|
|
remote_lot = xaccSplitGetLot (ll_txn_split);
|
|
if (!remote_lot)
|
|
continue;
|
|
|
|
sl_is_doc_lot = (gncInvoiceGetInvoiceFromLot (scrub_lot) != NULL);
|
|
rl_is_doc_lot = (gncInvoiceGetInvoiceFromLot (remote_lot) != NULL);
|
|
|
|
// Depending on the type of lots we're comparing, we need different actions
|
|
// - Two document lots (an invoice and a credit note):
|
|
// Special treatment - look for all document lots linked via ll_txn
|
|
// and update the memo to be of more use to the users.
|
|
// - Two payment lots:
|
|
// (Part of) the link will be eliminated and instead (part of)
|
|
// one payment will be added to the other lot to keep the balance.
|
|
// If the payments are not equal in abs value part of the bigger payment
|
|
// will be moved to the smaller payment's lot.
|
|
// - A document and a payment lot:
|
|
// (Part of) the link will be eliminated and instead (part of) the real
|
|
// payment will be added to the document lot to handle the payment.
|
|
if (sl_is_doc_lot && rl_is_doc_lot)
|
|
gncOwnerSetLotLinkMemo (ll_txn);
|
|
else if (!sl_is_doc_lot && !rl_is_doc_lot)
|
|
{
|
|
gint cmp = gnc_numeric_compare (gnc_numeric_abs (xaccSplitGetValue (sl_split)),
|
|
gnc_numeric_abs (xaccSplitGetValue (ll_txn_split)));
|
|
if (cmp >= 0)
|
|
restart_needed = scrub_other_link (scrub_lot, sl_split, remote_lot, ll_txn_split);
|
|
else
|
|
restart_needed = scrub_other_link (remote_lot, ll_txn_split, scrub_lot, sl_split);
|
|
}
|
|
else
|
|
{
|
|
GNCLot *doc_lot = sl_is_doc_lot ? scrub_lot : remote_lot;
|
|
GNCLot *pay_lot = sl_is_doc_lot ? remote_lot : scrub_lot;
|
|
Split *ll_doc_split = sl_is_doc_lot ? sl_split : ll_txn_split;
|
|
Split *ll_pay_split = sl_is_doc_lot ? ll_txn_split : sl_split;
|
|
// Ok, let's try to move a payment from pay_lot to doc_lot
|
|
restart_needed = scrub_other_link (pay_lot, ll_pay_split, doc_lot, ll_doc_split);
|
|
}
|
|
|
|
// If we got here, the splits in our lot and ll_txn have been severely mixed up
|
|
// And our iterator lists are probably no longer valid
|
|
// So let's start over
|
|
if (restart_needed)
|
|
{
|
|
modified = TRUE;
|
|
goto 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_prepend (filtered_list, free_split);
|
|
}
|
|
|
|
filtered_list = g_list_reverse (filtered_list);
|
|
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;
|
|
|
|
if (!lot) return FALSE;
|
|
lotname = g_strdup (gnc_lot_get_title (lot));
|
|
ENTER ("(lot=%p) %s", lot, lotname ? lotname : "(no lotname)");
|
|
|
|
acc = gnc_lot_get_account (lot);
|
|
if (acc)
|
|
xaccAccountBeginEdit(acc);
|
|
|
|
/* Check invoice link consistency
|
|
* A lot should have both or neither of:
|
|
* - one split from an invoice transaction
|
|
* - an invoice-guid set
|
|
*/
|
|
gncScrubInvoiceState (lot);
|
|
|
|
// Scrub lot links.
|
|
// They should only remain when two document lots are linked together
|
|
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))
|
|
{
|
|
PINFO("All splits were removed from lot, deleting");
|
|
gnc_lot_destroy (lot);
|
|
}
|
|
|
|
if (acc)
|
|
xaccAccountCommitEdit(acc);
|
|
|
|
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;
|
|
}
|
|
|
|
gboolean
|
|
gncScrubBusinessSplit (Split *split)
|
|
{
|
|
Transaction *txn;
|
|
gboolean deleted_split = FALSE;
|
|
|
|
if (!split) return FALSE;
|
|
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);
|
|
GncInvoice *invoice = gncInvoiceGetInvoiceFromTxn (txn);
|
|
Transaction *posted_txn = gncInvoiceGetPostedTxn (invoice);
|
|
|
|
/* Look for transactions as a result of double posting an invoice or bill
|
|
* Refer to https://bugs.gnucash.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)
|
|
{
|
|
const gchar *memo = _("Please delete this transaction. Explanation at https://wiki.gnucash.org/wiki/Business_Features_Issues#Double_posting");
|
|
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);
|
|
}
|
|
/* Next check for transactions which claim to be the posted transaction of
|
|
* an invoice but the invoice disagrees. In that case
|
|
*/
|
|
else if (invoice && (txn != posted_txn))
|
|
{
|
|
const gchar *memo = _("Please delete this transaction. Explanation at https://wiki.gnucash.org/wiki/Business_Features_Issues#I_can.27t_delete_a_transaction_of_type_.22I.22_from_the_AR.2FAP_account");
|
|
gchar *txn_date = qof_print_date (xaccTransGetDateEntered (txn));
|
|
xaccTransClearReadOnly (txn);
|
|
xaccTransSetTxnType (txn, TXN_TYPE_NONE);
|
|
xaccSplitSetMemo (split, memo);
|
|
if (lot)
|
|
{
|
|
gnc_lot_remove_split (lot, split);
|
|
gncInvoiceDetachFromLot (lot);
|
|
gncOwnerAttachToLot (gncInvoiceGetOwner(invoice), lot);
|
|
}
|
|
PWARN("Cleared double post status of transaction \"%s\", dated %s. "
|
|
"Please delete transaction and verify balance.",
|
|
xaccTransGetDescription (txn),
|
|
txn_date);
|
|
g_free (txn_date);
|
|
}
|
|
/* Next delete any empty splits that aren't part of an invoice transaction
|
|
* Such splits may be the result of scrubbing the business lots, which can
|
|
* merge splits together while reducing superfluous lot links
|
|
*/
|
|
else if (gnc_numeric_zero_p (xaccSplitGetAmount(split)) && !gncInvoiceGetInvoiceFromTxn (txn) && !is_void)
|
|
{
|
|
GNCLot *lot = xaccSplitGetLot (split);
|
|
time64 pdate = xaccTransGetDate (txn);
|
|
gchar *pdatestr = gnc_ctime (&pdate);
|
|
PINFO ("Destroying empty split %p from transaction %s (%s)", split, pdatestr, xaccTransGetDescription(txn));
|
|
xaccSplitDestroy (split);
|
|
g_free (pdatestr);
|
|
|
|
// Also delete the lot containing this split if it was the last split in that lot
|
|
if (lot && (gnc_lot_count_splits (lot) == 0))
|
|
gnc_lot_destroy (lot);
|
|
|
|
deleted_split = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
LEAVE ("(split=%p)", split);
|
|
return deleted_split;
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
void
|
|
gncScrubBusinessAccountLots (Account *acc, QofPercentageFunc percentagefunc)
|
|
{
|
|
LotList *lots, *node;
|
|
gint lot_count = 0;
|
|
gint curr_lot_no = 0;
|
|
const gchar *str;
|
|
const char *message = _( "Checking business lots in account %s: %u of %u");
|
|
|
|
if (!acc) return;
|
|
|
|
if (gnc_get_abort_scrub())
|
|
(percentagefunc)(NULL, -1.0);
|
|
|
|
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);
|
|
|
|
lots = xaccAccountGetLotList(acc);
|
|
lot_count = g_list_length (lots);
|
|
for (node = lots; node; node = node->next)
|
|
{
|
|
GNCLot *lot = node->data;
|
|
|
|
PINFO("Start processing lot %d of %d",
|
|
curr_lot_no + 1, lot_count);
|
|
|
|
if (curr_lot_no % 100 == 0)
|
|
{
|
|
char *progress_msg = g_strdup_printf (message, str, curr_lot_no, lot_count);
|
|
(percentagefunc)(progress_msg, (100 * curr_lot_no) / lot_count);
|
|
g_free (progress_msg);
|
|
}
|
|
|
|
if (lot)
|
|
gncScrubBusinessLot (lot);
|
|
|
|
PINFO("Finished processing lot %d of %d",
|
|
curr_lot_no + 1, lot_count);
|
|
curr_lot_no++;
|
|
}
|
|
g_list_free(lots);
|
|
xaccAccountCommitEdit(acc);
|
|
(percentagefunc)(NULL, -1.0);
|
|
LEAVE ("(acc=%s)", str);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
void
|
|
gncScrubBusinessAccountSplits (Account *acc, QofPercentageFunc percentagefunc)
|
|
{
|
|
SplitList *splits, *node;
|
|
gint split_count = 0;
|
|
gint curr_split_no;
|
|
const gchar *str;
|
|
const char *message = _( "Checking business splits in account %s: %u of %u");
|
|
|
|
if (!acc) return;
|
|
|
|
if (gnc_get_abort_scrub())
|
|
(percentagefunc)(NULL, -1.0);
|
|
|
|
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);
|
|
|
|
restart:
|
|
curr_split_no = 0;
|
|
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 + 1, split_count);
|
|
|
|
if (gnc_get_abort_scrub ())
|
|
break;
|
|
|
|
if (curr_split_no % 100 == 0)
|
|
{
|
|
char *progress_msg = g_strdup_printf (message, str, curr_split_no, split_count);
|
|
(percentagefunc)(progress_msg, (100 * curr_split_no) / split_count);
|
|
g_free (progress_msg);
|
|
}
|
|
|
|
if (split)
|
|
// If gncScrubBusinessSplit returns true, a split was deleted and hence
|
|
// The account split list has become invalid, so we need to start over
|
|
if (gncScrubBusinessSplit (split))
|
|
goto restart;
|
|
|
|
PINFO("Finished processing split %d of %d",
|
|
curr_split_no + 1, split_count);
|
|
curr_split_no++;
|
|
}
|
|
xaccAccountCommitEdit(acc);
|
|
(percentagefunc)(NULL, -1.0);
|
|
LEAVE ("(acc=%s)", str);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
void
|
|
gncScrubBusinessAccount (Account *acc, QofPercentageFunc percentagefunc)
|
|
{
|
|
if (!acc) return;
|
|
if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
|
|
|
|
gncScrubBusinessAccountLots (acc, percentagefunc);
|
|
gncScrubBusinessAccountSplits (acc, percentagefunc);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
static void
|
|
lot_scrub_cb (Account *acc, gpointer data)
|
|
{
|
|
if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
|
|
gncScrubBusinessAccount (acc, data);
|
|
}
|
|
|
|
void
|
|
gncScrubBusinessAccountTree (Account *acc, QofPercentageFunc percentagefunc)
|
|
{
|
|
if (!acc) return;
|
|
|
|
gnc_account_foreach_descendant(acc, lot_scrub_cb, percentagefunc);
|
|
gncScrubBusinessAccount (acc, percentagefunc);
|
|
}
|
|
|
|
/* ========================== END OF FILE ========================= */
|