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(), [¤t_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