GnuCash c935c2f+
Loading...
Searching...
No Matches
import-backend.cpp
1/********************************************************************\
2 * This program is free software; you can redistribute it and/or *
3 * modify it under the terms of the GNU General Public License as *
4 * published by the Free Software Foundation; either version 2 of *
5 * the License, or (at your option) any later version. *
6 * *
7 * This program is distributed in the hope that it will be useful, *
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
10 * GNU General Public License for more details. *
11 * *
12 * You should have received a copy of the GNU General Public License*
13 * along with this program; if not, contact: *
14 * *
15 * Free Software Foundation Voice: +1-617-542-5942 *
16 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
17 * Boston, MA 02110-1301, USA gnu@gnu.org *
18\********************************************************************/
29#include <config.h>
30
31#include <gtk/gtk.h>
32#include <glib/gi18n.h>
33#include <stdlib.h>
34#include <math.h>
35
36#include <errno.h>
37
38#include "import-backend.h"
39#include "import-utilities.h"
40#include "Account.h"
41#include "Account.hpp"
42#include "Query.h"
43#include "gnc-engine.h"
44#include "engine-helpers.h"
45#include "gnc-prefs.h"
46#include "gnc-ui-util.h"
47
48#include <algorithm>
49#include <string>
50
51#define GNCIMPORT_DESC "desc"
52#define GNCIMPORT_MEMO "memo"
53#define GNCIMPORT_PAYEE "payee"
54
55/********************************************************************\
56 * Constants *
57\********************************************************************/
58
59static QofLogModule log_module = GNC_MOD_IMPORT;
60
61/********************************************************************\
62 * Forward declared prototypes *
63\********************************************************************/
64
65static void matchmap_store_destination(Account* base_acc,
66 GNCImportTransInfo* trans_info,
67 gboolean use_match);
68
69static void trans_info_calculate_dest_amount (GNCImportTransInfo *info);
70
71
72/********************************************************************\
73 * Structures passed between the functions *
74\********************************************************************/
75
77{
78 GNCImportMatchInfo *selected_match;
79 gboolean selected_manually;
80};
81
83{
84 Transaction * trans;
85 Split * first_split;
86
87 /* GList of GNCImportMatchInfo's, one for each possible duplicate match. */
88 GList * match_list;
89 GNCImportSelectedMatchInfo selected_match_info;
90
91 GNCImportAction action;
92 GNCImportAction previous_action;
93
94 /* A list of tokenized strings to use for bayesian matching purposes */
95 GList * match_tokens;
96
97 /* In case of a single destination account it is stored here. */
98 Account *dest_acc;
99 gboolean dest_acc_selected_manually;
100
101 /* Reference id to link gnc transaction to external object. E.g. aqbanking job id. */
102 guint32 ref_id;
103
104 /* When updating a matched transaction, append Description and Notes instead of replacing */
105 gboolean append_text;
106
107 /* Extra data we can use to build the balancing split. It may be passed on by the
108 * code that calls the generic importer */
109 gnc_numeric lsplit_price;
110 char *lsplit_action;
111 char *lsplit_memo;
112 char lsplit_rec_state;
113 time64 lsplit_rec_date;
114
115 gnc_numeric lsplit_value;
116 /* Amount for the balancing split. This may be passed by the import front-
117 * ends or calculated. The latter is only possible when
118 * the destination account is known and may require an exchange rate
119 * if that account is not in the same commodity as the transaction. */
120 gnc_numeric lsplit_amount;
121 gboolean lsplit_amount_selected_manually;
122};
123
124/* Some simple getters and setters for the above data types. */
125
126GList *
127gnc_import_TransInfo_get_match_list (const GNCImportTransInfo *info)
128{
129 g_assert (info);
130 return info->match_list;
131}
132
133void
135{
136 g_return_if_fail (info);
137 info->match_list = g_list_remove (info->match_list, static_cast<gpointer>(info->match_list->data));
138 if (info->match_list)
139 info->selected_match_info.selected_match = static_cast<GNCImportMatchInfo*>(info->match_list->data);
140 else
141 {
142 info->selected_match_info.selected_match = nullptr;
143 gnc_import_TransInfo_set_action (info, GNCImport_ADD);
144 };
145}
146
147Transaction *
148gnc_import_TransInfo_get_trans (const GNCImportTransInfo *info)
149{
150 g_assert (info);
151 return info->trans;
152}
153
154gboolean
155gnc_import_TransInfo_is_balanced (const GNCImportTransInfo *info)
156{
157 g_assert (info);
159}
160
161Split *
162gnc_import_TransInfo_get_fsplit (const GNCImportTransInfo *info)
163{
164 g_assert (info);
165 return info->first_split;
166}
167
169gnc_import_TransInfo_get_selected_match (const GNCImportTransInfo *info)
170{
171 g_assert (info);
172 return info->selected_match_info.selected_match;
173}
174
175void
177 GNCImportMatchInfo *match,
178 gboolean selected_manually)
179{
180 g_assert (info);
181 info->selected_match_info.selected_match = match;
182 info->selected_match_info.selected_manually = selected_manually;
183}
184
185gboolean
187{
188 g_assert (info);
189 return info->selected_match_info.selected_manually;
190}
191
192GNCImportAction
193gnc_import_TransInfo_get_action (const GNCImportTransInfo *info)
194{
195 g_assert (info);
196 return info->action;
197}
198
199void
200gnc_import_TransInfo_set_action (GNCImportTransInfo *info,
201 GNCImportAction action)
202{
203 g_assert (info);
204 if (action != info->action)
205 {
206 info->previous_action = info->action;
207 info->action = action;
208 }
209}
210
211Account *
212gnc_import_TransInfo_get_destacc (const GNCImportTransInfo *info)
213{
214 g_assert (info);
215 return info->dest_acc;
216}
217void gnc_import_TransInfo_set_destacc (GNCImportTransInfo *info,
218 Account *acc,
219 gboolean selected_manually)
220{
221 g_assert (info);
222 info->dest_acc = acc;
223 info->dest_acc_selected_manually = selected_manually;
224
225 /* Store the mapping to the other account in the MatchMap. */
226 if (selected_manually)
227 matchmap_store_destination (nullptr, info, false);
228
229 trans_info_calculate_dest_amount (info);
230}
231
232gboolean
234{
235 g_assert (info);
236 return info->dest_acc_selected_manually;
237}
238
239guint32
240gnc_import_TransInfo_get_ref_id (const GNCImportTransInfo *info)
241{
242 g_assert (info);
243 return info->ref_id;
244}
245
246void
247gnc_import_TransInfo_set_ref_id (GNCImportTransInfo *info,
248 guint32 ref_id)
249{
250 g_assert (info);
251 info->ref_id = ref_id;
252}
253
254gnc_numeric
255gnc_import_TransInfo_get_price (const GNCImportTransInfo *info)
256{
257 g_assert (info);
258 return info->lsplit_price;
259}
260
261void
262gnc_import_TransInfo_set_price (GNCImportTransInfo *info,
263 gnc_numeric lprice)
264{
265 g_assert (info);
266 info->lsplit_price = lprice;
267 /* if a valid price is explicitly set, assume the user wants to
268 * use it to calculate balance split amount.
269 * Ensure this gets recalculated */
270 if (gnc_numeric_check (lprice) == 0)
271 {
272 info->lsplit_amount_selected_manually = false;
273 trans_info_calculate_dest_amount(info);
274 }
275}
276
277gnc_numeric
278gnc_import_TransInfo_get_dest_amount (const GNCImportTransInfo *info)
279{
280 g_assert (info);
281 return info->lsplit_amount;
282}
283
284gnc_numeric
285gnc_import_TransInfo_get_dest_value (const GNCImportTransInfo *info)
286{
287 g_assert (info);
288 return info->lsplit_value;
289}
290
291void
294{
295 g_assert (info);
296 if (lsplit)
297 {
298 info->lsplit_price = lsplit->price;
299 info->lsplit_action = g_strdup(lsplit->action);
300 info->lsplit_memo = g_strdup(lsplit->memo);
301 if (gnc_numeric_check (lsplit->amount) == 0)
302 {
303 info->lsplit_amount = lsplit->amount;
304 info->lsplit_amount_selected_manually = true;
305 }
306 /* Bayesian matching may have already set a candidate destination
307 * account. However if the csv data also provides one, the one from the
308 * csv data is preferred. */
309 if (lsplit->account)
310 info->dest_acc = lsplit->account;
311 info->lsplit_rec_state = lsplit->rec_state;
312 info->lsplit_rec_date = lsplit->rec_date;
313 }
314}
315
316void
317gnc_import_TransInfo_set_append_text (GNCImportTransInfo *info,
318 gboolean append_text)
319{
320 g_assert (info);
321 info->append_text = append_text;
322}
323
324
325Split *
327{
328 g_assert (info);
329 return info->split;
330}
331
332gint
334{
335 if (info)
336 return info->probability;
337 else
338 return 0;
339}
340
341void gnc_import_TransInfo_delete (GNCImportTransInfo *info)
342{
343 if (info)
344 {
345 g_list_free_full (info->match_list, g_free);
346 /*If the transaction exists and is still open, it must be destroyed*/
347 if (xaccTransIsOpen(info->trans))
348 {
349 xaccTransDestroy(info->trans);
350 xaccTransCommitEdit(info->trans);
351 }
352 g_list_free_full (info->match_tokens, g_free);
353 g_free(info->lsplit_action);
354 g_free(info->lsplit_memo);
355
356 g_free(info);
357 }
358}
359
360GdkPixbuf* gen_probability_pixbuf(gint score_original, GNCImportSettings *settings, GtkWidget * widget)
361{
362 constexpr gint height = 15;
363 constexpr gint width_each_bar = 7;
364 constexpr gint width_first_bar = 1;
365 constexpr gint num_colors = 5;
366 constexpr size_t xpm_size = 2 + num_colors + height;
367 gchar * xpm[xpm_size];
368
369 g_assert(settings);
370 g_assert(widget);
371
372 auto score = std::max (0, score_original);
373
374 /* Add size definition to xpm */
375 xpm[0] = g_strdup_printf("%d%s%d%s%d%s", (width_each_bar * score) + width_first_bar/*width*/, " ", height, " ", num_colors, " 1"/*characters per pixel*/);
376
377 /* Define used colors */
378 xpm[1] = g_strdup(" c None");
379 xpm[2] = g_strdup("g c green");
380 xpm[3] = g_strdup("y c yellow");
381 xpm[4] = g_strdup("r c red");
382 xpm[5] = g_strdup("b c black");
383
384 auto add_threshold = gnc_import_Settings_get_add_threshold(settings);
385 auto clear_threshold = gnc_import_Settings_get_clear_threshold(settings);
386 for (int i = 0; i < height; i++)
387 {
388 xpm[num_colors+1+i] = g_new0(char, (width_each_bar * score) + width_first_bar + 1);
389 for (int j = 0; j <= score; j++)
390 {
391 if (j == 0)
392 strcat(xpm[num_colors+1+i], "b");
393 else if (i == 0 || i == height - 1)
394 strcat(xpm[num_colors+1+i], "bbbbbb ");
395 else if (j <= add_threshold)
396 strcat(xpm[num_colors+1+i], "brrrrb ");
397 else if (j >= clear_threshold)
398 strcat(xpm[num_colors+1+i], "bggggb ");
399 else
400 strcat(xpm[num_colors+1+i], "byyyyb ");
401 }
402 }
403 GError *err = nullptr;
404 std::string xpm_str = "/* XPM */\nstatic char * XFACE[] = {\n";
405
406 for (auto i = 0UL; i < xpm_size - 1; i++)
407 {
408 xpm_str += "\"";
409 xpm_str += xpm[i];
410 xpm_str += "\",\n";
411 g_free(xpm[i]);
412 }
413 xpm_str += "};";
414
415 auto gstream = g_memory_input_stream_new_from_data(xpm_str.c_str(), -1,
416 nullptr);
417 auto retval =
418 gdk_pixbuf_new_from_stream(G_INPUT_STREAM(gstream), nullptr, &err);
419 g_object_unref(gstream);
420
421 if (!retval && err)
422 PERR("Failed to create pixbuf from XPM data: %s", err->message);
423
424 return retval;
425}
426
427/*************************************************************************
428 * MatchMap related functions (storing and retrieving)
429 */
430
431/* Tokenize a string and append the tokens to an existing GList
432 * (or an empty GList)
433 */
434static GList*
435tokenize_string(GList* existing_tokens, const char *string)
436{
437 char **tokenized_strings = g_strsplit(string, " ", 0);
438 char **stringpos = tokenized_strings;
439
440 /* add each unique non-empty token to the token GList */
441 while (stringpos && *stringpos)
442 {
443 if ((strlen(*stringpos) > 0) &&
444 (!g_list_find_custom (existing_tokens, *stringpos, (GCompareFunc)g_strcmp0)))
445 existing_tokens = g_list_prepend(existing_tokens, g_strdup(*stringpos));
446
447 stringpos++;
448 }
449
450 /* free up the strings that g_strsplit() created */
451 g_strfreev(tokenized_strings);
452
453 return existing_tokens;
454}
455
456/* create and return a list of tokens for a given transaction info. */
457static GList*
458TransactionGetTokens(GNCImportTransInfo *info)
459{
460
461 g_return_val_if_fail (info, nullptr);
462 if (info->match_tokens) return info->match_tokens;
463
464 auto transaction = gnc_import_TransInfo_get_trans(info);
465 g_assert(transaction);
466
467 /* make tokens from the transaction description */
468 auto text = xaccTransGetDescription(transaction);
469 GList *tokens = nullptr;
470 tokens = tokenize_string(tokens, text);
471
472 /* The day of week the transaction occurred is a good indicator of
473 * what account this transaction belongs in. Get the date and convert
474 * it to day of week as a token
475 */
476 auto transtime = xaccTransGetDate(transaction);
477 auto tm_struct = gnc_gmtime(&transtime);
478 char local_day_of_week[16];
479 if (!qof_strftime(local_day_of_week, sizeof(local_day_of_week), "%A", tm_struct))
480 PERR("TransactionGetTokens: error, strftime failed\n");
481 gnc_tm_free (tm_struct);
482 /* we cannot add a locally allocated string to this array, dup it so
483 * it frees the same way the rest do
484 */
485 tokens = g_list_prepend(tokens, g_strdup(local_day_of_week));
486
487 /* make tokens from the memo of each split of this transaction */
488 for (GList *node=xaccTransGetSplitList (transaction); node; node=node->next)
489 {
490 text = xaccSplitGetMemo(static_cast<Split*>(node->data));
491 tokens = tokenize_string(tokens, text);
492 }
493
494 info->match_tokens = tokens;
495 return tokens;
496}
497
498/* searches using the GNCImportTransInfo through all existing transactions
499 * if there is an exact match of the description and memo
500 */
501static Account *
502matchmap_find_destination (Account *base_acc, GNCImportTransInfo *info)
503{
504 g_assert (info);
505 auto orig_acc = (base_acc ? base_acc : xaccSplitGetAccount
507
508 Account *result = nullptr;
509 if (gnc_prefs_get_bool (GNC_PREFS_GROUP_IMPORT, GNC_PREF_USE_BAYES))
510 {
511 /* get the tokens for this transaction* */
512 GList* tokens = TransactionGetTokens(info);
513
514 /* try to find the destination account for this transaction from its tokens */
515 result = gnc_account_imap_find_account_bayes(orig_acc, tokens);
516
517 }
518 else
519 result = gnc_account_imap_find_account
520 (orig_acc, GNCIMPORT_DESC,
522
523 return result;
524}
525
530static void
531matchmap_store_destination (Account *base_acc,
532 GNCImportTransInfo *trans_info,
533 gboolean use_match)
534{
535 g_assert (trans_info);
536
537 /* This will store the destination account of the selected match if
538 the reconcile match selected has only two splits. */
539 Account *dest = nullptr;
540 if (use_match)
545 else
546 dest = gnc_import_TransInfo_get_destacc (trans_info);
547 if (!dest)
548 return;
549
550 auto orig_acc = (base_acc ? base_acc : xaccSplitGetAccount
551 (gnc_import_TransInfo_get_fsplit (trans_info)));
552
553 if (gnc_prefs_get_bool (GNC_PREFS_GROUP_IMPORT, GNC_PREF_USE_BAYES))
554 {
555 /* tokenize this transaction */
556 auto tokens = TransactionGetTokens(trans_info);
557
558 /* add the tokens to the imap with the given destination account */
559 gnc_account_imap_add_account_bayes(orig_acc, tokens, dest);
560 }
561 else
562 {
563 auto desc = xaccTransGetDescription
564 (gnc_import_TransInfo_get_trans (trans_info));
565 auto memo = xaccSplitGetMemo
566 (gnc_import_TransInfo_get_fsplit (trans_info));
567
568 if (desc && *desc)
569 gnc_account_imap_add_account (orig_acc, GNCIMPORT_DESC, desc, dest);
570 if (memo && *memo)
571 gnc_account_imap_add_account (orig_acc, GNCIMPORT_MEMO, memo, dest);
572 }
573}
574
575
576
579void split_find_match (GNCImportTransInfo * trans_info,
580 Split * split,
581 gint display_threshold,
582 gint date_threshold,
583 gint date_not_threshold,
584 double fuzzy_amount_difference)
585{
586 gint prob = 0;
587
588 auto new_trans = gnc_import_TransInfo_get_trans (trans_info);
589 auto new_trans_fsplit = gnc_import_TransInfo_get_fsplit (trans_info);
590
591 /* Matching heuristics */
592
593 /* Amount heuristics */
594 auto downloaded_split_amount =
595 gnc_numeric_to_double (xaccSplitGetAmount(new_trans_fsplit));
596 DEBUG(" downloaded_split_amount=%f", downloaded_split_amount);
597 auto match_split_amount = gnc_numeric_to_double(xaccSplitGetAmount(split));
598 DEBUG(" match_split_amount=%f", match_split_amount);
599 if (fabs(downloaded_split_amount - match_split_amount) < 1e-6)
600 /* bug#347791: Double type shouldn't be compared for exact
601 equality, so we're using fabs() instead. */
602 /*if (gnc_numeric_equal(xaccSplitGetAmount
603 (new_trans_fsplit),
604 xaccSplitGetAmount(split)))
605 -- gnc_numeric_equal is an expensive function call */
606 {
607 prob = prob + 3;
608 DEBUG("heuristics: probability + 3 (amount)");
609 }
610 else if (fabs (downloaded_split_amount - match_split_amount) <=
611 fuzzy_amount_difference)
612 {
613 /* ATM fees are sometimes added directly in the transaction.
614 So you withdraw 100$ and get charged 101,25$ in the same
615 transaction */
616 prob = prob + 2;
617 DEBUG("heuristics: probability + 2 (amount)");
618 }
619 else
620 {
621 /* If a transaction's amount doesn't match within the
622 threshold, it's very unlikely to be the same transaction
623 so we give it an extra -5 penalty */
624 prob = prob - 5;
625 DEBUG("heuristics: probability - 1 (amount)");
626 }
627
628 /* Date heuristics */
629 auto match_time = xaccTransGetDate (xaccSplitGetParent (split));
630 auto download_time = xaccTransGetDate (new_trans);
631 auto datediff_day = llabs(match_time - download_time) / 86400;
632 /* Sorry, there are not really functions around at all that
633 provide for less hacky calculation of days of date
634 differences. Whatever. On the other hand, the difference
635 calculation itself will work regardless of month/year
636 turnarounds. */
637 auto download_time_str = qof_print_date(download_time);
638 auto match_time_str = qof_print_date(match_time);
639 DEBUG("Date download: %s vs match: %s", download_time_str, match_time_str);
640 g_free (download_time_str);
641 g_free (match_time_str);
642 DEBUG("diff day %lld", datediff_day);
643 if (datediff_day == 0)
644 {
645 prob = prob + 3;
646 DEBUG("heuristics: probability + 3 (date)");
647 }
648 else if (datediff_day <= date_threshold)
649 {
650 prob = prob + 2;
651 DEBUG("heuristics: probability + 2 (date)");
652 }
653 else if (datediff_day > date_not_threshold)
654 {
655 /* Extra penalty if that split lies awfully far away from
656 the given one. */
657 prob = prob - 5;
658 DEBUG("heuristics: probability - 5 (date)");
659 /* Changed 2005-02-21: Revert the hard-limiting behaviour
660 back to the previous large penalty. (Changed 2004-11-27:
661 The penalty is so high that we can forget about this
662 split anyway and skip the rest of the tests.) */
663 }
664
665 /* Check if date and amount are identical */
666 auto update_proposed = (prob < 6);
667
668 /* Check number heuristics */
669 auto new_trans_str = gnc_get_num_action(new_trans, new_trans_fsplit);
670 auto split_str = gnc_get_num_action (xaccSplitGetParent (split), split);
671 DEBUG("number download: '%s' to match: '%s'", new_trans_str, split_str);
672 if (new_trans_str && *new_trans_str && split_str && *split_str)
673 {
674 char *endptr;
675 auto conversion_ok = true;
676
677 /* To distinguish success/failure after strtol call */
678 errno = 0;
679 auto new_trans_number = strtol(new_trans_str, &endptr, 10);
680 /* Possible addressed problems: over/underflow, only non
681 numbers on string and string empty */
682 conversion_ok = !(errno || endptr == new_trans_str);
683
684 errno = 0;
685 auto split_number = strtol(split_str, &endptr, 10);
686 conversion_ok = !(errno || endptr == split_str);
687
688 if ( (conversion_ok && (split_number == new_trans_number)) ||
689 (g_strcmp0(new_trans_str, split_str) == 0) )
690 {
691 /* An exact match of the Check number gives a +4 */
692 prob += 4;
693 DEBUG("heuristics: probability + 4 (Check number)");
694 }
695 else if (strlen(new_trans_str) > 0 && strlen(split_str) > 0)
696 {
697 /* If both number are not empty yet do not match, add a
698 little extra penalty */
699 prob -= 2;
700 }
701 }
702
703 /* Memo heuristics */
704 auto memo = xaccSplitGetMemo(new_trans_fsplit);
705 auto match_memo = xaccSplitGetMemo(split);
706 if (memo && *memo && match_memo && *match_memo)
707 {
708 DEBUG("memo download: '%s' to match: '%s'", memo, match_memo);
709 if (safe_strcasecmp(memo, match_memo) == 0)
710 {
711 /* An exact match of memo gives a +2 */
712 prob = prob + 2;
713 DEBUG("heuristics: probability + 2 (memo)");
714 }
715 else
716 {
717 const size_t match_memo_len = strlen(match_memo);
718 const size_t memo_len = strlen(memo);
719 const size_t max_memo_len = match_memo_len > memo_len ? match_memo_len : memo_len;
720 if (match_memo_len > 1 && memo_len > 1
721 && (strncasecmp(memo, match_memo, max_memo_len / 2) == 0))
722 {
723 /* Very primitive fuzzy match worth +1. This matches up to the
724 first 50% of the longest string to skip annoying transaction
725 number some banks seem to include in the memo but someone
726 should write something more sophisticated */
727 prob = prob + 1;
728 DEBUG("heuristics: probability + 1 (memo)");
729 }
730 }
731 }
732
733 /* Description heuristics */
734 auto descr = xaccTransGetDescription(new_trans);
735 auto match_descr = xaccTransGetDescription(xaccSplitGetParent(split));
736 if (descr && *descr && match_descr && *match_descr)
737 {
738 DEBUG("description: download: '%s' to match: '%s'", descr, match_descr);
739 if (safe_strcasecmp(descr, match_descr) == 0)
740 {
741 /*An exact match of Description gives a +2 */
742 prob = prob + 2;
743 DEBUG("heuristics: probability + 2 (description)");
744 }
745 else
746 {
747 const size_t match_descr_len = strlen(match_descr);
748 const size_t descr_len = strlen(descr);
749 const size_t max_descr_len = match_descr_len > descr_len ? match_descr_len : descr_len;
750 if (match_descr_len > 1 && descr_len > 1
751 && (strncasecmp(descr, match_descr, max_descr_len / 2) == 0))
752 {
753 /* Very primitive fuzzy match worth +1. This matches up to the
754 first 50% of the longest string to skip annoying transaction
755 number some banks seem to include in the description but someone
756 should write something more sophisticated */
757 prob = prob + 1;
758 DEBUG("heuristics: probability + 1 (description)");
759 }
760 }
761 }
762
763 /* Is the probability high enough? Otherwise do nothing and return. */
764 if (prob < display_threshold) {
765 DEBUG("below threshold: %d", prob);
766 return;
767 }
768
769 /* The probability is high enough, so allocate an object
770 here. Allocating it only when it's actually being used is
771 probably quite some performance gain. */
772 auto match_info = g_new0(GNCImportMatchInfo, 1);
773
774 match_info->probability = prob;
775 match_info->update_proposed = update_proposed;
776 match_info->split = split;
777 match_info->trans = xaccSplitGetParent(split);
778
779
780 /* Append that to the list. Do not use g_list_append because
781 it is slow. The list is sorted afterwards anyway. */
782 trans_info->match_list = g_list_prepend(trans_info->match_list, match_info);
783 DEBUG("Added to list of possible matches: %d", prob);
784}
785
786/***********************************************************************
787 */
788
789static char*
790maybe_append_string (const char* match_string, const char* imp_string)
791{
792 if (!(match_string && *match_string))
793 return g_strdup(imp_string);
794
795 if (!(imp_string && *imp_string))
796 return nullptr;
797
798 auto norm_match_string = g_utf8_normalize (match_string, -1, G_NORMALIZE_NFC);
799 auto norm_imp_string = g_utf8_normalize (imp_string, -1, G_NORMALIZE_NFC);
800
801 char *retval = nullptr;
802 if (g_utf8_strlen (norm_imp_string, -1) > g_utf8_strlen (norm_match_string, -1) ||
803 !strstr (norm_match_string, norm_imp_string))
804 retval = g_strconcat(match_string, "|", imp_string, nullptr);
805
806 g_free (norm_match_string);
807 g_free (norm_imp_string);
808 return retval;
809
810}
811
812/* Append or replace transaction description and notes
813 * depending on the Append checkbox
814 */
815static void
816update_desc_and_notes (const GNCImportTransInfo* trans_info)
817{
818 auto selected_match = gnc_import_TransInfo_get_selected_match (trans_info);
819 auto imp_trans = gnc_import_TransInfo_get_trans (trans_info);
820
821 if (trans_info->append_text)
822 {
823 auto match_trans = selected_match->trans;
824 auto repl_str =
825 maybe_append_string (xaccTransGetDescription(match_trans),
826 xaccTransGetDescription(imp_trans));
827 if (repl_str)
828 xaccTransSetDescription(match_trans, repl_str);
829 g_free (repl_str);
830
831 repl_str =
832 maybe_append_string (xaccTransGetNotes(match_trans),
833 xaccTransGetNotes(imp_trans));
834 if (repl_str)
835 xaccTransSetNotes (match_trans, repl_str);
836 g_free (repl_str);
837 }
838 else
839 {
840 xaccTransSetDescription (selected_match->trans,
841 xaccTransGetDescription (imp_trans));
842 xaccTransSetNotes (selected_match->trans,
843 xaccTransGetNotes (imp_trans));
844 }
845}
846
847static void
848process_reconcile(Account *base_acc,
849 GNCImportTransInfo *trans_info,
850 GNCImportMatchInfo *selected_match)
851{
852 /* Reconcile the matching transaction */
853 /*DEBUG("BeginEdit selected_match")*/
854 xaccTransBeginEdit(selected_match->trans);
855
856 if (xaccSplitGetReconcile(selected_match->split) == NREC)
857 xaccSplitSetReconcile(selected_match->split, CREC);
858
859 /* Set reconcile date to today */
860 xaccSplitSetDateReconciledSecs(selected_match->split, gnc_time (nullptr));
861
862 /* Copy the online id to the reconciled transaction, so
863 * the match will be remembered */
864 auto online_id = xaccSplitGetOnlineID(trans_info->first_split);
865 if (online_id && *online_id)
866 xaccSplitSetOnlineID(selected_match->split, online_id);
867
868 /* Done editing. */
869 /*DEBUG("CommitEdit selected_match")*/
870 xaccTransCommitEdit(selected_match->trans);
871
872 /* Store the mapping to the other account in the MatchMap. */
873 matchmap_store_destination(base_acc, trans_info, true);
874
875 /* Erase the downloaded transaction */
876 xaccTransDestroy(trans_info->trans);
877 /*DEBUG("CommitEdit trans")*/
878 xaccTransCommitEdit(trans_info->trans);
879 /* Very important: Make sure the freed transaction is not freed again! */
880 trans_info->trans = nullptr;
881}
882
885gboolean
887 GNCImportTransInfo *trans_info)
888{
889 g_assert (trans_info);
890 /*DEBUG("Iteration %d, action %d, split %s", i,
891 trans_info->action,
892 xaccTransGetDescription (gnc_import_TransInfo_get_trans
893 (trans_info)))*/
894 switch (gnc_import_TransInfo_get_action (trans_info))
895 {
896 case GNCImport_SKIP:
897 return false;
898 case GNCImport_ADD:
899 /* Transaction gets imported. */
900 if (!gnc_import_TransInfo_is_balanced(trans_info)
902 {
903 /* Create the 'other' split. */
904 auto trans = gnc_import_TransInfo_get_trans (trans_info);
905 auto acct = gnc_import_TransInfo_get_destacc (trans_info);
906 auto split = xaccMallocSplit (gnc_account_get_book (acct));
907 xaccTransAppendSplit (trans, split);
908 xaccAccountInsertSplit (acct, split);
909 xaccSplitSetValue (split, trans_info->lsplit_value);
910 if (!gnc_numeric_zero_p (trans_info->lsplit_amount))
911 xaccSplitSetAmount (split, trans_info->lsplit_amount);
912 else
913 {
914 /* Bad! user asked to create a balancing split in an account with
915 * different currency/commodit than the transaction but didn't provide
916 * an exchange rate.
917 * Continue anyway pretenting split is in transaction currency. */
918 xaccSplitSetAmount (split, trans_info->lsplit_value);
919 PWARN("Missing exchange rate while adding transaction '%s', will assume rate of 1",
921 }
922 }
923
925 /*Set reconcile date to today*/
927 gnc_time (nullptr));
928 /* Done editing. */
929 xaccTransCommitEdit(trans_info->trans);
930 xaccTransRecordPrice(trans_info->trans, PRICE_SOURCE_SPLIT_IMPORT);
931 return true;
932 case GNCImport_UPDATE:
933 {
934 auto selected_match = gnc_import_TransInfo_get_selected_match(trans_info);
935
936 /* If there is no selection, ignore this transaction. */
937 if (!selected_match)
938 {
939 PWARN("No matching transaction to be cleared was chosen. Imported transaction will be ignored.");
940 break;
941 }
942
943 /* Transaction gets not imported but the matching one gets
944 updated and reconciled. */
945 if (!gnc_import_MatchInfo_get_split(selected_match))
946 PERR("The split I am trying to update and reconcile is nullptr, shouldn't happen!");
947 else
948 {
949 /* Update and reconcile the matching transaction */
950 /*DEBUG("BeginEdit selected_match")*/
951 xaccTransBeginEdit(selected_match->trans);
952
953 auto fsplit = gnc_import_TransInfo_get_fsplit(trans_info);
954 xaccTransSetDatePostedSecsNormalized(selected_match->trans,
956
957 auto match_split_amount = xaccSplitGetAmount(selected_match->split);
958 xaccSplitSetAmount(selected_match->split, xaccSplitGetAmount(fsplit));
959 xaccSplitSetValue(selected_match->split, xaccSplitGetValue(fsplit));
960
961 auto imbalance_value = gnc_import_TransInfo_get_dest_value(trans_info);
962 auto other_split = xaccSplitGetOtherSplit(selected_match->split);
963 if (!gnc_numeric_zero_p(imbalance_value) && other_split)
964 {
965 if (xaccSplitGetReconcile(other_split) == NREC)
966 {
967 xaccSplitSetValue(other_split, imbalance_value);
968 auto new_amt = gnc_import_TransInfo_get_dest_value(trans_info);
969 if (gnc_numeric_zero_p(new_amt))
970 {
971 auto other_split_amount = xaccSplitGetAmount(other_split);
972 auto price = gnc_numeric_div(match_split_amount, other_split_amount,
975
976 new_amt = gnc_numeric_mul(xaccSplitGetAmount(fsplit), price,
979 }
980 xaccSplitSetAmount(other_split, new_amt);
981 }
982 else
983 {
984 /* else GC will automatically insert a split to equity
985 to balance the transaction */
986 PWARN("Updated transaction '%s', but not other split.",
987 xaccTransGetDescription(selected_match->trans));
988 }
989 }
990
991 auto fs_memo = xaccSplitGetMemo (trans_info->first_split);
992 if (fs_memo && *fs_memo)
993 xaccSplitSetMemo(selected_match->split, fs_memo);
994
995 update_desc_and_notes(trans_info);
996
997 /*DEBUG("CommitEdit selected_match")*/
998 xaccTransCommitEdit(selected_match->trans);
999
1000 process_reconcile (base_acc, trans_info, selected_match);
1001 }
1002 }
1003 return true;
1004 case GNCImport_CLEAR:
1005 {
1006 auto selected_match = gnc_import_TransInfo_get_selected_match (trans_info);
1007
1008 /* If there is no selection, ignore this transaction. */
1009 if (!selected_match)
1010 {
1011 PWARN("No matching translaction to be cleared was chosen. Imported transaction will be ignored.");
1012 break;
1013 }
1014
1015 /* Transaction gets not imported but the matching one gets
1016 reconciled. */
1017 if (!gnc_import_MatchInfo_get_split (selected_match))
1018 PERR("The split I am trying to reconcile is nullptr, shouldn't happen!");
1019 else
1020 {
1021 /* Reconcile the matching transaction */
1022 process_reconcile(base_acc, trans_info, selected_match);
1023 }
1024 }
1025 return true;
1026 default:
1027 DEBUG("Invalid GNCImportAction for this imported transaction.");
1028 break;
1029 }
1030 /*DEBUG("End");*/
1031 return false;
1032}
1033
1034static GHashTable*
1035hash_account_online_ids (Account *account)
1036{
1037 auto acct_hash = g_hash_table_new_full
1038 (g_str_hash, g_str_equal, g_free, nullptr);
1039 for (auto split : xaccAccountGetSplits (account))
1040 {
1041 auto id = xaccSplitGetOnlineID (split);
1042 if (id && *id)
1043 g_hash_table_insert (acct_hash, (void*) g_strdup (id), GINT_TO_POINTER (1));
1044 }
1045 return acct_hash;
1046}
1047
1050gboolean gnc_import_exists_online_id (Transaction *trans, GHashTable* acct_id_hash)
1051{
1052
1053 /* Look for an online_id in the first split */
1054 auto source_split = xaccTransGetSplit(trans, 0);
1055 g_assert(source_split);
1056
1057 auto source_online_id = xaccSplitGetOnlineID (source_split);
1058
1059 // No online id, no point in continuing. We'd crash if we tried.
1060 if (!source_online_id)
1061 return false;
1062
1063 // Create a hash per account of a hash of all split IDs. Then the
1064 // test below will be fast if we have many transactions to import.
1065 auto dest_acct = xaccSplitGetAccount (source_split);
1066
1067 auto online_id_hash = static_cast<GHashTable*>(g_hash_table_lookup (acct_id_hash, dest_acct));
1068
1069 if (!online_id_hash)
1070 {
1071 online_id_hash = hash_account_online_ids (dest_acct);
1072 g_hash_table_insert (acct_id_hash, dest_acct, online_id_hash);
1073 }
1074
1075 auto online_id_exists = g_hash_table_contains (online_id_hash, source_online_id);
1076 if (online_id_exists)
1077 {
1078 auto date_str = qof_print_date(xaccTransGetDate(trans));
1079 DEBUG("Transaction with online ID %s already exists, date: %s", source_online_id, date_str);
1080 g_free (date_str);
1081 }
1082 return online_id_exists;
1083}
1084
1085
1086/* ******************************************************************
1087 */
1088
1089/* Calculate lsplit_amount based on knowledge gathered so far
1090 * If insufficient info is available (eg multi currency transaction with missing
1091 * exchange rate provided), set amount to 0 */
1092static void trans_info_calculate_dest_amount (GNCImportTransInfo *info)
1093{
1094 info->lsplit_value = gnc_numeric_neg (xaccTransGetImbalanceValue (info->trans));
1095 if (!info->lsplit_amount_selected_manually)
1096 info->lsplit_amount = {0, 1};
1097
1098 if (info->dest_acc)
1099 {
1100 auto tcurr = xaccTransGetCurrency(info->trans);
1101 auto dcurr = xaccAccountGetCommodity(info->dest_acc);
1102
1103 if (gnc_numeric_zero_p(info->lsplit_value))
1104 return;
1105
1106 if (gnc_commodity_equiv(tcurr, dcurr))
1107 info->lsplit_amount = info->lsplit_value;
1108 else if (info->lsplit_amount_selected_manually &&
1109 gnc_numeric_check(info->lsplit_amount) == 0)
1110 {
1111 /* Nothing to do, user has provided amount already */
1112 }
1113 else if (gnc_numeric_check(info->lsplit_price) == 0)
1114 {
1115 /* We are in a multi currency situation and have a valid price
1116 * Reminder: value = amount * price => amount = value / price */
1117 gnc_numeric inv_price = gnc_numeric_invert (info->lsplit_price);
1118 info->lsplit_amount = gnc_numeric_mul (info->lsplit_value,
1119 inv_price,
1121 GNC_HOW_RND_ROUND_HALF_UP);
1122 }
1123 }
1124}
1125
1127GNCImportTransInfo *
1128gnc_import_TransInfo_new (Transaction *trans, Account *base_acc)
1129{
1130 g_assert (trans);
1131
1132 auto t_info = g_new0(GNCImportTransInfo, 1);
1133
1134 t_info->trans = trans;
1135 /* Only use first split, the source split */
1136 auto split = xaccTransGetSplit(trans, 0);
1137 g_assert(split);
1138 t_info->first_split = split;
1139
1140 /* Try to find a previously selected destination account
1141 string match for the ADD action */
1143 matchmap_find_destination (base_acc, t_info),
1144 false);
1145
1146 return t_info;
1147}
1148
1149
1151static gint compare_probability (gconstpointer a,
1152 gconstpointer b)
1153{
1154 return(((GNCImportMatchInfo *)b)->probability -
1155 ((GNCImportMatchInfo *)a)->probability);
1156}
1157
1162void
1163gnc_import_TransInfo_init_matches (GNCImportTransInfo *trans_info,
1164 GNCImportSettings *settings)
1165{
1166 g_assert (trans_info);
1167
1168 if (trans_info->match_list)
1169 {
1170 DEBUG("Number of matches %d", g_list_length(trans_info->match_list));
1171
1172 trans_info->match_list = g_list_sort(trans_info->match_list,
1173 compare_probability);
1174 auto best_match = static_cast<GNCImportMatchInfo*>(g_list_nth_data(trans_info->match_list, 0));
1175 gnc_import_TransInfo_set_selected_match_info (trans_info, best_match, false);
1176 if (best_match &&
1177 best_match->probability >= gnc_import_Settings_get_clear_threshold(settings))
1178 {
1180 best_match->update_proposed)
1181 trans_info->action = GNCImport_UPDATE;
1182 else
1183 trans_info->action = GNCImport_CLEAR;
1184 }
1185 else if (!best_match ||
1186 best_match->probability <= gnc_import_Settings_get_add_threshold(settings))
1187 trans_info->action = GNCImport_ADD;
1189 trans_info->action = GNCImport_SKIP;
1191 trans_info->action = GNCImport_UPDATE;
1192 else
1193 trans_info->action = GNCImport_ADD;
1194 }
1195 else
1196 trans_info->action = GNCImport_ADD;
1197
1198
1199 trans_info->previous_action = trans_info->action;
1200}
1201
1202
Account handling public routines.
Account public routines (C++ api)
All type declarations for the whole Gnucash engine.
Generic api to store and retrieve preferences.
utility functions for the GnuCash UI
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account's commodity
Definition Account.cpp:3408
gboolean gnc_commodity_equiv(const gnc_commodity *a, const gnc_commodity *b)
This routine returns TRUE if the two commodities are equivalent.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition gnc-date.h:87
char * qof_print_date(time64 t)
Convenience; calls through to qof_print_date_dmy_buff().
Definition gnc-date.cpp:610
void gnc_tm_free(struct tm *time)
free a struct tm* created with gnc_localtime() or gnc_gmtime()
Definition gnc-date.cpp:97
time64 gnc_time(time64 *tbuf)
get the current time
Definition gnc-date.cpp:262
struct tm * gnc_gmtime(const time64 *secs)
fill out a time struct from a 64-bit time value
Definition gnc-date.cpp:178
gsize qof_strftime(gchar *buf, gsize max, const gchar *format, const struct tm *tm)
qof_strftime calls qof_format_time to print a given time and afterwards tries to put the result into ...
void xaccTransSetDescription(Transaction *trans, const char *desc)
Sets the transaction Description.
gnc_commodity * xaccTransGetCurrency(const Transaction *trans)
Returns the valuation commodity of this transaction.
void xaccTransDestroy(Transaction *trans)
Destroys a transaction.
#define xaccTransAppendSplit(t, s)
Add a split to the transaction.
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
const char * xaccTransGetDescription(const Transaction *trans)
Gets the transaction Description.
gboolean xaccTransIsOpen(const Transaction *trans)
The xaccTransIsOpen() method returns TRUE if the transaction is open for editing.
Split * xaccTransGetSplit(const Transaction *trans, int i)
Return a pointer to the indexed split in this transaction's split list.
const char * xaccTransGetNotes(const Transaction *trans)
Gets the transaction Notes.
Account * gnc_account_imap_find_account_bayes(Account *acc, GList *tokens)
Look up an Account in the map.
Definition Account.cpp:5525
void xaccTransSetDatePostedSecsNormalized(Transaction *trans, time64 time)
This function sets the posted date of the transaction, specified by a time64 (see ctime(3)).
void gnc_account_imap_add_account_bayes(Account *acc, GList *tokens, Account *added_acc)
Updates the imap for a given account using a list of tokens.
Definition Account.cpp:5571
SplitList * xaccTransGetSplitList(const Transaction *trans)
The xaccTransGetSplitList() method returns a GList of the splits in a transaction.
#define xaccAccountInsertSplit(acc, s)
The xaccAccountInsertSplit() method will insert the indicated split into the indicated account.
Definition Account.h:1069
void xaccTransRecordPrice(Transaction *trans, PriceSource source)
The xaccTransRecordPrice() method iterates through the splits and and record the non-currency equival...
void xaccTransBeginEdit(Transaction *trans)
The xaccTransBeginEdit() method must be called before any changes are made to a transaction or any of...
void xaccTransSetNotes(Transaction *trans, const char *notes)
Sets the transaction Notes.
gnc_numeric xaccTransGetImbalanceValue(const Transaction *trans)
The xaccTransGetImbalanceValue() method returns the total value of the transaction.
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
gint gnc_import_Settings_get_add_threshold(GNCImportSettings *settings)
Return the selected threshold.
void gnc_import_TransInfo_remove_top_match(GNCImportTransInfo *info)
Remove the first match in the list of possible matches
void gnc_import_TransInfo_delete(GNCImportTransInfo *info)
Destructor.
GList * gnc_import_TransInfo_get_match_list(const GNCImportTransInfo *info)
Returns the stored list of possible matches.
gboolean gnc_import_TransInfo_get_match_selected_manually(const GNCImportTransInfo *info)
Returns if the currently selected match was selected by the user.
gboolean gnc_import_Settings_get_action_update_enabled(GNCImportSettings *settings)
Return the selected action is enable state.
gboolean gnc_import_process_trans_item(Account *base_acc, GNCImportTransInfo *trans_info)
/brief – Processes one match according to its selected action.
gboolean gnc_import_exists_online_id(Transaction *trans, GHashTable *acct_id_hash)
Checks whether the given transaction's online_id already exists in its parent account.
gnc_numeric gnc_import_TransInfo_get_dest_amount(const GNCImportTransInfo *info)
Returns the destination split amount for this TransInfo.
Split * gnc_import_TransInfo_get_fsplit(const GNCImportTransInfo *info)
Returns the first split of the transaction of this TransInfo.
void gnc_import_TransInfo_set_append_text(GNCImportTransInfo *info, gboolean append_text)
Set the append_text for this TransInfo.
guint32 gnc_import_TransInfo_get_ref_id(const GNCImportTransInfo *info)
Returns the reference id for this TransInfo.
void gnc_import_TransInfo_set_destacc(GNCImportTransInfo *info, Account *acc, gboolean selected_manually)
Set the 'other account' of this transaction (used for auto-balance if needed).
void gnc_import_TransInfo_set_last_split_info(GNCImportTransInfo *info, GNCImportLastSplitInfo *lsplit)
Sets additional parameters to be used to generate the closing split.
GNCImportAction gnc_import_TransInfo_get_action(const GNCImportTransInfo *info)
Returns the currently selected action for this TransInfo.
gnc_numeric gnc_import_TransInfo_get_dest_value(const GNCImportTransInfo *info)
Returns the destination split value for this TransInfo.
Transaction * gnc_import_TransInfo_get_trans(const GNCImportTransInfo *info)
Returns the transaction of this TransInfo.
GNCImportTransInfo * gnc_import_TransInfo_new(Transaction *trans, Account *base_acc)
Create a new object of GNCImportTransInfo here.
GdkPixbuf * gen_probability_pixbuf(gint score_original, GNCImportSettings *settings, GtkWidget *widget)
This function generates a new pixmap representing a match score.
void gnc_import_TransInfo_init_matches(GNCImportTransInfo *trans_info, GNCImportSettings *settings)
Iterates through all splits of trans_info's originating account match list.
void gnc_import_TransInfo_set_price(GNCImportTransInfo *info, gnc_numeric lprice)
Set the exchange rate for this TransInfo.
void gnc_import_TransInfo_set_action(GNCImportTransInfo *info, GNCImportAction action)
Set the action for this TransInfo.
Account * gnc_import_TransInfo_get_destacc(const GNCImportTransInfo *info)
Returns the 'other account' of this transaction.
gboolean gnc_import_TransInfo_is_balanced(const GNCImportTransInfo *info)
Returns if the transaction stored in the TransInfo is currently balanced.
gboolean gnc_import_TransInfo_get_destacc_selected_manually(const GNCImportTransInfo *info)
Returns if the currently selected destination account for auto-matching was selected by the user.
gnc_numeric gnc_import_TransInfo_get_price(const GNCImportTransInfo *info)
Returns the exchange rate for this TransInfo.
gboolean gnc_import_Settings_get_action_skip_enabled(GNCImportSettings *settings)
Return the selected action is enable state.
void gnc_import_TransInfo_set_ref_id(GNCImportTransInfo *info, guint32 ref_id)
Set the reference id for this TransInfo.
void gnc_import_TransInfo_set_selected_match_info(GNCImportTransInfo *info, GNCImportMatchInfo *match, gboolean selected_manually)
Sets the currently selected match in this TransInfo.
gint gnc_import_MatchInfo_get_probability(const GNCImportMatchInfo *info)
Get the probability (confidence level) of this MatchInfo.
gint gnc_import_Settings_get_clear_threshold(GNCImportSettings *settings)
Return the selected threshold.
Split * gnc_import_MatchInfo_get_split(const GNCImportMatchInfo *info)
Get the split ('this-side split') of this MatchInfo.
void split_find_match(GNCImportTransInfo *trans_info, Split *split, gint display_threshold, gint date_threshold, gint date_not_threshold, double fuzzy_amount_difference)
The transaction matching heuristics are here.
#define GNC_PREFS_GROUP_IMPORT
The preferences used by the importer.
GNCImportMatchInfo * gnc_import_TransInfo_get_selected_match(const GNCImportTransInfo *info)
Returns the currently selected match in this TransInfo.
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
double gnc_numeric_to_double(gnc_numeric in)
Convert numeric to floating-point value.
#define GNC_DENOM_AUTO
Values that can be passed as the 'denom' argument.
gnc_numeric gnc_numeric_div(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Division.
GNCNumericErrorCode gnc_numeric_check(gnc_numeric in)
Check for error signal in value.
gnc_numeric gnc_numeric_mul(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Multiply a times b, returning the product.
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
gnc_numeric gnc_numeric_neg(gnc_numeric a)
Returns a newly created gnc_numeric that is the negative of the given gnc_numeric value.
gnc_numeric gnc_numeric_invert(gnc_numeric num)
Invert a gnc_numeric.
@ GNC_HOW_RND_ROUND_HALF_UP
Round to the nearest integer, rounding away from zero when there are two equidistant nearest integers...
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
Transaction * xaccSplitGetParent(const Split *split)
Returns the parent transaction of the split.
void xaccSplitSetReconcile(Split *split, char recn)
Set the reconcile flag.
char xaccSplitGetReconcile(const Split *split)
Returns the value of the reconcile flag.
void xaccSplitSetMemo(Split *split, const char *memo)
The memo is an arbitrary string associated with a split.
void xaccSplitSetValue(Split *split, gnc_numeric val)
The xaccSplitSetValue() method sets the value of this split in the transaction's commodity.
Split * xaccMallocSplit(QofBook *book)
Constructor.
void xaccSplitSetAmount(Split *split, gnc_numeric amt)
The xaccSplitSetAmount() method sets the amount in the account's commodity that the split should have...
gnc_numeric xaccSplitGetValue(const Split *split)
Returns the value of this split in the transaction's commodity.
Account * xaccSplitGetAccount(const Split *split)
Returns the account of this split, which was set through xaccAccountInsertSplit().
const char * xaccSplitGetMemo(const Split *split)
Returns the memo string.
void xaccSplitSetDateReconciledSecs(Split *split, time64 secs)
Set the date on which this split was reconciled by specifying the time as time64.
void xaccSplitSetOnlineID(Split *split, const char *id)
The online_id is the OFX/HBCI "FITID" recorded on a split when it is imported.
#define NREC
not reconciled or cleared
Definition Split.h:76
const char * xaccSplitGetOnlineID(const Split *split)
Returns the split's online_id.
Split * xaccSplitGetOtherSplit(const Split *split)
The xaccSplitGetOtherSplit() is a convenience routine that returns the other of a pair of splits.
#define CREC
The Split has been cleared
Definition Split.h:73
gnc_numeric xaccSplitGetAmount(const Split *split)
Returns the amount of the split in the account's commodity.
gint safe_strcasecmp(const gchar *da, const gchar *db)
case sensitive comparison of strings da and db - either may be NULL.
Definition qofutil.cpp:100
Generic importer backend interface.
Preference keys for the generic importer.
STRUCTS.