mirror of https://github.com/Gnucash/gnucash
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5924 lines
185 KiB
5924 lines
185 KiB
/********************************************************************\
|
|
* Account.c -- Account data structure implementation *
|
|
* Copyright (C) 1997 Robin D. Clark *
|
|
* Copyright (C) 1997-2003 Linas Vepstas <linas@linas.org> *
|
|
* Copyright (C) 2007 David Hampton <hampton@employees.org> *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as *
|
|
* published by the Free Software Foundation; either version 2 of *
|
|
* the License, or (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License*
|
|
* along with this program; if not, contact: *
|
|
* *
|
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
|
* *
|
|
\********************************************************************/
|
|
|
|
#include <config.h>
|
|
|
|
#include "gnc-prefs.h"
|
|
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "AccountP.hpp"
|
|
#include "Account.hpp"
|
|
#include "Split.h"
|
|
#include "Transaction.h"
|
|
#include "TransactionP.hpp"
|
|
#include "gnc-event.h"
|
|
#include "gnc-glib-utils.h"
|
|
#include "gnc-lot.h"
|
|
#include "gnc-pricedb.h"
|
|
#include "qofevent.h"
|
|
#include "qofinstance-p.h"
|
|
#include "gnc-features.h"
|
|
#include "guid.hpp"
|
|
|
|
#include <numeric>
|
|
#include <map>
|
|
#include <unordered_set>
|
|
#include <algorithm>
|
|
|
|
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 KEY_IMP_APPEND_TEXT("import-append-text");
|
|
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 const std::string KEY_BALANCE_LIMIT("balance-limit");
|
|
static const std::string KEY_BALANCE_HIGHER_LIMIT_VALUE("higher-value");
|
|
static const std::string KEY_BALANCE_LOWER_LIMIT_VALUE("lower-value");
|
|
static const std::string KEY_BALANCE_INCLUDE_SUB_ACCTS("inlude-sub-accts");
|
|
|
|
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_IMP_APPEND_TEXT, /* 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*)gnc_account_get_instance_private((Account*)o))
|
|
|
|
/* 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 = nullptr;
|
|
|
|
if ( !invalid_account_names )
|
|
return nullptr;
|
|
|
|
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 != nullptr, 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);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
/* 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 = nullptr;
|
|
|
|
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 = nullptr;
|
|
|
|
priv->commodity = nullptr;
|
|
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;
|
|
|
|
new (&priv->children) AccountVec ();
|
|
new (&priv->splits) SplitsVec ();
|
|
priv->splits_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
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;
|
|
|
|
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_IMP_APPEND_TEXT:
|
|
g_value_set_boolean(value, xaccAccountGetAppendText(account));
|
|
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_IMP_APPEND_TEXT:
|
|
xaccAccountSetAppendText(account, g_value_get_boolean(value));
|
|
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.",
|
|
nullptr,
|
|
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.",
|
|
nullptr,
|
|
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.",
|
|
nullptr,
|
|
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.",
|
|
nullptr,
|
|
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.",
|
|
nullptr,
|
|
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.",
|
|
nullptr,
|
|
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.",
|
|
nullptr,
|
|
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.",
|
|
nullptr,
|
|
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.",
|
|
nullptr,
|
|
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.",
|
|
nullptr,
|
|
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",
|
|
nullptr,
|
|
static_cast<GParamFlags>(G_PARAM_READWRITE)));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class,
|
|
PROP_IMP_APPEND_TEXT,
|
|
g_param_spec_boolean ("import-append-text",
|
|
"Import Append Text",
|
|
"Saved state of Append checkbox for setting initial "
|
|
"value next time this account is imported.",
|
|
FALSE,
|
|
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",
|
|
nullptr,
|
|
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",
|
|
nullptr,
|
|
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);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
gnc_account_foreach_split (const Account *acc, std::function<void(Split*)> func)
|
|
{
|
|
if (!GNC_IS_ACCOUNT (acc))
|
|
return;
|
|
|
|
auto& splits{GET_PRIVATE(acc)->splits};
|
|
std::for_each (splits.begin(), splits.end(), func);
|
|
}
|
|
|
|
void
|
|
gnc_account_foreach_split_until_date (const Account *acc, time64 end_date,
|
|
std::function<void(Split*)> f)
|
|
{
|
|
if (!GNC_IS_ACCOUNT (acc))
|
|
return;
|
|
|
|
auto after_date = [](time64 end_date, auto s) -> bool
|
|
{ return (xaccTransGetDate (xaccSplitGetParent (s)) > end_date); };
|
|
|
|
auto& splits{GET_PRIVATE(acc)->splits};
|
|
auto after_date_iter = std::upper_bound (splits.begin(), splits.end(), end_date, after_date);
|
|
std::for_each (splits.begin(), after_date_iter, f);
|
|
}
|
|
|
|
|
|
Split*
|
|
gnc_account_find_split (const Account *acc, std::function<bool(const Split*)> predicate,
|
|
bool reverse)
|
|
{
|
|
if (!GNC_IS_ACCOUNT (acc))
|
|
return nullptr;
|
|
|
|
const auto& splits{GET_PRIVATE(acc)->splits};
|
|
if (reverse)
|
|
{
|
|
auto latest = std::find_if(splits.rbegin(), splits.rend(), predicate);
|
|
return (latest == splits.rend()) ? nullptr : *latest;
|
|
}
|
|
else
|
|
{
|
|
auto earliest = std::find_if(splits.begin(), splits.end(), predicate);
|
|
return (earliest == splits.end()) ? nullptr : *earliest;
|
|
}
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
QofBook *
|
|
gnc_account_get_book(const Account *account)
|
|
{
|
|
if (!account) return nullptr;
|
|
return qof_instance_get_book(QOF_INSTANCE(account));
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static Account *
|
|
gnc_coll_get_root_account (QofCollection *col)
|
|
{
|
|
if (!col) return nullptr;
|
|
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 nullptr;
|
|
col = qof_book_get_collection (book, GNC_ID_ROOT_ACCOUNT);
|
|
root = gnc_coll_get_root_account (col);
|
|
if (root == nullptr && !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, nullptr);
|
|
|
|
acc = static_cast<Account*>(g_object_new (GNC_TYPE_ACCOUNT, nullptr));
|
|
xaccInitAccount (acc, book);
|
|
qof_event_gen (&acc->inst, QOF_EVENT_CREATE, nullptr);
|
|
|
|
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), nullptr);
|
|
g_return_val_if_fail(QOF_IS_BOOK(book), nullptr);
|
|
|
|
ENTER (" ");
|
|
ret = static_cast<Account*>(g_object_new (GNC_TYPE_ACCOUNT, nullptr));
|
|
g_return_val_if_fail (ret, nullptr);
|
|
|
|
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)
|
|
{
|
|
/* 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)
|
|
{
|
|
auto priv{GET_PRIVATE(acc)};
|
|
/* Copy the list since it will be modified */
|
|
auto children = priv->children;
|
|
std::for_each (children.begin(), children.end(), xaccFreeOneChildAccount);
|
|
|
|
/* The foreach should have removed all the children already. */
|
|
priv->children.clear();
|
|
}
|
|
|
|
/* 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, nullptr);
|
|
|
|
/* Otherwise the lists below get munged while we're iterating
|
|
* them, possibly crashing.
|
|
*/
|
|
if (!qof_instance_get_destroying (acc))
|
|
qof_instance_set_destroying(acc, TRUE);
|
|
|
|
if (!priv->children.empty())
|
|
{
|
|
PERR (" instead of calling xaccFreeAccount(), please call\n"
|
|
" xaccAccountBeginEdit(); xaccAccountDestroy();\n");
|
|
|
|
/* First, recursively free children, also frees list */
|
|
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 = nullptr;
|
|
}
|
|
|
|
/* 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.empty())
|
|
{
|
|
PERR (" instead of calling xaccFreeAccount(), please call\n"
|
|
" xaccAccountBeginEdit(); xaccAccountDestroy();\n");
|
|
|
|
qof_instance_reset_editlevel(acc);
|
|
|
|
for (auto s : priv->splits)
|
|
{
|
|
g_assert(xaccSplitGetAccount(s) == acc);
|
|
xaccSplitDestroy (s);
|
|
}
|
|
/* Nothing here (or in xaccAccountCommitEdit) nullptrs priv->splits, so this asserts every time.
|
|
g_assert(priv->splits == nullptr);
|
|
*/
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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->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 = nullptr;
|
|
|
|
priv->balance_dirty = FALSE;
|
|
priv->sort_dirty = FALSE;
|
|
priv->splits.~SplitsVec();
|
|
priv->children.~AccountVec();
|
|
g_hash_table_destroy (priv->splits_hash);
|
|
|
|
/* 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, nullptr);
|
|
}
|
|
|
|
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))
|
|
{
|
|
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))
|
|
{
|
|
// We need to delete in reverse order so that the vector's iterators aren't invalidated.
|
|
for_each(priv->splits.rbegin(), priv->splits.rend(), [](Split *s) {
|
|
xaccSplitDestroy (s); });
|
|
}
|
|
else
|
|
{
|
|
priv->splits.clear();
|
|
g_hash_table_remove_all (priv->splits_hash);
|
|
}
|
|
|
|
/* 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 == nullptr || 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 (auto 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 = nullptr;
|
|
|
|
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);
|
|
}
|
|
|
|
void
|
|
xaccAccountDestroyAllTransactions(Account *acc)
|
|
{
|
|
auto priv = GET_PRIVATE(acc);
|
|
std::vector<Transaction*> transactions;
|
|
transactions.reserve(priv->splits.size());
|
|
std::transform(priv->splits.begin(), priv->splits.end(),
|
|
back_inserter(transactions),
|
|
[](auto split) { return split->parent; });
|
|
std::stable_sort(transactions.begin(), transactions.end());
|
|
transactions.erase(std::unique(transactions.begin(), transactions.end()),
|
|
transactions.end());
|
|
qof_event_suspend();
|
|
std::for_each(transactions.rbegin(), transactions.rend(),
|
|
[](auto trans) { xaccTransDestroy (trans); });
|
|
qof_event_resume();
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static gboolean
|
|
xaccAcctChildrenEqual(const AccountVec& na,
|
|
const AccountVec& nb,
|
|
gboolean check_guids)
|
|
{
|
|
if (na.size() != nb.size())
|
|
{
|
|
PINFO ("Accounts have different numbers of children");
|
|
return (FALSE);
|
|
}
|
|
|
|
for (auto aa : na)
|
|
{
|
|
auto it_b = std::find_if (nb.begin(), nb.end(), [aa](auto ab) -> bool
|
|
{
|
|
if (!aa) return (!ab);
|
|
if (!ab) return false;
|
|
auto code_a{GET_PRIVATE(aa)->accountCode};
|
|
auto code_b{GET_PRIVATE(ab)->accountCode};
|
|
if ((code_a && *code_a) || (code_b && *code_b)) return !g_strcmp0 (code_a, code_b);
|
|
return !g_strcmp0 (GET_PRIVATE(aa)->accountName, GET_PRIVATE(ab)->accountName);
|
|
});
|
|
|
|
if (it_b == nb.end())
|
|
{
|
|
PINFO ("Unable to find matching child account.");
|
|
return FALSE;
|
|
}
|
|
else if (auto ab = *it_b; !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);
|
|
}
|
|
}
|
|
|
|
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. */
|
|
|
|
if (!std::equal (priv_aa->splits.begin(), priv_aa->splits.end(),
|
|
priv_ab->splits.begin(), priv_ab->splits.end(),
|
|
[check_guids](auto sa, auto sb)
|
|
{ return xaccSplitEqual(sa, sb, check_guids, true, false); }))
|
|
{
|
|
PWARN ("splits differ");
|
|
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;
|
|
}
|
|
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static bool split_cmp_less (const Split* a, const Split* b)
|
|
{
|
|
return xaccSplitOrder (a, b) < 0;
|
|
}
|
|
|
|
gboolean
|
|
gnc_account_insert_split (Account *acc, Split *s)
|
|
{
|
|
AccountPrivate *priv;
|
|
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
|
|
g_return_val_if_fail(GNC_IS_SPLIT(s), FALSE);
|
|
|
|
priv = GET_PRIVATE(acc);
|
|
if (!g_hash_table_add (priv->splits_hash, s))
|
|
return false;
|
|
|
|
priv->splits.push_back (s);
|
|
|
|
if (qof_instance_get_editlevel(acc) == 0)
|
|
std::sort (priv->splits.begin(), priv->splits.end(), split_cmp_less);
|
|
else
|
|
priv->sort_dirty = true;
|
|
|
|
//FIXME: find better event
|
|
qof_event_gen (&acc->inst, QOF_EVENT_MODIFY, nullptr);
|
|
/* 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;
|
|
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
|
|
g_return_val_if_fail(GNC_IS_SPLIT(s), FALSE);
|
|
|
|
priv = GET_PRIVATE(acc);
|
|
|
|
if (!g_hash_table_remove (priv->splits_hash, s))
|
|
return false;
|
|
|
|
// shortcut pruning the last element. this is the most common
|
|
// remove_split operation during UI or book shutdown.
|
|
if (s == priv->splits.back())
|
|
priv->splits.pop_back();
|
|
else
|
|
priv->splits.erase (std::remove (priv->splits.begin(), priv->splits.end(), s),
|
|
priv->splits.end());
|
|
|
|
//FIXME: find better event type
|
|
qof_event_gen(&acc->inst, QOF_EVENT_MODIFY, nullptr);
|
|
// 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;
|
|
std::sort (priv->splits.begin(), priv->splits.end(), split_cmp_less);
|
|
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 nullptr;
|
|
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;
|
|
|
|
g_return_if_fail(GNC_IS_ACCOUNT(acc));
|
|
|
|
priv = GET_PRIVATE(acc);
|
|
priv->mark = val;
|
|
std::for_each (priv->children.begin(), priv->children.end(),
|
|
[val](auto acc){ xaccClearMarkDown(acc, val); });
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
GNCPolicy *
|
|
gnc_account_get_policy (Account *acc)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), nullptr);
|
|
|
|
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, nullptr);
|
|
qof_event_gen (&acc->inst, QOF_EVENT_MODIFY, nullptr);
|
|
LEAVE ("(acc=%p, lot=%p)", acc, lot);
|
|
}
|
|
|
|
void
|
|
xaccAccountInsertLot (Account *acc, GNCLot *lot)
|
|
{
|
|
AccountPrivate *priv, *opriv;
|
|
Account * old_acc = nullptr;
|
|
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, nullptr);
|
|
qof_event_gen (&acc->inst, QOF_EVENT_MODIFY, nullptr);
|
|
|
|
LEAVE ("(acc=%p, lot=%p)", acc, lot);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
static void
|
|
xaccPreSplitMove (Split *split)
|
|
{
|
|
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.empty() || 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. */
|
|
std::for_each (from_priv->splits.begin(), from_priv->splits.end(), xaccPreSplitMove);
|
|
|
|
/* 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.
|
|
*/
|
|
auto splits = from_priv->splits;
|
|
std::for_each (splits.begin(), splits.end(), [accto](auto s){ xaccPostSplitMove (s, accto); });
|
|
|
|
/* Finally empty accfrom. */
|
|
g_assert(from_priv->splits.empty());
|
|
g_assert(from_priv->lots == nullptr);
|
|
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;
|
|
|
|
if (nullptr == 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 (auto split : priv->splits)
|
|
{
|
|
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;
|
|
int ta, tb, result;
|
|
|
|
if (aa == ab) return 0;
|
|
if (!ab) return -1;
|
|
if (!aa) return +1;
|
|
|
|
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 void
|
|
set_kvp_gnc_numeric_path (Account *acc, const std::vector<std::string>& path,
|
|
std::optional<gnc_numeric> value)
|
|
{
|
|
xaccAccountBeginEdit(acc);
|
|
qof_instance_set_path_kvp<gnc_numeric> (QOF_INSTANCE(acc), value, path);
|
|
xaccAccountCommitEdit(acc);
|
|
}
|
|
|
|
static std::optional<gnc_numeric>
|
|
get_kvp_gnc_numeric_path (const Account *acc, const Path& path)
|
|
{
|
|
return qof_instance_get_path_kvp<gnc_numeric> (QOF_INSTANCE(acc), path);
|
|
}
|
|
|
|
static void
|
|
set_kvp_string_path (Account *acc, std::vector<std::string> const & path,
|
|
const char *value)
|
|
{
|
|
std::optional<const char*> val;
|
|
if (value && *value)
|
|
val = g_strdup(value);
|
|
|
|
xaccAccountBeginEdit(acc);
|
|
qof_instance_set_path_kvp<const char*> (QOF_INSTANCE(acc), val, path);
|
|
xaccAccountCommitEdit(acc);
|
|
}
|
|
|
|
static const char*
|
|
get_kvp_string_path (const Account *acc, const Path& path)
|
|
{
|
|
auto rv{qof_instance_get_path_kvp<const char*> (QOF_INSTANCE(acc), path)};
|
|
return rv ? *rv : nullptr;
|
|
}
|
|
|
|
static void
|
|
set_kvp_account_path (Account* acc, const Path& path, const Account* kvp_account)
|
|
{
|
|
std::optional<GncGUID*> val;
|
|
if (kvp_account)
|
|
val = guid_copy(xaccAccountGetGUID (kvp_account));
|
|
|
|
xaccAccountBeginEdit(acc);
|
|
qof_instance_set_path_kvp<GncGUID*> (QOF_INSTANCE(acc), val, path);
|
|
xaccAccountCommitEdit(acc);
|
|
}
|
|
|
|
static Account*
|
|
get_kvp_account_path (const Account *acc, const Path& path)
|
|
{
|
|
auto val{qof_instance_get_path_kvp<GncGUID*> (QOF_INSTANCE(acc), path)};
|
|
return val ? xaccAccountLookup (*val, gnc_account_get_book (acc)) : nullptr;
|
|
}
|
|
|
|
static void
|
|
set_kvp_boolean_path (Account *acc, const Path& path, gboolean option)
|
|
{
|
|
set_kvp_string_path (acc, path, option ? "true" : nullptr);
|
|
}
|
|
|
|
static gboolean
|
|
get_kvp_boolean_path (const Account *acc, const Path& path)
|
|
{
|
|
auto slot{QOF_INSTANCE(acc)->kvp_data->get_slot(path)};
|
|
if (!slot) return false;
|
|
switch (slot->get_type())
|
|
{
|
|
case KvpValueImpl::Type::INT64:
|
|
return slot->get<int64_t>() != 0;
|
|
case KvpValueImpl::Type::STRING:
|
|
return g_strcmp0 (slot->get<const char*>(), "true") == 0;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_kvp_int64_path (Account *acc, const Path& path, std::optional<gint64> value)
|
|
{
|
|
xaccAccountBeginEdit(acc);
|
|
qof_instance_set_path_kvp<int64_t> (QOF_INSTANCE(acc), value, path);
|
|
xaccAccountCommitEdit(acc);
|
|
}
|
|
|
|
static const std::optional<gint64>
|
|
get_kvp_int64_path (const Account *acc, const Path& path)
|
|
{
|
|
return qof_instance_get_path_kvp<int64_t> (QOF_INSTANCE(acc), path);
|
|
}
|
|
|
|
void
|
|
xaccAccountSetColor (Account *acc, const char *str)
|
|
{
|
|
set_kvp_string_path (acc, {"color"}, str);
|
|
}
|
|
|
|
void
|
|
xaccAccountSetFilter (Account *acc, const char *str)
|
|
{
|
|
set_kvp_string_path (acc, {"filter"}, str);
|
|
}
|
|
|
|
void
|
|
xaccAccountSetSortOrder (Account *acc, const char *str)
|
|
{
|
|
set_kvp_string_path (acc, {"sort-order"}, str);
|
|
}
|
|
|
|
void
|
|
xaccAccountSetSortReversed (Account *acc, gboolean sortreversed)
|
|
{
|
|
set_kvp_boolean_path (acc, {"sort-reversed"}, sortreversed);
|
|
}
|
|
|
|
static void
|
|
qofAccountSetParent (Account *acc, QofInstance *parent)
|
|
{
|
|
Account *parent_acc;
|
|
|
|
g_return_if_fail(GNC_IS_ACCOUNT(acc));
|
|
g_return_if_fail(GNC_IS_ACCOUNT(parent));
|
|
|
|
parent_acc = GNC_ACCOUNT(parent);
|
|
xaccAccountBeginEdit(acc);
|
|
xaccAccountBeginEdit(parent_acc);
|
|
gnc_account_append_child(parent_acc, acc);
|
|
mark_account (parent_acc);
|
|
mark_account (acc);
|
|
xaccAccountCommitEdit(acc);
|
|
xaccAccountCommitEdit(parent_acc);
|
|
}
|
|
|
|
void
|
|
xaccAccountSetNotes (Account *acc, const char *str)
|
|
{
|
|
set_kvp_string_path (acc, {"notes"}, str);
|
|
}
|
|
|
|
|
|
void
|
|
xaccAccountSetAssociatedAccount (Account *acc, const char *tag, const Account* assoc_acct)
|
|
{
|
|
g_return_if_fail (GNC_IS_ACCOUNT(acc));
|
|
g_return_if_fail (tag && *tag);
|
|
|
|
set_kvp_account_path (acc, {"associated-account", tag}, assoc_acct);
|
|
}
|
|
|
|
void
|
|
xaccAccountSetCommodity (Account * acc, gnc_commodity * com)
|
|
{
|
|
AccountPrivate *priv;
|
|
|
|
/* 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 (auto s : priv->splits)
|
|
{
|
|
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)
|
|
{
|
|
if ((!acc) || (!currency)) return;
|
|
|
|
auto s = gnc_commodity_get_unique_name (currency);
|
|
set_kvp_string_path (acc, {"old-currency"}, s);
|
|
|
|
auto book = qof_instance_get_book(acc);
|
|
auto table = gnc_commodity_table_get_table (book);
|
|
auto commodity = gnc_commodity_table_lookup_unique (table, s);
|
|
|
|
if (!commodity)
|
|
gnc_commodity_table_insert (table, currency);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
gnc_account_foreach_descendant (const Account *acc, std::function<void(Account*)> account_cb)
|
|
{
|
|
g_return_if_fail (GNC_IS_ACCOUNT(acc));
|
|
|
|
// children must be a vector copy instead of reference because
|
|
// some callers e.g. xaccAccountTreeScrubLots will modify the
|
|
// children
|
|
auto children = GET_PRIVATE(acc)->children;
|
|
for (auto child : children)
|
|
{
|
|
account_cb (child);
|
|
gnc_account_foreach_descendant (child, account_cb);
|
|
}
|
|
}
|
|
|
|
static void
|
|
account_foreach_descendant_sorted (const Account *acc, std::function<void(Account*)> account_cb)
|
|
{
|
|
g_return_if_fail (GNC_IS_ACCOUNT(acc));
|
|
|
|
auto children = GET_PRIVATE(acc)->children;
|
|
std::sort (children.begin(), children.end(),
|
|
[](auto a, auto b) { return xaccAccountOrder (a, b) < 0; });
|
|
|
|
for (auto child : children)
|
|
{
|
|
account_cb (child);
|
|
account_foreach_descendant_sorted (child, account_cb);
|
|
}
|
|
}
|
|
|
|
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, nullptr);
|
|
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, nullptr);
|
|
}
|
|
}
|
|
cpriv->parent = new_parent;
|
|
ppriv->children.push_back (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, nullptr);
|
|
// qof_event_gen (&new_parent->inst, QOF_EVENT_MODIFY, nullptr);
|
|
|
|
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 = gnc_account_child_index (parent, child);
|
|
|
|
ppriv->children.erase (std::remove (ppriv->children.begin(), ppriv->children.end(), child),
|
|
ppriv->children.end());
|
|
|
|
/* 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 = nullptr;
|
|
|
|
qof_event_gen (&parent->inst, QOF_EVENT_MODIFY, nullptr);
|
|
}
|
|
|
|
Account *
|
|
gnc_account_get_parent (const Account *acc)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), nullptr);
|
|
return GET_PRIVATE(acc)->parent;
|
|
}
|
|
|
|
Account *
|
|
gnc_account_get_root (Account *acc)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), nullptr);
|
|
|
|
while (auto parent = gnc_account_get_parent (acc))
|
|
acc = parent;
|
|
|
|
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 == nullptr);
|
|
}
|
|
|
|
GList *
|
|
gnc_account_get_children (const Account *account)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(account), nullptr);
|
|
auto& children = GET_PRIVATE(account)->children;
|
|
return std::accumulate (children.rbegin(), children.rend(), static_cast<GList*>(nullptr),
|
|
g_list_prepend);
|
|
}
|
|
|
|
GList *
|
|
gnc_account_get_children_sorted (const Account *account)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(account), nullptr);
|
|
return g_list_sort(gnc_account_get_children (account), (GCompareFunc)xaccAccountOrder);
|
|
}
|
|
|
|
gint
|
|
gnc_account_n_children (const Account *account)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(account), 0);
|
|
return GET_PRIVATE(account)->children.size();
|
|
}
|
|
|
|
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);
|
|
auto& children = GET_PRIVATE(parent)->children;
|
|
auto find_it = std::find (children.begin(), children.end(), child);
|
|
return find_it == children.end() ? -1 : std::distance (children.begin(), find_it);
|
|
}
|
|
|
|
Account *
|
|
gnc_account_nth_child (const Account *parent, gint num)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(parent), nullptr);
|
|
if ((size_t)num >= GET_PRIVATE(parent)->children.size())
|
|
return nullptr;
|
|
return static_cast<Account*>(GET_PRIVATE(parent)->children.at (num));
|
|
}
|
|
|
|
gint
|
|
gnc_account_n_descendants (const Account *account)
|
|
{
|
|
int count {0};
|
|
gnc_account_foreach_descendant (account, [&count](auto acc){ count++; });
|
|
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;
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(account), 0);
|
|
|
|
priv = GET_PRIVATE(account);
|
|
if (!priv->children.size())
|
|
return 1;
|
|
|
|
return 1 + std::accumulate (priv->children.begin(), priv->children.end(),
|
|
0, [](auto a, auto b)
|
|
{ return std::max (a, gnc_account_get_tree_depth (b)); });
|
|
}
|
|
|
|
GList *
|
|
gnc_account_get_descendants (const Account *account)
|
|
{
|
|
GList* list = nullptr;
|
|
gnc_account_foreach_descendant (account, [&list](auto a){ list = g_list_prepend (list, a); });
|
|
return g_list_reverse (list);
|
|
}
|
|
|
|
GList *
|
|
gnc_account_get_descendants_sorted (const Account *account)
|
|
{
|
|
GList* list = nullptr;
|
|
account_foreach_descendant_sorted (account, [&list](auto a){ list = g_list_prepend (list, a); });
|
|
return g_list_reverse (list);
|
|
}
|
|
|
|
// because gnc_account_lookup_by_name and gnc_account_lookup_by_code
|
|
// are described in Account.h searching breadth-first until 4.6, and
|
|
// accidentally modified to search depth-first from 4.7
|
|
// onwards. Restore breath-first searching in 4.11 onwards to match
|
|
// previous behaviour and function description in Account.h
|
|
static gpointer
|
|
account_foreach_descendant_breadthfirst_until (const Account *acc,
|
|
AccountCb2 thunk,
|
|
gpointer user_data)
|
|
{
|
|
g_return_val_if_fail (GNC_IS_ACCOUNT(acc), nullptr);
|
|
g_return_val_if_fail (thunk, nullptr);
|
|
|
|
auto& children{GET_PRIVATE(acc)->children};
|
|
|
|
for (auto acc : children)
|
|
if (auto result = thunk (acc, user_data))
|
|
return result;
|
|
|
|
for (auto acc: children)
|
|
if (auto result = account_foreach_descendant_breadthfirst_until (acc, thunk, user_data))
|
|
return result;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
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*)account_foreach_descendant_breadthfirst_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*)account_foreach_descendant_breadthfirst_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)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(parent), nullptr);
|
|
g_return_val_if_fail(names, nullptr);
|
|
|
|
/* Look for the first name in the children. */
|
|
for (auto account : GET_PRIVATE(parent)->children)
|
|
{
|
|
auto priv = GET_PRIVATE(account);
|
|
if (g_strcmp0(priv->accountName, names[0]) == 0)
|
|
{
|
|
/* We found an account. If the next entry is nullptr, there is
|
|
* nothing left in the name, so just return the account. */
|
|
if (names[1] == nullptr)
|
|
return account;
|
|
|
|
/* No children? We're done. */
|
|
if (priv->children.empty())
|
|
return nullptr;
|
|
|
|
/* There's stuff left to search for. Search recursively. */
|
|
if (auto found = gnc_account_lookup_by_full_name_helper(account, &names[1]))
|
|
return found;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
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), nullptr);
|
|
g_return_val_if_fail(name, nullptr);
|
|
|
|
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 account : rpriv->children)
|
|
{
|
|
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 account : rpriv->children)
|
|
{
|
|
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)
|
|
{
|
|
g_return_if_fail(GNC_IS_ACCOUNT(acc));
|
|
g_return_if_fail(thunk);
|
|
std::for_each (GET_PRIVATE(acc)->children.begin(), GET_PRIVATE(acc)->children.end(),
|
|
[user_data, thunk](auto a){ thunk (a, user_data); });
|
|
}
|
|
|
|
void
|
|
gnc_account_foreach_descendant (const Account *acc,
|
|
AccountCb thunk,
|
|
gpointer user_data)
|
|
{
|
|
gnc_account_foreach_descendant (acc, [&](auto acc){ thunk (acc, user_data); });
|
|
}
|
|
|
|
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);
|
|
|
|
for (auto child : GET_PRIVATE(acc)->children)
|
|
{
|
|
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), nullptr);
|
|
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), nullptr);
|
|
return GET_PRIVATE(acc)->accountName;
|
|
}
|
|
|
|
std::vector<const Account*>
|
|
gnc_account_get_all_parents (const Account *account)
|
|
{
|
|
std::vector<const Account*> rv;
|
|
for (auto a = account; !gnc_account_is_root (a); a = gnc_account_get_parent (a))
|
|
rv.push_back (a);
|
|
return rv;
|
|
}
|
|
|
|
gchar *
|
|
gnc_account_get_full_name(const Account *account)
|
|
{
|
|
/* So much for hardening the API. Too many callers to this function don't
|
|
* bother to check if they have a non-nullptr pointer before calling. */
|
|
if (nullptr == account)
|
|
return g_strdup("");
|
|
|
|
/* errors */
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(account), g_strdup(""));
|
|
|
|
auto path{gnc_account_get_all_parents (account)};
|
|
auto seps_size{path.empty() ? 0 : strlen (account_separator) * (path.size() - 1)};
|
|
auto alloc_size{std::accumulate (path.begin(), path.end(), seps_size,
|
|
[](auto sum, auto acc)
|
|
{ return sum + strlen (xaccAccountGetName (acc)); })};
|
|
auto rv = g_new (char, alloc_size + 1);
|
|
auto p = rv;
|
|
|
|
std::for_each (path.rbegin(), path.rend(),
|
|
[&p, rv](auto a)
|
|
{
|
|
if (p != rv)
|
|
p = stpcpy (p, account_separator);
|
|
p = stpcpy (p, xaccAccountGetName (a));
|
|
});
|
|
*p = '\0';
|
|
|
|
return rv;
|
|
}
|
|
|
|
const char *
|
|
xaccAccountGetCode (const Account *acc)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), nullptr);
|
|
return GET_PRIVATE(acc)->accountCode;
|
|
}
|
|
|
|
const char *
|
|
xaccAccountGetDescription (const Account *acc)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), nullptr);
|
|
return GET_PRIVATE(acc)->description;
|
|
}
|
|
|
|
const char *
|
|
xaccAccountGetColor (const Account *acc)
|
|
{
|
|
return get_kvp_string_path (acc, {"color"});
|
|
}
|
|
|
|
const char *
|
|
xaccAccountGetFilter (const Account *acc)
|
|
{
|
|
return get_kvp_string_path (acc, {"filter"});
|
|
}
|
|
|
|
const char *
|
|
xaccAccountGetSortOrder (const Account *acc)
|
|
{
|
|
return get_kvp_string_path (acc, {"sort-order"});
|
|
}
|
|
|
|
gboolean
|
|
xaccAccountGetSortReversed (const Account *acc)
|
|
{
|
|
return get_kvp_boolean_path (acc, {"sort-reversed"});
|
|
}
|
|
|
|
const char *
|
|
xaccAccountGetNotes (const Account *acc)
|
|
{
|
|
return get_kvp_string_path (acc, {"notes"});
|
|
}
|
|
|
|
Account*
|
|
xaccAccountGetAssociatedAccount (const Account *acc, const char *tag)
|
|
{
|
|
g_return_val_if_fail (tag && *tag, nullptr);
|
|
|
|
return get_kvp_account_path (acc, {"associated-account", tag});
|
|
}
|
|
|
|
|
|
gnc_commodity *
|
|
DxaccAccountGetCurrency (const Account *acc)
|
|
{
|
|
if (auto s = get_kvp_string_path (acc, {"old-currency"}))
|
|
{
|
|
auto table = gnc_commodity_table_get_table (qof_instance_get_book(acc));
|
|
return gnc_commodity_table_lookup_unique (table, s);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
gnc_commodity *
|
|
xaccAccountGetCommodity (const Account *acc)
|
|
{
|
|
if (!GNC_IS_ACCOUNT(acc))
|
|
return nullptr;
|
|
return GET_PRIVATE(acc)->commodity;
|
|
}
|
|
|
|
gnc_commodity * gnc_account_get_currency_or_parent(const Account* account)
|
|
{
|
|
g_return_val_if_fail (GNC_IS_ACCOUNT (account), nullptr);
|
|
|
|
for (auto acc = account; acc; acc = gnc_account_get_parent (acc))
|
|
if (auto comm = xaccAccountGetCommodity (acc); gnc_commodity_is_currency (comm))
|
|
return comm;
|
|
|
|
return nullptr; // 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)
|
|
{
|
|
auto today{gnc_time64_get_today_end()};
|
|
std::optional<gnc_numeric> minimum;
|
|
|
|
auto before_today_end = [&minimum, today](const Split *s) -> bool
|
|
{
|
|
auto bal{xaccSplitGetBalance(s)};
|
|
if (!minimum || gnc_numeric_compare (bal, *minimum) < 0)
|
|
minimum = bal;
|
|
return (xaccTransGetDate(xaccSplitGetParent(s)) < today);
|
|
};
|
|
// scan to find today's split, but we're really interested in the
|
|
// minimum balance
|
|
[[maybe_unused]] auto todays_split = gnc_account_find_split (acc, before_today_end, true);
|
|
return minimum ? *minimum : gnc_numeric_zero();
|
|
}
|
|
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static gnc_numeric
|
|
GetBalanceAsOfDate (Account *acc, time64 date, std::function<gnc_numeric(Split*)> split_to_numeric)
|
|
{
|
|
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 */
|
|
|
|
auto is_before_date = [date](auto s) -> bool
|
|
{ return xaccTransGetDate(xaccSplitGetParent(s)) < date; };
|
|
|
|
auto latest_split{gnc_account_find_split (acc, is_before_date, true)};
|
|
return latest_split ? split_to_numeric (latest_split) : gnc_numeric_zero();
|
|
}
|
|
|
|
gnc_numeric
|
|
xaccAccountGetBalanceAsOfDate (Account *acc, time64 date)
|
|
{
|
|
return GetBalanceAsOfDate (acc, date, xaccSplitGetBalance);
|
|
}
|
|
|
|
static gnc_numeric
|
|
xaccAccountGetNoclosingBalanceAsOfDate (Account *acc, time64 date)
|
|
{
|
|
return GetBalanceAsOfDate (acc, date, xaccSplitGetNoclosingBalance);
|
|
}
|
|
|
|
gnc_numeric
|
|
xaccAccountGetReconciledBalanceAsOfDate (Account *acc, time64 date)
|
|
{
|
|
return GetBalanceAsOfDate (acc, date, xaccSplitGetReconciledBalance);
|
|
}
|
|
|
|
/*
|
|
* 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 nullptr, 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, nullptr, 0 };
|
|
cb.balance = balance;
|
|
#else
|
|
CurrencyBalance cb = { report_commodity, balance, fn, nullptr, 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, nullptr, fn, date };
|
|
cb.balance = balance;
|
|
#else
|
|
CurrencyBalance cb = { report_commodity, balance, nullptr, 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, nullptr, recurse);
|
|
b2 = xaccAccountGetBalanceAsOfDateInCurrency(acc, t2, nullptr, 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, nullptr, recurse);
|
|
b2 = xaccAccountGetNoclosingBalanceAsOfDateInCurrency(acc, t2, nullptr, recurse);
|
|
return gnc_numeric_sub(b2, b1, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
const gnc_commodity *currency;
|
|
gnc_numeric balanceChange;
|
|
time64 t1;
|
|
time64 t2;
|
|
} CurrencyBalanceChange;
|
|
|
|
static void
|
|
xaccAccountBalanceChangeHelper (Account *acc, gpointer data)
|
|
{
|
|
CurrencyBalanceChange *cbdiff = static_cast<CurrencyBalanceChange*>(data);
|
|
|
|
gnc_numeric b1, b2;
|
|
b1 = GetBalanceAsOfDate(acc, cbdiff->t1, xaccSplitGetNoclosingBalance);
|
|
b2 = GetBalanceAsOfDate(acc, cbdiff->t2, xaccSplitGetNoclosingBalance);
|
|
gnc_numeric balanceChange = gnc_numeric_sub(b2, b1, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
|
|
gnc_numeric balanceChange_conv = xaccAccountConvertBalanceToCurrencyAsOfDate(acc, balanceChange, xaccAccountGetCommodity(acc), cbdiff->currency, cbdiff->t2);
|
|
cbdiff->balanceChange = gnc_numeric_add (cbdiff->balanceChange, balanceChange_conv,
|
|
gnc_commodity_get_fraction (cbdiff->currency),
|
|
GNC_HOW_RND_ROUND_HALF_UP);
|
|
}
|
|
|
|
gnc_numeric
|
|
xaccAccountGetNoclosingBalanceChangeInCurrencyForPeriod (Account *acc, time64 t1,
|
|
time64 t2, gboolean recurse)
|
|
{
|
|
|
|
|
|
gnc_numeric b1, b2;
|
|
b1 = GetBalanceAsOfDate(acc, t1, xaccSplitGetNoclosingBalance);
|
|
b2 = GetBalanceAsOfDate(acc, t2, xaccSplitGetNoclosingBalance);
|
|
gnc_numeric balanceChange = gnc_numeric_sub(b2, b1, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
|
|
|
|
gnc_commodity *report_commodity = xaccAccountGetCommodity(acc);
|
|
CurrencyBalanceChange cbdiff = { report_commodity, balanceChange, t1, t2 };
|
|
|
|
if(recurse)
|
|
{
|
|
gnc_account_foreach_descendant (acc, xaccAccountBalanceChangeHelper, &cbdiff);
|
|
balanceChange = cbdiff.balanceChange;
|
|
}
|
|
return balanceChange;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
const SplitsVec&
|
|
xaccAccountGetSplits (const Account *account)
|
|
{
|
|
static const SplitsVec empty;
|
|
g_return_val_if_fail (GNC_IS_ACCOUNT(account), empty);
|
|
return GET_PRIVATE(account)->splits;
|
|
}
|
|
|
|
SplitList *
|
|
xaccAccountGetSplitList (const Account *acc)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), nullptr);
|
|
auto priv{GET_PRIVATE(acc)};
|
|
return std::accumulate (priv->splits.rbegin(), priv->splits.rend(),
|
|
static_cast<GList*>(nullptr), g_list_prepend);
|
|
}
|
|
|
|
size_t
|
|
xaccAccountGetSplitsSize (const Account *account)
|
|
{
|
|
g_return_val_if_fail (GNC_IS_ACCOUNT(account), 0);
|
|
return GNC_IS_ACCOUNT(account) ? GET_PRIVATE(account)->splits.size() : 0;
|
|
}
|
|
|
|
gboolean gnc_account_and_descendants_empty (Account *acc)
|
|
{
|
|
g_return_val_if_fail (GNC_IS_ACCOUNT (acc), FALSE);
|
|
auto priv = GET_PRIVATE (acc);
|
|
if (!priv->splits.empty()) return FALSE;
|
|
return std::all_of (priv->children.begin(), priv->children.end(),
|
|
gnc_account_and_descendants_empty);
|
|
}
|
|
|
|
LotList *
|
|
xaccAccountGetLotList (const Account *acc)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), nullptr);
|
|
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 = nullptr;
|
|
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), nullptr);
|
|
|
|
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)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), nullptr);
|
|
g_return_val_if_fail(proc, nullptr);
|
|
|
|
for (auto node = GET_PRIVATE(acc)->lots; node; node = node->next)
|
|
if (auto result = proc(GNC_LOT(node->data), data))
|
|
return result;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
/* These functions use interchange gint64 and gboolean. Is that right? */
|
|
gboolean
|
|
xaccAccountGetTaxRelated (const Account *acc)
|
|
{
|
|
return get_kvp_boolean_path(acc, {"tax-related"});
|
|
}
|
|
|
|
void
|
|
xaccAccountSetTaxRelated (Account *acc, gboolean tax_related)
|
|
{
|
|
set_kvp_boolean_path(acc, {"tax-related"}, tax_related);
|
|
}
|
|
|
|
const char *
|
|
xaccAccountGetTaxUSCode (const Account *acc)
|
|
{
|
|
return get_kvp_string_path (acc, {"tax-US", "code"});
|
|
}
|
|
|
|
void
|
|
xaccAccountSetTaxUSCode (Account *acc, const char *code)
|
|
{
|
|
set_kvp_string_path (acc, {"tax-US", "code"}, code);
|
|
}
|
|
|
|
const char *
|
|
xaccAccountGetTaxUSPayerNameSource (const Account *acc)
|
|
{
|
|
return get_kvp_string_path (acc, {"tax-US", "payer-name-source"});
|
|
}
|
|
|
|
void
|
|
xaccAccountSetTaxUSPayerNameSource (Account *acc, const char *source)
|
|
{
|
|
set_kvp_string_path (acc, {"tax-US", "payer-name-source"}, source);
|
|
}
|
|
|
|
gint64
|
|
xaccAccountGetTaxUSCopyNumber (const Account *acc)
|
|
{
|
|
auto copy_number = get_kvp_int64_path (acc, {"tax-US", "copy-number"});
|
|
return (copy_number && (*copy_number != 0)) ? *copy_number : 1;
|
|
}
|
|
|
|
void
|
|
xaccAccountSetTaxUSCopyNumber (Account *acc, gint64 copy_number)
|
|
{
|
|
if (copy_number != 0)
|
|
set_kvp_int64_path (acc, {"tax-US", "copy-number"}, copy_number);
|
|
else
|
|
/* deletes KVP if it exists */
|
|
set_kvp_int64_path (acc, {"tax-US", "copy-number"}, std::nullopt);
|
|
}
|
|
|
|
/*********************************************************************\
|
|
\ ********************************************************************/
|
|
|
|
|
|
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 get_kvp_boolean_path(acc, {"placeholder"});
|
|
}
|
|
|
|
void
|
|
xaccAccountSetPlaceholder (Account *acc, gboolean val)
|
|
{
|
|
set_kvp_boolean_path(acc, {"placeholder"}, val);
|
|
}
|
|
|
|
gboolean
|
|
xaccAccountGetAppendText (const Account *acc)
|
|
{
|
|
return get_kvp_boolean_path(acc, {"import-append-text"});
|
|
}
|
|
|
|
void
|
|
xaccAccountSetAppendText (Account *acc, gboolean val)
|
|
{
|
|
set_kvp_boolean_path(acc, {"import-append-text"}, val);
|
|
}
|
|
|
|
gboolean
|
|
xaccAccountGetIsOpeningBalance (const Account *acc)
|
|
{
|
|
g_return_val_if_fail (GNC_IS_ACCOUNT(acc), false);
|
|
if (GET_PRIVATE(acc)->type != ACCT_TYPE_EQUITY)
|
|
return false;
|
|
|
|
return !g_strcmp0 (get_kvp_string_path (acc, {"equity-type"}), "opening-balance");
|
|
}
|
|
|
|
void
|
|
xaccAccountSetIsOpeningBalance (Account *acc, gboolean val)
|
|
{
|
|
g_return_if_fail (GNC_IS_ACCOUNT(acc));
|
|
if (GET_PRIVATE(acc)->type != ACCT_TYPE_EQUITY)
|
|
return;
|
|
set_kvp_string_path(acc, {"equity-type"}, val ? "opening-balance" : nullptr);
|
|
}
|
|
|
|
GNCPlaceholderType
|
|
xaccAccountGetDescendantPlaceholder (const Account *acc)
|
|
{
|
|
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), PLACEHOLDER_NONE);
|
|
if (xaccAccountGetPlaceholder(acc)) return PLACEHOLDER_THIS;
|
|
|
|
return gnc_account_foreach_descendant_until (acc, (AccountCb2)xaccAccountGetPlaceholder, nullptr)
|
|
? PLACEHOLDER_CHILD : PLACEHOLDER_NONE;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
gboolean
|
|
xaccAccountGetAutoInterest (const Account *acc)
|
|
{
|
|
return get_kvp_boolean_path (acc, {KEY_RECONCILE_INFO, "auto-interest-transfer"});
|
|
}
|
|
|
|
void
|
|
xaccAccountSetAutoInterest (Account *acc, gboolean val)
|
|
{
|
|
set_kvp_boolean_path (acc, {KEY_RECONCILE_INFO, "auto-interest-transfer"}, val);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
gboolean
|
|
xaccAccountGetHidden (const Account *acc)
|
|
{
|
|
return get_kvp_boolean_path (acc, {"hidden"});
|
|
}
|
|
|
|
void
|
|
xaccAccountSetHidden (Account *acc, gboolean val)
|
|
{
|
|
set_kvp_boolean_path (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) != nullptr)
|
|
{
|
|
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(nullptr);
|
|
}
|
|
|
|
#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)
|
|
{
|
|
gboolean retval = FALSE;
|
|
auto date = get_kvp_int64_path (acc, {KEY_RECONCILE_INFO, "last-date"});
|
|
|
|
if (date)
|
|
{
|
|
if (last_date)
|
|
*last_date = *date;
|
|
retval = TRUE;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
xaccAccountSetReconcileLastDate (Account *acc, time64 last_date)
|
|
{
|
|
set_kvp_int64_path (acc, {KEY_RECONCILE_INFO, "last-date"}, last_date);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
gboolean
|
|
xaccAccountGetReconcileLastInterval (const Account *acc,
|
|
int *months, int *days)
|
|
{
|
|
if (!acc) return FALSE;
|
|
auto m{get_kvp_int64_path (acc, {KEY_RECONCILE_INFO, "last-interval", "months"})};
|
|
auto d{get_kvp_int64_path (acc, {KEY_RECONCILE_INFO, "last-interval", "days"})};
|
|
if (m && d)
|
|
{
|
|
if (months)
|
|
*months = *m;
|
|
if (days)
|
|
*days = *d;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
xaccAccountSetReconcileLastInterval (Account *acc, int months, int days)
|
|
{
|
|
set_kvp_int64_path (acc, {KEY_RECONCILE_INFO, "last-interval", "months"}, months);
|
|
set_kvp_int64_path (acc, {KEY_RECONCILE_INFO, "last-interval", "days"}, days);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
gboolean
|
|
xaccAccountGetReconcilePostponeDate (const Account *acc, time64 *postpone_date)
|
|
{
|
|
if (auto date = get_kvp_int64_path (acc, {KEY_RECONCILE_INFO, KEY_POSTPONE, "date"}))
|
|
{
|
|
if (postpone_date)
|
|
*postpone_date = *date;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
xaccAccountSetReconcilePostponeDate (Account *acc, time64 postpone_date)
|
|
{
|
|
set_kvp_int64_path (acc, {KEY_RECONCILE_INFO, KEY_POSTPONE, "date"}, postpone_date);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
gboolean
|
|
xaccAccountGetReconcilePostponeBalance (const Account *acc,
|
|
gnc_numeric *balance)
|
|
{
|
|
if (auto bal = get_kvp_gnc_numeric_path (acc, {KEY_RECONCILE_INFO, KEY_POSTPONE, "balance"}))
|
|
{
|
|
if (balance)
|
|
*balance = *bal;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
xaccAccountSetReconcilePostponeBalance (Account *acc, gnc_numeric balance)
|
|
{
|
|
set_kvp_gnc_numeric_path (acc, {KEY_RECONCILE_INFO, KEY_POSTPONE, "balance"}, balance);
|
|
}
|
|
|
|
/********************************************************************\
|
|
|
|
\********************************************************************/
|
|
|
|
void
|
|
xaccAccountClearReconcilePostpone (Account *acc)
|
|
{
|
|
set_kvp_gnc_numeric_path (acc, {KEY_RECONCILE_INFO, KEY_POSTPONE}, {});
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
const char *
|
|
xaccAccountGetLastNum (const Account *acc)
|
|
{
|
|
return get_kvp_string_path (acc, {"last-num"});
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
xaccAccountSetLastNum (Account *acc, const char *num)
|
|
{
|
|
set_kvp_string_path (acc, {"last-num"}, num);
|
|
}
|
|
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static bool
|
|
get_balance_limit (const Account* acc, const std::string& key, gnc_numeric* balance)
|
|
{
|
|
auto limit = get_kvp_gnc_numeric_path (acc, {KEY_BALANCE_LIMIT, key});
|
|
if (limit)
|
|
*balance = gnc_numeric_create (limit->num, limit->denom);
|
|
return limit.has_value();
|
|
}
|
|
|
|
static void
|
|
set_balance_limit (Account *acc, const std::string& key, std::optional<gnc_numeric> balance)
|
|
{
|
|
if (balance && gnc_numeric_check (*balance))
|
|
return;
|
|
set_kvp_gnc_numeric_path (acc, {KEY_BALANCE_LIMIT, key}, balance);
|
|
}
|
|
|
|
gboolean
|
|
xaccAccountGetHigherBalanceLimit (const Account *acc,
|
|
gnc_numeric *balance)
|
|
{
|
|
return get_balance_limit (acc, KEY_BALANCE_HIGHER_LIMIT_VALUE, balance);
|
|
}
|
|
|
|
gboolean
|
|
xaccAccountGetLowerBalanceLimit (const Account *acc,
|
|
gnc_numeric *balance)
|
|
{
|
|
return get_balance_limit (acc, KEY_BALANCE_LOWER_LIMIT_VALUE, balance);
|
|
}
|
|
|
|
void
|
|
xaccAccountSetHigherBalanceLimit (Account *acc, gnc_numeric balance)
|
|
{
|
|
set_balance_limit (acc, KEY_BALANCE_HIGHER_LIMIT_VALUE, balance);
|
|
}
|
|
|
|
void
|
|
xaccAccountSetLowerBalanceLimit (Account *acc, gnc_numeric balance)
|
|
{
|
|
set_balance_limit (acc, KEY_BALANCE_LOWER_LIMIT_VALUE, balance);
|
|
}
|
|
|
|
void
|
|
xaccAccountClearHigherBalanceLimit (Account *acc)
|
|
{
|
|
set_balance_limit (acc, KEY_BALANCE_HIGHER_LIMIT_VALUE, {});
|
|
}
|
|
|
|
void
|
|
xaccAccountClearLowerBalanceLimit (Account *acc)
|
|
{
|
|
set_balance_limit (acc, KEY_BALANCE_LOWER_LIMIT_VALUE, {});
|
|
}
|
|
|
|
gboolean
|
|
xaccAccountGetIncludeSubAccountBalances (const Account *acc)
|
|
{
|
|
return get_kvp_boolean_path (acc, {KEY_BALANCE_LIMIT, KEY_BALANCE_INCLUDE_SUB_ACCTS});
|
|
}
|
|
|
|
void
|
|
xaccAccountSetIncludeSubAccountBalances (Account *acc, gboolean inc_sub)
|
|
{
|
|
set_kvp_boolean_path (acc, {KEY_BALANCE_LIMIT, KEY_BALANCE_INCLUDE_SUB_ACCTS}, inc_sub);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static Account *
|
|
GetOrMakeOrphanAccount (Account *root, gnc_commodity * currency)
|
|
{
|
|
char * accname;
|
|
Account * acc;
|
|
|
|
g_return_val_if_fail (root, nullptr);
|
|
|
|
/* build the account name */
|
|
if (!currency)
|
|
{
|
|
PERR ("No currency specified!");
|
|
return nullptr;
|
|
}
|
|
|
|
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 == nullptr)
|
|
{
|
|
/* 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)
|
|
{
|
|
Path path {KEY_LOT_MGMT, "gains-acct", gnc_commodity_get_unique_name (curr)};
|
|
auto gains_account = get_kvp_account_path (acc, path);
|
|
|
|
if (gains_account == nullptr) /* No gains account for this currency */
|
|
{
|
|
gains_account = GetOrMakeOrphanAccount (gnc_account_get_root (acc), curr);
|
|
set_kvp_account_path (acc, path, gains_account);
|
|
}
|
|
|
|
return gains_account;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
dxaccAccountSetPriceSrc(Account *acc, const char *src)
|
|
{
|
|
if (!acc) return;
|
|
|
|
if (xaccAccountIsPriced(acc))
|
|
set_kvp_string_path (acc, {"old-price-source"}, src);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
const char*
|
|
dxaccAccountGetPriceSrc(const Account *acc)
|
|
{
|
|
if (!acc) return nullptr;
|
|
|
|
if (!xaccAccountIsPriced(acc)) return nullptr;
|
|
|
|
return get_kvp_string_path (acc, {"old-price-source"});
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
dxaccAccountSetQuoteTZ(Account *acc, const char *tz)
|
|
{
|
|
if (!acc) return;
|
|
if (!xaccAccountIsPriced(acc)) return;
|
|
set_kvp_string_path (acc, {"old-quote-tz"}, tz);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
const char*
|
|
dxaccAccountGetQuoteTZ(const Account *acc)
|
|
{
|
|
if (!acc) return nullptr;
|
|
if (!xaccAccountIsPriced(acc)) return nullptr;
|
|
return get_kvp_string_path (acc, {"old-quote-tz"});
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
xaccAccountSetReconcileChildrenStatus(Account *acc, gboolean status)
|
|
{
|
|
/* 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.
|
|
*/
|
|
set_kvp_int64_path (acc, {KEY_RECONCILE_INFO, KEY_INCLUDE_CHILDREN}, status);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
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
|
|
*/
|
|
return get_kvp_boolean_path (acc, {KEY_RECONCILE_INFO, KEY_INCLUDE_CHILDREN});
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
Split *
|
|
xaccAccountFindSplitByDesc(const Account *acc, const char *description)
|
|
{
|
|
auto has_description = [description](const Split* s) -> bool
|
|
{ return !g_strcmp0 (description, xaccTransGetDescription (xaccSplitGetParent (s))); };
|
|
return gnc_account_find_split (acc, has_description, true);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
auto split = xaccAccountFindSplitByDesc (acc, description);
|
|
return split ? xaccSplitGetParent (split) : nullptr;
|
|
}
|
|
|
|
/* ================================================================ */
|
|
/* Concatenation, Merging functions */
|
|
|
|
void
|
|
gnc_account_join_children (Account *to_parent, Account *from_parent)
|
|
{
|
|
|
|
/* errors */
|
|
g_return_if_fail(GNC_IS_ACCOUNT(to_parent));
|
|
g_return_if_fail(GNC_IS_ACCOUNT(from_parent));
|
|
|
|
/* optimizations */
|
|
auto from_priv = GET_PRIVATE(from_parent);
|
|
if (from_priv->children.empty())
|
|
return;
|
|
|
|
ENTER (" ");
|
|
auto children = from_priv->children;
|
|
for (auto child : children)
|
|
gnc_account_append_child(to_parent, child);
|
|
LEAVE (" ");
|
|
}
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
gnc_account_merge_children (Account *parent)
|
|
{
|
|
g_return_if_fail(GNC_IS_ACCOUNT(parent));
|
|
|
|
auto ppriv = GET_PRIVATE(parent);
|
|
for (auto it_a = ppriv->children.begin(); it_a != ppriv->children.end(); it_a++)
|
|
{
|
|
auto acc_a = *it_a;
|
|
auto priv_a = GET_PRIVATE(acc_a);
|
|
for (auto it_b = std::next(it_a); it_b != ppriv->children.end(); it_b++)
|
|
{
|
|
auto acc_b = *it_b;
|
|
auto 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.empty())
|
|
{
|
|
auto work = priv_b->children;
|
|
for (auto w : work)
|
|
gnc_account_append_child (acc_a, w);
|
|
|
|
qof_event_gen (&acc_a->inst, QOF_EVENT_MODIFY, nullptr);
|
|
qof_event_gen (&acc_b->inst, QOF_EVENT_MODIFY, nullptr);
|
|
}
|
|
|
|
/* recurse to do the children's children */
|
|
gnc_account_merge_children (acc_a);
|
|
|
|
/* consolidate transactions */
|
|
while (!priv_b->splits.empty())
|
|
xaccSplitSetAccount (priv_b->splits.front(), acc_a);
|
|
|
|
/* move back one before removal. next iteration around the loop
|
|
* will get the node after node_b */
|
|
it_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 */
|
|
|
|
|
|
static void
|
|
xaccSplitsBeginStagedTransactionTraversals (SplitsVec& splits)
|
|
{
|
|
for (auto s : splits)
|
|
{
|
|
Transaction *trans = s->parent;
|
|
|
|
if (trans)
|
|
trans->marker = 0;
|
|
}
|
|
}
|
|
|
|
/* original function */
|
|
void
|
|
xaccAccountBeginStagedTransactionTraversals (const Account *account)
|
|
{
|
|
if (!account)
|
|
return;
|
|
xaccSplitsBeginStagedTransactionTraversals(GET_PRIVATE (account)->splits);
|
|
}
|
|
|
|
gboolean
|
|
xaccTransactionTraverse (Transaction *trans, int stage)
|
|
{
|
|
if (trans == nullptr) return FALSE;
|
|
|
|
if (trans->marker < stage)
|
|
{
|
|
trans->marker = stage;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* Replacement for xaccGroupBeginStagedTransactionTraversals */
|
|
void
|
|
gnc_account_tree_begin_staged_transaction_traversals (Account *account)
|
|
{
|
|
auto do_one_account = [](auto acc)
|
|
{ gnc_account_foreach_split (acc, [](auto s){ s->parent->marker = 0; }); };
|
|
gnc_account_foreach_descendant (account, do_one_account);
|
|
}
|
|
|
|
int
|
|
xaccAccountStagedTransactionTraversal (const Account *acc,
|
|
unsigned int stage,
|
|
TransactionCallback thunk,
|
|
void *cb_data)
|
|
{
|
|
if (!acc) return 0;
|
|
|
|
// iterate on copy of splits. some callers modify the splitsvec.
|
|
auto splits = GET_PRIVATE(acc)->splits;
|
|
for (auto s : splits)
|
|
{
|
|
auto trans = s->parent;
|
|
if (trans && (trans->marker < stage))
|
|
{
|
|
trans->marker = stage;
|
|
if (thunk)
|
|
{
|
|
auto 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;
|
|
Transaction *trans;
|
|
int retval;
|
|
|
|
if (!acc) return 0;
|
|
|
|
/* depth first traversal */
|
|
priv = GET_PRIVATE(acc);
|
|
for (auto acc_p : priv->children)
|
|
{
|
|
retval = gnc_account_tree_staged_transaction_traversal(acc_p, stage, thunk, cb_data);
|
|
if (retval) return retval;
|
|
}
|
|
|
|
/* Now this account */
|
|
for (auto s : priv->splits)
|
|
{
|
|
trans = s->parent;
|
|
if (trans && (trans->marker < stage))
|
|
{
|
|
trans->marker = stage;
|
|
if (thunk)
|
|
{
|
|
retval = thunk(trans, cb_data);
|
|
if (retval) return retval;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
time64
|
|
gnc_account_get_earliest_date (const Account* account)
|
|
{
|
|
g_return_val_if_fail (GNC_IS_ACCOUNT(account), INT64_MAX);
|
|
const auto& splits = xaccAccountGetSplits (account);
|
|
return splits.empty() ? INT64_MAX : xaccTransGetDate (xaccSplitGetParent (splits.front()));
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
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"
|
|
|
|
/* Look up an Account in the map */
|
|
Account*
|
|
gnc_account_imap_find_account (Account *acc,
|
|
const char *category,
|
|
const char *key)
|
|
{
|
|
if (!acc || !key) return nullptr;
|
|
std::vector<std::string> path {IMAP_FRAME};
|
|
if (category)
|
|
path.push_back (category);
|
|
path.push_back (key);
|
|
return get_kvp_account_path (acc, path);
|
|
}
|
|
|
|
Account*
|
|
gnc_account_imap_find_any (QofBook *book, const char* category, const char *key)
|
|
{
|
|
Account *account = nullptr;
|
|
|
|
/* Get list of Accounts */
|
|
auto root = gnc_book_get_root_account (book);
|
|
auto accts = gnc_account_get_descendants_sorted (root);
|
|
|
|
/* Go through list of accounts */
|
|
for (auto ptr = accts; ptr; ptr = g_list_next (ptr))
|
|
{
|
|
auto tmp_acc = static_cast<Account*> (ptr->data);
|
|
|
|
if (gnc_account_imap_find_account (tmp_acc, category, key))
|
|
{
|
|
account = tmp_acc;
|
|
break;
|
|
}
|
|
}
|
|
g_list_free (accts);
|
|
|
|
return account;
|
|
}
|
|
|
|
/* Store an Account in the map */
|
|
void
|
|
gnc_account_imap_add_account (Account *acc,
|
|
const char *category,
|
|
const char *key,
|
|
Account *added_acc)
|
|
{
|
|
if (!acc || !key || !added_acc || !*key) return;
|
|
|
|
auto path = category ? Path{IMAP_FRAME, category, key} : Path{IMAP_FRAME, key};
|
|
|
|
set_kvp_account_path (acc, path, added_acc);
|
|
}
|
|
|
|
/* Remove a reference to an Account in the map */
|
|
void
|
|
gnc_account_imap_delete_account (Account *acc,
|
|
const char *category,
|
|
const char *key)
|
|
{
|
|
if (!acc || !key) return;
|
|
|
|
auto path = category ? Path{IMAP_FRAME, category, key} : Path{IMAP_FRAME, key};
|
|
if (qof_instance_has_path_slot (QOF_INSTANCE (acc), path))
|
|
{
|
|
qof_instance_slot_path_delete (QOF_INSTANCE (acc), path);
|
|
if (category)
|
|
qof_instance_slot_path_delete_if_empty (QOF_INSTANCE (acc), {IMAP_FRAME, category});
|
|
qof_instance_slot_path_delete_if_empty (QOF_INSTANCE (acc), {IMAP_FRAME});
|
|
}
|
|
qof_instance_set_dirty (QOF_INSTANCE (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(Account* acc, 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 (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 (Account *acc, GList *tokens)
|
|
{
|
|
if (!acc)
|
|
return nullptr;
|
|
auto book = gnc_account_get_book(acc);
|
|
check_import_map_data (book);
|
|
auto first_pass = get_first_pass_probabilities(acc, 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), book);
|
|
return account;
|
|
}
|
|
|
|
static void
|
|
change_imap_entry (Account *acc, std::string const & path, int64_t token_count)
|
|
{
|
|
PINFO("Source Account is '%s', Count is '%" G_GINT64_FORMAT "'",
|
|
xaccAccountGetName (acc), token_count);
|
|
|
|
// check for existing guid entry
|
|
if (auto existing_token_count = get_kvp_int64_path (acc, {path}))
|
|
{
|
|
PINFO("found existing value of '%" G_GINT64_FORMAT "'", *existing_token_count);
|
|
token_count += *existing_token_count;
|
|
}
|
|
|
|
// Add or Update the entry based on guid
|
|
set_kvp_int64_path (acc, {path}, token_count);
|
|
}
|
|
|
|
/** Updates the imap for a given account using a list of tokens */
|
|
void
|
|
gnc_account_imap_add_account_bayes (Account *acc,
|
|
GList *tokens,
|
|
Account *added_acc)
|
|
{
|
|
GList *current_token;
|
|
gint64 token_count;
|
|
char *account_fullname;
|
|
char *guid_string;
|
|
|
|
ENTER(" ");
|
|
if (!acc)
|
|
{
|
|
LEAVE(" ");
|
|
return;
|
|
}
|
|
check_import_map_data (gnc_account_get_book(acc));
|
|
|
|
g_return_if_fail (added_acc != nullptr);
|
|
account_fullname = gnc_account_get_full_name(added_acc);
|
|
xaccAccountBeginEdit (acc);
|
|
|
|
PINFO("account name: '%s'", account_fullname);
|
|
|
|
guid_string = guid_to_string (xaccAccountGetGUID (added_acc));
|
|
|
|
/* process each token in the list */
|
|
for (current_token = g_list_first(tokens); current_token;
|
|
current_token = current_token->next)
|
|
{
|
|
char* token = static_cast<char*>(current_token->data);
|
|
/* 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 (!token || !token[0])
|
|
continue;
|
|
/* start off with one token for this account */
|
|
token_count = 1;
|
|
PINFO("adding token '%s'", token);
|
|
auto path = std::string {IMAP_FRAME_BAYES} + '/' + token + '/' + guid_string;
|
|
/* change the imap entry for the account */
|
|
change_imap_entry (acc, path, token_count);
|
|
}
|
|
/* free up the account fullname and guid string */
|
|
xaccAccountCommitEdit (acc);
|
|
gnc_features_set_used (gnc_account_get_book(acc), GNC_FEATURE_GUID_FLAT_BAYESIAN);
|
|
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 = nullptr;
|
|
gchar *guid_string = nullptr;
|
|
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);
|
|
}
|
|
|
|
void gnc_account_imap_info_destroy (GncImapInfo* imapInfo)
|
|
{
|
|
g_free (imapInfo->head);
|
|
g_free (imapInfo->category);
|
|
g_free (imapInfo->match_string);
|
|
g_free (imapInfo->count);
|
|
g_free (imapInfo);
|
|
}
|
|
|
|
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 = nullptr;
|
|
|
|
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);
|
|
}
|
|
g_free (imapInfo.head);
|
|
g_free (imapInfo.category);
|
|
return g_list_reverse(imapInfo.list);
|
|
}
|
|
|
|
/*******************************************************************************/
|
|
|
|
gchar *
|
|
gnc_account_get_map_entry (Account *acc, const char *head, const char *category)
|
|
{
|
|
return g_strdup (category ?
|
|
get_kvp_string_path (acc, {head, category}) :
|
|
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 != nullptr)
|
|
{
|
|
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 != nullptr)
|
|
{
|
|
auto slots = qof_instance_get_slots_prefix (QOF_INSTANCE (acc), IMAP_FRAME_BAYES);
|
|
if (!slots.size()) return;
|
|
xaccAccountBeginEdit (acc);
|
|
for (auto const & entry : slots)
|
|
{
|
|
qof_instance_slot_path_delete (QOF_INSTANCE (acc), {entry.first});
|
|
}
|
|
qof_instance_set_dirty (QOF_INSTANCE(acc));
|
|
xaccAccountCommitEdit (acc);
|
|
}
|
|
}
|
|
|
|
/* ================================================================ */
|
|
/* QofObject function implementation and registration */
|
|
|
|
static void
|
|
destroy_all_child_accounts (Account *acc, gpointer data)
|
|
{
|
|
xaccAccountBeginEdit (acc);
|
|
xaccAccountDestroy (acc);
|
|
}
|
|
|
|
static void
|
|
gnc_account_book_end(QofBook* book)
|
|
{
|
|
Account *root_account = gnc_book_get_root_account (book);
|
|
GList *accounts;
|
|
|
|
if (!root_account)
|
|
return;
|
|
|
|
accounts = gnc_account_get_descendants (root_account);
|
|
|
|
if (accounts)
|
|
{
|
|
accounts = g_list_reverse (accounts);
|
|
g_list_foreach (accounts, (GFunc)destroy_all_child_accounts, nullptr);
|
|
g_list_free (accounts);
|
|
}
|
|
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 = ) nullptr,
|
|
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, nullptr
|
|
},
|
|
{
|
|
ACCOUNT_BALANCE_, QOF_TYPE_NUMERIC,
|
|
(QofAccessFunc) xaccAccountGetBalance, nullptr
|
|
},
|
|
{
|
|
ACCOUNT_CLEARED_, QOF_TYPE_NUMERIC,
|
|
(QofAccessFunc) xaccAccountGetClearedBalance, nullptr
|
|
},
|
|
{
|
|
ACCOUNT_RECONCILED_, QOF_TYPE_NUMERIC,
|
|
(QofAccessFunc) xaccAccountGetReconciledBalance, nullptr
|
|
},
|
|
{
|
|
ACCOUNT_TYPE_, QOF_TYPE_STRING,
|
|
(QofAccessFunc) qofAccountGetTypeString,
|
|
(QofSetterFunc) qofAccountSetType
|
|
},
|
|
{
|
|
ACCOUNT_FUTURE_MINIMUM_, QOF_TYPE_NUMERIC,
|
|
(QofAccessFunc) xaccAccountGetProjectedMinimumBalance, nullptr
|
|
},
|
|
{
|
|
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, nullptr
|
|
},
|
|
{
|
|
QOF_PARAM_GUID, QOF_TYPE_GUID,
|
|
(QofAccessFunc) qof_instance_get_guid, nullptr
|
|
},
|
|
{ nullptr },
|
|
};
|
|
|
|
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 =========================== */
|