gnucash/libgnucash/engine/Account.c
John Ralls 4fc61b2ac8 Commit imap conversions once per account instead of per import map slot.
Commits are expensive in the SQL backend; this resulted in a >40x
speedup in loading a database with 3400 import map slots (>2 hours to 3
minutes).
2017-08-19 20:25:57 +02:00

6160 lines
185 KiB
C

/********************************************************************\
* Account.c -- Account data structure implementation *
* Copyright (C) 1997 Robin D. Clark *
* Copyright (C) 1997-2003 Linas Vepstas <linas@linas.org> *
* Copyright (C) 2007 David Hampton <hampton@employees.org> *
* *
* 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 *
* *
\********************************************************************/
#include "config.h"
#include <glib.h>
#include <glib/gi18n.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "AccountP.h"
#include "Split.h"
#include "Transaction.h"
#include "TransactionP.h"
#include "gnc-event.h"
#include "gnc-glib-utils.h"
#include "gnc-lot.h"
#include "gnc-pricedb.h"
#include "qofinstance-p.h"
#include "gnc-features.h"
static QofLogModule log_module = GNC_MOD_ACCOUNT;
/* The Canonical Account Separator. Pre-Initialized. */
static gchar account_separator[8] = ".";
static gunichar account_uc_separator = ':';
/* Predefined KVP paths */
static const char *KEY_ASSOC_INCOME_ACCOUNT = "ofx/associated-income-account";
#define AB_KEY "hbci"
#define AB_ACCOUNT_ID "account-id"
#define AB_ACCOUNT_UID "account-uid"
#define AB_BANK_CODE "bank-code"
#define AB_TRANS_RETRIEVAL "trans-retrieval"
enum
{
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_NAME, /* Table */
PROP_FULL_NAME, /* Constructed */
PROP_CODE, /* Table */
PROP_DESCRIPTION, /* Table */
PROP_COLOR, /* KVP */
PROP_NOTES, /* KVP */
PROP_TYPE, /* Table */
// PROP_PARENT, /* Table, Not a property */
PROP_COMMODITY, /* Table */
PROP_COMMODITY_SCU, /* Table */
PROP_NON_STD_SCU, /* Table */
PROP_END_BALANCE, /* Constructed */
PROP_END_CLEARED_BALANCE, /* Constructed */
PROP_END_RECONCILED_BALANCE, /* Constructed */
PROP_TAX_RELATED, /* KVP */
PROP_TAX_CODE, /* KVP */
PROP_TAX_SOURCE, /* KVP */
PROP_TAX_COPY_NUMBER, /* KVP */
PROP_HIDDEN, /* Table slot exists, but in KVP in memory & xml */
PROP_PLACEHOLDER, /* Table slot exists, but in KVP in memory & xml */
PROP_FILTER, /* KVP */
PROP_SORT_ORDER, /* KVP */
PROP_SORT_REVERSED,
PROP_LOT_NEXT_ID, /* KVP */
PROP_ONLINE_ACCOUNT, /* KVP */
PROP_OFX_INCOME_ACCOUNT, /* KVP */
PROP_AB_ACCOUNT_ID, /* KVP */
PROP_AB_ACCOUNT_UID, /* KVP */
PROP_AB_BANK_CODE, /* KVP */
PROP_AB_TRANS_RETRIEVAL, /* KVP */
PROP_RUNTIME_0,
PROP_POLICY, /* Cached Value */
PROP_MARK, /* Runtime Value */
PROP_SORT_DIRTY, /* Runtime Value */
PROP_BALANCE_DIRTY, /* Runtime Value */
PROP_START_BALANCE, /* Runtime Value */
PROP_START_CLEARED_BALANCE, /* Runtime Value */
PROP_START_RECONCILED_BALANCE, /* Runtime Value */
};
#define GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), GNC_TYPE_ACCOUNT, AccountPrivate))
/********************************************************************\
* Because I can't use C++ for this project, doesn't mean that I *
* can't pretend to! These functions perform actions on the *
* account data structure, in order to encapsulate the knowledge *
* of the internals of the Account in one file. *
\********************************************************************/
static void xaccAccountBringUpToDate (Account *acc);
/********************************************************************\
* gnc_get_account_separator *
* returns the current account separator character *
* *
* Args: none *
* Returns: account separator character *
\*******************************************************************/
const gchar *
gnc_get_account_separator_string (void)
{
return account_separator;
}
gunichar
gnc_get_account_separator (void)
{
return account_uc_separator;
}
void
gnc_set_account_separator (const gchar *separator)
{
gunichar uc;
gint count;
uc = g_utf8_get_char_validated(separator, -1);
if ((uc == (gunichar) - 2) || (uc == (gunichar) - 1) || g_unichar_isalnum(uc))
{
account_uc_separator = ':';
strcpy(account_separator, ":");
return;
}
account_uc_separator = uc;
count = g_unichar_to_utf8(uc, account_separator);
account_separator[count] = '\0';
}
gchar *gnc_account_name_violations_errmsg (const gchar *separator, GList* invalid_account_names)
{
GList *node;
gchar *message = NULL;
gchar *account_list = NULL;
if ( !invalid_account_names )
return NULL;
for ( node = invalid_account_names; node; node = g_list_next(node))
{
if ( !account_list )
account_list = node->data;
else
{
gchar *tmp_list = NULL;
tmp_list = g_strconcat (account_list, "\n", node->data, NULL );
g_free ( account_list );
account_list = tmp_list;
}
}
/* Translators: The first %s will be the account separator character,
the second %s is a list of account names.
The resulting string will be displayed to the user if there are
account names containing the separator character. */
message = g_strdup_printf(
_("The separator character \"%s\" is used in one or more account names.\n\n"
"This will result in unexpected behaviour. "
"Either change the account names or choose another separator character.\n\n"
"Below you will find the list of invalid account names:\n"
"%s"), separator, account_list );
g_free ( account_list );
return message;
}
GList *gnc_account_list_name_violations (QofBook *book, const gchar *separator)
{
Account *root_account = gnc_book_get_root_account(book);
GList *accounts, *node;
GList *invalid_list = NULL;
g_return_val_if_fail (separator != NULL, NULL);
if (root_account == NULL)
return NULL;
accounts = gnc_account_get_descendants (root_account);
for (node = accounts; node; node = g_list_next(node))
{
Account *acct = (Account*)node->data;
gchar *acct_name = g_strdup ( xaccAccountGetName ( acct ) );
if ( g_strstr_len ( acct_name, -1, separator ) )
invalid_list = g_list_prepend ( invalid_list, (gpointer) acct_name );
else
g_free ( acct_name );
}
if (accounts != NULL)
{
g_list_free(accounts);
}
return invalid_list;
}
/********************************************************************\
\********************************************************************/
G_INLINE_FUNC void mark_account (Account *acc);
void
mark_account (Account *acc)
{
qof_instance_set_dirty(&acc->inst);
}
/********************************************************************\
\********************************************************************/
/* GObject Initialization */
G_DEFINE_TYPE(Account, gnc_account, QOF_TYPE_INSTANCE)
static void
gnc_account_init(Account* acc)
{
AccountPrivate *priv;
priv = GET_PRIVATE(acc);
priv->parent = NULL;
priv->children = NULL;
priv->accountName = CACHE_INSERT("");
priv->accountCode = CACHE_INSERT("");
priv->description = CACHE_INSERT("");
priv->type = ACCT_TYPE_NONE;
priv->mark = 0;
priv->policy = xaccGetFIFOPolicy();
priv->lots = NULL;
priv->commodity = NULL;
priv->commodity_scu = 0;
priv->non_standard_scu = FALSE;
priv->balance = gnc_numeric_zero();
priv->cleared_balance = gnc_numeric_zero();
priv->reconciled_balance = gnc_numeric_zero();
priv->starting_balance = gnc_numeric_zero();
priv->starting_cleared_balance = gnc_numeric_zero();
priv->starting_reconciled_balance = gnc_numeric_zero();
priv->balance_dirty = FALSE;
priv->splits = NULL;
priv->sort_dirty = FALSE;
}
static void
gnc_account_dispose (GObject *acctp)
{
G_OBJECT_CLASS(gnc_account_parent_class)->dispose(acctp);
}
static void
gnc_account_finalize(GObject* acctp)
{
G_OBJECT_CLASS(gnc_account_parent_class)->finalize(acctp);
}
/* Note that g_value_set_object() refs the object, as does
* g_object_get(). But g_object_get() only unrefs once when it disgorges
* the object, leaving an unbalanced ref, which leaks. So instead of
* using g_value_set_object(), use g_value_take_object() which doesn't
* ref the object when used in get_property().
*/
static void
gnc_account_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
Account *account;
AccountPrivate *priv;
const gchar *key;
g_return_if_fail(GNC_IS_ACCOUNT(object));
account = GNC_ACCOUNT(object);
priv = GET_PRIVATE(account);
switch (prop_id)
{
case PROP_NAME:
g_value_set_string(value, priv->accountName);
break;
case PROP_FULL_NAME:
g_value_take_string(value, gnc_account_get_full_name(account));
break;
case PROP_CODE:
g_value_set_string(value, priv->accountCode);
break;
case PROP_DESCRIPTION:
g_value_set_string(value, priv->description);
break;
case PROP_COLOR:
g_value_set_string(value, xaccAccountGetColor(account));
break;
case PROP_NOTES:
g_value_set_string(value, xaccAccountGetNotes(account));
break;
case PROP_TYPE:
// NEED TO BE CONVERTED TO A G_TYPE_ENUM
g_value_set_int(value, priv->type);
break;
case PROP_COMMODITY:
g_value_take_object(value, priv->commodity);
break;
case PROP_COMMODITY_SCU:
g_value_set_int(value, priv->commodity_scu);
break;
case PROP_NON_STD_SCU:
g_value_set_boolean(value, priv->non_standard_scu);
break;
case PROP_SORT_DIRTY:
g_value_set_boolean(value, priv->sort_dirty);
break;
case PROP_BALANCE_DIRTY:
g_value_set_boolean(value, priv->balance_dirty);
break;
case PROP_START_BALANCE:
g_value_set_boxed(value, &priv->starting_balance);
break;
case PROP_START_CLEARED_BALANCE:
g_value_set_boxed(value, &priv->starting_cleared_balance);
break;
case PROP_START_RECONCILED_BALANCE:
g_value_set_boxed(value, &priv->starting_reconciled_balance);
break;
case PROP_END_BALANCE:
g_value_set_boxed(value, &priv->balance);
break;
case PROP_END_CLEARED_BALANCE:
g_value_set_boxed(value, &priv->cleared_balance);
break;
case PROP_END_RECONCILED_BALANCE:
g_value_set_boxed(value, &priv->reconciled_balance);
break;
case PROP_POLICY:
/* MAKE THIS A BOXED VALUE */
g_value_set_pointer(value, priv->policy);
break;
case PROP_MARK:
g_value_set_int(value, priv->mark);
break;
case PROP_TAX_RELATED:
g_value_set_boolean(value, xaccAccountGetTaxRelated(account));
break;
case PROP_TAX_CODE:
g_value_set_string(value, xaccAccountGetTaxUSCode(account));
break;
case PROP_TAX_SOURCE:
g_value_set_string(value,
xaccAccountGetTaxUSPayerNameSource(account));
break;
case PROP_TAX_COPY_NUMBER:
g_value_set_int64(value,
xaccAccountGetTaxUSCopyNumber(account));
break;
case PROP_HIDDEN:
g_value_set_boolean(value, xaccAccountGetHidden(account));
break;
case PROP_PLACEHOLDER:
g_value_set_boolean(value, xaccAccountGetPlaceholder(account));
break;
case PROP_FILTER:
g_value_set_string(value, xaccAccountGetFilter(account));
break;
case PROP_SORT_ORDER:
g_value_set_string(value, xaccAccountGetSortOrder(account));
break;
case PROP_SORT_REVERSED:
g_value_set_boolean(value, xaccAccountGetSortReversed(account));
case PROP_LOT_NEXT_ID:
key = "lot-mgmt/next-id";
/* Pre-set the value in case the frame is empty */
g_value_set_int64 (value, 0);
qof_instance_get_kvp (QOF_INSTANCE (account), key, value);
break;
case PROP_ONLINE_ACCOUNT:
key = "online_id";
qof_instance_get_kvp (QOF_INSTANCE (account), key, value);
break;
case PROP_OFX_INCOME_ACCOUNT:
key = KEY_ASSOC_INCOME_ACCOUNT;
qof_instance_get_kvp (QOF_INSTANCE (account), key, value);
break;
case PROP_AB_ACCOUNT_ID:
key = AB_KEY "/" AB_ACCOUNT_ID;
qof_instance_get_kvp (QOF_INSTANCE (account), key, value);
break;
case PROP_AB_ACCOUNT_UID:
key = AB_KEY "/" AB_ACCOUNT_UID;
qof_instance_get_kvp (QOF_INSTANCE (account), key, value);
break;
case PROP_AB_BANK_CODE:
key = AB_KEY "/" AB_BANK_CODE;
qof_instance_get_kvp (QOF_INSTANCE (account), key, value);
break;
case PROP_AB_TRANS_RETRIEVAL:
key = AB_KEY "/" AB_TRANS_RETRIEVAL;
qof_instance_get_kvp (QOF_INSTANCE (account), key, value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void
gnc_account_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
Account *account;
gnc_numeric *number;
const gchar *key = NULL;
g_return_if_fail(GNC_IS_ACCOUNT(object));
account = GNC_ACCOUNT(object);
if (prop_id < PROP_RUNTIME_0)
g_assert (qof_instance_get_editlevel(account));
switch (prop_id)
{
case PROP_NAME:
xaccAccountSetName(account, g_value_get_string(value));
break;
case PROP_CODE:
xaccAccountSetCode(account, g_value_get_string(value));
break;
case PROP_DESCRIPTION:
xaccAccountSetDescription(account, g_value_get_string(value));
break;
case PROP_COLOR:
xaccAccountSetColor(account, g_value_get_string(value));
break;
case PROP_NOTES:
xaccAccountSetNotes(account, g_value_get_string(value));
break;
case PROP_TYPE:
// NEED TO BE CONVERTED TO A G_TYPE_ENUM
xaccAccountSetType(account, g_value_get_int(value));
break;
case PROP_COMMODITY:
xaccAccountSetCommodity(account, g_value_get_object(value));
break;
case PROP_COMMODITY_SCU:
xaccAccountSetCommoditySCU(account, g_value_get_int(value));
break;
case PROP_NON_STD_SCU:
xaccAccountSetNonStdSCU(account, g_value_get_boolean(value));
break;
case PROP_SORT_DIRTY:
gnc_account_set_sort_dirty(account);
break;
case PROP_BALANCE_DIRTY:
gnc_account_set_balance_dirty(account);
break;
case PROP_START_BALANCE:
number = g_value_get_boxed(value);
gnc_account_set_start_balance(account, *number);
break;
case PROP_START_CLEARED_BALANCE:
number = g_value_get_boxed(value);
gnc_account_set_start_cleared_balance(account, *number);
break;
case PROP_START_RECONCILED_BALANCE:
number = g_value_get_boxed(value);
gnc_account_set_start_reconciled_balance(account, *number);
break;
case PROP_POLICY:
gnc_account_set_policy(account, g_value_get_pointer(value));
break;
case PROP_MARK:
xaccAccountSetMark(account, g_value_get_int(value));
break;
case PROP_TAX_RELATED:
xaccAccountSetTaxRelated(account, g_value_get_boolean(value));
break;
case PROP_TAX_CODE:
xaccAccountSetTaxUSCode(account, g_value_get_string(value));
break;
case PROP_TAX_SOURCE:
xaccAccountSetTaxUSPayerNameSource(account,
g_value_get_string(value));
break;
case PROP_TAX_COPY_NUMBER:
xaccAccountSetTaxUSCopyNumber(account,
g_value_get_int64(value));
break;
case PROP_HIDDEN:
xaccAccountSetHidden(account, g_value_get_boolean(value));
break;
case PROP_PLACEHOLDER:
xaccAccountSetPlaceholder(account, g_value_get_boolean(value));
break;
case PROP_FILTER:
xaccAccountSetFilter(account, g_value_get_string(value));
break;
case PROP_SORT_ORDER:
xaccAccountSetSortOrder(account, g_value_get_string(value));
break;
case PROP_SORT_REVERSED:
xaccAccountSetSortReversed(account, g_value_get_boolean(value));
case PROP_LOT_NEXT_ID:
key = "lot-mgmt/next-id";
qof_instance_set_kvp (QOF_INSTANCE (account), key, value);
break;
case PROP_ONLINE_ACCOUNT:
key = "online_id";
qof_instance_set_kvp (QOF_INSTANCE (account), key, value);
break;
case PROP_OFX_INCOME_ACCOUNT:
key = KEY_ASSOC_INCOME_ACCOUNT;
qof_instance_set_kvp (QOF_INSTANCE (account), key, value);
break;
case PROP_AB_ACCOUNT_ID:
key = AB_KEY "/" AB_ACCOUNT_ID;
qof_instance_set_kvp (QOF_INSTANCE (account), key, value);
break;
case PROP_AB_ACCOUNT_UID:
key = AB_KEY "/" AB_ACCOUNT_UID;
qof_instance_set_kvp (QOF_INSTANCE (account), key, value);
break;
case PROP_AB_BANK_CODE:
key = AB_KEY "/" AB_BANK_CODE;
qof_instance_set_kvp (QOF_INSTANCE (account), key, value);
break;
case PROP_AB_TRANS_RETRIEVAL:
key = AB_KEY "/" AB_TRANS_RETRIEVAL;
qof_instance_set_kvp (QOF_INSTANCE (account), key, value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void
gnc_account_class_init (AccountClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = gnc_account_dispose;
gobject_class->finalize = gnc_account_finalize;
gobject_class->set_property = gnc_account_set_property;
gobject_class->get_property = gnc_account_get_property;
g_type_class_add_private(klass, sizeof(AccountPrivate));
g_object_class_install_property
(gobject_class,
PROP_NAME,
g_param_spec_string ("name",
"Account Name",
"The accountName is an arbitrary string "
"assigned by the user. It is intended to "
"a short, 5 to 30 character long string "
"that is displayed by the GUI as the "
"account mnemonic. Account names may be "
"repeated. but no two accounts that share "
"a parent may have the same name.",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_FULL_NAME,
g_param_spec_string ("fullname",
"Full Account Name",
"The name of the account concatenated with "
"all its parent account names to indicate "
"a unique account.",
NULL,
G_PARAM_READABLE));
g_object_class_install_property
(gobject_class,
PROP_CODE,
g_param_spec_string ("code",
"Account Code",
"The account code is an arbitrary string "
"assigned by the user. It is intended to "
"be reporting code that is a synonym for "
"the accountName.",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_DESCRIPTION,
g_param_spec_string ("description",
"Account Description",
"The account description is an arbitrary "
"string assigned by the user. It is intended "
"to be a longer, 1-5 sentence description of "
"what this account is all about.",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_COLOR,
g_param_spec_string ("color",
"Account Color",
"The account color is a color string assigned "
"by the user. It is intended to highlight the "
"account based on the users wishes.",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_NOTES,
g_param_spec_string ("notes",
"Account Notes",
"The account notes is an arbitrary provided "
"for the user to attach any other text that "
"they would like to associate with the account.",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_TYPE,
g_param_spec_int ("type",
"Account Type",
"The account type, picked from the enumerated list "
"that includes ACCT_TYPE_BANK, ACCT_TYPE_STOCK, "
"ACCT_TYPE_CREDIT, ACCT_TYPE_INCOME, etc.",
ACCT_TYPE_NONE,
NUM_ACCOUNT_TYPES - 1,
ACCT_TYPE_BANK,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_COMMODITY,
g_param_spec_object ("commodity",
"Commodity",
"The commodity field denotes the kind of "
"'stuff' stored in this account, whether "
"it is USD, gold, stock, etc.",
GNC_TYPE_COMMODITY,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_COMMODITY_SCU,
g_param_spec_int ("commodity-scu",
"Commodity SCU",
"The smallest fraction of the commodity that is "
"tracked. This number is used as the denominator "
"value in 1/x, so a value of 100 says that the "
"commodity can be divided into hundreths. E.G."
"1 USD can be divided into 100 cents.",
0,
G_MAXINT32,
1000000,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_NON_STD_SCU,
g_param_spec_boolean ("non-std-scu",
"Non-std SCU",
"TRUE if the account SCU doesn't match "
"the commodity SCU. This indicates a case "
"where the two were accidentally set to "
"mismatched values in older versions of "
"GnuCash.",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_SORT_DIRTY,
g_param_spec_boolean("sort-dirty",
"Sort Dirty",
"TRUE if the splits in the account needs to be "
"resorted. This flag is set by the accounts "
"code for certain internal modifications, or "
"when external code calls the engine to say a "
"split has been modified in a way that may "
"affect the sort order of the account. Note: "
"This value can only be set to TRUE.",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_BALANCE_DIRTY,
g_param_spec_boolean("balance-dirty",
"Balance Dirty",
"TRUE if the running balances in the account "
"needs to be recalculated. This flag is set "
"by the accounts code for certain internal "
"modifications, or when external code calls "
"the engine to say a split has been modified. "
"Note: This value can only be set to TRUE.",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_START_BALANCE,
g_param_spec_boxed("start-balance",
"Starting Balance",
"The starting balance for the account. This "
"parameter is intended for use with backends that "
"do not return the complete list of splits for an "
"account, but rather return a partial list. In "
"such a case, the backend will typically return "
"all of the splits after some certain date, and "
"the 'starting balance' will represent the "
"summation of the splits up to that date.",
GNC_TYPE_NUMERIC,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_START_CLEARED_BALANCE,
g_param_spec_boxed("start-cleared-balance",
"Starting Cleared Balance",
"The starting cleared balance for the account. "
"This parameter is intended for use with backends "
"that do not return the complete list of splits "
"for an account, but rather return a partial "
"list. In such a case, the backend will "
"typically return all of the splits after "
"some certain date, and the 'starting cleared "
"balance' will represent the summation of the "
"splits up to that date.",
GNC_TYPE_NUMERIC,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_START_RECONCILED_BALANCE,
g_param_spec_boxed("start-reconciled-balance",
"Starting Reconciled Balance",
"The starting reconciled balance for the "
"account. This parameter is intended for use "
"with backends that do not return the complete "
"list of splits for an account, but rather return "
"a partial list. In such a case, the backend "
"will typically return all of the splits after "
"some certain date, and the 'starting reconciled "
"balance' will represent the summation of the "
"splits up to that date.",
GNC_TYPE_NUMERIC,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_END_BALANCE,
g_param_spec_boxed("end-balance",
"Ending Account Balance",
"This is the current ending balance for the "
"account. It is computed from the sum of the "
"starting balance and all splits in the account.",
GNC_TYPE_NUMERIC,
G_PARAM_READABLE));
g_object_class_install_property
(gobject_class,
PROP_END_CLEARED_BALANCE,
g_param_spec_boxed("end-cleared-balance",
"Ending Account Cleared Balance",
"This is the current ending cleared balance for "
"the account. It is computed from the sum of the "
"starting balance and all cleared splits in the "
"account.",
GNC_TYPE_NUMERIC,
G_PARAM_READABLE));
g_object_class_install_property
(gobject_class,
PROP_END_RECONCILED_BALANCE,
g_param_spec_boxed("end-reconciled-balance",
"Ending Account Reconciled Balance",
"This is the current ending reconciled balance "
"for the account. It is computed from the sum of "
"the starting balance and all reconciled splits "
"in the account.",
GNC_TYPE_NUMERIC,
G_PARAM_READABLE));
g_object_class_install_property
(gobject_class,
PROP_POLICY,
g_param_spec_pointer ("policy",
"Policy",
"The account lots policy.",
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_MARK,
g_param_spec_int ("acct-mark",
"Account Mark",
"Ipsum Lorem",
0,
G_MAXINT16,
0,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_TAX_RELATED,
g_param_spec_boolean ("tax-related",
"Tax Related",
"Whether the account maps to an entry on an "
"income tax document.",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_TAX_CODE,
g_param_spec_string ("tax-code",
"Tax Code",
"This is the code for mapping an account to a "
"specific entry on a taxable document. In the "
"United States it is used to transfer totals "
"into tax preparation software.",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_TAX_SOURCE,
g_param_spec_string ("tax-source",
"Tax Source",
"This specifies where exported name comes from.",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_TAX_COPY_NUMBER,
g_param_spec_int64 ("tax-copy-number",
"Tax Copy Number",
"This specifies the copy number of the tax "
"form/schedule.",
(gint64)1,
G_MAXINT64,
(gint64)1,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_HIDDEN,
g_param_spec_boolean ("hidden",
"Hidden",
"Whether the account should be hidden in the "
"account tree.",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_PLACEHOLDER,
g_param_spec_boolean ("placeholder",
"Placeholder",
"Whether the account is a placeholder account which does not "
"allow transactions to be created, edited or deleted.",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_FILTER,
g_param_spec_string ("filter",
"Account Filter",
"The account filter is a value saved to allow "
"filters to be recalled.",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_SORT_ORDER,
g_param_spec_string ("sort-order",
"Account Sort Order",
"The account sort order is a value saved to allow "
"the sort order to be recalled.",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_SORT_REVERSED,
g_param_spec_boolean ("sort-reversed",
"Account Sort Reversed",
"Parameter to store whether the sort order is reversed or not.",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_LOT_NEXT_ID,
g_param_spec_int64 ("lot-next-id",
"Lot Next ID",
"Tracks the next id to use in gnc_lot_make_default.",
(gint64)1,
G_MAXINT64,
(gint64)1,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_ONLINE_ACCOUNT,
g_param_spec_string ("online-id",
"Online Account ID",
"The online account which corresponds to this "
"account for OFX import",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property(
gobject_class,
PROP_OFX_INCOME_ACCOUNT,
g_param_spec_boxed("ofx-income-account",
"Associated income account",
"Used by the OFX importer.",
GNC_TYPE_GUID,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_AB_ACCOUNT_ID,
g_param_spec_string ("ab-account-id",
"AQBanking Account ID",
"The AqBanking account which corresponds to this "
"account for AQBanking import",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_AB_BANK_CODE,
g_param_spec_string ("ab-bank-code",
"AQBanking Bank Code",
"The online account which corresponds to this "
"account for AQBanking import",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_AB_ACCOUNT_UID,
g_param_spec_int64 ("ab-account-uid",
"AQBanking Account UID",
"Tracks the next id to use in gnc_lot_make_default.",
(gint64)1,
G_MAXINT64,
(gint64)1,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_AB_TRANS_RETRIEVAL,
g_param_spec_boxed("ab-trans-retrieval",
"AQBanking Last Transaction Retrieval",
"The time of the last transaction retrieval for this "
"account.",
GNC_TYPE_TIMESPEC,
G_PARAM_READWRITE));
}
static void
xaccInitAccount (Account * acc, QofBook *book)
{
ENTER ("book=%p\n", book);
qof_instance_init_data (&acc->inst, GNC_ID_ACCOUNT, book);
LEAVE ("account=%p\n", acc);
}
/********************************************************************\
\********************************************************************/
QofBook *
gnc_account_get_book(const Account *account)
{
return qof_instance_get_book(QOF_INSTANCE(account));
}
/********************************************************************\
\********************************************************************/
static Account *
gnc_coll_get_root_account (QofCollection *col)
{
if (!col) return NULL;
return qof_collection_get_data (col);
}
static void
gnc_coll_set_root_account (QofCollection *col, Account *root)
{
AccountPrivate *rpriv;
Account *old_root;
if (!col) return;
old_root = gnc_coll_get_root_account (col);
if (old_root == root) return;
/* If the new root is already linked into the tree somewhere, then
* remove it from its current position before adding it at the
* top. */
rpriv = GET_PRIVATE(root);
if (rpriv->parent)
{
xaccAccountBeginEdit(root);
gnc_account_remove_child(rpriv->parent, root);
xaccAccountCommitEdit(root);
}
qof_collection_set_data (col, root);
if (old_root)
{
xaccAccountBeginEdit (old_root);
xaccAccountDestroy (old_root);
}
}
Account *
gnc_book_get_root_account (QofBook *book)
{
QofCollection *col;
Account *root;
if (!book) return NULL;
col = qof_book_get_collection (book, GNC_ID_ROOT_ACCOUNT);
root = gnc_coll_get_root_account (col);
if (root == NULL)
root = gnc_account_create_root(book);
return root;
}
void
gnc_book_set_root_account (QofBook *book, Account *root)
{
QofCollection *col;
if (!book) return;
if (root && gnc_account_get_book(root) != book)
{
PERR ("cannot mix and match books freely!");
return;
}
col = qof_book_get_collection (book, GNC_ID_ROOT_ACCOUNT);
gnc_coll_set_root_account (col, root);
}
/********************************************************************\
\********************************************************************/
Account *
xaccMallocAccount (QofBook *book)
{
Account *acc;
g_return_val_if_fail (book, NULL);
acc = g_object_new (GNC_TYPE_ACCOUNT, NULL);
xaccInitAccount (acc, book);
qof_event_gen (&acc->inst, QOF_EVENT_CREATE, NULL);
return acc;
}
Account *
gnc_account_create_root (QofBook *book)
{
Account *root;
AccountPrivate *rpriv;
root = xaccMallocAccount(book);
rpriv = GET_PRIVATE(root);
xaccAccountBeginEdit(root);
rpriv->type = ACCT_TYPE_ROOT;
CACHE_REPLACE(rpriv->accountName, "Root Account");
mark_account (root);
xaccAccountCommitEdit(root);
gnc_book_set_root_account(book, root);
return root;
}
Account *
xaccCloneAccount(const Account *from, QofBook *book)
{
Account *ret;
AccountPrivate *from_priv, *priv;
g_return_val_if_fail(GNC_IS_ACCOUNT(from), NULL);
g_return_val_if_fail(QOF_IS_BOOK(book), NULL);
ENTER (" ");
ret = g_object_new (GNC_TYPE_ACCOUNT, NULL);
g_return_val_if_fail (ret, NULL);
from_priv = GET_PRIVATE(from);
priv = GET_PRIVATE(ret);
xaccInitAccount (ret, book);
/* Do not Begin/CommitEdit() here; give the caller
* a chance to fix things up, and let them do it.
* Also let caller issue the generate_event (EVENT_CREATE) */
priv->type = from_priv->type;
priv->accountName = CACHE_INSERT(from_priv->accountName);
priv->accountCode = CACHE_INSERT(from_priv->accountCode);
priv->description = CACHE_INSERT(from_priv->description);
qof_instance_copy_kvp (QOF_INSTANCE (ret), QOF_INSTANCE (from));
/* The new book should contain a commodity that matches
* the one in the old book. Find it, use it. */
priv->commodity = gnc_commodity_obtain_twin(from_priv->commodity, book);
gnc_commodity_increment_usage_count(priv->commodity);
priv->commodity_scu = from_priv->commodity_scu;
priv->non_standard_scu = from_priv->non_standard_scu;
qof_instance_set_dirty(&ret->inst);
LEAVE (" ");
return ret;
}
/********************************************************************\
\********************************************************************/
static void
xaccFreeOneChildAccount (Account *acc, gpointer dummy)
{
/* FIXME: this code is kind of hacky. actually, all this code
* seems to assume that the account edit levels are all 1. */
if (qof_instance_get_editlevel(acc) == 0)
xaccAccountBeginEdit(acc);
xaccAccountDestroy(acc);
}
static void
xaccFreeAccountChildren (Account *acc)
{
AccountPrivate *priv;
GList *children;
/* Copy the list since it will be modified */
priv = GET_PRIVATE(acc);
children = g_list_copy(priv->children);
g_list_foreach(children, (GFunc)xaccFreeOneChildAccount, NULL);
g_list_free(children);
/* The foreach should have removed all the children already. */
if (priv->children)
g_list_free(priv->children);
priv->children = NULL;
}
/* The xaccFreeAccount() routine releases memory associated with the
* account. It should never be called directly from user code;
* instead, the xaccAccountDestroy() routine should be used (because
* xaccAccountDestroy() has the correct commit semantics). */
static void
xaccFreeAccount (Account *acc)
{
AccountPrivate *priv;
GList *lp;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
priv = GET_PRIVATE(acc);
qof_event_gen (&acc->inst, QOF_EVENT_DESTROY, NULL);
if (priv->children)
{
PERR (" instead of calling xaccFreeAccount(), please call \n"
" xaccAccountBeginEdit(); xaccAccountDestroy(); \n");
/* First, recursively free children */
xaccFreeAccountChildren(acc);
}
/* remove lots -- although these should be gone by now. */
if (priv->lots)
{
PERR (" instead of calling xaccFreeAccount(), please call \n"
" xaccAccountBeginEdit(); xaccAccountDestroy(); \n");
for (lp = priv->lots; lp; lp = lp->next)
{
GNCLot *lot = lp->data;
gnc_lot_destroy (lot);
}
g_list_free (priv->lots);
priv->lots = NULL;
}
/* Next, clean up the splits */
/* NB there shouldn't be any splits by now ... they should
* have been all been freed by CommitEdit(). We can remove this
* check once we know the warning isn't occurring any more. */
if (priv->splits)
{
GList *slist;
PERR (" instead of calling xaccFreeAccount(), please call \n"
" xaccAccountBeginEdit(); xaccAccountDestroy(); \n");
qof_instance_reset_editlevel(acc);
slist = g_list_copy(priv->splits);
for (lp = slist; lp; lp = lp->next)
{
Split *s = (Split *) lp->data;
g_assert(xaccSplitGetAccount(s) == acc);
xaccSplitDestroy (s);
}
g_list_free(slist);
/* Nothing here (or in xaccAccountCommitEdit) NULLs priv->splits, so this asserts every time.
g_assert(priv->splits == NULL);
*/
}
CACHE_REPLACE(priv->accountName, NULL);
CACHE_REPLACE(priv->accountCode, NULL);
CACHE_REPLACE(priv->description, NULL);
/* zero out values, just in case stray
* pointers are pointing here. */
priv->parent = NULL;
priv->children = NULL;
priv->balance = gnc_numeric_zero();
priv->cleared_balance = gnc_numeric_zero();
priv->reconciled_balance = gnc_numeric_zero();
priv->type = ACCT_TYPE_NONE;
gnc_commodity_decrement_usage_count(priv->commodity);
priv->commodity = NULL;
priv->balance_dirty = FALSE;
priv->sort_dirty = FALSE;
/* qof_instance_release (&acc->inst); */
g_object_unref(acc);
}
/********************************************************************\
* transactional routines
\********************************************************************/
void
xaccAccountBeginEdit (Account *acc)
{
g_return_if_fail(acc);
qof_begin_edit(&acc->inst);
}
static void on_done(QofInstance *inst)
{
/* old event style */
qof_event_gen (inst, QOF_EVENT_MODIFY, NULL);
}
static void on_err (QofInstance *inst, QofBackendError errcode)
{
PERR("commit error: %d", errcode);
gnc_engine_signal_commit_error( errcode );
}
static void acc_free (QofInstance *inst)
{
AccountPrivate *priv;
Account *acc = (Account *) inst;
priv = GET_PRIVATE(acc);
if (priv->parent)
gnc_account_remove_child(priv->parent, acc);
xaccFreeAccount(acc);
}
static void
destroy_pending_splits_for_account(QofInstance *ent, gpointer acc)
{
Transaction *trans = (Transaction *) ent;
Split *split;
if (xaccTransIsOpen(trans))
while ((split = xaccTransFindSplitByAccount(trans, acc)))
xaccSplitDestroy(split);
}
void
xaccAccountCommitEdit (Account *acc)
{
AccountPrivate *priv;
QofBook *book;
g_return_if_fail(acc);
if (!qof_commit_edit(&acc->inst)) return;
/* If marked for deletion, get rid of subaccounts first,
* and then the splits ... */
priv = GET_PRIVATE(acc);
if (qof_instance_get_destroying(acc))
{
GList *lp, *slist;
QofCollection *col;
qof_instance_increase_editlevel(acc);
/* First, recursively free children */
xaccFreeAccountChildren(acc);
PINFO ("freeing splits for account %p (%s)",
acc, priv->accountName ? priv->accountName : "(null)");
book = qof_instance_get_book(acc);
/* If book is shutting down, just clear the split list. The splits
themselves will be destroyed by the transaction code */
if (!qof_book_shutting_down(book))
{
slist = g_list_copy(priv->splits);
for (lp = slist; lp; lp = lp->next)
{
Split *s = lp->data;
xaccSplitDestroy (s);
}
g_list_free(slist);
}
else
{
g_list_free(priv->splits);
priv->splits = NULL;
}
/* It turns out there's a case where this assertion does not hold:
When the user tries to delete an Imbalance account, while also
deleting all the splits in it. The splits will just get
recreated and put right back into the same account!
g_assert(priv->splits == NULL || qof_book_shutting_down(acc->inst.book));
*/
if (!qof_book_shutting_down(book))
{
col = qof_book_get_collection(book, GNC_ID_TRANS);
qof_collection_foreach(col, destroy_pending_splits_for_account, acc);
/* the lots should be empty by now */
for (lp = priv->lots; lp; lp = lp->next)
{
GNCLot *lot = lp->data;
gnc_lot_destroy (lot);
}
}
g_list_free(priv->lots);
priv->lots = NULL;
qof_instance_set_dirty(&acc->inst);
qof_instance_decrease_editlevel(acc);
}
else
{
xaccAccountBringUpToDate(acc);
}
qof_commit_edit_part2(&acc->inst, on_err, on_done, acc_free);
}
void
xaccAccountDestroy (Account *acc)
{
g_return_if_fail(GNC_IS_ACCOUNT(acc));
qof_instance_set_destroying(acc, TRUE);
xaccAccountCommitEdit (acc);
}
/********************************************************************\
\********************************************************************/
static gint
compare_account_by_name (gconstpointer a, gconstpointer b)
{
AccountPrivate *priv_a, *priv_b;
if (a && !b) return 1;
if (b && !a) return -1;
if (!a && !b) return 0;
priv_a = GET_PRIVATE((Account*)a);
priv_b = GET_PRIVATE((Account*)b);
if ((priv_a->accountCode && strlen (priv_a->accountCode)) ||
(priv_b->accountCode && strlen (priv_b->accountCode)))
return g_strcmp0 (priv_a->accountCode, priv_b->accountCode);
return g_strcmp0 (priv_a->accountName, priv_b->accountName);
}
static gboolean
xaccAcctChildrenEqual(const GList *na,
const GList *nb,
gboolean check_guids)
{
if ((!na && nb) || (na && !nb))
{
PINFO ("only one has accounts");
return(FALSE);
}
if (g_list_length ((GList*)na) != g_list_length ((GList*)nb))
{
PINFO ("Accounts have different numbers of children");
return (FALSE);
}
while (na)
{
Account *aa = na->data;
Account *ab;
GList *node = g_list_find_custom ((GList*)nb, aa,
(GCompareFunc)compare_account_by_name);
if (!node)
{
PINFO ("Unable to find matching child account.");
return FALSE;
}
ab = node->data;
if (!xaccAccountEqual(aa, ab, check_guids))
{
char sa[GUID_ENCODING_LENGTH + 1];
char sb[GUID_ENCODING_LENGTH + 1];
guid_to_string_buff (xaccAccountGetGUID (aa), sa);
guid_to_string_buff (xaccAccountGetGUID (ab), sb);
PWARN ("accounts %s and %s differ", sa, sb);
return(FALSE);
}
na = na->next;
}
return(TRUE);
}
gboolean
xaccAccountEqual(const Account *aa, const Account *ab, gboolean check_guids)
{
AccountPrivate *priv_aa, *priv_ab;
if (!aa && !ab) return TRUE;
g_return_val_if_fail(GNC_IS_ACCOUNT(aa), FALSE);
g_return_val_if_fail(GNC_IS_ACCOUNT(ab), FALSE);
priv_aa = GET_PRIVATE(aa);
priv_ab = GET_PRIVATE(ab);
if (priv_aa->type != priv_ab->type)
{
PWARN ("types differ: %d vs %d", priv_aa->type, priv_ab->type);
return FALSE;
}
if (g_strcmp0(priv_aa->accountName, priv_ab->accountName) != 0)
{
PWARN ("names differ: %s vs %s", priv_aa->accountName, priv_ab->accountName);
return FALSE;
}
if (g_strcmp0(priv_aa->accountCode, priv_ab->accountCode) != 0)
{
PWARN ("codes differ: %s vs %s", priv_aa->accountCode, priv_ab->accountCode);
return FALSE;
}
if (g_strcmp0(priv_aa->description, priv_ab->description) != 0)
{
PWARN ("descriptions differ: %s vs %s", priv_aa->description, priv_ab->description);
return FALSE;
}
if (!gnc_commodity_equal(priv_aa->commodity, priv_ab->commodity))
{
PWARN ("commodities differ");
return FALSE;
}
if (check_guids)
{
if (qof_instance_guid_compare(aa, ab) != 0)
{
PWARN ("GUIDs differ");
return FALSE;
}
}
if (qof_instance_compare_kvp (QOF_INSTANCE (aa), QOF_INSTANCE (ab)) != 0)
{
char *frame_a;
char *frame_b;
frame_a = qof_instance_kvp_as_string (QOF_INSTANCE (aa));
frame_b = qof_instance_kvp_as_string (QOF_INSTANCE (ab));
PWARN ("kvp frames differ:\n%s\n\nvs\n\n%s", frame_a, frame_b);
g_free (frame_a);
g_free (frame_b);
return FALSE;
}
if (!gnc_numeric_equal(priv_aa->starting_balance, priv_ab->starting_balance))
{
char *str_a;
char *str_b;
str_a = gnc_numeric_to_string(priv_aa->starting_balance);
str_b = gnc_numeric_to_string(priv_ab->starting_balance);
PWARN ("starting balances differ: %s vs %s", str_a, str_b);
g_free (str_a);
g_free (str_b);
return FALSE;
}
if (!gnc_numeric_equal(priv_aa->starting_cleared_balance,
priv_ab->starting_cleared_balance))
{
char *str_a;
char *str_b;
str_a = gnc_numeric_to_string(priv_aa->starting_cleared_balance);
str_b = gnc_numeric_to_string(priv_ab->starting_cleared_balance);
PWARN ("starting cleared balances differ: %s vs %s", str_a, str_b);
g_free (str_a);
g_free (str_b);
return FALSE;
}
if (!gnc_numeric_equal(priv_aa->starting_reconciled_balance,
priv_ab->starting_reconciled_balance))
{
char *str_a;
char *str_b;
str_a = gnc_numeric_to_string(priv_aa->starting_reconciled_balance);
str_b = gnc_numeric_to_string(priv_ab->starting_reconciled_balance);
PWARN ("starting reconciled balances differ: %s vs %s", str_a, str_b);
g_free (str_a);
g_free (str_b);
return FALSE;
}
if (!gnc_numeric_equal(priv_aa->balance, priv_ab->balance))
{
char *str_a;
char *str_b;
str_a = gnc_numeric_to_string(priv_aa->balance);
str_b = gnc_numeric_to_string(priv_ab->balance);
PWARN ("balances differ: %s vs %s", str_a, str_b);
g_free (str_a);
g_free (str_b);
return FALSE;
}
if (!gnc_numeric_equal(priv_aa->cleared_balance, priv_ab->cleared_balance))
{
char *str_a;
char *str_b;
str_a = gnc_numeric_to_string(priv_aa->cleared_balance);
str_b = gnc_numeric_to_string(priv_ab->cleared_balance);
PWARN ("cleared balances differ: %s vs %s", str_a, str_b);
g_free (str_a);
g_free (str_b);
return FALSE;
}
if (!gnc_numeric_equal(priv_aa->reconciled_balance, priv_ab->reconciled_balance))
{
char *str_a;
char *str_b;
str_a = gnc_numeric_to_string(priv_aa->reconciled_balance);
str_b = gnc_numeric_to_string(priv_ab->reconciled_balance);
PWARN ("reconciled balances differ: %s vs %s", str_a, str_b);
g_free (str_a);
g_free (str_b);
return FALSE;
}
/* no parent; always compare downwards. */
{
GList *la = priv_aa->splits;
GList *lb = priv_ab->splits;
if ((la && !lb) || (!la && lb))
{
PWARN ("only one has splits");
return FALSE;
}
if (la && lb)
{
/* presume that the splits are in the same order */
while (la && lb)
{
Split *sa = (Split *) la->data;
Split *sb = (Split *) lb->data;
if (!xaccSplitEqual(sa, sb, check_guids, TRUE, FALSE))
{
PWARN ("splits differ");
return(FALSE);
}
la = la->next;
lb = lb->next;
}
if ((la != NULL) || (lb != NULL))
{
PWARN ("number of splits differs");
return(FALSE);
}
}
}
if (!xaccAcctChildrenEqual(priv_aa->children, priv_ab->children, check_guids))
{
PWARN ("children differ");
return FALSE;
}
return(TRUE);
}
/********************************************************************\
\********************************************************************/
void
gnc_account_set_sort_dirty (Account *acc)
{
AccountPrivate *priv;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
if (qof_instance_get_destroying(acc))
return;
priv = GET_PRIVATE(acc);
priv->sort_dirty = TRUE;
}
void
gnc_account_set_balance_dirty (Account *acc)
{
AccountPrivate *priv;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
if (qof_instance_get_destroying(acc))
return;
priv = GET_PRIVATE(acc);
priv->balance_dirty = TRUE;
}
/********************************************************************\
\********************************************************************/
gboolean
gnc_account_insert_split (Account *acc, Split *s)
{
AccountPrivate *priv;
GList *node;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
g_return_val_if_fail(GNC_IS_SPLIT(s), FALSE);
priv = GET_PRIVATE(acc);
node = g_list_find(priv->splits, s);
if (node)
return FALSE;
if (qof_instance_get_editlevel(acc) == 0)
{
priv->splits = g_list_insert_sorted(priv->splits, s,
(GCompareFunc)xaccSplitOrder);
}
else
{
priv->splits = g_list_prepend(priv->splits, s);
priv->sort_dirty = TRUE;
}
//FIXME: find better event
qof_event_gen (&acc->inst, QOF_EVENT_MODIFY, NULL);
/* Also send an event based on the account */
qof_event_gen(&acc->inst, GNC_EVENT_ITEM_ADDED, s);
priv->balance_dirty = TRUE;
// DRH: Should the below be added? It is present in the delete path.
// xaccAccountRecomputeBalance(acc);
return TRUE;
}
gboolean
gnc_account_remove_split (Account *acc, Split *s)
{
AccountPrivate *priv;
GList *node;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
g_return_val_if_fail(GNC_IS_SPLIT(s), FALSE);
priv = GET_PRIVATE(acc);
node = g_list_find(priv->splits, s);
if (NULL == node)
return FALSE;
priv->splits = g_list_delete_link(priv->splits, node);
//FIXME: find better event type
qof_event_gen(&acc->inst, QOF_EVENT_MODIFY, NULL);
// And send the account-based event, too
qof_event_gen(&acc->inst, GNC_EVENT_ITEM_REMOVED, s);
priv->balance_dirty = TRUE;
xaccAccountRecomputeBalance(acc);
return TRUE;
}
void
xaccAccountSortSplits (Account *acc, gboolean force)
{
AccountPrivate *priv;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
priv = GET_PRIVATE(acc);
if (!priv->sort_dirty || (!force && qof_instance_get_editlevel(acc) > 0))
return;
priv->splits = g_list_sort(priv->splits, (GCompareFunc)xaccSplitOrder);
priv->sort_dirty = FALSE;
priv->balance_dirty = TRUE;
}
static void
xaccAccountBringUpToDate(Account *acc)
{
if (!acc) return;
/* if a re-sort happens here, then everything will update, so the
cost basis and balance calls are no-ops */
xaccAccountSortSplits(acc, FALSE);
xaccAccountRecomputeBalance(acc);
}
/********************************************************************\
\********************************************************************/
void
xaccAccountSetGUID (Account *acc, const GncGUID *guid)
{
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_return_if_fail(guid);
/* XXX this looks fishy and weird to me ... */
PINFO("acct=%p", acc);
xaccAccountBeginEdit (acc);
qof_instance_set_guid (&acc->inst, guid);
qof_instance_set_dirty(&acc->inst);
xaccAccountCommitEdit (acc);
}
/********************************************************************\
\********************************************************************/
Account *
xaccAccountLookup (const GncGUID *guid, QofBook *book)
{
QofCollection *col;
if (!guid || !book) return NULL;
col = qof_book_get_collection (book, GNC_ID_ACCOUNT);
return (Account *) qof_collection_lookup_entity (col, guid);
}
/********************************************************************\
\********************************************************************/
void
xaccAccountSetMark (Account *acc, short m)
{
AccountPrivate *priv;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
priv = GET_PRIVATE(acc);
priv->mark = m;
}
void
xaccClearMark (Account *acc, short val)
{
Account *root;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
root = gnc_account_get_root(acc);
xaccClearMarkDown(root ? root : acc, val);
}
void
xaccClearMarkDown (Account *acc, short val)
{
AccountPrivate *priv;
GList *node;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
priv = GET_PRIVATE(acc);
priv->mark = val;
for (node = priv->children; node; node = node->next)
{
xaccClearMarkDown(node->data, val);
}
}
/********************************************************************\
\********************************************************************/
GNCPolicy *
gnc_account_get_policy (Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
return GET_PRIVATE(acc)->policy;
}
void
gnc_account_set_policy (Account *acc, GNCPolicy *policy)
{
AccountPrivate *priv;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
priv = GET_PRIVATE(acc);
priv->policy = policy ? policy : xaccGetFIFOPolicy();
}
/********************************************************************\
\********************************************************************/
void
xaccAccountRemoveLot (Account *acc, GNCLot *lot)
{
AccountPrivate *priv;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_return_if_fail(GNC_IS_LOT(lot));
priv = GET_PRIVATE(acc);
g_return_if_fail(priv->lots);
ENTER ("(acc=%p, lot=%p)", acc, lot);
priv->lots = g_list_remove(priv->lots, lot);
qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_REMOVE, NULL);
qof_event_gen (&acc->inst, QOF_EVENT_MODIFY, NULL);
LEAVE ("(acc=%p, lot=%p)", acc, lot);
}
void
xaccAccountInsertLot (Account *acc, GNCLot *lot)
{
AccountPrivate *priv, *opriv;
Account * old_acc = NULL;
Account* lot_account;
/* errors */
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_return_if_fail(GNC_IS_LOT(lot));
/* optimizations */
lot_account = gnc_lot_get_account(lot);
if (lot_account == acc)
return;
ENTER ("(acc=%p, lot=%p)", acc, lot);
/* pull it out of the old account */
if (lot_account)
{
old_acc = lot_account;
opriv = GET_PRIVATE(old_acc);
opriv->lots = g_list_remove(opriv->lots, lot);
}
priv = GET_PRIVATE(acc);
priv->lots = g_list_prepend(priv->lots, lot);
gnc_lot_set_account(lot, acc);
/* Don't move the splits to the new account. The caller will do this
* if appropriate, and doing it here will not work if we are being
* called from gnc_book_close_period since xaccAccountInsertSplit
* will try to balance capital gains and things aren't ready for that. */
qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_ADD, NULL);
qof_event_gen (&acc->inst, QOF_EVENT_MODIFY, NULL);
LEAVE ("(acc=%p, lot=%p)", acc, lot);
}
/********************************************************************\
\********************************************************************/
static void
xaccPreSplitMove (Split *split, gpointer dummy)
{
xaccTransBeginEdit (xaccSplitGetParent (split));
}
static void
xaccPostSplitMove (Split *split, Account *accto)
{
Transaction *trans;
xaccSplitSetAccount(split, accto);
xaccSplitSetAmount(split, split->amount);
trans = xaccSplitGetParent (split);
xaccTransCommitEdit (trans);
}
void
xaccAccountMoveAllSplits (Account *accfrom, Account *accto)
{
AccountPrivate *from_priv;
/* errors */
g_return_if_fail(GNC_IS_ACCOUNT(accfrom));
g_return_if_fail(GNC_IS_ACCOUNT(accto));
/* optimizations */
from_priv = GET_PRIVATE(accfrom);
if (!from_priv->splits || accfrom == accto)
return;
/* check for book mix-up */
g_return_if_fail (qof_instance_books_equal(accfrom, accto));
ENTER ("(accfrom=%p, accto=%p)", accfrom, accto);
xaccAccountBeginEdit(accfrom);
xaccAccountBeginEdit(accto);
/* Begin editing both accounts and all transactions in accfrom. */
g_list_foreach(from_priv->splits, (GFunc)xaccPreSplitMove, NULL);
/* Concatenate accfrom's lists of splits and lots to accto's lists. */
//to_priv->splits = g_list_concat(to_priv->splits, from_priv->splits);
//to_priv->lots = g_list_concat(to_priv->lots, from_priv->lots);
/* Set appropriate flags. */
//from_priv->balance_dirty = TRUE;
//from_priv->sort_dirty = FALSE;
//to_priv->balance_dirty = TRUE;
//to_priv->sort_dirty = TRUE;
/*
* Change each split's account back pointer to accto.
* Convert each split's amount to accto's commodity.
* Commit to editing each transaction.
*/
g_list_foreach(from_priv->splits, (GFunc)xaccPostSplitMove, (gpointer)accto);
/* Finally empty accfrom. */
g_assert(from_priv->splits == NULL);
g_assert(from_priv->lots == NULL);
xaccAccountCommitEdit(accfrom);
xaccAccountCommitEdit(accto);
LEAVE ("(accfrom=%p, accto=%p)", accfrom, accto);
}
/********************************************************************\
* xaccAccountRecomputeBalance *
* recomputes the partial balances and the current balance for *
* this account. *
* *
* The way the computation is done depends on whether the partial *
* balances are for a monetary account (bank, cash, etc.) or a *
* certificate account (stock portfolio, mutual fund). For bank *
* accounts, the invariant amount is the dollar amount. For share *
* accounts, the invariant amount is the number of shares. For *
* share accounts, the share price fluctuates, and the current *
* value of such an account is the number of shares times the *
* current share price. *
* *
* Part of the complexity of this computation stems from the fact *
* xacc uses a double-entry system, meaning that one transaction *
* appears in two accounts: one account is debited, and the other *
* is credited. When the transaction represents a sale of shares, *
* or a purchase of shares, some care must be taken to compute *
* balances correctly. For a sale of shares, the stock account must*
* be debited in shares, but the bank account must be credited *
* in dollars. Thus, two different mechanisms must be used to *
* compute balances, depending on account type. *
* *
* Args: account -- the account for which to recompute balances *
* Return: void *
\********************************************************************/
void
xaccAccountRecomputeBalance (Account * acc)
{
AccountPrivate *priv;
gnc_numeric balance;
gnc_numeric cleared_balance;
gnc_numeric reconciled_balance;
GList *lp;
if (NULL == acc) return;
priv = GET_PRIVATE(acc);
if (qof_instance_get_editlevel(acc) > 0) return;
if (!priv->balance_dirty) return;
if (qof_instance_get_destroying(acc)) return;
if (qof_book_shutting_down(qof_instance_get_book(acc))) return;
balance = priv->starting_balance;
cleared_balance = priv->starting_cleared_balance;
reconciled_balance = priv->starting_reconciled_balance;
PINFO ("acct=%s starting baln=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
priv->accountName, balance.num, balance.denom);
for (lp = priv->splits; lp; lp = lp->next)
{
Split *split = (Split *) lp->data;
gnc_numeric amt = xaccSplitGetAmount (split);
balance = gnc_numeric_add_fixed(balance, amt);
if (NREC != split->reconciled)
{
cleared_balance = gnc_numeric_add_fixed(cleared_balance, amt);
}
if (YREC == split->reconciled ||
FREC == split->reconciled)
{
reconciled_balance =
gnc_numeric_add_fixed(reconciled_balance, amt);
}
split->balance = balance;
split->cleared_balance = cleared_balance;
split->reconciled_balance = reconciled_balance;
}
priv->balance = balance;
priv->cleared_balance = cleared_balance;
priv->reconciled_balance = reconciled_balance;
priv->balance_dirty = FALSE;
}
/********************************************************************\
\********************************************************************/
/* The sort order is used to implicitly define an
* order for report generation */
static int typeorder[NUM_ACCOUNT_TYPES] =
{
ACCT_TYPE_BANK, ACCT_TYPE_STOCK, ACCT_TYPE_MUTUAL, ACCT_TYPE_CURRENCY,
ACCT_TYPE_CASH, ACCT_TYPE_ASSET, ACCT_TYPE_RECEIVABLE,
ACCT_TYPE_CREDIT, ACCT_TYPE_LIABILITY, ACCT_TYPE_PAYABLE,
ACCT_TYPE_INCOME, ACCT_TYPE_EXPENSE, ACCT_TYPE_EQUITY, ACCT_TYPE_TRADING
};
static int revorder[NUM_ACCOUNT_TYPES] =
{
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
int
xaccAccountOrder (const Account *aa, const Account *ab)
{
AccountPrivate *priv_aa, *priv_ab;
char *da, *db;
char *endptr = NULL;
int ta, tb, result;
long la, lb;
if ( aa && !ab ) return -1;
if ( !aa && ab ) return +1;
if ( !aa && !ab ) return 0;
priv_aa = GET_PRIVATE(aa);
priv_ab = GET_PRIVATE(ab);
/* sort on accountCode strings */
da = priv_aa->accountCode;
db = priv_ab->accountCode;
/* If accountCodes are both base 36 integers do an integer sort */
la = strtoul (da, &endptr, 36);
if ((*da != '\0') && (*endptr == '\0'))
{
lb = strtoul (db, &endptr, 36);
if ((*db != '\0') && (*endptr == '\0'))
{
if (la < lb) return -1;
if (la > lb) return +1;
}
}
/* Otherwise do a string sort */
result = g_strcmp0 (da, db);
if (result)
return result;
/* if account-type-order array not initialized, initialize it */
/* this will happen at most once during program invocation */
if (-1 == revorder[0])
{
int i;
for (i = 0; i < NUM_ACCOUNT_TYPES; i++)
{
revorder [typeorder[i]] = i;
}
}
/* otherwise, sort on account type */
ta = priv_aa->type;
tb = priv_ab->type;
ta = revorder[ta];
tb = revorder[tb];
if (ta < tb) return -1;
if (ta > tb) return +1;
/* otherwise, sort on accountName strings */
da = priv_aa->accountName;
db = priv_ab->accountName;
result = safe_utf8_collate(da, db);
if (result)
return result;
/* guarantee a stable sort */
return qof_instance_guid_compare(aa, ab);
}
static int
qof_xaccAccountOrder (const Account **aa, const Account **ab)
{
return xaccAccountOrder(*aa, *ab);
}
/********************************************************************\
\********************************************************************/
void
xaccAccountSetType (Account *acc, GNCAccountType tip)
{
AccountPrivate *priv;
/* errors */
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_return_if_fail(tip < NUM_ACCOUNT_TYPES);
/* optimizations */
priv = GET_PRIVATE(acc);
if (priv->type == tip)
return;
xaccAccountBeginEdit(acc);
priv->type = tip;
priv->balance_dirty = TRUE; /* new type may affect balance computation */
mark_account(acc);
xaccAccountCommitEdit(acc);
}
void
xaccAccountSetName (Account *acc, const char *str)
{
AccountPrivate *priv;
/* errors */
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_return_if_fail(str);
/* optimizations */
priv = GET_PRIVATE(acc);
if (g_strcmp0(str, priv->accountName) == 0)
return;
xaccAccountBeginEdit(acc);
CACHE_REPLACE(priv->accountName, str);
mark_account (acc);
xaccAccountCommitEdit(acc);
}
void
xaccAccountSetCode (Account *acc, const char *str)
{
AccountPrivate *priv;
/* errors */
g_return_if_fail(GNC_IS_ACCOUNT(acc));
/* optimizations */
priv = GET_PRIVATE(acc);
if (g_strcmp0(str, priv->accountCode) == 0)
return;
xaccAccountBeginEdit(acc);
CACHE_REPLACE(priv->accountCode, str ? str : "");
mark_account (acc);
xaccAccountCommitEdit(acc);
}
void
xaccAccountSetDescription (Account *acc, const char *str)
{
AccountPrivate *priv;
/* errors */
g_return_if_fail(GNC_IS_ACCOUNT(acc));
/* optimizations */
priv = GET_PRIVATE(acc);
if (g_strcmp0(str, priv->description) == 0)
return;
xaccAccountBeginEdit(acc);
CACHE_REPLACE(priv->description, str ? str : "");
mark_account (acc);
xaccAccountCommitEdit(acc);
}
static void
set_kvp_string_tag (Account *acc, const char *tag, const char *value)
{
g_return_if_fail(GNC_IS_ACCOUNT(acc));
xaccAccountBeginEdit(acc);
if (value)
{
gchar *tmp = g_strstrip(g_strdup(value));
if (strlen (tmp))
{
GValue v = G_VALUE_INIT;
g_value_init (&v, G_TYPE_STRING);
g_value_set_string (&v, tmp);
qof_instance_set_kvp (QOF_INSTANCE (acc), tag , &v);
}
else
qof_instance_set_kvp (QOF_INSTANCE (acc), tag, NULL);
g_free(tmp);
}
else
{
qof_instance_set_kvp (QOF_INSTANCE (acc), tag, NULL);
}
mark_account (acc);
xaccAccountCommitEdit(acc);
}
static const char*
get_kvp_string_tag (const Account *acc, const char *tag)
{
GValue v = G_VALUE_INIT;
if (acc == NULL || tag == NULL) return NULL;
qof_instance_get_kvp (QOF_INSTANCE (acc), tag, &v);
return G_VALUE_HOLDS_STRING (&v) ? g_value_get_string (&v) : NULL;
}
void
xaccAccountSetColor (Account *acc, const char *str)
{
set_kvp_string_tag (acc, "color", str);
}
void
xaccAccountSetFilter (Account *acc, const char *str)
{
set_kvp_string_tag (acc, "filter", str);
}
void
xaccAccountSetSortOrder (Account *acc, const char *str)
{
set_kvp_string_tag (acc, "sort-order", str);
}
void
xaccAccountSetSortReversed (Account *acc, gboolean sortreversed)
{
set_kvp_string_tag (acc, "sort-reversed", sortreversed ? "true" : NULL);
}
static void
qofAccountSetParent (Account *acc, QofInstance *parent)
{
Account *parent_acc;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_return_if_fail(GNC_IS_ACCOUNT(parent));
parent_acc = GNC_ACCOUNT(parent);
xaccAccountBeginEdit(acc);
xaccAccountBeginEdit(parent_acc);
gnc_account_append_child(parent_acc, acc);
mark_account (parent_acc);
mark_account (acc);
xaccAccountCommitEdit(acc);
xaccAccountCommitEdit(parent_acc);
}
void
xaccAccountSetNotes (Account *acc, const char *str)
{
set_kvp_string_tag (acc, "notes", str);
}
void
xaccAccountSetCommodity (Account * acc, gnc_commodity * com)
{
AccountPrivate *priv;
GList *lp;
/* errors */
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_return_if_fail(GNC_IS_COMMODITY(com));
/* optimizations */
priv = GET_PRIVATE(acc);
if (com == priv->commodity)
return;
xaccAccountBeginEdit(acc);
gnc_commodity_decrement_usage_count(priv->commodity);
priv->commodity = com;
gnc_commodity_increment_usage_count(com);
priv->commodity_scu = gnc_commodity_get_fraction(com);
priv->non_standard_scu = FALSE;
/* iterate over splits */
for (lp = priv->splits; lp; lp = lp->next)
{
Split *s = (Split *) lp->data;
Transaction *trans = xaccSplitGetParent (s);
xaccTransBeginEdit (trans);
xaccSplitSetAmount (s, xaccSplitGetAmount(s));
xaccTransCommitEdit (trans);
}
priv->sort_dirty = TRUE; /* Not needed. */
priv->balance_dirty = TRUE;
mark_account (acc);
xaccAccountCommitEdit(acc);
}
/*
* Set the account scu and then check to see if it is the same as the
* commodity scu. This function is called when parsing the data file
* and is designed to catch cases where the two were accidentally set
* to mismatched values in the past.
*/
void
xaccAccountSetCommoditySCU (Account *acc, int scu)
{
AccountPrivate *priv;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
priv = GET_PRIVATE(acc);
xaccAccountBeginEdit(acc);
priv->commodity_scu = scu;
if (scu != gnc_commodity_get_fraction(priv->commodity))
priv->non_standard_scu = TRUE;
mark_account(acc);
xaccAccountCommitEdit(acc);
}
int
xaccAccountGetCommoditySCUi (const Account * acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
return GET_PRIVATE(acc)->commodity_scu;
}
int
xaccAccountGetCommoditySCU (const Account * acc)
{
AccountPrivate *priv;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
priv = GET_PRIVATE(acc);
if (priv->non_standard_scu || !priv->commodity)
return priv->commodity_scu;
return gnc_commodity_get_fraction(priv->commodity);
}
void
xaccAccountSetNonStdSCU (Account *acc, gboolean flag)
{
AccountPrivate *priv;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
priv = GET_PRIVATE(acc);
if (priv->non_standard_scu == flag)
return;
xaccAccountBeginEdit(acc);
priv->non_standard_scu = flag;
mark_account (acc);
xaccAccountCommitEdit(acc);
}
gboolean
xaccAccountGetNonStdSCU (const Account * acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
return GET_PRIVATE(acc)->non_standard_scu;
}
/********************************************************************\
\********************************************************************/
/* below follow the old, deprecated currency/security routines. */
void
DxaccAccountSetCurrency (Account * acc, gnc_commodity * currency)
{
QofBook *book;
GValue v = G_VALUE_INIT;
const char *s = gnc_commodity_get_unique_name (currency);
gnc_commodity *commodity;
gnc_commodity_table *table;
if ((!acc) || (!currency)) return;
g_value_init (&v, G_TYPE_STRING);
g_value_set_string (&v, s);
qof_instance_set_kvp (QOF_INSTANCE (acc), "old-currency", &v);
mark_account (acc);
xaccAccountCommitEdit(acc);
table = gnc_commodity_table_get_table (qof_instance_get_book(acc));
commodity = gnc_commodity_table_lookup_unique (table, s);
if (!commodity)
{
book = qof_instance_get_book(acc);
gnc_commodity_table_insert (gnc_commodity_table_get_table (book),
currency);
}
}
/********************************************************************\
\********************************************************************/
void
gnc_account_append_child (Account *new_parent, Account *child)
{
AccountPrivate *ppriv, *cpriv;
Account *old_parent;
QofCollection *col;
/* errors */
g_assert(GNC_IS_ACCOUNT(new_parent));
g_assert(GNC_IS_ACCOUNT(child));
/* optimizations */
ppriv = GET_PRIVATE(new_parent);
cpriv = GET_PRIVATE(child);
old_parent = cpriv->parent;
if (old_parent == new_parent)
return;
// xaccAccountBeginEdit(new_parent);
xaccAccountBeginEdit(child);
if (old_parent)
{
gnc_account_remove_child(old_parent, child);
if (!qof_instance_books_equal(old_parent, new_parent))
{
/* hack alert -- this implementation is not exactly correct.
* If the entity tables are not identical, then the 'from' book
* may have a different backend than the 'to' book. This means
* that we should get the 'from' backend to destroy this account,
* and the 'to' backend to save it. Right now, this is broken.
*
* A 'correct' implementation similar to this is in Period.c
* except its for transactions ...
*
* Note also, we need to reparent the children to the new book as well.
*/
PWARN ("reparenting accounts across books is not correctly supported\n");
qof_event_gen (&child->inst, QOF_EVENT_DESTROY, NULL);
col = qof_book_get_collection (qof_instance_get_book(new_parent),
GNC_ID_ACCOUNT);
qof_collection_insert_entity (col, &child->inst);
qof_event_gen (&child->inst, QOF_EVENT_CREATE, NULL);
}
}
cpriv->parent = new_parent;
ppriv->children = g_list_append(ppriv->children, child);
qof_instance_set_dirty(&new_parent->inst);
qof_instance_set_dirty(&child->inst);
/* Send events data. Warning: The call to commit_edit is also going
* to send a MODIFY event. If the gtktreemodelfilter code gets the
* MODIFY before it gets the ADD, it gets very confused and thinks
* that two nodes have been added. */
qof_event_gen (&child->inst, QOF_EVENT_ADD, NULL);
// qof_event_gen (&new_parent->inst, QOF_EVENT_MODIFY, NULL);
xaccAccountCommitEdit (child);
// xaccAccountCommitEdit(new_parent);
}
void
gnc_account_remove_child (Account *parent, Account *child)
{
AccountPrivate *ppriv, *cpriv;
GncEventData ed;
if (!child) return;
/* Note this routine might be called on accounts which
* are not yet parented. */
if (!parent) return;
ppriv = GET_PRIVATE(parent);
cpriv = GET_PRIVATE(child);
if (cpriv->parent != parent)
{
PERR ("account not a child of parent");
return;
}
/* Gather event data */
ed.node = parent;
ed.idx = g_list_index(ppriv->children, child);
ppriv->children = g_list_remove(ppriv->children, child);
/* Now send the event. */
qof_event_gen(&child->inst, QOF_EVENT_REMOVE, &ed);
/* clear the account's parent pointer after REMOVE event generation. */
cpriv->parent = NULL;
qof_event_gen (&parent->inst, QOF_EVENT_MODIFY, NULL);
}
Account *
gnc_account_get_parent (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
return GET_PRIVATE(acc)->parent;
}
Account *
gnc_account_get_root (Account *acc)
{
AccountPrivate *priv;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
priv = GET_PRIVATE(acc);
while (priv->parent)
{
acc = priv->parent;
priv = GET_PRIVATE(acc);
}
return acc;
}
gboolean
gnc_account_is_root (const Account *account)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(account), FALSE);
return (GET_PRIVATE(account)->parent == NULL);
}
GList *
gnc_account_get_children (const Account *account)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(account), NULL);
return g_list_copy(GET_PRIVATE(account)->children);
}
GList *
gnc_account_get_children_sorted (const Account *account)
{
AccountPrivate *priv;
/* errors */
g_return_val_if_fail(GNC_IS_ACCOUNT(account), NULL);
/* optimizations */
priv = GET_PRIVATE(account);
if (!priv->children)
return NULL;
return g_list_sort(g_list_copy(priv->children), (GCompareFunc)xaccAccountOrder);
}
gint
gnc_account_n_children (const Account *account)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(account), 0);
return g_list_length(GET_PRIVATE(account)->children);
}
gint
gnc_account_child_index (const Account *parent, const Account *child)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(parent), -1);
g_return_val_if_fail(GNC_IS_ACCOUNT(child), -1);
return g_list_index(GET_PRIVATE(parent)->children, child);
}
Account *
gnc_account_nth_child (const Account *parent, gint num)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(parent), NULL);
return g_list_nth_data(GET_PRIVATE(parent)->children, num);
}
gint
gnc_account_n_descendants (const Account *account)
{
AccountPrivate *priv;
GList *node;
gint count = 0;
g_return_val_if_fail(GNC_IS_ACCOUNT(account), 0);
priv = GET_PRIVATE(account);
for (node = priv->children; node; node = g_list_next(node))
{
count += gnc_account_n_descendants(node->data) + 1;
}
return count;
}
gint
gnc_account_get_current_depth (const Account *account)
{
AccountPrivate *priv;
int depth = 0;
g_return_val_if_fail(GNC_IS_ACCOUNT(account), 0);
priv = GET_PRIVATE(account);
while (priv->parent && (priv->type != ACCT_TYPE_ROOT))
{
account = priv->parent;
priv = GET_PRIVATE(account);
depth++;
}
return depth;
}
gint
gnc_account_get_tree_depth (const Account *account)
{
AccountPrivate *priv;
GList *node;
gint depth = 0, child_depth;
g_return_val_if_fail(GNC_IS_ACCOUNT(account), 0);
priv = GET_PRIVATE(account);
if (!priv->children)
return 1;
for (node = priv->children; node; node = g_list_next(node))
{
child_depth = gnc_account_get_tree_depth(node->data);
depth = MAX(depth, child_depth);
}
return depth + 1;
}
GList *
gnc_account_get_descendants (const Account *account)
{
AccountPrivate *priv;
GList *child, *descendants;
g_return_val_if_fail(GNC_IS_ACCOUNT(account), NULL);
priv = GET_PRIVATE(account);
if (!priv->children)
return NULL;
descendants = NULL;
for (child = priv->children; child; child = g_list_next(child))
{
descendants = g_list_append(descendants, child->data);
descendants = g_list_concat(descendants,
gnc_account_get_descendants(child->data));
}
return descendants;
}
GList *
gnc_account_get_descendants_sorted (const Account *account)
{
AccountPrivate *priv;
GList *child, *children, *descendants;
/* errors */
g_return_val_if_fail(GNC_IS_ACCOUNT(account), NULL);
/* optimizations */
priv = GET_PRIVATE(account);
if (!priv->children)
return NULL;
descendants = NULL;
children = g_list_sort(g_list_copy(priv->children), (GCompareFunc)xaccAccountOrder);
for (child = children; child; child = g_list_next(child))
{
descendants = g_list_append(descendants, child->data);
descendants = g_list_concat(descendants,
gnc_account_get_descendants_sorted(child->data));
}
g_list_free(children);
return descendants;
}
Account *
gnc_account_lookup_by_name (const Account *parent, const char * name)
{
AccountPrivate *cpriv, *ppriv;
Account *child, *result;
GList *node;
g_return_val_if_fail(GNC_IS_ACCOUNT(parent), NULL);
g_return_val_if_fail(name, NULL);
/* first, look for accounts hanging off the current node */
ppriv = GET_PRIVATE(parent);
for (node = ppriv->children; node; node = node->next)
{
child = node->data;
cpriv = GET_PRIVATE(child);
if (g_strcmp0(cpriv->accountName, name) == 0)
return child;
}
/* if we are still here, then we haven't found the account yet.
* Recursively search each of the child accounts next */
for (node = ppriv->children; node; node = node->next)
{
child = node->data;
result = gnc_account_lookup_by_name (child, name);
if (result)
return result;
}
return NULL;
}
Account *
gnc_account_lookup_by_code (const Account *parent, const char * code)
{
AccountPrivate *cpriv, *ppriv;
Account *child, *result;
GList *node;
g_return_val_if_fail(GNC_IS_ACCOUNT(parent), NULL);
g_return_val_if_fail(code, NULL);
/* first, look for accounts hanging off the current node */
ppriv = GET_PRIVATE(parent);
for (node = ppriv->children; node; node = node->next)
{
child = node->data;
cpriv = GET_PRIVATE(child);
if (g_strcmp0(cpriv->accountCode, code) == 0)
return child;
}
/* if we are still here, then we haven't found the account yet.
* Recursively search each of the child accounts next */
for (node = ppriv->children; node; node = node->next)
{
child = node->data;
result = gnc_account_lookup_by_code (child, code);
if (result)
return result;
}
return NULL;
}
/********************************************************************\
* Fetch an account, given its full name *
\********************************************************************/
static Account *
gnc_account_lookup_by_full_name_helper (const Account *parent,
gchar **names)
{
const AccountPrivate *priv, *ppriv;
Account *found;
GList *node;
g_return_val_if_fail(GNC_IS_ACCOUNT(parent), NULL);
g_return_val_if_fail(names, NULL);
/* Look for the first name in the children. */
ppriv = GET_PRIVATE(parent);
for (node = ppriv->children; node; node = node->next)
{
Account *account = node->data;
priv = GET_PRIVATE(account);
if (g_strcmp0(priv->accountName, names[0]) == 0)
{
/* We found an account. If the next entry is NULL, there is
* nothing left in the name, so just return the account. */
if (names[1] == NULL)
return account;
/* No children? We're done. */
if (!priv->children)
return NULL;
/* There's stuff left to search for. Search recursively. */
found = gnc_account_lookup_by_full_name_helper(account, &names[1]);
if (found != NULL)
{
return found;
}
}
}
return NULL;
}
Account *
gnc_account_lookup_by_full_name (const Account *any_acc,
const gchar *name)
{
const AccountPrivate *rpriv;
const Account *root;
Account *found;
gchar **names;
g_return_val_if_fail(GNC_IS_ACCOUNT(any_acc), NULL);
g_return_val_if_fail(name, NULL);
root = any_acc;
rpriv = GET_PRIVATE(root);
while (rpriv->parent)
{
root = rpriv->parent;
rpriv = GET_PRIVATE(root);
}
names = g_strsplit(name, gnc_get_account_separator_string(), -1);
found = gnc_account_lookup_by_full_name_helper(root, names);
g_strfreev(names);
return found;
}
void
gnc_account_foreach_child (const Account *acc,
AccountCb thunk,
gpointer user_data)
{
const AccountPrivate *priv;
GList *node;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_return_if_fail(thunk);
priv = GET_PRIVATE(acc);
for (node = priv->children; node; node = node->next)
{
thunk (node->data, user_data);
}
}
void
gnc_account_foreach_descendant (const Account *acc,
AccountCb thunk,
gpointer user_data)
{
const AccountPrivate *priv;
GList *node;
Account *child;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_return_if_fail(thunk);
priv = GET_PRIVATE(acc);
for (node = priv->children; node; node = node->next)
{
child = node->data;
thunk(child, user_data);
gnc_account_foreach_descendant(child, thunk, user_data);
}
}
gpointer
gnc_account_foreach_descendant_until (const Account *acc,
AccountCb2 thunk,
gpointer user_data)
{
const AccountPrivate *priv;
GList *node;
Account *child;
gpointer result;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
g_return_val_if_fail(thunk, NULL);
priv = GET_PRIVATE(acc);
for (node = priv->children; node; node = node->next)
{
child = node->data;
result = thunk(child, user_data);
if (result)
return(result);
result = gnc_account_foreach_descendant_until(child, thunk, user_data);
if (result)
return(result);
}
return NULL;
}
GNCAccountType
xaccAccountGetType (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), ACCT_TYPE_NONE);
return GET_PRIVATE(acc)->type;
}
static const char*
qofAccountGetTypeString (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
return xaccAccountTypeEnumAsString(GET_PRIVATE(acc)->type);
}
static void
qofAccountSetType (Account *acc, const char *type_string)
{
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_return_if_fail(type_string);
xaccAccountSetType(acc, xaccAccountStringToEnum(type_string));
}
const char *
xaccAccountGetName (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
return GET_PRIVATE(acc)->accountName;
}
gchar *
gnc_account_get_full_name(const Account *account)
{
AccountPrivate *priv;
const Account *a;
char *fullname;
gchar **names;
int level;
/* So much for hardening the API. Too many callers to this function don't
* bother to check if they have a non-NULL pointer before calling. */
if (NULL == account)
return g_strdup("");
/* errors */
g_return_val_if_fail(GNC_IS_ACCOUNT(account), g_strdup(""));
/* optimizations */
priv = GET_PRIVATE(account);
if (!priv->parent)
return g_strdup("");
/* Figure out how much space is needed by counting the nodes up to
* the root. */
level = 0;
for (a = account; a; a = priv->parent)
{
priv = GET_PRIVATE(a);
level++;
}
/* Get all the pointers in the right order. The root node "entry"
* becomes the terminating NULL pointer for the array of strings. */
names = g_malloc(level * sizeof(gchar *));
names[--level] = NULL;
for (a = account; level > 0; a = priv->parent)
{
priv = GET_PRIVATE(a);
names[--level] = priv->accountName;
}
/* Build the full name */
fullname = g_strjoinv(account_separator, names);
g_free(names);
return fullname;
}
const char *
xaccAccountGetCode (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
return GET_PRIVATE(acc)->accountCode;
}
const char *
xaccAccountGetDescription (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
return GET_PRIVATE(acc)->description;
}
const char *
xaccAccountGetColor (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
return get_kvp_string_tag (acc, "color");
}
const char *
xaccAccountGetFilter (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
return get_kvp_string_tag (acc, "filter");
}
const char *
xaccAccountGetSortOrder (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
return get_kvp_string_tag (acc, "sort-order");
}
gboolean
xaccAccountGetSortReversed (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
return g_strcmp0 (get_kvp_string_tag (acc, "sort-reversed"), "true") == 0;
}
const char *
xaccAccountGetNotes (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
return get_kvp_string_tag (acc, "notes");
}
gnc_commodity *
DxaccAccountGetCurrency (const Account *acc)
{
GValue v = G_VALUE_INIT;
const char *s = NULL;
gnc_commodity_table *table;
if (!acc) return NULL;
qof_instance_get_kvp (QOF_INSTANCE(acc), "old-currency", &v);
if (G_VALUE_HOLDS_STRING (&v))
s = g_value_get_string (&v);
if (!s) return NULL;
table = gnc_commodity_table_get_table (qof_instance_get_book(acc));
return gnc_commodity_table_lookup_unique (table, s);
}
gnc_commodity *
xaccAccountGetCommodity (const Account *acc)
{
if (!GNC_IS_ACCOUNT(acc))
return NULL;
return GET_PRIVATE(acc)->commodity;
}
gnc_commodity * gnc_account_get_currency_or_parent(const Account* account)
{
gnc_commodity * commodity;
g_assert(account);
commodity = xaccAccountGetCommodity (account);
if (gnc_commodity_is_currency(commodity))
return commodity;
else
{
const Account *parent_account = account;
/* Account commodity is not a currency, walk up the tree until
* we find a parent account that is a currency account and use
* it's currency.
*/
do
{
parent_account = gnc_account_get_parent (parent_account);
if (parent_account)
{
commodity = xaccAccountGetCommodity (parent_account);
if (gnc_commodity_is_currency(commodity))
{
return commodity;
//break;
}
}
}
while (parent_account);
}
return NULL; // no suitable commodity found.
}
/********************************************************************\
\********************************************************************/
void
gnc_account_set_start_balance (Account *acc, const gnc_numeric start_baln)
{
AccountPrivate *priv;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
priv = GET_PRIVATE(acc);
priv->starting_balance = start_baln;
priv->balance_dirty = TRUE;
}
void
gnc_account_set_start_cleared_balance (Account *acc,
const gnc_numeric start_baln)
{
AccountPrivate *priv;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
priv = GET_PRIVATE(acc);
priv->starting_cleared_balance = start_baln;
priv->balance_dirty = TRUE;
}
void
gnc_account_set_start_reconciled_balance (Account *acc,
const gnc_numeric start_baln)
{
AccountPrivate *priv;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
priv = GET_PRIVATE(acc);
priv->starting_reconciled_balance = start_baln;
priv->balance_dirty = TRUE;
}
gnc_numeric
xaccAccountGetBalance (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
return GET_PRIVATE(acc)->balance;
}
gnc_numeric
xaccAccountGetClearedBalance (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
return GET_PRIVATE(acc)->cleared_balance;
}
gnc_numeric
xaccAccountGetReconciledBalance (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
return GET_PRIVATE(acc)->reconciled_balance;
}
gnc_numeric
xaccAccountGetProjectedMinimumBalance (const Account *acc)
{
AccountPrivate *priv;
GList *node;
time64 today;
gnc_numeric lowest = gnc_numeric_zero ();
int seen_a_transaction = 0;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
priv = GET_PRIVATE(acc);
today = gnc_time64_get_today_end();
for (node = g_list_last(priv->splits); node; node = node->prev)
{
Split *split = node->data;
if (!seen_a_transaction)
{
lowest = xaccSplitGetBalance (split);
seen_a_transaction = 1;
}
else if (gnc_numeric_compare(xaccSplitGetBalance (split), lowest) < 0)
{
lowest = xaccSplitGetBalance (split);
}
if (xaccTransGetDate (xaccSplitGetParent (split)) <= today)
return lowest;
}
return lowest;
}
/********************************************************************\
\********************************************************************/
gnc_numeric
xaccAccountGetBalanceAsOfDate (Account *acc, time64 date)
{
/* Ideally this could use xaccAccountForEachSplit, but
* it doesn't exist yet and I'm uncertain of exactly how
* it would work at this time, since it differs from
* xaccAccountForEachTransaction by using gpointer return
* values rather than gints.
*/
AccountPrivate *priv;
GList *lp;
Timespec ts, trans_ts;
gboolean found = FALSE;
gnc_numeric balance;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
xaccAccountSortSplits (acc, TRUE); /* just in case, normally a noop */
xaccAccountRecomputeBalance (acc); /* just in case, normally a noop */
priv = GET_PRIVATE(acc);
balance = priv->balance;
/* Since transaction post times are stored as a Timespec,
* convert date into a Timespec as well rather than converting
* each transaction's Timespec into a time64.
*
* FIXME: CAS: I think this comment is a bogus justification for
* using xaccTransGetDatePostedTS. There's no benefit to using
* Timespec when the input argument is time64, and it's hard to
* imagine that casting long long to long and comparing two longs is
* worse than comparing two long longs every time. IMO,
* xaccAccountGetPresentBalance gets this right, and its algorithm
* should be used here.
*/
ts.tv_sec = date;
ts.tv_nsec = 0;
lp = priv->splits;
while ( lp && !found )
{
xaccTransGetDatePostedTS( xaccSplitGetParent( (Split *)lp->data ),
&trans_ts );
if ( timespec_cmp( &trans_ts, &ts ) >= 0 )
found = TRUE;
else
lp = lp->next;
}
if ( lp )
{
if ( lp->prev )
{
/* Since lp is now pointing to a split which was past the reconcile
* date, get the running balance of the previous split.
*/
balance = xaccSplitGetBalance( (Split *)lp->prev->data );
}
else
{
/* AsOf date must be before any entries, return zero. */
balance = gnc_numeric_zero();
}
}
/* Otherwise there were no splits posted after the given date,
* so the latest account balance should be good enough.
*/
return( balance );
}
/*
* Originally gsr_account_present_balance in gnc-split-reg.c
*
* How does this routine compare to xaccAccountGetBalanceAsOfDate just
* above? These two routines should eventually be collapsed into one.
* Perhaps the startup logic from that one, and the logic from this
* one that walks from the tail of the split list.
*/
gnc_numeric
xaccAccountGetPresentBalance (const Account *acc)
{
AccountPrivate *priv;
GList *node;
time64 today;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
priv = GET_PRIVATE(acc);
today = gnc_time64_get_today_end();
for (node = g_list_last(priv->splits); node; node = node->prev)
{
Split *split = node->data;
if (xaccTransGetDate (xaccSplitGetParent (split)) <= today)
return xaccSplitGetBalance (split);
}
return gnc_numeric_zero ();
}
/********************************************************************\
\********************************************************************/
/* XXX TODO: These 'GetBal' routines should be moved to some
* utility area outside of the core account engine area.
*/
/*
* Convert a balance from one currency to another.
*/
gnc_numeric
xaccAccountConvertBalanceToCurrency(const Account *acc, /* for book */
gnc_numeric balance,
const gnc_commodity *balance_currency,
const gnc_commodity *new_currency)
{
QofBook *book;
GNCPriceDB *pdb;
if (gnc_numeric_zero_p (balance) ||
gnc_commodity_equiv (balance_currency, new_currency))
return balance;
book = gnc_account_get_book (acc);
pdb = gnc_pricedb_get_db (book);
balance = gnc_pricedb_convert_balance_latest_price(
pdb, balance, balance_currency, new_currency);
return balance;
}
/*
* Convert a balance from one currency to another with price of
* a given date.
*/
gnc_numeric
xaccAccountConvertBalanceToCurrencyAsOfDate(const Account *acc, /* for book */
gnc_numeric balance,
gnc_commodity *balance_currency,
gnc_commodity *new_currency,
time64 date)
{
QofBook *book;
GNCPriceDB *pdb;
Timespec ts;
if (gnc_numeric_zero_p (balance) ||
gnc_commodity_equiv (balance_currency, new_currency))
return balance;
book = gnc_account_get_book (acc);
pdb = gnc_pricedb_get_db (book);
ts.tv_sec = date;
ts.tv_nsec = 0;
balance = gnc_pricedb_convert_balance_nearest_price(
pdb, balance, balance_currency, new_currency, ts);
return balance;
}
/*
* Given an account and a GetBalanceFn pointer, extract the requested
* balance from the account and then convert it to the desired
* currency.
*/
static gnc_numeric
xaccAccountGetXxxBalanceInCurrency (const Account *acc,
xaccGetBalanceFn fn,
const gnc_commodity *report_currency)
{
AccountPrivate *priv;
gnc_numeric balance;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
g_return_val_if_fail(fn, gnc_numeric_zero());
g_return_val_if_fail(GNC_IS_COMMODITY(report_currency), gnc_numeric_zero());
priv = GET_PRIVATE(acc);
balance = fn(acc);
balance = xaccAccountConvertBalanceToCurrency(acc, balance,
priv->commodity,
report_currency);
return balance;
}
static gnc_numeric
xaccAccountGetXxxBalanceAsOfDateInCurrency(Account *acc, time64 date,
xaccGetBalanceAsOfDateFn fn,
const gnc_commodity *report_commodity)
{
AccountPrivate *priv;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
g_return_val_if_fail(fn, gnc_numeric_zero());
g_return_val_if_fail(GNC_IS_COMMODITY(report_commodity), gnc_numeric_zero());
priv = GET_PRIVATE(acc);
return xaccAccountConvertBalanceToCurrency(
acc, fn(acc, date), priv->commodity, report_commodity);
}
/*
* Data structure used to pass various arguments into the following fn.
*/
typedef struct
{
const gnc_commodity *currency;
gnc_numeric balance;
xaccGetBalanceFn fn;
xaccGetBalanceAsOfDateFn asOfDateFn;
time64 date;
} CurrencyBalance;
/*
* A helper function for iterating over all the accounts in a list or
* tree. This function is called once per account, and sums up the
* values of all these accounts.
*/
static void
xaccAccountBalanceHelper (Account *acc, gpointer data)
{
CurrencyBalance *cb = data;
gnc_numeric balance;
if (!cb->fn || !cb->currency)
return;
balance = xaccAccountGetXxxBalanceInCurrency (acc, cb->fn, cb->currency);
cb->balance = gnc_numeric_add (cb->balance, balance,
gnc_commodity_get_fraction (cb->currency),
GNC_HOW_RND_ROUND_HALF_UP);
}
static void
xaccAccountBalanceAsOfDateHelper (Account *acc, gpointer data)
{
CurrencyBalance *cb = data;
gnc_numeric balance;
g_return_if_fail (cb->asOfDateFn && cb->currency);
balance = xaccAccountGetXxxBalanceAsOfDateInCurrency (
acc, cb->date, cb->asOfDateFn, cb->currency);
cb->balance = gnc_numeric_add (cb->balance, balance,
gnc_commodity_get_fraction (cb->currency),
GNC_HOW_RND_ROUND_HALF_UP);
}
/*
* Common function that iterates recursively over all accounts below
* the specified account. It uses xaccAccountBalanceHelper to sum up
* the balances of all its children, and uses the specified function
* 'fn' for extracting the balance. This function may extract the
* current value, the reconciled value, etc.
*
* If 'report_commodity' is NULL, just use the account's commodity.
* If 'include_children' is FALSE, this function doesn't recurse at all.
*/
static gnc_numeric
xaccAccountGetXxxBalanceInCurrencyRecursive (const Account *acc,
xaccGetBalanceFn fn,
const gnc_commodity *report_commodity,
gboolean include_children)
{
gnc_numeric balance;
if (!acc) return gnc_numeric_zero ();
if (!report_commodity)
report_commodity = xaccAccountGetCommodity (acc);
if (!report_commodity)
return gnc_numeric_zero();
balance = xaccAccountGetXxxBalanceInCurrency (acc, fn, report_commodity);
/* If needed, sum up the children converting to the *requested*
commodity. */
if (include_children)
{
#ifdef _MSC_VER
/* MSVC compiler: Somehow, the struct initialization containing a
gnc_numeric doesn't work. As an exception, we hand-initialize
that member afterwards. */
CurrencyBalance cb = { report_commodity, { 0 }, fn, NULL, 0 };
cb.balance = balance;
#else
CurrencyBalance cb = { report_commodity, balance, fn, NULL, 0 };
#endif
gnc_account_foreach_descendant (acc, xaccAccountBalanceHelper, &cb);
balance = cb.balance;
}
return balance;
}
static gnc_numeric
xaccAccountGetXxxBalanceAsOfDateInCurrencyRecursive (
Account *acc, time64 date, xaccGetBalanceAsOfDateFn fn,
gnc_commodity *report_commodity, gboolean include_children)
{
gnc_numeric balance;
g_return_val_if_fail(acc, gnc_numeric_zero());
if (!report_commodity)
report_commodity = xaccAccountGetCommodity (acc);
if (!report_commodity)
return gnc_numeric_zero();
balance = xaccAccountGetXxxBalanceAsOfDateInCurrency(
acc, date, fn, report_commodity);
/* If needed, sum up the children converting to the *requested*
commodity. */
if (include_children)
{
#ifdef _MSC_VER
/* MSVC compiler: Somehow, the struct initialization containing a
gnc_numeric doesn't work. As an exception, we hand-initialize
that member afterwards. */
CurrencyBalance cb = { report_commodity, 0, NULL, fn, date };
cb.balance = balance;
#else
CurrencyBalance cb = { report_commodity, balance, NULL, fn, date };
#endif
gnc_account_foreach_descendant (acc, xaccAccountBalanceAsOfDateHelper, &cb);
balance = cb.balance;
}
return balance;
}
gnc_numeric
xaccAccountGetBalanceInCurrency (const Account *acc,
const gnc_commodity *report_commodity,
gboolean include_children)
{
gnc_numeric rc;
rc = xaccAccountGetXxxBalanceInCurrencyRecursive (
acc, xaccAccountGetBalance, report_commodity, include_children);
PINFO(" baln=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, rc.num, rc.denom);
return rc;
}
gnc_numeric
xaccAccountGetClearedBalanceInCurrency (const Account *acc,
const gnc_commodity *report_commodity,
gboolean include_children)
{
return xaccAccountGetXxxBalanceInCurrencyRecursive (
acc, xaccAccountGetClearedBalance, report_commodity,
include_children);
}
gnc_numeric
xaccAccountGetReconciledBalanceInCurrency (const Account *acc,
const gnc_commodity *report_commodity,
gboolean include_children)
{
return xaccAccountGetXxxBalanceInCurrencyRecursive (
acc, xaccAccountGetReconciledBalance, report_commodity,
include_children);
}
gnc_numeric
xaccAccountGetPresentBalanceInCurrency (const Account *acc,
const gnc_commodity *report_commodity,
gboolean include_children)
{
return xaccAccountGetXxxBalanceInCurrencyRecursive (
acc, xaccAccountGetPresentBalance, report_commodity,
include_children);
}
gnc_numeric
xaccAccountGetProjectedMinimumBalanceInCurrency (
const Account *acc,
const gnc_commodity *report_commodity,
gboolean include_children)
{
return xaccAccountGetXxxBalanceInCurrencyRecursive (
acc, xaccAccountGetProjectedMinimumBalance, report_commodity,
include_children);
}
gnc_numeric
xaccAccountGetBalanceAsOfDateInCurrency(
Account *acc, time64 date, gnc_commodity *report_commodity,
gboolean include_children)
{
return xaccAccountGetXxxBalanceAsOfDateInCurrencyRecursive (
acc, date, xaccAccountGetBalanceAsOfDate, report_commodity,
include_children);
}
gnc_numeric
xaccAccountGetBalanceChangeForPeriod (Account *acc, time64 t1, time64 t2,
gboolean recurse)
{
gnc_numeric b1, b2;
b1 = xaccAccountGetBalanceAsOfDateInCurrency(acc, t1, NULL, recurse);
b2 = xaccAccountGetBalanceAsOfDateInCurrency(acc, t2, NULL, recurse);
return gnc_numeric_sub(b2, b1, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
}
/********************************************************************\
\********************************************************************/
/* THIS API NEEDS TO CHANGE.
*
* This code exposes the internal structure of the account object to
* external callers by returning the actual list used by the object.
* It should instead return a copy of the split list that the caller
* is required to free. That change would provide the freedom of
* allowing the internal organization to change data structures if
* necessary for whatever reason, while leaving the external API
* unchanged. */
/* XXX: violates the const'ness by forcing a sort before returning
* the splitlist */
SplitList *
xaccAccountGetSplitList (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
xaccAccountSortSplits((Account*)acc, FALSE); // normally a noop
return GET_PRIVATE(acc)->splits;
}
gint64
xaccAccountCountSplits (const Account *acc, gboolean include_children)
{
gint64 nr, i;
nr = 0;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
nr = g_list_length(xaccAccountGetSplitList(acc));
if (include_children && (gnc_account_n_children(acc) != 0))
{
for (i=0; i < gnc_account_n_children(acc); i++)
{
nr += xaccAccountCountSplits(gnc_account_nth_child(acc, i), TRUE);
}
}
return nr;
}
LotList *
xaccAccountGetLotList (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
return g_list_copy(GET_PRIVATE(acc)->lots);
}
LotList *
xaccAccountFindOpenLots (const Account *acc,
gboolean (*match_func)(GNCLot *lot,
gpointer user_data),
gpointer user_data, GCompareFunc sort_func)
{
AccountPrivate *priv;
GList *lot_list;
GList *retval = NULL;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
priv = GET_PRIVATE(acc);
for (lot_list = priv->lots; lot_list; lot_list = lot_list->next)
{
GNCLot *lot = lot_list->data;
/* If this lot is closed, then ignore it */
if (gnc_lot_is_closed (lot))
continue;
if (match_func && !(match_func)(lot, user_data))
continue;
/* Ok, this is a valid lot. Add it to our list of lots */
if (sort_func)
retval = g_list_insert_sorted (retval, lot, sort_func);
else
retval = g_list_prepend (retval, lot);
}
return retval;
}
gpointer
xaccAccountForEachLot(const Account *acc,
gpointer (*proc)(GNCLot *lot, void *data), void *data)
{
AccountPrivate *priv;
LotList *node;
gpointer result = NULL;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
g_return_val_if_fail(proc, NULL);
priv = GET_PRIVATE(acc);
for (node = priv->lots; node; node = node->next)
if ((result = proc((GNCLot *)node->data, data)))
break;
return result;
}
static void
set_boolean_key (Account *acc, const char* key, gboolean option)
{
GValue v = G_VALUE_INIT;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_value_init (&v, G_TYPE_BOOLEAN);
g_value_set_boolean (&v, option);
xaccAccountBeginEdit (acc);
qof_instance_set_kvp (QOF_INSTANCE (acc),key , &v);
mark_account (acc);
xaccAccountCommitEdit (acc);
}
static gboolean
boolean_from_key (const Account *acc, const char *key)
{
GValue v = G_VALUE_INIT;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
qof_instance_get_kvp (QOF_INSTANCE(acc), key, &v);
if (G_VALUE_HOLDS_BOOLEAN (&v))
return g_value_get_boolean (&v);
if (G_VALUE_HOLDS_STRING (&v))
return strcmp (g_value_get_string (&v), "true") == 0;
return FALSE;
}
/********************************************************************\
\********************************************************************/
/* These functions use interchange gint64 and gboolean. Is that right? */
gboolean
xaccAccountGetTaxRelated (const Account *acc)
{
return boolean_from_key(acc, "tax-related");
}
void
xaccAccountSetTaxRelated (Account *acc, gboolean tax_related)
{
set_boolean_key(acc, "tax-related", tax_related);
}
const char *
xaccAccountGetTaxUSCode (const Account *acc)
{
GValue v = G_VALUE_INIT;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
qof_instance_get_kvp (QOF_INSTANCE(acc), "/tax-US/code", &v);
return G_VALUE_HOLDS_STRING (&v) ? g_value_get_string (&v) : NULL;
}
void
xaccAccountSetTaxUSCode (Account *acc, const char *code)
{
GValue v = G_VALUE_INIT;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_value_init (&v, G_TYPE_STRING);
g_value_set_string (&v, code);
xaccAccountBeginEdit (acc);
qof_instance_set_kvp (QOF_INSTANCE (acc), "/tax-US/code", &v);
mark_account (acc);
xaccAccountCommitEdit (acc);
}
const char *
xaccAccountGetTaxUSPayerNameSource (const Account *acc)
{
GValue v = G_VALUE_INIT;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
qof_instance_get_kvp (QOF_INSTANCE(acc),
"/tax-US/payer-name-source", &v);
return G_VALUE_HOLDS_STRING (&v) ? g_value_get_string (&v) : NULL;
}
void
xaccAccountSetTaxUSPayerNameSource (Account *acc, const char *source)
{
GValue v = G_VALUE_INIT;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_value_init (&v, G_TYPE_STRING);
g_value_set_string (&v, source);
xaccAccountBeginEdit (acc);
qof_instance_set_kvp (QOF_INSTANCE (acc), "/tax-US/payer-name-source", &v);
mark_account (acc);
xaccAccountCommitEdit (acc);
}
gint64
xaccAccountGetTaxUSCopyNumber (const Account *acc)
{
gint64 copy_number = 0;
GValue v = G_VALUE_INIT;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
qof_instance_get_kvp (QOF_INSTANCE(acc), "/tax-US/copy-number", &v);
if (G_VALUE_HOLDS_INT64 (&v))
copy_number = g_value_get_int64 (&v);
return (copy_number == 0) ? 1 : copy_number;
}
void
xaccAccountSetTaxUSCopyNumber (Account *acc, gint64 copy_number)
{
g_return_if_fail(GNC_IS_ACCOUNT(acc));
xaccAccountBeginEdit (acc);
if (copy_number != 0)
{
GValue v = G_VALUE_INIT;
g_value_init (&v, G_TYPE_INT64);
g_value_set_int64 (&v, copy_number);
qof_instance_set_kvp (QOF_INSTANCE (acc), "/tax-US/copy-number", &v);
}
else
{
qof_instance_set_kvp (QOF_INSTANCE (acc), "/tax-US/copy-number", NULL);
}
mark_account (acc);
xaccAccountCommitEdit (acc);
}
/********************************************************************\
\********************************************************************/
gboolean
xaccAccountGetPlaceholder (const Account *acc)
{
return boolean_from_key(acc, "placeholder");
}
void
xaccAccountSetPlaceholder (Account *acc, gboolean val)
{
set_boolean_key(acc, "placeholder", val);
}
GNCPlaceholderType
xaccAccountGetDescendantPlaceholder (const Account *acc)
{
GList *descendants, *node;
GNCPlaceholderType ret = PLACEHOLDER_NONE;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), PLACEHOLDER_NONE);
if (xaccAccountGetPlaceholder(acc)) return PLACEHOLDER_THIS;
descendants = gnc_account_get_descendants(acc);
for (node = descendants; node; node = node->next)
if (xaccAccountGetPlaceholder((Account *) node->data))
{
ret = PLACEHOLDER_CHILD;
break;
}
g_list_free(descendants);
return ret;
}
/********************************************************************\
\********************************************************************/
gboolean
xaccAccountGetHidden (const Account *acc)
{
return boolean_from_key (acc, "hidden");
}
void
xaccAccountSetHidden (Account *acc, gboolean val)
{
set_boolean_key (acc, "hidden", val);
}
gboolean
xaccAccountIsHidden (const Account *acc)
{
AccountPrivate *priv;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
if (xaccAccountGetHidden(acc))
return TRUE;
priv = GET_PRIVATE(acc);
while ((acc = priv->parent) != NULL)
{
priv = GET_PRIVATE(acc);
if (xaccAccountGetHidden(acc))
return TRUE;
}
return FALSE;
}
/********************************************************************\
\********************************************************************/
gboolean
xaccAccountHasAncestor (const Account *acc, const Account * ancestor)
{
const Account *parent;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
g_return_val_if_fail(GNC_IS_ACCOUNT(ancestor), FALSE);
parent = acc;
while (parent && parent != ancestor)
parent = GET_PRIVATE(parent)->parent;
return (parent == ancestor);
}
/********************************************************************\
\********************************************************************/
/* You must edit the functions in this block in tandem. KEEP THEM IN
SYNC! */
#define GNC_RETURN_ENUM_AS_STRING(x) case (ACCT_TYPE_ ## x): return #x;
const char *
xaccAccountTypeEnumAsString(GNCAccountType type)
{
switch (type)
{
GNC_RETURN_ENUM_AS_STRING(NONE);
GNC_RETURN_ENUM_AS_STRING(BANK);
GNC_RETURN_ENUM_AS_STRING(CASH);
GNC_RETURN_ENUM_AS_STRING(CREDIT);
GNC_RETURN_ENUM_AS_STRING(ASSET);
GNC_RETURN_ENUM_AS_STRING(LIABILITY);
GNC_RETURN_ENUM_AS_STRING(STOCK);
GNC_RETURN_ENUM_AS_STRING(MUTUAL);
GNC_RETURN_ENUM_AS_STRING(CURRENCY);
GNC_RETURN_ENUM_AS_STRING(INCOME);
GNC_RETURN_ENUM_AS_STRING(EXPENSE);
GNC_RETURN_ENUM_AS_STRING(EQUITY);
GNC_RETURN_ENUM_AS_STRING(RECEIVABLE);
GNC_RETURN_ENUM_AS_STRING(PAYABLE);
GNC_RETURN_ENUM_AS_STRING(ROOT);
GNC_RETURN_ENUM_AS_STRING(TRADING);
GNC_RETURN_ENUM_AS_STRING(CHECKING);
GNC_RETURN_ENUM_AS_STRING(SAVINGS);
GNC_RETURN_ENUM_AS_STRING(MONEYMRKT);
GNC_RETURN_ENUM_AS_STRING(CREDITLINE);
default:
PERR ("asked to translate unknown account type %d.\n", type);
break;
}
return(NULL);
}
#undef GNC_RETURN_ENUM_AS_STRING
#define GNC_RETURN_ON_MATCH(x) \
if(g_strcmp0(#x, (str)) == 0) { *type = ACCT_TYPE_ ## x; return(TRUE); }
gboolean
xaccAccountStringToType(const char* str, GNCAccountType *type)
{
GNC_RETURN_ON_MATCH(NONE);
GNC_RETURN_ON_MATCH(BANK);
GNC_RETURN_ON_MATCH(CASH);
GNC_RETURN_ON_MATCH(CREDIT);
GNC_RETURN_ON_MATCH(ASSET);
GNC_RETURN_ON_MATCH(LIABILITY);
GNC_RETURN_ON_MATCH(STOCK);
GNC_RETURN_ON_MATCH(MUTUAL);
GNC_RETURN_ON_MATCH(CURRENCY);
GNC_RETURN_ON_MATCH(INCOME);
GNC_RETURN_ON_MATCH(EXPENSE);
GNC_RETURN_ON_MATCH(EQUITY);
GNC_RETURN_ON_MATCH(RECEIVABLE);
GNC_RETURN_ON_MATCH(PAYABLE);
GNC_RETURN_ON_MATCH(ROOT);
GNC_RETURN_ON_MATCH(TRADING);
GNC_RETURN_ON_MATCH(CHECKING);
GNC_RETURN_ON_MATCH(SAVINGS);
GNC_RETURN_ON_MATCH(MONEYMRKT);
GNC_RETURN_ON_MATCH(CREDITLINE);
PERR("asked to translate unknown account type string %s.\n",
str ? str : "(null)");
return(FALSE);
}
#undef GNC_RETURN_ON_MATCH
/* impedance mismatch is a source of loss */
GNCAccountType
xaccAccountStringToEnum(const char* str)
{
GNCAccountType type;
gboolean rc;
rc = xaccAccountStringToType(str, &type);
if (FALSE == rc) return ACCT_TYPE_INVALID;
return type;
}
/********************************************************************\
\********************************************************************/
static char *
account_type_name[NUM_ACCOUNT_TYPES] =
{
N_("Bank"),
N_("Cash"),
N_("Asset"),
N_("Credit Card"),
N_("Liability"),
N_("Stock"),
N_("Mutual Fund"),
N_("Currency"),
N_("Income"),
N_("Expense"),
N_("Equity"),
N_("A/Receivable"),
N_("A/Payable"),
N_("Root"),
N_("Trading")
/*
N_("Checking"),
N_("Savings"),
N_("Money Market"),
N_("Credit Line")
*/
};
const char *
xaccAccountGetTypeStr(GNCAccountType type)
{
if (type < 0 || NUM_ACCOUNT_TYPES <= type ) return "";
return _(account_type_name [type]);
}
/********************************************************************\
\********************************************************************/
guint32
xaccAccountTypesCompatibleWith (GNCAccountType type)
{
switch (type)
{
case ACCT_TYPE_BANK:
case ACCT_TYPE_CASH:
case ACCT_TYPE_ASSET:
case ACCT_TYPE_CREDIT:
case ACCT_TYPE_LIABILITY:
case ACCT_TYPE_INCOME:
case ACCT_TYPE_EXPENSE:
case ACCT_TYPE_EQUITY:
return
(1 << ACCT_TYPE_BANK) |
(1 << ACCT_TYPE_CASH) |
(1 << ACCT_TYPE_ASSET) |
(1 << ACCT_TYPE_CREDIT) |
(1 << ACCT_TYPE_LIABILITY) |
(1 << ACCT_TYPE_INCOME) |
(1 << ACCT_TYPE_EXPENSE) |
(1 << ACCT_TYPE_EQUITY);
case ACCT_TYPE_STOCK:
case ACCT_TYPE_MUTUAL:
case ACCT_TYPE_CURRENCY:
return
(1 << ACCT_TYPE_STOCK) |
(1 << ACCT_TYPE_MUTUAL) |
(1 << ACCT_TYPE_CURRENCY);
case ACCT_TYPE_RECEIVABLE:
return (1 << ACCT_TYPE_RECEIVABLE);
case ACCT_TYPE_PAYABLE:
return (1 << ACCT_TYPE_PAYABLE);
case ACCT_TYPE_TRADING:
return (1 << ACCT_TYPE_TRADING);
default:
PERR("bad account type: %d", type);
return 0;
}
}
guint32
xaccParentAccountTypesCompatibleWith (GNCAccountType type)
{
switch (type)
{
case ACCT_TYPE_BANK:
case ACCT_TYPE_CASH:
case ACCT_TYPE_ASSET:
case ACCT_TYPE_STOCK:
case ACCT_TYPE_MUTUAL:
case ACCT_TYPE_CURRENCY:
case ACCT_TYPE_CREDIT:
case ACCT_TYPE_LIABILITY:
case ACCT_TYPE_RECEIVABLE:
case ACCT_TYPE_PAYABLE:
return
(1 << ACCT_TYPE_BANK) |
(1 << ACCT_TYPE_CASH) |
(1 << ACCT_TYPE_ASSET) |
(1 << ACCT_TYPE_STOCK) |
(1 << ACCT_TYPE_MUTUAL) |
(1 << ACCT_TYPE_CURRENCY) |
(1 << ACCT_TYPE_CREDIT) |
(1 << ACCT_TYPE_LIABILITY) |
(1 << ACCT_TYPE_RECEIVABLE) |
(1 << ACCT_TYPE_PAYABLE) |
(1 << ACCT_TYPE_ROOT);
case ACCT_TYPE_INCOME:
case ACCT_TYPE_EXPENSE:
return
(1 << ACCT_TYPE_INCOME) |
(1 << ACCT_TYPE_EXPENSE) |
(1 << ACCT_TYPE_ROOT);
case ACCT_TYPE_EQUITY:
return
(1 << ACCT_TYPE_EQUITY) |
(1 << ACCT_TYPE_ROOT);
case ACCT_TYPE_TRADING:
return
(1 << ACCT_TYPE_TRADING) |
(1 << ACCT_TYPE_ROOT);
default:
PERR("bad account type: %d", type);
return 0;
}
}
gboolean
xaccAccountTypesCompatible (GNCAccountType parent_type,
GNCAccountType child_type)
{
return ((xaccParentAccountTypesCompatibleWith (parent_type) &
(1 << child_type))
!= 0);
}
guint32
xaccAccountTypesValid(void)
{
guint32 mask = (1 << NUM_ACCOUNT_TYPES) - 1;
mask &= ~((1 << ACCT_TYPE_CURRENCY) | /* DEPRECATED */
(1 << ACCT_TYPE_ROOT)); /* ROOT */
return mask;
}
gboolean xaccAccountIsAssetLiabType(GNCAccountType t)
{
switch (t)
{
case ACCT_TYPE_RECEIVABLE:
case ACCT_TYPE_PAYABLE:
return FALSE;
default:
return (xaccAccountTypesCompatible(ACCT_TYPE_ASSET, t)
|| xaccAccountTypesCompatible(ACCT_TYPE_LIABILITY, t));
}
}
gboolean xaccAccountIsAPARType(GNCAccountType t)
{
switch (t)
{
case ACCT_TYPE_RECEIVABLE:
case ACCT_TYPE_PAYABLE:
return TRUE;
default:
return FALSE;
}
}
gboolean xaccAccountIsEquityType(GNCAccountType t)
{
switch (t)
{
case ACCT_TYPE_EQUITY:
return TRUE;
default:
return FALSE;
}
}
gboolean
xaccAccountIsPriced(const Account *acc)
{
AccountPrivate *priv;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
priv = GET_PRIVATE(acc);
return (priv->type == ACCT_TYPE_STOCK || priv->type == ACCT_TYPE_MUTUAL ||
priv->type == ACCT_TYPE_CURRENCY);
}
/********************************************************************\
\********************************************************************/
gboolean
xaccAccountGetReconcileLastDate (const Account *acc, time64 *last_date)
{
gint64 date = 0;
GValue v = G_VALUE_INIT;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
qof_instance_get_kvp (QOF_INSTANCE(acc), "reconcile-info/last-date", &v);
if (G_VALUE_HOLDS_INT64 (&v))
date = g_value_get_int64 (&v);
if (date)
{
if (last_date)
*last_date = date;
return TRUE;
}
return FALSE;
}
/********************************************************************\
\********************************************************************/
void
xaccAccountSetReconcileLastDate (Account *acc, time64 last_date)
{
GValue v = G_VALUE_INIT;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_value_init (&v, G_TYPE_INT64);
g_value_set_int64 (&v, last_date);
xaccAccountBeginEdit (acc);
qof_instance_set_kvp (QOF_INSTANCE (acc), "/reconcile-info/last-date", &v);
mark_account (acc);
xaccAccountCommitEdit (acc);
}
/********************************************************************\
\********************************************************************/
gboolean
xaccAccountGetReconcileLastInterval (const Account *acc,
int *months, int *days)
{
GValue v1 = G_VALUE_INIT, v2 = G_VALUE_INIT;
int64_t m = 0, d = 0;
if (!acc) return FALSE;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
qof_instance_get_kvp (QOF_INSTANCE(acc),
"reconcile-info/last-interval/months", &v1);
qof_instance_get_kvp (QOF_INSTANCE(acc),
"reconcile-info/last-interval/days", &v2);
if (G_VALUE_HOLDS_INT64 (&v1))
m = g_value_get_int64 (&v1);
if (G_VALUE_HOLDS_INT64 (&v2))
d = g_value_get_int64 (&v2);
if (m && d)
{
if (months)
*months = m;
if (days)
*days = d;
return TRUE;
}
return FALSE;
}
/********************************************************************\
\********************************************************************/
void
xaccAccountSetReconcileLastInterval (Account *acc, int months, int days)
{
GValue v1 = G_VALUE_INIT, v2 = G_VALUE_INIT;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_value_init (&v1, G_TYPE_INT64);
g_value_set_int64 (&v1, months);
g_value_init (&v2, G_TYPE_INT64);
g_value_set_int64 (&v2, days);
xaccAccountBeginEdit (acc);
qof_instance_set_kvp (QOF_INSTANCE (acc),
"reconcile-info/last-interval/months", &v1);
qof_instance_set_kvp (QOF_INSTANCE (acc),
"reconcile-info/last-interval/days", &v2);
mark_account (acc);
xaccAccountCommitEdit (acc);
}
/********************************************************************\
\********************************************************************/
gboolean
xaccAccountGetReconcilePostponeDate (const Account *acc, time64 *postpone_date)
{
gint64 date = 0;
GValue v = G_VALUE_INIT;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
qof_instance_get_kvp (QOF_INSTANCE(acc),
"reconcile-info/postpone/date", &v);
if (G_VALUE_HOLDS_INT64 (&v))
date = g_value_get_int64 (&v);
if (date)
{
if (postpone_date)
*postpone_date = date;
return TRUE;
}
return FALSE;
}
/********************************************************************\
\********************************************************************/
void
xaccAccountSetReconcilePostponeDate (Account *acc, time64 postpone_date)
{
GValue v = G_VALUE_INIT;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_value_init (&v, G_TYPE_INT64);
g_value_set_int64 (&v, postpone_date);
xaccAccountBeginEdit (acc);
qof_instance_set_kvp (QOF_INSTANCE (acc),
"/reconcile-info/postpone/date", &v);
mark_account (acc);
xaccAccountCommitEdit (acc);
}
/********************************************************************\
\********************************************************************/
gboolean
xaccAccountGetReconcilePostponeBalance (const Account *acc,
gnc_numeric *balance)
{
gnc_numeric bal = gnc_numeric_zero ();
GValue v = G_VALUE_INIT;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
qof_instance_get_kvp (QOF_INSTANCE(acc),
"reconcile-info/postpone/balance", &v);
if (G_VALUE_HOLDS_INT64 (&v))
bal = *(gnc_numeric*)g_value_get_boxed (&v);
if (bal.denom)
{
if (balance)
*balance = bal;
return TRUE;
}
return FALSE;
}
/********************************************************************\
\********************************************************************/
void
xaccAccountSetReconcilePostponeBalance (Account *acc, gnc_numeric balance)
{
GValue v = G_VALUE_INIT;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_value_init (&v, GNC_TYPE_NUMERIC);
g_value_set_boxed (&v, &balance);
xaccAccountBeginEdit (acc);
qof_instance_set_kvp (QOF_INSTANCE (acc),
"/reconcile-info/postpone/balance", &v);
mark_account (acc);
xaccAccountCommitEdit (acc);
}
/********************************************************************\
\********************************************************************/
void
xaccAccountClearReconcilePostpone (Account *acc)
{
if (!acc) return;
xaccAccountBeginEdit (acc);
qof_instance_set_kvp (QOF_INSTANCE(acc), "reconcile-info/postpone", NULL);
mark_account (acc);
xaccAccountCommitEdit (acc);
}
/********************************************************************\
\********************************************************************/
/* xaccAccountGetAutoInterestXfer: determine whether the auto interest
* xfer option is enabled for this account, and return that value.
* If it is not defined for the account, return the default value.
*/
gboolean
xaccAccountGetAutoInterestXfer (const Account *acc, gboolean default_value)
{
return boolean_from_key (acc, "reconcile-info/auto-interest-transfer");
}
/********************************************************************\
\********************************************************************/
void
xaccAccountSetAutoInterestXfer (Account *acc, gboolean option)
{
set_boolean_key (acc, "reconcile-info/auto-interest-transfer", option);
}
/********************************************************************\
\********************************************************************/
const char *
xaccAccountGetLastNum (const Account *acc)
{
GValue v = G_VALUE_INIT;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
qof_instance_get_kvp (QOF_INSTANCE(acc), "last-num", &v);
return G_VALUE_HOLDS_STRING (&v) ? g_value_get_string (&v) : NULL;
}
/********************************************************************\
\********************************************************************/
void
xaccAccountSetLastNum (Account *acc, const char *num)
{
GValue v = G_VALUE_INIT;
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_value_init (&v, G_TYPE_STRING);
g_value_set_string (&v, num);
xaccAccountBeginEdit (acc);
qof_instance_set_kvp (QOF_INSTANCE (acc), "last-num", &v);
mark_account (acc);
xaccAccountCommitEdit (acc);
}
static Account *
GetOrMakeOrphanAccount (Account *root, gnc_commodity * currency)
{
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 (_("Orphaned Gains"), "-",
gnc_commodity_get_mnemonic (currency), NULL);
/* See if we've got one of these going already ... */
acc = gnc_account_lookup_by_name(root, accname);
if (acc == NULL)
{
/* Guess not. We'll have to build one. */
acc = xaccMallocAccount (gnc_account_get_book(root));
xaccAccountBeginEdit (acc);
xaccAccountSetName (acc, accname);
xaccAccountSetCommodity (acc, currency);
xaccAccountSetType (acc, ACCT_TYPE_INCOME);
xaccAccountSetDescription (acc, _("Realized Gain/Loss"));
xaccAccountSetNotes (acc,
_("Realized Gains or Losses from "
"Commodity or Trading Accounts "
"that haven't been recorded elsewhere."));
/* Hang the account off the root. */
gnc_account_append_child (root, acc);
xaccAccountCommitEdit (acc);
}
g_free (accname);
return acc;
}
Account *
xaccAccountGainsAccount (Account *acc, gnc_commodity *curr)
{
GValue v = G_VALUE_INIT;
gchar *curr_name = g_strdup_printf ("/lot-mgmt/gains-act/%s",
gnc_commodity_get_unique_name (curr));
GncGUID *guid = NULL;
Account *gains_account;
g_return_val_if_fail (acc != NULL, NULL);
qof_instance_get_kvp (QOF_INSTANCE(acc), curr_name, &v);
if (G_VALUE_HOLDS_BOXED (&v))
guid = (GncGUID*)g_value_get_boxed (&v);
if (guid == NULL) /* No gains account for this currency */
{
gains_account = GetOrMakeOrphanAccount (gnc_account_get_root (acc),
curr);
guid = (GncGUID*)qof_instance_get_guid (QOF_INSTANCE (gains_account));
xaccAccountBeginEdit (acc);
{
GValue vr = G_VALUE_INIT;
g_value_init (&vr, GNC_TYPE_GUID);
g_value_set_boxed (&vr, guid);
qof_instance_set_kvp (QOF_INSTANCE (acc), curr_name, &vr);
qof_instance_set_dirty (QOF_INSTANCE (acc));
}
xaccAccountCommitEdit (acc);
}
else
gains_account = xaccAccountLookup (guid,
qof_instance_get_book(acc));
g_free (curr_name);
return gains_account;
}
/********************************************************************\
\********************************************************************/
void
dxaccAccountSetPriceSrc(Account *acc, const char *src)
{
if (!acc) return;
if (xaccAccountIsPriced(acc))
{
xaccAccountBeginEdit(acc);
if (src)
{
GValue v = G_VALUE_INIT;
g_value_init (&v, G_TYPE_STRING);
g_value_set_string (&v, src);
qof_instance_set_kvp (QOF_INSTANCE(acc),
"old-price-source", &v);
}
else
qof_instance_set_kvp (QOF_INSTANCE(acc), "old-price-source", NULL);
mark_account (acc);
xaccAccountCommitEdit(acc);
}
}
/********************************************************************\
\********************************************************************/
const char*
dxaccAccountGetPriceSrc(const Account *acc)
{
GValue v = G_VALUE_INIT;
if (!acc) return NULL;
if (!xaccAccountIsPriced(acc)) return NULL;
qof_instance_get_kvp (QOF_INSTANCE(acc), "old-price-source", &v);
return G_VALUE_HOLDS_STRING (&v) ? g_value_get_string (&v) : NULL;
}
/********************************************************************\
\********************************************************************/
void
dxaccAccountSetQuoteTZ(Account *acc, const char *tz)
{
GValue v = G_VALUE_INIT;
if (!acc) return;
if (!xaccAccountIsPriced(acc)) return;
xaccAccountBeginEdit(acc);
g_value_init (&v, G_TYPE_STRING);
g_value_set_string (&v, tz);
qof_instance_set_kvp (QOF_INSTANCE (acc), "old-quote-tz", &v);
mark_account (acc);
xaccAccountCommitEdit(acc);
}
/********************************************************************\
\********************************************************************/
const char*
dxaccAccountGetQuoteTZ(const Account *acc)
{
GValue v = G_VALUE_INIT;
if (!acc) return NULL;
if (!xaccAccountIsPriced(acc)) return NULL;
qof_instance_get_kvp (QOF_INSTANCE (acc), "old-quote-tz", &v);
return G_VALUE_HOLDS_STRING (&v) ? g_value_get_string (&v) : NULL;
}
/********************************************************************\
\********************************************************************/
void
xaccAccountSetReconcileChildrenStatus(Account *acc, gboolean status)
{
GValue v = G_VALUE_INIT;
if (!acc) return;
xaccAccountBeginEdit (acc);
/* Would have been nice to use G_TYPE_BOOLEAN, but the other
* boolean kvps save the value as "true" or "false" and that would
* be file-incompatible with this.
*/
g_value_init (&v, G_TYPE_INT64);
g_value_set_int64 (&v, status);
qof_instance_set_kvp (QOF_INSTANCE (acc),
"/reconcile-info/include-children", &v);
mark_account(acc);
xaccAccountCommitEdit (acc);
}
/********************************************************************\
\********************************************************************/
gboolean
xaccAccountGetReconcileChildrenStatus(const Account *acc)
{
/* access the account's kvp-data for status and return that, if no value
* is found then we can assume not to include the children, that being
* the default behaviour
*/
GValue v = G_VALUE_INIT;
if (!acc) return FALSE;
qof_instance_get_kvp (QOF_INSTANCE (acc),
"reconcile-info/include-children", &v);
return G_VALUE_HOLDS_INT64 (&v) ? g_value_get_int64 (&v) : FALSE;
}
/********************************************************************\
\********************************************************************/
/* The caller of this function can get back one or both of the
* matching split and transaction pointers, depending on whether
* a valid pointer to the location to store those pointers is
* passed.
*/
static void
finder_help_function(const Account *acc, const char *description,
Split **split, Transaction **trans )
{
AccountPrivate *priv;
GList *slp;
/* First, make sure we set the data to NULL BEFORE we start */
if (split) *split = NULL;
if (trans) *trans = NULL;
/* Then see if we have any work to do */
if (acc == NULL) return;
/* Why is this loop iterated backwards ?? Presumably because the split
* list is in date order, and the most recent matches should be
* returned!? */
priv = GET_PRIVATE(acc);
for (slp = g_list_last(priv->splits); slp; slp = slp->prev)
{
Split *lsplit = slp->data;
Transaction *ltrans = xaccSplitGetParent(lsplit);
if (g_strcmp0 (description, xaccTransGetDescription (ltrans)) == 0)
{
if (split) *split = lsplit;
if (trans) *trans = ltrans;
return;
}
}
}
Split *
xaccAccountFindSplitByDesc(const Account *acc, const char *description)
{
Split *split;
/* Get the split which has a transaction matching the description. */
finder_help_function(acc, description, &split, NULL);
return split;
}
/* This routine is for finding a matching transaction in an account by
* matching on the description field. [CAS: The rest of this comment
* seems to belong somewhere else.] This routine is used for
* auto-filling in registers with a default leading account. The
* dest_trans is a transaction used for currency checking. */
Transaction *
xaccAccountFindTransByDesc(const Account *acc, const char *description)
{
Transaction *trans;
/* Get the translation matching the description. */
finder_help_function(acc, description, NULL, &trans);
return trans;
}
/* ================================================================ */
/* Concatenation, Merging functions */
void
gnc_account_join_children (Account *to_parent, Account *from_parent)
{
AccountPrivate *from_priv;
GList *children, *node;
/* errors */
g_return_if_fail(GNC_IS_ACCOUNT(to_parent));
g_return_if_fail(GNC_IS_ACCOUNT(from_parent));
/* optimizations */
from_priv = GET_PRIVATE(from_parent);
if (!from_priv->children)
return;
ENTER (" ");
children = g_list_copy(from_priv->children);
for (node = children; node; node = g_list_next(node))
gnc_account_append_child(to_parent, node->data);
g_list_free(children);
LEAVE (" ");
}
/********************************************************************\
\********************************************************************/
void
gnc_account_merge_children (Account *parent)
{
AccountPrivate *ppriv, *priv_a, *priv_b;
GList *node_a, *node_b, *work, *worker;
g_return_if_fail(GNC_IS_ACCOUNT(parent));
ppriv = GET_PRIVATE(parent);
for (node_a = ppriv->children; node_a; node_a = node_a->next)
{
Account *acc_a = node_a->data;
priv_a = GET_PRIVATE(acc_a);
for (node_b = node_a->next; node_b; node_b = g_list_next(node_b))
{
Account *acc_b = node_b->data;
priv_b = GET_PRIVATE(acc_b);
if (0 != null_strcmp(priv_a->accountName, priv_b->accountName))
continue;
if (0 != null_strcmp(priv_a->accountCode, priv_b->accountCode))
continue;
if (0 != null_strcmp(priv_a->description, priv_b->description))
continue;
if (0 != null_strcmp(xaccAccountGetColor(acc_a),
xaccAccountGetColor(acc_b)))
continue;
if (!gnc_commodity_equiv(priv_a->commodity, priv_b->commodity))
continue;
if (0 != null_strcmp(xaccAccountGetNotes(acc_a),
xaccAccountGetNotes(acc_b)))
continue;
if (priv_a->type != priv_b->type)
continue;
/* consolidate children */
if (priv_b->children)
{
work = g_list_copy(priv_b->children);
for (worker = work; worker; worker = g_list_next(worker))
gnc_account_append_child (acc_a, (Account *)worker->data);
g_list_free(work);
qof_event_gen (&acc_a->inst, QOF_EVENT_MODIFY, NULL);
qof_event_gen (&acc_b->inst, QOF_EVENT_MODIFY, NULL);
}
/* recurse to do the children's children */
gnc_account_merge_children (acc_a);
/* consolidate transactions */
while (priv_b->splits)
xaccSplitSetAccount (priv_b->splits->data, acc_a);
/* move back one before removal. next iteration around the loop
* will get the node after node_b */
node_b = g_list_previous(node_b);
/* The destroy function will remove from list -- node_a is ok,
* it's before node_b */
xaccAccountBeginEdit (acc_b);
xaccAccountDestroy (acc_b);
}
}
}
/* ================================================================ */
/* Transaction Traversal functions */
void
xaccSplitsBeginStagedTransactionTraversals (GList *splits)
{
GList *lp;
for (lp = splits; lp; lp = lp->next)
{
Split *s = lp->data;
Transaction *trans = s->parent;
if (trans)
trans->marker = 0;
}
}
/* original function */
void
xaccAccountBeginStagedTransactionTraversals (const Account *account)
{
AccountPrivate *priv;
if (!account)
return;
priv = GET_PRIVATE(account);
xaccSplitsBeginStagedTransactionTraversals(priv->splits);
}
gboolean
xaccTransactionTraverse (Transaction *trans, int stage)
{
if (trans == NULL) return FALSE;
if (trans->marker < stage)
{
trans->marker = stage;
return TRUE;
}
return FALSE;
}
static void do_one_split (Split *s, gpointer data)
{
Transaction *trans = s->parent;
trans->marker = 0;
}
static void do_one_account (Account *account, gpointer data)
{
AccountPrivate *priv = GET_PRIVATE(account);
g_list_foreach(priv->splits, (GFunc)do_one_split, NULL);
}
/* Replacement for xaccGroupBeginStagedTransactionTraversals */
void
gnc_account_tree_begin_staged_transaction_traversals (Account *account)
{
GList *descendants;
descendants = gnc_account_get_descendants(account);
g_list_foreach(descendants, (GFunc)do_one_account, NULL);
g_list_free(descendants);
}
int
xaccAccountStagedTransactionTraversal (const Account *acc,
unsigned int stage,
TransactionCallback thunk,
void *cb_data)
{
AccountPrivate *priv;
GList *split_p;
GList *next;
Transaction *trans;
Split *s;
int retval;
if (!acc) return 0;
priv = GET_PRIVATE(acc);
for (split_p = priv->splits; split_p; split_p = next)
{
/* Get the next element in the split list now, just in case some
* naughty thunk destroys the one we're using. This reduces, but
* does not eliminate, the possibility of undefined results if
* a thunk removes splits from this account. */
next = g_list_next(split_p);
s = split_p->data;
trans = s->parent;
if (trans && (trans->marker < stage))
{
trans->marker = stage;
if (thunk)
{
retval = thunk(trans, cb_data);
if (retval) return retval;
}
}
}
return 0;
}
int
gnc_account_tree_staged_transaction_traversal (const Account *acc,
unsigned int stage,
TransactionCallback thunk,
void *cb_data)
{
const AccountPrivate *priv;
GList *acc_p, *split_p;
Transaction *trans;
Split *s;
int retval;
if (!acc) return 0;
/* depth first traversal */
priv = GET_PRIVATE(acc);
for (acc_p = priv->children; acc_p; acc_p = g_list_next(acc_p))
{
retval = gnc_account_tree_staged_transaction_traversal(acc_p->data, stage,
thunk, cb_data);
if (retval) return retval;
}
/* Now this account */
for (split_p = priv->splits; split_p; split_p = g_list_next(split_p))
{
s = split_p->data;
trans = s->parent;
if (trans && (trans->marker < stage))
{
trans->marker = stage;
if (thunk)
{
retval = thunk(trans, cb_data);
if (retval) return retval;
}
}
}
return 0;
}
/********************************************************************\
\********************************************************************/
int
xaccAccountTreeForEachTransaction (Account *acc,
int (*proc)(Transaction *t, void *data),
void *data)
{
if (!acc || !proc) return 0;
gnc_account_tree_begin_staged_transaction_traversals (acc);
return gnc_account_tree_staged_transaction_traversal (acc, 42, proc, data);
}
gint
xaccAccountForEachTransaction(const Account *acc, TransactionCallback proc,
void *data)
{
if (!acc || !proc) return 0;
xaccAccountBeginStagedTransactionTraversals (acc);
return xaccAccountStagedTransactionTraversal(acc, 42, proc, data);
}
/* ================================================================ */
/* The following functions are used by
* src/import-export/import-backend.c to manipulate the contra-account
* matching data. See src/import-export/import-backend.c for explanations.
*/
#define IMAP_FRAME "import-map"
#define IMAP_FRAME_BAYES "import-map-bayes"
/* Obtain an ImportMatchMap object from an Account or a Book */
GncImportMatchMap *
gnc_account_imap_create_imap (Account *acc)
{
GncImportMatchMap *imap;
if (!acc) return NULL;
imap = g_new0(GncImportMatchMap, 1);
/* Cache the book for easy lookups; store the account/book for
* marking dirtiness
*/
imap->acc = acc;
imap->book = gnc_account_get_book (acc);
return imap;
}
/* Look up an Account in the map */
Account*
gnc_account_imap_find_account (GncImportMatchMap *imap,
const char *category,
const char *key)
{
GValue v = G_VALUE_INIT;
GncGUID * guid = NULL;
char *kvp_path;
if (!imap || !key) return NULL;
if (!category)
kvp_path = g_strdup_printf (IMAP_FRAME "/%s", key);
else
kvp_path = g_strdup_printf (IMAP_FRAME "/%s/%s", category, key);
qof_instance_get_kvp (QOF_INSTANCE (imap->acc), kvp_path, &v);
if (G_VALUE_HOLDS_BOXED (&v))
guid = (GncGUID*)g_value_get_boxed (&v);
g_free (kvp_path);
return xaccAccountLookup (guid, imap->book);
}
/* Store an Account in the map */
void
gnc_account_imap_add_account (GncImportMatchMap *imap,
const char *category,
const char *key,
Account *acc)
{
GValue v = G_VALUE_INIT;
char *kvp_path;
if (!imap || !key || !acc || (strlen (key) == 0)) return;
if (!category)
kvp_path = g_strdup_printf (IMAP_FRAME "/%s", key);
else
kvp_path = g_strdup_printf (IMAP_FRAME "/%s/%s", category, key);
g_value_init (&v, GNC_TYPE_GUID);
g_value_set_boxed (&v, xaccAccountGetGUID (acc));
xaccAccountBeginEdit (imap->acc);
qof_instance_set_kvp (QOF_INSTANCE (imap->acc), kvp_path, &v);
g_free (kvp_path);
qof_instance_set_dirty (QOF_INSTANCE (imap->acc));
xaccAccountCommitEdit (imap->acc);
}
/* Remove a reference to an Account in the map */
void
gnc_account_imap_delete_account (GncImportMatchMap *imap,
const char *category,
const char *key)
{
char *kvp_path;
if (!imap || !key) return;
if (!category)
kvp_path = g_strdup_printf (IMAP_FRAME "/%s", key);
else
kvp_path = g_strdup_printf (IMAP_FRAME "/%s/%s", category, key);
xaccAccountBeginEdit (imap->acc);
if (qof_instance_has_slot (QOF_INSTANCE (imap->acc), kvp_path))
{
qof_instance_slot_delete (QOF_INSTANCE (imap->acc), kvp_path);
g_free (kvp_path);
if (category)
{
kvp_path = g_strdup_printf (IMAP_FRAME "/%s", category);
qof_instance_slot_delete_if_empty (QOF_INSTANCE (imap->acc), kvp_path);
g_free (kvp_path);
}
qof_instance_slot_delete_if_empty (QOF_INSTANCE (imap->acc), IMAP_FRAME);
}
qof_instance_set_dirty (QOF_INSTANCE (imap->acc));
xaccAccountCommitEdit (imap->acc);
}
/*--------------------------------------------------------------------------
Below here is the bayes transaction to account matching system
--------------------------------------------------------------------------*/
struct account_token_count
{
char* account_guid;
gint64 token_count; /**< occurrences of a given token for this account_guid */
};
/** total_count and the token_count for a given account let us calculate the
* probability of a given account with any single token
*/
struct token_accounts_info
{
GList *accounts; /**< array of struct account_token_count */
gint64 total_count;
};
/** gpointer is a pointer to a struct token_accounts_info
* \note Can always assume that keys are unique, reduces code in this function
*/
static void
buildTokenInfo(const char *key, const GValue *value, gpointer data)
{
struct token_accounts_info *tokenInfo = (struct token_accounts_info*)data;
struct account_token_count* this_account;
// PINFO("buildTokenInfo: account '%s', token_count: '%" G_GINT64_FORMAT "'", (char*)key,
// g_value_get_int64(value));
/* add the count to the total_count */
tokenInfo->total_count += g_value_get_int64(value);
/* allocate a new structure for this account and it's token count */
this_account = (struct account_token_count*)
g_new0(struct account_token_count, 1);
/* fill in the account guid and number of tokens found for this account guid */
this_account->account_guid = (char*)key;
this_account->token_count = g_value_get_int64(value);
/* append onto the glist a pointer to the new account_token_count structure */
tokenInfo->accounts = g_list_prepend(tokenInfo->accounts, this_account);
}
/** intermediate values used to calculate the bayes probability of a given account
where p(AB) = (a*b)/[a*b + (1-a)(1-b)], product is (a*b),
product_difference is (1-a) * (1-b)
*/
struct account_probability
{
double product; /* product of probabilities */
double product_difference; /* product of (1-probabilities) */
};
/** convert a hash table of account names and (struct account_probability*)
into a hash table of 100000x the percentage match value, ie. 10% would be
0.10 * 100000 = 10000
*/
#define PROBABILITY_FACTOR 100000
static void
buildProbabilities(gpointer key, gpointer value, gpointer data)
{
GHashTable *final_probabilities = (GHashTable*)data;
struct account_probability *account_p = (struct account_probability*)value;
/* P(AB) = A*B / [A*B + (1-A)*(1-B)]
* NOTE: so we only keep track of a running product(A*B*C...)
* and product difference ((1-A)(1-B)...)
*/
gint32 probability =
(account_p->product /
(account_p->product + account_p->product_difference))
* PROBABILITY_FACTOR;
PINFO("P('%s') = '%d'", (char*)key, probability);
g_hash_table_insert(final_probabilities, key, GINT_TO_POINTER(probability));
}
/** Frees an array of the same time that buildProperties built */
static void
freeProbabilities(gpointer key, gpointer value, gpointer data)
{
/* free up the struct account_probability that was allocated
* in gnc_account_find_account_bayes()
*/
g_free(value);
}
/** holds an account guid and its corresponding integer probability
the integer probability is some factor of 10
*/
struct account_info
{
char* account_guid;
gint32 probability;
};
/** Find the highest probability and the corresponding account guid
store in data, a (struct account_info*)
NOTE: this is a g_hash_table_foreach() function for a hash table of entries
key is a pointer to the account guid, value is a gint32, 100000x
the probability for this account
*/
static void
highestProbability(gpointer key, gpointer value, gpointer data)
{
struct account_info *account_i = (struct account_info*)data;
/* if the current probability is greater than the stored, store the current */
if (GPOINTER_TO_INT(value) > account_i->probability)
{
/* Save the new highest probability and the assoaciated account guid */
account_i->probability = GPOINTER_TO_INT(value);
account_i->account_guid = key;
}
}
#define threshold (.90 * PROBABILITY_FACTOR) /* 90% */
/** Look up an Account in the map */
Account*
gnc_account_imap_find_account_bayes (GncImportMatchMap *imap, GList *tokens)
{
struct token_accounts_info tokenInfo; /**< holds the accounts and total
* token count for a single token */
GList *current_token; /**< pointer to the current
* token from the input GList
* tokens */
GList *current_account_token; /**< pointer to the struct
* account_token_count */
struct account_token_count *account_c; /**< an account name and the number
* of times a token has appeared
* for the account */
struct account_probability *account_p; /**< intermediate storage of values
* to compute the bayes probability
* of an account */
GHashTable *running_probabilities = g_hash_table_new(g_str_hash,
g_str_equal);
GHashTable *final_probabilities = g_hash_table_new(g_str_hash,
g_str_equal);
struct account_info account_i;
ENTER(" ");
/* check to see if the imap is NULL */
if (!imap)
{
PINFO("imap is null, returning null");
LEAVE(" ");
return NULL;
}
/* find the probability for each account that contains any of the tokens
* in the input tokens list
*/
for (current_token = tokens; current_token;
current_token = current_token->next)
{
char* path = g_strdup_printf (IMAP_FRAME_BAYES "/%s",
(char*)current_token->data);
/* zero out the token_accounts_info structure */
memset(&tokenInfo, 0, sizeof(struct token_accounts_info));
PINFO("token: '%s'", (char*)current_token->data);
/* process the accounts for this token, adding the account if it
* doesn't already exist or adding to the existing accounts token
* count if it does
*/
qof_instance_foreach_slot(QOF_INSTANCE (imap->acc), path,
buildTokenInfo, &tokenInfo);
g_free (path);
/* for each account we have just found, see if the account
* already exists in the list of account probabilities, if not
* add it
*/
for (current_account_token = tokenInfo.accounts; current_account_token;
current_account_token = current_account_token->next)
{
/* get the account name and corresponding token count */
account_c = (struct account_token_count*)current_account_token->data;
PINFO("account_c->account_guid('%s'), "
"account_c->token_count('%" G_GINT64_FORMAT
"')/total_count('%" G_GINT64_FORMAT "')",
account_c->account_guid, account_c->token_count,
tokenInfo.total_count);
account_p = g_hash_table_lookup(running_probabilities,
account_c->account_guid);
/* if the account exists in the list then continue
* the running probablities
*/
if (account_p)
{
account_p->product = (((double)account_c->token_count /
(double)tokenInfo.total_count)
* account_p->product);
account_p->product_difference =
((double)1 - ((double)account_c->token_count /
(double)tokenInfo.total_count))
* account_p->product_difference;
PINFO("product == %f, product_difference == %f",
account_p->product, account_p->product_difference);
}
else
{
/* add a new entry */
PINFO("adding a new entry for this account");
account_p = (struct account_probability*)
g_new0(struct account_probability, 1);
/* set the product and product difference values */
account_p->product = ((double)account_c->token_count /
(double)tokenInfo.total_count);
account_p->product_difference =
(double)1 - ((double)account_c->token_count /
(double)tokenInfo.total_count);
PINFO("product == %f, product_difference == %f",
account_p->product, account_p->product_difference);
/* add the account guid and (struct account_probability*)
* to the hash table */
g_hash_table_insert(running_probabilities,
account_c->account_guid, account_p);
}
} /* for all accounts in tokenInfo */
/* free the data in tokenInfo */
for (current_account_token = tokenInfo.accounts; current_account_token;
current_account_token = current_account_token->next)
{
/* free up each struct account_token_count we allocated */
g_free((struct account_token_count*)current_account_token->data);
}
g_list_free(tokenInfo.accounts); /* free the accounts GList */
}
/* build a hash table of account names and their final probabilities
* from each entry in the running_probabilties hash table
*/
g_hash_table_foreach(running_probabilities, buildProbabilities,
final_probabilities);
/* find the highest probabilty and the corresponding account */
memset(&account_i, 0, sizeof(struct account_info));
g_hash_table_foreach(final_probabilities, highestProbability, &account_i);
/* free each element of the running_probabilities hash */
g_hash_table_foreach(running_probabilities, freeProbabilities, NULL);
/* free the hash tables */
g_hash_table_destroy(running_probabilities);
g_hash_table_destroy(final_probabilities);
PINFO("highest P('%s') = '%d'",
account_i.account_guid ? account_i.account_guid : "(null)",
account_i.probability);
/* has this probability met our threshold? */
if (account_i.probability >= threshold)
{
GncGUID *guid;
Account *account = NULL;
PINFO("Probability has met threshold");
guid = g_new (GncGUID, 1);
if (string_to_guid (account_i.account_guid, guid))
account = xaccAccountLookup (guid, imap->book);
g_free (guid);
if (account != NULL)
LEAVE("Return account is '%s'", xaccAccountGetName (account));
else
LEAVE("Return NULL, account for Guid '%s' can not be found", account_i.account_guid);
return account;
}
PINFO("Probability has not met threshold");
LEAVE("Return NULL");
return NULL; /* we didn't meet our threshold, return NULL for an account */
}
static void
change_imap_entry (GncImportMatchMap *imap, gchar *kvp_path, int64_t token_count)
{
GValue value = G_VALUE_INIT;
PINFO("Source Account is '%s', kvp_path is '%s', Count is '%" G_GINT64_FORMAT "'",
xaccAccountGetName (imap->acc), kvp_path, token_count);
// check for existing guid entry
if (qof_instance_has_slot (QOF_INSTANCE(imap->acc), kvp_path))
{
int64_t existing_token_count = 0;
// get the existing_token_count value
qof_instance_get_kvp (QOF_INSTANCE (imap->acc), kvp_path, &value);
if (G_VALUE_HOLDS_INT64 (&value))
existing_token_count = g_value_get_int64 (&value);
PINFO("found existing value of '%" G_GINT64_FORMAT "'", existing_token_count);
token_count = token_count + existing_token_count;
}
if (!G_IS_VALUE (&value))
g_value_init (&value, G_TYPE_INT64);
g_value_set_int64 (&value, token_count);
// Add or Update the entry based on guid
qof_instance_set_kvp (QOF_INSTANCE (imap->acc), kvp_path, &value);
/* Set a feature flag in the book for the change to use guid.
* This will prevent older GnuCash versions that don't support this feature
* from opening this file. */
gnc_features_set_used (imap->book, GNC_FEATURE_GUID_BAYESIAN);
}
/** Updates the imap for a given account using a list of tokens */
void
gnc_account_imap_add_account_bayes (GncImportMatchMap *imap,
GList *tokens,
Account *acc)
{
GList *current_token;
gint64 token_count;
char *account_fullname, *kvp_path;
char *guid_string;
ENTER(" ");
if (!imap)
{
LEAVE(" ");
return;
}
g_return_if_fail (acc != NULL);
account_fullname = gnc_account_get_full_name(acc);
xaccAccountBeginEdit (imap->acc);
PINFO("account name: '%s'", account_fullname);
guid_string = guid_to_string (xaccAccountGetGUID (acc));
/* process each token in the list */
for (current_token = g_list_first(tokens); current_token;
current_token = current_token->next)
{
/* Jump to next iteration if the pointer is not valid or if the
string is empty. In HBCI import we almost always get an empty
string, which doesn't work in the kvp loopkup later. So we
skip this case here. */
if (!current_token->data || (*((char*)current_token->data) == '\0'))
continue;
/* start off with one token for this account */
token_count = 1;
PINFO("adding token '%s'", (char*)current_token->data);
kvp_path = g_strdup_printf (IMAP_FRAME_BAYES "/%s/%s",
(char*)current_token->data,
guid_string);
/* change the imap entry for the account */
change_imap_entry (imap, kvp_path, token_count);
g_free (kvp_path);
}
/* free up the account fullname and guid string */
qof_instance_set_dirty (QOF_INSTANCE (imap->acc));
xaccAccountCommitEdit (imap->acc);
g_free (account_fullname);
g_free (guid_string);
LEAVE(" ");
}
/*******************************************************************************/
static void
build_bayes_layer_two (const char *key, const GValue *value, gpointer user_data)
{
QofBook *book;
Account *map_account = NULL;
GncGUID *guid;
gchar *kvp_path;
gchar *count;
struct imap_info *imapInfo_node;
struct imap_info *imapInfo = (struct imap_info*)user_data;
// Get the book
book = qof_instance_get_book (imapInfo->source_account);
if (G_VALUE_HOLDS_INT64 (value))
{
PINFO("build_bayes_layer_two: account '%s', token_count: '%" G_GINT64_FORMAT "'",
(char*)key, g_value_get_int64(value));
count = g_strdup_printf ("%" G_GINT64_FORMAT, g_value_get_int64 (value));
}
else
count = g_strdup ("0");
kvp_path = g_strconcat (imapInfo->category_head, "/", key, NULL);
PINFO("build_bayes_layer_two: kvp_path is '%s'", kvp_path);
guid = g_new (GncGUID, 1);
if (string_to_guid (key, guid))
map_account = xaccAccountLookup (guid, book);
g_free (guid);
imapInfo_node = g_malloc(sizeof(*imapInfo_node));
imapInfo_node->source_account = imapInfo->source_account;
imapInfo_node->map_account = map_account;
imapInfo_node->full_category = g_strdup (kvp_path);
imapInfo_node->match_string = g_strdup (imapInfo->match_string);
imapInfo_node->category_head = g_strdup (imapInfo->category_head);
imapInfo_node->count = g_strdup (count);
imapInfo->list = g_list_append (imapInfo->list, imapInfo_node);
g_free (kvp_path);
g_free (count);
}
static void
build_bayes (const char *key, const GValue *value, gpointer user_data)
{
gchar *kvp_path;
struct imap_info *imapInfo = (struct imap_info*)user_data;
struct imap_info imapInfol2;
PINFO("build_bayes: match string '%s'", (char*)key);
if (G_VALUE_HOLDS (value, G_TYPE_STRING) && g_value_get_string (value) == NULL)
{
kvp_path = g_strdup_printf (IMAP_FRAME_BAYES "/%s", key);
if (qof_instance_has_slot (QOF_INSTANCE(imapInfo->source_account), kvp_path))
{
PINFO("build_bayes: kvp_path is '%s', key '%s'", kvp_path, key);
imapInfol2.source_account = imapInfo->source_account;
imapInfol2.match_string = g_strdup (key);
imapInfol2.category_head = g_strdup (kvp_path);
imapInfol2.list = imapInfo->list;
qof_instance_foreach_slot (QOF_INSTANCE(imapInfo->source_account), kvp_path,
build_bayes_layer_two, &imapInfol2);
imapInfo->list = imapInfol2.list;
g_free (imapInfol2.match_string);
g_free (imapInfol2.category_head);
}
g_free (kvp_path);
}
}
static void
build_non_bayes (const char *key, const GValue *value, gpointer user_data)
{
if (G_VALUE_HOLDS_BOXED (value))
{
QofBook *book;
GncGUID *guid = NULL;
gchar *kvp_path;
gchar *guid_string = NULL;
struct imap_info *imapInfo_node;
struct imap_info *imapInfo = (struct imap_info*)user_data;
// Get the book
book = qof_instance_get_book (imapInfo->source_account);
guid = (GncGUID*)g_value_get_boxed (value);
guid_string = guid_to_string (guid);
PINFO("build_non_bayes: account '%s', match account guid: '%s'",
(char*)key, guid_string);
kvp_path = g_strconcat (imapInfo->category_head, "/", key, NULL);
PINFO("build_non_bayes: kvp_path is '%s'", kvp_path);
imapInfo_node = g_malloc(sizeof(*imapInfo_node));
imapInfo_node->source_account = imapInfo->source_account;
imapInfo_node->map_account = xaccAccountLookup (guid, book);
imapInfo_node->full_category = g_strdup (kvp_path);
imapInfo_node->match_string = g_strdup (key);
imapInfo_node->category_head = g_strdup (imapInfo->category_head);
imapInfo_node->count = g_strdup (" ");
imapInfo->list = g_list_append (imapInfo->list, imapInfo_node);
g_free (kvp_path);
g_free (guid_string);
}
}
GList *
gnc_account_imap_get_info_bayes (Account *acc)
{
GList *list = NULL;
GncImapInfo imapInfo;
imapInfo.source_account = acc;
imapInfo.list = list;
if (qof_instance_has_slot (QOF_INSTANCE(acc), IMAP_FRAME_BAYES))
qof_instance_foreach_slot (QOF_INSTANCE(acc), IMAP_FRAME_BAYES,
build_bayes, &imapInfo);
return imapInfo.list;
}
GList *
gnc_account_imap_get_info (Account *acc, const char *category)
{
GList *list = NULL;
gchar *category_head = NULL;
GncImapInfo imapInfo;
imapInfo.source_account = acc;
imapInfo.list = list;
category_head = g_strdup_printf (IMAP_FRAME "/%s", category);
imapInfo.category_head = category_head;
if (qof_instance_has_slot (QOF_INSTANCE(acc), category_head))
qof_instance_foreach_slot (QOF_INSTANCE(acc), category_head,
build_non_bayes, &imapInfo);
g_free (category_head);
return imapInfo.list;
}
/*******************************************************************************/
gchar *
gnc_account_get_map_entry (Account *acc, const char *full_category)
{
GValue v = G_VALUE_INIT;
gchar *text = NULL;
gchar *kvp_path = g_strdup (full_category);
if (qof_instance_has_slot (QOF_INSTANCE(acc), kvp_path))
{
qof_instance_get_kvp (QOF_INSTANCE(acc), kvp_path, &v);
if (G_VALUE_HOLDS_STRING (&v))
{
gchar const *string;
string = g_value_get_string (&v);
text = g_strdup (string);
}
}
g_free (kvp_path);
return text;
}
void
gnc_account_delete_map_entry (Account *acc, char *full_category, gboolean empty)
{
gchar *kvp_path = g_strdup (full_category);
if ((acc != NULL) && qof_instance_has_slot (QOF_INSTANCE(acc), kvp_path))
{
xaccAccountBeginEdit (acc);
if (empty)
qof_instance_slot_delete_if_empty (QOF_INSTANCE(acc), kvp_path);
else
qof_instance_slot_delete (QOF_INSTANCE(acc), kvp_path);
PINFO("Account is '%s', path is '%s'", xaccAccountGetName (acc), kvp_path);
qof_instance_set_dirty (QOF_INSTANCE(acc));
xaccAccountCommitEdit (acc);
}
g_free (kvp_path);
g_free (full_category);
}
/*******************************************************************************/
static gchar *
look_for_old_separator_descendants (Account *root, gchar *full_name, const gchar *separator)
{
GList *top_accounts, *ptr;
gint found_len = 0;
gchar found_sep;
top_accounts = gnc_account_get_descendants (root);
PINFO("Incoming full_name is '%s', current separator is '%s'", full_name, separator);
/* Go through list of top level accounts */
for (ptr = top_accounts; ptr; ptr = g_list_next (ptr))
{
const gchar *name = xaccAccountGetName (ptr->data);
// we are looking for the longest top level account that matches
if (g_str_has_prefix (full_name, name))
{
gint name_len = strlen (name);
const gchar old_sep = full_name[name_len];
if (!g_ascii_isalnum (old_sep)) // test for non alpha numeric
{
if (name_len > found_len)
{
found_sep = full_name[name_len];
found_len = name_len;
}
}
}
}
g_list_free (top_accounts); // Free the List
if (found_len > 1)
full_name = g_strdelimit (full_name, &found_sep, *separator);
PINFO("Return full_name is '%s'", full_name);
return full_name;
}
static void
convert_imap_entry (GncImapInfo *imapInfo, Account *map_account)
{
GncImportMatchMap *imap;
gchar *guid_string;
gchar *kvp_path;
int64_t token_count = 1;
GValue value = G_VALUE_INIT;
// Create an ImportMatchMap object
imap = gnc_account_imap_create_imap (imapInfo->source_account);
xaccAccountBeginEdit (imapInfo->source_account);
guid_string = guid_to_string (xaccAccountGetGUID (map_account));
PINFO("Map Account is '%s', GUID is '%s', Count is %s", xaccAccountGetName (map_account),
guid_string, imapInfo->count);
// save converting string, get the count value
qof_instance_get_kvp (QOF_INSTANCE (imapInfo->source_account), imapInfo->full_category, &value);
if (G_VALUE_HOLDS_INT64 (&value))
token_count = g_value_get_int64 (&value);
// Delete the old entry based on full account name
kvp_path = g_strdup (imapInfo->full_category);
gnc_account_delete_map_entry (imapInfo->source_account, kvp_path, FALSE);
// create path based on guid
kvp_path = g_strdup_printf ("/%s/%s", imapInfo->category_head, guid_string);
// change the imap entry of source_account
change_imap_entry (imap, kvp_path, token_count);
qof_instance_set_dirty (QOF_INSTANCE (imapInfo->source_account));
xaccAccountCommitEdit (imapInfo->source_account);
g_free (kvp_path);
g_free (guid_string);
}
static Account *
look_for_old_mapping (GncImapInfo *imapInfo)
{
Account *root, *map_account = NULL;
const gchar *sep = gnc_get_account_separator_string ();
gchar *full_name;
PINFO("Category Head is '%s', Full Category is '%s'", imapInfo->category_head, imapInfo->full_category);
// do we have a map_account all ready, implying a guid string
if (imapInfo->map_account != NULL)
return NULL;
root = gnc_account_get_root (imapInfo->source_account);
full_name = g_strdup (imapInfo->full_category + strlen (imapInfo->category_head) + 1);
// may be top level or match with existing separator
map_account = gnc_account_lookup_by_full_name (root, full_name);
// do we have a valid account, if not, look for old separator
if (map_account == NULL)
{
full_name = look_for_old_separator_descendants (root, full_name, sep);
map_account = gnc_account_lookup_by_full_name (root, full_name); // lets try again
}
PINFO("Full account name is '%s'", full_name);
g_free (full_name);
return map_account;
}
static void
convert_imap_account (Account *acc)
{
GList *imap_list, *node;
gchar *acc_name = NULL;
acc_name = gnc_account_get_full_name (acc);
PINFO("Source Acc '%s'", acc_name);
imap_list = gnc_account_imap_get_info_bayes (acc);
if (g_list_length (imap_list) > 0) // we have mappings
{
PINFO("List length is %d", g_list_length (imap_list));
xaccAccountBeginEdit(acc);
for (node = imap_list; node; node = g_list_next (node))
{
Account *map_account = NULL;
GncImapInfo *imapInfo = node->data;
// Lets start doing stuff
map_account = look_for_old_mapping (imapInfo);
if (map_account != NULL) // we have an account, try and update it
convert_imap_entry (imapInfo, map_account);
// Free the members and structure
g_free (imapInfo->category_head);
g_free (imapInfo->full_category);
g_free (imapInfo->match_string);
g_free (imapInfo->count);
g_free (imapInfo);
}
xaccAccountCommitEdit(acc);
}
g_free (acc_name);
g_list_free (imap_list); // Free the List
}
void
gnc_account_imap_convert_bayes (QofBook *book)
{
Account *root;
GList *accts, *ptr;
gboolean run_once = FALSE;
GValue value_s = G_VALUE_INIT;
// get the run-once value
qof_instance_get_kvp (QOF_INSTANCE (book), "changed-bayesian-to-guid", &value_s);
if (G_VALUE_HOLDS_STRING (&value_s) && (strcmp(g_value_get_string (&value_s), "true") == 0))
run_once = TRUE;
if (run_once == FALSE)
{
GValue value_b = G_VALUE_INIT;
/* Get list of Accounts */
root = gnc_book_get_root_account (book);
accts = gnc_account_get_descendants_sorted (root);
/* Go through list of accounts */
for (ptr = accts; ptr; ptr = g_list_next (ptr))
{
Account *acc = ptr->data;
convert_imap_account (acc);
}
g_list_free (accts);
g_value_init (&value_b, G_TYPE_BOOLEAN);
g_value_set_boolean (&value_b, TRUE);
// set the run-once value
qof_instance_set_kvp (QOF_INSTANCE (book), "changed-bayesian-to-guid", &value_b);
}
}
/* ================================================================ */
/* QofObject function implementation and registration */
static void
gnc_account_book_end(QofBook* book)
{
Account *root_account = gnc_book_get_root_account(book);
xaccAccountBeginEdit(root_account);
xaccAccountDestroy(root_account);
}
#ifdef _MSC_VER
/* MSVC compiler doesn't have C99 "designated initializers"
* so we wrap them in a macro that is empty on MSVC. */
# define DI(x) /* */
#else
# define DI(x) x
#endif
static QofObject account_object_def =
{
DI(.interface_version = ) QOF_OBJECT_VERSION,
DI(.e_type = ) GNC_ID_ACCOUNT,
DI(.type_label = ) "Account",
DI(.create = ) (gpointer)xaccMallocAccount,
DI(.book_begin = ) NULL,
DI(.book_end = ) gnc_account_book_end,
DI(.is_dirty = ) qof_collection_is_dirty,
DI(.mark_clean = ) qof_collection_mark_clean,
DI(.foreach = ) qof_collection_foreach,
DI(.printable = ) (const char * (*)(gpointer)) xaccAccountGetName,
DI(.version_cmp = ) (int (*)(gpointer, gpointer)) qof_instance_version_cmp,
};
gboolean xaccAccountRegister (void)
{
static QofParam params[] =
{
{
ACCOUNT_NAME_, QOF_TYPE_STRING,
(QofAccessFunc) xaccAccountGetName,
(QofSetterFunc) xaccAccountSetName
},
{
ACCOUNT_CODE_, QOF_TYPE_STRING,
(QofAccessFunc) xaccAccountGetCode,
(QofSetterFunc) xaccAccountSetCode
},
{
ACCOUNT_DESCRIPTION_, QOF_TYPE_STRING,
(QofAccessFunc) xaccAccountGetDescription,
(QofSetterFunc) xaccAccountSetDescription
},
{
ACCOUNT_COLOR_, QOF_TYPE_STRING,
(QofAccessFunc) xaccAccountGetColor,
(QofSetterFunc) xaccAccountSetColor
},
{
ACCOUNT_FILTER_, QOF_TYPE_STRING,
(QofAccessFunc) xaccAccountGetFilter,
(QofSetterFunc) xaccAccountSetFilter
},
{
ACCOUNT_SORT_ORDER_, QOF_TYPE_STRING,
(QofAccessFunc) xaccAccountGetSortOrder,
(QofSetterFunc) xaccAccountSetSortOrder
},
{
ACCOUNT_SORT_REVERSED_, QOF_TYPE_BOOLEAN,
(QofAccessFunc) xaccAccountGetSortReversed,
(QofSetterFunc) xaccAccountSetSortReversed
},
{
ACCOUNT_NOTES_, QOF_TYPE_STRING,
(QofAccessFunc) xaccAccountGetNotes,
(QofSetterFunc) xaccAccountSetNotes
},
{
ACCOUNT_PRESENT_, QOF_TYPE_NUMERIC,
(QofAccessFunc) xaccAccountGetPresentBalance, NULL
},
{
ACCOUNT_BALANCE_, QOF_TYPE_NUMERIC,
(QofAccessFunc) xaccAccountGetBalance, NULL
},
{
ACCOUNT_CLEARED_, QOF_TYPE_NUMERIC,
(QofAccessFunc) xaccAccountGetClearedBalance, NULL
},
{
ACCOUNT_RECONCILED_, QOF_TYPE_NUMERIC,
(QofAccessFunc) xaccAccountGetReconciledBalance, NULL
},
{
ACCOUNT_TYPE_, QOF_TYPE_STRING,
(QofAccessFunc) qofAccountGetTypeString,
(QofSetterFunc) qofAccountSetType
},
{
ACCOUNT_FUTURE_MINIMUM_, QOF_TYPE_NUMERIC,
(QofAccessFunc) xaccAccountGetProjectedMinimumBalance, NULL
},
{
ACCOUNT_TAX_RELATED, QOF_TYPE_BOOLEAN,
(QofAccessFunc) xaccAccountGetTaxRelated,
(QofSetterFunc) xaccAccountSetTaxRelated
},
{
ACCOUNT_SCU, QOF_TYPE_INT32,
(QofAccessFunc) xaccAccountGetCommoditySCU,
(QofSetterFunc) xaccAccountSetCommoditySCU
},
{
ACCOUNT_NSCU, QOF_TYPE_BOOLEAN,
(QofAccessFunc) xaccAccountGetNonStdSCU,
(QofSetterFunc) xaccAccountSetNonStdSCU
},
{
ACCOUNT_PARENT, GNC_ID_ACCOUNT,
(QofAccessFunc) gnc_account_get_parent,
(QofSetterFunc) qofAccountSetParent
},
{
QOF_PARAM_BOOK, QOF_ID_BOOK,
(QofAccessFunc) qof_instance_get_book, NULL
},
{
QOF_PARAM_GUID, QOF_TYPE_GUID,
(QofAccessFunc) qof_instance_get_guid, NULL
},
{ NULL },
};
qof_class_register (GNC_ID_ACCOUNT, (QofSortFunc) qof_xaccAccountOrder, params);
return qof_object_register (&account_object_def);
}
/* ======================= UNIT TESTING ACCESS =======================
* The following functions are for unit testing use only.
*/
static AccountPrivate*
utest_account_get_private (Account *acc)
{
return GET_PRIVATE (acc);
}
AccountTestFunctions*
_utest_account_fill_functions(void)
{
AccountTestFunctions* func = g_new(AccountTestFunctions, 1);
func->get_private = utest_account_get_private;
func->coll_get_root_account = gnc_coll_get_root_account;
func->xaccFreeAccountChildren = xaccFreeAccountChildren;
func->xaccFreeAccount = xaccFreeAccount;
func->qofAccountSetParent = qofAccountSetParent;
func->gnc_account_lookup_by_full_name_helper =
gnc_account_lookup_by_full_name_helper;
return func;
}
/* ======================= END OF FILE =========================== */