1 /********************************************************************\
2  * Account.c -- Account data structure implementation               *
3  * Copyright (C) 1997 Robin D. Clark                                *
4  * Copyright (C) 1997-2003 Linas Vepstas <linas@linas.org>          *
5  * Copyright (C) 2007 David Hampton <hampton@employees.org>         *
6  *                                                                  *
7  * This program is free software; you can redistribute it and/or    *
8  * modify it under the terms of the GNU General Public License as   *
9  * published by the Free Software Foundation; either version 2 of   *
10  * the License, or (at your option) any later version.              *
11  *                                                                  *
12  * This program is distributed in the hope that it will be useful,  *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
15  * GNU General Public License for more details.                     *
16  *                                                                  *
17  * You should have received a copy of the GNU General Public License*
18  * along with this program; if not, contact:                        *
19  *                                                                  *
20  * Free Software Foundation           Voice:  +1-617-542-5942       *
21  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
22  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
23  *                                                                  *
24 \********************************************************************/
25 
26 #include <config.h>
27 
28 extern "C" {
29 #include "gnc-prefs.h"
30 }
31 
32 #include <glib.h>
33 #include <glib/gi18n.h>
34 #include <stdlib.h>
35 #include <stdint.h>
36 #include <string.h>
37 
38 #include "AccountP.h"
39 #include "Split.h"
40 #include "Transaction.h"
41 #include "TransactionP.h"
42 #include "gnc-event.h"
43 #include "gnc-glib-utils.h"
44 #include "gnc-lot.h"
45 #include "gnc-pricedb.h"
46 #include "qofinstance-p.h"
47 #include "gnc-features.h"
48 #include "guid.hpp"
49 
50 #include <numeric>
51 #include <map>
52 
53 static QofLogModule log_module = GNC_MOD_ACCOUNT;
54 
55 /* The Canonical Account Separator.  Pre-Initialized. */
56 static gchar account_separator[8] = ".";
57 static gunichar account_uc_separator = ':';
58 
59 static bool imap_convert_bayes_to_flat_run = false;
60 
61 /* Predefined KVP paths */
62 static const std::string KEY_ASSOC_INCOME_ACCOUNT("ofx/associated-income-account");
63 static const std::string KEY_RECONCILE_INFO("reconcile-info");
64 static const std::string KEY_INCLUDE_CHILDREN("include-children");
65 static const std::string KEY_POSTPONE("postpone");
66 static const std::string KEY_LOT_MGMT("lot-mgmt");
67 static const std::string KEY_ONLINE_ID("online_id");
68 static const std::string KEY_IMP_APPEND_TEXT("import-append-text");
69 static const std::string AB_KEY("hbci");
70 static const std::string AB_ACCOUNT_ID("account-id");
71 static const std::string AB_ACCOUNT_UID("account-uid");
72 static const std::string AB_BANK_CODE("bank-code");
73 static const std::string AB_TRANS_RETRIEVAL("trans-retrieval");
74 
75 static gnc_numeric GetBalanceAsOfDate (Account *acc, time64 date, gboolean ignclosing);
76 
77 using FinalProbabilityVec=std::vector<std::pair<std::string, int32_t>>;
78 using ProbabilityVec=std::vector<std::pair<std::string, struct AccountProbability>>;
79 using FlatKvpEntry=std::pair<std::string, KvpValue*>;
80 
81 enum
82 {
83     LAST_SIGNAL
84 };
85 
86 enum
87 {
88     PROP_0,
89     PROP_NAME,                          /* Table */
90     PROP_FULL_NAME,                     /* Constructed */
91     PROP_CODE,                          /* Table */
92     PROP_DESCRIPTION,                   /* Table */
93     PROP_COLOR,                         /* KVP */
94     PROP_NOTES,                         /* KVP */
95     PROP_TYPE,                          /* Table */
96 
97 //    PROP_PARENT,                      /* Table, Not a property */
98     PROP_COMMODITY,                     /* Table */
99     PROP_COMMODITY_SCU,                 /* Table */
100     PROP_NON_STD_SCU,                   /* Table */
101     PROP_END_BALANCE,                   /* Constructed */
102     PROP_END_NOCLOSING_BALANCE,         /* Constructed */
103     PROP_END_CLEARED_BALANCE,           /* Constructed */
104     PROP_END_RECONCILED_BALANCE,        /* Constructed */
105 
106     PROP_TAX_RELATED,                   /* KVP */
107     PROP_TAX_CODE,                      /* KVP */
108     PROP_TAX_SOURCE,                    /* KVP */
109     PROP_TAX_COPY_NUMBER,               /* KVP */
110 
111     PROP_HIDDEN,                        /* Table slot exists, but in KVP in memory & xml */
112     PROP_PLACEHOLDER,                   /* Table slot exists, but in KVP in memory & xml */
113     PROP_AUTO_INTEREST,
114     PROP_FILTER,                        /* KVP */
115     PROP_SORT_ORDER,                    /* KVP */
116     PROP_SORT_REVERSED,
117 
118     PROP_LOT_NEXT_ID,                   /* KVP */
119     PROP_ONLINE_ACCOUNT,                /* KVP */
120     PROP_IMP_APPEND_TEXT,               /* KVP */
121     PROP_IS_OPENING_BALANCE,            /* KVP */
122     PROP_OFX_INCOME_ACCOUNT,            /* KVP */
123     PROP_AB_ACCOUNT_ID,                 /* KVP */
124     PROP_AB_ACCOUNT_UID,                /* KVP */
125     PROP_AB_BANK_CODE,                  /* KVP */
126     PROP_AB_TRANS_RETRIEVAL,            /* KVP */
127 
128     PROP_RUNTIME_0,
129     PROP_POLICY,                        /* Cached Value */
130     PROP_MARK,                          /* Runtime Value */
131     PROP_SORT_DIRTY,                    /* Runtime Value */
132     PROP_BALANCE_DIRTY,                 /* Runtime Value */
133     PROP_START_BALANCE,                 /* Runtime Value */
134     PROP_START_NOCLOSING_BALANCE,       /* Runtime Value */
135     PROP_START_CLEARED_BALANCE,         /* Runtime Value */
136     PROP_START_RECONCILED_BALANCE,      /* Runtime Value */
137 };
138 
139 #define GET_PRIVATE(o)  \
140     ((AccountPrivate*)g_type_instance_get_private((GTypeInstance*)o, GNC_TYPE_ACCOUNT))
141 
142 /* This map contains a set of strings representing the different column types. */
143 static const std::map<GNCAccountType, const char*> gnc_acct_debit_strs = {
144     { ACCT_TYPE_NONE,       N_("Funds In") },
145     { ACCT_TYPE_BANK,       N_("Deposit") },
146     { ACCT_TYPE_CASH,       N_("Receive") },
147     { ACCT_TYPE_CREDIT,     N_("Payment") },
148     { ACCT_TYPE_ASSET,      N_("Increase") },
149     { ACCT_TYPE_LIABILITY,  N_("Decrease") },
150     { ACCT_TYPE_STOCK,      N_("Buy") },
151     { ACCT_TYPE_MUTUAL,     N_("Buy") },
152     { ACCT_TYPE_CURRENCY,   N_("Buy") },
153     { ACCT_TYPE_INCOME,     N_("Charge") },
154     { ACCT_TYPE_EXPENSE,    N_("Expense") },
155     { ACCT_TYPE_PAYABLE,    N_("Payment") },
156     { ACCT_TYPE_RECEIVABLE, N_("Invoice") },
157     { ACCT_TYPE_TRADING,    N_("Decrease") },
158     { ACCT_TYPE_EQUITY,     N_("Decrease") },
159 };
160 static const char* dflt_acct_debit_str = N_("Debit");
161 
162 /* This map contains a set of strings representing the different column types. */
163 static const std::map<GNCAccountType, const char*> gnc_acct_credit_strs = {
164     { ACCT_TYPE_NONE,       N_("Funds Out") },
165     { ACCT_TYPE_BANK,       N_("Withdrawal") },
166     { ACCT_TYPE_CASH,       N_("Spend") },
167     { ACCT_TYPE_CREDIT,     N_("Charge") },
168     { ACCT_TYPE_ASSET,      N_("Decrease") },
169     { ACCT_TYPE_LIABILITY,  N_("Increase") },
170     { ACCT_TYPE_STOCK,      N_("Sell") },
171     { ACCT_TYPE_MUTUAL,     N_("Sell") },
172     { ACCT_TYPE_CURRENCY,   N_("Sell") },
173     { ACCT_TYPE_INCOME,     N_("Income") },
174     { ACCT_TYPE_EXPENSE,    N_("Rebate") },
175     { ACCT_TYPE_PAYABLE,    N_("Bill") },
176     { ACCT_TYPE_RECEIVABLE, N_("Payment") },
177     { ACCT_TYPE_TRADING,    N_("Increase") },
178     { ACCT_TYPE_EQUITY,     N_("Increase") },
179 };
180 static const char* dflt_acct_credit_str = N_("Credit");
181 
182 /********************************************************************\
183  * Because I can't use C++ for this project, doesn't mean that I    *
184  * can't pretend to!  These functions perform actions on the        *
185  * account data structure, in order to encapsulate the knowledge    *
186  * of the internals of the Account in one file.                     *
187 \********************************************************************/
188 
189 static void xaccAccountBringUpToDate (Account *acc);
190 
191 
192 /********************************************************************\
193  * gnc_get_account_separator                                        *
194  *   returns the current account separator character                *
195  *                                                                  *
196  * Args: none                                                       *
197  * Returns: account separator character                             *
198  \*******************************************************************/
199 const gchar *
gnc_get_account_separator_string(void)200 gnc_get_account_separator_string (void)
201 {
202     return account_separator;
203 }
204 
205 gunichar
gnc_get_account_separator(void)206 gnc_get_account_separator (void)
207 {
208     return account_uc_separator;
209 }
210 
211 void
gnc_set_account_separator(const gchar * separator)212 gnc_set_account_separator (const gchar *separator)
213 {
214     gunichar uc;
215     gint count;
216 
217     uc = g_utf8_get_char_validated(separator, -1);
218     if ((uc == (gunichar) - 2) || (uc == (gunichar) - 1) || g_unichar_isalnum(uc))
219     {
220         account_uc_separator = ':';
221         strcpy(account_separator, ":");
222         return;
223     }
224 
225     account_uc_separator = uc;
226     count = g_unichar_to_utf8(uc, account_separator);
227     account_separator[count] = '\0';
228 }
229 
gnc_account_name_violations_errmsg(const gchar * separator,GList * invalid_account_names)230 gchar *gnc_account_name_violations_errmsg (const gchar *separator, GList* invalid_account_names)
231 {
232     gchar *message = NULL;
233 
234     if ( !invalid_account_names )
235         return NULL;
236 
237     auto account_list {gnc_g_list_stringjoin (invalid_account_names, "\n")};
238 
239     /* Translators: The first %s will be the account separator character,
240        the second %s is a list of account names.
241        The resulting string will be displayed to the user if there are
242        account names containing the separator character. */
243     message = g_strdup_printf(
244                   _("The separator character \"%s\" is used in one or more account names.\n\n"
245                     "This will result in unexpected behaviour. "
246                     "Either change the account names or choose another separator character.\n\n"
247                     "Below you will find the list of invalid account names:\n"
248                     "%s"), separator, account_list );
249     g_free ( account_list );
250     return message;
251 }
252 
253 struct ViolationData
254 {
255     GList *list;
256     const gchar *separator;
257 };
258 
259 static void
check_acct_name(Account * acct,gpointer user_data)260 check_acct_name (Account *acct, gpointer user_data)
261 {
262     auto cb {static_cast<ViolationData*>(user_data)};
263     auto name {xaccAccountGetName (acct)};
264     if (g_strstr_len (name, -1, cb->separator))
265         cb->list = g_list_prepend (cb->list, g_strdup (name));
266 }
267 
gnc_account_list_name_violations(QofBook * book,const gchar * separator)268 GList *gnc_account_list_name_violations (QofBook *book, const gchar *separator)
269 {
270     g_return_val_if_fail (separator != NULL, nullptr);
271     if (!book) return nullptr;
272     ViolationData cb = { nullptr, separator };
273     gnc_account_foreach_descendant (gnc_book_get_root_account (book),
274                                     (AccountCb)check_acct_name, &cb);
275     return cb.list;
276 }
277 
278 /********************************************************************\
279 \********************************************************************/
280 
281 static inline void mark_account (Account *acc);
282 void
mark_account(Account * acc)283 mark_account (Account *acc)
284 {
285     qof_instance_set_dirty(&acc->inst);
286 }
287 
288 /********************************************************************\
289 \********************************************************************/
290 
291 static constexpr const char* is_unset {"unset"};
292 
293 /* GObject Initialization */
G_DEFINE_TYPE_WITH_PRIVATE(Account,gnc_account,QOF_TYPE_INSTANCE)294 G_DEFINE_TYPE_WITH_PRIVATE(Account, gnc_account, QOF_TYPE_INSTANCE)
295 
296 static void
297 gnc_account_init(Account* acc)
298 {
299     AccountPrivate *priv;
300 
301     priv = GET_PRIVATE(acc);
302     priv->parent   = NULL;
303     priv->children = NULL;
304 
305     priv->accountName = qof_string_cache_insert("");
306     priv->accountCode = qof_string_cache_insert("");
307     priv->description = qof_string_cache_insert("");
308 
309     priv->type = ACCT_TYPE_NONE;
310 
311     priv->mark = 0;
312 
313     priv->policy = xaccGetFIFOPolicy();
314     priv->lots = NULL;
315 
316     priv->commodity = NULL;
317     priv->commodity_scu = 0;
318     priv->non_standard_scu = FALSE;
319 
320     priv->balance = gnc_numeric_zero();
321     priv->noclosing_balance = gnc_numeric_zero();
322     priv->cleared_balance = gnc_numeric_zero();
323     priv->reconciled_balance = gnc_numeric_zero();
324     priv->starting_balance = gnc_numeric_zero();
325     priv->starting_noclosing_balance = gnc_numeric_zero();
326     priv->starting_cleared_balance = gnc_numeric_zero();
327     priv->starting_reconciled_balance = gnc_numeric_zero();
328     priv->balance_dirty = FALSE;
329 
330     priv->last_num = (char*) is_unset;
331     priv->tax_us_code = (char*) is_unset;
332     priv->tax_us_pns = (char*) is_unset;
333     priv->color = (char*) is_unset;
334     priv->sort_order = (char*) is_unset;
335     priv->notes = (char*) is_unset;
336     priv->filter = (char*) is_unset;
337     priv->equity_type = TriState::Unset;
338     priv->sort_reversed = TriState::Unset;
339 
340     priv->splits = NULL;
341     priv->sort_dirty = FALSE;
342 }
343 
344 static void
gnc_account_dispose(GObject * acctp)345 gnc_account_dispose (GObject *acctp)
346 {
347     G_OBJECT_CLASS(gnc_account_parent_class)->dispose(acctp);
348 }
349 
350 static void
gnc_account_finalize(GObject * acctp)351 gnc_account_finalize(GObject* acctp)
352 {
353     G_OBJECT_CLASS(gnc_account_parent_class)->finalize(acctp);
354 }
355 
356 /* Note that g_value_set_object() refs the object, as does
357  * g_object_get(). But g_object_get() only unrefs once when it disgorges
358  * the object, leaving an unbalanced ref, which leaks. So instead of
359  * using g_value_set_object(), use g_value_take_object() which doesn't
360  * ref the object when used in get_property().
361  */
362 static void
gnc_account_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)363 gnc_account_get_property (GObject         *object,
364                           guint            prop_id,
365                           GValue          *value,
366                           GParamSpec      *pspec)
367 {
368     Account *account;
369     AccountPrivate *priv;
370     const gchar *key;
371 
372     g_return_if_fail(GNC_IS_ACCOUNT(object));
373 
374     account = GNC_ACCOUNT(object);
375     priv = GET_PRIVATE(account);
376     switch (prop_id)
377     {
378     case PROP_NAME:
379         g_value_set_string(value, priv->accountName);
380         break;
381     case PROP_FULL_NAME:
382         g_value_take_string(value, gnc_account_get_full_name(account));
383         break;
384     case PROP_CODE:
385         g_value_set_string(value, priv->accountCode);
386         break;
387     case PROP_DESCRIPTION:
388         g_value_set_string(value, priv->description);
389         break;
390     case PROP_COLOR:
391         g_value_set_string(value, xaccAccountGetColor(account));
392         break;
393     case PROP_NOTES:
394         g_value_set_string(value, xaccAccountGetNotes(account));
395         break;
396     case PROP_TYPE:
397         // NEED TO BE CONVERTED TO A G_TYPE_ENUM
398         g_value_set_int(value, priv->type);
399         break;
400     case PROP_COMMODITY:
401         g_value_take_object(value, priv->commodity);
402         break;
403     case PROP_COMMODITY_SCU:
404         g_value_set_int(value, priv->commodity_scu);
405         break;
406     case PROP_NON_STD_SCU:
407         g_value_set_boolean(value, priv->non_standard_scu);
408         break;
409     case PROP_SORT_DIRTY:
410         g_value_set_boolean(value, priv->sort_dirty);
411         break;
412     case PROP_BALANCE_DIRTY:
413         g_value_set_boolean(value, priv->balance_dirty);
414         break;
415     case PROP_START_BALANCE:
416         g_value_set_boxed(value, &priv->starting_balance);
417         break;
418     case PROP_START_NOCLOSING_BALANCE:
419         g_value_set_boxed(value, &priv->starting_noclosing_balance);
420         break;
421     case PROP_START_CLEARED_BALANCE:
422         g_value_set_boxed(value, &priv->starting_cleared_balance);
423         break;
424     case PROP_START_RECONCILED_BALANCE:
425         g_value_set_boxed(value, &priv->starting_reconciled_balance);
426         break;
427     case PROP_END_BALANCE:
428         g_value_set_boxed(value, &priv->balance);
429         break;
430     case PROP_END_NOCLOSING_BALANCE:
431         g_value_set_boxed(value, &priv->noclosing_balance);
432         break;
433     case PROP_END_CLEARED_BALANCE:
434         g_value_set_boxed(value, &priv->cleared_balance);
435         break;
436     case PROP_END_RECONCILED_BALANCE:
437         g_value_set_boxed(value, &priv->reconciled_balance);
438         break;
439     case PROP_POLICY:
440         /* MAKE THIS A BOXED VALUE */
441         g_value_set_pointer(value, priv->policy);
442         break;
443     case PROP_MARK:
444         g_value_set_int(value, priv->mark);
445         break;
446     case PROP_TAX_RELATED:
447         g_value_set_boolean(value, xaccAccountGetTaxRelated(account));
448         break;
449     case PROP_TAX_CODE:
450         g_value_set_string(value, xaccAccountGetTaxUSCode(account));
451         break;
452     case PROP_TAX_SOURCE:
453         g_value_set_string(value,
454                            xaccAccountGetTaxUSPayerNameSource(account));
455         break;
456     case PROP_TAX_COPY_NUMBER:
457         g_value_set_int64(value,
458                           xaccAccountGetTaxUSCopyNumber(account));
459         break;
460     case PROP_HIDDEN:
461         g_value_set_boolean(value, xaccAccountGetHidden(account));
462         break;
463     case PROP_AUTO_INTEREST:
464         g_value_set_boolean (value, xaccAccountGetAutoInterest (account));
465         break;
466     case PROP_IS_OPENING_BALANCE:
467         g_value_set_boolean(value, xaccAccountGetIsOpeningBalance(account));
468         break;
469     case PROP_PLACEHOLDER:
470         g_value_set_boolean(value, xaccAccountGetPlaceholder(account));
471         break;
472     case PROP_FILTER:
473         g_value_set_string(value, xaccAccountGetFilter(account));
474         break;
475     case PROP_SORT_ORDER:
476         g_value_set_string(value, xaccAccountGetSortOrder(account));
477         break;
478     case PROP_SORT_REVERSED:
479         g_value_set_boolean(value, xaccAccountGetSortReversed(account));
480         break;
481     case PROP_LOT_NEXT_ID:
482         /* Pre-set the value in case the frame is empty */
483         g_value_set_int64 (value, 0);
484         qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {KEY_LOT_MGMT, "next-id"});
485         break;
486     case PROP_ONLINE_ACCOUNT:
487         qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {KEY_ONLINE_ID});
488         break;
489     case PROP_IMP_APPEND_TEXT:
490         g_value_set_boolean(value, xaccAccountGetAppendText(account));
491         break;
492     case PROP_OFX_INCOME_ACCOUNT:
493         qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {KEY_ASSOC_INCOME_ACCOUNT});
494         break;
495     case PROP_AB_ACCOUNT_ID:
496         qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_ACCOUNT_ID});
497         break;
498     case PROP_AB_ACCOUNT_UID:
499         qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_ACCOUNT_UID});
500         break;
501     case PROP_AB_BANK_CODE:
502         qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_BANK_CODE});
503         break;
504     case PROP_AB_TRANS_RETRIEVAL:
505         qof_instance_get_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_TRANS_RETRIEVAL});
506         break;
507     default:
508         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
509         break;
510     }
511 }
512 
513 static void
gnc_account_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)514 gnc_account_set_property (GObject         *object,
515                           guint            prop_id,
516                           const GValue    *value,
517                           GParamSpec      *pspec)
518 {
519     Account *account;
520     gnc_numeric *number;
521     g_return_if_fail(GNC_IS_ACCOUNT(object));
522     account = GNC_ACCOUNT(object);
523     if (prop_id < PROP_RUNTIME_0)
524         g_assert (qof_instance_get_editlevel(account));
525 
526     switch (prop_id)
527     {
528     case PROP_NAME:
529         xaccAccountSetName(account, g_value_get_string(value));
530         break;
531     case PROP_CODE:
532         xaccAccountSetCode(account, g_value_get_string(value));
533         break;
534     case PROP_DESCRIPTION:
535         xaccAccountSetDescription(account, g_value_get_string(value));
536         break;
537     case PROP_COLOR:
538         xaccAccountSetColor(account, g_value_get_string(value));
539         break;
540     case PROP_NOTES:
541         xaccAccountSetNotes(account, g_value_get_string(value));
542         break;
543     case PROP_TYPE:
544         // NEED TO BE CONVERTED TO A G_TYPE_ENUM
545         xaccAccountSetType(account, static_cast<GNCAccountType>(g_value_get_int(value)));
546         break;
547     case PROP_COMMODITY:
548         xaccAccountSetCommodity(account, static_cast<gnc_commodity*>(g_value_get_object(value)));
549         break;
550     case PROP_COMMODITY_SCU:
551         xaccAccountSetCommoditySCU(account, g_value_get_int(value));
552         break;
553     case PROP_NON_STD_SCU:
554         xaccAccountSetNonStdSCU(account, g_value_get_boolean(value));
555         break;
556     case PROP_SORT_DIRTY:
557         gnc_account_set_sort_dirty(account);
558         break;
559     case PROP_BALANCE_DIRTY:
560         gnc_account_set_balance_dirty(account);
561         break;
562     case PROP_START_BALANCE:
563         number = static_cast<gnc_numeric*>(g_value_get_boxed(value));
564         gnc_account_set_start_balance(account, *number);
565         break;
566     case PROP_START_CLEARED_BALANCE:
567         number = static_cast<gnc_numeric*>(g_value_get_boxed(value));
568         gnc_account_set_start_cleared_balance(account, *number);
569         break;
570     case PROP_START_RECONCILED_BALANCE:
571         number = static_cast<gnc_numeric*>(g_value_get_boxed(value));
572         gnc_account_set_start_reconciled_balance(account, *number);
573         break;
574     case PROP_POLICY:
575         gnc_account_set_policy(account, static_cast<GNCPolicy*>(g_value_get_pointer(value)));
576         break;
577     case PROP_MARK:
578         xaccAccountSetMark(account, g_value_get_int(value));
579         break;
580     case PROP_TAX_RELATED:
581         xaccAccountSetTaxRelated(account, g_value_get_boolean(value));
582         break;
583     case PROP_TAX_CODE:
584         xaccAccountSetTaxUSCode(account, g_value_get_string(value));
585         break;
586     case PROP_TAX_SOURCE:
587         xaccAccountSetTaxUSPayerNameSource(account,
588                                            g_value_get_string(value));
589         break;
590     case PROP_TAX_COPY_NUMBER:
591         xaccAccountSetTaxUSCopyNumber(account,
592                                       g_value_get_int64(value));
593         break;
594     case PROP_HIDDEN:
595         xaccAccountSetHidden(account, g_value_get_boolean(value));
596         break;
597     case PROP_AUTO_INTEREST:
598         xaccAccountSetAutoInterest (account, g_value_get_boolean (value));
599         break;
600     case PROP_IS_OPENING_BALANCE:
601         xaccAccountSetIsOpeningBalance (account, g_value_get_boolean (value));
602         break;
603     case PROP_PLACEHOLDER:
604         xaccAccountSetPlaceholder(account, g_value_get_boolean(value));
605         break;
606     case PROP_FILTER:
607         xaccAccountSetFilter(account, g_value_get_string(value));
608         break;
609     case PROP_SORT_ORDER:
610         xaccAccountSetSortOrder(account, g_value_get_string(value));
611         break;
612     case PROP_SORT_REVERSED:
613         xaccAccountSetSortReversed(account, g_value_get_boolean(value));
614         break;
615     case PROP_LOT_NEXT_ID:
616         qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {KEY_LOT_MGMT, "next-id"});
617         break;
618     case PROP_ONLINE_ACCOUNT:
619         qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {KEY_ONLINE_ID});
620         break;
621     case PROP_IMP_APPEND_TEXT:
622         xaccAccountSetAppendText(account, g_value_get_boolean(value));
623         break;
624     case PROP_OFX_INCOME_ACCOUNT:
625         qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {KEY_ASSOC_INCOME_ACCOUNT});
626         break;
627     case PROP_AB_ACCOUNT_ID:
628         qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_ACCOUNT_ID});
629         break;
630     case PROP_AB_ACCOUNT_UID:
631         qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_ACCOUNT_UID});
632         break;
633     case PROP_AB_BANK_CODE:
634         qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_BANK_CODE});
635         break;
636     case PROP_AB_TRANS_RETRIEVAL:
637         qof_instance_set_path_kvp (QOF_INSTANCE (account), value, {AB_KEY, AB_TRANS_RETRIEVAL});
638         break;
639     default:
640         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
641         break;
642     }
643 }
644 
645 static void
gnc_account_class_init(AccountClass * klass)646 gnc_account_class_init (AccountClass *klass)
647 {
648     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
649 
650     gobject_class->dispose = gnc_account_dispose;
651     gobject_class->finalize = gnc_account_finalize;
652     gobject_class->set_property = gnc_account_set_property;
653     gobject_class->get_property = gnc_account_get_property;
654 
655     g_object_class_install_property
656     (gobject_class,
657      PROP_NAME,
658      g_param_spec_string ("name",
659                           "Account Name",
660                           "The accountName is an arbitrary string "
661                           "assigned by the user.  It is intended to "
662                           "a short, 5 to 30 character long string "
663                           "that is displayed by the GUI as the "
664                           "account mnemonic.  Account names may be "
665                           "repeated. but no two accounts that share "
666                           "a parent may have the same name.",
667                           NULL,
668                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
669 
670     g_object_class_install_property
671     (gobject_class,
672      PROP_FULL_NAME,
673      g_param_spec_string ("fullname",
674                           "Full Account Name",
675                           "The name of the account concatenated with "
676                           "all its parent account names to indicate "
677                           "a unique account.",
678                           NULL,
679                           static_cast<GParamFlags>(G_PARAM_READABLE)));
680 
681     g_object_class_install_property
682     (gobject_class,
683      PROP_CODE,
684      g_param_spec_string ("code",
685                           "Account Code",
686                           "The account code is an arbitrary string "
687                           "assigned by the user. It is intended to "
688                           "be reporting code that is a synonym for "
689                           "the accountName.",
690                           NULL,
691                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
692 
693     g_object_class_install_property
694     (gobject_class,
695      PROP_DESCRIPTION,
696      g_param_spec_string ("description",
697                           "Account Description",
698                           "The account description is an arbitrary "
699                           "string assigned by the user. It is intended "
700                           "to be a longer, 1-5 sentence description of "
701                           "what this account is all about.",
702                           NULL,
703                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
704 
705     g_object_class_install_property
706     (gobject_class,
707      PROP_COLOR,
708      g_param_spec_string ("color",
709                           "Account Color",
710                           "The account color is a color string assigned "
711                           "by the user. It is intended to highlight the "
712                           "account based on the users wishes.",
713                           NULL,
714                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
715 
716     g_object_class_install_property
717     (gobject_class,
718      PROP_NOTES,
719      g_param_spec_string ("notes",
720                           "Account Notes",
721                           "The account notes is an arbitrary provided "
722                           "for the user to attach any other text that "
723                           "they would like to associate with the account.",
724                           NULL,
725                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
726 
727     g_object_class_install_property
728     (gobject_class,
729      PROP_TYPE,
730      g_param_spec_int ("type",
731                        "Account Type",
732                        "The account type, picked from the enumerated list "
733                        "that includes ACCT_TYPE_BANK, ACCT_TYPE_STOCK, "
734                        "ACCT_TYPE_CREDIT, ACCT_TYPE_INCOME, etc.",
735                        ACCT_TYPE_NONE,
736                        NUM_ACCOUNT_TYPES - 1,
737                        ACCT_TYPE_BANK,
738                        static_cast<GParamFlags>(G_PARAM_READWRITE)));
739 
740     g_object_class_install_property
741     (gobject_class,
742      PROP_COMMODITY,
743      g_param_spec_object ("commodity",
744                           "Commodity",
745                           "The commodity field denotes the kind of "
746                           "'stuff' stored  in this account, whether "
747                           "it is USD, gold, stock, etc.",
748                           GNC_TYPE_COMMODITY,
749                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
750 
751     g_object_class_install_property
752     (gobject_class,
753      PROP_COMMODITY_SCU,
754      g_param_spec_int ("commodity-scu",
755                        "Commodity SCU",
756                        "The smallest fraction of the commodity that is "
757                        "tracked.  This number is used as the denominator "
758                        "value in 1/x, so a value of 100 says that the "
759                        "commodity can be divided into hundredths.  E.G."
760                        "1 USD can be divided into 100 cents.",
761                        0,
762                        G_MAXINT32,
763                        GNC_COMMODITY_MAX_FRACTION,
764                        static_cast<GParamFlags>(G_PARAM_READWRITE)));
765 
766     g_object_class_install_property
767     (gobject_class,
768      PROP_NON_STD_SCU,
769      g_param_spec_boolean ("non-std-scu",
770                            "Non-std SCU",
771                            "TRUE if the account SCU doesn't match "
772                            "the commodity SCU.  This indicates a case "
773                            "where the two were accidentally set to "
774                            "mismatched values in older versions of "
775                            "GnuCash.",
776                            FALSE,
777                            static_cast<GParamFlags>(G_PARAM_READWRITE)));
778 
779     g_object_class_install_property
780     (gobject_class,
781      PROP_SORT_DIRTY,
782      g_param_spec_boolean("sort-dirty",
783                           "Sort Dirty",
784                           "TRUE if the splits in the account needs to be "
785                           "resorted.  This flag is set by the accounts "
786                           "code for certain internal modifications, or "
787                           "when external code calls the engine to say a "
788                           "split has been modified in a way that may "
789                           "affect the sort order of the account. Note: "
790                           "This value can only be set to TRUE.",
791                           FALSE,
792                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
793 
794     g_object_class_install_property
795     (gobject_class,
796      PROP_BALANCE_DIRTY,
797      g_param_spec_boolean("balance-dirty",
798                           "Balance Dirty",
799                           "TRUE if the running balances in the account "
800                           "needs to be recalculated.  This flag is set "
801                           "by the accounts code for certain internal "
802                           "modifications, or when external code calls "
803                           "the engine to say a split has been modified. "
804                           "Note: This value can only be set to TRUE.",
805                           FALSE,
806                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
807 
808     g_object_class_install_property
809     (gobject_class,
810      PROP_START_BALANCE,
811      g_param_spec_boxed("start-balance",
812                         "Starting Balance",
813                         "The starting balance for the account.  This "
814                         "parameter is intended for use with backends that "
815                         "do not return the complete list of splits for an "
816                         "account, but rather return a partial list.  In "
817                         "such a case, the backend will typically return "
818                         "all of the splits after some certain date, and "
819                         "the 'starting balance' will represent the "
820                         "summation of the splits up to that date.",
821                         GNC_TYPE_NUMERIC,
822                         static_cast<GParamFlags>(G_PARAM_READWRITE)));
823 
824     g_object_class_install_property
825     (gobject_class,
826      PROP_START_NOCLOSING_BALANCE,
827      g_param_spec_boxed("start-noclosing-balance",
828                         "Starting No-closing Balance",
829                         "The starting balance for the account, ignoring closing."
830                         "This parameter is intended for use with backends "
831                         "that do not return the complete list of splits "
832                         "for an account, but rather return a partial "
833                         "list.  In such a case, the backend will "
834                         "typically return all of the splits after "
835                         "some certain date, and the 'starting noclosing "
836                         "balance' will represent the summation of the "
837                         "splits up to that date, ignoring closing splits.",
838                         GNC_TYPE_NUMERIC,
839                         static_cast<GParamFlags>(G_PARAM_READWRITE)));
840 
841     g_object_class_install_property
842     (gobject_class,
843      PROP_START_CLEARED_BALANCE,
844      g_param_spec_boxed("start-cleared-balance",
845                         "Starting Cleared Balance",
846                         "The starting cleared balance for the account.  "
847                         "This parameter is intended for use with backends "
848                         "that do not return the complete list of splits "
849                         "for an account, but rather return a partial "
850                         "list.  In such a case, the backend will "
851                         "typically return all of the splits after "
852                         "some certain date, and the 'starting cleared "
853                         "balance' will represent the summation of the "
854                         "splits up to that date.",
855                         GNC_TYPE_NUMERIC,
856                         static_cast<GParamFlags>(G_PARAM_READWRITE)));
857 
858     g_object_class_install_property
859     (gobject_class,
860      PROP_START_RECONCILED_BALANCE,
861      g_param_spec_boxed("start-reconciled-balance",
862                         "Starting Reconciled Balance",
863                         "The starting reconciled balance for the "
864                         "account.  This parameter is intended for use "
865                         "with backends that do not return the complete "
866                         "list of splits for an account, but rather return "
867                         "a partial list.  In such a case, the backend "
868                         "will typically return all of the splits after "
869                         "some certain date, and the 'starting reconciled "
870                         "balance' will represent the summation of the "
871                         "splits up to that date.",
872                         GNC_TYPE_NUMERIC,
873                         static_cast<GParamFlags>(G_PARAM_READWRITE)));
874 
875     g_object_class_install_property
876     (gobject_class,
877      PROP_END_BALANCE,
878      g_param_spec_boxed("end-balance",
879                         "Ending Account Balance",
880                         "This is the current ending balance for the "
881                         "account.  It is computed from the sum of the "
882                         "starting balance and all splits in the account.",
883                         GNC_TYPE_NUMERIC,
884                         G_PARAM_READABLE));
885 
886     g_object_class_install_property
887     (gobject_class,
888      PROP_END_NOCLOSING_BALANCE,
889      g_param_spec_boxed("end-noclosing-balance",
890                         "Ending Account Noclosing Balance",
891                         "This is the current ending no-closing balance for "
892                         "the account.  It is computed from the sum of the "
893                         "starting balance and all cleared splits in the "
894                         "account.",
895                         GNC_TYPE_NUMERIC,
896                         G_PARAM_READABLE));
897 
898     g_object_class_install_property
899     (gobject_class,
900      PROP_END_CLEARED_BALANCE,
901      g_param_spec_boxed("end-cleared-balance",
902                         "Ending Account Cleared Balance",
903                         "This is the current ending cleared balance for "
904                         "the account.  It is computed from the sum of the "
905                         "starting balance and all cleared splits in the "
906                         "account.",
907                         GNC_TYPE_NUMERIC,
908                         G_PARAM_READABLE));
909 
910     g_object_class_install_property
911     (gobject_class,
912      PROP_END_RECONCILED_BALANCE,
913      g_param_spec_boxed("end-reconciled-balance",
914                         "Ending Account Reconciled Balance",
915                         "This is the current ending reconciled balance "
916                         "for the account.  It is computed from the sum of "
917                         "the starting balance and all reconciled splits "
918                         "in the account.",
919                         GNC_TYPE_NUMERIC,
920                         static_cast<GParamFlags>(G_PARAM_READABLE)));
921 
922     g_object_class_install_property
923     (gobject_class,
924      PROP_POLICY,
925      g_param_spec_pointer ("policy",
926                            "Policy",
927                            "The account lots policy.",
928                            static_cast<GParamFlags>(G_PARAM_READWRITE)));
929 
930     g_object_class_install_property
931     (gobject_class,
932      PROP_MARK,
933      g_param_spec_int ("acct-mark",
934                        "Account Mark",
935                        "Ipsum Lorem",
936                        0,
937                        G_MAXINT16,
938                        0,
939                        static_cast<GParamFlags>(G_PARAM_READWRITE)));
940 
941     g_object_class_install_property
942     (gobject_class,
943      PROP_TAX_RELATED,
944      g_param_spec_boolean ("tax-related",
945                            "Tax Related",
946                            "Whether the account maps to an entry on an "
947                            "income tax document.",
948                            FALSE,
949                            static_cast<GParamFlags>(G_PARAM_READWRITE)));
950 
951     g_object_class_install_property
952     (gobject_class,
953      PROP_IS_OPENING_BALANCE,
954      g_param_spec_boolean ("opening-balance",
955                            "Opening Balance",
956                            "Whether the account holds opening balances",
957                            FALSE,
958                            static_cast<GParamFlags>(G_PARAM_READWRITE)));
959 
960     g_object_class_install_property
961     (gobject_class,
962      PROP_TAX_CODE,
963      g_param_spec_string ("tax-code",
964                           "Tax Code",
965                           "This is the code for mapping an account to a "
966                           "specific entry on a taxable document.  In the "
967                           "United States it is used to transfer totals "
968                           "into tax preparation software.",
969                           NULL,
970                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
971 
972     g_object_class_install_property
973     (gobject_class,
974      PROP_TAX_SOURCE,
975      g_param_spec_string ("tax-source",
976                           "Tax Source",
977                           "This specifies where exported name comes from.",
978                           NULL,
979                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
980 
981     g_object_class_install_property
982     (gobject_class,
983      PROP_TAX_COPY_NUMBER,
984      g_param_spec_int64 ("tax-copy-number",
985                          "Tax Copy Number",
986                          "This specifies the copy number of the tax "
987                          "form/schedule.",
988                          (gint64)1,
989                          G_MAXINT64,
990                          (gint64)1,
991                          static_cast<GParamFlags>(G_PARAM_READWRITE)));
992 
993     g_object_class_install_property
994     (gobject_class,
995      PROP_HIDDEN,
996      g_param_spec_boolean ("hidden",
997                            "Hidden",
998                            "Whether the account should be hidden in the  "
999                            "account tree.",
1000                            FALSE,
1001                            static_cast<GParamFlags>(G_PARAM_READWRITE)));
1002 
1003     g_object_class_install_property
1004     (gobject_class,
1005      PROP_AUTO_INTEREST,
1006      g_param_spec_boolean ("auto-interest-transfer",
1007                            "Auto Interest",
1008                            "Whether an interest transfer should be automatically  "
1009                            "added before reconcile.",
1010                            FALSE,
1011                            static_cast<GParamFlags>(G_PARAM_READWRITE)));
1012 
1013     g_object_class_install_property
1014     (gobject_class,
1015      PROP_PLACEHOLDER,
1016      g_param_spec_boolean ("placeholder",
1017                            "Placeholder",
1018                            "Whether the account is a placeholder account which does not "
1019                            "allow transactions to be created, edited or deleted.",
1020                            FALSE,
1021                            static_cast<GParamFlags>(G_PARAM_READWRITE)));
1022 
1023     g_object_class_install_property
1024     (gobject_class,
1025      PROP_FILTER,
1026      g_param_spec_string ("filter",
1027                           "Account Filter",
1028                           "The account filter is a value saved to allow "
1029                           "filters to be recalled.",
1030                           NULL,
1031                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
1032 
1033     g_object_class_install_property
1034     (gobject_class,
1035      PROP_SORT_ORDER,
1036      g_param_spec_string ("sort-order",
1037                           "Account Sort Order",
1038                           "The account sort order is a value saved to allow "
1039                           "the sort order to be recalled.",
1040                           NULL,
1041                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
1042 
1043     g_object_class_install_property
1044     (gobject_class,
1045      PROP_SORT_REVERSED,
1046      g_param_spec_boolean ("sort-reversed",
1047                           "Account Sort Reversed",
1048                           "Parameter to store whether the sort order is reversed or not.",
1049                           FALSE,
1050                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
1051 
1052     g_object_class_install_property
1053     (gobject_class,
1054      PROP_LOT_NEXT_ID,
1055      g_param_spec_int64 ("lot-next-id",
1056                          "Lot Next ID",
1057                          "Tracks the next id to use in gnc_lot_make_default.",
1058                          (gint64)1,
1059                          G_MAXINT64,
1060                          (gint64)1,
1061                          static_cast<GParamFlags>(G_PARAM_READWRITE)));
1062 
1063     g_object_class_install_property
1064     (gobject_class,
1065      PROP_ONLINE_ACCOUNT,
1066      g_param_spec_string ("online-id",
1067                           "Online Account ID",
1068                           "The online account which corresponds to this "
1069                           "account for OFX import",
1070                           NULL,
1071                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
1072 
1073     g_object_class_install_property
1074     (gobject_class,
1075      PROP_IMP_APPEND_TEXT,
1076      g_param_spec_boolean ("import-append-text",
1077                            "Import Append Text",
1078                            "Saved state of Append checkbox for setting initial "
1079                            "value next time this account is imported.",
1080                            FALSE,
1081                            static_cast<GParamFlags>(G_PARAM_READWRITE)));
1082 
1083      g_object_class_install_property(
1084        gobject_class,
1085        PROP_OFX_INCOME_ACCOUNT,
1086         g_param_spec_boxed("ofx-income-account",
1087                            "Associated income account",
1088                            "Used by the OFX importer.",
1089                            GNC_TYPE_GUID,
1090                            static_cast<GParamFlags>(G_PARAM_READWRITE)));
1091 
1092     g_object_class_install_property
1093     (gobject_class,
1094      PROP_AB_ACCOUNT_ID,
1095      g_param_spec_string ("ab-account-id",
1096                           "AQBanking Account ID",
1097                           "The AqBanking account which corresponds to this "
1098                           "account for AQBanking import",
1099                           NULL,
1100                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
1101     g_object_class_install_property
1102     (gobject_class,
1103      PROP_AB_BANK_CODE,
1104      g_param_spec_string ("ab-bank-code",
1105                           "AQBanking Bank Code",
1106                           "The online account which corresponds to this "
1107                           "account for AQBanking import",
1108                           NULL,
1109                           static_cast<GParamFlags>(G_PARAM_READWRITE)));
1110 
1111     g_object_class_install_property
1112     (gobject_class,
1113      PROP_AB_ACCOUNT_UID,
1114      g_param_spec_int64 ("ab-account-uid",
1115                          "AQBanking Account UID",
1116                          "Tracks the next id to use in gnc_lot_make_default.",
1117                          (gint64)1,
1118                          G_MAXINT64,
1119                          (gint64)1,
1120                          static_cast<GParamFlags>(G_PARAM_READWRITE)));
1121 
1122     g_object_class_install_property
1123     (gobject_class,
1124      PROP_AB_TRANS_RETRIEVAL,
1125      g_param_spec_boxed("ab-trans-retrieval",
1126                         "AQBanking Last Transaction Retrieval",
1127                         "The time of the last transaction retrieval for this "
1128                         "account.",
1129                         GNC_TYPE_TIME64,
1130                         static_cast<GParamFlags>(G_PARAM_READWRITE)));
1131 
1132 }
1133 
1134 static void
xaccInitAccount(Account * acc,QofBook * book)1135 xaccInitAccount (Account * acc, QofBook *book)
1136 {
1137     ENTER ("book=%p\n", book);
1138     qof_instance_init_data (&acc->inst, GNC_ID_ACCOUNT, book);
1139 
1140     LEAVE ("account=%p\n", acc);
1141 }
1142 
1143 /********************************************************************\
1144 \********************************************************************/
1145 
1146 QofBook *
gnc_account_get_book(const Account * account)1147 gnc_account_get_book(const Account *account)
1148 {
1149     if (!account) return NULL;
1150     return qof_instance_get_book(QOF_INSTANCE(account));
1151 }
1152 
1153 /********************************************************************\
1154 \********************************************************************/
1155 
1156 static Account *
gnc_coll_get_root_account(QofCollection * col)1157 gnc_coll_get_root_account (QofCollection *col)
1158 {
1159     if (!col) return NULL;
1160     return static_cast<Account*>(qof_collection_get_data (col));
1161 }
1162 
1163 static void
gnc_coll_set_root_account(QofCollection * col,Account * root)1164 gnc_coll_set_root_account (QofCollection *col, Account *root)
1165 {
1166     AccountPrivate *rpriv;
1167     Account *old_root;
1168     if (!col) return;
1169 
1170     old_root = gnc_coll_get_root_account (col);
1171     if (old_root == root) return;
1172 
1173     /* If the new root is already linked into the tree somewhere, then
1174      * remove it from its current position before adding it at the
1175      * top. */
1176     rpriv = GET_PRIVATE(root);
1177     if (rpriv->parent)
1178     {
1179         xaccAccountBeginEdit(root);
1180         gnc_account_remove_child(rpriv->parent, root);
1181         xaccAccountCommitEdit(root);
1182     }
1183 
1184     qof_collection_set_data (col, root);
1185 
1186     if (old_root)
1187     {
1188         xaccAccountBeginEdit (old_root);
1189         xaccAccountDestroy (old_root);
1190     }
1191 }
1192 
1193 Account *
gnc_book_get_root_account(QofBook * book)1194 gnc_book_get_root_account (QofBook *book)
1195 {
1196     QofCollection *col;
1197     Account *root;
1198 
1199     if (!book) return NULL;
1200     col = qof_book_get_collection (book, GNC_ID_ROOT_ACCOUNT);
1201     root = gnc_coll_get_root_account (col);
1202     if (root == NULL && !qof_book_shutting_down(book))
1203         root = gnc_account_create_root(book);
1204     return root;
1205 }
1206 
1207 void
gnc_book_set_root_account(QofBook * book,Account * root)1208 gnc_book_set_root_account (QofBook *book, Account *root)
1209 {
1210     QofCollection *col;
1211     if (!book) return;
1212 
1213     if (root && gnc_account_get_book(root) != book)
1214     {
1215         PERR ("cannot mix and match books freely!");
1216         return;
1217     }
1218 
1219     col = qof_book_get_collection (book, GNC_ID_ROOT_ACCOUNT);
1220     gnc_coll_set_root_account (col, root);
1221 }
1222 
1223 /********************************************************************\
1224 \********************************************************************/
1225 
1226 Account *
xaccMallocAccount(QofBook * book)1227 xaccMallocAccount (QofBook *book)
1228 {
1229     Account *acc;
1230 
1231     g_return_val_if_fail (book, NULL);
1232 
1233     acc = static_cast<Account*>(g_object_new (GNC_TYPE_ACCOUNT, NULL));
1234     xaccInitAccount (acc, book);
1235     qof_event_gen (&acc->inst, QOF_EVENT_CREATE, NULL);
1236 
1237     return acc;
1238 }
1239 
1240 Account *
gnc_account_create_root(QofBook * book)1241 gnc_account_create_root (QofBook *book)
1242 {
1243     Account *root;
1244     AccountPrivate *rpriv;
1245 
1246     root = xaccMallocAccount(book);
1247     rpriv = GET_PRIVATE(root);
1248     xaccAccountBeginEdit(root);
1249     rpriv->type = ACCT_TYPE_ROOT;
1250     rpriv->accountName = qof_string_cache_replace(rpriv->accountName, "Root Account");
1251     mark_account (root);
1252     xaccAccountCommitEdit(root);
1253     gnc_book_set_root_account(book, root);
1254     return root;
1255 }
1256 
1257 Account *
xaccCloneAccount(const Account * from,QofBook * book)1258 xaccCloneAccount(const Account *from, QofBook *book)
1259 {
1260     Account *ret;
1261     AccountPrivate *from_priv, *priv;
1262 
1263     g_return_val_if_fail(GNC_IS_ACCOUNT(from), NULL);
1264     g_return_val_if_fail(QOF_IS_BOOK(book), NULL);
1265 
1266     ENTER (" ");
1267     ret = static_cast<Account*>(g_object_new (GNC_TYPE_ACCOUNT, NULL));
1268     g_return_val_if_fail (ret, NULL);
1269 
1270     from_priv = GET_PRIVATE(from);
1271     priv = GET_PRIVATE(ret);
1272     xaccInitAccount (ret, book);
1273 
1274     /* Do not Begin/CommitEdit() here; give the caller
1275      * a chance to fix things up, and let them do it.
1276      * Also let caller issue the generate_event (EVENT_CREATE) */
1277     priv->type = from_priv->type;
1278 
1279     priv->accountName = qof_string_cache_replace(priv->accountName, from_priv->accountName);
1280     priv->accountCode = qof_string_cache_replace(priv->accountCode, from_priv->accountCode);
1281     priv->description = qof_string_cache_replace(priv->description, from_priv->description);
1282 
1283     qof_instance_copy_kvp (QOF_INSTANCE (ret), QOF_INSTANCE (from));
1284 
1285     /* The new book should contain a commodity that matches
1286      * the one in the old book. Find it, use it. */
1287     priv->commodity = gnc_commodity_obtain_twin(from_priv->commodity, book);
1288     gnc_commodity_increment_usage_count(priv->commodity);
1289 
1290     priv->commodity_scu = from_priv->commodity_scu;
1291     priv->non_standard_scu = from_priv->non_standard_scu;
1292 
1293     qof_instance_set_dirty(&ret->inst);
1294     LEAVE (" ");
1295     return ret;
1296 }
1297 
1298 /********************************************************************\
1299 \********************************************************************/
1300 
1301 static void
xaccFreeOneChildAccount(Account * acc,gpointer dummy)1302 xaccFreeOneChildAccount (Account *acc, gpointer dummy)
1303 {
1304     /* FIXME: this code is kind of hacky.  actually, all this code
1305      * seems to assume that the account edit levels are all 1. */
1306     if (qof_instance_get_editlevel(acc) == 0)
1307         xaccAccountBeginEdit(acc);
1308     xaccAccountDestroy(acc);
1309 }
1310 
1311 static void
xaccFreeAccountChildren(Account * acc)1312 xaccFreeAccountChildren (Account *acc)
1313 {
1314     AccountPrivate *priv;
1315     GList *children;
1316 
1317     /* Copy the list since it will be modified */
1318     priv = GET_PRIVATE(acc);
1319     children = g_list_copy(priv->children);
1320     g_list_foreach(children, (GFunc)xaccFreeOneChildAccount, NULL);
1321     g_list_free(children);
1322 
1323     /* The foreach should have removed all the children already. */
1324     if (priv->children)
1325         g_list_free(priv->children);
1326     priv->children = NULL;
1327 }
1328 
1329 /* The xaccFreeAccount() routine releases memory associated with the
1330  * account.  It should never be called directly from user code;
1331  * instead, the xaccAccountDestroy() routine should be used (because
1332  * xaccAccountDestroy() has the correct commit semantics). */
1333 static void
xaccFreeAccount(Account * acc)1334 xaccFreeAccount (Account *acc)
1335 {
1336     AccountPrivate *priv;
1337     GList *lp;
1338 
1339     g_return_if_fail(GNC_IS_ACCOUNT(acc));
1340 
1341     priv = GET_PRIVATE(acc);
1342     qof_event_gen (&acc->inst, QOF_EVENT_DESTROY, NULL);
1343 
1344     if (priv->children)
1345     {
1346         PERR (" instead of calling xaccFreeAccount(), please call\n"
1347               " xaccAccountBeginEdit(); xaccAccountDestroy();\n");
1348 
1349         /* First, recursively free children */
1350         xaccFreeAccountChildren(acc);
1351     }
1352 
1353     /* remove lots -- although these should be gone by now. */
1354     if (priv->lots)
1355     {
1356         PERR (" instead of calling xaccFreeAccount(), please call\n"
1357               " xaccAccountBeginEdit(); xaccAccountDestroy();\n");
1358 
1359         for (lp = priv->lots; lp; lp = lp->next)
1360         {
1361             GNCLot *lot = static_cast<GNCLot*>(lp->data);
1362             gnc_lot_destroy (lot);
1363         }
1364         g_list_free (priv->lots);
1365         priv->lots = NULL;
1366     }
1367 
1368     /* Next, clean up the splits */
1369     /* NB there shouldn't be any splits by now ... they should
1370      * have been all been freed by CommitEdit().  We can remove this
1371      * check once we know the warning isn't occurring any more. */
1372     if (priv->splits)
1373     {
1374         GList *slist;
1375         PERR (" instead of calling xaccFreeAccount(), please call\n"
1376               " xaccAccountBeginEdit(); xaccAccountDestroy();\n");
1377 
1378         qof_instance_reset_editlevel(acc);
1379 
1380         slist = g_list_copy(priv->splits);
1381         for (lp = slist; lp; lp = lp->next)
1382         {
1383             Split *s = (Split *) lp->data;
1384             g_assert(xaccSplitGetAccount(s) == acc);
1385             xaccSplitDestroy (s);
1386         }
1387         g_list_free(slist);
1388 /* Nothing here (or in xaccAccountCommitEdit) NULLs priv->splits, so this asserts every time.
1389         g_assert(priv->splits == NULL);
1390 */
1391     }
1392 
1393     qof_string_cache_remove(priv->accountName);
1394     qof_string_cache_remove(priv->accountCode);
1395     qof_string_cache_remove(priv->description);
1396     priv->accountName = priv->accountCode = priv->description = nullptr;
1397 
1398     if (priv->last_num != is_unset)
1399         g_free (priv->last_num);
1400     if (priv->tax_us_code != is_unset)
1401         g_free (priv->tax_us_code);
1402     if (priv->tax_us_pns != is_unset)
1403         g_free (priv->tax_us_pns);
1404     if (priv->color != is_unset)
1405         g_free (priv->color);
1406     if (priv->sort_order != is_unset)
1407         g_free (priv->sort_order);
1408     if (priv->notes != is_unset)
1409         g_free (priv->notes);
1410     if (priv->filter != is_unset)
1411         g_free (priv->filter);
1412 
1413     /* zero out values, just in case stray
1414      * pointers are pointing here. */
1415 
1416     priv->last_num = nullptr;
1417     priv->tax_us_code = nullptr;
1418     priv->tax_us_pns = nullptr;
1419     priv->color == nullptr;
1420     priv->sort_order == nullptr;
1421     priv->notes == nullptr;
1422     priv->filter == nullptr;
1423 
1424     priv->parent = nullptr;
1425     priv->children = nullptr;
1426 
1427     priv->balance  = gnc_numeric_zero();
1428     priv->noclosing_balance = gnc_numeric_zero();
1429     priv->cleared_balance = gnc_numeric_zero();
1430     priv->reconciled_balance = gnc_numeric_zero();
1431 
1432     priv->type = ACCT_TYPE_NONE;
1433     gnc_commodity_decrement_usage_count(priv->commodity);
1434     priv->commodity = NULL;
1435 
1436     priv->balance_dirty = FALSE;
1437     priv->sort_dirty = FALSE;
1438 
1439     /* qof_instance_release (&acc->inst); */
1440     g_object_unref(acc);
1441 }
1442 
1443 /********************************************************************\
1444  * transactional routines
1445 \********************************************************************/
1446 
1447 void
xaccAccountBeginEdit(Account * acc)1448 xaccAccountBeginEdit (Account *acc)
1449 {
1450     g_return_if_fail(acc);
1451     qof_begin_edit(&acc->inst);
1452 }
1453 
on_done(QofInstance * inst)1454 static void on_done(QofInstance *inst)
1455 {
1456     /* old event style */
1457     qof_event_gen (inst, QOF_EVENT_MODIFY, NULL);
1458 }
1459 
on_err(QofInstance * inst,QofBackendError errcode)1460 static void on_err (QofInstance *inst, QofBackendError errcode)
1461 {
1462     PERR("commit error: %d", errcode);
1463     gnc_engine_signal_commit_error( errcode );
1464 }
1465 
acc_free(QofInstance * inst)1466 static void acc_free (QofInstance *inst)
1467 {
1468     AccountPrivate *priv;
1469     Account *acc = (Account *) inst;
1470 
1471     priv = GET_PRIVATE(acc);
1472     if (priv->parent)
1473         gnc_account_remove_child(priv->parent, acc);
1474     xaccFreeAccount(acc);
1475 }
1476 
1477 static void
destroy_pending_splits_for_account(QofInstance * ent,gpointer acc)1478 destroy_pending_splits_for_account(QofInstance *ent, gpointer acc)
1479 {
1480     Transaction *trans = (Transaction *) ent;
1481     Split *split;
1482 
1483     if (xaccTransIsOpen(trans))
1484         while ((split = xaccTransFindSplitByAccount(trans, static_cast<Account*>(acc))))
1485             xaccSplitDestroy(split);
1486 }
1487 
1488 void
xaccAccountCommitEdit(Account * acc)1489 xaccAccountCommitEdit (Account *acc)
1490 {
1491     AccountPrivate *priv;
1492     QofBook *book;
1493 
1494     g_return_if_fail(acc);
1495     if (!qof_commit_edit(&acc->inst)) return;
1496 
1497     /* If marked for deletion, get rid of subaccounts first,
1498      * and then the splits ... */
1499     priv = GET_PRIVATE(acc);
1500     if (qof_instance_get_destroying(acc))
1501     {
1502         GList *lp, *slist;
1503         QofCollection *col;
1504 
1505         qof_instance_increase_editlevel(acc);
1506 
1507         /* First, recursively free children */
1508         xaccFreeAccountChildren(acc);
1509 
1510         PINFO ("freeing splits for account %p (%s)",
1511                acc, priv->accountName ? priv->accountName : "(null)");
1512 
1513         book = qof_instance_get_book(acc);
1514 
1515         /* If book is shutting down, just clear the split list.  The splits
1516            themselves will be destroyed by the transaction code */
1517         if (!qof_book_shutting_down(book))
1518         {
1519             slist = g_list_copy(priv->splits);
1520             for (lp = slist; lp; lp = lp->next)
1521             {
1522                 Split *s = static_cast<Split *>(lp->data);
1523                 xaccSplitDestroy (s);
1524             }
1525             g_list_free(slist);
1526         }
1527         else
1528         {
1529             g_list_free(priv->splits);
1530             priv->splits = NULL;
1531         }
1532 
1533         /* It turns out there's a case where this assertion does not hold:
1534            When the user tries to delete an Imbalance account, while also
1535            deleting all the splits in it.  The splits will just get
1536            recreated and put right back into the same account!
1537 
1538            g_assert(priv->splits == NULL || qof_book_shutting_down(acc->inst.book));
1539         */
1540 
1541         if (!qof_book_shutting_down(book))
1542         {
1543             col = qof_book_get_collection(book, GNC_ID_TRANS);
1544             qof_collection_foreach(col, destroy_pending_splits_for_account, acc);
1545 
1546             /* the lots should be empty by now */
1547             for (lp = priv->lots; lp; lp = lp->next)
1548             {
1549                 GNCLot *lot = static_cast<GNCLot*>(lp->data);
1550                 gnc_lot_destroy (lot);
1551             }
1552         }
1553         g_list_free(priv->lots);
1554         priv->lots = NULL;
1555 
1556         qof_instance_set_dirty(&acc->inst);
1557         qof_instance_decrease_editlevel(acc);
1558     }
1559     else
1560     {
1561         xaccAccountBringUpToDate(acc);
1562     }
1563 
1564     qof_commit_edit_part2(&acc->inst, on_err, on_done, acc_free);
1565 }
1566 
1567 void
xaccAccountDestroy(Account * acc)1568 xaccAccountDestroy (Account *acc)
1569 {
1570     g_return_if_fail(GNC_IS_ACCOUNT(acc));
1571 
1572     qof_instance_set_destroying(acc, TRUE);
1573 
1574     xaccAccountCommitEdit (acc);
1575 }
1576 
1577 /********************************************************************\
1578 \********************************************************************/
1579 static gint
compare_account_by_name(gconstpointer a,gconstpointer b)1580 compare_account_by_name (gconstpointer a, gconstpointer b)
1581 {
1582     AccountPrivate *priv_a, *priv_b;
1583     if (a && !b) return 1;
1584     if (b && !a) return -1;
1585     if (!a && !b) return 0;
1586     priv_a = GET_PRIVATE((Account*)a);
1587     priv_b = GET_PRIVATE((Account*)b);
1588     if ((priv_a->accountCode && strlen (priv_a->accountCode)) ||
1589         (priv_b->accountCode && strlen (priv_b->accountCode)))
1590         return g_strcmp0 (priv_a->accountCode, priv_b->accountCode);
1591     return g_strcmp0 (priv_a->accountName, priv_b->accountName);
1592 }
1593 
1594 static gboolean
xaccAcctChildrenEqual(const GList * na,const GList * nb,gboolean check_guids)1595 xaccAcctChildrenEqual(const GList *na,
1596                       const GList *nb,
1597                       gboolean check_guids)
1598 {
1599     if ((!na && nb) || (na && !nb))
1600     {
1601         PINFO ("only one has accounts");
1602         return(FALSE);
1603     }
1604     if (g_list_length ((GList*)na) != g_list_length ((GList*)nb))
1605     {
1606         PINFO ("Accounts have different numbers of children");
1607         return (FALSE);
1608     }
1609 
1610     while (na)
1611     {
1612         Account *aa = static_cast<Account*>(na->data);
1613         Account *ab;
1614         GList *node = g_list_find_custom ((GList*)nb, aa,
1615                                           (GCompareFunc)compare_account_by_name);
1616 
1617         if (!node)
1618         {
1619             PINFO ("Unable to find matching child account.");
1620             return FALSE;
1621         }
1622         ab = static_cast<Account*>(node->data);
1623         if (!xaccAccountEqual(aa, ab, check_guids))
1624         {
1625             char sa[GUID_ENCODING_LENGTH + 1];
1626             char sb[GUID_ENCODING_LENGTH + 1];
1627 
1628             guid_to_string_buff (xaccAccountGetGUID (aa), sa);
1629             guid_to_string_buff (xaccAccountGetGUID (ab), sb);
1630 
1631             PWARN ("accounts %s and %s differ", sa, sb);
1632 
1633             return(FALSE);
1634         }
1635 
1636         na = na->next;
1637     }
1638 
1639     return(TRUE);
1640 }
1641 
1642 gboolean
xaccAccountEqual(const Account * aa,const Account * ab,gboolean check_guids)1643 xaccAccountEqual(const Account *aa, const Account *ab, gboolean check_guids)
1644 {
1645     AccountPrivate *priv_aa, *priv_ab;
1646 
1647     if (!aa && !ab) return TRUE;
1648 
1649     g_return_val_if_fail(GNC_IS_ACCOUNT(aa), FALSE);
1650     g_return_val_if_fail(GNC_IS_ACCOUNT(ab), FALSE);
1651 
1652     priv_aa = GET_PRIVATE(aa);
1653     priv_ab = GET_PRIVATE(ab);
1654     if (priv_aa->type != priv_ab->type)
1655     {
1656         PWARN ("types differ: %d vs %d", priv_aa->type, priv_ab->type);
1657         return FALSE;
1658     }
1659 
1660     if (g_strcmp0(priv_aa->accountName, priv_ab->accountName) != 0)
1661     {
1662         PWARN ("names differ: %s vs %s", priv_aa->accountName, priv_ab->accountName);
1663         return FALSE;
1664     }
1665 
1666     if (g_strcmp0(priv_aa->accountCode, priv_ab->accountCode) != 0)
1667     {
1668         PWARN ("codes differ: %s vs %s", priv_aa->accountCode, priv_ab->accountCode);
1669         return FALSE;
1670     }
1671 
1672     if (g_strcmp0(priv_aa->description, priv_ab->description) != 0)
1673     {
1674         PWARN ("descriptions differ: %s vs %s", priv_aa->description, priv_ab->description);
1675         return FALSE;
1676     }
1677 
1678     if (!gnc_commodity_equal(priv_aa->commodity, priv_ab->commodity))
1679     {
1680         PWARN ("commodities differ");
1681         return FALSE;
1682     }
1683 
1684     if (check_guids)
1685     {
1686         if (qof_instance_guid_compare(aa, ab) != 0)
1687         {
1688             PWARN ("GUIDs differ");
1689             return FALSE;
1690         }
1691     }
1692 
1693     if (qof_instance_compare_kvp (QOF_INSTANCE (aa), QOF_INSTANCE (ab)) != 0)
1694     {
1695         char *frame_a;
1696         char *frame_b;
1697 
1698         frame_a = qof_instance_kvp_as_string (QOF_INSTANCE (aa));
1699         frame_b = qof_instance_kvp_as_string (QOF_INSTANCE (ab));
1700 
1701         PWARN ("kvp frames differ:\n%s\n\nvs\n\n%s", frame_a, frame_b);
1702 
1703         g_free (frame_a);
1704         g_free (frame_b);
1705 
1706         return FALSE;
1707     }
1708 
1709     if (!gnc_numeric_equal(priv_aa->starting_balance, priv_ab->starting_balance))
1710     {
1711         char *str_a;
1712         char *str_b;
1713 
1714         str_a = gnc_numeric_to_string(priv_aa->starting_balance);
1715         str_b = gnc_numeric_to_string(priv_ab->starting_balance);
1716 
1717         PWARN ("starting balances differ: %s vs %s", str_a, str_b);
1718 
1719         g_free (str_a);
1720         g_free (str_b);
1721 
1722         return FALSE;
1723     }
1724 
1725     if (!gnc_numeric_equal(priv_aa->starting_noclosing_balance,
1726                            priv_ab->starting_noclosing_balance))
1727     {
1728         char *str_a;
1729         char *str_b;
1730 
1731         str_a = gnc_numeric_to_string(priv_aa->starting_noclosing_balance);
1732         str_b = gnc_numeric_to_string(priv_ab->starting_noclosing_balance);
1733 
1734         PWARN ("starting noclosing balances differ: %s vs %s", str_a, str_b);
1735 
1736         g_free (str_a);
1737         g_free (str_b);
1738 
1739         return FALSE;
1740     }
1741     if (!gnc_numeric_equal(priv_aa->starting_cleared_balance,
1742                            priv_ab->starting_cleared_balance))
1743     {
1744         char *str_a;
1745         char *str_b;
1746 
1747         str_a = gnc_numeric_to_string(priv_aa->starting_cleared_balance);
1748         str_b = gnc_numeric_to_string(priv_ab->starting_cleared_balance);
1749 
1750         PWARN ("starting cleared balances differ: %s vs %s", str_a, str_b);
1751 
1752         g_free (str_a);
1753         g_free (str_b);
1754 
1755         return FALSE;
1756     }
1757 
1758     if (!gnc_numeric_equal(priv_aa->starting_reconciled_balance,
1759                            priv_ab->starting_reconciled_balance))
1760     {
1761         char *str_a;
1762         char *str_b;
1763 
1764         str_a = gnc_numeric_to_string(priv_aa->starting_reconciled_balance);
1765         str_b = gnc_numeric_to_string(priv_ab->starting_reconciled_balance);
1766 
1767         PWARN ("starting reconciled balances differ: %s vs %s", str_a, str_b);
1768 
1769         g_free (str_a);
1770         g_free (str_b);
1771 
1772         return FALSE;
1773     }
1774 
1775     if (!gnc_numeric_equal(priv_aa->balance, priv_ab->balance))
1776     {
1777         char *str_a;
1778         char *str_b;
1779 
1780         str_a = gnc_numeric_to_string(priv_aa->balance);
1781         str_b = gnc_numeric_to_string(priv_ab->balance);
1782 
1783         PWARN ("balances differ: %s vs %s", str_a, str_b);
1784 
1785         g_free (str_a);
1786         g_free (str_b);
1787 
1788         return FALSE;
1789     }
1790 
1791     if (!gnc_numeric_equal(priv_aa->noclosing_balance, priv_ab->noclosing_balance))
1792     {
1793         char *str_a;
1794         char *str_b;
1795 
1796         str_a = gnc_numeric_to_string(priv_aa->noclosing_balance);
1797         str_b = gnc_numeric_to_string(priv_ab->noclosing_balance);
1798 
1799         PWARN ("noclosing balances differ: %s vs %s", str_a, str_b);
1800 
1801         g_free (str_a);
1802         g_free (str_b);
1803 
1804         return FALSE;
1805     }
1806     if (!gnc_numeric_equal(priv_aa->cleared_balance, priv_ab->cleared_balance))
1807     {
1808         char *str_a;
1809         char *str_b;
1810 
1811         str_a = gnc_numeric_to_string(priv_aa->cleared_balance);
1812         str_b = gnc_numeric_to_string(priv_ab->cleared_balance);
1813 
1814         PWARN ("cleared balances differ: %s vs %s", str_a, str_b);
1815 
1816         g_free (str_a);
1817         g_free (str_b);
1818 
1819         return FALSE;
1820     }
1821 
1822     if (!gnc_numeric_equal(priv_aa->reconciled_balance, priv_ab->reconciled_balance))
1823     {
1824         char *str_a;
1825         char *str_b;
1826 
1827         str_a = gnc_numeric_to_string(priv_aa->reconciled_balance);
1828         str_b = gnc_numeric_to_string(priv_ab->reconciled_balance);
1829 
1830         PWARN ("reconciled balances differ: %s vs %s", str_a, str_b);
1831 
1832         g_free (str_a);
1833         g_free (str_b);
1834 
1835         return FALSE;
1836     }
1837 
1838     /* no parent; always compare downwards. */
1839 
1840     {
1841         GList *la = priv_aa->splits;
1842         GList *lb = priv_ab->splits;
1843 
1844         if ((la && !lb) || (!la && lb))
1845         {
1846             PWARN ("only one has splits");
1847             return FALSE;
1848         }
1849 
1850         if (la && lb)
1851         {
1852             /* presume that the splits are in the same order */
1853             while (la && lb)
1854             {
1855                 Split *sa = (Split *) la->data;
1856                 Split *sb = (Split *) lb->data;
1857 
1858                 if (!xaccSplitEqual(sa, sb, check_guids, TRUE, FALSE))
1859                 {
1860                     PWARN ("splits differ");
1861                     return(FALSE);
1862                 }
1863 
1864                 la = la->next;
1865                 lb = lb->next;
1866             }
1867 
1868             if ((la != NULL) || (lb != NULL))
1869             {
1870                 PWARN ("number of splits differs");
1871                 return(FALSE);
1872             }
1873         }
1874     }
1875 
1876     if (!xaccAcctChildrenEqual(priv_aa->children, priv_ab->children, check_guids))
1877     {
1878         PWARN ("children differ");
1879         return FALSE;
1880     }
1881 
1882     return(TRUE);
1883 }
1884 
1885 /********************************************************************\
1886 \********************************************************************/
1887 void
gnc_account_set_sort_dirty(Account * acc)1888 gnc_account_set_sort_dirty (Account *acc)
1889 {
1890     AccountPrivate *priv;
1891 
1892     g_return_if_fail(GNC_IS_ACCOUNT(acc));
1893 
1894     if (qof_instance_get_destroying(acc))
1895         return;
1896 
1897     priv = GET_PRIVATE(acc);
1898     priv->sort_dirty = TRUE;
1899 }
1900 
1901 void
gnc_account_set_balance_dirty(Account * acc)1902 gnc_account_set_balance_dirty (Account *acc)
1903 {
1904     AccountPrivate *priv;
1905 
1906     g_return_if_fail(GNC_IS_ACCOUNT(acc));
1907 
1908     if (qof_instance_get_destroying(acc))
1909         return;
1910 
1911     priv = GET_PRIVATE(acc);
1912     priv->balance_dirty = TRUE;
1913 }
1914 
gnc_account_set_defer_bal_computation(Account * acc,gboolean defer)1915 void gnc_account_set_defer_bal_computation (Account *acc, gboolean defer)
1916 {
1917     AccountPrivate *priv;
1918 
1919     g_return_if_fail (GNC_IS_ACCOUNT (acc));
1920 
1921     if (qof_instance_get_destroying (acc))
1922         return;
1923 
1924     priv = GET_PRIVATE (acc);
1925     priv->defer_bal_computation = defer;
1926 }
1927 
gnc_account_get_defer_bal_computation(Account * acc)1928 gboolean gnc_account_get_defer_bal_computation (Account *acc)
1929 {
1930     AccountPrivate *priv;
1931     if (!acc)
1932         return false;
1933     priv = GET_PRIVATE (acc);
1934     return priv->defer_bal_computation;
1935 }
1936 
1937 
1938 /********************************************************************\
1939 \********************************************************************/
1940 
1941 gboolean
gnc_account_insert_split(Account * acc,Split * s)1942 gnc_account_insert_split (Account *acc, Split *s)
1943 {
1944     AccountPrivate *priv;
1945     GList *node;
1946 
1947     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
1948     g_return_val_if_fail(GNC_IS_SPLIT(s), FALSE);
1949 
1950     priv = GET_PRIVATE(acc);
1951     node = g_list_find(priv->splits, s);
1952     if (node)
1953         return FALSE;
1954 
1955     if (qof_instance_get_editlevel(acc) == 0)
1956     {
1957         priv->splits = g_list_insert_sorted(priv->splits, s,
1958                                             (GCompareFunc)xaccSplitOrder);
1959     }
1960     else
1961     {
1962         priv->splits = g_list_prepend(priv->splits, s);
1963         priv->sort_dirty = TRUE;
1964     }
1965 
1966     //FIXME: find better event
1967     qof_event_gen (&acc->inst, QOF_EVENT_MODIFY, NULL);
1968     /* Also send an event based on the account */
1969     qof_event_gen(&acc->inst, GNC_EVENT_ITEM_ADDED, s);
1970 
1971     priv->balance_dirty = TRUE;
1972 //  DRH: Should the below be added? It is present in the delete path.
1973 //  xaccAccountRecomputeBalance(acc);
1974     return TRUE;
1975 }
1976 
1977 gboolean
gnc_account_remove_split(Account * acc,Split * s)1978 gnc_account_remove_split (Account *acc, Split *s)
1979 {
1980     AccountPrivate *priv;
1981     GList *node;
1982 
1983     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
1984     g_return_val_if_fail(GNC_IS_SPLIT(s), FALSE);
1985 
1986     priv = GET_PRIVATE(acc);
1987     node = g_list_find(priv->splits, s);
1988     if (NULL == node)
1989         return FALSE;
1990 
1991     priv->splits = g_list_delete_link(priv->splits, node);
1992     //FIXME: find better event type
1993     qof_event_gen(&acc->inst, QOF_EVENT_MODIFY, NULL);
1994     // And send the account-based event, too
1995     qof_event_gen(&acc->inst, GNC_EVENT_ITEM_REMOVED, s);
1996 
1997     priv->balance_dirty = TRUE;
1998     xaccAccountRecomputeBalance(acc);
1999     return TRUE;
2000 }
2001 
2002 void
xaccAccountSortSplits(Account * acc,gboolean force)2003 xaccAccountSortSplits (Account *acc, gboolean force)
2004 {
2005     AccountPrivate *priv;
2006 
2007     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2008 
2009     priv = GET_PRIVATE(acc);
2010     if (!priv->sort_dirty || (!force && qof_instance_get_editlevel(acc) > 0))
2011         return;
2012     priv->splits = g_list_sort(priv->splits, (GCompareFunc)xaccSplitOrder);
2013     priv->sort_dirty = FALSE;
2014     priv->balance_dirty = TRUE;
2015 }
2016 
2017 static void
xaccAccountBringUpToDate(Account * acc)2018 xaccAccountBringUpToDate(Account *acc)
2019 {
2020     if (!acc) return;
2021 
2022     /* if a re-sort happens here, then everything will update, so the
2023        cost basis and balance calls are no-ops */
2024     xaccAccountSortSplits(acc, FALSE);
2025     xaccAccountRecomputeBalance(acc);
2026 }
2027 
2028 /********************************************************************\
2029 \********************************************************************/
2030 
2031 void
xaccAccountSetGUID(Account * acc,const GncGUID * guid)2032 xaccAccountSetGUID (Account *acc, const GncGUID *guid)
2033 {
2034     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2035     g_return_if_fail(guid);
2036 
2037     /* XXX this looks fishy and weird to me ... */
2038     PINFO("acct=%p", acc);
2039     xaccAccountBeginEdit (acc);
2040     qof_instance_set_guid (&acc->inst, guid);
2041     qof_instance_set_dirty(&acc->inst);
2042     xaccAccountCommitEdit (acc);
2043 }
2044 
2045 /********************************************************************\
2046 \********************************************************************/
2047 
2048 Account *
xaccAccountLookup(const GncGUID * guid,QofBook * book)2049 xaccAccountLookup (const GncGUID *guid, QofBook *book)
2050 {
2051     QofCollection *col;
2052     if (!guid || !book) return NULL;
2053     col = qof_book_get_collection (book, GNC_ID_ACCOUNT);
2054     return (Account *) qof_collection_lookup_entity (col, guid);
2055 }
2056 
2057 /********************************************************************\
2058 \********************************************************************/
2059 
2060 void
xaccAccountSetMark(Account * acc,short m)2061 xaccAccountSetMark (Account *acc, short m)
2062 {
2063     AccountPrivate *priv;
2064 
2065     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2066 
2067     priv = GET_PRIVATE(acc);
2068     priv->mark = m;
2069 }
2070 
2071 void
xaccClearMark(Account * acc,short val)2072 xaccClearMark (Account *acc, short val)
2073 {
2074     Account *root;
2075 
2076     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2077 
2078     root = gnc_account_get_root(acc);
2079     xaccClearMarkDown(root ? root : acc, val);
2080 }
2081 
2082 void
xaccClearMarkDown(Account * acc,short val)2083 xaccClearMarkDown (Account *acc, short val)
2084 {
2085     AccountPrivate *priv;
2086     GList *node;
2087 
2088     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2089 
2090     priv = GET_PRIVATE(acc);
2091     priv->mark = val;
2092     for (node = priv->children; node; node = node->next)
2093     {
2094         xaccClearMarkDown(static_cast<Account*>(node->data), val);
2095     }
2096 }
2097 
2098 /********************************************************************\
2099 \********************************************************************/
2100 
2101 GNCPolicy *
gnc_account_get_policy(Account * acc)2102 gnc_account_get_policy (Account *acc)
2103 {
2104     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
2105 
2106     return GET_PRIVATE(acc)->policy;
2107 }
2108 
2109 void
gnc_account_set_policy(Account * acc,GNCPolicy * policy)2110 gnc_account_set_policy (Account *acc, GNCPolicy *policy)
2111 {
2112     AccountPrivate *priv;
2113 
2114     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2115 
2116     priv = GET_PRIVATE(acc);
2117     priv->policy = policy ? policy : xaccGetFIFOPolicy();
2118 }
2119 
2120 /********************************************************************\
2121 \********************************************************************/
2122 
2123 void
xaccAccountRemoveLot(Account * acc,GNCLot * lot)2124 xaccAccountRemoveLot (Account *acc, GNCLot *lot)
2125 {
2126     AccountPrivate *priv;
2127 
2128     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2129     g_return_if_fail(GNC_IS_LOT(lot));
2130 
2131     priv = GET_PRIVATE(acc);
2132     g_return_if_fail(priv->lots);
2133 
2134     ENTER ("(acc=%p, lot=%p)", acc, lot);
2135     priv->lots = g_list_remove(priv->lots, lot);
2136     qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_REMOVE, NULL);
2137     qof_event_gen (&acc->inst, QOF_EVENT_MODIFY, NULL);
2138     LEAVE ("(acc=%p, lot=%p)", acc, lot);
2139 }
2140 
2141 void
xaccAccountInsertLot(Account * acc,GNCLot * lot)2142 xaccAccountInsertLot (Account *acc, GNCLot *lot)
2143 {
2144     AccountPrivate *priv, *opriv;
2145     Account * old_acc = NULL;
2146     Account* lot_account;
2147 
2148     /* errors */
2149     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2150     g_return_if_fail(GNC_IS_LOT(lot));
2151 
2152     /* optimizations */
2153     lot_account = gnc_lot_get_account(lot);
2154     if (lot_account == acc)
2155         return;
2156 
2157     ENTER ("(acc=%p, lot=%p)", acc, lot);
2158 
2159     /* pull it out of the old account */
2160     if (lot_account)
2161     {
2162         old_acc = lot_account;
2163         opriv = GET_PRIVATE(old_acc);
2164         opriv->lots = g_list_remove(opriv->lots, lot);
2165     }
2166 
2167     priv = GET_PRIVATE(acc);
2168     priv->lots = g_list_prepend(priv->lots, lot);
2169     gnc_lot_set_account(lot, acc);
2170 
2171     /* Don't move the splits to the new account.  The caller will do this
2172      * if appropriate, and doing it here will not work if we are being
2173      * called from gnc_book_close_period since xaccAccountInsertSplit
2174      * will try to balance capital gains and things aren't ready for that. */
2175 
2176     qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_ADD, NULL);
2177     qof_event_gen (&acc->inst, QOF_EVENT_MODIFY, NULL);
2178 
2179     LEAVE ("(acc=%p, lot=%p)", acc, lot);
2180 }
2181 
2182 /********************************************************************\
2183 \********************************************************************/
2184 static void
xaccPreSplitMove(Split * split,gpointer dummy)2185 xaccPreSplitMove (Split *split, gpointer dummy)
2186 {
2187     xaccTransBeginEdit (xaccSplitGetParent (split));
2188 }
2189 
2190 static void
xaccPostSplitMove(Split * split,Account * accto)2191 xaccPostSplitMove (Split *split, Account *accto)
2192 {
2193     Transaction *trans;
2194 
2195     xaccSplitSetAccount(split, accto);
2196     xaccSplitSetAmount(split, split->amount);
2197     trans = xaccSplitGetParent (split);
2198     xaccTransCommitEdit (trans);
2199 }
2200 
2201 void
xaccAccountMoveAllSplits(Account * accfrom,Account * accto)2202 xaccAccountMoveAllSplits (Account *accfrom, Account *accto)
2203 {
2204     AccountPrivate *from_priv;
2205 
2206     /* errors */
2207     g_return_if_fail(GNC_IS_ACCOUNT(accfrom));
2208     g_return_if_fail(GNC_IS_ACCOUNT(accto));
2209 
2210     /* optimizations */
2211     from_priv = GET_PRIVATE(accfrom);
2212     if (!from_priv->splits || accfrom == accto)
2213         return;
2214 
2215     /* check for book mix-up */
2216     g_return_if_fail (qof_instance_books_equal(accfrom, accto));
2217     ENTER ("(accfrom=%p, accto=%p)", accfrom, accto);
2218 
2219     xaccAccountBeginEdit(accfrom);
2220     xaccAccountBeginEdit(accto);
2221     /* Begin editing both accounts and all transactions in accfrom. */
2222     g_list_foreach(from_priv->splits, (GFunc)xaccPreSplitMove, NULL);
2223 
2224     /* Concatenate accfrom's lists of splits and lots to accto's lists. */
2225     //to_priv->splits = g_list_concat(to_priv->splits, from_priv->splits);
2226     //to_priv->lots = g_list_concat(to_priv->lots, from_priv->lots);
2227 
2228     /* Set appropriate flags. */
2229     //from_priv->balance_dirty = TRUE;
2230     //from_priv->sort_dirty = FALSE;
2231     //to_priv->balance_dirty = TRUE;
2232     //to_priv->sort_dirty = TRUE;
2233 
2234     /*
2235      * Change each split's account back pointer to accto.
2236      * Convert each split's amount to accto's commodity.
2237      * Commit to editing each transaction.
2238      */
2239     g_list_foreach(from_priv->splits, (GFunc)xaccPostSplitMove, (gpointer)accto);
2240 
2241     /* Finally empty accfrom. */
2242     g_assert(from_priv->splits == NULL);
2243     g_assert(from_priv->lots == NULL);
2244     xaccAccountCommitEdit(accfrom);
2245     xaccAccountCommitEdit(accto);
2246 
2247     LEAVE ("(accfrom=%p, accto=%p)", accfrom, accto);
2248 }
2249 
2250 
2251 /********************************************************************\
2252  * xaccAccountRecomputeBalance                                      *
2253  *   recomputes the partial balances and the current balance for    *
2254  *   this account.                                                  *
2255  *                                                                  *
2256  * The way the computation is done depends on whether the partial   *
2257  * balances are for a monetary account (bank, cash, etc.) or a      *
2258  * certificate account (stock portfolio, mutual fund).  For bank    *
2259  * accounts, the invariant amount is the dollar amount. For share   *
2260  * accounts, the invariant amount is the number of shares. For      *
2261  * share accounts, the share price fluctuates, and the current      *
2262  * value of such an account is the number of shares times the       *
2263  * current share price.                                             *
2264  *                                                                  *
2265  * Part of the complexity of this computation stems from the fact   *
2266  * xacc uses a double-entry system, meaning that one transaction    *
2267  * appears in two accounts: one account is debited, and the other   *
2268  * is credited.  When the transaction represents a sale of shares,  *
2269  * or a purchase of shares, some care must be taken to compute      *
2270  * balances correctly.  For a sale of shares, the stock account must*
2271  * be debited in shares, but the bank account must be credited      *
2272  * in dollars.  Thus, two different mechanisms must be used to      *
2273  * compute balances, depending on account type.                     *
2274  *                                                                  *
2275  * Args:   account -- the account for which to recompute balances   *
2276  * Return: void                                                     *
2277 \********************************************************************/
2278 
2279 void
xaccAccountRecomputeBalance(Account * acc)2280 xaccAccountRecomputeBalance (Account * acc)
2281 {
2282     AccountPrivate *priv;
2283     gnc_numeric  balance;
2284     gnc_numeric  noclosing_balance;
2285     gnc_numeric  cleared_balance;
2286     gnc_numeric  reconciled_balance;
2287     GList *lp;
2288 
2289     if (NULL == acc) return;
2290 
2291     priv = GET_PRIVATE(acc);
2292     if (qof_instance_get_editlevel(acc) > 0) return;
2293     if (!priv->balance_dirty || priv->defer_bal_computation) return;
2294     if (qof_instance_get_destroying(acc)) return;
2295     if (qof_book_shutting_down(qof_instance_get_book(acc))) return;
2296 
2297     balance            = priv->starting_balance;
2298     noclosing_balance  = priv->starting_noclosing_balance;
2299     cleared_balance    = priv->starting_cleared_balance;
2300     reconciled_balance = priv->starting_reconciled_balance;
2301 
2302     PINFO ("acct=%s starting baln=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
2303            priv->accountName, balance.num, balance.denom);
2304     for (lp = priv->splits; lp; lp = lp->next)
2305     {
2306         Split *split = (Split *) lp->data;
2307         gnc_numeric amt = xaccSplitGetAmount (split);
2308 
2309         balance = gnc_numeric_add_fixed(balance, amt);
2310 
2311         if (NREC != split->reconciled)
2312         {
2313             cleared_balance = gnc_numeric_add_fixed(cleared_balance, amt);
2314         }
2315 
2316         if (YREC == split->reconciled ||
2317                 FREC == split->reconciled)
2318         {
2319             reconciled_balance =
2320                 gnc_numeric_add_fixed(reconciled_balance, amt);
2321         }
2322 
2323         if (!(xaccTransGetIsClosingTxn (split->parent)))
2324             noclosing_balance = gnc_numeric_add_fixed(noclosing_balance, amt);
2325 
2326         split->balance = balance;
2327         split->noclosing_balance = noclosing_balance;
2328         split->cleared_balance = cleared_balance;
2329         split->reconciled_balance = reconciled_balance;
2330 
2331     }
2332 
2333     priv->balance = balance;
2334     priv->noclosing_balance = noclosing_balance;
2335     priv->cleared_balance = cleared_balance;
2336     priv->reconciled_balance = reconciled_balance;
2337     priv->balance_dirty = FALSE;
2338 }
2339 
2340 /********************************************************************\
2341 \********************************************************************/
2342 
2343 /* The sort order is used to implicitly define an
2344  * order for report generation */
2345 
2346 static int typeorder[NUM_ACCOUNT_TYPES] =
2347 {
2348     ACCT_TYPE_BANK, ACCT_TYPE_STOCK, ACCT_TYPE_MUTUAL, ACCT_TYPE_CURRENCY,
2349     ACCT_TYPE_CASH, ACCT_TYPE_ASSET, ACCT_TYPE_RECEIVABLE,
2350     ACCT_TYPE_CREDIT, ACCT_TYPE_LIABILITY, ACCT_TYPE_PAYABLE,
2351     ACCT_TYPE_INCOME, ACCT_TYPE_EXPENSE, ACCT_TYPE_EQUITY, ACCT_TYPE_TRADING
2352 };
2353 
2354 static int revorder[NUM_ACCOUNT_TYPES] =
2355 {
2356     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
2357 };
2358 
2359 
2360 int
xaccAccountOrder(const Account * aa,const Account * ab)2361 xaccAccountOrder (const Account *aa, const Account *ab)
2362 {
2363     AccountPrivate *priv_aa, *priv_ab;
2364     const char *da, *db;
2365     char *endptr = NULL;
2366     int ta, tb, result;
2367     long la, lb;
2368 
2369     if ( aa && !ab ) return -1;
2370     if ( !aa && ab ) return +1;
2371     if ( !aa && !ab ) return 0;
2372 
2373     priv_aa = GET_PRIVATE(aa);
2374     priv_ab = GET_PRIVATE(ab);
2375 
2376     /* sort on accountCode strings */
2377     da = priv_aa->accountCode;
2378     db = priv_ab->accountCode;
2379 
2380     /* Otherwise do a string sort */
2381     result = g_strcmp0 (da, db);
2382     if (result)
2383         return result;
2384 
2385     /* if account-type-order array not initialized, initialize it */
2386     /* this will happen at most once during program invocation */
2387     if (-1 == revorder[0])
2388     {
2389         int i;
2390         for (i = 0; i < NUM_ACCOUNT_TYPES; i++)
2391         {
2392             revorder [typeorder[i]] = i;
2393         }
2394     }
2395 
2396     /* otherwise, sort on account type */
2397     ta = priv_aa->type;
2398     tb = priv_ab->type;
2399     ta = revorder[ta];
2400     tb = revorder[tb];
2401     if (ta < tb) return -1;
2402     if (ta > tb) return +1;
2403 
2404     /* otherwise, sort on accountName strings */
2405     da = priv_aa->accountName;
2406     db = priv_ab->accountName;
2407     result = safe_utf8_collate(da, db);
2408     if (result)
2409         return result;
2410 
2411     /* guarantee a stable sort */
2412     return qof_instance_guid_compare(aa, ab);
2413 }
2414 
2415 static int
qof_xaccAccountOrder(const Account ** aa,const Account ** ab)2416 qof_xaccAccountOrder (const Account **aa, const Account **ab)
2417 {
2418     return xaccAccountOrder(*aa, *ab);
2419 }
2420 
2421 /********************************************************************\
2422 \********************************************************************/
2423 
2424 void
xaccAccountSetType(Account * acc,GNCAccountType tip)2425 xaccAccountSetType (Account *acc, GNCAccountType tip)
2426 {
2427     AccountPrivate *priv;
2428 
2429     /* errors */
2430     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2431     g_return_if_fail(tip < NUM_ACCOUNT_TYPES);
2432 
2433     /* optimizations */
2434     priv = GET_PRIVATE(acc);
2435     if (priv->type == tip)
2436         return;
2437 
2438     xaccAccountBeginEdit(acc);
2439     priv->type = tip;
2440     priv->balance_dirty = TRUE; /* new type may affect balance computation */
2441     mark_account(acc);
2442     xaccAccountCommitEdit(acc);
2443 }
2444 
2445 void
xaccAccountSetName(Account * acc,const char * str)2446 xaccAccountSetName (Account *acc, const char *str)
2447 {
2448     AccountPrivate *priv;
2449 
2450     /* errors */
2451     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2452     g_return_if_fail(str);
2453 
2454     /* optimizations */
2455     priv = GET_PRIVATE(acc);
2456     if (g_strcmp0(str, priv->accountName) == 0)
2457         return;
2458 
2459     xaccAccountBeginEdit(acc);
2460     priv->accountName = qof_string_cache_replace(priv->accountName, str);
2461     mark_account (acc);
2462     xaccAccountCommitEdit(acc);
2463 }
2464 
2465 void
xaccAccountSetCode(Account * acc,const char * str)2466 xaccAccountSetCode (Account *acc, const char *str)
2467 {
2468     AccountPrivate *priv;
2469 
2470     /* errors */
2471     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2472 
2473     /* optimizations */
2474     priv = GET_PRIVATE(acc);
2475     if (g_strcmp0(str, priv->accountCode) == 0)
2476         return;
2477 
2478     xaccAccountBeginEdit(acc);
2479     priv->accountCode = qof_string_cache_replace(priv->accountCode, str ? str : "");
2480     mark_account (acc);
2481     xaccAccountCommitEdit(acc);
2482 }
2483 
2484 void
xaccAccountSetDescription(Account * acc,const char * str)2485 xaccAccountSetDescription (Account *acc, const char *str)
2486 {
2487     AccountPrivate *priv;
2488 
2489     /* errors */
2490     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2491 
2492     /* optimizations */
2493     priv = GET_PRIVATE(acc);
2494     if (g_strcmp0(str, priv->description) == 0)
2495         return;
2496 
2497     xaccAccountBeginEdit(acc);
2498     priv->description = qof_string_cache_replace(priv->description, str ? str : "");
2499     mark_account (acc);
2500     xaccAccountCommitEdit(acc);
2501 }
2502 
2503 static char*
stripdup_or_null(const char * value)2504 stripdup_or_null (const char *value)
2505 {
2506     if (value)
2507     {
2508         auto temp = g_strstrip (g_strdup (value));
2509         if (*temp)
2510             return temp;
2511         g_free (temp);
2512     }
2513     return nullptr;
2514 }
2515 
2516 // note the *value argument is expected to be either a strstripped
2517 // char* or nullptr, as returned by stripdup_or_null above.
2518 static void
set_kvp_string_path(Account * acc,std::vector<std::string> const & path,const char * value)2519 set_kvp_string_path (Account *acc, std::vector<std::string> const & path,
2520                      const char *value)
2521 {
2522     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2523 
2524     xaccAccountBeginEdit(acc);
2525     if (value)
2526     {
2527         GValue v = G_VALUE_INIT;
2528         g_value_init (&v, G_TYPE_STRING);
2529         g_value_set_string (&v, value);
2530         qof_instance_set_path_kvp (QOF_INSTANCE (acc), &v, path);
2531         g_value_unset (&v);
2532     }
2533     else
2534     {
2535          qof_instance_set_path_kvp (QOF_INSTANCE (acc), NULL, path);
2536     }
2537     mark_account (acc);
2538     xaccAccountCommitEdit(acc);
2539 }
2540 
2541 static void
set_kvp_string_tag(Account * acc,const char * tag,const char * value)2542 set_kvp_string_tag (Account *acc, const char *tag, const char *value)
2543 {
2544     set_kvp_string_path (acc, {tag}, value);
2545 }
2546 
2547 static char*
get_kvp_string_path(const Account * acc,std::vector<std::string> const & path)2548 get_kvp_string_path (const Account *acc, std::vector<std::string> const & path)
2549 {
2550     GValue v = G_VALUE_INIT;
2551     if (acc == NULL) return NULL; // how to check path is valid??
2552     qof_instance_get_path_kvp (QOF_INSTANCE (acc), &v, path);
2553     auto retval = G_VALUE_HOLDS_STRING (&v) ? g_value_dup_string (&v) : NULL;
2554     g_value_unset (&v);
2555     return retval;
2556 }
2557 
2558 static char*
get_kvp_string_tag(const Account * acc,const char * tag)2559 get_kvp_string_tag (const Account *acc, const char *tag)
2560 {
2561     return get_kvp_string_path (acc, {tag});
2562 }
2563 
2564 void
xaccAccountSetColor(Account * acc,const char * str)2565 xaccAccountSetColor (Account *acc, const char *str)
2566 {
2567     auto priv = GET_PRIVATE (acc);
2568     if (priv->color != is_unset)
2569         g_free (priv->color);
2570     priv->color = stripdup_or_null (str);
2571     set_kvp_string_tag (acc, "color", priv->color);
2572 }
2573 
2574 void
xaccAccountSetFilter(Account * acc,const char * str)2575 xaccAccountSetFilter (Account *acc, const char *str)
2576 {
2577     auto priv = GET_PRIVATE (acc);
2578     if (priv->filter != is_unset)
2579         g_free (priv->filter);
2580     priv->filter = stripdup_or_null (str);
2581     set_kvp_string_tag (acc, "filter", priv->filter);
2582 }
2583 
2584 void
xaccAccountSetSortOrder(Account * acc,const char * str)2585 xaccAccountSetSortOrder (Account *acc, const char *str)
2586 {
2587     auto priv = GET_PRIVATE (acc);
2588     if (priv->sort_order != is_unset)
2589         g_free (priv->sort_order);
2590     priv->sort_order = stripdup_or_null (str);
2591     set_kvp_string_tag (acc, "sort-order", priv->sort_order);
2592 }
2593 
2594 void
xaccAccountSetSortReversed(Account * acc,gboolean sortreversed)2595 xaccAccountSetSortReversed (Account *acc, gboolean sortreversed)
2596 {
2597     auto priv = GET_PRIVATE (acc);
2598     priv->sort_reversed = sortreversed ? TriState::True : TriState::False;
2599     set_kvp_string_tag (acc, "sort-reversed", sortreversed ? "true" : NULL);
2600 }
2601 
2602 static void
qofAccountSetParent(Account * acc,QofInstance * parent)2603 qofAccountSetParent (Account *acc, QofInstance *parent)
2604 {
2605     Account *parent_acc;
2606 
2607     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2608     g_return_if_fail(GNC_IS_ACCOUNT(parent));
2609 
2610     parent_acc = GNC_ACCOUNT(parent);
2611     xaccAccountBeginEdit(acc);
2612     xaccAccountBeginEdit(parent_acc);
2613     gnc_account_append_child(parent_acc, acc);
2614     mark_account (parent_acc);
2615     mark_account (acc);
2616     xaccAccountCommitEdit(acc);
2617     xaccAccountCommitEdit(parent_acc);
2618 }
2619 
2620 void
xaccAccountSetNotes(Account * acc,const char * str)2621 xaccAccountSetNotes (Account *acc, const char *str)
2622 {
2623     auto priv = GET_PRIVATE (acc);
2624     if (priv->notes != is_unset)
2625         g_free (priv->notes);
2626     priv->notes = stripdup_or_null (str);
2627     set_kvp_string_tag (acc, "notes", priv->notes);
2628 }
2629 
2630 void
xaccAccountSetCommodity(Account * acc,gnc_commodity * com)2631 xaccAccountSetCommodity (Account * acc, gnc_commodity * com)
2632 {
2633     AccountPrivate *priv;
2634     GList *lp;
2635 
2636     /* errors */
2637     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2638     g_return_if_fail(GNC_IS_COMMODITY(com));
2639 
2640     /* optimizations */
2641     priv = GET_PRIVATE(acc);
2642     if (com == priv->commodity)
2643         return;
2644 
2645     xaccAccountBeginEdit(acc);
2646     gnc_commodity_decrement_usage_count(priv->commodity);
2647     priv->commodity = com;
2648     gnc_commodity_increment_usage_count(com);
2649     priv->commodity_scu = gnc_commodity_get_fraction(com);
2650     priv->non_standard_scu = FALSE;
2651 
2652     /* iterate over splits */
2653     for (lp = priv->splits; lp; lp = lp->next)
2654     {
2655         Split *s = (Split *) lp->data;
2656         Transaction *trans = xaccSplitGetParent (s);
2657 
2658         xaccTransBeginEdit (trans);
2659         xaccSplitSetAmount (s, xaccSplitGetAmount(s));
2660         xaccTransCommitEdit (trans);
2661     }
2662 
2663     priv->sort_dirty = TRUE;  /* Not needed. */
2664     priv->balance_dirty = TRUE;
2665     mark_account (acc);
2666 
2667     xaccAccountCommitEdit(acc);
2668 }
2669 
2670 /*
2671  * Set the account scu and then check to see if it is the same as the
2672  * commodity scu.  This function is called when parsing the data file
2673  * and is designed to catch cases where the two were accidentally set
2674  * to mismatched values in the past.
2675  */
2676 void
xaccAccountSetCommoditySCU(Account * acc,int scu)2677 xaccAccountSetCommoditySCU (Account *acc, int scu)
2678 {
2679     AccountPrivate *priv;
2680 
2681     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2682 
2683     priv = GET_PRIVATE(acc);
2684     xaccAccountBeginEdit(acc);
2685     priv->commodity_scu = scu;
2686     if (scu != gnc_commodity_get_fraction(priv->commodity))
2687         priv->non_standard_scu = TRUE;
2688     mark_account(acc);
2689     xaccAccountCommitEdit(acc);
2690 }
2691 
2692 int
xaccAccountGetCommoditySCUi(const Account * acc)2693 xaccAccountGetCommoditySCUi (const Account * acc)
2694 {
2695     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
2696     return GET_PRIVATE(acc)->commodity_scu;
2697 }
2698 
2699 int
xaccAccountGetCommoditySCU(const Account * acc)2700 xaccAccountGetCommoditySCU (const Account * acc)
2701 {
2702     AccountPrivate *priv;
2703 
2704     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
2705 
2706     priv = GET_PRIVATE(acc);
2707     if (priv->non_standard_scu || !priv->commodity)
2708         return priv->commodity_scu;
2709     return gnc_commodity_get_fraction(priv->commodity);
2710 }
2711 
2712 void
xaccAccountSetNonStdSCU(Account * acc,gboolean flag)2713 xaccAccountSetNonStdSCU (Account *acc, gboolean flag)
2714 {
2715     AccountPrivate *priv;
2716 
2717     g_return_if_fail(GNC_IS_ACCOUNT(acc));
2718 
2719     priv = GET_PRIVATE(acc);
2720     if (priv->non_standard_scu == flag)
2721         return;
2722     xaccAccountBeginEdit(acc);
2723     priv->non_standard_scu = flag;
2724     mark_account (acc);
2725     xaccAccountCommitEdit(acc);
2726 }
2727 
2728 gboolean
xaccAccountGetNonStdSCU(const Account * acc)2729 xaccAccountGetNonStdSCU (const Account * acc)
2730 {
2731     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
2732     return GET_PRIVATE(acc)->non_standard_scu;
2733 }
2734 
2735 /********************************************************************\
2736 \********************************************************************/
2737 /* below follow the old, deprecated currency/security routines. */
2738 
2739 void
DxaccAccountSetCurrency(Account * acc,gnc_commodity * currency)2740 DxaccAccountSetCurrency (Account * acc, gnc_commodity * currency)
2741 {
2742     QofBook *book;
2743     GValue v = G_VALUE_INIT;
2744     const char *s = gnc_commodity_get_unique_name (currency);
2745     gnc_commodity *commodity;
2746     gnc_commodity_table *table;
2747 
2748     if ((!acc) || (!currency)) return;
2749     g_value_init (&v, G_TYPE_STRING);
2750     g_value_set_string (&v, s);
2751     qof_instance_set_path_kvp (QOF_INSTANCE (acc), &v, {"old-currency"});
2752     mark_account (acc);
2753     xaccAccountCommitEdit(acc);
2754     g_value_unset (&v);
2755 
2756     table = gnc_commodity_table_get_table (qof_instance_get_book(acc));
2757     commodity = gnc_commodity_table_lookup_unique (table, s);
2758 
2759     if (!commodity)
2760     {
2761         book = qof_instance_get_book(acc);
2762         gnc_commodity_table_insert (gnc_commodity_table_get_table (book),
2763 				    currency);
2764     }
2765 }
2766 
2767 /********************************************************************\
2768 \********************************************************************/
2769 
2770 static void
account_foreach_descendant(const Account * acc,AccountCb thunk,void * user_data,bool sort)2771 account_foreach_descendant (const Account *acc, AccountCb thunk,
2772                             void* user_data, bool sort)
2773 {
2774     GList *children;
2775 
2776     g_return_if_fail (GNC_IS_ACCOUNT(acc));
2777     g_return_if_fail (thunk);
2778 
2779     auto priv{GET_PRIVATE(acc)};
2780     if (sort)
2781     {
2782         children = g_list_copy (priv->children);
2783         children = g_list_sort (children, (GCompareFunc)xaccAccountOrder);
2784     }
2785     else
2786         children = priv->children;
2787 
2788     for (auto node = children; node; node = node->next)
2789     {
2790         auto child = static_cast<Account*>(node->data);
2791         thunk (child, user_data);
2792         account_foreach_descendant (child, thunk, user_data, sort);
2793     }
2794 
2795     if (sort)
2796         g_list_free (children);
2797 }
2798 
2799 void
gnc_account_append_child(Account * new_parent,Account * child)2800 gnc_account_append_child (Account *new_parent, Account *child)
2801 {
2802     AccountPrivate *ppriv, *cpriv;
2803     Account *old_parent;
2804     QofCollection *col;
2805 
2806     /* errors */
2807     g_assert(GNC_IS_ACCOUNT(new_parent));
2808     g_assert(GNC_IS_ACCOUNT(child));
2809 
2810     /* optimizations */
2811     ppriv = GET_PRIVATE(new_parent);
2812     cpriv = GET_PRIVATE(child);
2813     old_parent = cpriv->parent;
2814     if (old_parent == new_parent)
2815         return;
2816 
2817     //  xaccAccountBeginEdit(new_parent);
2818     xaccAccountBeginEdit(child);
2819     if (old_parent)
2820     {
2821         gnc_account_remove_child(old_parent, child);
2822 
2823         if (!qof_instance_books_equal(old_parent, new_parent))
2824         {
2825             /* hack alert -- this implementation is not exactly correct.
2826              * If the entity tables are not identical, then the 'from' book
2827              * may have a different backend than the 'to' book.  This means
2828              * that we should get the 'from' backend to destroy this account,
2829              * and the 'to' backend to save it.  Right now, this is broken.
2830              *
2831              * A 'correct' implementation similar to this is in Period.c
2832              * except its for transactions ...
2833              *
2834              * Note also, we need to reparent the children to the new book as well.
2835              */
2836             PWARN ("reparenting accounts across books is not correctly supported\n");
2837 
2838             qof_event_gen (&child->inst, QOF_EVENT_DESTROY, NULL);
2839             col = qof_book_get_collection (qof_instance_get_book(new_parent),
2840                                            GNC_ID_ACCOUNT);
2841             qof_collection_insert_entity (col, &child->inst);
2842             qof_event_gen (&child->inst, QOF_EVENT_CREATE, NULL);
2843         }
2844     }
2845     cpriv->parent = new_parent;
2846     ppriv->children = g_list_append(ppriv->children, child);
2847     qof_instance_set_dirty(&new_parent->inst);
2848     qof_instance_set_dirty(&child->inst);
2849 
2850     /* Send events data. Warning: The call to commit_edit is also going
2851      * to send a MODIFY event. If the gtktreemodelfilter code gets the
2852      * MODIFY before it gets the ADD, it gets very confused and thinks
2853      * that two nodes have been added. */
2854     qof_event_gen (&child->inst, QOF_EVENT_ADD, NULL);
2855     // qof_event_gen (&new_parent->inst, QOF_EVENT_MODIFY, NULL);
2856 
2857     xaccAccountCommitEdit (child);
2858     //  xaccAccountCommitEdit(new_parent);
2859 }
2860 
2861 void
gnc_account_remove_child(Account * parent,Account * child)2862 gnc_account_remove_child (Account *parent, Account *child)
2863 {
2864     AccountPrivate *ppriv, *cpriv;
2865     GncEventData ed;
2866 
2867     if (!child) return;
2868 
2869     /* Note this routine might be called on accounts which
2870      * are not yet parented. */
2871     if (!parent) return;
2872 
2873     ppriv = GET_PRIVATE(parent);
2874     cpriv = GET_PRIVATE(child);
2875 
2876     if (cpriv->parent != parent)
2877     {
2878         PERR ("account not a child of parent");
2879         return;
2880     }
2881 
2882     /* Gather event data */
2883     ed.node = parent;
2884     ed.idx = g_list_index(ppriv->children, child);
2885 
2886     ppriv->children = g_list_remove(ppriv->children, child);
2887 
2888     /* Now send the event. */
2889     qof_event_gen(&child->inst, QOF_EVENT_REMOVE, &ed);
2890 
2891     /* clear the account's parent pointer after REMOVE event generation. */
2892     cpriv->parent = NULL;
2893 
2894     qof_event_gen (&parent->inst, QOF_EVENT_MODIFY, NULL);
2895 }
2896 
2897 Account *
gnc_account_get_parent(const Account * acc)2898 gnc_account_get_parent (const Account *acc)
2899 {
2900     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
2901     return GET_PRIVATE(acc)->parent;
2902 }
2903 
2904 Account *
gnc_account_get_root(Account * acc)2905 gnc_account_get_root (Account *acc)
2906 {
2907     AccountPrivate *priv;
2908 
2909     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
2910 
2911     priv = GET_PRIVATE(acc);
2912     while (priv->parent)
2913     {
2914         acc = priv->parent;
2915         priv = GET_PRIVATE(acc);
2916     }
2917 
2918     return acc;
2919 }
2920 
2921 gboolean
gnc_account_is_root(const Account * account)2922 gnc_account_is_root (const Account *account)
2923 {
2924     g_return_val_if_fail(GNC_IS_ACCOUNT(account), FALSE);
2925     return (GET_PRIVATE(account)->parent == NULL);
2926 }
2927 
2928 GList *
gnc_account_get_children(const Account * account)2929 gnc_account_get_children (const Account *account)
2930 {
2931     g_return_val_if_fail(GNC_IS_ACCOUNT(account), NULL);
2932     return g_list_copy(GET_PRIVATE(account)->children);
2933 }
2934 
2935 GList *
gnc_account_get_children_sorted(const Account * account)2936 gnc_account_get_children_sorted (const Account *account)
2937 {
2938     AccountPrivate *priv;
2939 
2940     /* errors */
2941     g_return_val_if_fail(GNC_IS_ACCOUNT(account), NULL);
2942 
2943     /* optimizations */
2944     priv = GET_PRIVATE(account);
2945     if (!priv->children)
2946         return NULL;
2947     return g_list_sort(g_list_copy(priv->children), (GCompareFunc)xaccAccountOrder);
2948 }
2949 
2950 gint
gnc_account_n_children(const Account * account)2951 gnc_account_n_children (const Account *account)
2952 {
2953     g_return_val_if_fail(GNC_IS_ACCOUNT(account), 0);
2954     return g_list_length(GET_PRIVATE(account)->children);
2955 }
2956 
2957 gint
gnc_account_child_index(const Account * parent,const Account * child)2958 gnc_account_child_index (const Account *parent, const Account *child)
2959 {
2960     g_return_val_if_fail(GNC_IS_ACCOUNT(parent), -1);
2961     g_return_val_if_fail(GNC_IS_ACCOUNT(child), -1);
2962     return g_list_index(GET_PRIVATE(parent)->children, child);
2963 }
2964 
2965 Account *
gnc_account_nth_child(const Account * parent,gint num)2966 gnc_account_nth_child (const Account *parent, gint num)
2967 {
2968     g_return_val_if_fail(GNC_IS_ACCOUNT(parent), NULL);
2969     return static_cast<Account*>(g_list_nth_data(GET_PRIVATE(parent)->children, num));
2970 }
2971 
2972 static void
count_acct(Account * account,gpointer user_data)2973 count_acct (Account *account, gpointer user_data)
2974 {
2975     auto count {static_cast<int*>(user_data)};
2976     ++*count;
2977 }
2978 
2979 gint
gnc_account_n_descendants(const Account * account)2980 gnc_account_n_descendants (const Account *account)
2981 {
2982     int count {0};
2983     account_foreach_descendant (account, count_acct, &count, FALSE);
2984     return count;
2985 }
2986 
2987 gint
gnc_account_get_current_depth(const Account * account)2988 gnc_account_get_current_depth (const Account *account)
2989 {
2990     AccountPrivate *priv;
2991     int depth = 0;
2992 
2993     g_return_val_if_fail(GNC_IS_ACCOUNT(account), 0);
2994 
2995     priv = GET_PRIVATE(account);
2996     while (priv->parent && (priv->type != ACCT_TYPE_ROOT))
2997     {
2998         account = priv->parent;
2999         priv = GET_PRIVATE(account);
3000         depth++;
3001     }
3002 
3003     return depth;
3004 }
3005 
3006 gint
gnc_account_get_tree_depth(const Account * account)3007 gnc_account_get_tree_depth (const Account *account)
3008 {
3009     AccountPrivate *priv;
3010     GList *node;
3011     gint depth = 0, child_depth;
3012 
3013     g_return_val_if_fail(GNC_IS_ACCOUNT(account), 0);
3014 
3015     priv = GET_PRIVATE(account);
3016     if (!priv->children)
3017         return 1;
3018 
3019     for (node = priv->children; node; node = g_list_next(node))
3020     {
3021         child_depth = gnc_account_get_tree_depth(static_cast<Account const *>(node->data));
3022         depth = MAX(depth, child_depth);
3023     }
3024     return depth + 1;
3025 }
3026 
3027 static void
collect_acct(Account * account,gpointer user_data)3028 collect_acct (Account *account, gpointer user_data)
3029 {
3030     auto listptr{static_cast<GList**>(user_data)};
3031     *listptr = g_list_prepend (*listptr, account);
3032 }
3033 
3034 GList *
gnc_account_get_descendants(const Account * account)3035 gnc_account_get_descendants (const Account *account)
3036 {
3037     GList* list = nullptr;
3038     account_foreach_descendant (account, collect_acct, &list, FALSE);
3039     return g_list_reverse (list);
3040 }
3041 
3042 GList *
gnc_account_get_descendants_sorted(const Account * account)3043 gnc_account_get_descendants_sorted (const Account *account)
3044 {
3045     GList* list = nullptr;
3046     account_foreach_descendant (account, collect_acct, &list, TRUE);
3047     return g_list_reverse (list);
3048 }
3049 
3050 static gpointer
is_acct_name(Account * account,gpointer user_data)3051 is_acct_name (Account *account, gpointer user_data)
3052 {
3053     auto name {static_cast<gchar*>(user_data)};
3054     return (g_strcmp0 (name, xaccAccountGetName (account)) ? nullptr : account);
3055 }
3056 
3057 Account *
gnc_account_lookup_by_name(const Account * parent,const char * name)3058 gnc_account_lookup_by_name (const Account *parent, const char * name)
3059 {
3060     return (Account*)gnc_account_foreach_descendant_until (parent, is_acct_name, (char*)name);
3061 }
3062 
3063 static gpointer
is_acct_code(Account * account,gpointer user_data)3064 is_acct_code (Account *account, gpointer user_data)
3065 {
3066     auto name {static_cast<gchar*>(user_data)};
3067     return (g_strcmp0 (name, xaccAccountGetCode (account)) ? nullptr : account);
3068 }
3069 
3070 Account *
gnc_account_lookup_by_code(const Account * parent,const char * code)3071 gnc_account_lookup_by_code (const Account *parent, const char * code)
3072 {
3073     return (Account*)gnc_account_foreach_descendant_until (parent, is_acct_code, (char*)code);
3074 }
3075 
3076 static gpointer
is_opening_balance_account(Account * account,gpointer data)3077 is_opening_balance_account (Account* account, gpointer data)
3078 {
3079     gnc_commodity* commodity = GNC_COMMODITY(data);
3080     if (xaccAccountGetIsOpeningBalance(account) && gnc_commodity_equiv(commodity, xaccAccountGetCommodity(account)))
3081         return account;
3082     return nullptr;
3083 }
3084 
3085 Account*
gnc_account_lookup_by_opening_balance(Account * account,gnc_commodity * commodity)3086 gnc_account_lookup_by_opening_balance (Account* account, gnc_commodity* commodity)
3087 {
3088     return (Account *)gnc_account_foreach_descendant_until (account, is_opening_balance_account, commodity);
3089 }
3090 
3091 /********************************************************************\
3092  * Fetch an account, given its full name                            *
3093 \********************************************************************/
3094 
3095 static Account *
gnc_account_lookup_by_full_name_helper(const Account * parent,gchar ** names)3096 gnc_account_lookup_by_full_name_helper (const Account *parent,
3097                                         gchar **names)
3098 {
3099     const AccountPrivate *priv, *ppriv;
3100     Account *found;
3101     GList *node;
3102 
3103     g_return_val_if_fail(GNC_IS_ACCOUNT(parent), NULL);
3104     g_return_val_if_fail(names, NULL);
3105 
3106     /* Look for the first name in the children. */
3107     ppriv = GET_PRIVATE(parent);
3108     for (node = ppriv->children; node; node = node->next)
3109     {
3110         Account *account = static_cast<Account*>(node->data);
3111 
3112         priv = GET_PRIVATE(account);
3113         if (g_strcmp0(priv->accountName, names[0]) == 0)
3114         {
3115             /* We found an account.  If the next entry is NULL, there is
3116              * nothing left in the name, so just return the account. */
3117             if (names[1] == NULL)
3118                 return account;
3119 
3120             /* No children?  We're done. */
3121             if (!priv->children)
3122                 return NULL;
3123 
3124             /* There's stuff left to search for.  Search recursively. */
3125             found = gnc_account_lookup_by_full_name_helper(account, &names[1]);
3126             if (found != NULL)
3127             {
3128                 return found;
3129             }
3130         }
3131     }
3132 
3133     return NULL;
3134 }
3135 
3136 
3137 Account *
gnc_account_lookup_by_full_name(const Account * any_acc,const gchar * name)3138 gnc_account_lookup_by_full_name (const Account *any_acc,
3139                                  const gchar *name)
3140 {
3141     const AccountPrivate *rpriv;
3142     const Account *root;
3143     Account *found;
3144     gchar **names;
3145 
3146     g_return_val_if_fail(GNC_IS_ACCOUNT(any_acc), NULL);
3147     g_return_val_if_fail(name, NULL);
3148 
3149     root = any_acc;
3150     rpriv = GET_PRIVATE(root);
3151     while (rpriv->parent)
3152     {
3153         root = rpriv->parent;
3154         rpriv = GET_PRIVATE(root);
3155     }
3156     names = g_strsplit(name, gnc_get_account_separator_string(), -1);
3157     found = gnc_account_lookup_by_full_name_helper(root, names);
3158     g_strfreev(names);
3159     return found;
3160 }
3161 
3162 GList*
gnc_account_lookup_by_type_and_commodity(Account * root,const char * name,GNCAccountType acctype,gnc_commodity * commodity)3163 gnc_account_lookup_by_type_and_commodity (Account* root,
3164                                           const char* name,
3165                                           GNCAccountType acctype,
3166                                           gnc_commodity* commodity)
3167 {
3168     GList *retval{};
3169     auto rpriv{GET_PRIVATE(root)};
3170     for (auto node = rpriv->children; node; node = node->next)
3171     {
3172         auto account{static_cast<Account*>(node->data)};
3173         if (xaccAccountGetType (account) == acctype)
3174         {
3175             if (commodity &&
3176                 !gnc_commodity_equiv(xaccAccountGetCommodity (account),
3177                                      commodity))
3178                 continue;
3179 
3180             if (name && strcmp(name, xaccAccountGetName(account)))
3181                 continue;
3182 
3183             retval = g_list_prepend(retval, account);
3184         }
3185     }
3186 
3187     if (!retval) // Recurse through the children
3188         for (auto node = rpriv->children; node; node = node->next)
3189         {
3190             auto account{static_cast<Account*>(node->data)};
3191             auto result = gnc_account_lookup_by_type_and_commodity(account,
3192                                                                    name,
3193                                                                    acctype,
3194                                                                    commodity);
3195             if (result)
3196                 retval = g_list_concat(result, retval);
3197         }
3198     return retval;
3199 }
3200 
3201 void
gnc_account_foreach_child(const Account * acc,AccountCb thunk,gpointer user_data)3202 gnc_account_foreach_child (const Account *acc,
3203                            AccountCb thunk,
3204                            gpointer user_data)
3205 {
3206     const AccountPrivate *priv;
3207     GList *node;
3208 
3209     g_return_if_fail(GNC_IS_ACCOUNT(acc));
3210     g_return_if_fail(thunk);
3211 
3212     priv = GET_PRIVATE(acc);
3213     for (node = priv->children; node; node = node->next)
3214     {
3215         thunk (static_cast<Account*>(node->data), user_data);
3216     }
3217 }
3218 
3219 void
gnc_account_foreach_descendant(const Account * acc,AccountCb thunk,gpointer user_data)3220 gnc_account_foreach_descendant (const Account *acc,
3221                                 AccountCb thunk,
3222                                 gpointer user_data)
3223 {
3224     account_foreach_descendant (acc, thunk, user_data, FALSE);
3225 }
3226 
3227 gpointer
gnc_account_foreach_descendant_until(const Account * acc,AccountCb2 thunk,gpointer user_data)3228 gnc_account_foreach_descendant_until (const Account *acc,
3229                                       AccountCb2 thunk,
3230                                       gpointer user_data)
3231 {
3232     gpointer result {nullptr};
3233 
3234     g_return_val_if_fail (GNC_IS_ACCOUNT(acc), nullptr);
3235     g_return_val_if_fail (thunk, nullptr);
3236 
3237     auto priv{GET_PRIVATE(acc)};
3238 
3239     for (auto node = priv->children; node; node = node->next)
3240     {
3241         auto child = static_cast<Account*>(node->data);
3242         result = thunk (child, user_data);
3243         if (result) break;
3244 
3245         result = gnc_account_foreach_descendant_until (child, thunk, user_data);
3246         if (result) break;
3247     }
3248 
3249     return result;
3250 }
3251 
3252 
3253 GNCAccountType
xaccAccountGetType(const Account * acc)3254 xaccAccountGetType (const Account *acc)
3255 {
3256     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), ACCT_TYPE_NONE);
3257     return GET_PRIVATE(acc)->type;
3258 }
3259 
3260 static const char*
qofAccountGetTypeString(const Account * acc)3261 qofAccountGetTypeString (const Account *acc)
3262 {
3263     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
3264     return xaccAccountTypeEnumAsString(GET_PRIVATE(acc)->type);
3265 }
3266 
3267 static void
qofAccountSetType(Account * acc,const char * type_string)3268 qofAccountSetType (Account *acc, const char *type_string)
3269 {
3270     g_return_if_fail(GNC_IS_ACCOUNT(acc));
3271     g_return_if_fail(type_string);
3272     xaccAccountSetType(acc, xaccAccountStringToEnum(type_string));
3273 }
3274 
3275 const char *
xaccAccountGetName(const Account * acc)3276 xaccAccountGetName (const Account *acc)
3277 {
3278     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
3279     return GET_PRIVATE(acc)->accountName;
3280 }
3281 
3282 gchar *
gnc_account_get_full_name(const Account * account)3283 gnc_account_get_full_name(const Account *account)
3284 {
3285     AccountPrivate *priv;
3286     const Account *a;
3287     char *fullname;
3288     const gchar **names;
3289     int level;
3290 
3291     /* So much for hardening the API. Too many callers to this function don't
3292      * bother to check if they have a non-NULL pointer before calling. */
3293     if (NULL == account)
3294         return g_strdup("");
3295 
3296     /* errors */
3297     g_return_val_if_fail(GNC_IS_ACCOUNT(account), g_strdup(""));
3298 
3299     /* optimizations */
3300     priv = GET_PRIVATE(account);
3301     if (!priv->parent)
3302         return g_strdup("");
3303 
3304     /* Figure out how much space is needed by counting the nodes up to
3305      * the root. */
3306     level = 0;
3307     for (a = account; a; a = priv->parent)
3308     {
3309         priv = GET_PRIVATE(a);
3310         level++;
3311     }
3312 
3313     /* Get all the pointers in the right order. The root node "entry"
3314      * becomes the terminating NULL pointer for the array of strings. */
3315     names = (const gchar **)g_malloc(level * sizeof(gchar *));
3316     names[--level] = NULL;
3317     for (a = account; level > 0; a = priv->parent)
3318     {
3319         priv = GET_PRIVATE(a);
3320         names[--level] = priv->accountName;
3321     }
3322 
3323     /* Build the full name */
3324     fullname = g_strjoinv(account_separator, (gchar **)names);
3325     g_free(names);
3326 
3327     return fullname;
3328 }
3329 
3330 const char *
xaccAccountGetCode(const Account * acc)3331 xaccAccountGetCode (const Account *acc)
3332 {
3333     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
3334     return GET_PRIVATE(acc)->accountCode;
3335 }
3336 
3337 const char *
xaccAccountGetDescription(const Account * acc)3338 xaccAccountGetDescription (const Account *acc)
3339 {
3340     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
3341     return GET_PRIVATE(acc)->description;
3342 }
3343 
3344 const char *
xaccAccountGetColor(const Account * acc)3345 xaccAccountGetColor (const Account *acc)
3346 {
3347     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
3348     auto priv = GET_PRIVATE (acc);
3349     if (priv->color == is_unset)
3350         priv->color = get_kvp_string_tag (acc, "color");
3351     return priv->color;
3352 }
3353 
3354 const char *
xaccAccountGetFilter(const Account * acc)3355 xaccAccountGetFilter (const Account *acc)
3356 {
3357     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
3358     auto priv = GET_PRIVATE (acc);
3359     if (priv->filter == is_unset)
3360         priv->filter = get_kvp_string_tag (acc, "filter");
3361     return priv->filter;
3362 }
3363 
3364 const char *
xaccAccountGetSortOrder(const Account * acc)3365 xaccAccountGetSortOrder (const Account *acc)
3366 {
3367     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
3368     auto priv = GET_PRIVATE (acc);
3369     if (priv->sort_order == is_unset)
3370         priv->sort_order = get_kvp_string_tag (acc, "sort-order");
3371     return priv->sort_order;
3372 }
3373 
3374 gboolean
xaccAccountGetSortReversed(const Account * acc)3375 xaccAccountGetSortReversed (const Account *acc)
3376 {
3377 
3378     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
3379     auto priv = GET_PRIVATE (acc);
3380     if (priv->sort_reversed == TriState::Unset)
3381     {
3382         auto sort_reversed = get_kvp_string_tag (acc, "sort-reversed");
3383         priv->sort_reversed = g_strcmp0 (sort_reversed, "true") ?
3384             TriState::False : TriState::True;
3385         g_free (sort_reversed);
3386     }
3387     return (priv->sort_reversed == TriState::True);
3388 }
3389 
3390 const char *
xaccAccountGetNotes(const Account * acc)3391 xaccAccountGetNotes (const Account *acc)
3392 {
3393     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
3394     auto priv = GET_PRIVATE (acc);
3395     if (priv->notes == is_unset)
3396         priv->notes = get_kvp_string_tag (acc, "notes");
3397     return priv->notes;
3398 }
3399 
3400 gnc_commodity *
DxaccAccountGetCurrency(const Account * acc)3401 DxaccAccountGetCurrency (const Account *acc)
3402 {
3403     GValue v = G_VALUE_INIT;
3404     const char *s = NULL;
3405     gnc_commodity_table *table;
3406     gnc_commodity *retval = NULL;
3407 
3408     if (!acc) return NULL;
3409     qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v, {"old-currency"});
3410     if (G_VALUE_HOLDS_STRING (&v))
3411         s = g_value_get_string (&v);
3412     if (s)
3413     {
3414         table = gnc_commodity_table_get_table (qof_instance_get_book(acc));
3415         retval = gnc_commodity_table_lookup_unique (table, s);
3416     }
3417     g_value_unset (&v);
3418 
3419     return retval;
3420 }
3421 
3422 gnc_commodity *
xaccAccountGetCommodity(const Account * acc)3423 xaccAccountGetCommodity (const Account *acc)
3424 {
3425     if (!GNC_IS_ACCOUNT(acc))
3426         return NULL;
3427     return GET_PRIVATE(acc)->commodity;
3428 }
3429 
gnc_account_get_currency_or_parent(const Account * account)3430 gnc_commodity * gnc_account_get_currency_or_parent(const Account* account)
3431 {
3432     gnc_commodity * commodity;
3433     g_return_val_if_fail (account, NULL);
3434 
3435     commodity = xaccAccountGetCommodity (account);
3436     if (gnc_commodity_is_currency(commodity))
3437         return commodity;
3438     else
3439     {
3440         const Account *parent_account = account;
3441         /* Account commodity is not a currency, walk up the tree until
3442          * we find a parent account that is a currency account and use
3443          * it's currency.
3444          */
3445         do
3446         {
3447             parent_account = gnc_account_get_parent (parent_account);
3448             if (parent_account)
3449             {
3450                 commodity = xaccAccountGetCommodity (parent_account);
3451                 if (gnc_commodity_is_currency(commodity))
3452                 {
3453                     return commodity;
3454                     //break;
3455                 }
3456             }
3457         }
3458         while (parent_account);
3459     }
3460     return NULL; // no suitable commodity found.
3461 }
3462 
3463 /********************************************************************\
3464 \********************************************************************/
3465 void
gnc_account_set_start_balance(Account * acc,const gnc_numeric start_baln)3466 gnc_account_set_start_balance (Account *acc, const gnc_numeric start_baln)
3467 {
3468     AccountPrivate *priv;
3469 
3470     g_return_if_fail(GNC_IS_ACCOUNT(acc));
3471 
3472     priv = GET_PRIVATE(acc);
3473     priv->starting_balance = start_baln;
3474     priv->balance_dirty = TRUE;
3475 }
3476 
3477 void
gnc_account_set_start_cleared_balance(Account * acc,const gnc_numeric start_baln)3478 gnc_account_set_start_cleared_balance (Account *acc,
3479                                        const gnc_numeric start_baln)
3480 {
3481     AccountPrivate *priv;
3482 
3483     g_return_if_fail(GNC_IS_ACCOUNT(acc));
3484 
3485     priv = GET_PRIVATE(acc);
3486     priv->starting_cleared_balance = start_baln;
3487     priv->balance_dirty = TRUE;
3488 }
3489 
3490 void
gnc_account_set_start_reconciled_balance(Account * acc,const gnc_numeric start_baln)3491 gnc_account_set_start_reconciled_balance (Account *acc,
3492         const gnc_numeric start_baln)
3493 {
3494     AccountPrivate *priv;
3495 
3496     g_return_if_fail(GNC_IS_ACCOUNT(acc));
3497 
3498     priv = GET_PRIVATE(acc);
3499     priv->starting_reconciled_balance = start_baln;
3500     priv->balance_dirty = TRUE;
3501 }
3502 
3503 gnc_numeric
xaccAccountGetBalance(const Account * acc)3504 xaccAccountGetBalance (const Account *acc)
3505 {
3506     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
3507     return GET_PRIVATE(acc)->balance;
3508 }
3509 
3510 gnc_numeric
xaccAccountGetClearedBalance(const Account * acc)3511 xaccAccountGetClearedBalance (const Account *acc)
3512 {
3513     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
3514     return GET_PRIVATE(acc)->cleared_balance;
3515 }
3516 
3517 gnc_numeric
xaccAccountGetReconciledBalance(const Account * acc)3518 xaccAccountGetReconciledBalance (const Account *acc)
3519 {
3520     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
3521     return GET_PRIVATE(acc)->reconciled_balance;
3522 }
3523 
3524 gnc_numeric
xaccAccountGetProjectedMinimumBalance(const Account * acc)3525 xaccAccountGetProjectedMinimumBalance (const Account *acc)
3526 {
3527     AccountPrivate *priv;
3528     GList *node;
3529     time64 today;
3530     gnc_numeric lowest = gnc_numeric_zero ();
3531     int seen_a_transaction = 0;
3532 
3533     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
3534 
3535     priv = GET_PRIVATE(acc);
3536     today = gnc_time64_get_today_end();
3537     for (node = g_list_last(priv->splits); node; node = node->prev)
3538     {
3539         Split *split = static_cast<Split*>(node->data);
3540 
3541         if (!seen_a_transaction)
3542         {
3543             lowest = xaccSplitGetBalance (split);
3544             seen_a_transaction = 1;
3545         }
3546         else if (gnc_numeric_compare(xaccSplitGetBalance (split), lowest) < 0)
3547         {
3548             lowest = xaccSplitGetBalance (split);
3549         }
3550 
3551         if (xaccTransGetDate (xaccSplitGetParent (split)) <= today)
3552             return lowest;
3553     }
3554 
3555     return lowest;
3556 }
3557 
3558 
3559 /********************************************************************\
3560 \********************************************************************/
3561 
3562 static gnc_numeric
GetBalanceAsOfDate(Account * acc,time64 date,gboolean ignclosing)3563 GetBalanceAsOfDate (Account *acc, time64 date, gboolean ignclosing)
3564 {
3565     /* Ideally this could use xaccAccountForEachSplit, but
3566      * it doesn't exist yet and I'm uncertain of exactly how
3567      * it would work at this time, since it differs from
3568      * xaccAccountForEachTransaction by using gpointer return
3569      * values rather than gints.
3570      */
3571     Split *latest = nullptr;
3572 
3573     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
3574 
3575     xaccAccountSortSplits (acc, TRUE); /* just in case, normally a noop */
3576     xaccAccountRecomputeBalance (acc); /* just in case, normally a noop */
3577 
3578     for (GList *lp = GET_PRIVATE(acc)->splits; lp; lp = lp->next)
3579     {
3580         if (xaccTransGetDate (xaccSplitGetParent ((Split *)lp->data)) >= date)
3581             break;
3582         latest = (Split *)lp->data;
3583     }
3584 
3585     if (!latest)
3586         return gnc_numeric_zero();
3587 
3588     if (ignclosing)
3589         return xaccSplitGetNoclosingBalance (latest);
3590     else
3591         return xaccSplitGetBalance (latest);
3592 }
3593 
3594 gnc_numeric
xaccAccountGetBalanceAsOfDate(Account * acc,time64 date)3595 xaccAccountGetBalanceAsOfDate (Account *acc, time64 date)
3596 {
3597     return GetBalanceAsOfDate (acc, date, FALSE);
3598 }
3599 
3600 static gnc_numeric
xaccAccountGetNoclosingBalanceAsOfDate(Account * acc,time64 date)3601 xaccAccountGetNoclosingBalanceAsOfDate (Account *acc, time64 date)
3602 {
3603     return GetBalanceAsOfDate (acc, date, TRUE);
3604 }
3605 
3606 gnc_numeric
xaccAccountGetReconciledBalanceAsOfDate(Account * acc,time64 date)3607 xaccAccountGetReconciledBalanceAsOfDate (Account *acc, time64 date)
3608 {
3609     gnc_numeric balance = gnc_numeric_zero();
3610 
3611     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
3612 
3613     for (GList *node = GET_PRIVATE(acc)->splits; node; node = node->next)
3614     {
3615         Split *split = (Split*) node->data;
3616         if ((xaccSplitGetReconcile (split) == YREC) &&
3617             (xaccSplitGetDateReconciled (split) <= date))
3618             balance = gnc_numeric_add_fixed (balance, xaccSplitGetAmount (split));
3619     };
3620 
3621     return balance;
3622 }
3623 
3624 /*
3625  * Originally gsr_account_present_balance in gnc-split-reg.c
3626  */
3627 gnc_numeric
xaccAccountGetPresentBalance(const Account * acc)3628 xaccAccountGetPresentBalance (const Account *acc)
3629 {
3630     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
3631 
3632     return xaccAccountGetBalanceAsOfDate (GNC_ACCOUNT (acc),
3633                                           gnc_time64_get_today_end ());
3634 }
3635 
3636 
3637 /********************************************************************\
3638 \********************************************************************/
3639 /* XXX TODO: These 'GetBal' routines should be moved to some
3640  * utility area outside of the core account engine area.
3641  */
3642 
3643 /*
3644  * Convert a balance from one currency to another.
3645  */
3646 gnc_numeric
xaccAccountConvertBalanceToCurrency(const Account * acc,gnc_numeric balance,const gnc_commodity * balance_currency,const gnc_commodity * new_currency)3647 xaccAccountConvertBalanceToCurrency(const Account *acc, /* for book */
3648                                     gnc_numeric balance,
3649                                     const gnc_commodity *balance_currency,
3650                                     const gnc_commodity *new_currency)
3651 {
3652     QofBook *book;
3653     GNCPriceDB *pdb;
3654 
3655     if (gnc_numeric_zero_p (balance) ||
3656             gnc_commodity_equiv (balance_currency, new_currency))
3657         return balance;
3658 
3659     book = gnc_account_get_book (acc);
3660     pdb = gnc_pricedb_get_db (book);
3661 
3662     balance = gnc_pricedb_convert_balance_latest_price(
3663                   pdb, balance, balance_currency, new_currency);
3664 
3665     return balance;
3666 }
3667 
3668 /*
3669  * Convert a balance from one currency to another with price of
3670  * a given date.
3671  */
3672 gnc_numeric
xaccAccountConvertBalanceToCurrencyAsOfDate(const Account * acc,gnc_numeric balance,const gnc_commodity * balance_currency,const gnc_commodity * new_currency,time64 date)3673 xaccAccountConvertBalanceToCurrencyAsOfDate(const Account *acc, /* for book */
3674         gnc_numeric balance,
3675         const gnc_commodity *balance_currency,
3676         const gnc_commodity *new_currency,
3677         time64 date)
3678 {
3679     QofBook *book;
3680     GNCPriceDB *pdb;
3681 
3682     if (gnc_numeric_zero_p (balance) ||
3683             gnc_commodity_equiv (balance_currency, new_currency))
3684         return balance;
3685 
3686     book = gnc_account_get_book (acc);
3687     pdb = gnc_pricedb_get_db (book);
3688 
3689     balance = gnc_pricedb_convert_balance_nearest_before_price_t64 (
3690                   pdb, balance, balance_currency, new_currency, date);
3691 
3692     return balance;
3693 }
3694 
3695 /*
3696  * Given an account and a GetBalanceFn pointer, extract the requested
3697  * balance from the account and then convert it to the desired
3698  * currency.
3699  */
3700 static gnc_numeric
xaccAccountGetXxxBalanceInCurrency(const Account * acc,xaccGetBalanceFn fn,const gnc_commodity * report_currency)3701 xaccAccountGetXxxBalanceInCurrency (const Account *acc,
3702                                     xaccGetBalanceFn fn,
3703                                     const gnc_commodity *report_currency)
3704 {
3705     AccountPrivate *priv;
3706     gnc_numeric balance;
3707 
3708     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
3709     g_return_val_if_fail(fn, gnc_numeric_zero());
3710     g_return_val_if_fail(GNC_IS_COMMODITY(report_currency), gnc_numeric_zero());
3711 
3712     priv = GET_PRIVATE(acc);
3713     balance = fn(acc);
3714     balance = xaccAccountConvertBalanceToCurrency(acc, balance,
3715               priv->commodity,
3716               report_currency);
3717     return balance;
3718 }
3719 
3720 static gnc_numeric
xaccAccountGetXxxBalanceAsOfDateInCurrency(Account * acc,time64 date,xaccGetBalanceAsOfDateFn fn,const gnc_commodity * report_commodity)3721 xaccAccountGetXxxBalanceAsOfDateInCurrency(Account *acc, time64 date,
3722         xaccGetBalanceAsOfDateFn fn,
3723         const gnc_commodity *report_commodity)
3724 {
3725     AccountPrivate *priv;
3726 
3727     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), gnc_numeric_zero());
3728     g_return_val_if_fail(fn, gnc_numeric_zero());
3729     g_return_val_if_fail(GNC_IS_COMMODITY(report_commodity), gnc_numeric_zero());
3730 
3731     priv = GET_PRIVATE(acc);
3732     return xaccAccountConvertBalanceToCurrencyAsOfDate(
3733                acc, fn(acc, date), priv->commodity, report_commodity, date);
3734 }
3735 
3736 /*
3737  * Data structure used to pass various arguments into the following fn.
3738  */
3739 typedef struct
3740 {
3741     const gnc_commodity *currency;
3742     gnc_numeric balance;
3743     xaccGetBalanceFn fn;
3744     xaccGetBalanceAsOfDateFn asOfDateFn;
3745     time64 date;
3746 } CurrencyBalance;
3747 
3748 
3749 /*
3750  * A helper function for iterating over all the accounts in a list or
3751  * tree.  This function is called once per account, and sums up the
3752  * values of all these accounts.
3753  */
3754 static void
xaccAccountBalanceHelper(Account * acc,gpointer data)3755 xaccAccountBalanceHelper (Account *acc, gpointer data)
3756 {
3757     CurrencyBalance *cb = static_cast<CurrencyBalance*>(data);
3758     gnc_numeric balance;
3759 
3760     if (!cb->fn || !cb->currency)
3761         return;
3762     balance = xaccAccountGetXxxBalanceInCurrency (acc, cb->fn, cb->currency);
3763     cb->balance = gnc_numeric_add (cb->balance, balance,
3764                                    gnc_commodity_get_fraction (cb->currency),
3765                                    GNC_HOW_RND_ROUND_HALF_UP);
3766 }
3767 
3768 static void
xaccAccountBalanceAsOfDateHelper(Account * acc,gpointer data)3769 xaccAccountBalanceAsOfDateHelper (Account *acc, gpointer data)
3770 {
3771     CurrencyBalance *cb = static_cast<CurrencyBalance*>(data);
3772     gnc_numeric balance;
3773 
3774     g_return_if_fail (cb->asOfDateFn && cb->currency);
3775 
3776     balance = xaccAccountGetXxxBalanceAsOfDateInCurrency (
3777                   acc, cb->date, cb->asOfDateFn, cb->currency);
3778     cb->balance = gnc_numeric_add (cb->balance, balance,
3779                                    gnc_commodity_get_fraction (cb->currency),
3780                                    GNC_HOW_RND_ROUND_HALF_UP);
3781 }
3782 
3783 
3784 
3785 /*
3786  * Common function that iterates recursively over all accounts below
3787  * the specified account.  It uses xaccAccountBalanceHelper to sum up
3788  * the balances of all its children, and uses the specified function
3789  * 'fn' for extracting the balance.  This function may extract the
3790  * current value, the reconciled value, etc.
3791  *
3792  * If 'report_commodity' is NULL, just use the account's commodity.
3793  * If 'include_children' is FALSE, this function doesn't recurse at all.
3794  */
3795 static gnc_numeric
xaccAccountGetXxxBalanceInCurrencyRecursive(const Account * acc,xaccGetBalanceFn fn,const gnc_commodity * report_commodity,gboolean include_children)3796 xaccAccountGetXxxBalanceInCurrencyRecursive (const Account *acc,
3797         xaccGetBalanceFn fn,
3798         const gnc_commodity *report_commodity,
3799         gboolean include_children)
3800 {
3801     gnc_numeric balance;
3802 
3803     if (!acc) return gnc_numeric_zero ();
3804     if (!report_commodity)
3805         report_commodity = xaccAccountGetCommodity (acc);
3806     if (!report_commodity)
3807         return gnc_numeric_zero();
3808 
3809     balance = xaccAccountGetXxxBalanceInCurrency (acc, fn, report_commodity);
3810 
3811     /* If needed, sum up the children converting to the *requested*
3812        commodity. */
3813     if (include_children)
3814     {
3815 #ifdef _MSC_VER
3816         /* MSVC compiler: Somehow, the struct initialization containing a
3817            gnc_numeric doesn't work. As an exception, we hand-initialize
3818            that member afterwards. */
3819         CurrencyBalance cb = { report_commodity, { 0 }, fn, NULL, 0 };
3820         cb.balance = balance;
3821 #else
3822         CurrencyBalance cb = { report_commodity, balance, fn, NULL, 0 };
3823 #endif
3824 
3825         gnc_account_foreach_descendant (acc, xaccAccountBalanceHelper, &cb);
3826         balance = cb.balance;
3827     }
3828 
3829     return balance;
3830 }
3831 
3832 static gnc_numeric
xaccAccountGetXxxBalanceAsOfDateInCurrencyRecursive(Account * acc,time64 date,xaccGetBalanceAsOfDateFn fn,const gnc_commodity * report_commodity,gboolean include_children)3833 xaccAccountGetXxxBalanceAsOfDateInCurrencyRecursive (
3834     Account *acc, time64 date, xaccGetBalanceAsOfDateFn fn,
3835     const gnc_commodity *report_commodity, gboolean include_children)
3836 {
3837     gnc_numeric balance;
3838 
3839     g_return_val_if_fail(acc, gnc_numeric_zero());
3840     if (!report_commodity)
3841         report_commodity = xaccAccountGetCommodity (acc);
3842     if (!report_commodity)
3843         return gnc_numeric_zero();
3844 
3845     balance = xaccAccountGetXxxBalanceAsOfDateInCurrency(
3846                   acc, date, fn, report_commodity);
3847 
3848     /* If needed, sum up the children converting to the *requested*
3849        commodity. */
3850     if (include_children)
3851     {
3852 #ifdef _MSC_VER
3853         /* MSVC compiler: Somehow, the struct initialization containing a
3854            gnc_numeric doesn't work. As an exception, we hand-initialize
3855            that member afterwards. */
3856         CurrencyBalance cb = { report_commodity, 0, NULL, fn, date };
3857         cb.balance = balance;
3858 #else
3859         CurrencyBalance cb = { report_commodity, balance, NULL, fn, date };
3860 #endif
3861 
3862         gnc_account_foreach_descendant (acc, xaccAccountBalanceAsOfDateHelper, &cb);
3863         balance = cb.balance;
3864     }
3865 
3866     return balance;
3867 }
3868 
3869 gnc_numeric
xaccAccountGetBalanceInCurrency(const Account * acc,const gnc_commodity * report_commodity,gboolean include_children)3870 xaccAccountGetBalanceInCurrency (const Account *acc,
3871                                  const gnc_commodity *report_commodity,
3872                                  gboolean include_children)
3873 {
3874     gnc_numeric rc;
3875     rc = xaccAccountGetXxxBalanceInCurrencyRecursive (
3876              acc, xaccAccountGetBalance, report_commodity, include_children);
3877     PINFO(" baln=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, rc.num, rc.denom);
3878     return rc;
3879 }
3880 
3881 
3882 gnc_numeric
xaccAccountGetClearedBalanceInCurrency(const Account * acc,const gnc_commodity * report_commodity,gboolean include_children)3883 xaccAccountGetClearedBalanceInCurrency (const Account *acc,
3884                                         const gnc_commodity *report_commodity,
3885                                         gboolean include_children)
3886 {
3887     return xaccAccountGetXxxBalanceInCurrencyRecursive (
3888                acc, xaccAccountGetClearedBalance, report_commodity,
3889                include_children);
3890 }
3891 
3892 gnc_numeric
xaccAccountGetReconciledBalanceInCurrency(const Account * acc,const gnc_commodity * report_commodity,gboolean include_children)3893 xaccAccountGetReconciledBalanceInCurrency (const Account *acc,
3894         const gnc_commodity *report_commodity,
3895         gboolean include_children)
3896 {
3897     return xaccAccountGetXxxBalanceInCurrencyRecursive (
3898                acc, xaccAccountGetReconciledBalance, report_commodity,
3899                include_children);
3900 }
3901 
3902 gnc_numeric
xaccAccountGetPresentBalanceInCurrency(const Account * acc,const gnc_commodity * report_commodity,gboolean include_children)3903 xaccAccountGetPresentBalanceInCurrency (const Account *acc,
3904                                         const gnc_commodity *report_commodity,
3905                                         gboolean include_children)
3906 {
3907     return xaccAccountGetXxxBalanceAsOfDateInCurrencyRecursive (
3908                (Account*)acc, gnc_time64_get_today_end (), xaccAccountGetBalanceAsOfDate,
3909                report_commodity,
3910                include_children);
3911 }
3912 
3913 gnc_numeric
xaccAccountGetProjectedMinimumBalanceInCurrency(const Account * acc,const gnc_commodity * report_commodity,gboolean include_children)3914 xaccAccountGetProjectedMinimumBalanceInCurrency (
3915     const Account *acc,
3916     const gnc_commodity *report_commodity,
3917     gboolean include_children)
3918 {
3919     return xaccAccountGetXxxBalanceInCurrencyRecursive (
3920                acc, xaccAccountGetProjectedMinimumBalance, report_commodity,
3921                include_children);
3922 }
3923 
3924 gnc_numeric
xaccAccountGetBalanceAsOfDateInCurrency(Account * acc,time64 date,gnc_commodity * report_commodity,gboolean include_children)3925 xaccAccountGetBalanceAsOfDateInCurrency(
3926     Account *acc, time64 date, gnc_commodity *report_commodity,
3927     gboolean include_children)
3928 {
3929     return xaccAccountGetXxxBalanceAsOfDateInCurrencyRecursive (
3930                acc, date, xaccAccountGetBalanceAsOfDate, report_commodity,
3931                include_children);
3932 }
3933 
3934 gnc_numeric
xaccAccountGetNoclosingBalanceAsOfDateInCurrency(Account * acc,time64 date,gnc_commodity * report_commodity,gboolean include_children)3935 xaccAccountGetNoclosingBalanceAsOfDateInCurrency(
3936     Account *acc, time64 date, gnc_commodity *report_commodity,
3937     gboolean include_children)
3938 {
3939     return xaccAccountGetXxxBalanceAsOfDateInCurrencyRecursive
3940       (acc, date, xaccAccountGetNoclosingBalanceAsOfDate,
3941        report_commodity, include_children);
3942 }
3943 
3944 gnc_numeric
xaccAccountGetBalanceChangeForPeriod(Account * acc,time64 t1,time64 t2,gboolean recurse)3945 xaccAccountGetBalanceChangeForPeriod (Account *acc, time64 t1, time64 t2,
3946                                       gboolean recurse)
3947 {
3948     gnc_numeric b1, b2;
3949 
3950     b1 = xaccAccountGetBalanceAsOfDateInCurrency(acc, t1, NULL, recurse);
3951     b2 = xaccAccountGetBalanceAsOfDateInCurrency(acc, t2, NULL, recurse);
3952     return gnc_numeric_sub(b2, b1, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
3953 }
3954 
3955 gnc_numeric
xaccAccountGetNoclosingBalanceChangeForPeriod(Account * acc,time64 t1,time64 t2,gboolean recurse)3956 xaccAccountGetNoclosingBalanceChangeForPeriod (Account *acc, time64 t1,
3957                                                time64 t2, gboolean recurse)
3958 {
3959     gnc_numeric b1, b2;
3960 
3961     b1 = xaccAccountGetNoclosingBalanceAsOfDateInCurrency(acc, t1, NULL, recurse);
3962     b2 = xaccAccountGetNoclosingBalanceAsOfDateInCurrency(acc, t2, NULL, recurse);
3963     return gnc_numeric_sub(b2, b1, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
3964 }
3965 
3966 
3967 /********************************************************************\
3968 \********************************************************************/
3969 
3970 /* THIS API NEEDS TO CHANGE.
3971  *
3972  * This code exposes the internal structure of the account object to
3973  * external callers by returning the actual list used by the object.
3974  * It should instead return a copy of the split list that the caller
3975  * is required to free.  That change would provide the freedom of
3976  * allowing the internal organization to change data structures if
3977  * necessary for whatever reason, while leaving the external API
3978  * unchanged. */
3979 /* XXX: violates the const'ness by forcing a sort before returning
3980  * the splitlist */
3981 SplitList *
xaccAccountGetSplitList(const Account * acc)3982 xaccAccountGetSplitList (const Account *acc)
3983 {
3984     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
3985     xaccAccountSortSplits((Account*)acc, FALSE);  // normally a noop
3986     return GET_PRIVATE(acc)->splits;
3987 }
3988 
3989 gint64
xaccAccountCountSplits(const Account * acc,gboolean include_children)3990 xaccAccountCountSplits (const Account *acc, gboolean include_children)
3991 {
3992     gint64 nr, i;
3993 
3994     PWARN ("xaccAccountCountSplits is deprecated and will be removed \
3995 in GnuCash 5.0. If testing for an empty account, use \
3996 xaccAccountGetSplitList(account) == NULL instead. To test descendants \
3997 as well, use gnc_account_and_descendants_empty.");
3998     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), 0);
3999 
4000     nr = g_list_length(xaccAccountGetSplitList(acc));
4001     if (include_children && (gnc_account_n_children(acc) != 0))
4002     {
4003         for (i=0; i < gnc_account_n_children(acc); i++)
4004         {
4005             nr += xaccAccountCountSplits(gnc_account_nth_child(acc, i), TRUE);
4006         }
4007     }
4008     return nr;
4009 }
4010 
gnc_account_and_descendants_empty(Account * acc)4011 gboolean gnc_account_and_descendants_empty (Account *acc)
4012 {
4013     g_return_val_if_fail (GNC_IS_ACCOUNT (acc), FALSE);
4014     auto priv = GET_PRIVATE (acc);
4015     if (priv->splits != nullptr) return FALSE;
4016     for (auto *n = priv->children; n; n = n->next)
4017     {
4018 	if (!gnc_account_and_descendants_empty (static_cast<Account*>(n->data)))
4019 	    return FALSE;
4020     }
4021     return TRUE;
4022 }
4023 
4024 LotList *
xaccAccountGetLotList(const Account * acc)4025 xaccAccountGetLotList (const Account *acc)
4026 {
4027     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
4028     return g_list_copy(GET_PRIVATE(acc)->lots);
4029 }
4030 
4031 LotList *
xaccAccountFindOpenLots(const Account * acc,gboolean (* match_func)(GNCLot * lot,gpointer user_data),gpointer user_data,GCompareFunc sort_func)4032 xaccAccountFindOpenLots (const Account *acc,
4033                          gboolean (*match_func)(GNCLot *lot,
4034                                  gpointer user_data),
4035                          gpointer user_data, GCompareFunc sort_func)
4036 {
4037     AccountPrivate *priv;
4038     GList *lot_list;
4039     GList *retval = NULL;
4040 
4041     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
4042 
4043     priv = GET_PRIVATE(acc);
4044     for (lot_list = priv->lots; lot_list; lot_list = lot_list->next)
4045     {
4046         GNCLot *lot = static_cast<GNCLot*>(lot_list->data);
4047 
4048         /* If this lot is closed, then ignore it */
4049         if (gnc_lot_is_closed (lot))
4050             continue;
4051 
4052         if (match_func && !(match_func)(lot, user_data))
4053             continue;
4054 
4055         /* Ok, this is a valid lot.  Add it to our list of lots */
4056         retval = g_list_prepend (retval, lot);
4057     }
4058 
4059     if (sort_func)
4060         retval = g_list_sort (retval, sort_func);
4061 
4062     return retval;
4063 }
4064 
4065 gpointer
xaccAccountForEachLot(const Account * acc,gpointer (* proc)(GNCLot * lot,void * data),void * data)4066 xaccAccountForEachLot(const Account *acc,
4067                       gpointer (*proc)(GNCLot *lot, void *data), void *data)
4068 {
4069     AccountPrivate *priv;
4070     LotList *node;
4071     gpointer result = NULL;
4072 
4073     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), NULL);
4074     g_return_val_if_fail(proc, NULL);
4075 
4076     priv = GET_PRIVATE(acc);
4077     for (node = priv->lots; node; node = node->next)
4078         if ((result = proc((GNCLot *)node->data, data)))
4079             break;
4080 
4081     return result;
4082 }
4083 
4084 static void
set_boolean_key(Account * acc,std::vector<std::string> const & path,gboolean option)4085 set_boolean_key (Account *acc, std::vector<std::string> const & path, gboolean option)
4086 {
4087     GValue v = G_VALUE_INIT;
4088     g_return_if_fail(GNC_IS_ACCOUNT(acc));
4089 
4090     g_value_init (&v, G_TYPE_BOOLEAN);
4091     g_value_set_boolean (&v, option);
4092     xaccAccountBeginEdit (acc);
4093     qof_instance_set_path_kvp (QOF_INSTANCE (acc), &v, path);
4094     mark_account (acc);
4095     xaccAccountCommitEdit (acc);
4096     g_value_unset (&v);
4097 }
4098 
4099 static gboolean
boolean_from_key(const Account * acc,std::vector<std::string> const & path)4100 boolean_from_key (const Account *acc, std::vector<std::string> const & path)
4101 {
4102     GValue v = G_VALUE_INIT;
4103     gboolean retval = FALSE;
4104     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
4105     qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v, path);
4106     if (G_VALUE_HOLDS_INT64 (&v))
4107         retval = (g_value_get_int64 (&v) != 0);
4108     if (G_VALUE_HOLDS_BOOLEAN (&v))
4109         retval = (g_value_get_boolean (&v));
4110     if (G_VALUE_HOLDS_STRING (&v))
4111         retval = !strcmp (g_value_get_string (&v), "true");
4112     g_value_unset (&v);
4113     return retval;
4114 }
4115 
4116 /********************************************************************\
4117 \********************************************************************/
4118 
4119 /* These functions use interchange gint64 and gboolean.  Is that right? */
4120 gboolean
xaccAccountGetTaxRelated(const Account * acc)4121 xaccAccountGetTaxRelated (const Account *acc)
4122 {
4123     return boolean_from_key(acc, {"tax-related"});
4124 }
4125 
4126 void
xaccAccountSetTaxRelated(Account * acc,gboolean tax_related)4127 xaccAccountSetTaxRelated (Account *acc, gboolean tax_related)
4128 {
4129     set_boolean_key(acc, {"tax-related"}, tax_related);
4130 }
4131 
4132 const char *
xaccAccountGetTaxUSCode(const Account * acc)4133 xaccAccountGetTaxUSCode (const Account *acc)
4134 {
4135     auto priv = GET_PRIVATE (acc);
4136     if (priv->tax_us_code == is_unset)
4137         priv->tax_us_code = get_kvp_string_path (acc, {"tax-US", "code"});
4138     return priv->tax_us_code;
4139 }
4140 
4141 void
xaccAccountSetTaxUSCode(Account * acc,const char * code)4142 xaccAccountSetTaxUSCode (Account *acc, const char *code)
4143 {
4144     auto priv = GET_PRIVATE (acc);
4145     if (priv->tax_us_code != is_unset)
4146         g_free (priv->tax_us_code);
4147     priv->tax_us_code = g_strdup (code);
4148     set_kvp_string_path (acc, {"tax-US", "code"}, priv->tax_us_code);
4149 }
4150 
4151 const char *
xaccAccountGetTaxUSPayerNameSource(const Account * acc)4152 xaccAccountGetTaxUSPayerNameSource (const Account *acc)
4153 {
4154     auto priv = GET_PRIVATE (acc);
4155     if (priv->tax_us_pns == is_unset)
4156         priv->tax_us_pns = get_kvp_string_path (acc, {"tax-US", "payer-name-source"});
4157     return priv->tax_us_pns;
4158  }
4159 
4160 void
xaccAccountSetTaxUSPayerNameSource(Account * acc,const char * source)4161 xaccAccountSetTaxUSPayerNameSource (Account *acc, const char *source)
4162 {
4163     auto priv = GET_PRIVATE (acc);
4164     if (priv->tax_us_pns != is_unset)
4165         g_free (priv->tax_us_pns);
4166     priv->tax_us_pns = g_strdup (source);
4167     set_kvp_string_path (acc, {"tax-US", "payer-name-source"}, priv->tax_us_pns);
4168 }
4169 
4170 gint64
xaccAccountGetTaxUSCopyNumber(const Account * acc)4171 xaccAccountGetTaxUSCopyNumber (const Account *acc)
4172 {
4173     gint64 copy_number = 0;
4174     GValue v = G_VALUE_INIT;
4175     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
4176     qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v, {"tax-US", "copy-number"});
4177     if (G_VALUE_HOLDS_INT64 (&v))
4178         copy_number = g_value_get_int64 (&v);
4179 
4180     g_value_unset (&v);
4181     return (copy_number == 0) ? 1 : copy_number;
4182 }
4183 
4184 void
xaccAccountSetTaxUSCopyNumber(Account * acc,gint64 copy_number)4185 xaccAccountSetTaxUSCopyNumber (Account *acc, gint64 copy_number)
4186 {
4187     g_return_if_fail(GNC_IS_ACCOUNT(acc));
4188     xaccAccountBeginEdit (acc);
4189     if (copy_number != 0)
4190     {
4191         GValue v = G_VALUE_INIT;
4192         g_value_init (&v, G_TYPE_INT64);
4193         g_value_set_int64 (&v, copy_number);
4194         qof_instance_set_path_kvp (QOF_INSTANCE (acc), &v, {"tax-US", "copy-number"});
4195         g_value_unset (&v);
4196     }
4197     else
4198     {
4199         qof_instance_set_path_kvp (QOF_INSTANCE (acc), nullptr, {"tax-US", "copy-number"});
4200     }
4201     mark_account (acc);
4202     xaccAccountCommitEdit (acc);
4203 }
4204 
4205 /*********************************************************************\
4206 \ ********************************************************************/
4207 
4208 
gnc_account_get_debit_string(GNCAccountType acct_type)4209 const char *gnc_account_get_debit_string (GNCAccountType acct_type)
4210 {
4211     if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_ACCOUNTING_LABELS))
4212         return _(dflt_acct_debit_str);
4213 
4214     auto result = gnc_acct_debit_strs.find(acct_type);
4215     if (result != gnc_acct_debit_strs.end())
4216         return _(result->second);
4217     else
4218         return _(dflt_acct_debit_str);
4219 }
4220 
gnc_account_get_credit_string(GNCAccountType acct_type)4221 const char *gnc_account_get_credit_string (GNCAccountType acct_type)
4222 {
4223     if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_ACCOUNTING_LABELS))
4224         return _(dflt_acct_credit_str);
4225 
4226     auto result = gnc_acct_credit_strs.find(acct_type);
4227     if (result != gnc_acct_credit_strs.end())
4228         return _(result->second);
4229     else
4230         return _(dflt_acct_credit_str);
4231 }
4232 
4233 /********************************************************************\
4234 \********************************************************************/
4235 
4236 gboolean
xaccAccountGetPlaceholder(const Account * acc)4237 xaccAccountGetPlaceholder (const Account *acc)
4238 {
4239     return boolean_from_key(acc, {"placeholder"});
4240 }
4241 
4242 void
xaccAccountSetPlaceholder(Account * acc,gboolean val)4243 xaccAccountSetPlaceholder (Account *acc, gboolean val)
4244 {
4245     set_boolean_key(acc, {"placeholder"}, val);
4246 }
4247 
4248 gboolean
xaccAccountGetAppendText(const Account * acc)4249 xaccAccountGetAppendText (const Account *acc)
4250 {
4251     return boolean_from_key(acc, {"import-append-text"});
4252 }
4253 
4254 void
xaccAccountSetAppendText(Account * acc,gboolean val)4255 xaccAccountSetAppendText (Account *acc, gboolean val)
4256 {
4257     set_boolean_key(acc, {"import-append-text"}, val);
4258 }
4259 
4260 gboolean
xaccAccountGetIsOpeningBalance(const Account * acc)4261 xaccAccountGetIsOpeningBalance (const Account *acc)
4262 {
4263     if (GET_PRIVATE(acc)->type != ACCT_TYPE_EQUITY)
4264         return false;
4265     auto priv = GET_PRIVATE(acc);
4266     if (priv->equity_type == TriState::Unset)
4267     {
4268         auto equity_type = get_kvp_string_tag (acc, "equity-type");
4269         priv->equity_type = g_strcmp0 (equity_type, "opening-balance") ?
4270             TriState::False : TriState::True;
4271         g_free (equity_type);
4272     }
4273     return (priv->equity_type == TriState::True);
4274 }
4275 
4276 void
xaccAccountSetIsOpeningBalance(Account * acc,gboolean val)4277 xaccAccountSetIsOpeningBalance (Account *acc, gboolean val)
4278 {
4279     if (GET_PRIVATE(acc)->type != ACCT_TYPE_EQUITY)
4280         return;
4281     auto priv = GET_PRIVATE (acc);
4282     priv->equity_type = val ? TriState::True : TriState::False;
4283     set_kvp_string_tag(acc, "equity-type", val ? "opening-balance" : nullptr);
4284 }
4285 
4286 GNCPlaceholderType
xaccAccountGetDescendantPlaceholder(const Account * acc)4287 xaccAccountGetDescendantPlaceholder (const Account *acc)
4288 {
4289     GList *descendants, *node;
4290     GNCPlaceholderType ret = PLACEHOLDER_NONE;
4291 
4292     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), PLACEHOLDER_NONE);
4293     if (xaccAccountGetPlaceholder(acc)) return PLACEHOLDER_THIS;
4294 
4295     descendants = gnc_account_get_descendants(acc);
4296     for (node = descendants; node; node = node->next)
4297         if (xaccAccountGetPlaceholder((Account *) node->data))
4298         {
4299             ret = PLACEHOLDER_CHILD;
4300             break;
4301         }
4302 
4303     g_list_free(descendants);
4304     return ret;
4305 }
4306 
4307 /********************************************************************\
4308  \********************************************************************/
4309 
4310 gboolean
xaccAccountGetAutoInterest(const Account * acc)4311 xaccAccountGetAutoInterest (const Account *acc)
4312 {
4313     return boolean_from_key (acc, {KEY_RECONCILE_INFO, "auto-interest-transfer"});
4314 }
4315 
4316 void
xaccAccountSetAutoInterest(Account * acc,gboolean val)4317 xaccAccountSetAutoInterest (Account *acc, gboolean val)
4318 {
4319     set_boolean_key (acc, {KEY_RECONCILE_INFO, "auto-interest-transfer"}, val);
4320 }
4321 
4322 /********************************************************************\
4323 \********************************************************************/
4324 
4325 gboolean
xaccAccountGetHidden(const Account * acc)4326 xaccAccountGetHidden (const Account *acc)
4327 {
4328     return boolean_from_key (acc, {"hidden"});
4329 }
4330 
4331 void
xaccAccountSetHidden(Account * acc,gboolean val)4332 xaccAccountSetHidden (Account *acc, gboolean val)
4333 {
4334     set_boolean_key (acc, {"hidden"}, val);
4335 }
4336 
4337 gboolean
xaccAccountIsHidden(const Account * acc)4338 xaccAccountIsHidden (const Account *acc)
4339 {
4340     AccountPrivate *priv;
4341 
4342     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
4343 
4344     if (xaccAccountGetHidden(acc))
4345         return TRUE;
4346     priv = GET_PRIVATE(acc);
4347     while ((acc = priv->parent) != NULL)
4348     {
4349         priv = GET_PRIVATE(acc);
4350         if (xaccAccountGetHidden(acc))
4351             return TRUE;
4352     }
4353     return FALSE;
4354 }
4355 
4356 /********************************************************************\
4357 \********************************************************************/
4358 
4359 gboolean
xaccAccountHasAncestor(const Account * acc,const Account * ancestor)4360 xaccAccountHasAncestor (const Account *acc, const Account * ancestor)
4361 {
4362     const Account *parent;
4363 
4364     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
4365     g_return_val_if_fail(GNC_IS_ACCOUNT(ancestor), FALSE);
4366 
4367     parent = acc;
4368     while (parent && parent != ancestor)
4369         parent = GET_PRIVATE(parent)->parent;
4370 
4371     return (parent == ancestor);
4372 }
4373 
4374 /********************************************************************\
4375 \********************************************************************/
4376 
4377 /* You must edit the functions in this block in tandem.  KEEP THEM IN
4378    SYNC! */
4379 
4380 #define GNC_RETURN_ENUM_AS_STRING(x) case (ACCT_TYPE_ ## x): return #x;
4381 
4382 const char *
xaccAccountTypeEnumAsString(GNCAccountType type)4383 xaccAccountTypeEnumAsString(GNCAccountType type)
4384 {
4385     switch (type)
4386     {
4387         GNC_RETURN_ENUM_AS_STRING(NONE);
4388         GNC_RETURN_ENUM_AS_STRING(BANK);
4389         GNC_RETURN_ENUM_AS_STRING(CASH);
4390         GNC_RETURN_ENUM_AS_STRING(CREDIT);
4391         GNC_RETURN_ENUM_AS_STRING(ASSET);
4392         GNC_RETURN_ENUM_AS_STRING(LIABILITY);
4393         GNC_RETURN_ENUM_AS_STRING(STOCK);
4394         GNC_RETURN_ENUM_AS_STRING(MUTUAL);
4395         GNC_RETURN_ENUM_AS_STRING(CURRENCY);
4396         GNC_RETURN_ENUM_AS_STRING(INCOME);
4397         GNC_RETURN_ENUM_AS_STRING(EXPENSE);
4398         GNC_RETURN_ENUM_AS_STRING(EQUITY);
4399         GNC_RETURN_ENUM_AS_STRING(RECEIVABLE);
4400         GNC_RETURN_ENUM_AS_STRING(PAYABLE);
4401         GNC_RETURN_ENUM_AS_STRING(ROOT);
4402         GNC_RETURN_ENUM_AS_STRING(TRADING);
4403         GNC_RETURN_ENUM_AS_STRING(CHECKING);
4404         GNC_RETURN_ENUM_AS_STRING(SAVINGS);
4405         GNC_RETURN_ENUM_AS_STRING(MONEYMRKT);
4406         GNC_RETURN_ENUM_AS_STRING(CREDITLINE);
4407     default:
4408         PERR ("asked to translate unknown account type %d.\n", type);
4409         break;
4410     }
4411     return(NULL);
4412 }
4413 
4414 #undef GNC_RETURN_ENUM_AS_STRING
4415 
4416 #define GNC_RETURN_ON_MATCH(x) \
4417   if(g_strcmp0(#x, (str)) == 0) { *type = ACCT_TYPE_ ## x; return(TRUE); }
4418 
4419 gboolean
xaccAccountStringToType(const char * str,GNCAccountType * type)4420 xaccAccountStringToType(const char* str, GNCAccountType *type)
4421 {
4422 
4423     GNC_RETURN_ON_MATCH(NONE);
4424     GNC_RETURN_ON_MATCH(BANK);
4425     GNC_RETURN_ON_MATCH(CASH);
4426     GNC_RETURN_ON_MATCH(CREDIT);
4427     GNC_RETURN_ON_MATCH(ASSET);
4428     GNC_RETURN_ON_MATCH(LIABILITY);
4429     GNC_RETURN_ON_MATCH(STOCK);
4430     GNC_RETURN_ON_MATCH(MUTUAL);
4431     GNC_RETURN_ON_MATCH(CURRENCY);
4432     GNC_RETURN_ON_MATCH(INCOME);
4433     GNC_RETURN_ON_MATCH(EXPENSE);
4434     GNC_RETURN_ON_MATCH(EQUITY);
4435     GNC_RETURN_ON_MATCH(RECEIVABLE);
4436     GNC_RETURN_ON_MATCH(PAYABLE);
4437     GNC_RETURN_ON_MATCH(ROOT);
4438     GNC_RETURN_ON_MATCH(TRADING);
4439     GNC_RETURN_ON_MATCH(CHECKING);
4440     GNC_RETURN_ON_MATCH(SAVINGS);
4441     GNC_RETURN_ON_MATCH(MONEYMRKT);
4442     GNC_RETURN_ON_MATCH(CREDITLINE);
4443 
4444     PERR("asked to translate unknown account type string %s.\n",
4445          str ? str : "(null)");
4446 
4447     return(FALSE);
4448 }
4449 
4450 #undef GNC_RETURN_ON_MATCH
4451 
4452 /* impedance mismatch is a source of loss */
4453 GNCAccountType
xaccAccountStringToEnum(const char * str)4454 xaccAccountStringToEnum(const char* str)
4455 {
4456     GNCAccountType type;
4457     gboolean rc;
4458     rc = xaccAccountStringToType(str, &type);
4459     if (FALSE == rc) return ACCT_TYPE_INVALID;
4460     return type;
4461 }
4462 
4463 /********************************************************************\
4464 \********************************************************************/
4465 
4466 static char const *
4467 account_type_name[NUM_ACCOUNT_TYPES] =
4468 {
4469     N_("Bank"),
4470     N_("Cash"),
4471     N_("Asset"),
4472     N_("Credit Card"),
4473     N_("Liability"),
4474     N_("Stock"),
4475     N_("Mutual Fund"),
4476     N_("Currency"),
4477     N_("Income"),
4478     N_("Expense"),
4479     N_("Equity"),
4480     N_("A/Receivable"),
4481     N_("A/Payable"),
4482     N_("Root"),
4483     N_("Trading")
4484     /*
4485       N_("Checking"),
4486       N_("Savings"),
4487       N_("Money Market"),
4488       N_("Credit Line")
4489     */
4490 };
4491 
4492 const char *
xaccAccountGetTypeStr(GNCAccountType type)4493 xaccAccountGetTypeStr(GNCAccountType type)
4494 {
4495     if (type < 0 || NUM_ACCOUNT_TYPES <= type ) return "";
4496     return _(account_type_name [type]);
4497 }
4498 
4499 /********************************************************************\
4500 \********************************************************************/
4501 
4502 guint32
xaccAccountTypesCompatibleWith(GNCAccountType type)4503 xaccAccountTypesCompatibleWith (GNCAccountType type)
4504 {
4505     switch (type)
4506     {
4507         case ACCT_TYPE_BANK:
4508         case ACCT_TYPE_CASH:
4509         case ACCT_TYPE_ASSET:
4510         case ACCT_TYPE_CREDIT:
4511         case ACCT_TYPE_LIABILITY:
4512         case ACCT_TYPE_INCOME:
4513         case ACCT_TYPE_EXPENSE:
4514         case ACCT_TYPE_EQUITY:
4515             return
4516             (1 << ACCT_TYPE_BANK)       |
4517             (1 << ACCT_TYPE_CASH)       |
4518             (1 << ACCT_TYPE_ASSET)      |
4519             (1 << ACCT_TYPE_CREDIT)     |
4520             (1 << ACCT_TYPE_LIABILITY)  |
4521             (1 << ACCT_TYPE_INCOME)     |
4522             (1 << ACCT_TYPE_EXPENSE)    |
4523             (1 << ACCT_TYPE_EQUITY);
4524         case ACCT_TYPE_STOCK:
4525         case ACCT_TYPE_MUTUAL:
4526         case ACCT_TYPE_CURRENCY:
4527             return
4528             (1 << ACCT_TYPE_STOCK)      |
4529             (1 << ACCT_TYPE_MUTUAL)     |
4530             (1 << ACCT_TYPE_CURRENCY);
4531         case ACCT_TYPE_RECEIVABLE:
4532             return (1 << ACCT_TYPE_RECEIVABLE);
4533         case ACCT_TYPE_PAYABLE:
4534             return (1 << ACCT_TYPE_PAYABLE);
4535         case ACCT_TYPE_TRADING:
4536             return (1 << ACCT_TYPE_TRADING);
4537         default:
4538             PERR("bad account type: %d", type);
4539             return 0;
4540     }
4541 }
4542 guint32
xaccParentAccountTypesCompatibleWith(GNCAccountType type)4543 xaccParentAccountTypesCompatibleWith (GNCAccountType type)
4544 {
4545     switch (type)
4546     {
4547     case ACCT_TYPE_BANK:
4548     case ACCT_TYPE_CASH:
4549     case ACCT_TYPE_ASSET:
4550     case ACCT_TYPE_STOCK:
4551     case ACCT_TYPE_MUTUAL:
4552     case ACCT_TYPE_CURRENCY:
4553     case ACCT_TYPE_CREDIT:
4554     case ACCT_TYPE_LIABILITY:
4555     case ACCT_TYPE_RECEIVABLE:
4556     case ACCT_TYPE_PAYABLE:
4557         return
4558             (1 << ACCT_TYPE_BANK)       |
4559             (1 << ACCT_TYPE_CASH)       |
4560             (1 << ACCT_TYPE_ASSET)      |
4561             (1 << ACCT_TYPE_STOCK)      |
4562             (1 << ACCT_TYPE_MUTUAL)     |
4563             (1 << ACCT_TYPE_CURRENCY)   |
4564             (1 << ACCT_TYPE_CREDIT)     |
4565             (1 << ACCT_TYPE_LIABILITY)  |
4566             (1 << ACCT_TYPE_RECEIVABLE) |
4567             (1 << ACCT_TYPE_PAYABLE)    |
4568             (1 << ACCT_TYPE_ROOT);
4569     case ACCT_TYPE_INCOME:
4570     case ACCT_TYPE_EXPENSE:
4571         return
4572             (1 << ACCT_TYPE_INCOME)     |
4573             (1 << ACCT_TYPE_EXPENSE)    |
4574             (1 << ACCT_TYPE_ROOT);
4575     case ACCT_TYPE_EQUITY:
4576         return
4577             (1 << ACCT_TYPE_EQUITY)     |
4578             (1 << ACCT_TYPE_ROOT);
4579     case ACCT_TYPE_TRADING:
4580         return
4581             (1 << ACCT_TYPE_TRADING)    |
4582             (1 << ACCT_TYPE_ROOT);
4583     default:
4584         PERR("bad account type: %d", type);
4585         return 0;
4586     }
4587 }
4588 
4589 gboolean
xaccAccountTypesCompatible(GNCAccountType parent_type,GNCAccountType child_type)4590 xaccAccountTypesCompatible (GNCAccountType parent_type,
4591                             GNCAccountType child_type)
4592 {
4593     /* ACCT_TYPE_NONE isn't compatible with anything, even ACCT_TYPE_NONE. */
4594     if (parent_type == ACCT_TYPE_NONE || child_type == ACCT_TYPE_NONE)
4595         return FALSE;
4596 
4597     /* ACCT_TYPE_ROOT can't have a parent account, and asking will raise
4598      * an error. */
4599     if (child_type == ACCT_TYPE_ROOT)
4600         return FALSE;
4601 
4602     return ((xaccParentAccountTypesCompatibleWith (child_type) &
4603              (1 << parent_type))
4604             != 0);
4605 }
4606 
4607 guint32
xaccAccountTypesValid(void)4608 xaccAccountTypesValid(void)
4609 {
4610     guint32 mask = (1 << NUM_ACCOUNT_TYPES) - 1;
4611     mask &= ~((1 << ACCT_TYPE_CURRENCY) |  /* DEPRECATED */
4612               (1 << ACCT_TYPE_ROOT));      /* ROOT */
4613 
4614     return mask;
4615 }
4616 
xaccAccountIsAssetLiabType(GNCAccountType t)4617 gboolean xaccAccountIsAssetLiabType(GNCAccountType t)
4618 {
4619     switch (t)
4620     {
4621     case ACCT_TYPE_RECEIVABLE:
4622     case ACCT_TYPE_PAYABLE:
4623         return FALSE;
4624     default:
4625         return (xaccAccountTypesCompatible(ACCT_TYPE_ASSET, t)
4626                 || xaccAccountTypesCompatible(ACCT_TYPE_LIABILITY, t));
4627     }
4628 }
4629 
4630 GNCAccountType
xaccAccountTypeGetFundamental(GNCAccountType t)4631 xaccAccountTypeGetFundamental (GNCAccountType t)
4632 {
4633     switch (t)
4634     {
4635         case ACCT_TYPE_BANK:
4636         case ACCT_TYPE_STOCK:
4637         case ACCT_TYPE_MONEYMRKT:
4638         case ACCT_TYPE_CHECKING:
4639         case ACCT_TYPE_SAVINGS:
4640         case ACCT_TYPE_MUTUAL:
4641         case ACCT_TYPE_CURRENCY:
4642         case ACCT_TYPE_CASH:
4643         case ACCT_TYPE_ASSET:
4644         case ACCT_TYPE_RECEIVABLE:
4645             return ACCT_TYPE_ASSET;
4646         case ACCT_TYPE_CREDIT:
4647         case ACCT_TYPE_LIABILITY:
4648         case ACCT_TYPE_PAYABLE:
4649         case ACCT_TYPE_CREDITLINE:
4650             return ACCT_TYPE_LIABILITY;
4651         case ACCT_TYPE_INCOME:
4652             return ACCT_TYPE_INCOME;
4653         case ACCT_TYPE_EXPENSE:
4654             return ACCT_TYPE_EXPENSE;
4655         case ACCT_TYPE_EQUITY:
4656             return ACCT_TYPE_EQUITY;
4657         case ACCT_TYPE_TRADING:
4658         default:
4659             return ACCT_TYPE_NONE;
4660     }
4661 }
4662 
xaccAccountIsAPARType(GNCAccountType t)4663 gboolean xaccAccountIsAPARType(GNCAccountType t)
4664 {
4665     switch (t)
4666     {
4667     case ACCT_TYPE_RECEIVABLE:
4668     case ACCT_TYPE_PAYABLE:
4669         return TRUE;
4670     default:
4671         return FALSE;
4672     }
4673 }
4674 
xaccAccountIsEquityType(GNCAccountType t)4675 gboolean xaccAccountIsEquityType(GNCAccountType t)
4676 {
4677     switch (t)
4678     {
4679     case ACCT_TYPE_EQUITY:
4680         return TRUE;
4681     default:
4682         return FALSE;
4683     }
4684 }
4685 
4686 gboolean
xaccAccountIsPriced(const Account * acc)4687 xaccAccountIsPriced(const Account *acc)
4688 {
4689     AccountPrivate *priv;
4690 
4691     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
4692 
4693     priv = GET_PRIVATE(acc);
4694     return (priv->type == ACCT_TYPE_STOCK || priv->type == ACCT_TYPE_MUTUAL ||
4695             priv->type == ACCT_TYPE_CURRENCY);
4696 }
4697 
4698 /********************************************************************\
4699 \********************************************************************/
4700 
4701 gboolean
xaccAccountGetReconcileLastDate(const Account * acc,time64 * last_date)4702 xaccAccountGetReconcileLastDate (const Account *acc, time64 *last_date)
4703 {
4704     gint64 date = 0;
4705     GValue v = G_VALUE_INIT;
4706     gboolean retval = FALSE;
4707     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
4708     qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v, {KEY_RECONCILE_INFO, "last-date"});
4709     if (G_VALUE_HOLDS_INT64 (&v))
4710         date = g_value_get_int64 (&v);
4711 
4712     g_value_unset (&v);
4713     if (date)
4714     {
4715         if (last_date)
4716             *last_date = date;
4717         retval = TRUE;
4718     }
4719     g_value_unset (&v);
4720     return retval;
4721 }
4722 
4723 /********************************************************************\
4724 \********************************************************************/
4725 
4726 void
xaccAccountSetReconcileLastDate(Account * acc,time64 last_date)4727 xaccAccountSetReconcileLastDate (Account *acc, time64 last_date)
4728 {
4729     GValue v = G_VALUE_INIT;
4730     g_return_if_fail(GNC_IS_ACCOUNT(acc));
4731 
4732     g_value_init (&v, G_TYPE_INT64);
4733     g_value_set_int64 (&v, last_date);
4734     xaccAccountBeginEdit (acc);
4735     qof_instance_set_path_kvp (QOF_INSTANCE (acc), &v, {KEY_RECONCILE_INFO, "last-date"});
4736     mark_account (acc);
4737     xaccAccountCommitEdit (acc);
4738     g_value_unset (&v);
4739 }
4740 
4741 /********************************************************************\
4742 \********************************************************************/
4743 
4744 gboolean
xaccAccountGetReconcileLastInterval(const Account * acc,int * months,int * days)4745 xaccAccountGetReconcileLastInterval (const Account *acc,
4746                                      int *months, int *days)
4747 {
4748     GValue v1 = G_VALUE_INIT, v2 = G_VALUE_INIT;
4749     int64_t m = 0, d = 0;
4750     gboolean retval = FALSE;
4751 
4752     if (!acc) return FALSE;
4753     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
4754     qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v1,
4755             {KEY_RECONCILE_INFO, "last-interval", "months"});
4756     qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v2,
4757             {KEY_RECONCILE_INFO, "last-interval", "days"});
4758     if (G_VALUE_HOLDS_INT64 (&v1))
4759         m = g_value_get_int64 (&v1);
4760     if (G_VALUE_HOLDS_INT64 (&v2))
4761         d = g_value_get_int64 (&v2);
4762     if (m && d)
4763     {
4764         if (months)
4765             *months = m;
4766         if (days)
4767             *days = d;
4768         retval = TRUE;
4769     }
4770     g_value_unset (&v1);
4771     g_value_unset (&v2);
4772     return retval;
4773 }
4774 
4775 /********************************************************************\
4776 \********************************************************************/
4777 
4778 void
xaccAccountSetReconcileLastInterval(Account * acc,int months,int days)4779 xaccAccountSetReconcileLastInterval (Account *acc, int months, int days)
4780 {
4781     GValue v1 = G_VALUE_INIT, v2 = G_VALUE_INIT;
4782     g_return_if_fail(GNC_IS_ACCOUNT(acc));
4783 
4784     g_value_init (&v1, G_TYPE_INT64);
4785     g_value_set_int64 (&v1, months);
4786     g_value_init (&v2, G_TYPE_INT64);
4787     g_value_set_int64 (&v2, days);
4788     xaccAccountBeginEdit (acc);
4789     qof_instance_set_path_kvp (QOF_INSTANCE (acc), &v1,
4790             {KEY_RECONCILE_INFO, "last-interval", "months"});
4791     qof_instance_set_path_kvp (QOF_INSTANCE (acc), &v2,
4792             {KEY_RECONCILE_INFO, "last-interval", "days"});
4793     mark_account (acc);
4794     xaccAccountCommitEdit (acc);
4795     g_value_unset (&v1);
4796     g_value_unset (&v2);
4797 }
4798 
4799 /********************************************************************\
4800 \********************************************************************/
4801 
4802 gboolean
xaccAccountGetReconcilePostponeDate(const Account * acc,time64 * postpone_date)4803 xaccAccountGetReconcilePostponeDate (const Account *acc, time64 *postpone_date)
4804 {
4805     gint64 date = 0;
4806     gboolean retval = FALSE;
4807     GValue v = G_VALUE_INIT;
4808     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
4809     qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v,
4810             {KEY_RECONCILE_INFO, KEY_POSTPONE, "date"});
4811     if (G_VALUE_HOLDS_INT64 (&v))
4812         date = g_value_get_int64 (&v);
4813 
4814     if (date)
4815     {
4816         if (postpone_date)
4817             *postpone_date = date;
4818         retval = TRUE;
4819     }
4820     g_value_unset (&v);
4821     return retval;
4822 }
4823 
4824 /********************************************************************\
4825 \********************************************************************/
4826 
4827 void
xaccAccountSetReconcilePostponeDate(Account * acc,time64 postpone_date)4828 xaccAccountSetReconcilePostponeDate (Account *acc, time64 postpone_date)
4829 {
4830     GValue v = G_VALUE_INIT;
4831     g_return_if_fail(GNC_IS_ACCOUNT(acc));
4832 
4833     g_value_init (&v, G_TYPE_INT64);
4834     g_value_set_int64 (&v, postpone_date);
4835     xaccAccountBeginEdit (acc);
4836     qof_instance_set_path_kvp (QOF_INSTANCE (acc), &v,
4837             {KEY_RECONCILE_INFO, KEY_POSTPONE, "date"});
4838     mark_account (acc);
4839     xaccAccountCommitEdit (acc);
4840     g_value_unset (&v);
4841 }
4842 
4843 /********************************************************************\
4844 \********************************************************************/
4845 
4846 gboolean
xaccAccountGetReconcilePostponeBalance(const Account * acc,gnc_numeric * balance)4847 xaccAccountGetReconcilePostponeBalance (const Account *acc,
4848                                         gnc_numeric *balance)
4849 {
4850     gnc_numeric bal = gnc_numeric_zero ();
4851     GValue v = G_VALUE_INIT;
4852     gboolean retval = FALSE;
4853     g_return_val_if_fail(GNC_IS_ACCOUNT(acc), FALSE);
4854     qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v,
4855             {KEY_RECONCILE_INFO, KEY_POSTPONE, "balance"});
4856     if (G_VALUE_HOLDS_INT64 (&v))
4857     {
4858         bal = *(gnc_numeric*)g_value_get_boxed (&v);
4859         if (bal.denom)
4860         {
4861             if (balance)
4862                 *balance = bal;
4863             retval = TRUE;
4864         }
4865     }
4866     g_value_unset (&v);
4867     return retval;
4868 }
4869 
4870 /********************************************************************\
4871 \********************************************************************/
4872 
4873 void
xaccAccountSetReconcilePostponeBalance(Account * acc,gnc_numeric balance)4874 xaccAccountSetReconcilePostponeBalance (Account *acc, gnc_numeric balance)
4875 {
4876     GValue v = G_VALUE_INIT;
4877     g_return_if_fail(GNC_IS_ACCOUNT(acc));
4878 
4879     g_value_init (&v, GNC_TYPE_NUMERIC);
4880     g_value_set_boxed (&v, &balance);
4881     xaccAccountBeginEdit (acc);
4882     qof_instance_set_path_kvp (QOF_INSTANCE (acc), &v,
4883             {KEY_RECONCILE_INFO, KEY_POSTPONE, "balance"});
4884     mark_account (acc);
4885     xaccAccountCommitEdit (acc);
4886     g_value_unset (&v);
4887 }
4888 
4889 /********************************************************************\
4890 
4891 \********************************************************************/
4892 
4893 void
xaccAccountClearReconcilePostpone(Account * acc)4894 xaccAccountClearReconcilePostpone (Account *acc)
4895 {
4896     if (!acc) return;
4897 
4898     xaccAccountBeginEdit (acc);
4899     qof_instance_set_path_kvp (QOF_INSTANCE(acc), nullptr, {KEY_RECONCILE_INFO, KEY_POSTPONE});
4900     mark_account (acc);
4901     xaccAccountCommitEdit (acc);
4902 }
4903 
4904 /********************************************************************\
4905 \********************************************************************/
4906 
4907 const char *
xaccAccountGetLastNum(const Account * acc)4908 xaccAccountGetLastNum (const Account *acc)
4909 {
4910     auto priv = GET_PRIVATE (acc);
4911     if (priv->last_num == is_unset)
4912         priv->last_num = get_kvp_string_tag (acc, "last-num");
4913     return priv->last_num;
4914 }
4915 
4916 /********************************************************************\
4917 \********************************************************************/
4918 
4919 void
xaccAccountSetLastNum(Account * acc,const char * num)4920 xaccAccountSetLastNum (Account *acc, const char *num)
4921 {
4922     auto priv = GET_PRIVATE (acc);
4923     if (priv->last_num != is_unset)
4924         g_free (priv->last_num);
4925     priv->last_num = g_strdup (num);
4926     set_kvp_string_tag (acc, "last-num", priv->last_num);
4927 }
4928 
4929 static Account *
GetOrMakeOrphanAccount(Account * root,gnc_commodity * currency)4930 GetOrMakeOrphanAccount (Account *root, gnc_commodity * currency)
4931 {
4932     char * accname;
4933     Account * acc;
4934 
4935     g_return_val_if_fail (root, NULL);
4936 
4937     /* build the account name */
4938     if (!currency)
4939     {
4940         PERR ("No currency specified!");
4941         return NULL;
4942     }
4943 
4944     accname = g_strconcat (_("Orphaned Gains"), "-",
4945                            gnc_commodity_get_mnemonic (currency), nullptr);
4946 
4947     /* See if we've got one of these going already ... */
4948     acc = gnc_account_lookup_by_name(root, accname);
4949 
4950     if (acc == NULL)
4951     {
4952         /* Guess not. We'll have to build one. */
4953         acc = xaccMallocAccount (gnc_account_get_book(root));
4954         xaccAccountBeginEdit (acc);
4955         xaccAccountSetName (acc, accname);
4956         xaccAccountSetCommodity (acc, currency);
4957         xaccAccountSetType (acc, ACCT_TYPE_INCOME);
4958         xaccAccountSetDescription (acc, _("Realized Gain/Loss"));
4959         xaccAccountSetNotes (acc,
4960                              _("Realized Gains or Losses from "
4961                                "Commodity or Trading Accounts "
4962                                "that haven't been recorded elsewhere."));
4963 
4964         /* Hang the account off the root. */
4965         gnc_account_append_child (root, acc);
4966         xaccAccountCommitEdit (acc);
4967     }
4968 
4969     g_free (accname);
4970 
4971     return acc;
4972 }
4973 
4974 Account *
xaccAccountGainsAccount(Account * acc,gnc_commodity * curr)4975 xaccAccountGainsAccount (Account *acc, gnc_commodity *curr)
4976 {
4977     GValue v = G_VALUE_INIT;
4978     std::vector<std::string> path {KEY_LOT_MGMT, "gains-acct",
4979         gnc_commodity_get_unique_name (curr)};
4980     GncGUID *guid = NULL;
4981     Account *gains_account;
4982 
4983     g_return_val_if_fail (acc != NULL, NULL);
4984     qof_instance_get_path_kvp (QOF_INSTANCE(acc), &v, path);
4985     if (G_VALUE_HOLDS_BOXED (&v))
4986         guid = (GncGUID*)g_value_get_boxed (&v);
4987     if (guid == NULL) /* No gains account for this currency */
4988     {
4989         gains_account = GetOrMakeOrphanAccount (gnc_account_get_root (acc),
4990                                                 curr);
4991         guid = (GncGUID*)qof_instance_get_guid (QOF_INSTANCE (gains_account));
4992         xaccAccountBeginEdit (acc);
4993         {
4994              GValue vr = G_VALUE_INIT;
4995              g_value_init (&vr, GNC_TYPE_GUID);
4996              g_value_set_boxed (&vr, guid);
4997              qof_instance_set_path_kvp (QOF_INSTANCE (acc), &vr, path);
4998              qof_instance_set_dirty (QOF_INSTANCE (acc));
4999              g_value_unset (&vr);
5000         }
5001         xaccAccountCommitEdit (acc);
5002     }
5003     else
5004         gains_account = xaccAccountLookup (guid,
5005                                            qof_instance_get_book(acc));
5006 
5007     g_value_unset (&v);
5008     return gains_account;
5009 }
5010 
5011 /********************************************************************\
5012 \********************************************************************/
5013 
5014 void
dxaccAccountSetPriceSrc(Account * acc,const char * src)5015 dxaccAccountSetPriceSrc(Account *acc, const char *src)
5016 {
5017     if (!acc) return;
5018 
5019     if (xaccAccountIsPriced(acc))
5020         set_kvp_string_tag (acc, "old-price-source", src);
5021 }
5022 
5023 /********************************************************************\
5024 \********************************************************************/
5025 
5026 const char*
dxaccAccountGetPriceSrc(const Account * acc)5027 dxaccAccountGetPriceSrc(const Account *acc)
5028 {
5029     static char *source = nullptr;
5030     if (!acc) return NULL;
5031 
5032     if (!xaccAccountIsPriced(acc)) return NULL;
5033 
5034     g_free (source);
5035     source = get_kvp_string_tag (acc, "old-price-source");
5036     return source;
5037 }
5038 
5039 /********************************************************************\
5040 \********************************************************************/
5041 
5042 void
dxaccAccountSetQuoteTZ(Account * acc,const char * tz)5043 dxaccAccountSetQuoteTZ(Account *acc, const char *tz)
5044 {
5045     if (!acc) return;
5046     if (!xaccAccountIsPriced(acc)) return;
5047     set_kvp_string_tag (acc, "old-quote-tz", tz);
5048 }
5049 
5050 /********************************************************************\
5051 \********************************************************************/
5052 
5053 const char*
dxaccAccountGetQuoteTZ(const Account * acc)5054 dxaccAccountGetQuoteTZ(const Account *acc)
5055 {
5056     static char *quote_tz = nullptr;
5057     if (!acc) return NULL;
5058     if (!xaccAccountIsPriced(acc)) return NULL;
5059     g_free (quote_tz);
5060     quote_tz = get_kvp_string_tag (acc, "old-quote-tz");
5061     return quote_tz;
5062 }
5063 
5064 /********************************************************************\
5065 \********************************************************************/
5066 
5067 void
xaccAccountSetReconcileChildrenStatus(Account * acc,gboolean status)5068 xaccAccountSetReconcileChildrenStatus(Account *acc, gboolean status)
5069 {
5070     GValue v = G_VALUE_INIT;
5071     if (!acc) return;
5072 
5073     xaccAccountBeginEdit (acc);
5074     /* Would have been nice to use G_TYPE_BOOLEAN, but the other
5075      * boolean kvps save the value as "true" or "false" and that would
5076      * be file-incompatible with this.
5077      */
5078     g_value_init (&v, G_TYPE_INT64);
5079     g_value_set_int64 (&v, status);
5080     qof_instance_set_path_kvp (QOF_INSTANCE (acc), &v,
5081             {KEY_RECONCILE_INFO, KEY_INCLUDE_CHILDREN});
5082     mark_account(acc);
5083     xaccAccountCommitEdit (acc);
5084     g_value_unset (&v);
5085 }
5086 
5087 /********************************************************************\
5088 \********************************************************************/
5089 
5090 gboolean
xaccAccountGetReconcileChildrenStatus(const Account * acc)5091 xaccAccountGetReconcileChildrenStatus(const Account *acc)
5092 {
5093     /* access the account's kvp-data for status and return that, if no value
5094      * is found then we can assume not to include the children, that being
5095      * the default behaviour
5096      */
5097     GValue v = G_VALUE_INIT;
5098     gboolean retval;
5099     if (!acc) return FALSE;
5100     qof_instance_get_path_kvp (QOF_INSTANCE (acc), &v,
5101             {KEY_RECONCILE_INFO, KEY_INCLUDE_CHILDREN});
5102     retval = G_VALUE_HOLDS_INT64 (&v) ? g_value_get_int64 (&v) : FALSE;
5103     g_value_unset (&v);
5104     return retval;
5105 }
5106 
5107 /********************************************************************\
5108 \********************************************************************/
5109 
5110 /* The caller of this function can get back one or both of the
5111  * matching split and transaction pointers, depending on whether
5112  * a valid pointer to the location to store those pointers is
5113  * passed.
5114  */
5115 static void
finder_help_function(const Account * acc,const char * description,Split ** split,Transaction ** trans)5116 finder_help_function(const Account *acc, const char *description,
5117                      Split **split, Transaction **trans )
5118 {
5119     AccountPrivate *priv;
5120     GList *slp;
5121 
5122     /* First, make sure we set the data to NULL BEFORE we start */
5123     if (split) *split = NULL;
5124     if (trans) *trans = NULL;
5125 
5126     /* Then see if we have any work to do */
5127     if (acc == NULL) return;
5128 
5129     /* Why is this loop iterated backwards ?? Presumably because the split
5130      * list is in date order, and the most recent matches should be
5131      * returned!?  */
5132     priv = GET_PRIVATE(acc);
5133     for (slp = g_list_last(priv->splits); slp; slp = slp->prev)
5134     {
5135         Split *lsplit = static_cast<Split*>(slp->data);
5136         Transaction *ltrans = xaccSplitGetParent(lsplit);
5137 
5138         if (g_strcmp0 (description, xaccTransGetDescription (ltrans)) == 0)
5139         {
5140             if (split) *split = lsplit;
5141             if (trans) *trans = ltrans;
5142             return;
5143         }
5144     }
5145 }
5146 
5147 Split *
xaccAccountFindSplitByDesc(const Account * acc,const char * description)5148 xaccAccountFindSplitByDesc(const Account *acc, const char *description)
5149 {
5150     Split *split;
5151 
5152     /* Get the split which has a transaction matching the description. */
5153     finder_help_function(acc, description, &split, NULL);
5154     return split;
5155 }
5156 
5157 /* This routine is for finding a matching transaction in an account by
5158  * matching on the description field. [CAS: The rest of this comment
5159  * seems to belong somewhere else.] This routine is used for
5160  * auto-filling in registers with a default leading account. The
5161  * dest_trans is a transaction used for currency checking. */
5162 Transaction *
xaccAccountFindTransByDesc(const Account * acc,const char * description)5163 xaccAccountFindTransByDesc(const Account *acc, const char *description)
5164 {
5165     Transaction *trans;
5166 
5167     /* Get the translation matching the description. */
5168     finder_help_function(acc, description, NULL, &trans);
5169     return trans;
5170 }
5171 
5172 /* ================================================================ */
5173 /* Concatenation, Merging functions                                */
5174 
5175 void
gnc_account_join_children(Account * to_parent,Account * from_parent)5176 gnc_account_join_children (Account *to_parent, Account *from_parent)
5177 {
5178     AccountPrivate *from_priv;
5179     GList *children, *node;
5180 
5181     /* errors */
5182     g_return_if_fail(GNC_IS_ACCOUNT(to_parent));
5183     g_return_if_fail(GNC_IS_ACCOUNT(from_parent));
5184 
5185     /* optimizations */
5186     from_priv = GET_PRIVATE(from_parent);
5187     if (!from_priv->children)
5188         return;
5189 
5190     ENTER (" ");
5191     children = g_list_copy(from_priv->children);
5192     for (node = children; node; node = g_list_next(node))
5193         gnc_account_append_child(to_parent, static_cast <Account*> (node->data));
5194     g_list_free(children);
5195     LEAVE (" ");
5196 }
5197 /********************************************************************\
5198 \********************************************************************/
5199 
5200 void
gnc_account_merge_children(Account * parent)5201 gnc_account_merge_children (Account *parent)
5202 {
5203     AccountPrivate *ppriv, *priv_a, *priv_b;
5204     GList *node_a, *node_b, *work, *worker;
5205 
5206     g_return_if_fail(GNC_IS_ACCOUNT(parent));
5207 
5208     ppriv = GET_PRIVATE(parent);
5209     for (node_a = ppriv->children; node_a; node_a = node_a->next)
5210     {
5211         Account *acc_a = static_cast <Account*> (node_a->data);
5212 
5213         priv_a = GET_PRIVATE(acc_a);
5214         for (node_b = node_a->next; node_b; node_b = g_list_next(node_b))
5215         {
5216             Account *acc_b = static_cast <Account*> (node_b->data);
5217 
5218             priv_b = GET_PRIVATE(acc_b);
5219             if (0 != null_strcmp(priv_a->accountName, priv_b->accountName))
5220                 continue;
5221             if (0 != null_strcmp(priv_a->accountCode, priv_b->accountCode))
5222                 continue;
5223             if (0 != null_strcmp(priv_a->description, priv_b->description))
5224                 continue;
5225             if (0 != null_strcmp(xaccAccountGetColor(acc_a),
5226                                  xaccAccountGetColor(acc_b)))
5227                 continue;
5228             if (!gnc_commodity_equiv(priv_a->commodity, priv_b->commodity))
5229                 continue;
5230             if (0 != null_strcmp(xaccAccountGetNotes(acc_a),
5231                                  xaccAccountGetNotes(acc_b)))
5232                 continue;
5233             if (priv_a->type != priv_b->type)
5234                 continue;
5235 
5236             /* consolidate children */
5237             if (priv_b->children)
5238             {
5239                 work = g_list_copy(priv_b->children);
5240                 for (worker = work; worker; worker = g_list_next(worker))
5241                     gnc_account_append_child (acc_a, (Account *)worker->data);
5242                 g_list_free(work);
5243 
5244                 qof_event_gen (&acc_a->inst, QOF_EVENT_MODIFY, NULL);
5245                 qof_event_gen (&acc_b->inst, QOF_EVENT_MODIFY, NULL);
5246             }
5247 
5248             /* recurse to do the children's children */
5249             gnc_account_merge_children (acc_a);
5250 
5251             /* consolidate transactions */
5252             while (priv_b->splits)
5253                 xaccSplitSetAccount (static_cast <Split*> (priv_b->splits->data), acc_a);
5254 
5255             /* move back one before removal. next iteration around the loop
5256              * will get the node after node_b */
5257             node_b = g_list_previous(node_b);
5258 
5259             /* The destroy function will remove from list -- node_a is ok,
5260              * it's before node_b */
5261             xaccAccountBeginEdit (acc_b);
5262             xaccAccountDestroy (acc_b);
5263         }
5264     }
5265 }
5266 
5267 /* ================================================================ */
5268 /* Transaction Traversal functions                                  */
5269 
5270 
5271 void
xaccSplitsBeginStagedTransactionTraversals(GList * splits)5272 xaccSplitsBeginStagedTransactionTraversals (GList *splits)
5273 {
5274     GList *lp;
5275 
5276     for (lp = splits; lp; lp = lp->next)
5277     {
5278         Split *s = static_cast <Split*> (lp->data);
5279         Transaction *trans = s->parent;
5280 
5281         if (trans)
5282             trans->marker = 0;
5283     }
5284 }
5285 
5286 /* original function */
5287 void
xaccAccountBeginStagedTransactionTraversals(const Account * account)5288 xaccAccountBeginStagedTransactionTraversals (const Account *account)
5289 {
5290     AccountPrivate *priv;
5291 
5292     if (!account)
5293         return;
5294     priv = GET_PRIVATE(account);
5295     xaccSplitsBeginStagedTransactionTraversals(priv->splits);
5296 }
5297 
5298 gboolean
xaccTransactionTraverse(Transaction * trans,int stage)5299 xaccTransactionTraverse (Transaction *trans, int stage)
5300 {
5301     if (trans == NULL) return FALSE;
5302 
5303     if (trans->marker < stage)
5304     {
5305         trans->marker = stage;
5306         return TRUE;
5307     }
5308 
5309     return FALSE;
5310 }
5311 
do_one_split(Split * s,gpointer data)5312 static void do_one_split (Split *s, gpointer data)
5313 {
5314     Transaction *trans = s->parent;
5315     trans->marker = 0;
5316 }
5317 
do_one_account(Account * account,gpointer data)5318 static void do_one_account (Account *account, gpointer data)
5319 {
5320     AccountPrivate *priv = GET_PRIVATE(account);
5321     g_list_foreach(priv->splits, (GFunc)do_one_split, NULL);
5322 }
5323 
5324 /* Replacement for xaccGroupBeginStagedTransactionTraversals */
5325 void
gnc_account_tree_begin_staged_transaction_traversals(Account * account)5326 gnc_account_tree_begin_staged_transaction_traversals (Account *account)
5327 {
5328     GList *descendants;
5329 
5330     descendants = gnc_account_get_descendants(account);
5331     g_list_foreach(descendants, (GFunc)do_one_account, NULL);
5332     g_list_free(descendants);
5333 }
5334 
5335 int
xaccAccountStagedTransactionTraversal(const Account * acc,unsigned int stage,TransactionCallback thunk,void * cb_data)5336 xaccAccountStagedTransactionTraversal (const Account *acc,
5337                                        unsigned int stage,
5338                                        TransactionCallback thunk,
5339                                        void *cb_data)
5340 {
5341     AccountPrivate *priv;
5342     GList *split_p;
5343     GList *next;
5344     Transaction *trans;
5345     Split *s;
5346     int retval;
5347 
5348     if (!acc) return 0;
5349 
5350     priv = GET_PRIVATE(acc);
5351     for (split_p = priv->splits; split_p; split_p = next)
5352     {
5353         /* Get the next element in the split list now, just in case some
5354          * naughty thunk destroys the one we're using. This reduces, but
5355          * does not eliminate, the possibility of undefined results if
5356          * a thunk removes splits from this account. */
5357         next = g_list_next(split_p);
5358 
5359         s = static_cast <Split*> (split_p->data);
5360         trans = s->parent;
5361         if (trans && (trans->marker < stage))
5362         {
5363             trans->marker = stage;
5364             if (thunk)
5365             {
5366                 retval = thunk(trans, cb_data);
5367                 if (retval) return retval;
5368             }
5369         }
5370     }
5371 
5372     return 0;
5373 }
5374 
5375 int
gnc_account_tree_staged_transaction_traversal(const Account * acc,unsigned int stage,TransactionCallback thunk,void * cb_data)5376 gnc_account_tree_staged_transaction_traversal (const Account *acc,
5377         unsigned int stage,
5378         TransactionCallback thunk,
5379         void *cb_data)
5380 {
5381     const AccountPrivate *priv;
5382     GList *acc_p, *split_p;
5383     Transaction *trans;
5384     Split *s;
5385     int retval;
5386 
5387     if (!acc) return 0;
5388 
5389     /* depth first traversal */
5390     priv = GET_PRIVATE(acc);
5391     for (acc_p = priv->children; acc_p; acc_p = g_list_next(acc_p))
5392     {
5393         retval = gnc_account_tree_staged_transaction_traversal(static_cast <Account*> (acc_p->data),
5394                 stage, thunk, cb_data);
5395         if (retval) return retval;
5396     }
5397 
5398     /* Now this account */
5399     for (split_p = priv->splits; split_p; split_p = g_list_next(split_p))
5400     {
5401         s = static_cast <Split*> (split_p->data);
5402         trans = s->parent;
5403         if (trans && (trans->marker < stage))
5404         {
5405             trans->marker = stage;
5406             if (thunk)
5407             {
5408                 retval = thunk(trans, cb_data);
5409                 if (retval) return retval;
5410             }
5411         }
5412     }
5413 
5414     return 0;
5415 }
5416 
5417 /********************************************************************\
5418 \********************************************************************/
5419 
5420 int
xaccAccountTreeForEachTransaction(Account * acc,int (* proc)(Transaction * t,void * data),void * data)5421 xaccAccountTreeForEachTransaction (Account *acc,
5422                                    int (*proc)(Transaction *t, void *data),
5423                                    void *data)
5424 {
5425     if (!acc || !proc) return 0;
5426 
5427     gnc_account_tree_begin_staged_transaction_traversals (acc);
5428     return gnc_account_tree_staged_transaction_traversal (acc, 42, proc, data);
5429 }
5430 
5431 
5432 gint
xaccAccountForEachTransaction(const Account * acc,TransactionCallback proc,void * data)5433 xaccAccountForEachTransaction(const Account *acc, TransactionCallback proc,
5434                               void *data)
5435 {
5436     if (!acc || !proc) return 0;
5437     xaccAccountBeginStagedTransactionTraversals (acc);
5438     return xaccAccountStagedTransactionTraversal(acc, 42, proc, data);
5439 }
5440 
5441 /* ================================================================ */
5442 /* The following functions are used by
5443  * src/import-export/import-backend.c to manipulate the contra-account
5444  * matching data. See src/import-export/import-backend.c for explanations.
5445  */
5446 
5447 #define IMAP_FRAME              "import-map"
5448 #define IMAP_FRAME_BAYES        "import-map-bayes"
5449 
5450 /* Obtain an ImportMatchMap object from an Account or a Book */
5451 GncImportMatchMap *
gnc_account_imap_create_imap(Account * acc)5452 gnc_account_imap_create_imap (Account *acc)
5453 {
5454     GncImportMatchMap *imap;
5455 
5456     if (!acc) return NULL;
5457 
5458     imap = g_new0(GncImportMatchMap, 1);
5459 
5460     /* Cache the book for easy lookups; store the account/book for
5461      * marking dirtiness
5462      */
5463     imap->acc = acc;
5464     imap->book = gnc_account_get_book (acc);
5465 
5466     return imap;
5467 }
5468 
5469 /* Look up an Account in the map */
5470 Account*
gnc_account_imap_find_account(GncImportMatchMap * imap,const char * category,const char * key)5471 gnc_account_imap_find_account (GncImportMatchMap *imap,
5472                                const char *category,
5473                                const char *key)
5474 {
5475     GValue v = G_VALUE_INIT;
5476     GncGUID * guid = NULL;
5477     Account *retval;
5478     if (!imap || !key) return NULL;
5479     std::vector<std::string> path {IMAP_FRAME};
5480     if (category)
5481         path.push_back (category);
5482     path.push_back (key);
5483     qof_instance_get_path_kvp (QOF_INSTANCE (imap->acc), &v, path);
5484     if (G_VALUE_HOLDS_BOXED (&v))
5485         guid = (GncGUID*)g_value_get_boxed (&v);
5486     retval = xaccAccountLookup (guid, imap->book);
5487     g_value_unset (&v);
5488     return retval;
5489 }
5490 
5491 /* Store an Account in the map */
5492 void
gnc_account_imap_add_account(GncImportMatchMap * imap,const char * category,const char * key,Account * acc)5493 gnc_account_imap_add_account (GncImportMatchMap *imap,
5494                               const char *category,
5495                               const char *key,
5496                               Account *acc)
5497 {
5498     GValue v = G_VALUE_INIT;
5499     if (!imap || !key || !acc || (strlen (key) == 0)) return;
5500     std::vector<std::string> path {IMAP_FRAME};
5501     if (category)
5502         path.emplace_back (category);
5503     path.emplace_back (key);
5504     g_value_init (&v, GNC_TYPE_GUID);
5505     g_value_set_boxed (&v, xaccAccountGetGUID (acc));
5506     xaccAccountBeginEdit (imap->acc);
5507     qof_instance_set_path_kvp (QOF_INSTANCE (imap->acc), &v, path);
5508     qof_instance_set_dirty (QOF_INSTANCE (imap->acc));
5509     xaccAccountCommitEdit (imap->acc);
5510     g_value_unset (&v);
5511 }
5512 
5513 /* Remove a reference to an Account in the map */
5514 void
gnc_account_imap_delete_account(GncImportMatchMap * imap,const char * category,const char * key)5515 gnc_account_imap_delete_account (GncImportMatchMap *imap,
5516                                  const char *category,
5517                                  const char *key)
5518 {
5519     if (!imap || !key) return;
5520     std::vector<std::string> path {IMAP_FRAME};
5521     if (category)
5522         path.emplace_back (category);
5523     path.emplace_back (key);
5524     xaccAccountBeginEdit (imap->acc);
5525     if (qof_instance_has_path_slot (QOF_INSTANCE (imap->acc), path))
5526     {
5527         qof_instance_slot_path_delete (QOF_INSTANCE (imap->acc), path);
5528         if (category)
5529             qof_instance_slot_path_delete_if_empty (QOF_INSTANCE (imap->acc), {IMAP_FRAME, category});
5530         qof_instance_slot_path_delete_if_empty (QOF_INSTANCE (imap->acc), {IMAP_FRAME});
5531     }
5532     qof_instance_set_dirty (QOF_INSTANCE (imap->acc));
5533     xaccAccountCommitEdit (imap->acc);
5534 }
5535 
5536 /*--------------------------------------------------------------------------
5537  Below here is the bayes transaction to account matching system
5538 --------------------------------------------------------------------------*/
5539 
5540 
5541 /** intermediate values used to calculate the bayes probability of a given account
5542   where p(AB) = (a*b)/[a*b + (1-a)(1-b)], product is (a*b),
5543   product_difference is (1-a) * (1-b)
5544  */
5545 struct AccountProbability
5546 {
5547     double product; /* product of probabilities */
5548     double product_difference; /* product of (1-probabilities) */
5549 };
5550 
5551 struct AccountTokenCount
5552 {
5553     std::string account_guid;
5554     int64_t token_count; /** occurrences of a given token for this account_guid */
5555 };
5556 
5557 /** total_count and the token_count for a given account let us calculate the
5558  * probability of a given account with any single token
5559  */
5560 struct TokenAccountsInfo
5561 {
5562     std::vector<AccountTokenCount> accounts;
5563     int64_t total_count;
5564 };
5565 
5566 /** holds an account guid and its corresponding integer probability
5567   the integer probability is some factor of 10
5568  */
5569 struct AccountInfo
5570 {
5571     std::string account_guid;
5572     int32_t probability;
5573 };
5574 
5575 static void
build_token_info(char const * suffix,KvpValue * value,TokenAccountsInfo & tokenInfo)5576 build_token_info(char const * suffix, KvpValue * value, TokenAccountsInfo & tokenInfo)
5577 {
5578     if (strlen(suffix) == GUID_ENCODING_LENGTH)
5579     {
5580         tokenInfo.total_count += value->get<int64_t>();
5581         /*By convention, the key ends with the account GUID.*/
5582         tokenInfo.accounts.emplace_back(AccountTokenCount{std::string{suffix}, value->get<int64_t>()});
5583     }
5584 }
5585 
5586 /** We scale the probability values by probability_factor.
5587   ie. with probability_factor of 100000, 10% would be
5588   0.10 * 100000 = 10000 */
5589 static constexpr int probability_factor = 100000;
5590 
5591 static FinalProbabilityVec
build_probabilities(ProbabilityVec const & first_pass)5592 build_probabilities(ProbabilityVec const & first_pass)
5593 {
5594     FinalProbabilityVec ret;
5595     for (auto const & first_pass_prob : first_pass)
5596     {
5597         auto const & account_probability = first_pass_prob.second;
5598         /* P(AB) = A*B / [A*B + (1-A)*(1-B)]
5599          * NOTE: so we only keep track of a running product(A*B*C...)
5600          * and product difference ((1-A)(1-B)...)
5601          */
5602         int32_t probability = (account_probability.product /
5603                 (account_probability.product + account_probability.product_difference)) * probability_factor;
5604         ret.push_back({first_pass_prob.first, probability});
5605     }
5606     return ret;
5607 }
5608 
5609 static AccountInfo
highest_probability(FinalProbabilityVec const & probabilities)5610 highest_probability(FinalProbabilityVec const & probabilities)
5611 {
5612     AccountInfo ret {"", std::numeric_limits<int32_t>::min()};
5613     for (auto const & prob : probabilities)
5614         if (prob.second > ret.probability)
5615             ret = AccountInfo {prob.first, prob.second};
5616     return ret;
5617 }
5618 
5619 static ProbabilityVec
get_first_pass_probabilities(GncImportMatchMap * imap,GList * tokens)5620 get_first_pass_probabilities(GncImportMatchMap * imap, GList * tokens)
5621 {
5622     ProbabilityVec ret;
5623     /* find the probability for each account that contains any of the tokens
5624      * in the input tokens list. */
5625     for (auto current_token = tokens; current_token; current_token = current_token->next)
5626     {
5627         TokenAccountsInfo tokenInfo{};
5628         auto path = std::string{IMAP_FRAME_BAYES "/"} + static_cast <char const *> (current_token->data) + "/";
5629         qof_instance_foreach_slot_prefix (QOF_INSTANCE (imap->acc), path, &build_token_info, tokenInfo);
5630         for (auto const & current_account_token : tokenInfo.accounts)
5631         {
5632             auto item = std::find_if(ret.begin(), ret.end(), [&current_account_token]
5633                 (std::pair<std::string, AccountProbability> const & a) {
5634                     return current_account_token.account_guid == a.first;
5635                 });
5636             if (item != ret.end())
5637             {/* This account is already in the map */
5638                 item->second.product = ((double)current_account_token.token_count /
5639                                       (double)tokenInfo.total_count) * item->second.product;
5640                 item->second.product_difference = ((double)1 - ((double)current_account_token.token_count /
5641                                               (double)tokenInfo.total_count)) * item->second.product_difference;
5642             }
5643             else
5644             {
5645                 /* add a new entry */
5646                 AccountProbability new_probability;
5647                 new_probability.product = ((double)current_account_token.token_count /
5648                                       (double)tokenInfo.total_count);
5649                 new_probability.product_difference = 1 - (new_probability.product);
5650                 ret.push_back({current_account_token.account_guid, std::move(new_probability)});
5651             }
5652         } /* for all accounts in tokenInfo */
5653     }
5654     return ret;
5655 }
5656 
5657 static std::string
look_for_old_separator_descendants(Account * root,std::string const & full_name,const gchar * separator)5658 look_for_old_separator_descendants (Account *root, std::string const & full_name, const gchar *separator)
5659 {
5660     GList *top_accounts, *ptr;
5661     gint   found_len = 0;
5662     gchar  found_sep;
5663     top_accounts = gnc_account_get_descendants (root);
5664     PINFO("Incoming full_name is '%s', current separator is '%s'", full_name.c_str (), separator);
5665     /* Go through list of top level accounts */
5666     for (ptr = top_accounts; ptr; ptr = g_list_next (ptr))
5667     {
5668         const gchar *name = xaccAccountGetName (static_cast <Account const *> (ptr->data));
5669         // we are looking for the longest top level account that matches
5670         if (g_str_has_prefix (full_name.c_str (), name))
5671         {
5672             gint name_len = strlen (name);
5673             const gchar old_sep = full_name[name_len];
5674             if (!g_ascii_isalnum (old_sep)) // test for non alpha numeric
5675             {
5676                 if (name_len > found_len)
5677                 {
5678                     found_sep = full_name[name_len];
5679                     found_len = name_len;
5680                 }
5681             }
5682         }
5683     }
5684     g_list_free (top_accounts); // Free the List
5685     std::string new_name {full_name};
5686     if (found_len > 1)
5687         std::replace (new_name.begin (), new_name.end (), found_sep, *separator);
5688     PINFO ("Return full_name is '%s'", new_name.c_str ());
5689     return new_name;
5690 }
5691 
5692 static std::string
get_guid_from_account_name(Account * root,std::string const & name)5693 get_guid_from_account_name (Account * root, std::string const & name)
5694 {
5695     auto map_account = gnc_account_lookup_by_full_name (root, name.c_str ());
5696     if (!map_account)
5697     {
5698         auto temp_account_name = look_for_old_separator_descendants (root, name,
5699              gnc_get_account_separator_string ());
5700         map_account = gnc_account_lookup_by_full_name (root, temp_account_name.c_str ());
5701     }
5702     auto temp_guid = gnc::GUID {*xaccAccountGetGUID (map_account)};
5703     return temp_guid.to_string ();
5704 }
5705 
5706 static FlatKvpEntry
convert_entry(KvpEntry entry,Account * root)5707 convert_entry (KvpEntry entry, Account* root)
5708 {
5709     /*We need to make a copy here.*/
5710     auto account_name = entry.first.back();
5711     if (!gnc::GUID::is_valid_guid (account_name))
5712     {
5713         /* Earlier version stored the account name in the import map, and
5714          * there were early beta versions of 2.7 that stored a GUID.
5715          * If there is no GUID, we assume it's an account name. */
5716         /* Take off the account name and replace it with the GUID */
5717         entry.first.pop_back();
5718         auto guid_str = get_guid_from_account_name (root, account_name);
5719         entry.first.emplace_back (guid_str);
5720     }
5721     std::string new_key {std::accumulate (entry.first.begin(), entry.first.end(), std::string {})};
5722     new_key = IMAP_FRAME_BAYES + new_key;
5723     return {new_key, entry.second};
5724 }
5725 
5726 static std::vector<FlatKvpEntry>
get_flat_imap(Account * acc)5727 get_flat_imap (Account * acc)
5728 {
5729     auto frame = qof_instance_get_slots (QOF_INSTANCE (acc));
5730     auto slot = frame->get_slot ({IMAP_FRAME_BAYES});
5731     if (!slot)
5732         return {};
5733     auto imap_frame = slot->get<KvpFrame*> ();
5734     auto flat_kvp = imap_frame->flatten_kvp ();
5735     auto root = gnc_account_get_root (acc);
5736     std::vector <FlatKvpEntry> ret;
5737     for (auto const & flat_entry : flat_kvp)
5738     {
5739         auto converted_entry = convert_entry (flat_entry, root);
5740         /*If the entry was invalid, we don't perpetuate it.*/
5741         if (converted_entry.first.size())
5742             ret.emplace_back (converted_entry);
5743     }
5744     return ret;
5745 }
5746 
5747 static bool
convert_imap_account_bayes_to_flat(Account * acc)5748 convert_imap_account_bayes_to_flat (Account *acc)
5749 {
5750     auto frame = qof_instance_get_slots (QOF_INSTANCE (acc));
5751     if (!frame->get_keys().size())
5752         return false;
5753     auto flat_imap = get_flat_imap(acc);
5754     if (!flat_imap.size ())
5755         return false;
5756     xaccAccountBeginEdit(acc);
5757     frame->set({IMAP_FRAME_BAYES}, nullptr);
5758     std::for_each(flat_imap.begin(), flat_imap.end(),
5759                   [&frame] (FlatKvpEntry const & entry) {
5760                       frame->set({entry.first.c_str()}, entry.second);
5761                   });
5762     qof_instance_set_dirty (QOF_INSTANCE (acc));
5763     xaccAccountCommitEdit(acc);
5764     return true;
5765 }
5766 
5767 /*
5768  * Checks for import map data and converts them when found.
5769  */
5770 static bool
imap_convert_bayes_to_flat(QofBook * book)5771 imap_convert_bayes_to_flat (QofBook * book)
5772 {
5773     auto root = gnc_book_get_root_account (book);
5774     auto accts = gnc_account_get_descendants_sorted (root);
5775     bool ret = false;
5776     for (auto ptr = accts; ptr; ptr = g_list_next (ptr))
5777     {
5778         Account *acc = static_cast <Account*> (ptr->data);
5779         if (convert_imap_account_bayes_to_flat (acc))
5780         {
5781             ret = true;
5782             gnc_features_set_used (book, GNC_FEATURE_GUID_FLAT_BAYESIAN);
5783         }
5784     }
5785     g_list_free (accts);
5786     return ret;
5787 }
5788 
5789 void
gnc_account_reset_convert_bayes_to_flat(void)5790 gnc_account_reset_convert_bayes_to_flat (void)
5791 {
5792     imap_convert_bayes_to_flat_run = false;
5793 }
5794 
5795 /*
5796  * Here we check to see the state of import map data.
5797  *
5798  * If the GUID_FLAT_BAYESIAN feature flag is set, everything
5799  * should be fine.
5800  *
5801  * If it is not set, there are two possibilities: import data
5802  * are present from a previous version or not. If they are,
5803  * they are converted, and the feature flag set. If there are
5804  * no previous data, nothing is done.
5805  */
5806 static void
check_import_map_data(QofBook * book)5807 check_import_map_data (QofBook *book)
5808 {
5809     if (gnc_features_check_used (book, GNC_FEATURE_GUID_FLAT_BAYESIAN) ||
5810         imap_convert_bayes_to_flat_run)
5811         return;
5812 
5813     /* This function will set GNC_FEATURE_GUID_FLAT_BAYESIAN if necessary.*/
5814     imap_convert_bayes_to_flat (book);
5815     imap_convert_bayes_to_flat_run = true;
5816 }
5817 
5818 static constexpr double threshold = .90 * probability_factor; /* 90% */
5819 
5820 /** Look up an Account in the map */
5821 Account*
gnc_account_imap_find_account_bayes(GncImportMatchMap * imap,GList * tokens)5822 gnc_account_imap_find_account_bayes (GncImportMatchMap *imap, GList *tokens)
5823 {
5824     if (!imap)
5825         return nullptr;
5826     check_import_map_data (imap->book);
5827     auto first_pass = get_first_pass_probabilities(imap, tokens);
5828     if (!first_pass.size())
5829         return nullptr;
5830     auto final_probabilities = build_probabilities(first_pass);
5831     if (!final_probabilities.size())
5832         return nullptr;
5833     auto best = highest_probability(final_probabilities);
5834     if (best.account_guid == "")
5835         return nullptr;
5836     if (best.probability < threshold)
5837         return nullptr;
5838     gnc::GUID guid;
5839     try {
5840         guid = gnc::GUID::from_string(best.account_guid);
5841     } catch (gnc::guid_syntax_exception&) {
5842         return nullptr;
5843     }
5844     auto account = xaccAccountLookup (reinterpret_cast<GncGUID*>(&guid), imap->book);
5845     return account;
5846 }
5847 
5848 static void
change_imap_entry(GncImportMatchMap * imap,std::string const & path,int64_t token_count)5849 change_imap_entry (GncImportMatchMap *imap, std::string const & path, int64_t token_count)
5850 {
5851     GValue value = G_VALUE_INIT;
5852 
5853     PINFO("Source Account is '%s', Count is '%" G_GINT64_FORMAT "'",
5854            xaccAccountGetName (imap->acc), token_count);
5855 
5856     // check for existing guid entry
5857     if (qof_instance_has_slot (QOF_INSTANCE(imap->acc), path.c_str ()))
5858     {
5859         int64_t  existing_token_count = 0;
5860 
5861         // get the existing_token_count value
5862         qof_instance_get_path_kvp (QOF_INSTANCE (imap->acc), &value, {path});
5863 
5864         if (G_VALUE_HOLDS_INT64 (&value))
5865             existing_token_count = g_value_get_int64 (&value);
5866 
5867         PINFO("found existing value of '%" G_GINT64_FORMAT "'", existing_token_count);
5868 
5869         token_count = token_count + existing_token_count;
5870     }
5871 
5872     if (!G_IS_VALUE (&value))
5873         g_value_init (&value, G_TYPE_INT64);
5874 
5875     g_value_set_int64 (&value, token_count);
5876 
5877     // Add or Update the entry based on guid
5878     qof_instance_set_path_kvp (QOF_INSTANCE (imap->acc), &value, {path});
5879     gnc_features_set_used (imap->book, GNC_FEATURE_GUID_FLAT_BAYESIAN);
5880     g_value_unset (&value);
5881 }
5882 
5883 /** Updates the imap for a given account using a list of tokens */
5884 void
gnc_account_imap_add_account_bayes(GncImportMatchMap * imap,GList * tokens,Account * acc)5885 gnc_account_imap_add_account_bayes (GncImportMatchMap *imap,
5886                                     GList *tokens,
5887                                     Account *acc)
5888 {
5889     GList *current_token;
5890     gint64 token_count;
5891     char *account_fullname;
5892     char *guid_string;
5893 
5894     ENTER(" ");
5895     if (!imap)
5896     {
5897         LEAVE(" ");
5898         return;
5899     }
5900     check_import_map_data (imap->book);
5901 
5902     g_return_if_fail (acc != NULL);
5903     account_fullname = gnc_account_get_full_name(acc);
5904     xaccAccountBeginEdit (imap->acc);
5905 
5906     PINFO("account name: '%s'", account_fullname);
5907 
5908     guid_string = guid_to_string (xaccAccountGetGUID (acc));
5909 
5910     /* process each token in the list */
5911     for (current_token = g_list_first(tokens); current_token;
5912             current_token = current_token->next)
5913     {
5914         /* Jump to next iteration if the pointer is not valid or if the
5915                  string is empty. In HBCI import we almost always get an empty
5916                  string, which doesn't work in the kvp loopkup later. So we
5917                  skip this case here. */
5918         if (!current_token->data || (*((char*)current_token->data) == '\0'))
5919             continue;
5920         /* start off with one token for this account */
5921         token_count = 1;
5922         PINFO("adding token '%s'", (char*)current_token->data);
5923         auto path = std::string {IMAP_FRAME_BAYES} + '/' + static_cast<char*>(current_token->data) + '/' + guid_string;
5924         /* change the imap entry for the account */
5925         change_imap_entry (imap, path, token_count);
5926     }
5927     /* free up the account fullname and guid string */
5928     qof_instance_set_dirty (QOF_INSTANCE (imap->acc));
5929     xaccAccountCommitEdit (imap->acc);
5930     g_free (account_fullname);
5931     g_free (guid_string);
5932     LEAVE(" ");
5933 }
5934 
5935 /*******************************************************************************/
5936 
5937 static void
build_non_bayes(const char * key,const GValue * value,gpointer user_data)5938 build_non_bayes (const char *key, const GValue *value, gpointer user_data)
5939 {
5940     if (!G_VALUE_HOLDS_BOXED (value))
5941         return;
5942     QofBook     *book;
5943     GncGUID     *guid = NULL;
5944     gchar       *guid_string = NULL;
5945     auto imapInfo = (GncImapInfo*)user_data;
5946     // Get the book
5947     book = qof_instance_get_book (imapInfo->source_account);
5948 
5949     guid = (GncGUID*)g_value_get_boxed (value);
5950     guid_string = guid_to_string (guid);
5951 
5952     PINFO("build_non_bayes: match string '%s', match account guid: '%s'",
5953                             (char*)key, guid_string);
5954 
5955     auto imapInfo_node = static_cast <GncImapInfo*> (g_malloc(sizeof(GncImapInfo)));
5956 
5957     imapInfo_node->source_account = imapInfo->source_account;
5958     imapInfo_node->map_account    = xaccAccountLookup (guid, book);
5959     imapInfo_node->head           = g_strdup (imapInfo->head);
5960     imapInfo_node->match_string   = g_strdup (key);
5961     imapInfo_node->category       = g_strdup (imapInfo->category);
5962     imapInfo_node->count          = g_strdup (" ");
5963 
5964     imapInfo->list = g_list_prepend (imapInfo->list, imapInfo_node);
5965 
5966     g_free (guid_string);
5967 }
5968 
5969 static void
build_bayes(const char * suffix,KvpValue * value,GncImapInfo & imapInfo)5970 build_bayes (const char *suffix, KvpValue * value, GncImapInfo & imapInfo)
5971 {
5972     size_t guid_start = strlen(suffix) - GUID_ENCODING_LENGTH;
5973     std::string account_guid {&suffix[guid_start]};
5974     GncGUID guid;
5975     try
5976     {
5977         guid = gnc::GUID::from_string (account_guid);
5978     }
5979     catch (const gnc::guid_syntax_exception& err)
5980     {
5981         PWARN("Invalid GUID string from %s%s", IMAP_FRAME_BAYES, suffix);
5982     }
5983     auto map_account = xaccAccountLookup (&guid, gnc_account_get_book (imapInfo.source_account));
5984     auto imap_node = static_cast <GncImapInfo*> (g_malloc (sizeof (GncImapInfo)));
5985     auto count = value->get <int64_t> ();
5986     imap_node->source_account = imapInfo.source_account;
5987     imap_node->map_account = map_account;
5988     imap_node->head = g_strdup_printf ("%s%s", IMAP_FRAME_BAYES, suffix);
5989     imap_node->match_string = g_strndup (&suffix[1], guid_start - 2);
5990     imap_node->category = g_strdup(" ");
5991     imap_node->count = g_strdup_printf ("%" G_GINT64_FORMAT, count);
5992     imapInfo.list = g_list_prepend (imapInfo.list, imap_node);
5993 }
5994 
5995 GList *
gnc_account_imap_get_info_bayes(Account * acc)5996 gnc_account_imap_get_info_bayes (Account *acc)
5997 {
5998     check_import_map_data (gnc_account_get_book (acc));
5999     /* A dummy object which is used to hold the specified account, and the list
6000      * of data about which we care. */
6001     GncImapInfo imapInfo {acc, nullptr};
6002     qof_instance_foreach_slot_prefix (QOF_INSTANCE (acc), IMAP_FRAME_BAYES, &build_bayes, imapInfo);
6003     return g_list_reverse(imapInfo.list);
6004 }
6005 
6006 GList *
gnc_account_imap_get_info(Account * acc,const char * category)6007 gnc_account_imap_get_info (Account *acc, const char *category)
6008 {
6009     GList *list = NULL;
6010 
6011     GncImapInfo imapInfo;
6012 
6013     std::vector<std::string> path {IMAP_FRAME};
6014     if (category)
6015         path.emplace_back (category);
6016 
6017     imapInfo.source_account = acc;
6018     imapInfo.list = list;
6019 
6020     imapInfo.head = g_strdup (IMAP_FRAME);
6021     imapInfo.category = g_strdup (category);
6022 
6023     if (qof_instance_has_path_slot (QOF_INSTANCE (acc), path))
6024     {
6025         qof_instance_foreach_slot (QOF_INSTANCE(acc), IMAP_FRAME, category,
6026                                    build_non_bayes, &imapInfo);
6027     }
6028     return g_list_reverse(imapInfo.list);
6029 }
6030 
6031 /*******************************************************************************/
6032 
6033 gchar *
gnc_account_get_map_entry(Account * acc,const char * head,const char * category)6034 gnc_account_get_map_entry (Account *acc, const char *head, const char *category)
6035 {
6036     if (category)
6037         return get_kvp_string_path (acc, {head, category});
6038     else
6039         return get_kvp_string_path (acc, {head});
6040 }
6041 
6042 
6043 void
gnc_account_delete_map_entry(Account * acc,char * head,char * category,char * match_string,gboolean empty)6044 gnc_account_delete_map_entry (Account *acc, char *head, char *category,
6045                               char *match_string, gboolean empty)
6046 {
6047     if (acc != NULL)
6048     {
6049         std::vector<std::string> path {head};
6050         if (category)
6051             path.emplace_back (category);
6052         if (match_string)
6053             path.emplace_back (match_string);
6054 
6055         if (qof_instance_has_path_slot (QOF_INSTANCE (acc), path))
6056         {
6057             xaccAccountBeginEdit (acc);
6058             if (empty)
6059                 qof_instance_slot_path_delete_if_empty (QOF_INSTANCE(acc), path);
6060             else
6061                 qof_instance_slot_path_delete (QOF_INSTANCE(acc), path);
6062             PINFO("Account is '%s', head is '%s', category is '%s', match_string is'%s'",
6063                    xaccAccountGetName (acc), head, category, match_string);
6064             qof_instance_set_dirty (QOF_INSTANCE(acc));
6065             xaccAccountCommitEdit (acc);
6066         }
6067     }
6068 }
6069 
6070 void
gnc_account_delete_all_bayes_maps(Account * acc)6071 gnc_account_delete_all_bayes_maps (Account *acc)
6072 {
6073     if (acc != NULL)
6074     {
6075         auto slots = qof_instance_get_slots_prefix (QOF_INSTANCE (acc), IMAP_FRAME_BAYES);
6076         if (!slots.size()) return;
6077         for (auto const & entry : slots)
6078         {
6079              qof_instance_slot_path_delete (QOF_INSTANCE (acc), {entry.first});
6080         }
6081     }
6082 }
6083 
6084 /* ================================================================ */
6085 /* QofObject function implementation and registration */
6086 
6087 static void
gnc_account_book_end(QofBook * book)6088 gnc_account_book_end(QofBook* book)
6089 {
6090     Account *root_account = gnc_book_get_root_account(book);
6091     if (!root_account)
6092         return;
6093     xaccAccountBeginEdit(root_account);
6094     xaccAccountDestroy(root_account);
6095 }
6096 
6097 #ifdef _MSC_VER
6098 /* MSVC compiler doesn't have C99 "designated initializers"
6099  * so we wrap them in a macro that is empty on MSVC. */
6100 # define DI(x) /* */
6101 #else
6102 # define DI(x) x
6103 #endif
6104 static QofObject account_object_def =
6105 {
6106     DI(.interface_version = ) QOF_OBJECT_VERSION,
6107     DI(.e_type            = ) GNC_ID_ACCOUNT,
6108     DI(.type_label        = ) "Account",
6109     DI(.create            = ) (void*(*)(QofBook*)) xaccMallocAccount,
6110     DI(.book_begin        = ) NULL,
6111     DI(.book_end          = ) gnc_account_book_end,
6112     DI(.is_dirty          = ) qof_collection_is_dirty,
6113     DI(.mark_clean        = ) qof_collection_mark_clean,
6114     DI(.foreach           = ) qof_collection_foreach,
6115     DI(.printable         = ) (const char * (*)(gpointer)) xaccAccountGetName,
6116     DI(.version_cmp       = ) (int (*)(gpointer, gpointer)) qof_instance_version_cmp,
6117 };
6118 
xaccAccountRegister(void)6119 gboolean xaccAccountRegister (void)
6120 {
6121     static QofParam params[] =
6122     {
6123         {
6124             ACCOUNT_NAME_, QOF_TYPE_STRING,
6125             (QofAccessFunc) xaccAccountGetName,
6126             (QofSetterFunc) xaccAccountSetName
6127         },
6128         {
6129             ACCOUNT_CODE_, QOF_TYPE_STRING,
6130             (QofAccessFunc) xaccAccountGetCode,
6131             (QofSetterFunc) xaccAccountSetCode
6132         },
6133         {
6134             ACCOUNT_DESCRIPTION_, QOF_TYPE_STRING,
6135             (QofAccessFunc) xaccAccountGetDescription,
6136             (QofSetterFunc) xaccAccountSetDescription
6137         },
6138         {
6139             ACCOUNT_COLOR_, QOF_TYPE_STRING,
6140             (QofAccessFunc) xaccAccountGetColor,
6141             (QofSetterFunc) xaccAccountSetColor
6142         },
6143         {
6144             ACCOUNT_FILTER_, QOF_TYPE_STRING,
6145             (QofAccessFunc) xaccAccountGetFilter,
6146             (QofSetterFunc) xaccAccountSetFilter
6147         },
6148         {
6149             ACCOUNT_SORT_ORDER_, QOF_TYPE_STRING,
6150             (QofAccessFunc) xaccAccountGetSortOrder,
6151             (QofSetterFunc) xaccAccountSetSortOrder
6152         },
6153         {
6154             ACCOUNT_SORT_REVERSED_, QOF_TYPE_BOOLEAN,
6155             (QofAccessFunc) xaccAccountGetSortReversed,
6156             (QofSetterFunc) xaccAccountSetSortReversed
6157         },
6158         {
6159             ACCOUNT_NOTES_, QOF_TYPE_STRING,
6160             (QofAccessFunc) xaccAccountGetNotes,
6161             (QofSetterFunc) xaccAccountSetNotes
6162         },
6163         {
6164             ACCOUNT_PRESENT_, QOF_TYPE_NUMERIC,
6165             (QofAccessFunc) xaccAccountGetPresentBalance, NULL
6166         },
6167         {
6168             ACCOUNT_BALANCE_, QOF_TYPE_NUMERIC,
6169             (QofAccessFunc) xaccAccountGetBalance, NULL
6170         },
6171         {
6172             ACCOUNT_CLEARED_, QOF_TYPE_NUMERIC,
6173             (QofAccessFunc) xaccAccountGetClearedBalance, NULL
6174         },
6175         {
6176             ACCOUNT_RECONCILED_, QOF_TYPE_NUMERIC,
6177             (QofAccessFunc) xaccAccountGetReconciledBalance, NULL
6178         },
6179         {
6180             ACCOUNT_TYPE_, QOF_TYPE_STRING,
6181             (QofAccessFunc) qofAccountGetTypeString,
6182             (QofSetterFunc) qofAccountSetType
6183         },
6184         {
6185             ACCOUNT_FUTURE_MINIMUM_, QOF_TYPE_NUMERIC,
6186             (QofAccessFunc) xaccAccountGetProjectedMinimumBalance, NULL
6187         },
6188         {
6189             ACCOUNT_TAX_RELATED, QOF_TYPE_BOOLEAN,
6190             (QofAccessFunc) xaccAccountGetTaxRelated,
6191             (QofSetterFunc) xaccAccountSetTaxRelated
6192         },
6193         {
6194             ACCOUNT_OPENING_BALANCE_, QOF_TYPE_BOOLEAN,
6195             (QofAccessFunc) xaccAccountGetIsOpeningBalance,
6196             (QofSetterFunc) xaccAccountSetIsOpeningBalance
6197         },
6198         {
6199             ACCOUNT_SCU, QOF_TYPE_INT32,
6200             (QofAccessFunc) xaccAccountGetCommoditySCU,
6201             (QofSetterFunc) xaccAccountSetCommoditySCU
6202         },
6203         {
6204             ACCOUNT_NSCU, QOF_TYPE_BOOLEAN,
6205             (QofAccessFunc) xaccAccountGetNonStdSCU,
6206             (QofSetterFunc) xaccAccountSetNonStdSCU
6207         },
6208         {
6209             ACCOUNT_PARENT, GNC_ID_ACCOUNT,
6210             (QofAccessFunc) gnc_account_get_parent,
6211             (QofSetterFunc) qofAccountSetParent
6212         },
6213         {
6214             QOF_PARAM_BOOK, QOF_ID_BOOK,
6215             (QofAccessFunc) qof_instance_get_book, NULL
6216         },
6217         {
6218             QOF_PARAM_GUID, QOF_TYPE_GUID,
6219             (QofAccessFunc) qof_instance_get_guid, NULL
6220         },
6221         { NULL },
6222     };
6223 
6224     qof_class_register (GNC_ID_ACCOUNT, (QofSortFunc) qof_xaccAccountOrder, params);
6225 
6226     return qof_object_register (&account_object_def);
6227 }
6228 
6229 /* ======================= UNIT TESTING ACCESS =======================
6230  * The following functions are for unit testing use only.
6231  */
6232 static AccountPrivate*
utest_account_get_private(Account * acc)6233 utest_account_get_private (Account *acc)
6234 {
6235     return GET_PRIVATE (acc);
6236 }
6237 
6238 AccountTestFunctions*
_utest_account_fill_functions(void)6239 _utest_account_fill_functions(void)
6240 {
6241     AccountTestFunctions* func = g_new(AccountTestFunctions, 1);
6242 
6243     func->get_private = utest_account_get_private;
6244     func->coll_get_root_account = gnc_coll_get_root_account;
6245     func->xaccFreeAccountChildren = xaccFreeAccountChildren;
6246     func->xaccFreeAccount = xaccFreeAccount;
6247     func->qofAccountSetParent = qofAccountSetParent;
6248     func->gnc_account_lookup_by_full_name_helper =
6249         gnc_account_lookup_by_full_name_helper;
6250 
6251     return func;
6252 }
6253 /* ======================= END OF FILE =========================== */
6254