[Account.hpp][API] xaccAccountGetSplits and gnc_account_find_split

This commit is contained in:
Christopher Lam 2024-04-13 21:35:35 +08:00
parent a54caaab61
commit 3f7a5a8267
8 changed files with 197 additions and 236 deletions

View File

@ -34,6 +34,7 @@
#include <string.h> #include <string.h>
#include "AccountP.hpp" #include "AccountP.hpp"
#include "Account.hpp"
#include "Split.h" #include "Split.h"
#include "Transaction.h" #include "Transaction.h"
#include "TransactionP.h" #include "TransactionP.h"
@ -331,7 +332,7 @@ gnc_account_init(Account* acc)
priv->lower_balance_limit = {}; priv->lower_balance_limit = {};
priv->include_sub_account_balances = {}; priv->include_sub_account_balances = {};
priv->splits = nullptr; priv->splits.clear();
priv->sort_dirty = FALSE; priv->sort_dirty = FALSE;
} }
@ -1136,6 +1137,29 @@ xaccInitAccount (Account * acc, QofBook *book)
/********************************************************************\ /********************************************************************\
\********************************************************************/ \********************************************************************/
Split*
gnc_account_find_split (const Account *acc, std::function<bool(const Split*)> predicate,
bool reverse)
{
if (!GNC_IS_ACCOUNT (acc))
return nullptr;
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 * QofBook *
gnc_account_get_book(const Account *account) gnc_account_get_book(const Account *account)
{ {
@ -1368,22 +1392,18 @@ xaccFreeAccount (Account *acc)
/* NB there shouldn't be any splits by now ... they should /* NB there shouldn't be any splits by now ... they should
* have been all been freed by CommitEdit(). We can remove this * have been all been freed by CommitEdit(). We can remove this
* check once we know the warning isn't occurring any more. */ * check once we know the warning isn't occurring any more. */
if (priv->splits) if (!priv->splits.empty())
{ {
GList *slist;
PERR (" instead of calling xaccFreeAccount(), please call\n" PERR (" instead of calling xaccFreeAccount(), please call\n"
" xaccAccountBeginEdit(); xaccAccountDestroy();\n"); " xaccAccountBeginEdit(); xaccAccountDestroy();\n");
qof_instance_reset_editlevel(acc); qof_instance_reset_editlevel(acc);
slist = g_list_copy(priv->splits); for (auto s : priv->splits)
for (lp = slist; lp; lp = lp->next)
{ {
Split *s = (Split *) lp->data;
g_assert(xaccSplitGetAccount(s) == acc); g_assert(xaccSplitGetAccount(s) == acc);
xaccSplitDestroy (s); xaccSplitDestroy (s);
} }
g_list_free(slist);
/* Nothing here (or in xaccAccountCommitEdit) nullptrs priv->splits, so this asserts every time. /* Nothing here (or in xaccAccountCommitEdit) nullptrs priv->splits, so this asserts every time.
g_assert(priv->splits == nullptr); g_assert(priv->splits == nullptr);
*/ */
@ -1416,6 +1436,7 @@ xaccFreeAccount (Account *acc)
priv->type = ACCT_TYPE_NONE; priv->type = ACCT_TYPE_NONE;
gnc_commodity_decrement_usage_count(priv->commodity); gnc_commodity_decrement_usage_count(priv->commodity);
priv->commodity = nullptr; priv->commodity = nullptr;
priv->splits.clear();
priv->balance_dirty = FALSE; priv->balance_dirty = FALSE;
priv->sort_dirty = FALSE; priv->sort_dirty = FALSE;
@ -1483,7 +1504,6 @@ xaccAccountCommitEdit (Account *acc)
priv = GET_PRIVATE(acc); priv = GET_PRIVATE(acc);
if (qof_instance_get_destroying(acc)) if (qof_instance_get_destroying(acc))
{ {
GList *lp, *slist;
QofCollection *col; QofCollection *col;
qof_instance_increase_editlevel(acc); qof_instance_increase_editlevel(acc);
@ -1500,18 +1520,12 @@ xaccAccountCommitEdit (Account *acc)
themselves will be destroyed by the transaction code */ themselves will be destroyed by the transaction code */
if (!qof_book_shutting_down(book)) if (!qof_book_shutting_down(book))
{ {
slist = g_list_copy(priv->splits); for (auto s : priv->splits)
for (lp = slist; lp; lp = lp->next)
{
Split *s = static_cast<Split *>(lp->data);
xaccSplitDestroy (s); xaccSplitDestroy (s);
}
g_list_free(slist);
} }
else else
{ {
g_list_free(priv->splits); priv->splits.clear();
priv->splits = nullptr;
} }
/* It turns out there's a case where this assertion does not hold: /* It turns out there's a case where this assertion does not hold:
@ -1528,7 +1542,7 @@ xaccAccountCommitEdit (Account *acc)
qof_collection_foreach(col, destroy_pending_splits_for_account, acc); qof_collection_foreach(col, destroy_pending_splits_for_account, acc);
/* the lots should be empty by now */ /* the lots should be empty by now */
for (lp = priv->lots; lp; lp = lp->next) for (auto lp = priv->lots; lp; lp = lp->next)
{ {
GNCLot *lot = static_cast<GNCLot*>(lp->data); GNCLot *lot = static_cast<GNCLot*>(lp->data);
gnc_lot_destroy (lot); gnc_lot_destroy (lot);
@ -1822,37 +1836,23 @@ xaccAccountEqual(const Account *aa, const Account *ab, gboolean check_guids)
/* no parent; always compare downwards. */ /* no parent; always compare downwards. */
{ {
GList *la = priv_aa->splits; if (priv_aa->splits.size() != priv_ab->splits.size())
GList *lb = priv_ab->splits;
if ((la && !lb) || (!la && lb))
{ {
PWARN ("only one has splits"); PWARN ("number of splits differs");
return FALSE; return false;
} }
if (la && lb) for (auto it_a = priv_aa->splits.begin(), it_b = priv_ab->splits.begin();
it_a != priv_aa->splits.end() && it_b != priv_ab->splits.end();
++it_a, ++it_b)
{ {
/* presume that the splits are in the same order */ Split *sa = *it_a;
while (la && lb) Split *sb = *it_b;
if (!xaccSplitEqual(sa, sb, check_guids, TRUE, FALSE))
{ {
Split *sa = (Split *) la->data; PWARN ("splits differ");
Split *sb = (Split *) lb->data; return false;
if (!xaccSplitEqual(sa, sb, check_guids, TRUE, FALSE))
{
PWARN ("splits differ");
return(FALSE);
}
la = la->next;
lb = lb->next;
}
if ((la != nullptr) || (lb != nullptr))
{
PWARN ("number of splits differs");
return(FALSE);
} }
} }
} }
@ -1922,30 +1922,29 @@ gboolean gnc_account_get_defer_bal_computation (Account *acc)
/********************************************************************\ /********************************************************************\
\********************************************************************/ \********************************************************************/
static bool split_cmp_less (const Split* a, const Split* b)
{
return xaccSplitOrder (a, b) < 0;
}
gboolean gboolean
gnc_account_insert_split (Account *acc, Split *s) gnc_account_insert_split (Account *acc, Split *s)
{ {
AccountPrivate *priv; AccountPrivate *priv;
GList *node;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE); g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
g_return_val_if_fail(GNC_IS_SPLIT(s), FALSE); g_return_val_if_fail(GNC_IS_SPLIT(s), FALSE);
priv = GET_PRIVATE(acc); priv = GET_PRIVATE(acc);
node = g_list_find(priv->splits, s); if (std::find (priv->splits.begin(), priv->splits.end(), s) != priv->splits.end())
if (node)
return FALSE; return FALSE;
priv->splits.push_back (s);
if (qof_instance_get_editlevel(acc) == 0) if (qof_instance_get_editlevel(acc) == 0)
{ std::sort (priv->splits.begin(), priv->splits.end(), split_cmp_less);
priv->splits = g_list_insert_sorted(priv->splits, s,
(GCompareFunc)xaccSplitOrder);
}
else else
{ priv->sort_dirty = true;
priv->splits = g_list_prepend(priv->splits, s);
priv->sort_dirty = TRUE;
}
//FIXME: find better event //FIXME: find better event
qof_event_gen (&acc->inst, QOF_EVENT_MODIFY, nullptr); qof_event_gen (&acc->inst, QOF_EVENT_MODIFY, nullptr);
@ -1962,17 +1961,17 @@ gboolean
gnc_account_remove_split (Account *acc, Split *s) gnc_account_remove_split (Account *acc, Split *s)
{ {
AccountPrivate *priv; AccountPrivate *priv;
GList *node;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE); g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
g_return_val_if_fail(GNC_IS_SPLIT(s), FALSE); g_return_val_if_fail(GNC_IS_SPLIT(s), FALSE);
priv = GET_PRIVATE(acc); priv = GET_PRIVATE(acc);
node = g_list_find(priv->splits, s);
if (nullptr == node) auto it = std::remove (priv->splits.begin(), priv->splits.end(), s);
if (it == priv->splits.end())
return FALSE; return FALSE;
priv->splits = g_list_delete_link(priv->splits, node); priv->splits.erase (it, priv->splits.end());
//FIXME: find better event type //FIXME: find better event type
qof_event_gen(&acc->inst, QOF_EVENT_MODIFY, nullptr); qof_event_gen(&acc->inst, QOF_EVENT_MODIFY, nullptr);
// And send the account-based event, too // And send the account-based event, too
@ -1993,7 +1992,7 @@ xaccAccountSortSplits (Account *acc, gboolean force)
priv = GET_PRIVATE(acc); priv = GET_PRIVATE(acc);
if (!priv->sort_dirty || (!force && qof_instance_get_editlevel(acc) > 0)) if (!priv->sort_dirty || (!force && qof_instance_get_editlevel(acc) > 0))
return; return;
priv->splits = g_list_sort(priv->splits, (GCompareFunc)xaccSplitOrder); std::sort (priv->splits.begin(), priv->splits.end(), split_cmp_less);
priv->sort_dirty = FALSE; priv->sort_dirty = FALSE;
priv->balance_dirty = TRUE; priv->balance_dirty = TRUE;
} }
@ -2166,7 +2165,7 @@ xaccAccountInsertLot (Account *acc, GNCLot *lot)
/********************************************************************\ /********************************************************************\
\********************************************************************/ \********************************************************************/
static void static void
xaccPreSplitMove (Split *split, gpointer dummy) xaccPreSplitMove (Split *split)
{ {
xaccTransBeginEdit (xaccSplitGetParent (split)); xaccTransBeginEdit (xaccSplitGetParent (split));
} }
@ -2193,7 +2192,7 @@ xaccAccountMoveAllSplits (Account *accfrom, Account *accto)
/* optimizations */ /* optimizations */
from_priv = GET_PRIVATE(accfrom); from_priv = GET_PRIVATE(accfrom);
if (!from_priv->splits || accfrom == accto) if (from_priv->splits.empty() || accfrom == accto)
return; return;
/* check for book mix-up */ /* check for book mix-up */
@ -2203,7 +2202,7 @@ xaccAccountMoveAllSplits (Account *accfrom, Account *accto)
xaccAccountBeginEdit(accfrom); xaccAccountBeginEdit(accfrom);
xaccAccountBeginEdit(accto); xaccAccountBeginEdit(accto);
/* Begin editing both accounts and all transactions in accfrom. */ /* Begin editing both accounts and all transactions in accfrom. */
g_list_foreach(from_priv->splits, (GFunc)xaccPreSplitMove, nullptr); std::for_each (from_priv->splits.begin(), from_priv->splits.end(), xaccPreSplitMove);
/* Concatenate accfrom's lists of splits and lots to accto's lists. */ /* 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->splits = g_list_concat(to_priv->splits, from_priv->splits);
@ -2220,10 +2219,11 @@ xaccAccountMoveAllSplits (Account *accfrom, Account *accto)
* Convert each split's amount to accto's commodity. * Convert each split's amount to accto's commodity.
* Commit to editing each transaction. * Commit to editing each transaction.
*/ */
g_list_foreach(from_priv->splits, (GFunc)xaccPostSplitMove, (gpointer)accto); std::for_each (from_priv->splits.begin(), from_priv->splits.end(),
[accto](Split *s){ xaccPostSplitMove (s, accto); });
/* Finally empty accfrom. */ /* Finally empty accfrom. */
g_assert(from_priv->splits == nullptr); g_assert(from_priv->splits.empty());
g_assert(from_priv->lots == nullptr); g_assert(from_priv->lots == nullptr);
xaccAccountCommitEdit(accfrom); xaccAccountCommitEdit(accfrom);
xaccAccountCommitEdit(accto); xaccAccountCommitEdit(accto);
@ -2268,7 +2268,6 @@ xaccAccountRecomputeBalance (Account * acc)
gnc_numeric noclosing_balance; gnc_numeric noclosing_balance;
gnc_numeric cleared_balance; gnc_numeric cleared_balance;
gnc_numeric reconciled_balance; gnc_numeric reconciled_balance;
GList *lp;
if (nullptr == acc) return; if (nullptr == acc) return;
@ -2285,9 +2284,8 @@ xaccAccountRecomputeBalance (Account * acc)
PINFO ("acct=%s starting baln=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, PINFO ("acct=%s starting baln=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
priv->accountName, balance.num, balance.denom); priv->accountName, balance.num, balance.denom);
for (lp = priv->splits; lp; lp = lp->next) for (auto split : priv->splits)
{ {
Split *split = (Split *) lp->data;
gnc_numeric amt = xaccSplitGetAmount (split); gnc_numeric amt = xaccSplitGetAmount (split);
balance = gnc_numeric_add_fixed(balance, amt); balance = gnc_numeric_add_fixed(balance, amt);
@ -2607,7 +2605,6 @@ void
xaccAccountSetCommodity (Account * acc, gnc_commodity * com) xaccAccountSetCommodity (Account * acc, gnc_commodity * com)
{ {
AccountPrivate *priv; AccountPrivate *priv;
GList *lp;
/* errors */ /* errors */
g_return_if_fail(GNC_IS_ACCOUNT(acc)); g_return_if_fail(GNC_IS_ACCOUNT(acc));
@ -2626,9 +2623,8 @@ xaccAccountSetCommodity (Account * acc, gnc_commodity * com)
priv->non_standard_scu = FALSE; priv->non_standard_scu = FALSE;
/* iterate over splits */ /* iterate over splits */
for (lp = priv->splits; lp; lp = lp->next) for (auto s : priv->splits)
{ {
Split *s = (Split *) lp->data;
Transaction *trans = xaccSplitGetParent (s); Transaction *trans = xaccSplitGetParent (s);
xaccTransBeginEdit (trans); xaccTransBeginEdit (trans);
@ -3540,35 +3536,20 @@ xaccAccountGetReconciledBalance (const Account *acc)
gnc_numeric gnc_numeric
xaccAccountGetProjectedMinimumBalance (const Account *acc) xaccAccountGetProjectedMinimumBalance (const Account *acc)
{ {
AccountPrivate *priv; auto today{gnc_time64_get_today_end()};
GList *node; std::optional<gnc_numeric> minimum;
time64 today;
gnc_numeric lowest = gnc_numeric_zero ();
int seen_a_transaction = 0;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero()); auto before_today_end = [&minimum, today](const Split *s) -> bool
priv = GET_PRIVATE(acc);
today = gnc_time64_get_today_end();
for (node = g_list_last(priv->splits); node; node = node->prev)
{ {
Split *split = static_cast<Split*>(node->data); auto bal{xaccSplitGetBalance(s)};
if (!minimum || gnc_numeric_compare (bal, *minimum) < 0)
if (!seen_a_transaction) minimum = bal;
{ return (xaccTransGetDate(xaccSplitGetParent(s)) < today);
lowest = xaccSplitGetBalance (split); };
seen_a_transaction = 1; // scan to find today's split, but we're really interested in the
} // minimum balance
else if (gnc_numeric_compare(xaccSplitGetBalance (split), lowest) < 0) [[maybe_unused]] auto todays_split = gnc_account_find_split (acc, before_today_end, true);
{ return minimum ? *minimum : gnc_numeric_zero();
lowest = xaccSplitGetBalance (split);
}
if (xaccTransGetDate (xaccSplitGetParent (split)) <= today)
return lowest;
}
return lowest;
} }
@ -3578,27 +3559,16 @@ xaccAccountGetProjectedMinimumBalance (const Account *acc)
static gnc_numeric static gnc_numeric
GetBalanceAsOfDate (Account *acc, time64 date, std::function<gnc_numeric(Split*)> split_to_numeric) GetBalanceAsOfDate (Account *acc, time64 date, std::function<gnc_numeric(Split*)> split_to_numeric)
{ {
/* Ideally this could use xaccAccountForEachSplit, but
* it doesn't exist yet and I'm uncertain of exactly how
* it would work at this time, since it differs from
* xaccAccountForEachTransaction by using gpointer return
* values rather than gints.
*/
Split *latest = nullptr;
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero()); g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
xaccAccountSortSplits (acc, TRUE); /* just in case, normally a noop */ xaccAccountSortSplits (acc, TRUE); /* just in case, normally a noop */
xaccAccountRecomputeBalance (acc); /* just in case, normally a noop */ xaccAccountRecomputeBalance (acc); /* just in case, normally a noop */
for (GList *lp = GET_PRIVATE(acc)->splits; lp; lp = lp->next) auto is_before_date = [date](auto s) -> bool
{ { return xaccTransGetDate(xaccSplitGetParent(s)) < date; };
if (xaccTransGetDate (xaccSplitGetParent ((Split *)lp->data)) >= date)
break;
latest = (Split *)lp->data;
}
return latest ? split_to_numeric (latest) : gnc_numeric_zero(); 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 gnc_numeric
@ -3616,19 +3586,7 @@ xaccAccountGetNoclosingBalanceAsOfDate (Account *acc, time64 date)
gnc_numeric gnc_numeric
xaccAccountGetReconciledBalanceAsOfDate (Account *acc, time64 date) xaccAccountGetReconciledBalanceAsOfDate (Account *acc, time64 date)
{ {
gnc_numeric balance = gnc_numeric_zero(); return GetBalanceAsOfDate (acc, date, xaccSplitGetReconciledBalance);
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
for (GList *node = GET_PRIVATE(acc)->splits; node; node = node->next)
{
Split *split = (Split*) node->data;
if ((xaccSplitGetReconcile (split) == YREC) &&
(xaccSplitGetDateReconciled (split) <= date))
balance = gnc_numeric_add_fixed (balance, xaccSplitGetAmount (split));
};
return balance;
} }
/* /*
@ -4021,6 +3979,12 @@ xaccAccountGetNoclosingBalanceChangeInCurrencyForPeriod (Account *acc, time64 t1
/********************************************************************\ /********************************************************************\
\********************************************************************/ \********************************************************************/
const SplitsVec
xaccAccountGetSplits (const Account *account)
{
return GNC_IS_ACCOUNT(account) ? GET_PRIVATE(account)->splits : SplitsVec{};
}
/* THIS API NEEDS TO CHANGE. /* THIS API NEEDS TO CHANGE.
* *
* This code exposes the internal structure of the account object to * This code exposes the internal structure of the account object to
@ -4036,21 +4000,22 @@ SplitList *
xaccAccountGetSplitList (const Account *acc) xaccAccountGetSplitList (const Account *acc)
{ {
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), nullptr); g_return_val_if_fail(GNC_IS_ACCOUNT(acc), nullptr);
xaccAccountSortSplits((Account*)acc, FALSE); // normally a noop auto priv{GET_PRIVATE(acc)};
return GET_PRIVATE(acc)->splits; return std::accumulate (priv->splits.rbegin(), priv->splits.rend(),
static_cast<GList*>(nullptr), g_list_prepend);
} }
size_t size_t
xaccAccountGetSplitsSize (const Account *account) xaccAccountGetSplitsSize (const Account *account)
{ {
return GNC_IS_ACCOUNT(account) ? g_list_length (GET_PRIVATE(account)->splits) : 0; return GNC_IS_ACCOUNT(account) ? GET_PRIVATE(account)->splits.size() : 0;
} }
gboolean gnc_account_and_descendants_empty (Account *acc) gboolean gnc_account_and_descendants_empty (Account *acc)
{ {
g_return_val_if_fail (GNC_IS_ACCOUNT (acc), FALSE); g_return_val_if_fail (GNC_IS_ACCOUNT (acc), FALSE);
auto priv = GET_PRIVATE (acc); auto priv = GET_PRIVATE (acc);
if (priv->splits != nullptr) return FALSE; if (!priv->splits.empty()) return FALSE;
for (auto *n = priv->children; n; n = n->next) for (auto *n = priv->children; n; n = n->next)
{ {
if (!gnc_account_and_descendants_empty (static_cast<Account*>(n->data))) if (!gnc_account_and_descendants_empty (static_cast<Account*>(n->data)))
@ -5377,51 +5342,12 @@ xaccAccountGetReconcileChildrenStatus(const Account *acc)
/********************************************************************\ /********************************************************************\
\********************************************************************/ \********************************************************************/
/* The caller of this function can get back one or both of the
* matching split and transaction pointers, depending on whether
* a valid pointer to the location to store those pointers is
* passed.
*/
static void
finder_help_function(const Account *acc, const char *description,
Split **split, Transaction **trans )
{
AccountPrivate *priv;
GList *slp;
/* First, make sure we set the data to nullptr BEFORE we start */
if (split) *split = nullptr;
if (trans) *trans = nullptr;
/* Then see if we have any work to do */
if (acc == nullptr) return;
/* Why is this loop iterated backwards ?? Presumably because the split
* list is in date order, and the most recent matches should be
* returned!? */
priv = GET_PRIVATE(acc);
for (slp = g_list_last(priv->splits); slp; slp = slp->prev)
{
Split *lsplit = static_cast<Split*>(slp->data);
Transaction *ltrans = xaccSplitGetParent(lsplit);
if (g_strcmp0 (description, xaccTransGetDescription (ltrans)) == 0)
{
if (split) *split = lsplit;
if (trans) *trans = ltrans;
return;
}
}
}
Split * Split *
xaccAccountFindSplitByDesc(const Account *acc, const char *description) xaccAccountFindSplitByDesc(const Account *acc, const char *description)
{ {
Split *split; auto has_description = [description](const Split* s) -> bool
{ return !g_strcmp0 (description, xaccTransGetDescription (xaccSplitGetParent (s))); };
/* Get the split which has a transaction matching the description. */ return gnc_account_find_split (acc, has_description, true);
finder_help_function(acc, description, &split, nullptr);
return split;
} }
/* This routine is for finding a matching transaction in an account by /* This routine is for finding a matching transaction in an account by
@ -5432,11 +5358,8 @@ xaccAccountFindSplitByDesc(const Account *acc, const char *description)
Transaction * Transaction *
xaccAccountFindTransByDesc(const Account *acc, const char *description) xaccAccountFindTransByDesc(const Account *acc, const char *description)
{ {
Transaction *trans; auto split = xaccAccountFindSplitByDesc (acc, description);
return split ? xaccSplitGetParent (split) : nullptr;
/* Get the translation matching the description. */
finder_help_function(acc, description, nullptr, &trans);
return trans;
} }
/* ================================================================ */ /* ================================================================ */
@ -5519,8 +5442,8 @@ gnc_account_merge_children (Account *parent)
gnc_account_merge_children (acc_a); gnc_account_merge_children (acc_a);
/* consolidate transactions */ /* consolidate transactions */
while (priv_b->splits) while (!priv_b->splits.empty())
xaccSplitSetAccount (static_cast <Split*> (priv_b->splits->data), acc_a); xaccSplitSetAccount (priv_b->splits.front(), acc_a);
/* move back one before removal. next iteration around the loop /* move back one before removal. next iteration around the loop
* will get the node after node_b */ * will get the node after node_b */
@ -5538,14 +5461,11 @@ gnc_account_merge_children (Account *parent)
/* Transaction Traversal functions */ /* Transaction Traversal functions */
void static void
xaccSplitsBeginStagedTransactionTraversals (GList *splits) xaccSplitsBeginStagedTransactionTraversals (SplitsVec splits)
{ {
GList *lp; for (auto s : splits)
for (lp = splits; lp; lp = lp->next)
{ {
Split *s = static_cast <Split*> (lp->data);
Transaction *trans = s->parent; Transaction *trans = s->parent;
if (trans) if (trans)
@ -5557,12 +5477,9 @@ xaccSplitsBeginStagedTransactionTraversals (GList *splits)
void void
xaccAccountBeginStagedTransactionTraversals (const Account *account) xaccAccountBeginStagedTransactionTraversals (const Account *account)
{ {
AccountPrivate *priv;
if (!account) if (!account)
return; return;
priv = GET_PRIVATE(account); xaccSplitsBeginStagedTransactionTraversals(GET_PRIVATE (account)->splits);
xaccSplitsBeginStagedTransactionTraversals(priv->splits);
} }
gboolean gboolean
@ -5579,16 +5496,11 @@ xaccTransactionTraverse (Transaction *trans, int stage)
return FALSE; return FALSE;
} }
static void do_one_split (Split *s, gpointer data)
{
Transaction *trans = s->parent;
trans->marker = 0;
}
static void do_one_account (Account *account, gpointer data) static void do_one_account (Account *account, gpointer data)
{ {
AccountPrivate *priv = GET_PRIVATE(account); AccountPrivate *priv = GET_PRIVATE(account);
g_list_foreach(priv->splits, (GFunc)do_one_split, nullptr); std::for_each (priv->splits.begin(), priv->splits.end(),
[](auto s){ s->parent->marker = 0; });
} }
/* Replacement for xaccGroupBeginStagedTransactionTraversals */ /* Replacement for xaccGroupBeginStagedTransactionTraversals */
@ -5608,32 +5520,18 @@ xaccAccountStagedTransactionTraversal (const Account *acc,
TransactionCallback thunk, TransactionCallback thunk,
void *cb_data) void *cb_data)
{ {
AccountPrivate *priv;
GList *split_p;
GList *next;
Transaction *trans;
Split *s;
int retval;
if (!acc) return 0; if (!acc) return 0;
priv = GET_PRIVATE(acc); auto splits = GET_PRIVATE(acc)->splits;
for (split_p = priv->splits; split_p; split_p = next) for (auto s : splits)
{ {
/* Get the next element in the split list now, just in case some auto trans = s->parent;
* naughty thunk destroys the one we're using. This reduces, but
* does not eliminate, the possibility of undefined results if
* a thunk removes splits from this account. */
next = g_list_next(split_p);
s = static_cast <Split*> (split_p->data);
trans = s->parent;
if (trans && (trans->marker < stage)) if (trans && (trans->marker < stage))
{ {
trans->marker = stage; trans->marker = stage;
if (thunk) if (thunk)
{ {
retval = thunk(trans, cb_data); auto retval = thunk(trans, cb_data);
if (retval) return retval; if (retval) return retval;
} }
} }
@ -5649,16 +5547,14 @@ gnc_account_tree_staged_transaction_traversal (const Account *acc,
void *cb_data) void *cb_data)
{ {
const AccountPrivate *priv; const AccountPrivate *priv;
GList *acc_p, *split_p;
Transaction *trans; Transaction *trans;
Split *s;
int retval; int retval;
if (!acc) return 0; if (!acc) return 0;
/* depth first traversal */ /* depth first traversal */
priv = GET_PRIVATE(acc); priv = GET_PRIVATE(acc);
for (acc_p = priv->children; acc_p; acc_p = g_list_next(acc_p)) for (auto acc_p = priv->children; acc_p; acc_p = g_list_next(acc_p))
{ {
retval = gnc_account_tree_staged_transaction_traversal(static_cast <Account*> (acc_p->data), retval = gnc_account_tree_staged_transaction_traversal(static_cast <Account*> (acc_p->data),
stage, thunk, cb_data); stage, thunk, cb_data);
@ -5666,9 +5562,8 @@ gnc_account_tree_staged_transaction_traversal (const Account *acc,
} }
/* Now this account */ /* Now this account */
for (split_p = priv->splits; split_p; split_p = g_list_next(split_p)) for (auto s : priv->splits)
{ {
s = static_cast <Split*> (split_p->data);
trans = s->parent; trans = s->parent;
if (trans && (trans->marker < stage)) if (trans && (trans->marker < stage))
{ {

View File

@ -1493,12 +1493,6 @@ typedef enum
*/ */
void gnc_account_tree_begin_staged_transaction_traversals(Account *acc); void gnc_account_tree_begin_staged_transaction_traversals(Account *acc);
/** xaccSplitsBeginStagedTransactionTraversals() resets the traversal
* marker for each transaction which is a parent of one of the
* splits in the list.
*/
void xaccSplitsBeginStagedTransactionTraversals(SplitList *splits);
/** xaccAccountBeginStagedTransactionTraversals() resets the traversal /** xaccAccountBeginStagedTransactionTraversals() resets the traversal
* marker for each transaction which is a parent of one of the * marker for each transaction which is a parent of one of the
* splits in the account. * splits in the account.

View File

@ -0,0 +1,58 @@
/**********************************************************************
* Account.hpp
* *
* 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 *
* *
*********************************************************************/
/** @addtogroup Engine
@{ */
/** @addtogroup Account
@{ */
/** @file Account.hpp
* @brief Account public routines (C++ api)
*/
#ifndef GNC_ACCOUNT_HPP
#define GNC_ACCOUNT_HPP
#include <vector>
#include <functional>
#include <Account.h>
using SplitsVec = std::vector<Split*>;
const SplitsVec xaccAccountGetSplits (const Account*);
/** scans account split list (in forward or reverse order) until
* predicate split->bool returns true. Maybe return the split.
*
* @param acc The account to which the split should be added.
*
* @param predicate A split->bool predicate.
*
* @param reverse To scan in reverse order
*
* @result Split* or nullptr if not found */
Split* gnc_account_find_split (const Account*, std::function<bool(const Split*)>, bool);
#endif /* GNC_COMMODITY_HPP */
/** @} */
/** @} */

View File

@ -39,6 +39,7 @@
#ifndef XACC_ACCOUNT_P_H #ifndef XACC_ACCOUNT_P_H
#define XACC_ACCOUNT_P_H #define XACC_ACCOUNT_P_H
#include <vector>
#include <optional> #include <optional>
#include "Account.h" #include "Account.h"
@ -118,7 +119,7 @@ typedef struct AccountPrivate
gboolean balance_dirty; /* balances in splits incorrect */ gboolean balance_dirty; /* balances in splits incorrect */
GList *splits; /* list of split pointers */ std::vector<Split*> splits; /* list of split pointers */
gboolean sort_dirty; /* sort order of splits is bad */ gboolean sort_dirty; /* sort order of splits is bad */
LotList *lots; /* list of lot pointers */ LotList *lots; /* list of lot pointers */

View File

@ -31,6 +31,7 @@ set(engine_noinst_HEADERS
) )
set (engine_HEADERS set (engine_HEADERS
Account.hpp
Account.h Account.h
FreqSpec.h FreqSpec.h
Recurrence.h Recurrence.h

View File

@ -76,6 +76,16 @@ xaccAccountGetSplitList (const Account *account)
return mockaccount ? mockaccount->xaccAccountGetSplitList() : nullptr; return mockaccount ? mockaccount->xaccAccountGetSplitList() : nullptr;
} }
const std::vector<Split*>
xaccAccountGetSplits (const Account *account)
{
SCOPED_TRACE("");
auto mockaccount = gnc_mockaccount(account);
if (!mockaccount)
return {};
return mockaccount->xaccAccountGetSplits();
}
Account* Account*
gnc_account_imap_find_account ( gnc_account_imap_find_account (

View File

@ -4,6 +4,7 @@
#include <gmock/gmock.h> #include <gmock/gmock.h>
#include <Account.h> #include <Account.h>
#include <Account.hpp>
#include <AccountP.hpp> #include <AccountP.hpp>
#include <qofbook.h> #include <qofbook.h>
@ -45,6 +46,7 @@ public:
MOCK_CONST_METHOD0(get_commodity, gnc_commodity*()); MOCK_CONST_METHOD0(get_commodity, gnc_commodity*());
MOCK_CONST_METHOD2(for_each_transaction, gint(TransactionCallback, void*)); MOCK_CONST_METHOD2(for_each_transaction, gint(TransactionCallback, void*));
MOCK_CONST_METHOD0(xaccAccountGetSplitList, SplitList*()); MOCK_CONST_METHOD0(xaccAccountGetSplitList, SplitList*());
MOCK_CONST_METHOD0(xaccAccountGetSplits, std::vector<Split*>());
MOCK_METHOD2(find_account, Account *(const char*, const char*)); MOCK_METHOD2(find_account, Account *(const char*, const char*));
MOCK_METHOD3(add_account, void(const char*, const char*, Account*)); MOCK_METHOD3(add_account, void(const char*, const char*, Account*));
MOCK_METHOD1(find_account_bayes, Account *(std::vector<const char*>&)); MOCK_METHOD1(find_account_bayes, Account *(std::vector<const char*>&));

View File

@ -882,7 +882,7 @@ test_xaccFreeAccount (Fixture *fixture, gconstpointer pData)
/* Check that we've got children, lots, and splits to remove */ /* Check that we've got children, lots, and splits to remove */
g_assert_true (p_priv->children != NULL); g_assert_true (p_priv->children != NULL);
g_assert_true (p_priv->lots != NULL); g_assert_true (p_priv->lots != NULL);
g_assert_true (p_priv->splits != NULL); g_assert_true (p_priv->splits.size());
g_assert_true (p_priv->parent != NULL); g_assert_true (p_priv->parent != NULL);
g_assert_true (p_priv->commodity != NULL); g_assert_true (p_priv->commodity != NULL);
g_assert_cmpint (check1->hits, ==, 0); g_assert_cmpint (check1->hits, ==, 0);
@ -983,7 +983,7 @@ test_xaccAccountCommitEdit (Fixture *fixture, gconstpointer pData)
/* Check that we've got children, lots, and splits to remove */ /* Check that we've got children, lots, and splits to remove */
g_assert_true (p_priv->children != NULL); g_assert_true (p_priv->children != NULL);
g_assert_true (p_priv->lots != NULL); g_assert_true (p_priv->lots != NULL);
g_assert_true (p_priv->splits != NULL); g_assert_true (p_priv->splits.size());
g_assert_true (p_priv->parent != NULL); g_assert_true (p_priv->parent != NULL);
g_assert_true (p_priv->commodity != NULL); g_assert_true (p_priv->commodity != NULL);
g_assert_cmpint (check1->hits, ==, 0); g_assert_cmpint (check1->hits, ==, 0);
@ -998,7 +998,7 @@ test_xaccAccountCommitEdit (Fixture *fixture, gconstpointer pData)
test_signal_assert_hits (sig2, 0); test_signal_assert_hits (sig2, 0);
g_assert_true (p_priv->children != NULL); g_assert_true (p_priv->children != NULL);
g_assert_true (p_priv->lots != NULL); g_assert_true (p_priv->lots != NULL);
g_assert_true (p_priv->splits != NULL); g_assert_true (p_priv->splits.size());
g_assert_true (p_priv->parent != NULL); g_assert_true (p_priv->parent != NULL);
g_assert_true (p_priv->commodity != NULL); g_assert_true (p_priv->commodity != NULL);
g_assert_cmpint (check1->hits, ==, 0); g_assert_cmpint (check1->hits, ==, 0);
@ -1393,19 +1393,19 @@ test_gnc_account_insert_remove_split (Fixture *fixture, gconstpointer pData)
/* Check that the call fails with invalid account and split (throws) */ /* Check that the call fails with invalid account and split (throws) */
g_assert_true (!gnc_account_insert_split (NULL, split1)); g_assert_true (!gnc_account_insert_split (NULL, split1));
g_assert_cmpuint (g_list_length (priv->splits), == , 0); g_assert_cmpuint (priv->splits.size(), == , 0);
g_assert_true (!priv->sort_dirty); g_assert_true (!priv->sort_dirty);
g_assert_true (!priv->balance_dirty); g_assert_true (!priv->balance_dirty);
test_signal_assert_hits (sig1, 0); test_signal_assert_hits (sig1, 0);
test_signal_assert_hits (sig2, 0); test_signal_assert_hits (sig2, 0);
g_assert_true (!gnc_account_insert_split (fixture->acct, NULL)); g_assert_true (!gnc_account_insert_split (fixture->acct, NULL));
g_assert_cmpuint (g_list_length (priv->splits), == , 0); g_assert_cmpuint (priv->splits.size(), == , 0);
g_assert_true (!priv->sort_dirty); g_assert_true (!priv->sort_dirty);
g_assert_true (!priv->balance_dirty); g_assert_true (!priv->balance_dirty);
test_signal_assert_hits (sig1, 0); test_signal_assert_hits (sig1, 0);
test_signal_assert_hits (sig2, 0); test_signal_assert_hits (sig2, 0);
/* g_assert_true (!gnc_account_insert_split (fixture->acct, (Split*)priv)); */ /* g_assert_true (!gnc_account_insert_split (fixture->acct, (Split*)priv)); */
/* g_assert_cmpuint (g_list_length (priv->splits), == , 0); */ /* g_assert_cmpuint (priv->splits.size(), == , 0); */
/* g_assert_true (!priv->sort_dirty); */ /* g_assert_true (!priv->sort_dirty); */
/* g_assert_true (!priv->balance_dirty); */ /* g_assert_true (!priv->balance_dirty); */
/* test_signal_assert_hits (sig1, 0); */ /* test_signal_assert_hits (sig1, 0); */
@ -1418,7 +1418,7 @@ test_gnc_account_insert_remove_split (Fixture *fixture, gconstpointer pData)
/* Check that it works the first time */ /* Check that it works the first time */
g_assert_true (gnc_account_insert_split (fixture->acct, split1)); g_assert_true (gnc_account_insert_split (fixture->acct, split1));
g_assert_cmpuint (g_list_length (priv->splits), == , 1); g_assert_cmpuint (priv->splits.size(), == , 1);
g_assert_true (!priv->sort_dirty); g_assert_true (!priv->sort_dirty);
g_assert_true (priv->balance_dirty); g_assert_true (priv->balance_dirty);
test_signal_assert_hits (sig1, 1); test_signal_assert_hits (sig1, 1);
@ -1430,7 +1430,7 @@ test_gnc_account_insert_remove_split (Fixture *fixture, gconstpointer pData)
sig3 = test_signal_new (&fixture->acct->inst, GNC_EVENT_ITEM_ADDED, split2); sig3 = test_signal_new (&fixture->acct->inst, GNC_EVENT_ITEM_ADDED, split2);
/* Now add a second split to the account and check that sort_dirty isn't set. We have to bump the editlevel to force this. */ /* Now add a second split to the account and check that sort_dirty isn't set. We have to bump the editlevel to force this. */
g_assert_true (gnc_account_insert_split (fixture->acct, split2)); g_assert_true (gnc_account_insert_split (fixture->acct, split2));
g_assert_cmpuint (g_list_length (priv->splits), == , 2); g_assert_cmpuint (priv->splits.size(), == , 2);
g_assert_true (!priv->sort_dirty); g_assert_true (!priv->sort_dirty);
g_assert_true (priv->balance_dirty); g_assert_true (priv->balance_dirty);
test_signal_assert_hits (sig1, 2); test_signal_assert_hits (sig1, 2);
@ -1441,7 +1441,7 @@ test_gnc_account_insert_remove_split (Fixture *fixture, gconstpointer pData)
qof_instance_increase_editlevel (fixture->acct); qof_instance_increase_editlevel (fixture->acct);
g_assert_true (gnc_account_insert_split (fixture->acct, split3)); g_assert_true (gnc_account_insert_split (fixture->acct, split3));
qof_instance_decrease_editlevel (fixture->acct); qof_instance_decrease_editlevel (fixture->acct);
g_assert_cmpuint (g_list_length (priv->splits), == , 3); g_assert_cmpuint (priv->splits.size(), == , 3);
g_assert_true (priv->sort_dirty); g_assert_true (priv->sort_dirty);
g_assert_true (priv->balance_dirty); g_assert_true (priv->balance_dirty);
test_signal_assert_hits (sig1, 3); test_signal_assert_hits (sig1, 3);
@ -1452,7 +1452,7 @@ test_gnc_account_insert_remove_split (Fixture *fixture, gconstpointer pData)
sig3 = test_signal_new (&fixture->acct->inst, GNC_EVENT_ITEM_REMOVED, sig3 = test_signal_new (&fixture->acct->inst, GNC_EVENT_ITEM_REMOVED,
split3); split3);
g_assert_true (gnc_account_remove_split (fixture->acct, split3)); g_assert_true (gnc_account_remove_split (fixture->acct, split3));
g_assert_cmpuint (g_list_length (priv->splits), == , 2); g_assert_cmpuint (priv->splits.size(), == , 2);
g_assert_true (priv->sort_dirty); g_assert_true (priv->sort_dirty);
g_assert_true (!priv->balance_dirty); g_assert_true (!priv->balance_dirty);
test_signal_assert_hits (sig1, 4); test_signal_assert_hits (sig1, 4);
@ -1460,7 +1460,7 @@ test_gnc_account_insert_remove_split (Fixture *fixture, gconstpointer pData)
/* And do it again to make sure that it fails when the split has /* And do it again to make sure that it fails when the split has
* already been removed */ * already been removed */
g_assert_true (!gnc_account_remove_split (fixture->acct, split3)); g_assert_true (!gnc_account_remove_split (fixture->acct, split3));
g_assert_cmpuint (g_list_length (priv->splits), == , 2); g_assert_cmpuint (priv->splits.size(), == , 2);
g_assert_true (priv->sort_dirty); g_assert_true (priv->sort_dirty);
g_assert_true (!priv->balance_dirty); g_assert_true (!priv->balance_dirty);
test_signal_assert_hits (sig1, 4); test_signal_assert_hits (sig1, 4);