GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
import-backend.c
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 [email protected] *
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 "Query.h"
42 #include "gnc-engine.h"
43 #include "engine-helpers.h"
44 #include "gnc-prefs.h"
45 #include "gnc-ui-util.h"
46 
47 /* Private interface to Account GncImportMatchMap functions */
48 
54 /* Look up an Account in the map */
55 extern Account* gnc_imap_find_account(GncImportMatchMap *imap,
56  const char* category,
57  const char *key);
58 
59 /* Store an Account in the map. This mapping is immediatly stored in
60  the underlying kvp frame, regardless of whether the MatchMap is
61  destroyed later or not. */
62 extern void gnc_imap_add_account (GncImportMatchMap *imap,
63  const char *category,
64  const char *key, Account *acc);
65 
66 /* Look up an Account in the map from a GList* of pointers to strings(tokens)
67  from the current transaction */
69  GList* tokens);
70 
71 /* Store an Account in the map. This mapping is immediatly stored in
72  the underlying kvp frame, regardless of whether the MatchMap is
73  destroyed later or not. */
75  GList* tokens,
76  Account *acc);
77 
78 #define GNCIMPORT_DESC "desc"
79 #define GNCIMPORT_MEMO "memo"
80 #define GNCIMPORT_PAYEE "payee"
81 
82 /********************************************************************\
83  * Constants *
84 \********************************************************************/
85 
86 static QofLogModule log_module = GNC_MOD_IMPORT;
87 
88 /********************************************************************\
89  * Constants, should ideally be defined a user preference dialog *
90 \********************************************************************/
91 
92 static const int MATCH_DATE_THRESHOLD = 4; /*within 4 days*/
93 static const int MATCH_DATE_NOT_THRESHOLD = 14;
94 
95 /********************************************************************\
96  * Forward declared prototypes *
97 \********************************************************************/
98 
99 static void
100 matchmap_store_destination (GncImportMatchMap *matchmap,
101  GNCImportTransInfo *trans_info,
102  gboolean use_match);
103 
104 
105 /********************************************************************\
106  * Structures passed between the functions *
107 \********************************************************************/
108 
110 {
111  Transaction * trans;
112  Split * first_split;
113 
114  /* GList of GNCImportMatchInfo's, one for each possible duplicate match. */
115  GList * match_list;
116  GNCImportMatchInfo * selected_match_info;
117  gboolean match_selected_manually;
118 
119  GNCImportAction action;
120  GNCImportAction previous_action;
121 
122  /* A list of tokenized strings to use for bayesian matching purposes */
123  GList * match_tokens;
124 
125  /* In case of a single destination account it is stored here. */
126  Account *dest_acc;
127  gboolean dest_acc_selected_manually;
128 
129  /* Reference id to link gnc transaction to external object. E.g. aqbanking job id. */
130  guint32 ref_id;
131 };
132 
134 {
135  Transaction * trans;
136  Split * split;
137  /*GNC_match_probability probability;*/
138  gint probability;
139  gboolean update_proposed;
140 };
141 
142 /* Some simple getters and setters for the above data types. */
143 
144 GList *
146 {
147  g_assert (info);
148  return info->match_list;
149 }
150 
151 Transaction *
153 {
154  g_assert (info);
155  return info->trans;
156 }
157 
158 gboolean
160 {
161  g_assert (info);
162  /* Assume that the importer won't create a transaction that involves two or more
163  currencies and no non-currency commodity. In that case can use the simpler
164  value imbalance check. */
166  {
167  return TRUE;
168  }
169  else
170  {
171  return FALSE;
172  }
173 }
174 
175 Split *
177 {
178  g_assert (info);
179  return info->first_split;
180 }
181 
184 {
185  g_assert (info);
186  return info->selected_match_info;
187 }
188 
189 void
191  GNCImportMatchInfo *match,
192  gboolean selected_manually)
193 {
194  g_assert (info);
195  info->selected_match_info = match;
196  info->match_selected_manually = selected_manually;
197 }
198 
199 gboolean
201 {
202  g_assert (info);
203  return info->match_selected_manually;
204 }
205 
206 GNCImportAction
208 {
209  g_assert (info);
210  return info->action;
211 }
212 
213 void
215  GNCImportAction action)
216 {
217  g_assert (info);
218  if (action != info->action)
219  {
220  info->previous_action = info->action;
221  info->action = action;
222  }
223 }
224 
225 Account *
227 {
228  g_assert (info);
229  return info->dest_acc;
230 }
232  Account *acc,
233  gboolean selected_manually)
234 {
235  g_assert (info);
236  info->dest_acc = acc;
237  info->dest_acc_selected_manually = selected_manually;
238 
239  /* Store the mapping to the other account in the MatchMap. */
240  if (selected_manually)
241  {
242  matchmap_store_destination (NULL, info, FALSE);
243  }
244 }
245 
246 gboolean
248 {
249  g_assert (info);
250  return info->dest_acc_selected_manually;
251 }
252 
253 guint32
255 {
256  g_assert (info);
257  return info->ref_id;
258 }
259 
260 void
262  guint32 ref_id)
263 {
264  g_assert (info);
265  info->ref_id = ref_id;
266 }
267 
268 
269 Split *
271 {
272  g_assert (info);
273  return info->split;
274 }
275 
276 gint
278 {
279  if (info)
280  {
281  return info->probability;
282  }
283  else
284  {
285  return 0;
286  }
287 }
288 
290 {
291  if (info)
292  {
293  g_list_free (info->match_list);
294  /*If the transaction exists and is still open, it must be destroyed*/
295  if (info->trans && xaccTransIsOpen(info->trans))
296  {
297  xaccTransDestroy(info->trans);
298  xaccTransCommitEdit(info->trans);
299  }
300  if (info->match_tokens)
301  {
302  GList *node;
303 
304  for (node = info->match_tokens; node; node = node->next)
305  g_free (node->data);
306 
307  g_list_free (info->match_tokens);
308  }
309  g_free(info);
310  }
311 }
312 
313 GdkPixbuf* gen_probability_pixbuf(gint score_original, GNCImportSettings *settings, GtkWidget * widget)
314 {
315  GdkPixbuf* retval = NULL;
316  gint i, j;
317  gint score;
318  const gint height = 15;
319  const gint width_each_bar = 7;
320  gchar * green_bar = ("bggggb ");
321  gchar * yellow_bar = ("byyyyb ");
322  gchar * red_bar = ("brrrrb ");
323  gchar * black_bar = ("bbbbbb ");
324  const gint width_first_bar = 1;
325  gchar * black_first_bar = ("b");
326  const gint num_colors = 5;
327  gchar * size_str;
328  gchar * none_color_str = g_strdup_printf(" c None");
329  gchar * green_color_str = g_strdup_printf("g c green");
330  gchar * yellow_color_str = g_strdup_printf("y c yellow");
331  gchar * red_color_str = g_strdup_printf("r c red");
332  gchar * black_color_str = g_strdup_printf("b c black");
333  gchar * xpm[2+num_colors+height];
334  gint add_threshold, clear_threshold;
335 
336  g_assert(settings);
337  g_assert(widget);
338  if (score_original < 0)
339  {
340  score = 0;
341  }
342  else
343  {
344  score = score_original;
345  }
346  size_str = g_strdup_printf("%d%s%d%s%d%s", (width_each_bar * score) + width_first_bar/*width*/, " ", height, " ", num_colors, " 1"/*characters per pixel*/);
347 
348  /*DEBUG("Begin");*/
349  xpm[0] = size_str;
350  xpm[1] = none_color_str;
351  xpm[2] = green_color_str;
352  xpm[3] = yellow_color_str;
353  xpm[4] = red_color_str;
354  xpm[5] = black_color_str;
355  add_threshold = gnc_import_Settings_get_add_threshold(settings);
356  clear_threshold = gnc_import_Settings_get_clear_threshold(settings);
357 
358  for (i = 0; i < height; i++)
359  {
360  xpm[num_colors+1+i] = g_new0(char, (width_each_bar * score) + width_first_bar + 1);
361  for (j = 0; j <= score; j++)
362  {
363  if (i == 0 || i == height - 1)
364  {
365  if (j == 0)
366  {
367  strcat(xpm[num_colors+1+i], black_first_bar);
368  }
369  else
370  {
371  strcat(xpm[num_colors+1+i], black_bar);
372  }
373  }
374  else
375  {
376  if (j == 0)
377  {
378  strcat(xpm[num_colors+1+i], black_first_bar);
379  }
380  else if (j <= add_threshold)
381  {
382  strcat(xpm[num_colors+1+i], red_bar);
383  }
384  else if (j >= clear_threshold)
385  {
386  strcat(xpm[num_colors+1+i], green_bar);
387  }
388  else
389  {
390  strcat(xpm[num_colors+1+i], yellow_bar);
391  }
392  }
393  }
394  }
395 
396  retval = gdk_pixbuf_new_from_xpm_data((const gchar **)xpm);
397  for (i = 0; i <= num_colors + height; i++)
398  {
399  /*DEBUG("free_loop i=%d%s%s",i,": ",xpm[i]);*/
400  g_free(xpm[i]);
401  }
402 
403  return retval;
404 }
405 
406 /*************************************************************************
407  * MatchMap- related functions (storing and retrieving)
408  */
409 
410 /* Tokenize a string and append to an existing GList(or an empty GList)
411  * the tokens
412  */
413 static GList*
414 tokenize_string(GList* existing_tokens, const char *string)
415 {
416  char **tokenized_strings; /* array of strings returned by g_strsplit() */
417  char **stringpos;
418 
419  tokenized_strings = g_strsplit(string, " ", 0);
420  stringpos = tokenized_strings;
421 
422  /* add each token to the token GList */
423  while (stringpos && *stringpos)
424  {
425  /* prepend the char* to the token GList */
426  existing_tokens = g_list_prepend(existing_tokens, g_strdup(*stringpos));
427 
428  /* then move to the next string */
429  stringpos++;
430  }
431 
432  /* free up the strings that g_strsplit() created */
433  g_strfreev(tokenized_strings);
434 
435  return existing_tokens;
436 }
437 
438 /* create and return a list of tokens for a given transaction info. */
439 static GList*
440 TransactionGetTokens(GNCImportTransInfo *info)
441 {
442  Transaction* transaction;
443  GList* tokens;
444  const char* text;
445  time64 transtime;
446  struct tm *tm_struct;
447  char local_day_of_week[16];
448  Split* split;
449  int split_index;
450 
451  g_return_val_if_fail (info, NULL);
452  if (info->match_tokens) return info->match_tokens;
453 
454  transaction = gnc_import_TransInfo_get_trans(info);
455  g_assert(transaction);
456 
457  tokens = 0; /* start off with an empty list */
458 
459  /* make tokens from the transaction description */
460  text = xaccTransGetDescription(transaction);
461  tokens = tokenize_string(tokens, text);
462 
463  /* The day of week the transaction occured is a good indicator of
464  * what account this transaction belongs in. Get the date and covert
465  * it to day of week as a token
466  */
467  transtime = xaccTransGetDate(transaction);
468  tm_struct = gnc_gmtime(&transtime);
469  if (!qof_strftime(local_day_of_week, sizeof(local_day_of_week), "%A", tm_struct))
470  {
471  PERR("TransactionGetTokens: error, strftime failed\n");
472  }
473  gnc_tm_free (tm_struct);
474  /* we cannot add a locally allocated string to this array, dup it so
475  * it frees the same way the rest do
476  */
477  tokens = g_list_prepend(tokens, g_strdup(local_day_of_week));
478 
479  /* make tokens from the memo of each split of this transaction */
480  split_index = 0;
481  while ((split = xaccTransGetSplit(transaction, split_index)))
482  {
483  text = xaccSplitGetMemo(split);
484  tokens = tokenize_string(tokens, text);
485  split_index++; /* next split */
486  }
487 
488  /* remember the list of tokens for later.. */
489  info->match_tokens = tokens;
490 
491  /* return the pointer to the GList */
492  return tokens;
493 }
494 /* Destroy an import map. But all stored entries will still continue
495  * to exist in the underlying kvp frame of the account.
496  */
497 static void
498 gnc_imap_destroy (GncImportMatchMap *imap)
499 {
500  if (!imap) return;
501  g_free (imap);
502 }
503 
504 /* searches using the GNCImportTransInfo through all existing transactions
505  * if there is an exact match of the description and memo
506  */
507 static Account *
508 matchmap_find_destination (GncImportMatchMap *matchmap, GNCImportTransInfo *info)
509 {
510  GncImportMatchMap *tmp_map;
511  Account *result;
512  GList* tokens;
513  gboolean useBayes;
514 
515  g_assert (info);
516  tmp_map = ((matchmap != NULL) ? matchmap :
520 
521  useBayes = gnc_prefs_get_bool (GNC_PREFS_GROUP_IMPORT, GNC_PREF_USE_BAYES);
522  if (useBayes)
523  {
524  /* get the tokens for this transaction* */
525  tokens = TransactionGetTokens(info);
526 
527  /* try to find the destination account for this transaction from its tokens */
528  result = gnc_imap_find_account_bayes(tmp_map, tokens);
529 
530  }
531  else
532  {
533  /* old system of transaction to account matching */
534  result = gnc_imap_find_account
535  (tmp_map, GNCIMPORT_DESC,
537  }
538 
539  /* Disable matching by memo, until bayesian filtering is implemented.
540  * It's currently unlikely to help, and has adverse effects,
541  * causing false positives, since very often the type of the
542  * transaction is stored there.
543 
544  if (result == NULL)
545  result = gnc_imap_find_account
546  (tmp_map, GNCIMPORT_MEMO,
547  xaccSplitGetMemo (gnc_import_TransInfo_get_fsplit (info)));
548  */
549 
550  if (matchmap == NULL)
551  gnc_imap_destroy (tmp_map);
552 
553  return result;
554 }
555 
560 static void
561 matchmap_store_destination (GncImportMatchMap *matchmap,
562  GNCImportTransInfo *trans_info,
563  gboolean use_match)
564 {
565  GncImportMatchMap *tmp_matchmap = NULL;
566  Account *dest;
567  const char *descr, *memo;
568  GList *tokens;
569  gboolean useBayes;
570 
571  g_assert (trans_info);
572 
573  /* This will store the destination account of the selected match if
574  the reconcile match selected has only two splits. Good idea
575  Christian! */
576  dest = ((use_match) ?
580  (gnc_import_TransInfo_get_selected_match (trans_info)))) :
581  gnc_import_TransInfo_get_destacc (trans_info));
582  if (dest == NULL)
583  return;
584 
585  tmp_matchmap = ((matchmap != NULL) ?
586  matchmap :
589  (gnc_import_TransInfo_get_fsplit (trans_info))));
590 
591  /* see what matching system we are currently using */
592  useBayes = gnc_prefs_get_bool (GNC_PREFS_GROUP_IMPORT, GNC_PREF_USE_BAYES);
593  if (useBayes)
594  {
595  /* tokenize this transaction */
596  tokens = TransactionGetTokens(trans_info);
597 
598  /* add the tokens to the imap with the given destination account */
599  gnc_imap_add_account_bayes(tmp_matchmap, tokens, dest);
600 
601  }
602  else
603  {
604  /* old matching system */
606  (gnc_import_TransInfo_get_trans (trans_info));
607  if (descr && (strlen (descr) > 0))
608  gnc_imap_add_account (tmp_matchmap,
609  GNCIMPORT_DESC,
610  descr,
611  dest);
612  memo = xaccSplitGetMemo
613  (gnc_import_TransInfo_get_fsplit (trans_info));
614  if (memo && (strlen (memo) > 0))
615  gnc_imap_add_account (tmp_matchmap,
616  GNCIMPORT_MEMO,
617  memo,
618  dest);
619  } /* if(useBayes) */
620 
621  if (matchmap == NULL)
622  gnc_imap_destroy (tmp_matchmap);
623 }
624 
625 
626 
629 static void split_find_match (GNCImportTransInfo * trans_info,
630  Split * split,
631  gint display_threshold,
632  double fuzzy_amount_difference)
633 {
634  /* DEBUG("Begin"); */
635 
636  /*Ignore the split if the transaction is open for edit, meaning it
637  was just downloaded. */
638  if (xaccTransIsOpen(xaccSplitGetParent(split)) == FALSE)
639  {
640  GNCImportMatchInfo * match_info;
641  gint prob = 0;
642  gboolean update_proposed;
643  double downloaded_split_amount, match_split_amount;
644  time64 match_time, download_time;
645  int datediff_day;
646  Transaction *new_trans = gnc_import_TransInfo_get_trans (trans_info);
647  Split *new_trans_fsplit = gnc_import_TransInfo_get_fsplit (trans_info);
648 
649  /* Matching heuristics */
650 
651  /* Amount heuristics */
652  downloaded_split_amount =
653  gnc_numeric_to_double (xaccSplitGetAmount(new_trans_fsplit));
654  /*DEBUG(" downloaded_split_amount=%f", downloaded_split_amount);*/
655  match_split_amount = gnc_numeric_to_double(xaccSplitGetAmount(split));
656  /*DEBUG(" match_split_amount=%f", match_split_amount);*/
657  if (fabs(downloaded_split_amount - match_split_amount) < 1e-6)
658  /* bug#347791: Double type shouldn't be compared for exact
659  equality, so we're using fabs() instead. */
660  /*if (gnc_numeric_equal(xaccSplitGetAmount
661  (new_trans_fsplit),
662  xaccSplitGetAmount(split)))
663  -- gnc_numeric_equal is an expensive function call */
664  {
665  prob = prob + 3;
666  /*DEBUG("heuristics: probability + 3 (amount)");*/
667  }
668  else if (fabs (downloaded_split_amount - match_split_amount) <=
669  fuzzy_amount_difference)
670  {
671  /* ATM fees are sometimes added directly in the transaction.
672  So you withdraw 100$ and get charged 101,25$ in the same
673  transaction */
674  prob = prob + 2;
675  /*DEBUG("heuristics: probability + 2 (amount)");*/
676  }
677  else
678  {
679  /* If a transaction's amount doesn't match within the
680  threshold, it's very unlikely to be the same transaction
681  so we give it an extra -5 penality */
682  prob = prob - 5;
683  /* DEBUG("heuristics: probability - 1 (amount)"); */
684  }
685 
686  /* Date heuristics */
687  match_time = xaccTransGetDate (xaccSplitGetParent (split));
688  download_time = xaccTransGetDate (new_trans);
689  datediff_day = labs(match_time - download_time) / 86400;
690  /* Sorry, there are not really functions around at all that
691  provide for less hacky calculation of days of date
692  differences. Whatever. On the other hand, the difference
693  calculation itself will work regardless of month/year
694  turnarounds. */
695  /*DEBUG("diff day %d", datediff_day);*/
696  if (datediff_day == 0)
697  {
698  prob = prob + 3;
699  /*DEBUG("heuristics: probability + 3 (date)");*/
700  }
701  else if (datediff_day <= MATCH_DATE_THRESHOLD)
702  {
703  prob = prob + 2;
704  /*DEBUG("heuristics: probability + 2 (date)");*/
705  }
706  else if (datediff_day > MATCH_DATE_NOT_THRESHOLD)
707  {
708  /* Extra penalty if that split lies awfully far away from
709  the given one. */
710  prob = prob - 5;
711  /*DEBUG("heuristics: probability - 5 (date)"); */
712  /* Changed 2005-02-21: Revert the hard-limiting behaviour
713  back to the previous large penalty. (Changed 2004-11-27:
714  The penalty is so high that we can forget about this
715  split anyway and skip the rest of the tests.) */
716  }
717 
718  /* Check if date and amount are identical */
719  update_proposed = (prob < 6);
720 
721  /* Check number heuristics */
722  {
723  const char *new_trans_str = gnc_get_num_action(new_trans, new_trans_fsplit);
724  if (new_trans_str && strlen(new_trans_str) != 0)
725  {
726  long new_trans_number, split_number;
727  const gchar *split_str;
728  char *endptr;
729  gboolean conversion_ok = TRUE;
730 
731  /* To distinguish success/failure after strtol call */
732  errno = 0;
733  new_trans_number = strtol(new_trans_str, &endptr, 10);
734  /* Possible addressed problems: over/underflow, only non
735  numbers on string and string empty */
736  if (errno || endptr == new_trans_str)
737  conversion_ok = FALSE;
738 
739  split_str = gnc_get_num_action (xaccSplitGetParent (split), split);
740  errno = 0;
741  split_number = strtol(split_str, &endptr, 10);
742  if (errno || endptr == split_str)
743  conversion_ok = FALSE;
744 
745  if ( (conversion_ok && (split_number == new_trans_number)) ||
746  (g_strcmp0(new_trans_str, split_str) == 0) )
747  {
748  /* An exact match of the Check number gives a +4 */
749  prob += 4;
750  /*DEBUG("heuristics: probability + 4 (Check number)");*/
751  }
752  else if (strlen(new_trans_str) > 0 && strlen(split_str) > 0)
753  {
754  /* If both number are not empty yet do not match, add a
755  little extra penality */
756  prob -= 2;
757  }
758  }
759  }
760 
761  /* Memo heuristics */
762  {
763  const char *memo = xaccSplitGetMemo(new_trans_fsplit);
764  if (memo && strlen(memo) != 0)
765  {
766  if (safe_strcasecmp(memo, xaccSplitGetMemo(split)) == 0)
767  {
768  /* An exact match of memo gives a +2 */
769  prob = prob + 2;
770  /* DEBUG("heuristics: probability + 2 (memo)"); */
771  }
772  else if ((strncasecmp(memo, xaccSplitGetMemo(split),
773  strlen(xaccSplitGetMemo(split)) / 2)
774  == 0))
775  {
776  /* Very primitive fuzzy match worth +1. This matches the
777  first 50% of the strings to skip annoying transaction
778  number some banks seem to include in the memo but someone
779  should write something more sophisticated */
780  prob = prob + 1;
781  /*DEBUG("heuristics: probability + 1 (memo)"); */
782  }
783  }
784  }
785 
786  /* Description heuristics */
787  {
788  const char *descr = xaccTransGetDescription(new_trans);
789  if (descr && strlen(descr) != 0)
790  {
791  if (safe_strcasecmp(descr,
793  == 0)
794  {
795  /*An exact match of Description gives a +2 */
796  prob = prob + 2;
797  /*DEBUG("heuristics: probability + 2 (description)");*/
798  }
799  else if ((strncasecmp(descr,
801  strlen(xaccTransGetDescription (new_trans)) / 2)
802  == 0))
803  {
804  /* Very primitive fuzzy match worth +1. This matches the
805  first 50% of the strings to skip annoying transaction
806  number some banks seem to include in the memo but someone
807  should write something more sophisticated */
808  prob = prob + 1;
809  /*DEBUG("heuristics: probability + 1 (description)"); */
810  }
811  }
812  }
813 
814  /* Is the probability high enough? Otherwise do nothing and return. */
815  if (prob < display_threshold)
816  {
817  return;
818  }
819 
820  /* The probability is high enough, so allocate an object
821  here. Allocating it only when it's actually being used is
822  probably quite some performance gain. */
823  match_info = g_new0(GNCImportMatchInfo, 1);
824 
825  match_info->probability = prob;
826  match_info->update_proposed = update_proposed;
827  match_info->split = split;
828  match_info->trans = xaccSplitGetParent(split);
829 
830 
831  /* Append that to the list. Do not use g_list_append because
832  it is slow. The list is sorted afterwards anyway. */
833  trans_info->match_list =
834  g_list_prepend(trans_info->match_list,
835  match_info);
836  }
837 }/* end split_find_match */
838 
839 
843  gint process_threshold,
844  double fuzzy_amount_difference,
845  gint match_date_hardlimit)
846 {
847  GList * list_element;
848  Query *query = qof_query_create_for(GNC_ID_SPLIT);
849  g_assert (trans_info);
850 
851  /* Get list of splits of the originating account. */
852  {
853  /* We used to traverse *all* splits of the account by using
854  xaccAccountGetSplitList, which is a bad idea because 90% of these
855  splits are outside the date range that is interesting. We should
856  rather use a query according to the date region, which is
857  implemented here.
858  */
859  Account *importaccount =
861  time64 download_time = xaccTransGetDate (gnc_import_TransInfo_get_trans (trans_info));
862 
863  qof_query_set_book (query, gnc_get_current_book());
864  xaccQueryAddSingleAccountMatch (query, importaccount,
865  QOF_QUERY_AND);
866  xaccQueryAddDateMatchTT (query,
867  TRUE, download_time - match_date_hardlimit * 86400,
868  TRUE, download_time + match_date_hardlimit * 86400,
869  QOF_QUERY_AND);
870  list_element = qof_query_run (query);
871  /* Sigh. Doesnt help too much. We still create and run one query
872  for each imported transaction. Maybe it would improve
873  performance further if there is one single (master-)query at
874  the beginning, matching the full date range and all accounts in
875  question. However, this doesnt quite work because this function
876  here is called from each gnc_gen_trans_list_add_trans(), which
877  is called one at a time. Therefore the whole importer would
878  have to change its behaviour: Accept the imported txns via
879  gnc_gen_trans_list_add_trans(), and only when
880  gnc_gen_trans_list_run() is called, then calculate all the
881  different match candidates. That's too much work for now.
882  */
883  }
884 
885  /* Traverse that list, calling split_find_match on each one. Note
886  that xaccAccountForEachSplit is declared in Account.h but
887  implemented nowhere :-( */
888  while (list_element != NULL)
889  {
890  split_find_match (trans_info, list_element->data,
891  process_threshold, fuzzy_amount_difference);
892  list_element = g_list_next (list_element);
893  }
894 
895  qof_query_destroy (query);
896 }
897 
898 
899 /***********************************************************************
900  */
901 
904 gboolean
906  GNCImportTransInfo *trans_info)
907 {
908  Split * other_split;
909  gnc_numeric imbalance_value;
910 
911  /* DEBUG("Begin"); */
912 
913  g_assert (trans_info);
914  /*DEBUG("Iteration %d, action %d, split %s", i,
915  trans_info->action,
916  xaccTransGetDescription (gnc_import_TransInfo_get_trans
917  (trans_info)))*/
918  switch (gnc_import_TransInfo_get_action (trans_info))
919  {
920  case GNCImport_SKIP:
921  return FALSE;
922  case GNCImport_ADD:
923  /* Transaction gets imported. */
924 
925  /* Is the transaction not balanced and there is a non-NULL destination account? */
926  if (gnc_import_TransInfo_is_balanced(trans_info) == FALSE
927  && gnc_import_TransInfo_get_destacc(trans_info) != NULL)
928  {
929  /* Create the 'other' split. */
930  Split *split =
932  (gnc_account_get_book
933  (gnc_import_TransInfo_get_destacc (trans_info)));
935  (gnc_import_TransInfo_get_trans (trans_info), split);
937  (gnc_import_TransInfo_get_destacc (trans_info), split);
938  /*xaccSplitSetBaseValue
939  (split,
940  gnc_numeric_neg(xaccTransGetImbalance
941  (gnc_import_TransInfo_get_trans (trans_info))),
942  xaccTransGetCurrency
943  (gnc_import_TransInfo_get_trans (trans_info)));*/
944  {
945  /* This is a quick workaround for the bug described in
946  http://gnucash.org/pipermail/gnucash-devel/2003-August/009982.html
947  Assume that importers won't create transactions involving two or more
948  currencies so we can use xaccTransGetImbalanceValue. */
949  imbalance_value =
951  (gnc_import_TransInfo_get_trans (trans_info)));
952  xaccSplitSetValue (split, imbalance_value);
953  xaccSplitSetAmount (split, imbalance_value);
954  }
955  /*xaccSplitSetMemo (split, _("Auto-Balance split"));
956  -- disabled due to popular request */
957  }
958 
960  /*Set reconcile date to today*/
962  gnc_time (NULL));
963  /* Done editing. */
965  return TRUE;
966  case GNCImport_UPDATE:
967  {
968  GNCImportMatchInfo *selected_match =
970 
971  /* If there is no selection, ignore this transaction. */
972  if (!selected_match)
973  {
974  PWARN("No matching translaction to be cleared was chosen. Imported transaction will be ignored.");
975  break;
976  }
977 
978  /* Transaction gets not imported but the matching one gets
979  updated and reconciled. */
980  if (gnc_import_MatchInfo_get_split(selected_match) == NULL)
981  {
982  PERR("The split I am trying to update and reconcile is NULL, shouldn't happen!");
983  }
984  else
985  {
986  /* Update and reconcile the matching transaction */
987  /*DEBUG("BeginEdit selected_match")*/
988  xaccTransBeginEdit(selected_match->trans);
989 
990  xaccTransSetDatePostedSecsNormalized(selected_match->trans,
992  gnc_import_TransInfo_get_fsplit(trans_info))));
993 
994  xaccSplitSetAmount(selected_match->split,
996  gnc_import_TransInfo_get_fsplit(trans_info)));
997  xaccSplitSetValue(selected_match->split,
999  gnc_import_TransInfo_get_fsplit(trans_info)));
1000 
1001  imbalance_value = xaccTransGetImbalanceValue(
1002  gnc_import_TransInfo_get_trans(trans_info));
1003  other_split = xaccSplitGetOtherSplit(selected_match->split);
1004  if (!gnc_numeric_zero_p(imbalance_value) && other_split)
1005  {
1006  if (xaccSplitGetReconcile(other_split) == NREC)
1007  {
1008  imbalance_value = gnc_numeric_neg(imbalance_value);
1009  xaccSplitSetValue(other_split, imbalance_value);
1010  xaccSplitSetAmount(other_split, imbalance_value);
1011  }
1012  /* else GC will automatically insert a split to equity
1013  to balance the transaction */
1014  }
1015 
1016  xaccTransSetDescription(selected_match->trans,
1018  gnc_import_TransInfo_get_trans(trans_info)));
1019 
1020  if (xaccSplitGetReconcile(selected_match->split) == NREC)
1021  {
1022  xaccSplitSetReconcile(selected_match->split, CREC);
1023  }
1024 
1025  /* Set reconcile date to today */
1026  xaccSplitSetDateReconciledSecs(selected_match->split, gnc_time (NULL));
1027 
1028  /* Copy the online id to the reconciled transaction, so
1029  the match will be remembered */
1030  if (gnc_import_split_has_online_id(trans_info->first_split))
1031  {
1032  gnc_import_set_split_online_id(selected_match->split,
1033  gnc_import_get_split_online_id(trans_info->first_split));
1034  }
1035 
1036  /* Done editing. */
1037  /*DEBUG("CommitEdit selected_match")*/
1038  xaccTransCommitEdit(selected_match->trans);
1039 
1040  /* Store the mapping to the other account in the MatchMap. */
1041  matchmap_store_destination(matchmap, trans_info, TRUE);
1042 
1043  /* Erase the downloaded transaction */
1044  xaccTransDestroy(trans_info->trans);
1045  /*DEBUG("CommitEdit trans")*/
1046  xaccTransCommitEdit(trans_info->trans);
1047  /* Very important: Make sure the freed transaction is not freed again! */
1048  trans_info->trans = NULL;
1049  }
1050  }
1051  return TRUE;
1052  case GNCImport_CLEAR:
1053  {
1054  GNCImportMatchInfo *selected_match =
1056 
1057  /* If there is no selection, ignore this transaction. */
1058  if (!selected_match)
1059  {
1060  PWARN("No matching translaction to be cleared was chosen. Imported transaction will be ignored.");
1061  break;
1062  }
1063 
1064  /* Transaction gets not imported but the matching one gets
1065  reconciled. */
1066  if (gnc_import_MatchInfo_get_split (selected_match) == NULL)
1067  {
1068  PERR("The split I am trying to reconcile is NULL, shouldn't happen!");
1069  }
1070  else
1071  {
1072  /* Reconcile the matching transaction */
1073  /*DEBUG("BeginEdit selected_match")*/
1074  xaccTransBeginEdit(selected_match->trans);
1075 
1077  (selected_match->split) == NREC)
1079  (selected_match->split, CREC);
1080  /* Set reconcile date to today */
1082  (selected_match->split, gnc_time (NULL));
1083 
1084  /* Copy the online id to the reconciled transaction, so
1085  the match will be remembered */
1086  if (gnc_import_split_has_online_id(trans_info->first_split))
1087  gnc_import_set_split_online_id
1088  (selected_match->split,
1089  gnc_import_get_split_online_id(trans_info->first_split));
1090 
1091  /* Done editing. */
1092  /*DEBUG("CommitEdit selected_match")*/
1094  (selected_match->trans);
1095 
1096  /* Store the mapping to the other account in the MatchMap. */
1097  matchmap_store_destination (matchmap, trans_info, TRUE);
1098 
1099  /* Erase the downloaded transaction */
1100  xaccTransDestroy(trans_info->trans);
1101  /*DEBUG("CommitEdit trans")*/
1102  xaccTransCommitEdit(trans_info->trans);
1103  /* Very important: Make sure the freed transaction is not freed again! */
1104  trans_info->trans = NULL;
1105  }
1106  }
1107  return TRUE;
1108  default:
1109  DEBUG("Invalid GNCImportAction for this imported transaction.");
1110  break;
1111  }
1112  /*DEBUG("End");*/
1113  return FALSE;
1114 }
1115 
1116 /********************************************************************\
1117  * check_trans_online_id() Callback function used by
1118  * gnc_import_exists_online_id. Takes pointers to transaction and split,
1119  * returns 0 if their online_id kvp_frames do NOT match, or if the split
1120  * belongs to the transaction
1121 \********************************************************************/
1122 static gint check_trans_online_id(Transaction *trans1, void *user_data)
1123 {
1124  Account *account;
1125  Split *split1;
1126  Split *split2 = user_data;
1127  const gchar *online_id1;
1128  const gchar *online_id2;
1129 
1130  account = xaccSplitGetAccount(split2);
1131  split1 = xaccTransFindSplitByAccount(trans1, account);
1132  if (split1 == split2)
1133  return 0;
1134 
1135  /* hack - we really want to iterate over the _splits_ of the account
1136  instead of the transactions */
1137  g_assert(split1 != NULL);
1138 
1139  if (gnc_import_split_has_online_id(split1))
1140  online_id1 = gnc_import_get_split_online_id(split1);
1141  else
1142  online_id1 = gnc_import_get_trans_online_id(trans1);
1143 
1144  online_id2 = gnc_import_get_split_online_id(split2);
1145 
1146  if ((online_id1 == NULL) ||
1147  (online_id2 == NULL) ||
1148  (strcmp(online_id1, online_id2) != 0))
1149  {
1150  return 0;
1151  }
1152  else
1153  {
1154  /*printf("test_trans_online_id(): Duplicate found\n");*/
1155  return 1;
1156  }
1157 }
1158 
1162 {
1163  gboolean online_id_exists = FALSE;
1164  Account *dest_acct;
1165  Split *source_split;
1166 
1167  /* Look for an online_id in the first split */
1168  source_split = xaccTransGetSplit(trans, 0);
1169  g_assert(source_split);
1170 
1171  /* DEBUG("%s%d%s","Checking split ",i," for duplicates"); */
1172  dest_acct = xaccSplitGetAccount(source_split);
1173  online_id_exists = xaccAccountForEachTransaction(dest_acct,
1174  check_trans_online_id,
1175  source_split);
1176 
1177  /* If it does, abort the process for this transaction, since it is
1178  already in the system. */
1179  if (online_id_exists == TRUE)
1180  {
1181  DEBUG("%s", "Transaction with same online ID exists, destroying current transaction");
1182  xaccTransDestroy(trans);
1183  xaccTransCommitEdit(trans);
1184  }
1185  return online_id_exists;
1186 }
1187 
1188 
1189 /* ******************************************************************
1190  */
1191 
1195 {
1196  GNCImportTransInfo *transaction_info;
1197  Split *split;
1198  g_assert (trans);
1199 
1200  transaction_info = g_new0(GNCImportTransInfo, 1);
1201 
1202  transaction_info->trans = trans;
1203  /* Only use first split, the source split */
1204  split = xaccTransGetSplit(trans, 0);
1205  g_assert(split);
1206  transaction_info->first_split = split;
1207 
1208  /* Try to find a previously selected destination account
1209  string match for the ADD action */
1210  gnc_import_TransInfo_set_destacc (transaction_info,
1211  matchmap_find_destination (matchmap, transaction_info),
1212  FALSE);
1213  return transaction_info;
1214 }
1215 
1216 
1218 static gint compare_probability (gconstpointer a,
1219  gconstpointer b)
1220 {
1221  return(((GNCImportMatchInfo *)b)->probability -
1222  ((GNCImportMatchInfo *)a)->probability);
1223 }
1224 
1229 void
1231  GNCImportSettings *settings)
1232 {
1233  GNCImportMatchInfo * best_match = NULL;
1234  g_assert (trans_info);
1235 
1236 
1237  /* Find all split matches in originating account. */
1238  gnc_import_find_split_matches(trans_info,
1242 
1243  if (trans_info->match_list != NULL)
1244  {
1245  trans_info->match_list = g_list_sort(trans_info->match_list,
1246  compare_probability);
1247  best_match = g_list_nth_data(trans_info->match_list, 0);
1249  best_match,
1250  FALSE);
1251  if (best_match != NULL &&
1252  best_match->probability >= gnc_import_Settings_get_clear_threshold(settings))
1253  {
1254  trans_info->action = GNCImport_CLEAR;
1255  trans_info->selected_match_info = best_match;
1256  }
1257  else if (best_match == NULL ||
1258  best_match->probability <= gnc_import_Settings_get_add_threshold(settings))
1259  {
1260  trans_info->action = GNCImport_ADD;
1261  }
1263  {
1264  trans_info->action = GNCImport_SKIP;
1265  }
1267  {
1268  trans_info->action = GNCImport_UPDATE;
1269  }
1270  else
1271  {
1272  trans_info->action = GNCImport_ADD;
1273  }
1274  }
1275  else
1276  {
1277  trans_info->action = GNCImport_ADD;
1278  }
1279  if (best_match &&
1280  trans_info->action == GNCImport_CLEAR &&
1282  {
1283  if (best_match->update_proposed)
1284  {
1285  trans_info->action = GNCImport_UPDATE;
1286  }
1287  }
1288 
1289  trans_info->previous_action = trans_info->action;
1290 }
1291 
1292 
1293 /* Try to automatch a transaction to a destination account if the */
1294 /* transaction hasn't already been manually assigned to another account */
1295 gboolean
1297  GncImportMatchMap *matchmap)
1298 {
1299  Account *orig_destacc;
1300  Account *new_destacc = NULL;
1301  g_assert(transaction_info);
1302 
1303  orig_destacc = gnc_import_TransInfo_get_destacc(transaction_info);
1304 
1305  /* if we haven't manually selected a destination account for this transaction */
1306  if (gnc_import_TransInfo_get_destacc_selected_manually(transaction_info) == FALSE)
1307  {
1308  /* Try to find the destination account for this transaction based on prior ones */
1309  new_destacc = matchmap_find_destination(matchmap, transaction_info);
1310  gnc_import_TransInfo_set_destacc(transaction_info, new_destacc, FALSE);
1311  }
1312  else
1313  {
1314  new_destacc = orig_destacc;
1315  }
1316 
1317  /* account has changed */
1318  if (new_destacc != orig_destacc)
1319  {
1320  return TRUE;
1321  }
1322  else /* account is the same */
1323  {
1324  return FALSE;
1325  }
1326 }
1327 
1328 
void xaccSplitSetValue(Split *s, gnc_numeric amt)
Definition: Split.c:1294
gint gnc_import_Settings_get_match_date_hardlimit(const GNCImportSettings *s)
gint xaccAccountForEachTransaction(const Account *acc, TransactionCallback proc, void *data)
Definition: Account.c:4996
GNCImportTransInfo * gnc_import_TransInfo_new(Transaction *trans, GncImportMatchMap *matchmap)
#define xaccTransAppendSplit(t, s)
Definition: Transaction.h:357
void xaccTransSetDatePostedSecsNormalized(Transaction *trans, time64 time)
Definition: Transaction.c:1920
gint gnc_import_Settings_get_clear_threshold(GNCImportSettings *settings)
Split * xaccTransGetSplit(const Transaction *trans, int i)
Definition: Transaction.c:2144
time64 xaccTransGetDate(const Transaction *trans)
Definition: Transaction.c:2215
gboolean xaccTransIsOpen(const Transaction *trans)
Definition: Transaction.c:1819
utility functions for the GnuCash UI
void gnc_import_TransInfo_delete(GNCImportTransInfo *info)
gnc_numeric gnc_numeric_neg(gnc_numeric a)
gint gnc_import_Settings_get_display_threshold(GNCImportSettings *settings)
void gnc_import_TransInfo_set_ref_id(GNCImportTransInfo *info, guint32 ref_id)
#define DEBUG(format, args...)
Definition: qoflog.h:255
void gnc_imap_add_account_bayes(GncImportMatchMap *imap, GList *tokens, Account *acc)
Definition: Account.c:5418
#define GNC_PREFS_GROUP_IMPORT
Generic importer backend interface.
char xaccSplitGetReconcile(const Split *split)
Definition: Split.c:1980
gint safe_strcasecmp(const gchar *da, const gchar *db)
void gnc_import_TransInfo_set_selected_match(GNCImportTransInfo *info, GNCImportMatchInfo *match, gboolean selected_manually)
Split * gnc_import_TransInfo_get_fsplit(const GNCImportTransInfo *info)
void xaccTransSetDescription(Transaction *trans, const char *desc)
Definition: Transaction.c:2085
GdkPixbuf * gen_probability_pixbuf(gint score_original, GNCImportSettings *settings, GtkWidget *widget)
Transaction * gnc_import_TransInfo_get_trans(const GNCImportTransInfo *info)
gboolean gnc_numeric_zero_p(gnc_numeric a)
void xaccSplitSetReconcile(Split *split, char recn)
Definition: Split.c:1826
Transaction * xaccSplitGetParent(const Split *split)
Definition: Split.c:1903
void gnc_import_TransInfo_set_destacc(GNCImportTransInfo *info, Account *acc, gboolean selected_manually)
Account * gnc_imap_find_account_bayes(GncImportMatchMap *imap, GList *tokens)
Definition: Account.c:5238
guint32 gnc_import_TransInfo_get_ref_id(const GNCImportTransInfo *info)
#define PERR(format, args...)
Definition: qoflog.h:237
void gnc_import_TransInfo_init_matches(GNCImportTransInfo *trans_info, GNCImportSettings *settings)
GNCImportAction gnc_import_TransInfo_get_action(const GNCImportTransInfo *info)
void xaccTransDestroy(Transaction *trans)
Definition: Transaction.c:1402
void gnc_tm_free(struct tm *time)
free a struct tm* created with gnc_localtime() or gnc_gmtime()
#define PWARN(format, args...)
Definition: qoflog.h:243
Split * gnc_import_MatchInfo_get_split(const GNCImportMatchInfo *info)
gdouble gnc_numeric_to_double(gnc_numeric n)
gint gnc_import_MatchInfo_get_probability(const GNCImportMatchInfo *info)
void xaccSplitSetAmount(Split *s, gnc_numeric amt)
Definition: Split.c:1258
Account handling public routines.
void qof_query_destroy(QofQuery *q)
gnc_numeric xaccTransGetImbalanceValue(const Transaction *trans)
Definition: Transaction.c:1036
void qof_query_set_book(QofQuery *q, QofBook *book)
gboolean gnc_import_process_trans_item(GncImportMatchMap *matchmap, GNCImportTransInfo *trans_info)
void gnc_import_TransInfo_set_action(GNCImportTransInfo *info, GNCImportAction action)
gboolean gnc_import_TransInfo_refresh_destacc(GNCImportTransInfo *transaction_info, GncImportMatchMap *matchmap)
double gnc_import_Settings_get_fuzzy_amount(GNCImportSettings *settings)
const char * xaccTransGetDescription(const Transaction *trans)
Definition: Transaction.c:2184
gsize qof_strftime(gchar *buf, gsize max, const gchar *format, const struct tm *tm)
void xaccTransCommitEdit(Transaction *trans)
Definition: Transaction.c:1579
void xaccTransBeginEdit(Transaction *trans)
Definition: Transaction.c:1380
gboolean gnc_import_exists_online_id(Transaction *trans)
All type declarations for the whole Gnucash engine.
#define CREC
Definition: Split.h:67
Split * xaccMallocSplit(QofBook *book)
Definition: Split.c:582
void gnc_import_find_split_matches(GNCImportTransInfo *trans_info, gint process_threshold, double fuzzy_amount_difference, gint match_date_hardlimit)
Generic api to store and retrieve preferences.
void xaccSplitSetDateReconciledSecs(Split *split, time64 secs)
Definition: Split.c:1852
gboolean gnc_import_Settings_get_action_update_enabled(GNCImportSettings *settings)
gboolean gnc_import_TransInfo_get_match_selected_manually(const GNCImportTransInfo *info)
GList * qof_query_run(QofQuery *query)
Definition: SplitP.h:71
gnc_numeric xaccSplitGetValue(const Split *split)
Definition: Split.c:1993
Account * xaccSplitGetAccount(const Split *s)
Definition: Split.c:968
#define xaccAccountInsertSplit(acc, s)
Definition: Account.h:972
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Definition: gnc-prefs.c:196
Split * xaccSplitGetOtherSplit(const Split *split)
Definition: Split.c:2086
Utility functions for writing import modules.
Account * gnc_import_TransInfo_get_destacc(const GNCImportTransInfo *info)
struct tm * gnc_gmtime(const time64 *secs)
fill out a time struct from a 64-bit time value
time64 gnc_time(time64 *tbuf)
get the current local time
const char * xaccSplitGetMemo(const Split *split)
Definition: Split.c:1968
GList * gnc_import_TransInfo_get_match_list(const GNCImportTransInfo *info)
gint64 time64
Definition: gnc-date.h:83
GNCImportMatchInfo * gnc_import_TransInfo_get_selected_match(const GNCImportTransInfo *info)
GncImportMatchMap * gnc_account_create_imap(Account *acc)
Definition: Account.c:5035
gboolean gnc_import_Settings_get_action_skip_enabled(GNCImportSettings *settings)
gboolean gnc_import_TransInfo_is_balanced(const GNCImportTransInfo *info)
gint gnc_import_Settings_get_add_threshold(GNCImportSettings *settings)
const gchar * QofLogModule
Definition: qofid.h:89
#define NREC
Definition: Split.h:70
gnc_numeric xaccSplitGetAmount(const Split *split)
Definition: Split.c:1987
gboolean gnc_import_TransInfo_get_destacc_selected_manually(const GNCImportTransInfo *info)