mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-20 11:48:30 -06:00
6204 lines
191 KiB
C++
6204 lines
191 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>
|
|
|
|
extern "C" {
|
|
#include "gnc-prefs.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"
|
|
#include "guid.hpp"
|
|
|
|
#include <numeric>
|
|
#include <map>
|
|
|
|
static QofLogModule log_module = GNC_MOD_ACCOUNT;
|
|
|
|
/* The Canonical Account Separator. Pre-Initialized. */
|
|
static gchar account_separator[8] = ".";
|
|
static gunichar account_uc_separator = ':';
|
|
|
|
static bool imap_convert_bayes_to_flat_run = false;
|
|
|
|
/* Predefined KVP paths */
|
|
static const std::string KEY_ASSOC_INCOME_ACCOUNT("ofx/associated-income-account");
|
|
static const std::string KEY_RECONCILE_INFO("reconcile-info");
|
|
static const std::string KEY_INCLUDE_CHILDREN("include-children");
|
|
static const std::string KEY_POSTPONE("postpone");
|
|
static const std::string KEY_LOT_MGMT("lot-mgmt");
|
|
static const std::string KEY_ONLINE_ID("online_id");
|
|
static const std::string AB_KEY("hbci");
|
|
static const std::string AB_ACCOUNT_ID("account-id");
|
|
static const std::string AB_ACCOUNT_UID("account-uid");
|
|
static const std::string AB_BANK_CODE("bank-code");
|
|
static const std::string AB_TRANS_RETRIEVAL("trans-retrieval");
|
|
|
|
static gnc_numeric GetBalanceAsOfDate (Account *acc, time64 date, gboolean ignclosing);
|
|
|
|
using FinalProbabilityVec=std::vector<std::pair<std::string, int32_t>>;
|
|
using ProbabilityVec=std::vector<std::pair<std::string, struct AccountProbability>>;
|
|
using FlatKvpEntry=std::pair<std::string, KvpValue*>;
|
|
|
|
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_NOCLOSING_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_AUTO_INTEREST,
|
|
PROP_FILTER, /* KVP */
|
|
PROP_SORT_ORDER, /* KVP */
|
|
PROP_SORT_REVERSED,
|
|
|
|
PROP_LOT_NEXT_ID, /* KVP */
|
|
PROP_ONLINE_ACCOUNT, /* KVP */
|
|
PROP_IS_OPENING_BALANCE, /* 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_NOCLOSING_BALANCE, /* Runtime Value */
|
|
PROP_START_CLEARED_BALANCE, /* Runtime Value */
|
|
PROP_START_RECONCILED_BALANCE, /* Runtime Value */
|
|
};
|
|
|
|
#define GET_PRIVATE(o) \
|
|
((AccountPrivate*)g_type_instance_get_private((GTypeInstance*)o, GNC_TYPE_ACCOUNT))
|
|
|
|
/* This map contains a set of strings representing the different column types. */
|
|
static const std::map<GNCAccountType, const char*> gnc_acct_debit_strs = {
|
|
{ ACCT_TYPE_NONE, N_("Funds In") },
|
|
{ ACCT_TYPE_BANK, N_("Deposit") },
|
|
{ ACCT_TYPE_CASH, N_("Receive") },
|
|
{ ACCT_TYPE_CREDIT, N_("Payment") },
|
|
{ ACCT_TYPE_ASSET, N_("Increase") },
|
|
{ ACCT_TYPE_LIABILITY, N_("Decrease") },
|
|
{ ACCT_TYPE_STOCK, N_("Buy") },
|
|
{ ACCT_TYPE_MUTUAL, N_("Buy") },
|
|
{ ACCT_TYPE_CURRENCY, N_("Buy") },
|
|
{ ACCT_TYPE_INCOME, N_("Charge") },
|
|
{ ACCT_TYPE_EXPENSE, N_("Expense") },
|
|
{ ACCT_TYPE_PAYABLE, N_("Payment") },
|
|
{ ACCT_TYPE_RECEIVABLE, N_("Invoice") },
|
|
{ ACCT_TYPE_TRADING, N_("Decrease") },
|
|
{ ACCT_TYPE_EQUITY, N_("Decrease") },
|
|
};
|
|
static const char* dflt_acct_debit_str = N_("Debit");
|
|
|
|
/* This map contains a set of strings representing the different column types. */
|
|
static const std::map<GNCAccountType, const char*> gnc_acct_credit_strs = {
|
|
{ ACCT_TYPE_NONE, N_("Funds Out") },
|
|
{ ACCT_TYPE_BANK, N_("Withdrawal") },
|
|
{ ACCT_TYPE_CASH, N_("Spend") },
|
|
{ ACCT_TYPE_CREDIT, N_("Charge") },
|
|
{ ACCT_TYPE_ASSET, N_("Decrease") },
|
|
{ ACCT_TYPE_LIABILITY, N_("Increase") },
|
|
{ ACCT_TYPE_STOCK, N_("Sell") },
|
|
{ ACCT_TYPE_MUTUAL, N_("Sell") },
|
|
{ ACCT_TYPE_CURRENCY, N_("Sell") },
|
|
{ ACCT_TYPE_INCOME, N_("Income") },
|
|
{ ACCT_TYPE_EXPENSE, N_("Rebate") },
|
|
{ ACCT_TYPE_PAYABLE, N_("Bill") },
|
|
{ ACCT_TYPE_RECEIVABLE, N_("Payment") },
|
|
{ ACCT_TYPE_TRADING, N_("Increase") },
|
|
{ ACCT_TYPE_EQUITY, N_("Increase") },
|
|
};
|
|
static const char* dflt_acct_credit_str = N_("Credit");
|
|
|
|
/********************************************************************\
|
|
* 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)
|
|
{
|
|
gchar *message = NULL;
|
|
|
|
if ( !invalid_account_names )
|
|
return NULL;
|
|
|
|
auto account_list {gnc_g_list_stringjoin (invalid_account_names, "\n")};
|
|
|
|
/* 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;
|
|
}
|
|
|
|
struct ViolationData
|
|
{
|
|
GList *list;
|
|
const gchar *separator;
|
|
};
|
|
|
|
static void
|
|
check_acct_name (Account *acct, gpointer user_data)
|
|
{
|
|
auto cb {static_cast<ViolationData*>(user_data)};
|
|
auto name {xaccAccountGetName (acct)};
|
|
if (g_strstr_len (name, -1, cb->separator))
|
|
cb->list = g_list_prepend (cb->list, g_strdup (name));
|
|
}
|
|
|
|
GList *gnc_account_list_name_violations (QofBook *book, const gchar *separator)
|
|
{
|
|
g_return_val_if_fail (separator != NULL, nullptr);
|
|
if (!book) return nullptr;
|
|
ViolationData cb = { nullptr, separator };
|
|
gnc_account_foreach_descendant (gnc_book_get_root_account (book),
|
|
(AccountCb)check_acct_name, &cb);
|
|
return cb.list;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static inline void mark_account (Account *acc);
|
|
void
|
|
mark_account (Account *acc)
|
|
{
|
|
qof_instance_set_dirty(&acc->inst);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static constexpr const char* is_unset {"unset"};
|
|
|
|
/* GObject Initialization */
|
|
G_DEFINE_TYPE_WITH_PRIVATE(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 = qof_string_cache_insert("");
|
|
priv->accountCode = qof_string_cache_insert("");
|
|
priv->description = qof_string_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->noclosing_balance = gnc_numeric_zero();
|
|
priv->cleared_balance = gnc_numeric_zero();
|
|
priv->reconciled_balance = gnc_numeric_zero();
|
|
priv->starting_balance = gnc_numeric_zero();
|
|
priv->starting_noclosing_balance = gnc_numeric_zero();
|
|
priv->starting_cleared_balance = gnc_numeric_zero();
|
|
priv->starting_reconciled_balance = gnc_numeric_zero();
|
|
priv->balance_dirty = FALSE;
|
|
|
|
priv->last_num = (char*) is_unset;
|
|
priv->tax_us_code = (char*) is_unset;
|
|
priv->tax_us_pns = (char*) is_unset;
|
|
priv->color = (char*) is_unset;
|
|
priv->sort_order = (char*) is_unset;
|
|
priv->notes = (char*) is_unset;
|
|
priv->filter = (char*) is_unset;
|
|
priv->equity_type = TriState::Unset;
|
|
priv->sort_reversed = TriState::Unset;
|
|
|
|
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_NOCLOSING_BALANCE:
|
|
g_value_set_boxed(value, &priv->starting_noclosing_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_NOCLOSING_BALANCE:
|
|
g_value_set_boxed(value, &priv->noclosing_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_AUTO_INTEREST:
|
|
g_value_set_boolean (value, xaccAccountGetAutoInterest (account));
|
|
break;
|
|
case PROP_IS_OPENING_BALANCE:
|
|
g_value_set_boolean(value, xaccAccountGetIsOpeningBalance(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));
|
|
break;
|
|
case PROP_LOT_NEXT_ID:
|
|
/* Pre-set the value in case the frame is empty */
|
|
g_value_set_int64 (value, 0);
|
|
qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {KEY_LOT_MGMT, "next-id"});
|
|
break;
|
|
case PROP_ONLINE_ACCOUNT:
|
|
qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {KEY_ONLINE_ID});
|
|
break;
|
|
case PROP_OFX_INCOME_ACCOUNT:
|
|
qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {KEY_ASSOC_INCOME_ACCOUNT});
|
|
break;
|
|
case PROP_AB_ACCOUNT_ID:
|
|
qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_ACCOUNT_ID});
|
|
break;
|
|
case PROP_AB_ACCOUNT_UID:
|
|
qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_ACCOUNT_UID});
|
|
break;
|
|
case PROP_AB_BANK_CODE:
|
|
qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_BANK_CODE});
|
|
break;
|
|
case PROP_AB_TRANS_RETRIEVAL:
|
|
qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_TRANS_RETRIEVAL});
|
|
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;
|
|
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, static_cast<GNCAccountType>(g_value_get_int(value)));
|
|
break;
|
|
case PROP_COMMODITY:
|
|
xaccAccountSetCommodity(account, static_cast<gnc_commodity*>(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 = static_cast<gnc_numeric*>(g_value_get_boxed(value));
|
|
gnc_account_set_start_balance(account, *number);
|
|
break;
|
|
case PROP_START_CLEARED_BALANCE:
|
|
number = static_cast<gnc_numeric*>(g_value_get_boxed(value));
|
|
gnc_account_set_start_cleared_balance(account, *number);
|
|
break;
|
|
case PROP_START_RECONCILED_BALANCE:
|
|
number = static_cast<gnc_numeric*>(g_value_get_boxed(value));
|
|
gnc_account_set_start_reconciled_balance(account, *number);
|
|
break;
|
|
case PROP_POLICY:
|
|
gnc_account_set_policy(account, static_cast<GNCPolicy*>(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_AUTO_INTEREST:
|
|
xaccAccountSetAutoInterest (account, g_value_get_boolean (value));
|
|
break;
|
|
case PROP_IS_OPENING_BALANCE:
|
|
xaccAccountSetIsOpeningBalance (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));
|
|
break;
|
|
case PROP_LOT_NEXT_ID:
|
|
qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {KEY_LOT_MGMT, "next-id"});
|
|
break;
|
|
case PROP_ONLINE_ACCOUNT:
|
|
qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {KEY_ONLINE_ID});
|
|
break;
|
|
case PROP_OFX_INCOME_ACCOUNT:
|
|
qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {KEY_ASSOC_INCOME_ACCOUNT});
|
|
break;
|
|
case PROP_AB_ACCOUNT_ID:
|
|
qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_ACCOUNT_ID});
|
|
break;
|
|
case PROP_AB_ACCOUNT_UID:
|
|
qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_ACCOUNT_UID});
|
|
break;
|
|
case PROP_AB_BANK_CODE:
|
|
qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_BANK_CODE});
|
|
break;
|
|
case PROP_AB_TRANS_RETRIEVAL:
|
|
qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_TRANS_RETRIEVAL});
|
|
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_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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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 hundredths. E.G."
|
|
"1 USD can be divided into 100 cents.",
|
|
0,
|
|
G_MAXINT32,
|
|
GNC_COMMODITY_MAX_FRACTION,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(G_PARAM_READWRITE)));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class,
|
|
PROP_START_NOCLOSING_BALANCE,
|
|
g_param_spec_boxed("start-noclosing-balance",
|
|
"Starting No-closing Balance",
|
|
"The starting balance for the account, ignoring closing."
|
|
"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 noclosing "
|
|
"balance' will represent the summation of the "
|
|
"splits up to that date, ignoring closing splits.",
|
|
GNC_TYPE_NUMERIC,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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_NOCLOSING_BALANCE,
|
|
g_param_spec_boxed("end-noclosing-balance",
|
|
"Ending Account Noclosing Balance",
|
|
"This is the current ending no-closing 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_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,
|
|
static_cast<GParamFlags>(G_PARAM_READABLE)));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class,
|
|
PROP_POLICY,
|
|
g_param_spec_pointer ("policy",
|
|
"Policy",
|
|
"The account lots policy.",
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(G_PARAM_READWRITE)));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class,
|
|
PROP_IS_OPENING_BALANCE,
|
|
g_param_spec_boolean ("opening-balance",
|
|
"Opening Balance",
|
|
"Whether the account holds opening balances",
|
|
FALSE,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(G_PARAM_READWRITE)));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class,
|
|
PROP_AUTO_INTEREST,
|
|
g_param_spec_boolean ("auto-interest-transfer",
|
|
"Auto Interest",
|
|
"Whether an interest transfer should be automatically "
|
|
"added before reconcile.",
|
|
FALSE,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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,
|
|
static_cast<GParamFlags>(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_TIME64,
|
|
static_cast<GParamFlags>(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)
|
|
{
|
|
if (!account) return NULL;
|
|
return qof_instance_get_book(QOF_INSTANCE(account));
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static Account *
|
|
gnc_coll_get_root_account (QofCollection *col)
|
|
{
|
|
if (!col) return NULL;
|
|
return static_cast<Account*>(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 && !qof_book_shutting_down(book))
|
|
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 = static_cast<Account*>(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;
|
|
rpriv->accountName = qof_string_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 = static_cast<Account*>(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 = qof_string_cache_replace(priv->accountName, from_priv->accountName);
|
|
priv->accountCode = qof_string_cache_replace(priv->accountCode, from_priv->accountCode);
|
|
priv->description = qof_string_cache_replace(priv->description, 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 = static_cast<GNCLot*>(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);
|
|
*/
|
|
}
|
|
|
|
qof_string_cache_remove(priv->accountName);
|
|
qof_string_cache_remove(priv->accountCode);
|
|
qof_string_cache_remove(priv->description);
|
|
priv->accountName = priv->accountCode = priv->description = nullptr;
|
|
|
|
if (priv->last_num != is_unset)
|
|
g_free (priv->last_num);
|
|
if (priv->tax_us_code != is_unset)
|
|
g_free (priv->tax_us_code);
|
|
if (priv->tax_us_pns != is_unset)
|
|
g_free (priv->tax_us_pns);
|
|
if (priv->color != is_unset)
|
|
g_free (priv->color);
|
|
if (priv->sort_order != is_unset)
|
|
g_free (priv->sort_order);
|
|
if (priv->notes != is_unset)
|
|
g_free (priv->notes);
|
|
if (priv->filter != is_unset)
|
|
g_free (priv->filter);
|
|
|
|
/* zero out values, just in case stray
|
|
* pointers are pointing here. */
|
|
|
|
priv->last_num = nullptr;
|
|
priv->tax_us_code = nullptr;
|
|
priv->tax_us_pns = nullptr;
|
|
priv->color == nullptr;
|
|
priv->sort_order == nullptr;
|
|
priv->notes == nullptr;
|
|
priv->filter == nullptr;
|
|
|
|
priv->parent = nullptr;
|
|
priv->children = nullptr;
|
|
|
|
priv->balance = gnc_numeric_zero();
|
|
priv->noclosing_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, static_cast<Account*>(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 = static_cast<Split *>(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 = static_cast<GNCLot*>(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 = static_cast<Account*>(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 = static_cast<Account*>(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_noclosing_balance,
|
|
priv_ab->starting_noclosing_balance))
|
|
{
|
|
char *str_a;
|
|
char *str_b;
|
|
|
|
str_a = gnc_numeric_to_string(priv_aa->starting_noclosing_balance);
|
|
str_b = gnc_numeric_to_string(priv_ab->starting_noclosing_balance);
|
|
|
|
PWARN ("starting noclosing 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->noclosing_balance, priv_ab->noclosing_balance))
|
|
{
|
|
char *str_a;
|
|
char *str_b;
|
|
|
|
str_a = gnc_numeric_to_string(priv_aa->noclosing_balance);
|
|
str_b = gnc_numeric_to_string(priv_ab->noclosing_balance);
|
|
|
|
PWARN ("noclosing 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;
|
|
}
|
|
|
|
void gnc_account_set_defer_bal_computation (Account *acc, gboolean defer)
|
|
{
|
|
AccountPrivate *priv;
|
|
|
|
g_return_if_fail (GNC_IS_ACCOUNT (acc));
|
|
|
|
if (qof_instance_get_destroying (acc))
|
|
return;
|
|
|
|
priv = GET_PRIVATE (acc);
|
|
priv->defer_bal_computation = defer;
|
|
}
|
|
|
|
gboolean gnc_account_get_defer_bal_computation (Account *acc)
|
|
{
|
|
AccountPrivate *priv;
|
|
if (!acc)
|
|
return false;
|
|
priv = GET_PRIVATE (acc);
|
|
return priv->defer_bal_computation;
|
|
}
|
|
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
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(static_cast<Account*>(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 noclosing_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 || priv->defer_bal_computation) return;
|
|
if (qof_instance_get_destroying(acc)) return;
|
|
if (qof_book_shutting_down(qof_instance_get_book(acc))) return;
|
|
|
|
balance = priv->starting_balance;
|
|
noclosing_balance = priv->starting_noclosing_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);
|
|
}
|
|
|
|
if (!(xaccTransGetIsClosingTxn (split->parent)))
|
|
noclosing_balance = gnc_numeric_add_fixed(noclosing_balance, amt);
|
|
|
|
split->balance = balance;
|
|
split->noclosing_balance = noclosing_balance;
|
|
split->cleared_balance = cleared_balance;
|
|
split->reconciled_balance = reconciled_balance;
|
|
|
|
}
|
|
|
|
priv->balance = balance;
|
|
priv->noclosing_balance = noclosing_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;
|
|
const 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;
|
|
|
|
/* 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);
|
|
priv->accountName = qof_string_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);
|
|
priv->accountCode = qof_string_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);
|
|
priv->description = qof_string_cache_replace(priv->description, str ? str : "");
|
|
mark_account (acc);
|
|
xaccAccountCommitEdit(acc);
|
|
}
|
|
|
|
static char*
|
|
stripdup_or_null (const char *value)
|
|
{
|
|
if (value)
|
|
{
|
|
auto temp = g_strstrip (g_strdup (value));
|
|
if (*temp)
|
|
return temp;
|
|
g_free (temp);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// note the *value argument is expected to be either a strstripped
|
|
// char* or nullptr, as returned by stripdup_or_null above.
|
|
static void
|
|
set_kvp_string_path (Account *acc, std::vector<std::string> const & path,
|
|
const char *value)
|
|
{
|
|
g_return_if_fail(GNC_IS_ACCOUNT(acc));
|
|
|
|
xaccAccountBeginEdit(acc);
|
|
if (value)
|
|
{
|
|
GValue v = G_VALUE_INIT;
|
|
g_value_init (&v, G_TYPE_STRING);
|
|
g_value_set_string (&v, value);
|
|
qof_instance_set_path_kvp (QOF_INSTANCE (acc), &v, path);
|
|
g_value_unset (&v);
|
|
}
|
|
else
|
|
{
|
|
qof_instance_set_path_kvp (QOF_INSTANCE (acc), NULL, path);
|
|
}
|
|
mark_account (acc);
|
|
xaccAccountCommitEdit(acc);
|
|
}
|
|
|
|
static void
|
|
set_kvp_string_tag (Account *acc, const char *tag, const char *value)
|
|
{
|
|
set_kvp_string_path (acc, {tag}, value);
|
|
}
|
|
|
|
static char*
|
|
get_kvp_string_path (const Account *acc, std::vector<std::string> const & path)
|
|
{
|
|
GValue v = G_VALUE_INIT;
|
|
if (acc == NULL) return NULL; // how to check path is valid??
|
|
qof_instance_get_path_kvp (QOF_INSTANCE (acc), &v, path);
|
|
auto retval = G_VALUE_HOLDS_STRING (&v) ? g_value_dup_string (&v) : NULL;
|
|
g_value_unset (&v);
|
|
return retval;
|
|
}
|
|
|
|
static char*
|
|
get_kvp_string_tag (const Account *acc, const char *tag)
|
|
{
|
|
return get_kvp_string_path (acc, {tag});
|
|
}
|
|
|
|
void
|
|
xaccAccountSetColor (Account *acc, const char *str)
|
|
{
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->color != is_unset)
|
|
g_free (priv->color);
|
|
priv->color = stripdup_or_null (str);
|
|
set_kvp_string_tag (acc, "color", priv->color);
|
|
}
|
|
|
|
void
|
|
xaccAccountSetFilter (Account *acc, const char *str)
|
|
{
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->filter != is_unset)
|
|
g_free (priv->filter);
|
|
priv->filter = stripdup_or_null (str);
|
|
set_kvp_string_tag (acc, "filter", priv->filter);
|
|
}
|
|
|
|
void
|
|
xaccAccountSetSortOrder (Account *acc, const char *str)
|
|
{
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->sort_order != is_unset)
|
|
g_free (priv->sort_order);
|
|
priv->sort_order = stripdup_or_null (str);
|
|
set_kvp_string_tag (acc, "sort-order", priv->sort_order);
|
|
}
|
|
|
|
void
|
|
xaccAccountSetSortReversed (Account *acc, gboolean sortreversed)
|
|
{
|
|
auto priv = GET_PRIVATE (acc);
|
|
priv->sort_reversed = sortreversed ? TriState::True : TriState::False;
|
|
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)
|
|
{
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->notes != is_unset)
|
|
g_free (priv->notes);
|
|
priv->notes = stripdup_or_null (str);
|
|
set_kvp_string_tag (acc, "notes", priv->notes);
|
|
}
|
|
|
|
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_path_kvp (QOF_INSTANCE (acc), &v, {"old-currency"});
|
|
mark_account (acc);
|
|
xaccAccountCommitEdit(acc);
|
|
g_value_unset (&v);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static void
|
|
account_foreach_descendant (const Account *acc, AccountCb thunk,
|
|
void* user_data, bool sort)
|
|
{
|
|
GList *children;
|
|
|
|
g_return_if_fail (GNC_IS_ACCOUNT(acc));
|
|
g_return_if_fail (thunk);
|
|
|
|
auto priv{GET_PRIVATE(acc)};
|
|
if (sort)
|
|
{
|
|
children = g_list_copy (priv->children);
|
|
children = g_list_sort (children, (GCompareFunc)xaccAccountOrder);
|
|
}
|
|
else
|
|
children = priv->children;
|
|
|
|
for (auto node = children; node; node = node->next)
|
|
{
|
|
auto child = static_cast<Account*>(node->data);
|
|
thunk (child, user_data);
|
|
account_foreach_descendant (child, thunk, user_data, sort);
|
|
}
|
|
|
|
if (sort)
|
|
g_list_free (children);
|
|
}
|
|
|
|
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 static_cast<Account*>(g_list_nth_data(GET_PRIVATE(parent)->children, num));
|
|
}
|
|
|
|
static void
|
|
count_acct (Account *account, gpointer user_data)
|
|
{
|
|
auto count {static_cast<int*>(user_data)};
|
|
++*count;
|
|
}
|
|
|
|
gint
|
|
gnc_account_n_descendants (const Account *account)
|
|
{
|
|
int count {0};
|
|
account_foreach_descendant (account, count_acct, &count, FALSE);
|
|
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(static_cast<Account const *>(node->data));
|
|
depth = MAX(depth, child_depth);
|
|
}
|
|
return depth + 1;
|
|
}
|
|
|
|
static void
|
|
collect_acct (Account *account, gpointer user_data)
|
|
{
|
|
auto listptr{static_cast<GList**>(user_data)};
|
|
*listptr = g_list_prepend (*listptr, account);
|
|
}
|
|
|
|
GList *
|
|
gnc_account_get_descendants (const Account *account)
|
|
{
|
|
GList* list = nullptr;
|
|
account_foreach_descendant (account, collect_acct, &list, FALSE);
|
|
return g_list_reverse (list);
|
|
}
|
|
|
|
GList *
|
|
gnc_account_get_descendants_sorted (const Account *account)
|
|
{
|
|
GList* list = nullptr;
|
|
account_foreach_descendant (account, collect_acct, &list, TRUE);
|
|
return g_list_reverse (list);
|
|
}
|
|
|
|
static gpointer
|
|
is_acct_name (Account *account, gpointer user_data)
|
|
{
|
|
auto name {static_cast<gchar*>(user_data)};
|
|
return (g_strcmp0 (name, xaccAccountGetName (account)) ? nullptr : account);
|
|
}
|
|
|
|
Account *
|
|
gnc_account_lookup_by_name (const Account *parent, const char * name)
|
|
{
|
|
return (Account*)gnc_account_foreach_descendant_until (parent, is_acct_name, (char*)name);
|
|
}
|
|
|
|
static gpointer
|
|
is_acct_code (Account *account, gpointer user_data)
|
|
{
|
|
auto name {static_cast<gchar*>(user_data)};
|
|
return (g_strcmp0 (name, xaccAccountGetCode (account)) ? nullptr : account);
|
|
}
|
|
|
|
Account *
|
|
gnc_account_lookup_by_code (const Account *parent, const char * code)
|
|
{
|
|
return (Account*)gnc_account_foreach_descendant_until (parent, is_acct_code, (char*)code);
|
|
}
|
|
|
|
static gpointer
|
|
is_opening_balance_account (Account* account, gpointer data)
|
|
{
|
|
gnc_commodity* commodity = GNC_COMMODITY(data);
|
|
if (xaccAccountGetIsOpeningBalance(account) && gnc_commodity_equiv(commodity, xaccAccountGetCommodity(account)))
|
|
return account;
|
|
return nullptr;
|
|
}
|
|
|
|
Account*
|
|
gnc_account_lookup_by_opening_balance (Account* account, gnc_commodity* commodity)
|
|
{
|
|
return (Account *)gnc_account_foreach_descendant_until (account, is_opening_balance_account, commodity);
|
|
}
|
|
|
|
/********************************************************************\
|
|
* 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 = static_cast<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;
|
|
}
|
|
|
|
GList*
|
|
gnc_account_lookup_by_type_and_commodity (Account* root,
|
|
const char* name,
|
|
GNCAccountType acctype,
|
|
gnc_commodity* commodity)
|
|
{
|
|
GList *retval{};
|
|
auto rpriv{GET_PRIVATE(root)};
|
|
for (auto node = rpriv->children; node; node = node->next)
|
|
{
|
|
auto account{static_cast<Account*>(node->data)};
|
|
if (xaccAccountGetType (account) == acctype)
|
|
{
|
|
if (commodity &&
|
|
!gnc_commodity_equiv(xaccAccountGetCommodity (account),
|
|
commodity))
|
|
continue;
|
|
|
|
if (name && strcmp(name, xaccAccountGetName(account)))
|
|
continue;
|
|
|
|
retval = g_list_prepend(retval, account);
|
|
}
|
|
}
|
|
|
|
if (!retval) // Recurse through the children
|
|
for (auto node = rpriv->children; node; node = node->next)
|
|
{
|
|
auto account{static_cast<Account*>(node->data)};
|
|
auto result = gnc_account_lookup_by_type_and_commodity(account,
|
|
name,
|
|
acctype,
|
|
commodity);
|
|
if (result)
|
|
retval = g_list_concat(result, retval);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
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 (static_cast<Account*>(node->data), user_data);
|
|
}
|
|
}
|
|
|
|
void
|
|
gnc_account_foreach_descendant (const Account *acc,
|
|
AccountCb thunk,
|
|
gpointer user_data)
|
|
{
|
|
account_foreach_descendant (acc, thunk, user_data, FALSE);
|
|
}
|
|
|
|
gpointer
|
|
gnc_account_foreach_descendant_until (const Account *acc,
|
|
AccountCb2 thunk,
|
|
gpointer user_data)
|
|
{
|
|
gpointer result {nullptr};
|
|
|
|
g_return_val_if_fail (GNC_IS_ACCOUNT(acc), nullptr);
|
|
g_return_val_if_fail (thunk, nullptr);
|
|
|
|
auto priv{GET_PRIVATE(acc)};
|
|
|
|
for (auto node = priv->children; node; node = node->next)
|
|
{
|
|
auto child = static_cast<Account*>(node->data);
|
|
result = thunk (child, user_data);
|
|
if (result) break;
|
|
|
|
result = gnc_account_foreach_descendant_until (child, thunk, user_data);
|
|
if (result) break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
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;
|
|
const 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 = (const gchar **)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, (gchar **)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);
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->color == is_unset)
|
|
priv->color = get_kvp_string_tag (acc, "color");
|
|
return priv->color;
|
|
}
|
|
|
|
const char *
|
|
xaccAccountGetFilter (const Account *acc)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->filter == is_unset)
|
|
priv->filter = get_kvp_string_tag (acc, "filter");
|
|
return priv->filter;
|
|
}
|
|
|
|
const char *
|
|
xaccAccountGetSortOrder (const Account *acc)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->sort_order == is_unset)
|
|
priv->sort_order = get_kvp_string_tag (acc, "sort-order");
|
|
return priv->sort_order;
|
|
}
|
|
|
|
gboolean
|
|
xaccAccountGetSortReversed (const Account *acc)
|
|
{
|
|
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->sort_reversed == TriState::Unset)
|
|
{
|
|
auto sort_reversed = get_kvp_string_tag (acc, "sort-reversed");
|
|
priv->sort_reversed = g_strcmp0 (sort_reversed, "true") ?
|
|
TriState::False : TriState::True;
|
|
g_free (sort_reversed);
|
|
}
|
|
return (priv->sort_reversed == TriState::True);
|
|
}
|
|
|
|
const char *
|
|
xaccAccountGetNotes (const Account *acc)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->notes == is_unset)
|
|
priv->notes = get_kvp_string_tag (acc, "notes");
|
|
return priv->notes;
|
|
}
|
|
|
|
gnc_commodity *
|
|
DxaccAccountGetCurrency (const Account *acc)
|
|
{
|
|
GValue v = G_VALUE_INIT;
|
|
const char *s = NULL;
|
|
gnc_commodity_table *table;
|
|
gnc_commodity *retval = NULL;
|
|
|
|
if (!acc) return NULL;
|
|
qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v, {"old-currency"});
|
|
if (G_VALUE_HOLDS_STRING (&v))
|
|
s = g_value_get_string (&v);
|
|
if (s)
|
|
{
|
|
table = gnc_commodity_table_get_table (qof_instance_get_book(acc));
|
|
retval = gnc_commodity_table_lookup_unique (table, s);
|
|
}
|
|
g_value_unset (&v);
|
|
|
|
return retval;
|
|
}
|
|
|
|
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_return_val_if_fail (account, NULL);
|
|
|
|
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 = static_cast<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;
|
|
}
|
|
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static gnc_numeric
|
|
GetBalanceAsOfDate (Account *acc, time64 date, gboolean ignclosing)
|
|
{
|
|
/* 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.
|
|
*/
|
|
Split *latest = nullptr;
|
|
|
|
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 */
|
|
|
|
for (GList *lp = GET_PRIVATE(acc)->splits; lp; lp = lp->next)
|
|
{
|
|
if (xaccTransGetDate (xaccSplitGetParent ((Split *)lp->data)) >= date)
|
|
break;
|
|
latest = (Split *)lp->data;
|
|
}
|
|
|
|
if (!latest)
|
|
return gnc_numeric_zero();
|
|
|
|
if (ignclosing)
|
|
return xaccSplitGetNoclosingBalance (latest);
|
|
else
|
|
return xaccSplitGetBalance (latest);
|
|
}
|
|
|
|
gnc_numeric
|
|
xaccAccountGetBalanceAsOfDate (Account *acc, time64 date)
|
|
{
|
|
return GetBalanceAsOfDate (acc, date, FALSE);
|
|
}
|
|
|
|
static gnc_numeric
|
|
xaccAccountGetNoclosingBalanceAsOfDate (Account *acc, time64 date)
|
|
{
|
|
return GetBalanceAsOfDate (acc, date, TRUE);
|
|
}
|
|
|
|
gnc_numeric
|
|
xaccAccountGetReconciledBalanceAsOfDate (Account *acc, time64 date)
|
|
{
|
|
gnc_numeric balance = gnc_numeric_zero();
|
|
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
|
|
|
|
for (GList *node = GET_PRIVATE(acc)->splits; node; node = node->next)
|
|
{
|
|
Split *split = (Split*) node->data;
|
|
if ((xaccSplitGetReconcile (split) == YREC) &&
|
|
(xaccSplitGetDateReconciled (split) <= date))
|
|
balance = gnc_numeric_add_fixed (balance, xaccSplitGetAmount (split));
|
|
};
|
|
|
|
return balance;
|
|
}
|
|
|
|
/*
|
|
* Originally gsr_account_present_balance in gnc-split-reg.c
|
|
*/
|
|
gnc_numeric
|
|
xaccAccountGetPresentBalance (const Account *acc)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
|
|
|
|
return xaccAccountGetBalanceAsOfDate (GNC_ACCOUNT (acc),
|
|
gnc_time64_get_today_end ());
|
|
}
|
|
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
/* 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,
|
|
const gnc_commodity *balance_currency,
|
|
const gnc_commodity *new_currency,
|
|
time64 date)
|
|
{
|
|
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_nearest_before_price_t64 (
|
|
pdb, balance, balance_currency, new_currency, date);
|
|
|
|
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 xaccAccountConvertBalanceToCurrencyAsOfDate(
|
|
acc, fn(acc, date), priv->commodity, report_commodity, date);
|
|
}
|
|
|
|
/*
|
|
* 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 = static_cast<CurrencyBalance*>(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 = static_cast<CurrencyBalance*>(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,
|
|
const 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 xaccAccountGetXxxBalanceAsOfDateInCurrencyRecursive (
|
|
(Account*)acc, gnc_time64_get_today_end (), xaccAccountGetBalanceAsOfDate,
|
|
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
|
|
xaccAccountGetNoclosingBalanceAsOfDateInCurrency(
|
|
Account *acc, time64 date, gnc_commodity *report_commodity,
|
|
gboolean include_children)
|
|
{
|
|
return xaccAccountGetXxxBalanceAsOfDateInCurrencyRecursive
|
|
(acc, date, xaccAccountGetNoclosingBalanceAsOfDate,
|
|
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);
|
|
}
|
|
|
|
gnc_numeric
|
|
xaccAccountGetNoclosingBalanceChangeForPeriod (Account *acc, time64 t1,
|
|
time64 t2, gboolean recurse)
|
|
{
|
|
gnc_numeric b1, b2;
|
|
|
|
b1 = xaccAccountGetNoclosingBalanceAsOfDateInCurrency(acc, t1, NULL, recurse);
|
|
b2 = xaccAccountGetNoclosingBalanceAsOfDateInCurrency(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;
|
|
}
|
|
|
|
|
|
gboolean gnc_account_and_descendants_empty (Account *acc)
|
|
{
|
|
g_return_val_if_fail (GNC_IS_ACCOUNT (acc), FALSE);
|
|
if (xaccAccountGetSplitList (acc)) return FALSE;
|
|
auto empty = TRUE;
|
|
auto *children = gnc_account_get_children (acc);
|
|
for (auto *n = children; n && empty; n = n->next)
|
|
{
|
|
empty = gnc_account_and_descendants_empty ((Account*)n->data);
|
|
}
|
|
g_list_free (children);
|
|
return empty;
|
|
}
|
|
|
|
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 = static_cast<GNCLot*>(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 */
|
|
retval = g_list_prepend (retval, lot);
|
|
}
|
|
|
|
if (sort_func)
|
|
retval = g_list_sort (retval, sort_func);
|
|
|
|
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, std::vector<std::string> const & path, 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_path_kvp (QOF_INSTANCE (acc), &v, path);
|
|
mark_account (acc);
|
|
xaccAccountCommitEdit (acc);
|
|
g_value_unset (&v);
|
|
}
|
|
|
|
static gboolean
|
|
boolean_from_key (const Account *acc, std::vector<std::string> const & path)
|
|
{
|
|
GValue v = G_VALUE_INIT;
|
|
gboolean retval = FALSE;
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
|
|
qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v, path);
|
|
if (G_VALUE_HOLDS_INT64 (&v))
|
|
retval = (g_value_get_int64 (&v) != 0);
|
|
if (G_VALUE_HOLDS_BOOLEAN (&v))
|
|
retval = (g_value_get_boolean (&v));
|
|
if (G_VALUE_HOLDS_STRING (&v))
|
|
retval = !strcmp (g_value_get_string (&v), "true");
|
|
g_value_unset (&v);
|
|
return retval;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
/* 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)
|
|
{
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->tax_us_code == is_unset)
|
|
priv->tax_us_code = get_kvp_string_path (acc, {"tax-US", "code"});
|
|
return priv->tax_us_code;
|
|
}
|
|
|
|
void
|
|
xaccAccountSetTaxUSCode (Account *acc, const char *code)
|
|
{
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->tax_us_code != is_unset)
|
|
g_free (priv->tax_us_code);
|
|
priv->tax_us_code = g_strdup (code);
|
|
set_kvp_string_path (acc, {"tax-US", "code"}, priv->tax_us_code);
|
|
}
|
|
|
|
const char *
|
|
xaccAccountGetTaxUSPayerNameSource (const Account *acc)
|
|
{
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->tax_us_pns == is_unset)
|
|
priv->tax_us_pns = get_kvp_string_path (acc, {"tax-US", "payer-name-source"});
|
|
return priv->tax_us_pns;
|
|
}
|
|
|
|
void
|
|
xaccAccountSetTaxUSPayerNameSource (Account *acc, const char *source)
|
|
{
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->tax_us_pns != is_unset)
|
|
g_free (priv->tax_us_pns);
|
|
priv->tax_us_pns = g_strdup (source);
|
|
set_kvp_string_path (acc, {"tax-US", "payer-name-source"}, priv->tax_us_pns);
|
|
}
|
|
|
|
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_path_kvp (QOF_INSTANCE(acc), &v, {"tax-US", "copy-number"});
|
|
if (G_VALUE_HOLDS_INT64 (&v))
|
|
copy_number = g_value_get_int64 (&v);
|
|
|
|
g_value_unset (&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_path_kvp (QOF_INSTANCE (acc), &v, {"tax-US", "copy-number"});
|
|
g_value_unset (&v);
|
|
}
|
|
else
|
|
{
|
|
qof_instance_set_path_kvp (QOF_INSTANCE (acc), nullptr, {"tax-US", "copy-number"});
|
|
}
|
|
mark_account (acc);
|
|
xaccAccountCommitEdit (acc);
|
|
}
|
|
|
|
/*********************************************************************\
|
|
\ ********************************************************************/
|
|
|
|
|
|
const char *gnc_account_get_debit_string (GNCAccountType acct_type)
|
|
{
|
|
if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_ACCOUNTING_LABELS))
|
|
return _(dflt_acct_debit_str);
|
|
|
|
auto result = gnc_acct_debit_strs.find(acct_type);
|
|
if (result != gnc_acct_debit_strs.end())
|
|
return _(result->second);
|
|
else
|
|
return _(dflt_acct_debit_str);
|
|
}
|
|
|
|
const char *gnc_account_get_credit_string (GNCAccountType acct_type)
|
|
{
|
|
if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_ACCOUNTING_LABELS))
|
|
return _(dflt_acct_credit_str);
|
|
|
|
auto result = gnc_acct_credit_strs.find(acct_type);
|
|
if (result != gnc_acct_credit_strs.end())
|
|
return _(result->second);
|
|
else
|
|
return _(dflt_acct_credit_str);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
gboolean
|
|
xaccAccountGetPlaceholder (const Account *acc)
|
|
{
|
|
return boolean_from_key(acc, {"placeholder"});
|
|
}
|
|
|
|
void
|
|
xaccAccountSetPlaceholder (Account *acc, gboolean val)
|
|
{
|
|
set_boolean_key(acc, {"placeholder"}, val);
|
|
}
|
|
|
|
gboolean
|
|
xaccAccountGetIsOpeningBalance (const Account *acc)
|
|
{
|
|
if (GET_PRIVATE(acc)->type != ACCT_TYPE_EQUITY)
|
|
return false;
|
|
auto priv = GET_PRIVATE(acc);
|
|
if (priv->equity_type == TriState::Unset)
|
|
{
|
|
auto equity_type = get_kvp_string_tag (acc, "equity-type");
|
|
priv->equity_type = g_strcmp0 (equity_type, "opening-balance") ?
|
|
TriState::False : TriState::True;
|
|
g_free (equity_type);
|
|
}
|
|
return (priv->equity_type == TriState::True);
|
|
}
|
|
|
|
void
|
|
xaccAccountSetIsOpeningBalance (Account *acc, gboolean val)
|
|
{
|
|
if (GET_PRIVATE(acc)->type != ACCT_TYPE_EQUITY)
|
|
return;
|
|
auto priv = GET_PRIVATE (acc);
|
|
priv->equity_type = val ? TriState::True : TriState::False;
|
|
set_kvp_string_tag(acc, "equity-type", val ? "opening-balance" : nullptr);
|
|
}
|
|
|
|
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
|
|
xaccAccountGetAutoInterest (const Account *acc)
|
|
{
|
|
return boolean_from_key (acc, {KEY_RECONCILE_INFO, "auto-interest-transfer"});
|
|
}
|
|
|
|
void
|
|
xaccAccountSetAutoInterest (Account *acc, gboolean val)
|
|
{
|
|
set_boolean_key (acc, {KEY_RECONCILE_INFO, "auto-interest-transfer"}, val);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
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 const *
|
|
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)
|
|
{
|
|
/* ACCT_TYPE_NONE isn't compatible with anything, even ACCT_TYPE_NONE. */
|
|
if (parent_type == ACCT_TYPE_NONE || child_type == ACCT_TYPE_NONE)
|
|
return FALSE;
|
|
|
|
/* ACCT_TYPE_ROOT can't have a parent account, and asking will raise
|
|
* an error. */
|
|
if (child_type == ACCT_TYPE_ROOT)
|
|
return FALSE;
|
|
|
|
return ((xaccParentAccountTypesCompatibleWith (child_type) &
|
|
(1 << parent_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));
|
|
}
|
|
}
|
|
|
|
GNCAccountType
|
|
xaccAccountTypeGetFundamental (GNCAccountType t)
|
|
{
|
|
switch (t)
|
|
{
|
|
case ACCT_TYPE_BANK:
|
|
case ACCT_TYPE_STOCK:
|
|
case ACCT_TYPE_MONEYMRKT:
|
|
case ACCT_TYPE_CHECKING:
|
|
case ACCT_TYPE_SAVINGS:
|
|
case ACCT_TYPE_MUTUAL:
|
|
case ACCT_TYPE_CURRENCY:
|
|
case ACCT_TYPE_CASH:
|
|
case ACCT_TYPE_ASSET:
|
|
case ACCT_TYPE_RECEIVABLE:
|
|
return ACCT_TYPE_ASSET;
|
|
case ACCT_TYPE_CREDIT:
|
|
case ACCT_TYPE_LIABILITY:
|
|
case ACCT_TYPE_PAYABLE:
|
|
case ACCT_TYPE_CREDITLINE:
|
|
return ACCT_TYPE_LIABILITY;
|
|
case ACCT_TYPE_INCOME:
|
|
return ACCT_TYPE_INCOME;
|
|
case ACCT_TYPE_EXPENSE:
|
|
return ACCT_TYPE_EXPENSE;
|
|
case ACCT_TYPE_EQUITY:
|
|
return ACCT_TYPE_EQUITY;
|
|
case ACCT_TYPE_TRADING:
|
|
default:
|
|
return ACCT_TYPE_NONE;
|
|
}
|
|
}
|
|
|
|
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;
|
|
gboolean retval = FALSE;
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
|
|
qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v, {KEY_RECONCILE_INFO, "last-date"});
|
|
if (G_VALUE_HOLDS_INT64 (&v))
|
|
date = g_value_get_int64 (&v);
|
|
|
|
g_value_unset (&v);
|
|
if (date)
|
|
{
|
|
if (last_date)
|
|
*last_date = date;
|
|
retval = TRUE;
|
|
}
|
|
g_value_unset (&v);
|
|
return retval;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
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_path_kvp (QOF_INSTANCE (acc), &v, {KEY_RECONCILE_INFO, "last-date"});
|
|
mark_account (acc);
|
|
xaccAccountCommitEdit (acc);
|
|
g_value_unset (&v);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
gboolean
|
|
xaccAccountGetReconcileLastInterval (const Account *acc,
|
|
int *months, int *days)
|
|
{
|
|
GValue v1 = G_VALUE_INIT, v2 = G_VALUE_INIT;
|
|
int64_t m = 0, d = 0;
|
|
gboolean retval = FALSE;
|
|
|
|
if (!acc) return FALSE;
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
|
|
qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v1,
|
|
{KEY_RECONCILE_INFO, "last-interval", "months"});
|
|
qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v2,
|
|
{KEY_RECONCILE_INFO, "last-interval", "days"});
|
|
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;
|
|
retval = TRUE;
|
|
}
|
|
g_value_unset (&v1);
|
|
g_value_unset (&v2);
|
|
return retval;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
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_path_kvp (QOF_INSTANCE (acc), &v1,
|
|
{KEY_RECONCILE_INFO, "last-interval", "months"});
|
|
qof_instance_set_path_kvp (QOF_INSTANCE (acc), &v2,
|
|
{KEY_RECONCILE_INFO, "last-interval", "days"});
|
|
mark_account (acc);
|
|
xaccAccountCommitEdit (acc);
|
|
g_value_unset (&v1);
|
|
g_value_unset (&v2);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
gboolean
|
|
xaccAccountGetReconcilePostponeDate (const Account *acc, time64 *postpone_date)
|
|
{
|
|
gint64 date = 0;
|
|
gboolean retval = FALSE;
|
|
GValue v = G_VALUE_INIT;
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
|
|
qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v,
|
|
{KEY_RECONCILE_INFO, KEY_POSTPONE, "date"});
|
|
if (G_VALUE_HOLDS_INT64 (&v))
|
|
date = g_value_get_int64 (&v);
|
|
|
|
if (date)
|
|
{
|
|
if (postpone_date)
|
|
*postpone_date = date;
|
|
retval = TRUE;
|
|
}
|
|
g_value_unset (&v);
|
|
return retval;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
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_path_kvp (QOF_INSTANCE (acc), &v,
|
|
{KEY_RECONCILE_INFO, KEY_POSTPONE, "date"});
|
|
mark_account (acc);
|
|
xaccAccountCommitEdit (acc);
|
|
g_value_unset (&v);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
gboolean
|
|
xaccAccountGetReconcilePostponeBalance (const Account *acc,
|
|
gnc_numeric *balance)
|
|
{
|
|
gnc_numeric bal = gnc_numeric_zero ();
|
|
GValue v = G_VALUE_INIT;
|
|
gboolean retval = FALSE;
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
|
|
qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v,
|
|
{KEY_RECONCILE_INFO, KEY_POSTPONE, "balance"});
|
|
if (G_VALUE_HOLDS_INT64 (&v))
|
|
{
|
|
bal = *(gnc_numeric*)g_value_get_boxed (&v);
|
|
if (bal.denom)
|
|
{
|
|
if (balance)
|
|
*balance = bal;
|
|
retval = TRUE;
|
|
}
|
|
}
|
|
g_value_unset (&v);
|
|
return retval;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
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_path_kvp (QOF_INSTANCE (acc), &v,
|
|
{KEY_RECONCILE_INFO, KEY_POSTPONE, "balance"});
|
|
mark_account (acc);
|
|
xaccAccountCommitEdit (acc);
|
|
g_value_unset (&v);
|
|
}
|
|
|
|
/********************************************************************\
|
|
|
|
\********************************************************************/
|
|
|
|
void
|
|
xaccAccountClearReconcilePostpone (Account *acc)
|
|
{
|
|
if (!acc) return;
|
|
|
|
xaccAccountBeginEdit (acc);
|
|
qof_instance_set_path_kvp (QOF_INSTANCE(acc), nullptr, {KEY_RECONCILE_INFO, KEY_POSTPONE});
|
|
mark_account (acc);
|
|
xaccAccountCommitEdit (acc);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
const char *
|
|
xaccAccountGetLastNum (const Account *acc)
|
|
{
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->last_num == is_unset)
|
|
priv->last_num = get_kvp_string_tag (acc, "last-num");
|
|
return priv->last_num;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
xaccAccountSetLastNum (Account *acc, const char *num)
|
|
{
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (priv->last_num != is_unset)
|
|
g_free (priv->last_num);
|
|
priv->last_num = g_strdup (num);
|
|
set_kvp_string_tag (acc, "last-num", priv->last_num);
|
|
}
|
|
|
|
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), nullptr);
|
|
|
|
/* 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;
|
|
std::vector<std::string> path {KEY_LOT_MGMT, "gains-acct",
|
|
gnc_commodity_get_unique_name (curr)};
|
|
GncGUID *guid = NULL;
|
|
Account *gains_account;
|
|
|
|
g_return_val_if_fail (acc != NULL, NULL);
|
|
qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v, path);
|
|
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_path_kvp (QOF_INSTANCE (acc), &vr, path);
|
|
qof_instance_set_dirty (QOF_INSTANCE (acc));
|
|
g_value_unset (&vr);
|
|
}
|
|
xaccAccountCommitEdit (acc);
|
|
}
|
|
else
|
|
gains_account = xaccAccountLookup (guid,
|
|
qof_instance_get_book(acc));
|
|
|
|
g_value_unset (&v);
|
|
return gains_account;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
dxaccAccountSetPriceSrc(Account *acc, const char *src)
|
|
{
|
|
if (!acc) return;
|
|
|
|
if (xaccAccountIsPriced(acc))
|
|
set_kvp_string_tag (acc, "old-price-source", src);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
const char*
|
|
dxaccAccountGetPriceSrc(const Account *acc)
|
|
{
|
|
static char *source = nullptr;
|
|
if (!acc) return NULL;
|
|
|
|
if (!xaccAccountIsPriced(acc)) return NULL;
|
|
|
|
g_free (source);
|
|
source = get_kvp_string_tag (acc, "old-price-source");
|
|
return source;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
dxaccAccountSetQuoteTZ(Account *acc, const char *tz)
|
|
{
|
|
if (!acc) return;
|
|
if (!xaccAccountIsPriced(acc)) return;
|
|
set_kvp_string_tag (acc, "old-quote-tz", tz);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
const char*
|
|
dxaccAccountGetQuoteTZ(const Account *acc)
|
|
{
|
|
static char *quote_tz = nullptr;
|
|
if (!acc) return NULL;
|
|
if (!xaccAccountIsPriced(acc)) return NULL;
|
|
g_free (quote_tz);
|
|
quote_tz = get_kvp_string_tag (acc, "old-quote-tz");
|
|
return quote_tz;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
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_path_kvp (QOF_INSTANCE (acc), &v,
|
|
{KEY_RECONCILE_INFO, KEY_INCLUDE_CHILDREN});
|
|
mark_account(acc);
|
|
xaccAccountCommitEdit (acc);
|
|
g_value_unset (&v);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
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;
|
|
gboolean retval;
|
|
if (!acc) return FALSE;
|
|
qof_instance_get_path_kvp (QOF_INSTANCE (acc), &v,
|
|
{KEY_RECONCILE_INFO, KEY_INCLUDE_CHILDREN});
|
|
retval = G_VALUE_HOLDS_INT64 (&v) ? g_value_get_int64 (&v) : FALSE;
|
|
g_value_unset (&v);
|
|
return retval;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
/* 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 = static_cast<Split*>(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, static_cast <Account*> (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 = static_cast <Account*> (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 = static_cast <Account*> (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 (static_cast <Split*> (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 = static_cast <Split*> (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 = static_cast <Split*> (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(static_cast <Account*> (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 = static_cast <Split*> (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;
|
|
Account *retval;
|
|
if (!imap || !key) return NULL;
|
|
std::vector<std::string> path {IMAP_FRAME};
|
|
if (category)
|
|
path.push_back (category);
|
|
path.push_back (key);
|
|
qof_instance_get_path_kvp (QOF_INSTANCE (imap->acc), &v, path);
|
|
if (G_VALUE_HOLDS_BOXED (&v))
|
|
guid = (GncGUID*)g_value_get_boxed (&v);
|
|
retval = xaccAccountLookup (guid, imap->book);
|
|
g_value_unset (&v);
|
|
return retval;
|
|
}
|
|
|
|
/* 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;
|
|
if (!imap || !key || !acc || (strlen (key) == 0)) return;
|
|
std::vector<std::string> path {IMAP_FRAME};
|
|
if (category)
|
|
path.emplace_back (category);
|
|
path.emplace_back (key);
|
|
g_value_init (&v, GNC_TYPE_GUID);
|
|
g_value_set_boxed (&v, xaccAccountGetGUID (acc));
|
|
xaccAccountBeginEdit (imap->acc);
|
|
qof_instance_set_path_kvp (QOF_INSTANCE (imap->acc), &v, path);
|
|
qof_instance_set_dirty (QOF_INSTANCE (imap->acc));
|
|
xaccAccountCommitEdit (imap->acc);
|
|
g_value_unset (&v);
|
|
}
|
|
|
|
/* Remove a reference to an Account in the map */
|
|
void
|
|
gnc_account_imap_delete_account (GncImportMatchMap *imap,
|
|
const char *category,
|
|
const char *key)
|
|
{
|
|
if (!imap || !key) return;
|
|
std::vector<std::string> path {IMAP_FRAME};
|
|
if (category)
|
|
path.emplace_back (category);
|
|
path.emplace_back (key);
|
|
xaccAccountBeginEdit (imap->acc);
|
|
if (qof_instance_has_path_slot (QOF_INSTANCE (imap->acc), path))
|
|
{
|
|
qof_instance_slot_path_delete (QOF_INSTANCE (imap->acc), path);
|
|
if (category)
|
|
qof_instance_slot_path_delete_if_empty (QOF_INSTANCE (imap->acc), {IMAP_FRAME, category});
|
|
qof_instance_slot_path_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
|
|
--------------------------------------------------------------------------*/
|
|
|
|
|
|
/** 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 AccountProbability
|
|
{
|
|
double product; /* product of probabilities */
|
|
double product_difference; /* product of (1-probabilities) */
|
|
};
|
|
|
|
struct AccountTokenCount
|
|
{
|
|
std::string account_guid;
|
|
int64_t 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 TokenAccountsInfo
|
|
{
|
|
std::vector<AccountTokenCount> accounts;
|
|
int64_t total_count;
|
|
};
|
|
|
|
/** holds an account guid and its corresponding integer probability
|
|
the integer probability is some factor of 10
|
|
*/
|
|
struct AccountInfo
|
|
{
|
|
std::string account_guid;
|
|
int32_t probability;
|
|
};
|
|
|
|
static void
|
|
build_token_info(char const * suffix, KvpValue * value, TokenAccountsInfo & tokenInfo)
|
|
{
|
|
if (strlen(suffix) == GUID_ENCODING_LENGTH)
|
|
{
|
|
tokenInfo.total_count += value->get<int64_t>();
|
|
/*By convention, the key ends with the account GUID.*/
|
|
tokenInfo.accounts.emplace_back(AccountTokenCount{std::string{suffix}, value->get<int64_t>()});
|
|
}
|
|
}
|
|
|
|
/** We scale the probability values by probability_factor.
|
|
ie. with probability_factor of 100000, 10% would be
|
|
0.10 * 100000 = 10000 */
|
|
static constexpr int probability_factor = 100000;
|
|
|
|
static FinalProbabilityVec
|
|
build_probabilities(ProbabilityVec const & first_pass)
|
|
{
|
|
FinalProbabilityVec ret;
|
|
for (auto const & first_pass_prob : first_pass)
|
|
{
|
|
auto const & account_probability = first_pass_prob.second;
|
|
/* 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)...)
|
|
*/
|
|
int32_t probability = (account_probability.product /
|
|
(account_probability.product + account_probability.product_difference)) * probability_factor;
|
|
ret.push_back({first_pass_prob.first, probability});
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static AccountInfo
|
|
highest_probability(FinalProbabilityVec const & probabilities)
|
|
{
|
|
AccountInfo ret {"", std::numeric_limits<int32_t>::min()};
|
|
for (auto const & prob : probabilities)
|
|
if (prob.second > ret.probability)
|
|
ret = AccountInfo {prob.first, prob.second};
|
|
return ret;
|
|
}
|
|
|
|
static ProbabilityVec
|
|
get_first_pass_probabilities(GncImportMatchMap * imap, GList * tokens)
|
|
{
|
|
ProbabilityVec ret;
|
|
/* find the probability for each account that contains any of the tokens
|
|
* in the input tokens list. */
|
|
for (auto current_token = tokens; current_token; current_token = current_token->next)
|
|
{
|
|
TokenAccountsInfo tokenInfo{};
|
|
auto path = std::string{IMAP_FRAME_BAYES "/"} + static_cast <char const *> (current_token->data) + "/";
|
|
qof_instance_foreach_slot_prefix (QOF_INSTANCE (imap->acc), path, &build_token_info, tokenInfo);
|
|
for (auto const & current_account_token : tokenInfo.accounts)
|
|
{
|
|
auto item = std::find_if(ret.begin(), ret.end(), [¤t_account_token]
|
|
(std::pair<std::string, AccountProbability> const & a) {
|
|
return current_account_token.account_guid == a.first;
|
|
});
|
|
if (item != ret.end())
|
|
{/* This account is already in the map */
|
|
item->second.product = ((double)current_account_token.token_count /
|
|
(double)tokenInfo.total_count) * item->second.product;
|
|
item->second.product_difference = ((double)1 - ((double)current_account_token.token_count /
|
|
(double)tokenInfo.total_count)) * item->second.product_difference;
|
|
}
|
|
else
|
|
{
|
|
/* add a new entry */
|
|
AccountProbability new_probability;
|
|
new_probability.product = ((double)current_account_token.token_count /
|
|
(double)tokenInfo.total_count);
|
|
new_probability.product_difference = 1 - (new_probability.product);
|
|
ret.push_back({current_account_token.account_guid, std::move(new_probability)});
|
|
}
|
|
} /* for all accounts in tokenInfo */
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static std::string
|
|
look_for_old_separator_descendants (Account *root, std::string const & 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.c_str (), separator);
|
|
/* Go through list of top level accounts */
|
|
for (ptr = top_accounts; ptr; ptr = g_list_next (ptr))
|
|
{
|
|
const gchar *name = xaccAccountGetName (static_cast <Account const *> (ptr->data));
|
|
// we are looking for the longest top level account that matches
|
|
if (g_str_has_prefix (full_name.c_str (), 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
|
|
std::string new_name {full_name};
|
|
if (found_len > 1)
|
|
std::replace (new_name.begin (), new_name.end (), found_sep, *separator);
|
|
PINFO ("Return full_name is '%s'", new_name.c_str ());
|
|
return new_name;
|
|
}
|
|
|
|
static std::string
|
|
get_guid_from_account_name (Account * root, std::string const & name)
|
|
{
|
|
auto map_account = gnc_account_lookup_by_full_name (root, name.c_str ());
|
|
if (!map_account)
|
|
{
|
|
auto temp_account_name = look_for_old_separator_descendants (root, name,
|
|
gnc_get_account_separator_string ());
|
|
map_account = gnc_account_lookup_by_full_name (root, temp_account_name.c_str ());
|
|
}
|
|
auto temp_guid = gnc::GUID {*xaccAccountGetGUID (map_account)};
|
|
return temp_guid.to_string ();
|
|
}
|
|
|
|
static FlatKvpEntry
|
|
convert_entry (KvpEntry entry, Account* root)
|
|
{
|
|
/*We need to make a copy here.*/
|
|
auto account_name = entry.first.back();
|
|
if (!gnc::GUID::is_valid_guid (account_name))
|
|
{
|
|
/* Earlier version stored the account name in the import map, and
|
|
* there were early beta versions of 2.7 that stored a GUID.
|
|
* If there is no GUID, we assume it's an account name. */
|
|
/* Take off the account name and replace it with the GUID */
|
|
entry.first.pop_back();
|
|
auto guid_str = get_guid_from_account_name (root, account_name);
|
|
entry.first.emplace_back (guid_str);
|
|
}
|
|
std::string new_key {std::accumulate (entry.first.begin(), entry.first.end(), std::string {})};
|
|
new_key = IMAP_FRAME_BAYES + new_key;
|
|
return {new_key, entry.second};
|
|
}
|
|
|
|
static std::vector<FlatKvpEntry>
|
|
get_flat_imap (Account * acc)
|
|
{
|
|
auto frame = qof_instance_get_slots (QOF_INSTANCE (acc));
|
|
auto slot = frame->get_slot ({IMAP_FRAME_BAYES});
|
|
if (!slot)
|
|
return {};
|
|
auto imap_frame = slot->get<KvpFrame*> ();
|
|
auto flat_kvp = imap_frame->flatten_kvp ();
|
|
auto root = gnc_account_get_root (acc);
|
|
std::vector <FlatKvpEntry> ret;
|
|
for (auto const & flat_entry : flat_kvp)
|
|
{
|
|
auto converted_entry = convert_entry (flat_entry, root);
|
|
/*If the entry was invalid, we don't perpetuate it.*/
|
|
if (converted_entry.first.size())
|
|
ret.emplace_back (converted_entry);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static bool
|
|
convert_imap_account_bayes_to_flat (Account *acc)
|
|
{
|
|
auto frame = qof_instance_get_slots (QOF_INSTANCE (acc));
|
|
if (!frame->get_keys().size())
|
|
return false;
|
|
auto flat_imap = get_flat_imap(acc);
|
|
if (!flat_imap.size ())
|
|
return false;
|
|
xaccAccountBeginEdit(acc);
|
|
frame->set({IMAP_FRAME_BAYES}, nullptr);
|
|
std::for_each(flat_imap.begin(), flat_imap.end(),
|
|
[&frame] (FlatKvpEntry const & entry) {
|
|
frame->set({entry.first.c_str()}, entry.second);
|
|
});
|
|
qof_instance_set_dirty (QOF_INSTANCE (acc));
|
|
xaccAccountCommitEdit(acc);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Checks for import map data and converts them when found.
|
|
*/
|
|
static bool
|
|
imap_convert_bayes_to_flat (QofBook * book)
|
|
{
|
|
auto root = gnc_book_get_root_account (book);
|
|
auto accts = gnc_account_get_descendants_sorted (root);
|
|
bool ret = false;
|
|
for (auto ptr = accts; ptr; ptr = g_list_next (ptr))
|
|
{
|
|
Account *acc = static_cast <Account*> (ptr->data);
|
|
if (convert_imap_account_bayes_to_flat (acc))
|
|
{
|
|
ret = true;
|
|
gnc_features_set_used (book, GNC_FEATURE_GUID_FLAT_BAYESIAN);
|
|
}
|
|
}
|
|
g_list_free (accts);
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
gnc_account_reset_convert_bayes_to_flat (void)
|
|
{
|
|
imap_convert_bayes_to_flat_run = false;
|
|
}
|
|
|
|
/*
|
|
* Here we check to see the state of import map data.
|
|
*
|
|
* If the GUID_FLAT_BAYESIAN feature flag is set, everything
|
|
* should be fine.
|
|
*
|
|
* If it is not set, there are two possibilities: import data
|
|
* are present from a previous version or not. If they are,
|
|
* they are converted, and the feature flag set. If there are
|
|
* no previous data, nothing is done.
|
|
*/
|
|
static void
|
|
check_import_map_data (QofBook *book)
|
|
{
|
|
if (gnc_features_check_used (book, GNC_FEATURE_GUID_FLAT_BAYESIAN) ||
|
|
imap_convert_bayes_to_flat_run)
|
|
return;
|
|
|
|
/* This function will set GNC_FEATURE_GUID_FLAT_BAYESIAN if necessary.*/
|
|
imap_convert_bayes_to_flat (book);
|
|
imap_convert_bayes_to_flat_run = true;
|
|
}
|
|
|
|
static constexpr double threshold = .90 * probability_factor; /* 90% */
|
|
|
|
/** Look up an Account in the map */
|
|
Account*
|
|
gnc_account_imap_find_account_bayes (GncImportMatchMap *imap, GList *tokens)
|
|
{
|
|
if (!imap)
|
|
return nullptr;
|
|
check_import_map_data (imap->book);
|
|
auto first_pass = get_first_pass_probabilities(imap, tokens);
|
|
if (!first_pass.size())
|
|
return nullptr;
|
|
auto final_probabilities = build_probabilities(first_pass);
|
|
if (!final_probabilities.size())
|
|
return nullptr;
|
|
auto best = highest_probability(final_probabilities);
|
|
if (best.account_guid == "")
|
|
return nullptr;
|
|
if (best.probability < threshold)
|
|
return nullptr;
|
|
gnc::GUID guid;
|
|
try {
|
|
guid = gnc::GUID::from_string(best.account_guid);
|
|
} catch (gnc::guid_syntax_exception&) {
|
|
return nullptr;
|
|
}
|
|
auto account = xaccAccountLookup (reinterpret_cast<GncGUID*>(&guid), imap->book);
|
|
return account;
|
|
}
|
|
|
|
static void
|
|
change_imap_entry (GncImportMatchMap *imap, std::string const & path, int64_t token_count)
|
|
{
|
|
GValue value = G_VALUE_INIT;
|
|
|
|
PINFO("Source Account is '%s', Count is '%" G_GINT64_FORMAT "'",
|
|
xaccAccountGetName (imap->acc), token_count);
|
|
|
|
// check for existing guid entry
|
|
if (qof_instance_has_slot (QOF_INSTANCE(imap->acc), path.c_str ()))
|
|
{
|
|
int64_t existing_token_count = 0;
|
|
|
|
// get the existing_token_count value
|
|
qof_instance_get_path_kvp (QOF_INSTANCE (imap->acc), &value, {path});
|
|
|
|
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_path_kvp (QOF_INSTANCE (imap->acc), &value, {path});
|
|
gnc_features_set_used (imap->book, GNC_FEATURE_GUID_FLAT_BAYESIAN);
|
|
g_value_unset (&value);
|
|
}
|
|
|
|
/** 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;
|
|
char *guid_string;
|
|
|
|
ENTER(" ");
|
|
if (!imap)
|
|
{
|
|
LEAVE(" ");
|
|
return;
|
|
}
|
|
check_import_map_data (imap->book);
|
|
|
|
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);
|
|
auto path = std::string {IMAP_FRAME_BAYES} + '/' + static_cast<char*>(current_token->data) + '/' + guid_string;
|
|
/* change the imap entry for the account */
|
|
change_imap_entry (imap, path, token_count);
|
|
}
|
|
/* 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_non_bayes (const char *key, const GValue *value, gpointer user_data)
|
|
{
|
|
if (!G_VALUE_HOLDS_BOXED (value))
|
|
return;
|
|
QofBook *book;
|
|
GncGUID *guid = NULL;
|
|
gchar *guid_string = NULL;
|
|
auto imapInfo = (GncImapInfo*)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: match string '%s', match account guid: '%s'",
|
|
(char*)key, guid_string);
|
|
|
|
auto imapInfo_node = static_cast <GncImapInfo*> (g_malloc(sizeof(GncImapInfo)));
|
|
|
|
imapInfo_node->source_account = imapInfo->source_account;
|
|
imapInfo_node->map_account = xaccAccountLookup (guid, book);
|
|
imapInfo_node->head = g_strdup (imapInfo->head);
|
|
imapInfo_node->match_string = g_strdup (key);
|
|
imapInfo_node->category = g_strdup (imapInfo->category);
|
|
imapInfo_node->count = g_strdup (" ");
|
|
|
|
imapInfo->list = g_list_prepend (imapInfo->list, imapInfo_node);
|
|
|
|
g_free (guid_string);
|
|
}
|
|
|
|
static void
|
|
build_bayes (const char *suffix, KvpValue * value, GncImapInfo & imapInfo)
|
|
{
|
|
size_t guid_start = strlen(suffix) - GUID_ENCODING_LENGTH;
|
|
std::string account_guid {&suffix[guid_start]};
|
|
GncGUID guid;
|
|
try
|
|
{
|
|
guid = gnc::GUID::from_string (account_guid);
|
|
}
|
|
catch (const gnc::guid_syntax_exception& err)
|
|
{
|
|
PWARN("Invalid GUID string from %s%s", IMAP_FRAME_BAYES, suffix);
|
|
}
|
|
auto map_account = xaccAccountLookup (&guid, gnc_account_get_book (imapInfo.source_account));
|
|
auto imap_node = static_cast <GncImapInfo*> (g_malloc (sizeof (GncImapInfo)));
|
|
auto count = value->get <int64_t> ();
|
|
imap_node->source_account = imapInfo.source_account;
|
|
imap_node->map_account = map_account;
|
|
imap_node->head = g_strdup_printf ("%s%s", IMAP_FRAME_BAYES, suffix);
|
|
imap_node->match_string = g_strndup (&suffix[1], guid_start - 2);
|
|
imap_node->category = g_strdup(" ");
|
|
imap_node->count = g_strdup_printf ("%" G_GINT64_FORMAT, count);
|
|
imapInfo.list = g_list_prepend (imapInfo.list, imap_node);
|
|
}
|
|
|
|
GList *
|
|
gnc_account_imap_get_info_bayes (Account *acc)
|
|
{
|
|
check_import_map_data (gnc_account_get_book (acc));
|
|
/* A dummy object which is used to hold the specified account, and the list
|
|
* of data about which we care. */
|
|
GncImapInfo imapInfo {acc, nullptr};
|
|
qof_instance_foreach_slot_prefix (QOF_INSTANCE (acc), IMAP_FRAME_BAYES, &build_bayes, imapInfo);
|
|
return g_list_reverse(imapInfo.list);
|
|
}
|
|
|
|
GList *
|
|
gnc_account_imap_get_info (Account *acc, const char *category)
|
|
{
|
|
GList *list = NULL;
|
|
|
|
GncImapInfo imapInfo;
|
|
|
|
std::vector<std::string> path {IMAP_FRAME};
|
|
if (category)
|
|
path.emplace_back (category);
|
|
|
|
imapInfo.source_account = acc;
|
|
imapInfo.list = list;
|
|
|
|
imapInfo.head = g_strdup (IMAP_FRAME);
|
|
imapInfo.category = g_strdup (category);
|
|
|
|
if (qof_instance_has_path_slot (QOF_INSTANCE (acc), path))
|
|
{
|
|
qof_instance_foreach_slot (QOF_INSTANCE(acc), IMAP_FRAME, category,
|
|
build_non_bayes, &imapInfo);
|
|
}
|
|
return g_list_reverse(imapInfo.list);
|
|
}
|
|
|
|
/*******************************************************************************/
|
|
|
|
gchar *
|
|
gnc_account_get_map_entry (Account *acc, const char *head, const char *category)
|
|
{
|
|
if (category)
|
|
return get_kvp_string_path (acc, {head, category});
|
|
else
|
|
return get_kvp_string_path (acc, {head});
|
|
}
|
|
|
|
|
|
void
|
|
gnc_account_delete_map_entry (Account *acc, char *head, char *category,
|
|
char *match_string, gboolean empty)
|
|
{
|
|
if (acc != NULL)
|
|
{
|
|
std::vector<std::string> path {head};
|
|
if (category)
|
|
path.emplace_back (category);
|
|
if (match_string)
|
|
path.emplace_back (match_string);
|
|
|
|
if (qof_instance_has_path_slot (QOF_INSTANCE (acc), path))
|
|
{
|
|
xaccAccountBeginEdit (acc);
|
|
if (empty)
|
|
qof_instance_slot_path_delete_if_empty (QOF_INSTANCE(acc), path);
|
|
else
|
|
qof_instance_slot_path_delete (QOF_INSTANCE(acc), path);
|
|
PINFO("Account is '%s', head is '%s', category is '%s', match_string is'%s'",
|
|
xaccAccountGetName (acc), head, category, match_string);
|
|
qof_instance_set_dirty (QOF_INSTANCE(acc));
|
|
xaccAccountCommitEdit (acc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
gnc_account_delete_all_bayes_maps (Account *acc)
|
|
{
|
|
if (acc != NULL)
|
|
{
|
|
auto slots = qof_instance_get_slots_prefix (QOF_INSTANCE (acc), IMAP_FRAME_BAYES);
|
|
if (!slots.size()) return;
|
|
for (auto const & entry : slots)
|
|
{
|
|
qof_instance_slot_path_delete (QOF_INSTANCE (acc), {entry.first});
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ================================================================ */
|
|
/* QofObject function implementation and registration */
|
|
|
|
static void
|
|
gnc_account_book_end(QofBook* book)
|
|
{
|
|
Account *root_account = gnc_book_get_root_account(book);
|
|
if (!root_account)
|
|
return;
|
|
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 = ) (void*(*)(QofBook*)) 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_OPENING_BALANCE_, QOF_TYPE_BOOLEAN,
|
|
(QofAccessFunc) xaccAccountGetIsOpeningBalance,
|
|
(QofSetterFunc) xaccAccountSetIsOpeningBalance
|
|
},
|
|
{
|
|
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 =========================== */
|