You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gnucash/src/engine/Scrub.c

883 lines
23 KiB

/********************************************************************\
* Scrub.c -- convert single-entry accounts into clean double-entry *
* *
* 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:
* Scrub.c
*
* FUNCTION:
* Provides a set of functions and utilities for scrubbing clean
* single-entry accounts so that they can be promoted into
* self-consistent, clean double-entry accounts.
*
* HISTORY:
* Created by Linas Vepstas December 1998
* Copyright (c) 1998-2000, 2003 Linas Vepstas <linas@linas.org>
* Copyright (c) 2002 Christian Stimming
*/
#include "config.h"
#include <glib.h>
#include <glib/gi18n.h>
#include <stdio.h>
#include <string.h>
#include "Account.h"
#include "AccountP.h"
#include "Group.h"
#include "GroupP.h"
#include "Scrub.h"
#include "ScrubP.h"
#include "Transaction.h"
#include "TransactionP.h"
#include "gnc-commodity.h"
static QofLogModule log_module = GNC_MOD_SCRUB;
/* ================================================================ */
void
xaccGroupScrubOrphans (AccountGroup *grp)
{
GList *list;
GList *node;
if (!grp) return;
list = xaccGroupGetAccountList (grp);
for (node = list; node; node = node->next)
{
Account *account = node->data;
xaccAccountTreeScrubOrphans (account);
}
}
void
xaccAccountTreeScrubOrphans (Account *acc)
{
if (!acc) return;
xaccGroupScrubOrphans (xaccAccountGetChildren(acc));
xaccAccountScrubOrphans (acc);
}
static void
TransScrubOrphansFast (Transaction *trans, AccountGroup *root)
{
GList *node;
if (!trans) return;
g_return_if_fail (root);
for (node = trans->splits; node; node = node->next)
{
Split *split = node->data;
Account *orph;
if (split->acc) continue;
DEBUG ("Found an orphan \n");
orph = xaccScrubUtilityGetOrMakeAccount (root, trans->common_currency, _("Orphan"));
if (!orph) continue;
xaccAccountBeginEdit (orph);
xaccAccountInsertSplit (orph, split);
xaccAccountCommitEdit (orph);
}
}
void
xaccAccountScrubOrphans (Account *acc)
{
GList *node;
const char *str;
if (!acc) return;
str = xaccAccountGetName (acc);
str = str ? str : "(null)";
PINFO ("Looking for orphans in account %s \n", str);
for (node = xaccAccountGetSplitList(acc); node; node = node->next)
{
Split *split = node->data;
TransScrubOrphansFast (xaccSplitGetParent (split),
xaccAccountGetRoot (acc));
}
}
void
xaccTransScrubOrphans (Transaction *trans)
{
SplitList *node;
QofBook *book = NULL;
AccountGroup *root = NULL;
for (node = trans->splits; node; node = node->next)
{
Split *split = node->data;
if (split->acc)
{
TransScrubOrphansFast (trans, xaccAccountGetRoot(split->acc));
return;
}
}
/* If we got to here, then *none* of the splits belonged to an
* account. Not a happy situation. We should dig an account
* out of the book the transaction belongs to.
* XXX we should probably *always* to this, instead of the above loop!
*/
PINFO ("Free Floating Transaction!");
book = xaccTransGetBook (trans);
root = xaccGetAccountGroup (book);
TransScrubOrphansFast (trans, root);
}
/* ================================================================ */
void
xaccGroupScrubSplits (AccountGroup *group)
{
GList *list;
GList *node;
if (!group) return;
list = xaccGroupGetAccountList (group);
for (node = list; node; node = node->next)
{
Account *account = node->data;
xaccAccountTreeScrubSplits (account);
}
}
void
xaccAccountTreeScrubSplits (Account *account)
{
xaccGroupScrubSplits (xaccAccountGetChildren(account));
xaccAccountScrubSplits (account);
}
void
xaccAccountScrubSplits (Account *account)
{
GList *node;
for (node = xaccAccountGetSplitList (account); node; node = node->next)
xaccSplitScrub (node->data);
}
void
xaccTransScrubSplits (Transaction *trans)
{
gnc_commodity *currency;
GList *node;
if (!trans) return;
/* The split scrub expects the transaction to have a currency! */
currency = xaccTransGetCurrency (trans);
if (!currency)
{
PERR ("Transaction doesn't have a currency!");
}
for (node = trans->splits; node; node = node->next)
{
xaccSplitScrub (node->data);
}
}
void
xaccSplitScrub (Split *split)
{
Account *account;
Transaction *trans;
gnc_numeric value, amount;
gnc_commodity *currency;
int scu;
if (!split) return;
ENTER ("(split=%p)", split);
trans = xaccSplitGetParent (split);
if (!trans) return;
account = xaccSplitGetAccount (split);
/* If theres no account, this split is an orphan.
* We need to fix that first, before proceeding.
*/
if (!account)
{
xaccTransScrubOrphans (trans);
account = xaccSplitGetAccount (split);
}
/* Grrr... the register gnc_split_register_load() line 203 of
* src/register/ledger-core/split-register-load.c will create
* free-floating bogus transactions. Ignore these for now ...
*/
if (!account)
{
PINFO ("Free Floating Transaction!");
return;
}
/* Split amounts and values should be valid numbers */
value = xaccSplitGetValue (split);
if (gnc_numeric_check (value))
{
value = gnc_numeric_zero();
xaccSplitSetValue (split, value);
}
amount = xaccSplitGetAmount (split);
if (gnc_numeric_check (amount))
{
amount = gnc_numeric_zero();
xaccSplitSetAmount (split, amount);
}
currency = xaccTransGetCurrency (trans);
/* If the account doesn't have a commodity,
* we should attempt to fix that first.
*/
if (!account->commodity)
{
xaccAccountScrubCommodity (account);
}
if (!account->commodity ||
!gnc_commodity_equiv (account->commodity, currency))
{
LEAVE ("(split=%p) inequiv currency", split);
return;
}
scu = MIN (xaccAccountGetCommoditySCU (account),
gnc_commodity_get_fraction (currency));
if (gnc_numeric_same (amount, value, scu, GNC_HOW_RND_ROUND))
{
return;
}
/*
* This will be hit every time you answer yes to the dialog "The
* current transaction has changed. Would you like to record it.
*/
PINFO ("Adjusted split with mismatched values, desc=\"%s\" memo=\"%s\""
" old amount %s %s, new amount %s",
trans->description, split->memo,
gnc_num_dbg_to_string (xaccSplitGetAmount(split)),
gnc_commodity_get_mnemonic (currency),
gnc_num_dbg_to_string (xaccSplitGetValue(split)));
xaccTransBeginEdit (trans);
xaccSplitSetAmount (split, value);
xaccTransCommitEdit (trans);
LEAVE ("(split=%p)", split);
}
/* ================================================================ */
void
xaccGroupScrubImbalance (AccountGroup *grp)
{
GList *list;
GList *node;
if (!grp) return;
list = xaccGroupGetAccountList (grp);
for (node = list; node; node = node->next)
{
Account *account = node->data;
xaccAccountTreeScrubImbalance (account);
}
}
void
xaccAccountTreeScrubImbalance (Account *acc)
{
xaccGroupScrubImbalance (xaccAccountGetChildren(acc));
xaccAccountScrubImbalance (acc);
}
void
xaccAccountScrubImbalance (Account *acc)
{
GList *node;
const char *str;
if (!acc) return;
str = xaccAccountGetName(acc);
str = str ? str : "(null)";
PINFO ("Looking for imbalance in account %s \n", str);
for(node = xaccAccountGetSplitList(acc); node; node = node->next)
{
Split *split = node->data;
Transaction *trans = xaccSplitGetParent(split);
xaccTransScrubImbalance (trans, xaccAccountGetRoot (acc), NULL);
}
}
void
xaccTransScrubImbalance (Transaction *trans, AccountGroup *root,
Account *parent)
{
Split *balance_split = NULL;
QofBook *book = NULL;
gnc_numeric imbalance;
Account *account;
SplitList *node, *slist;
if (!trans) return;
ENTER ("()");
xaccTransScrubSplits (trans);
/* If the transaction is balanced, nothing more to do */
imbalance = xaccTransGetImbalance (trans);
if (gnc_numeric_zero_p (imbalance)) return;
slist = xaccTransGetSplitList (trans);
if (!slist) return;
if (!parent)
{
if (!root)
{
Split *s = slist->data;
if (NULL == s->acc)
{
/* This should never occur, since xaccTransScrubSplits()
* above should have fixed things up. */
PERR ("Split is not assigned to any account");
}
root = xaccAccountGetRoot (s->acc);
if (NULL == root)
{
/* This should never occur, accounts are always
* in an account group */
PERR ("Can't find root account");
book = xaccTransGetBook (trans);
root = xaccGetAccountGroup (book);
}
if (NULL == root)
{
/* This can't occur, things should be in books */
PERR ("Bad data corruption, no root account in book");
return;
}
}
account = xaccScrubUtilityGetOrMakeAccount (root,
trans->common_currency, _("Imbalance"));
}
else
{
account = parent;
}
if (!account)
{
PERR ("Can't get balancing account");
return;
}
for (node = slist; node; node = node->next)
{
Split *split = node->data;
if (xaccSplitGetAccount (split) == account)
{
balance_split = split;
break;
}
}
/* Put split into account before setting split value */
if (!balance_split)
{
balance_split = xaccMallocSplit (trans->inst.book);
xaccAccountBeginEdit (account);
xaccAccountInsertSplit (account, balance_split);
xaccAccountCommitEdit (account);
}
PINFO ("unbalanced transaction");
{
const gnc_commodity *currency;
const gnc_commodity *commodity;
gnc_numeric old_value, new_value;
xaccTransBeginEdit (trans);
currency = xaccTransGetCurrency (trans);
old_value = xaccSplitGetValue (balance_split);
/* Note: We have to round for the commodity's fraction, NOT any
* already existing denominator (bug #104343), because either one
* of the denominators might already be reduced. */
new_value = gnc_numeric_sub (old_value, imbalance,
gnc_commodity_get_fraction(currency),
GNC_HOW_RND_ROUND);
xaccSplitSetValue (balance_split, new_value);
commodity = xaccAccountGetCommodity (account);
if (gnc_commodity_equiv (currency, commodity))
{
xaccSplitSetAmount (balance_split, new_value);
}
xaccTransAppendSplit (trans, balance_split);
xaccSplitScrub (balance_split);
xaccTransCommitEdit (trans);
}
LEAVE ("()");
}
/* ================================================================ */
/* The xaccTransFindCommonCurrency () method returns
* a gnc_commodity indicating a currency denomination that all
* of the splits in this transaction have in common, using the
* old/obsolete currency/security fields of the split accounts.
*/
static gnc_commodity *
FindCommonExclSCurrency (SplitList *splits,
gnc_commodity * ra, gnc_commodity * rb,
Split *excl_split)
{
GList *node;
if (!splits) return NULL;
for (node = splits; node; node = node->next)
{
Split *s = node->data;
gnc_commodity * sa, * sb;
if (s == excl_split) continue;
g_return_val_if_fail (s->acc, NULL);
sa = DxaccAccountGetCurrency (s->acc);
sb = xaccAccountGetCommodity (s->acc);
if (ra && rb) {
int aa = !gnc_commodity_equiv(ra,sa);
int ab = !gnc_commodity_equiv(ra,sb);
int ba = !gnc_commodity_equiv(rb,sa);
int bb = !gnc_commodity_equiv(rb,sb);
if ( (!aa) && bb) rb = NULL;
else
if ( (!ab) && ba) rb = NULL;
else
if ( (!ba) && ab) ra = NULL;
else
if ( (!bb) && aa) ra = NULL;
else
if ( aa && bb && ab && ba ) { ra = NULL; rb = NULL; }
if (!ra) { ra = rb; rb = NULL; }
}
else
if (ra && !rb) {
int aa = !gnc_commodity_equiv(ra,sa);
int ab = !gnc_commodity_equiv(ra,sb);
if ( aa && ab ) ra = NULL;
}
if ((!ra) && (!rb)) return NULL;
}
return (ra);
}
/* This is the wrapper for those calls (i.e. the older ones) which
* don't exclude one split from the splitlist when looking for a
* common currency.
*/
static gnc_commodity *
FindCommonCurrency (GList *splits, gnc_commodity * ra, gnc_commodity * rb)
{
return FindCommonExclSCurrency(splits, ra, rb, NULL);
}
static gnc_commodity *
xaccTransFindOldCommonCurrency (Transaction *trans, QofBook *book)
{
gnc_commodity *ra, *rb, *retval;
Split *split;
if (!trans) return NULL;
if (trans->splits == NULL) return NULL;
g_return_val_if_fail (book, NULL);
split = trans->splits->data;
if (!split || NULL == split->acc) return NULL;
ra = DxaccAccountGetCurrency (split->acc);
rb = xaccAccountGetCommodity (split->acc);
retval = FindCommonCurrency (trans->splits, ra, rb);
/* Compare this value to what we think should be the 'right' value */
if (!trans->common_currency)
{
trans->common_currency = retval;
}
else if (!gnc_commodity_equiv (retval,trans->common_currency))
{
char guid_str[GUID_ENCODING_LENGTH+1];
guid_to_string_buff(xaccTransGetGUID(trans), guid_str);
PWARN ("expected common currency %s but found %s in txn %s\n",
gnc_commodity_get_unique_name (trans->common_currency),
gnc_commodity_get_unique_name (retval), guid_str);
}
if (NULL == retval)
{
/* In every situation I can think of, this routine should return
* common currency. So make note of this ... */
char guid_str[GUID_ENCODING_LENGTH+1];
guid_to_string_buff(xaccTransGetGUID(trans), guid_str);
PWARN ("unable to find a common currency in txn %s, and that is strange.",
guid_str);
}
return retval;
}
/* ================================================================ */
void
xaccTransScrubCurrency (Transaction *trans)
{
SplitList *node;
gnc_commodity *currency;
if (!trans) return;
/* If there are any orphaned splits in a transaction, then the
* this routine will fail. Therefore, we want to make sure that
* tehre are no orphans (splits without parent account).
*/
xaccTransScrubOrphans (trans);
currency = xaccTransGetCurrency (trans);
if (currency) return;
currency = xaccTransFindOldCommonCurrency (trans, trans->inst.book);
if (currency)
{
xaccTransBeginEdit (trans);
xaccTransSetCurrency (trans, currency);
xaccTransCommitEdit (trans);
}
else
{
if (NULL == trans->splits)
{
PWARN ("Transaction \"%s\" has no splits in it!", trans->description);
}
else
{
SplitList *node;
char guid_str[GUID_ENCODING_LENGTH+1];
guid_to_string_buff(xaccTransGetGUID(trans), guid_str);
PWARN ("no common transaction currency found for trans=\"%s\" (%s)",
trans->description, guid_str);
for (node=trans->splits; node; node=node->next)
{
Split *split = node->data;
if (NULL == split->acc)
{
PWARN (" split=\"%s\" is not in any account!", split->memo);
}
else
{
PWARN (" split=\"%s\" account=\"%s\" commodity=\"%s\"",
split->memo, split->acc->accountName, gnc_commodity_get_mnemonic (split->acc->commodity));
}
}
}
}
for (node=trans->splits; node; node=node->next)
{
Split *sp = node->data;
if (!gnc_numeric_equal(xaccSplitGetAmount (sp),
xaccSplitGetValue (sp)))
{
gnc_commodity *acc_currency = xaccAccountGetCommodity (sp->acc);
if (acc_currency == currency)
{
/* This Split needs fixing: The transaction-currency equals
* the account-currency/commodity, but the amount/values are
* inequal i.e. they still correspond to the security
* (amount) and the currency (value). In the new model, the
* value is the amount in the account-commodity -- so it
* needs to be set to equal the amount (since the
* account-currency doesn't exist anymore).
*
* Note: Nevertheless we lose some information here. Namely,
* the information that the 'amount' in 'account-old-security'
* was worth 'value' in 'account-old-currency'. Maybe it would
* be better to store that information in the price database?
* But then, for old currency transactions there is still the
* 'other' transaction, which is going to keep that
* information. So I don't bother with that here. -- cstim,
* 2002/11/20. */
PWARN ("Adjusted split with mismatched values, desc=\"%s\" memo=\"%s\""
" old amount %s %s, new amount %s",
trans->description, sp->memo,
gnc_num_dbg_to_string (xaccSplitGetAmount(sp)),
gnc_commodity_get_mnemonic (currency),
gnc_num_dbg_to_string (xaccSplitGetValue(sp)));
xaccTransBeginEdit (trans);
xaccSplitSetAmount (sp, xaccSplitGetValue(sp));
xaccTransCommitEdit (trans);
}
/*else
{
PINFO ("Ok: Split '%s' Amount %s %s, value %s %s",
xaccSplitGetMemo (sp),
gnc_num_dbg_to_string (amount),
gnc_commodity_get_mnemonic (currency),
gnc_num_dbg_to_string (value),
gnc_commodity_get_mnemonic (acc_currency));
}*/
}
}
}
/* ================================================================ */
void
xaccAccountScrubCommodity (Account *account)
{
gnc_commodity *commodity;
if (!account) return;
commodity = xaccAccountGetCommodity (account);
if (commodity) return;
/* Use the 'obsolete' routines to try to figure out what the
* account commodity should have been. */
commodity = DxaccAccountGetSecurity (account);
if (commodity)
{
xaccAccountSetCommodity (account, commodity);
return;
}
commodity = DxaccAccountGetCurrency (account);
if (commodity)
{
xaccAccountSetCommodity (account, commodity);
return;
}
PERR ("Account \"%s\" does not have a commodity!", account->accountName);
}
/* ================================================================ */
static void
xaccAccountDeleteOldData (Account *account)
{
if (!account) return;
kvp_frame_set_slot_nc (account->inst.kvp_data, "old-currency", NULL);
kvp_frame_set_slot_nc (account->inst.kvp_data, "old-security", NULL);
kvp_frame_set_slot_nc (account->inst.kvp_data, "old-currency-scu", NULL);
kvp_frame_set_slot_nc (account->inst.kvp_data, "old-security-scu", NULL);
}
static int
scrub_trans_currency_helper (Transaction *t, gpointer data)
{
xaccTransScrubCurrency (t);
return 0;
}
static gpointer
scrub_account_commodity_helper (Account *account, gpointer data)
{
xaccAccountScrubCommodity (account);
xaccAccountDeleteOldData (account);
return NULL;
}
void
xaccGroupScrubCommodities (AccountGroup *group)
{
if (!group) return;
xaccAccountGroupBeginEdit (group);
xaccGroupForEachTransaction (group, scrub_trans_currency_helper, NULL);
xaccGroupForEachAccount (group, scrub_account_commodity_helper,
NULL, TRUE);
xaccAccountGroupCommitEdit (group);
}
/* ================================================================ */
static gboolean
check_quote_source (gnc_commodity *com, gpointer data)
{
gboolean *commodity_has_quote_src = (gboolean *)data;
if (com && !gnc_commodity_is_iso(com))
*commodity_has_quote_src |= gnc_commodity_get_quote_flag(com);
return TRUE;
}
static gpointer
move_quote_source (Account *account, gpointer data)
{
gnc_commodity *com;
gnc_quote_source *quote_source;
gboolean new_style = GPOINTER_TO_INT(data);
const char *source, *tz;
com = xaccAccountGetCommodity(account);
if (!com)
return NULL;
if (!new_style) {
source = dxaccAccountGetPriceSrc(account);
if (!source || !*source)
return NULL;
tz = dxaccAccountGetQuoteTZ(account);
PINFO("to %8s from %s", gnc_commodity_get_mnemonic(com),
xaccAccountGetName(account));
gnc_commodity_set_quote_flag(com, TRUE);
quote_source = gnc_quote_source_lookup_by_internal(source);
if (!quote_source)
quote_source = gnc_quote_source_add_new(source, FALSE);
gnc_commodity_set_quote_source(com, quote_source);
gnc_commodity_set_quote_tz(com, tz);
}
dxaccAccountSetPriceSrc(account, NULL);
dxaccAccountSetQuoteTZ(account, NULL);
return NULL;
}
void
xaccGroupScrubQuoteSources (AccountGroup *group, gnc_commodity_table *table)
{
gboolean new_style = FALSE;
ENTER(" ");
if (!group || !table) {
LEAVE("Oops");
return;
}
gnc_commodity_table_foreach_commodity (table, check_quote_source, &new_style);
xaccAccountGroupBeginEdit (group);
xaccGroupForEachAccount (group, move_quote_source,
GINT_TO_POINTER(new_style), TRUE);
xaccAccountGroupCommitEdit (group);
LEAVE("Migration done");
}
/* ================================================================ */
Account *
xaccScrubUtilityGetOrMakeAccount (AccountGroup *root, gnc_commodity * currency,
const char *name_root)
{
char * accname;
Account * acc;
g_return_val_if_fail (root, NULL);
/* build the account name */
if (!currency)
{
PERR ("No currency specified!");
return NULL;
}
accname = g_strconcat (name_root, "-",
gnc_commodity_get_mnemonic (currency), NULL);
/* See if we've got one of these going already ... */
acc = xaccGetAccountFromName (root, accname);
if (acc == NULL)
{
/* Guess not. We'll have to build one. */
acc = xaccMallocAccount (root->book);
xaccAccountBeginEdit (acc);
xaccAccountSetName (acc, accname);
xaccAccountSetCommodity (acc, currency);
xaccAccountSetType (acc, BANK);
/* Hang the account off the root. */
xaccGroupInsertAccount (root, acc);
xaccAccountCommitEdit (acc);
}
g_free (accname);
return acc;
}
/* ==================== END OF FILE ==================== */