2014-08-23 08:55:46 -05:00
|
|
|
/********************************************************************\
|
|
|
|
* 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 "Scrub2.h"
|
|
|
|
#include "ScrubBusiness.h"
|
|
|
|
#include "Transaction.h"
|
|
|
|
|
|
|
|
static QofLogModule log_module = GNC_MOD_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);
|
|
|
|
|
2014-08-26 12:24:56 -05:00
|
|
|
if (gnc_numeric_compare (gnc_numeric_abs (valA), gnc_numeric_abs (valB)) >= 0)
|
|
|
|
return gncOwnerReduceSplitTo (splitA, gnc_numeric_neg (valB));
|
2014-08-23 08:55:46 -05:00
|
|
|
else
|
2014-08-26 12:24:56 -05:00
|
|
|
return gncOwnerReduceSplitTo (splitB, gnc_numeric_neg (valA));
|
2014-08-23 08:55:46 -05:00
|
|
|
}
|
|
|
|
|
2014-08-26 03:19:37 -05:00
|
|
|
// 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.
|
|
|
|
|
2014-08-23 08:55:46 -05:00
|
|
|
static gboolean
|
2014-08-26 03:19:37 -05:00
|
|
|
scrub_other_link (GNCLot *from_lot, Split *ll_from_split,
|
|
|
|
GNCLot *to_lot, Split *ll_to_split)
|
2014-08-23 08:55:46 -05:00
|
|
|
{
|
2014-08-26 03:19:37 -05:00
|
|
|
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;
|
2014-08-23 08:55:46 -05:00
|
|
|
gboolean modified = FALSE;
|
2014-08-26 03:19:37 -05:00
|
|
|
Transaction *ll_txn = xaccSplitGetParent (ll_to_split);
|
2014-08-23 08:55:46 -05:00
|
|
|
|
|
|
|
// 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
|
2014-08-26 03:19:37 -05:00
|
|
|
modified = reduce_biggest_split (ll_from_split, ll_to_split);
|
2014-08-23 08:55:46 -05:00
|
|
|
|
|
|
|
// Next we have to find the original payment split so we can
|
|
|
|
// add (part of) it to the document lot
|
2014-08-27 04:45:07 -05:00
|
|
|
real_from_split = gncOwnerFindOffsettingSplit (from_lot, xaccSplitGetValue (ll_from_split));
|
2014-08-26 03:19:37 -05:00
|
|
|
if (!real_from_split)
|
2014-08-23 08:55:46 -05:00
|
|
|
return modified; // 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
|
|
|
|
// The remainder is added to the lot link transaction and the lot to keep everything balanced
|
|
|
|
// and will be processed in a future iteration
|
2014-08-26 03:19:37 -05:00
|
|
|
modified = reduce_biggest_split (real_from_split, ll_from_split);
|
2014-08-23 08:55:46 -05:00
|
|
|
|
|
|
|
// 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
|
2014-08-26 03:19:37 -05:00
|
|
|
modified = reduce_biggest_split (ll_from_split, ll_to_split);
|
2014-08-23 08:55:46 -05:00
|
|
|
|
2014-08-26 03:19:37 -05:00
|
|
|
// At this point ll_to_split and real_from_split should have the same value
|
2014-08-23 08:55:46 -05:00
|
|
|
// If not, flag a warning and skip to the next iteration
|
2014-08-26 03:19:37 -05:00
|
|
|
to_val = xaccSplitGetValue (ll_to_split);
|
|
|
|
from_val = xaccSplitGetValue (ll_from_split);
|
|
|
|
real_from_val = xaccSplitGetValue (real_from_split);
|
|
|
|
if (!gnc_numeric_equal (real_from_val, to_val))
|
2014-08-23 08:55:46 -05:00
|
|
|
{
|
|
|
|
// This is unexpected - write a warning message and skip this split
|
2014-08-26 03:19:37 -05:00
|
|
|
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);
|
2014-08-23 08:55:46 -05:00
|
|
|
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
|
2014-08-26 03:19:37 -05:00
|
|
|
gnc_lot_add_split (to_lot, real_from_split);
|
2014-08-23 08:55:46 -05:00
|
|
|
xaccTransBeginEdit (ll_txn);
|
2014-08-26 03:19:37 -05:00
|
|
|
xaccSplitDestroy (ll_to_split);
|
|
|
|
xaccSplitDestroy (ll_from_split);
|
2014-08-23 08:55:46 -05:00
|
|
|
xaccTransCommitEdit (ll_txn);
|
|
|
|
|
|
|
|
// Cleanup the lots
|
2014-08-26 03:19:37 -05:00
|
|
|
xaccScrubMergeLotSubSplits (to_lot, FALSE);
|
|
|
|
xaccScrubMergeLotSubSplits (from_lot, FALSE);
|
2014-08-23 08:55:46 -05:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
// Only lot link transactions need to be scrubbed
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK)
|
|
|
|
continue; // next scrub lot split
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// Only splits of opposite sign 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
|
|
|
|
|
|
|
|
// Find linked lot via split
|
|
|
|
remote_lot = xaccSplitGetLot (ll_txn_split);
|
|
|
|
if (!remote_lot)
|
|
|
|
{
|
|
|
|
// This is unexpected - write a warning message and skip this split
|
|
|
|
PWARN("Encountered a Lot Link transaction with a split that's not in any lot. "
|
|
|
|
"This is unexpected! Skipping split %p from transaction %p.", ll_txn_split, ll_txn);
|
|
|
|
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 uses.
|
|
|
|
// - Two payment lots:
|
2014-08-26 05:41:57 -05:00
|
|
|
// (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.
|
2014-08-23 08:55:46 -05:00
|
|
|
// - 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)
|
2014-08-26 12:00:41 -05:00
|
|
|
gncOwnerSetLotLinkMemo (ll_txn);
|
2014-08-23 08:55:46 -05:00
|
|
|
else if (!sl_is_doc_lot && !rl_is_doc_lot)
|
2014-08-26 05:41:57 -05:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
2014-08-23 08:55:46 -05:00
|
|
|
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;
|
2014-08-26 03:19:37 -05:00
|
|
|
// 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);
|
2014-08-23 08:55:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
gboolean
|
|
|
|
gncScrubBusinessLot (GNCLot *lot)
|
|
|
|
{
|
|
|
|
gboolean splits_deleted = 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);
|
|
|
|
|
|
|
|
// Scrub lot links.
|
|
|
|
// They should only remain when two document lots are linked together
|
|
|
|
xaccScrubMergeLotSubSplits (lot, FALSE);
|
|
|
|
splits_deleted = gncScrubLotLinks (lot);
|
|
|
|
|
|
|
|
// 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)", lotname ? lotname : "(no lotname)", splits_deleted);
|
|
|
|
g_free (lotname);
|
|
|
|
|
|
|
|
return splits_deleted;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ============================================================== */
|
|
|
|
|
|
|
|
void
|
|
|
|
gncScrubBusinessAccountLots (Account *acc)
|
|
|
|
{
|
|
|
|
LotList *lots, *node;
|
|
|
|
if (!acc) return;
|
|
|
|
if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
|
|
|
|
|
|
|
|
ENTER ("(acc=%s)", xaccAccountGetName(acc));
|
|
|
|
xaccAccountBeginEdit(acc);
|
|
|
|
|
|
|
|
lots = xaccAccountGetLotList(acc);
|
|
|
|
for (node = lots; node; node = node->next)
|
|
|
|
{
|
|
|
|
GNCLot *lot = node->data;
|
|
|
|
if (lot)
|
|
|
|
gncScrubBusinessLot (lot);
|
|
|
|
}
|
|
|
|
g_list_free(lots);
|
|
|
|
xaccAccountCommitEdit(acc);
|
|
|
|
LEAVE ("(acc=%s)", xaccAccountGetName(acc));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ============================================================== */
|
|
|
|
|
|
|
|
static void
|
|
|
|
lot_scrub_cb (Account *acc, gpointer data)
|
|
|
|
{
|
|
|
|
if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
|
|
|
|
gncScrubBusinessAccountLots (acc);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
gncScrubBusinessAccountTreeLots (Account *acc)
|
|
|
|
{
|
|
|
|
if (!acc) return;
|
|
|
|
|
|
|
|
gnc_account_foreach_descendant(acc, lot_scrub_cb, NULL);
|
|
|
|
gncScrubBusinessAccountLots (acc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ========================== END OF FILE ========================= */
|