GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
gnc-ab-utils.c
1 /*
2  * gnc-ab-utils.c --
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of
7  * the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, contact:
16  *
17  * Free Software Foundation Voice: +1-617-542-5942
18  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
19  * Boston, MA 02110-1301, USA [email protected]
20  */
21 
30 #include "config.h"
31 
32 #include <glib/gi18n.h>
33 #include <gwenhywfar/gwenhywfar.h>
34 #include <aqbanking/banking.h>
35 
36 #include "window-reconcile.h"
37 #include "Transaction.h"
38 #include "dialog-ab-trans.h"
39 #include "gnc-ab-kvp.h"
40 #include "gnc-ab-utils.h"
41 #include "gnc-glib-utils.h"
42 #include "gnc-gwen-gui.h"
43 #include "gnc-prefs.h"
44 #include "gnc-ui.h"
45 #include "import-account-matcher.h"
46 #include "import-main-matcher.h"
47 #include "import-utilities.h"
48 #include "qof.h"
49 #include "engine-helpers.h"
50 
51 #ifdef AQBANKING_VERSION_5_PLUS
52 # include <aqbanking/abgui.h>
53 #endif /* AQBANKING_VERSION_5_PLUS */
54 
55 /* This static indicates the debugging module that this .o belongs to. */
56 G_GNUC_UNUSED static QofLogModule log_module = G_LOG_DOMAIN;
57 
58 /* Global variables for AB_BANKING caching. */
59 static AB_BANKING *gnc_AB_BANKING = NULL;
60 static gint gnc_AB_BANKING_refcount = 0;
61 
62 static gpointer join_ab_strings_cb(const gchar *str, gpointer user_data);
63 static Account *gnc_ab_accinfo_to_gnc_acc(
64  AB_IMEXPORTER_ACCOUNTINFO *account_info);
65 static const AB_TRANSACTION *txn_transaction_cb(
66  const AB_TRANSACTION *element, gpointer user_data);
67 static AB_IMEXPORTER_ACCOUNTINFO *txn_accountinfo_cb(
68  AB_IMEXPORTER_ACCOUNTINFO *element, gpointer user_data);
69 static AB_IMEXPORTER_ACCOUNTINFO *bal_accountinfo_cb(
70  AB_IMEXPORTER_ACCOUNTINFO *element, gpointer user_data);
71 
73 {
74  guint awaiting;
75  gboolean txn_found;
76  Account *gnc_acc;
77  AB_ACCOUNT *ab_acc;
78  gboolean execute_txns;
79  AB_BANKING *api;
80  GtkWidget *parent;
81  AB_JOB_LIST2 *job_list;
82  GNCImportMainMatcher *generic_importer;
83  GData *tmp_job_list;
84 };
85 
86 void
88 {
89  gchar* gwen_logging = g_strdup(g_getenv("GWEN_LOGLEVEL"));
90  gchar* aqb_logging = g_strdup(g_getenv("AQBANKING_LOGLEVEL"));
91 
92  /* Initialize gwen library */
93  GWEN_Init();
94 
95  /* Initialize gwen logging */
96  if (gnc_prefs_get_bool(GNC_PREFS_GROUP_AQBANKING, GNC_PREF_VERBOSE_DEBUG))
97  {
98  if (!gwen_logging)
99  {
100  GWEN_Logger_SetLevel(NULL, GWEN_LoggerLevel_Info);
101  GWEN_Logger_SetLevel(GWEN_LOGDOMAIN, GWEN_LoggerLevel_Info);
102  }
103  if (!aqb_logging)
104  GWEN_Logger_SetLevel(AQBANKING_LOGDOMAIN, GWEN_LoggerLevel_Debug);
105  }
106  else
107  {
108  if (!gwen_logging)
109  {
110  GWEN_Logger_SetLevel(NULL, GWEN_LoggerLevel_Error);
111  GWEN_Logger_SetLevel(GWEN_LOGDOMAIN, GWEN_LoggerLevel_Error);
112  }
113  if (!aqb_logging)
114  GWEN_Logger_SetLevel(AQBANKING_LOGDOMAIN, GWEN_LoggerLevel_Warning);
115  }
116  g_free(gwen_logging);
117  g_free(aqb_logging);
119 }
120 
121 void
123 {
124  /* Shutdown the GWEN_GUIs */
126  GWEN_Logger_SetLevel(NULL, GWEN_LoggerLevel_Error);
127  GWEN_Logger_SetLevel(GWEN_LOGDOMAIN, GWEN_LoggerLevel_Warning);
128  GWEN_Logger_SetLevel(AQBANKING_LOGDOMAIN, GWEN_LoggerLevel_Warning);
129 
130  /* Finalize gwen library */
131  GWEN_Fini();
132 }
133 
134 static GWEN_GUI *gnc_gwengui_extended_by_ABBanking;
135 
136 AB_BANKING *
138 {
139  AB_BANKING *api;
140 
141  if (gnc_AB_BANKING)
142  {
143  /* API cached. */
144  api = gnc_AB_BANKING;
145 
146  /* Init the API again. */
147  if (gnc_AB_BANKING_refcount == 0)
148  g_return_val_if_fail(AB_Banking_Init(api) == 0, NULL);
149 
150  }
151  else
152  {
153  api = AB_Banking_new("gnucash", NULL, 0);
154  g_return_val_if_fail(api, NULL);
155 
156 #ifdef AQBANKING_VERSION_4_PLUS
157  /* Check for config migration */
158  if (AB_Banking_HasConf4(api
159 # ifndef AQBANKING_VERSION_5_PLUS
160  , 0
161 # endif
162  ) != 0)
163  {
164  if (AB_Banking_HasConf3(api
165 # ifndef AQBANKING_VERSION_5_PLUS
166  , 0
167 # endif
168  ) == 0)
169  {
170  g_message("gnc_AB_BANKING_new: importing aqbanking3 configuration\n");
171  if (AB_Banking_ImportConf3(api
172 # ifndef AQBANKING_VERSION_5_PLUS
173  , 0
174 # endif
175  ) < 0)
176  {
177  g_message("gnc_AB_BANKING_new: unable to import aqbanking3 configuration\n");
178  }
179  }
180  else if (AB_Banking_HasConf2(api
181 # ifndef AQBANKING_VERSION_5_PLUS
182  , 0
183 # endif
184  ) == 0)
185  {
186  g_message("gnc_AB_BANKING_new: importing aqbanking2 configuration\n");
187  if (AB_Banking_ImportConf2(api
188 # ifndef AQBANKING_VERSION_5_PLUS
189  , 0
190 # endif
191  ) < 0)
192  {
193  g_message("gnc_AB_BANKING_new: unable to import aqbanking2 configuration\n");
194  }
195  }
196  }
197 #endif /* AQBANKING_VERSION_4_PLUS */
198 
199  /* Init the API */
200  g_return_val_if_fail(AB_Banking_Init(api) == 0, NULL);
201 
202 #ifdef AQBANKING_VERSION_5_PLUS
203  gnc_gwengui_extended_by_ABBanking = GWEN_Gui_GetGui();
204  AB_Gui_Extend(gnc_gwengui_extended_by_ABBanking, api);
205 #endif /* AQBANKING_VERSION_5_PLUS */
206 
207  /* Cache it */
208  gnc_AB_BANKING = api;
209  gnc_AB_BANKING_refcount = 0;
210  }
211 
212  gnc_AB_BANKING_refcount++;
213 
214  return api;
215 }
216 
217 void
218 gnc_AB_BANKING_delete(AB_BANKING *api)
219 {
220  if (!api)
221  api = gnc_AB_BANKING;
222 
223  if (api)
224  {
225  if (api == gnc_AB_BANKING)
226  {
227  gnc_AB_BANKING = NULL;
228  gnc_AB_BANKING_fini(api);
229  }
230 
231  AB_Banking_free(api);
232  }
233 }
234 
235 
236 gint
237 gnc_AB_BANKING_fini(AB_BANKING *api)
238 {
239  if (api == gnc_AB_BANKING)
240  {
241  if (--gnc_AB_BANKING_refcount == 0)
242  {
243 #ifdef AQBANKING_VERSION_5_PLUS
244  if (gnc_gwengui_extended_by_ABBanking)
245  AB_Gui_Unextend(gnc_gwengui_extended_by_ABBanking);
246  gnc_gwengui_extended_by_ABBanking = NULL;
247 #endif /* AQBANKING_VERSION_5_PLUS */
248  return AB_Banking_Fini(api);
249  }
250  }
251  else
252  {
253 #ifdef AQBANKING_VERSION_5_PLUS
254  if (gnc_gwengui_extended_by_ABBanking)
255  AB_Gui_Unextend(gnc_gwengui_extended_by_ABBanking);
256  gnc_gwengui_extended_by_ABBanking = NULL;
257 #endif /* AQBANKING_VERSION_5_PLUS */
258  return AB_Banking_Fini(api);
259  }
260  return 0;
261 }
262 
263 AB_ACCOUNT *
264 gnc_ab_get_ab_account(const AB_BANKING *api, Account *gnc_acc)
265 {
266  AB_ACCOUNT *ab_account = NULL;
267  const gchar *bankcode = NULL;
268  const gchar *accountid = NULL;
269  guint32 account_uid = 0;
270 
271  bankcode = gnc_ab_get_account_bankcode(gnc_acc);
272  accountid = gnc_ab_get_account_accountid(gnc_acc);
273  account_uid = gnc_ab_get_account_uid (gnc_acc);
274 
275  if (account_uid > 0)
276  {
277  ab_account = AB_Banking_GetAccount(api, account_uid);
278 
279  if (!ab_account && bankcode && *bankcode && accountid && *accountid)
280  {
281  g_message("gnc_ab_get_ab_account: No AB_ACCOUNT found for UID %d, "
282  "trying bank code\n", account_uid);
283  ab_account = AB_Banking_GetAccountByCodeAndNumber(api, bankcode,
284  accountid);
285  }
286  return ab_account;
287 
288  }
289  else if (bankcode && *bankcode && accountid && *accountid)
290  {
291  ab_account = AB_Banking_GetAccountByCodeAndNumber(api, bankcode,
292  accountid);
293  return ab_account;
294  }
295 
296  return NULL;
297 }
298 
299 gchar *
300 gnc_AB_VALUE_to_readable_string(const AB_VALUE *value)
301 {
302  if (value)
303  return g_strdup_printf("%.2f %s",
304  AB_Value_GetValueAsDouble(value),
305  AB_Value_GetCurrency(value));
306  else
307  return g_strdup_printf("%.2f", 0.0);
308 }
309 
314 static gpointer
315 join_ab_strings_cb(const gchar *str, gpointer user_data)
316 {
317  gchar **acc = user_data;
318  gchar *tmp;
319 
320  if (!str || !*str)
321  return NULL;
322 
323  tmp = g_strdup(str);
324  g_strstrip(tmp);
326 
327  if (*acc)
328  {
329  gchar *join = g_strjoin(" ", *acc, tmp, (gchar*) NULL);
330  g_free(*acc);
331  g_free(tmp);
332  *acc = join;
333  }
334  else
335  {
336  *acc = tmp;
337  }
338  return NULL;
339 }
340 
341 gchar *
342 gnc_ab_get_remote_name(const AB_TRANSACTION *ab_trans)
343 {
344  const GWEN_STRINGLIST *ab_remote_name;
345  gchar *gnc_other_name = NULL;
346 
347  g_return_val_if_fail(ab_trans, NULL);
348 
349  ab_remote_name = AB_Transaction_GetRemoteName(ab_trans);
350  if (ab_remote_name)
351  GWEN_StringList_ForEach(ab_remote_name, join_ab_strings_cb,
352  &gnc_other_name);
353 
354  if (!gnc_other_name || !*gnc_other_name)
355  {
356  g_free(gnc_other_name);
357  gnc_other_name = NULL;
358  }
359 
360  return gnc_other_name;
361 }
362 
363 gchar *
364 gnc_ab_get_purpose(const AB_TRANSACTION *ab_trans)
365 {
366  const GWEN_STRINGLIST *ab_purpose;
367  gchar *gnc_description = NULL;
368 
369  g_return_val_if_fail(ab_trans, g_strdup(""));
370 
371  ab_purpose = AB_Transaction_GetPurpose(ab_trans);
372  if (ab_purpose)
373  GWEN_StringList_ForEach(ab_purpose, join_ab_strings_cb,
374  &gnc_description);
375 
376  if (!gnc_description)
377  gnc_description = g_strdup("");
378 
379  return gnc_description;
380 }
381 
382 gchar *
383 gnc_ab_description_to_gnc(const AB_TRANSACTION *ab_trans)
384 {
385  /* Description */
386  gchar *description = gnc_ab_get_purpose(ab_trans);
387  gchar *other_name = gnc_ab_get_remote_name(ab_trans);
388  gchar *retval;
389 
390  if (other_name)
391  {
392  if (description && *description)
393  {
394  retval = g_strdup_printf("%s; %s", description, other_name);
395  }
396  else
397  {
398  retval = g_strdup(other_name);
399  }
400  }
401  else
402  {
403  if (description && *description)
404  {
405  retval = g_strdup(description);
406  }
407  else
408  {
409  retval = g_strdup(_("Unspecified"));
410  }
411  }
412  g_free(description);
413  g_free(other_name);
414 
415  return retval;
416 }
417 
418 gchar *
419 gnc_ab_memo_to_gnc(const AB_TRANSACTION *ab_trans)
420 {
421  const gchar *ab_remote_accountnumber =
422  AB_Transaction_GetRemoteAccountNumber(ab_trans);
423  const gchar *ab_remote_bankcode =
424  AB_Transaction_GetRemoteBankCode(ab_trans);
425 
426  gchar *ab_other_accountid;
427  gchar *ab_other_bankcode;
428 
429  gboolean have_accountid;
430  gboolean have_bankcode;
431 
432  gchar *retval;
433 
434  // For SEPA transactions, we need to ask for something different here
435  if (!ab_remote_accountnumber)
436  ab_remote_accountnumber = AB_Transaction_GetRemoteIban(ab_trans);
437  if (!ab_remote_bankcode)
438  ab_remote_bankcode = AB_Transaction_GetRemoteBic(ab_trans);
439 
440  ab_other_accountid = g_strdup(ab_remote_accountnumber ? ab_remote_accountnumber : "");
441  ab_other_bankcode = g_strdup(ab_remote_bankcode ? ab_remote_bankcode : "");
442 
443  /* Ensure string is in utf8 */
444  gnc_utf8_strip_invalid(ab_other_accountid);
445  gnc_utf8_strip_invalid(ab_other_bankcode);
446 
447  /* and -then- trim it */
448  g_strstrip(ab_other_accountid);
449  g_strstrip(ab_other_bankcode);
450 
451 
452  have_accountid = ab_other_accountid && *ab_other_accountid;
453  have_bankcode = ab_other_bankcode && *ab_other_bankcode;
454 
455  if ( have_accountid || have_bankcode )
456  {
457  retval = g_strdup_printf("%s %s %s %s",
458  have_accountid ? _("Account") : "",
459  have_accountid ? ab_other_accountid : "",
460  have_bankcode ? _("Bank") : "",
461  have_bankcode ? ab_other_bankcode : ""
462  );
463  g_strstrip(retval);
464  }
465  else
466  {
467  retval = g_strdup("");
468  }
469 
470  g_free(ab_other_accountid);
471  g_free(ab_other_bankcode);
472 
473  return retval;
474 }
475 
476 Transaction *
477 gnc_ab_trans_to_gnc(const AB_TRANSACTION *ab_trans, Account *gnc_acc)
478 {
479  QofBook *book;
480  Transaction *gnc_trans;
481  const gchar *fitid;
482  const GWEN_TIME *valuta_date;
483  time64 current_time;
484  const char *custref;
485  gchar *description;
486  Split *split;
487  gchar *memo;
488 
489  g_return_val_if_fail(ab_trans && gnc_acc, NULL);
490 
491  /* Create new GnuCash transaction for the given AqBanking one */
492  book = gnc_account_get_book(gnc_acc);
493  gnc_trans = xaccMallocTransaction(book);
494  xaccTransBeginEdit(gnc_trans);
495 
496  /* Date / Time */
497  valuta_date = AB_Transaction_GetValutaDate(ab_trans);
498  if (!valuta_date)
499  {
500  const GWEN_TIME *normal_date = AB_Transaction_GetDate(ab_trans);
501  if (normal_date)
502  valuta_date = normal_date;
503  }
504  if (valuta_date)
505  xaccTransSetDatePostedSecsNormalized(gnc_trans, GWEN_Time_toTime_t(valuta_date));
506  else
507  g_warning("transaction_cb: Oops, date 'valuta_date' was NULL");
508 
509  xaccTransSetDateEnteredSecs(gnc_trans, gnc_time_utc (NULL));
510 
511  /* Currency. We take simply the default currency of the gnucash account */
512  xaccTransSetCurrency(gnc_trans, xaccAccountGetCommodity(gnc_acc));
513 
514  /* Trans-Num or Split-Action set with gnc_set_num_action below per book
515  * option */
516 
517  /* Description */
518  description = gnc_ab_description_to_gnc(ab_trans);
519  xaccTransSetDescription(gnc_trans, description);
520  g_free(description);
521 
522  /* Notes. */
523  /* xaccTransSetNotes(gnc_trans, g_notes); */
524  /* But Nobody ever uses the Notes field? */
525 
526  /* Add one split */
527  split = xaccMallocSplit(book);
528  xaccSplitSetParent(split, gnc_trans);
529  xaccSplitSetAccount(split, gnc_acc);
530 
531  /* Set the transaction number or split action field based on book option.
532  * We use the "customer reference", if there is one. */
533  custref = AB_Transaction_GetCustomerReference(ab_trans);
534  if (custref && *custref
535  && g_ascii_strncasecmp(custref, "NONREF", 6) != 0)
536  gnc_set_num_action (gnc_trans, split, custref, NULL);
537 
538  /* Set OFX unique transaction ID */
539  fitid = AB_Transaction_GetFiId(ab_trans);
540  if (fitid && *fitid)
541  gnc_import_set_split_online_id(split, fitid);
542 
543  {
544  /* Amount into the split */
545  const AB_VALUE *ab_value = AB_Transaction_GetValue(ab_trans);
546  double d_value = ab_value ? AB_Value_GetValueAsDouble (ab_value) : 0.0;
547  AB_TRANSACTION_TYPE ab_type = AB_Transaction_GetType (ab_trans);
548  gnc_numeric gnc_amount;
549 
550  /*printf("Transaction with value %f has type %d\n", d_value, ab_type);*/
551  /* If the value is positive, but the transaction type says the
552  money is transferred away from our account (Transfer instead of
553  DebitNote), we switch the value to negative. */
554  if (d_value > 0.0 && ab_type == AB_Transaction_TypeTransfer)
555  d_value = -d_value;
556 
557  gnc_amount = double_to_gnc_numeric(
558  d_value,
561  if (!ab_value)
562  g_warning("transaction_cb: Oops, value was NULL. Using 0");
563  xaccSplitSetBaseValue(split, gnc_amount, xaccAccountGetCommodity(gnc_acc));
564  }
565 
566  /* Memo in the Split. */
567  memo = gnc_ab_memo_to_gnc(ab_trans);
568  xaccSplitSetMemo(split, memo);
569  g_free(memo);
570 
571  return gnc_trans;
572 }
573 
581 static Account *
582 gnc_ab_accinfo_to_gnc_acc(AB_IMEXPORTER_ACCOUNTINFO *acc_info)
583 {
584  const gchar *bankcode, *accountnumber;
585  gchar *online_id;
586  Account *gnc_acc;
587 
588  g_return_val_if_fail(acc_info, NULL);
589 
590  bankcode = AB_ImExporterAccountInfo_GetBankCode(acc_info);
591  accountnumber = AB_ImExporterAccountInfo_GetAccountNumber(acc_info);
592  online_id = g_strconcat(bankcode ? bankcode : "",
593  accountnumber ? accountnumber : "",
594  (gchar*)NULL);
595  gnc_acc = gnc_import_select_account(
596  NULL, online_id, 1, AB_ImExporterAccountInfo_GetAccountName(acc_info),
597  NULL, ACCT_TYPE_NONE, NULL, NULL);
598  if (!gnc_acc)
599  {
600  g_warning("gnc_ab_accinfo_to_gnc_acc: Could not determine source account"
601  " for online_id %s", online_id);
602  }
603  g_free(online_id);
604 
605  return gnc_acc;
606 }
607 
608 static const AB_TRANSACTION *
609 txn_transaction_cb(const AB_TRANSACTION *element, gpointer user_data)
610 {
611  GncABImExContextImport *data = user_data;
612  Transaction *gnc_trans;
613  GncABTransType trans_type;
614 
615  g_return_val_if_fail(element && data, NULL);
616 
617  /* Create a GnuCash transaction from ab_trans */
618  gnc_trans = gnc_ab_trans_to_gnc(element, data->gnc_acc);
619 
620  if (data->execute_txns && data->ab_acc)
621  {
622  AB_TRANSACTION *ab_trans = AB_Transaction_dup(element);
623  AB_JOB *job;
624 
625  /* NEW: The imported transaction has been imported into gnucash.
626  * Now also add it as a job to aqbanking */
627  AB_Transaction_SetLocalBankCode(
628  ab_trans, AB_Account_GetBankCode(data->ab_acc));
629  AB_Transaction_SetLocalAccountNumber(
630  ab_trans, AB_Account_GetAccountNumber(data->ab_acc));
631  AB_Transaction_SetLocalCountry(ab_trans, "DE");
632 
633 
634  switch (AB_Transaction_GetType(ab_trans))
635  {
636  case AB_Transaction_TypeDebitNote:
637  trans_type = SINGLE_DEBITNOTE;
638  break;
639  case AB_Transaction_TypeEuTransfer:
640  trans_type = SEPA_TRANSFER;
641  break;
642  case AB_Transaction_TypeTransaction:
643  /* trans_type = SINGLE_INTERNAL_TRANSFER;
644  * break; */
645  case AB_Transaction_TypeTransfer:
646  default:
647  trans_type = SINGLE_TRANSFER;
648  } /* switch */
649 
650  job = gnc_ab_get_trans_job(data->ab_acc, ab_trans, trans_type);
651 
652  /* Check whether we really got a job */
653  if (!job || AB_Job_CheckAvailability(job
654 #ifndef AQBANKING_VERSION_5_PLUS
655  , 0
656 #endif
657  ))
658  {
659  /* Oops, no job, probably not supported by bank */
660  if (gnc_verify_dialog(
661  NULL, FALSE, "%s",
662  _("The backend found an error during the preparation "
663  "of the job. It is not possible to execute this job. \n"
664  "\n"
665  "Most probably the bank does not support your chosen "
666  "job or your Online Banking account does not have the permission "
667  "to execute this job. More error messages might be "
668  "visible on your console log.\n"
669  "\n"
670  "Do you want to enter the job again?")))
671  {
672  gnc_error_dialog(NULL, "Sorry, not implemented yet. Please check the console or trace file logs to see which job was rejected.");
673  }
674  }
675  else
676  {
677  gnc_gen_trans_list_add_trans_with_ref_id(data->generic_importer, gnc_trans, AB_Job_GetJobId(job));
678 
679  /* AB_Job_List2_PushBack(data->job_list, job); -> delayed until trans is successfully imported */
680  g_datalist_set_data(&data->tmp_job_list, gnc_AB_JOB_to_readable_string(job), job);
681  }
682  AB_Transaction_free(ab_trans);
683  }
684  else
685  {
686  /* Instead of xaccTransCommitEdit(gnc_trans) */
687  gnc_gen_trans_list_add_trans(data->generic_importer, gnc_trans);
688  }
689 
690  return NULL;
691 }
692 
693 static void gnc_ab_trans_processed_cb(GNCImportTransInfo *trans_info,
694  gboolean imported,
695  gpointer user_data)
696 {
697  GncABImExContextImport *data = user_data;
698  gchar *jobname = gnc_AB_JOB_ID_to_string(gnc_import_TransInfo_get_ref_id(trans_info));
699  AB_JOB *job = g_datalist_get_data(&data->tmp_job_list, jobname);
700 
701  if (imported)
702  {
703  AB_Job_List2_PushBack(data->job_list, job);
704  }
705  else
706  {
707  AB_Job_free(job);
708  }
709 
710  g_datalist_remove_data(&data->tmp_job_list, jobname);
711 }
712 
713 gchar *
715 {
716  if (job)
717  {
718  return gnc_AB_JOB_ID_to_string(AB_Job_GetJobId(job));
719  }
720  else
721  {
722  return gnc_AB_JOB_ID_to_string(0);
723  }
724 }
725 gchar *
727 {
728  return g_strdup_printf("job_%lu", job_id);
729 }
730 
731 
732 
733 static AB_IMEXPORTER_ACCOUNTINFO *
734 txn_accountinfo_cb(AB_IMEXPORTER_ACCOUNTINFO *element, gpointer user_data)
735 {
736  GncABImExContextImport *data = user_data;
737  Account *gnc_acc;
738 
739  g_return_val_if_fail(element && data, NULL);
740 
741  if (data->awaiting & IGNORE_TRANSACTIONS)
742  /* Ignore them */
743  return NULL;
744 
745  if (!AB_ImExporterAccountInfo_GetFirstTransaction(element))
746  /* No transaction found */
747  return NULL;
748  else
749  data->awaiting |= FOUND_TRANSACTIONS;
750 
751  if (!(data->awaiting & AWAIT_TRANSACTIONS))
752  {
753  if (gnc_verify_dialog(data->parent, TRUE, "%s",
754  _("The bank has sent transaction information "
755  "in its response."
756  "\n"
757  "Do you want to import it?")))
758  {
759  data->awaiting |= AWAIT_TRANSACTIONS;
760  }
761  else
762  {
763  data->awaiting |= IGNORE_TRANSACTIONS;
764  return NULL;
765  }
766  }
767 
768  /* Lookup the corresponding gnucash account */
769  gnc_acc = gnc_ab_accinfo_to_gnc_acc(element);
770  if (!gnc_acc) return NULL;
771  data->gnc_acc = gnc_acc;
772 
773  if (data->execute_txns)
774  {
775  /* Retrieve the aqbanking account that belongs to this gnucash
776  * account */
777  data->ab_acc = gnc_ab_get_ab_account(data->api, gnc_acc);
778  if (!data->ab_acc)
779  {
780  gnc_error_dialog(NULL, "%s",
781  _("No Online Banking account found for this "
782  "gnucash account. These transactions will "
783  "not be executed by Online Banking."));
784  }
785  }
786  else
787  {
788  data->ab_acc = NULL;
789  }
790 
791  if (!data->generic_importer)
792  {
793  data->generic_importer = gnc_gen_trans_list_new(data->parent, NULL,
794  TRUE, 14);
795  if (data->execute_txns)
796  {
797  gnc_gen_trans_list_add_tp_cb(data->generic_importer,
798  gnc_ab_trans_processed_cb, data);
799  }
800  }
801 
802  /* Iterate through all transactions */
803  AB_ImExporterAccountInfo_TransactionsForEach(element, txn_transaction_cb,
804  data);
805 
806  return NULL;
807 }
808 
809 static AB_IMEXPORTER_ACCOUNTINFO *
810 bal_accountinfo_cb(AB_IMEXPORTER_ACCOUNTINFO *element, gpointer user_data)
811 {
812  GncABImExContextImport *data = user_data;
813  Account *gnc_acc;
814  AB_ACCOUNT_STATUS *item, *best = NULL;
815  const GWEN_TIME *best_time = NULL;
816  const AB_BALANCE *booked_bal, *noted_bal;
817  const AB_VALUE *booked_val = NULL, *noted_val = NULL;
818  gdouble booked_value, noted_value;
819  gnc_numeric value;
820  time64 booked_tt = 0;
821  GtkWidget *dialog;
822  gboolean show_recn_window = FALSE;
823 
824  g_return_val_if_fail(element && data, NULL);
825 
826  if (data->awaiting & IGNORE_BALANCES)
827  /* Ignore them */
828  return NULL;
829 
830  if (!AB_ImExporterAccountInfo_GetFirstAccountStatus(element))
831  /* No balance found */
832  return NULL;
833  else
834  data->awaiting |= FOUND_BALANCES;
835 
836  /* Lookup the most recent ACCOUNT_STATUS available */
837  item = AB_ImExporterAccountInfo_GetFirstAccountStatus(element);
838  while (item)
839  {
840  const GWEN_TIME *item_time = AB_AccountStatus_GetTime(item);
841  if (!best || GWEN_Time_Diff(best_time, item_time) < 0.0)
842  {
843  best = item;
844  best_time = item_time;
845  }
846  item = AB_ImExporterAccountInfo_GetNextAccountStatus(element);
847  }
848 
849  booked_bal = AB_AccountStatus_GetBookedBalance(best);
850  if (!(data->awaiting & AWAIT_BALANCES))
851  {
852  /* Ignore zero balances if we don't await a balance */
853  if (!booked_bal || AB_Value_IsZero(AB_Balance_GetValue(booked_bal)))
854  return NULL;
855 
856  /* Ask the user whether to import unawaited non-zero balance */
857  if (gnc_verify_dialog(data->parent, TRUE, "%s",
858  _("The bank has sent balance information "
859  "in its response."
860  "\n"
861  "Do you want to import it?")))
862  {
863  data->awaiting |= AWAIT_BALANCES;
864  }
865  else
866  {
867  data->awaiting |= IGNORE_BALANCES;
868  return NULL;
869  }
870  }
871 
872  /* Lookup the corresponding gnucash account */
873  gnc_acc = gnc_ab_accinfo_to_gnc_acc(element);
874  if (!gnc_acc) return NULL;
875  data->gnc_acc = gnc_acc;
876 
877  /* Lookup booked balance and time */
878  if (booked_bal)
879  {
880  const GWEN_TIME *ti = AB_Balance_GetTime(booked_bal);
881  if (ti)
882  {
883  booked_tt = GWEN_Time_toTime_t(ti);
884  }
885  else
886  {
887  /* No time found? Use today because the HBCI query asked for today's
888  * balance. */
889  booked_tt = gnc_time64_get_day_start(gnc_time(NULL));
890  }
891  booked_val = AB_Balance_GetValue(booked_bal);
892  if (booked_val)
893  {
894  booked_value = AB_Value_GetValueAsDouble(booked_val);
895  }
896  else
897  {
898  g_warning("bal_accountinfo_cb: booked_val == NULL. Assuming 0");
899  booked_value = 0.0;
900  }
901  }
902  else
903  {
904  g_warning("bal_accountinfo_cb: booked_bal == NULL. Assuming 0");
905  booked_tt = 0;
906  booked_value = 0.0;
907  }
908 
909  /* Lookup noted balance */
910  noted_bal = AB_AccountStatus_GetNotedBalance(best);
911  if (noted_bal)
912  {
913  noted_val = AB_Balance_GetValue(noted_bal);
914  if (noted_val)
915  noted_value = AB_Value_GetValueAsDouble(noted_val);
916  else
917  {
918  g_warning("bal_accountinfo_cb: noted_val == NULL. Assuming 0");
919  noted_value = 0.0;
920  }
921  }
922  else
923  {
924  g_warning("bal_accountinfo_cb: noted_bal == NULL. Assuming 0");
925  noted_value = 0.0;
926  }
927 
928  value = double_to_gnc_numeric(booked_value,
931  if (noted_value == 0.0 && booked_value == 0.0)
932  {
933  dialog = gtk_message_dialog_new(
934  GTK_WINDOW(data->parent),
935  GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
936  GTK_MESSAGE_INFO,
937  GTK_BUTTONS_OK,
938  "%s",
939  /* Translators: Strings from this file are needed only in
940  * countries that have one of aqbanking's Online Banking
941  * techniques available. This is 'OFX DirectConnect'
942  * (U.S. and others), 'HBCI' (in Germany), or 'YellowNet'
943  * (Switzerland). If none of these techniques are available
944  * in your country, you may safely ignore strings from the
945  * import-export/hbci subdirectory. */
946  _("The downloaded Online Banking Balance was zero.\n\n"
947  "Either this is the correct balance, or your bank does not "
948  "support Balance download in this Online Banking version. "
949  "In the latter case you should choose a different "
950  "Online Banking version number in the Online Banking "
951  "(AqBanking or HBCI) Setup. After that, try again to "
952  "download the Online Banking Balance."));
953  gtk_dialog_run(GTK_DIALOG(dialog));
954  gtk_widget_destroy(dialog);
955 
956  }
957  else
958  {
959  gnc_numeric reconc_balance = xaccAccountGetReconciledBalance(gnc_acc);
960 
961  gchar *booked_str = gnc_AB_VALUE_to_readable_string(booked_val);
962  gchar *message1 = g_strdup_printf(
963  _("Result of Online Banking job: \n"
964  "Account booked balance is %s"),
965  booked_str);
966  gchar *message2 =
967  (noted_value == 0.0) ?
968  g_strdup("") :
969  g_strdup_printf(_("For your information: This account also "
970  "has a noted balance of %s\n"),
972 
973  if (gnc_numeric_equal(value, reconc_balance))
974  {
975  const gchar *message3 =
976  _("The booked balance is identical to the current "
977  "reconciled balance of the account.");
978  dialog = gtk_message_dialog_new(
979  GTK_WINDOW(data->parent),
980  GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
981  GTK_MESSAGE_INFO,
982  GTK_BUTTONS_OK,
983  "%s\n%s\n%s",
984  message1, message2, message3);
985  gtk_dialog_run(GTK_DIALOG(dialog));
986  gtk_widget_destroy(GTK_WIDGET(dialog));
987 
988  }
989  else
990  {
991  const char *message3 = _("Reconcile account now?");
992 
993  show_recn_window = gnc_verify_dialog(data->parent, TRUE, "%s\n%s\n%s",
994  message1, message2, message3);
995  }
996  g_free(booked_str);
997  g_free(message1);
998  g_free(message2);
999  }
1000 
1001  /* Show reconciliation window */
1002  if (show_recn_window)
1003  recnWindowWithBalance(data->parent, gnc_acc, value, booked_tt);
1004 
1005  return NULL;
1006 }
1007 
1009 gnc_ab_import_context(AB_IMEXPORTER_CONTEXT *context,
1010  guint awaiting, gboolean execute_txns,
1011  AB_BANKING *api, GtkWidget *parent)
1012 {
1014 
1015  g_return_val_if_fail(context, NULL);
1016  /* Do not await and ignore at the same time */
1017  g_return_val_if_fail(!(awaiting & AWAIT_BALANCES)
1018  || !(awaiting & IGNORE_BALANCES),
1019  NULL);
1020  g_return_val_if_fail(!(awaiting & AWAIT_TRANSACTIONS)
1021  || !(awaiting & IGNORE_TRANSACTIONS),
1022  NULL);
1023  /* execute_txns must be FALSE if txns are not awaited */
1024  g_return_val_if_fail(awaiting & AWAIT_TRANSACTIONS || !execute_txns, NULL);
1025  /* An api is needed for the jobs */
1026  g_return_val_if_fail(!execute_txns || api, NULL);
1027 
1028  data->awaiting = awaiting;
1029  data->txn_found = FALSE;
1030  data->execute_txns = execute_txns;
1031  data->api = api;
1032  data->parent = parent;
1033  data->job_list = AB_Job_List2_new();
1034  data->tmp_job_list = NULL;
1035  data->generic_importer = NULL;
1036 
1037  g_datalist_init(&data->tmp_job_list);
1038 
1039  /* Import transactions */
1040  if (!(awaiting & IGNORE_TRANSACTIONS))
1041  AB_ImExporterContext_AccountInfoForEach(context, txn_accountinfo_cb,
1042  data);
1043 
1044  /* Check balances */
1045  if (!(awaiting & IGNORE_BALANCES))
1046  AB_ImExporterContext_AccountInfoForEach(context, bal_accountinfo_cb,
1047  data);
1048 
1049  return data;
1050 }
1051 
1052 guint
1054 {
1055  g_return_val_if_fail(ieci, 0);
1056 
1057  return ieci->awaiting;
1058 }
1059 
1060 AB_JOB_LIST2 *
1062 {
1063  g_return_val_if_fail(ieci, NULL);
1064 
1065  return ieci->job_list;
1066 }
1067 
1068 gboolean
1070 {
1071  g_return_val_if_fail(ieci, FALSE);
1072 
1073  return gnc_gen_trans_list_run(ieci->generic_importer);
1074 }
1075 
1076 GWEN_DB_NODE *
1078 {
1079  int rv;
1080  GWEN_DB_NODE *perm_certs = NULL;
1081  AB_BANKING *banking = gnc_AB_BANKING_new();
1082 
1083  g_return_val_if_fail(banking, NULL);
1084 #ifdef AQBANKING_VERSION_4_PLUS
1085  rv = AB_Banking_LoadSharedConfig(banking, "certs", &perm_certs
1086 # ifndef AQBANKING_VERSION_5_PLUS
1087  , 0
1088 # endif
1089  );
1090 #else
1091  /* FIXME: Add code for older AqBanking versions */
1092  /* See QBankmanager 0.9.50 in src/kbanking/libs/kbanking.cpp lines 323ff
1093  for a proper example of how to do this */
1094  rv = 0;
1095 #endif
1096  gnc_AB_BANKING_fini(banking);
1097  g_return_val_if_fail(rv >= 0, NULL);
1098  return perm_certs;
1099 }
Dialog for AqBanking transaction data.
guint32 gnc_ab_get_account_uid(const Account *a)
Definition: gnc-ab-kvp.c:79
gchar * gnc_ab_get_remote_name(const AB_TRANSACTION *ab_trans)
Definition: gnc-ab-utils.c:342
Transaction * xaccMallocTransaction(QofBook *book)
Definition: Transaction.c:513
gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
void xaccSplitSetBaseValue(Split *s, gnc_numeric value, const gnc_commodity *base_currency)
Definition: Split.c:1341
void xaccTransSetDatePostedSecsNormalized(Transaction *trans, time64 time)
Definition: Transaction.c:1920
gchar * gnc_AB_JOB_to_readable_string(const AB_JOB *job)
Definition: gnc-ab-utils.c:714
gnc_numeric double_to_gnc_numeric(double n, gint64 denom, gint how)
void gnc_GWEN_Gui_shutdown(void)
Definition: gnc-gwen-gui.c:356
#define G_LOG_DOMAIN
Functions providing the SX List as a plugin page.
AB_JOB_LIST2 * gnc_ab_ieci_get_job_list(GncABImExContextImport *ieci)
int xaccAccountGetCommoditySCU(const Account *acc)
Definition: Account.c:2458
gchar * gnc_ab_get_purpose(const AB_TRANSACTION *ab_trans)
Definition: gnc-ab-utils.c:364
gchar * gnc_ab_description_to_gnc(const AB_TRANSACTION *ab_trans)
Definition: gnc-ab-utils.c:383
gchar * gnc_AB_VALUE_to_readable_string(const AB_VALUE *value)
Definition: gnc-ab-utils.c:300
void xaccTransSetDescription(Transaction *trans, const char *desc)
Definition: Transaction.c:2085
gchar * gnc_AB_JOB_ID_to_string(gulong job_id)
Definition: gnc-ab-utils.c:726
Account * gnc_import_select_account(GtkWidget *parent, const gchar *account_online_id_value, gboolean auto_create, const gchar *account_human_description, const gnc_commodity *new_account_default_commodity, GNCAccountType new_account_default_type, Account *default_selection, gboolean *ok_pressed)
Transaction matcher main window.
gchar * gnc_ab_memo_to_gnc(const AB_TRANSACTION *ab_trans)
Definition: gnc-ab-utils.c:419
gboolean gnc_ab_ieci_run_matcher(GncABImExContextImport *ieci)
void gnc_gen_trans_list_add_tp_cb(GNCImportMainMatcher *info, GNCTransactionProcessedCB trans_processed_cb, gpointer user_data)
Generic and very flexible account matcher/picker.
guint32 gnc_import_TransInfo_get_ref_id(const GNCImportTransInfo *info)
const gchar * gnc_ab_get_account_bankcode(const Account *a)
Definition: gnc-ab-kvp.c:59
GNCImportMainMatcher * gnc_gen_trans_list_new(GtkWidget *parent, const gchar *heading, gboolean all_from_same_account, gint match_date_hardlimit)
void gnc_gen_trans_list_add_trans(GNCImportMainMatcher *gui, Transaction *trans)
gboolean gnc_gen_trans_list_run(GNCImportMainMatcher *info)
GWEN_DB_NODE * gnc_ab_get_permanent_certs(void)
void xaccTransSetCurrency(Transaction *trans, gnc_commodity *curr)
Definition: Transaction.c:1354
Transaction * gnc_ab_trans_to_gnc(const AB_TRANSACTION *ab_trans, Account *gnc_acc)
Definition: gnc-ab-utils.c:477
void xaccSplitSetMemo(Split *split, const char *memo)
Definition: Split.c:1774
AB_BANKING * gnc_AB_BANKING_new(void)
Definition: gnc-ab-utils.c:137
void gnc_utf8_strip_invalid(gchar *str)
void xaccTransBeginEdit(Transaction *trans)
Definition: Transaction.c:1380
gnc_numeric xaccAccountGetReconciledBalance(const Account *acc)
Definition: Account.c:3243
gint gnc_AB_BANKING_fini(AB_BANKING *api)
Definition: gnc-ab-utils.c:237
Split * xaccMallocSplit(QofBook *book)
Definition: Split.c:582
GLib helper routines.
Generic api to store and retrieve preferences.
void gnc_gen_trans_list_add_trans_with_ref_id(GNCImportMainMatcher *gui, Transaction *trans, guint32 ref_id)
void gnc_AB_BANKING_delete(AB_BANKING *api)
Definition: gnc-ab-utils.c:218
time64 gnc_time64_get_day_start(time64 time_val)
void gnc_GWEN_Fini(void)
Definition: gnc-ab-utils.c:122
Definition: SplitP.h:71
AB_ACCOUNT * gnc_ab_get_ab_account(const AB_BANKING *api, Account *gnc_acc)
Definition: gnc-ab-utils.c:264
GncABImExContextImport * gnc_ab_import_context(AB_IMEXPORTER_CONTEXT *context, guint awaiting, gboolean execute_txns, AB_BANKING *api, GtkWidget *parent)
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Definition: Account.c:3148
time64 gnc_time_utc(time64 *tbuf)
get the current utc time
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Definition: gnc-prefs.c:196
Utility functions for writing import modules.
AB_JOB * gnc_ab_get_trans_job(AB_ACCOUNT *ab_acc, const AB_TRANSACTION *ab_trans, GncABTransType trans_type)
time64 gnc_time(time64 *tbuf)
get the current local time
gint64 time64
Definition: gnc-date.h:83
const gchar * gnc_ab_get_account_accountid(const Account *a)
Definition: gnc-ab-kvp.c:39
void xaccTransSetDateEnteredSecs(Transaction *trans, time64 secs)
Definition: Transaction.c:1951
void gnc_GWEN_Init(void)
Definition: gnc-ab-utils.c:87
AqBanking KVP handling.
GUI callbacks for AqBanking.
guint gnc_ab_ieci_get_found(GncABImExContextImport *ieci)
API for Transactions and Splits (journal entries)
void gnc_GWEN_Gui_log_init(void)
Definition: gnc-gwen-gui.c:289
AqBanking utility functions.
const gchar * QofLogModule
Definition: qofid.h:89