GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-ofx-import.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\********************************************************************/
26#include <config.h>
27
28#include <gtk/gtk.h>
29#include <glib/gi18n.h>
30#include <stdio.h>
31#include <string.h>
32#include <sys/time.h>
33#include <math.h>
34#include <inttypes.h>
35
36#include <libofx/libofx.h>
39#include "import-utilities.h"
40#include "import-main-matcher.h"
41
42#include "Account.h"
43#include "Transaction.h"
44#include "engine-helpers.h"
45#include "gnc-ofx-import.h"
46#include "gnc-file.h"
47#include "gnc-engine.h"
48#include "gnc-ui-util.h"
49#include "gnc-glib-utils.h"
50#include "gnc-prefs.h"
51#include "gnc-ui.h"
52#include "gnc-window.h"
53#include "dialog-account.h"
54#include "dialog-utils.h"
55#include "window-reconcile.h"
56
57#include <string>
58#include <sstream>
59#include <unordered_map>
60
61#define GNC_PREFS_GROUP "dialogs.import.ofx"
62#define GNC_PREF_AUTO_COMMODITY "auto-create-commodity"
63
64static QofLogModule log_module = GNC_MOD_IMPORT;
65
66/********************************************************************\
67 * gnc_file_ofx_import
68 * Entry point
69\********************************************************************/
70
71static gboolean auto_create_commodity = FALSE;
72static Account *ofx_parent_account = NULL;
73
74typedef struct OfxTransactionData OfxTransactionData;
75
76// Structure we use to gather information about statement balance/account etc.
77typedef struct _ofx_info
78{
79 GtkWindow* parent;
80 GNCImportMainMatcher *gnc_ofx_importer_gui;
81 Account *last_import_account;
82 Account *last_investment_account;
83 Account *last_income_account;
84 gint num_trans_processed; // Number of transactions processed
85 GList* statement; // Statement, if any
86 gboolean run_reconcile; // If TRUE the reconcile window is opened after matching.
87 GSList* file_list; // List of OFX files to import
88 GList* trans_list; // We store the processed ofx transactions here
89 gint response; // Response sent by the match gui
90} ofx_info ;
91
92static void runMatcher(ofx_info* info, char * selected_filename, gboolean go_to_next_file);
93
94/*
95int ofx_proc_status_cb(struct OfxStatusData data)
96{
97 return 0;
98}
99*/
100
101static const char *PROP_OFX_INCOME_ACCOUNT = "ofx-income-account";
102
103static Account*
104get_associated_income_account(const Account* investment_account)
105{
106 GncGUID *income_guid = NULL;
107 Account *acct = NULL;
108 g_assert(investment_account);
109 qof_instance_get (QOF_INSTANCE (investment_account),
110 PROP_OFX_INCOME_ACCOUNT, &income_guid,
111 NULL);
112 acct = xaccAccountLookup (income_guid,
113 gnc_account_get_book(investment_account));
114 guid_free (income_guid);
115 return acct;
116}
117
118static void
119set_associated_income_account(Account* investment_account,
120 const Account *income_account)
121{
122 const GncGUID * income_acc_guid;
123
124 g_assert(investment_account);
125 g_assert(income_account);
126
127 income_acc_guid = xaccAccountGetGUID(income_account);
128 xaccAccountBeginEdit(investment_account);
129 qof_instance_set (QOF_INSTANCE (investment_account),
130 PROP_OFX_INCOME_ACCOUNT, income_acc_guid,
131 NULL);
132 xaccAccountCommitEdit(investment_account);
133}
134
135int ofx_proc_statement_cb (struct OfxStatementData data, void * statement_user_data);
136int ofx_proc_security_cb (const struct OfxSecurityData data, void * security_user_data);
137int ofx_proc_transaction_cb (OfxTransactionData data, void *user_data);
138int ofx_proc_account_cb (struct OfxAccountData data, void * account_user_data);
139static double ofx_get_investment_amount (const OfxTransactionData* data);
140
141static const gchar *gnc_ofx_ttype_to_string(TransactionType t)
142{
143 switch (t)
144 {
145 case OFX_CREDIT:
146 return "Generic credit";
147 case OFX_DEBIT:
148 return "Generic debit";
149 case OFX_INT:
150 return "Interest earned or paid (Note: Depends on signage of amount)";
151 case OFX_DIV:
152 return "Dividend";
153 case OFX_FEE:
154 return "FI fee";
155 case OFX_SRVCHG:
156 return "Service charge";
157 case OFX_DEP:
158 return "Deposit";
159 case OFX_ATM:
160 return "ATM debit or credit (Note: Depends on signage of amount)";
161 case OFX_POS:
162 return "Point of sale debit or credit (Note: Depends on signage of amount)";
163 case OFX_XFER:
164 return "Transfer";
165 case OFX_CHECK:
166 return "Check";
167 case OFX_PAYMENT:
168 return "Electronic payment";
169 case OFX_CASH:
170 return "Cash withdrawal";
171 case OFX_DIRECTDEP:
172 return "Direct deposit";
173 case OFX_DIRECTDEBIT:
174 return "Merchant initiated debit";
175 case OFX_REPEATPMT:
176 return "Repeating payment/standing order";
177 case OFX_OTHER:
178 return "Other";
179 default:
180 return "Unknown transaction type";
181 }
182}
183
184static const gchar *gnc_ofx_invttype_to_str(InvTransactionType t)
185{
186 switch (t)
187 {
188 case OFX_BUYDEBT:
189 return "BUYDEBT (Buy debt security)";
190 case OFX_BUYMF:
191 return "BUYMF (Buy mutual fund)";
192 case OFX_BUYOPT:
193 return "BUYOPT (Buy option)";
194 case OFX_BUYOTHER:
195 return "BUYOTHER (Buy other security type)";
196 case OFX_BUYSTOCK:
197 return "BUYSTOCK (Buy stock))";
198 case OFX_CLOSUREOPT:
199 return "CLOSUREOPT (Close a position for an option)";
200 case OFX_INCOME:
201 return "INCOME (Investment income is realized as cash into the investment account)";
202 case OFX_INVEXPENSE:
203 return "INVEXPENSE (Misc investment expense that is associated with a specific security)";
204 case OFX_JRNLFUND:
205 return "JRNLFUND (Journaling cash holdings between subaccounts within the same investment account)";
206 case OFX_MARGININTEREST:
207 return "MARGININTEREST (Margin interest expense)";
208 case OFX_REINVEST:
209 return "REINVEST (Reinvestment of income)";
210 case OFX_RETOFCAP:
211 return "RETOFCAP (Return of capital)";
212 case OFX_SELLDEBT:
213 return "SELLDEBT (Sell debt security. Used when debt is sold, called, or reached maturity)";
214 case OFX_SELLMF:
215 return "SELLMF (Sell mutual fund)";
216 case OFX_SELLOPT:
217 return "SELLOPT (Sell option)";
218 case OFX_SELLOTHER:
219 return "SELLOTHER (Sell other type of security)";
220 case OFX_SELLSTOCK:
221 return "SELLSTOCK (Sell stock)";
222 case OFX_SPLIT:
223 return "SPLIT (Stock or mutial fund split)";
224 case OFX_TRANSFER:
225 return "TRANSFER (Transfer holdings in and out of the investment account)";
226#ifdef HAVE_LIBOFX_VERSION_0_10
227 case OFX_INVBANKTRAN:
228 return "Transfer cash in and out of the investment account";
229#endif
230 default:
231 return "ERROR, this investment transaction type is unknown. This is a bug in ofxdump";
232 }
233
234}
235
236static gchar*
237sanitize_string (gchar* str)
238{
239 gchar *inval;
240 const int length = -1; /*Assumes str is null-terminated */
241 while (!g_utf8_validate (str, length, (const gchar **)(&inval)))
242 *inval = '@';
243 return str;
244}
245
246int ofx_proc_security_cb(const struct OfxSecurityData data, void * security_user_data)
247{
248 char* cusip = NULL;
249 char* default_fullname = NULL;
250 char* default_mnemonic = NULL;
251
252 if (data.unique_id_valid)
253 {
254 cusip = gnc_utf8_strip_invalid_strdup (data.unique_id);
255 }
256 if (data.secname_valid)
257 {
258 default_fullname = gnc_utf8_strip_invalid_strdup (data.secname);
259 }
260 if (data.ticker_valid)
261 {
262 default_mnemonic = gnc_utf8_strip_invalid_strdup (data.ticker);
263 }
264
265 if (auto_create_commodity)
266 {
267 gnc_commodity *commodity =
269 FALSE,
270 default_fullname,
271 default_mnemonic);
272
273 if (!commodity)
274 {
275 QofBook *book = gnc_get_current_book();
276 gnc_quote_source *source;
277 gint source_selection = 0; // FIXME: This is just a wild guess
278 char *commodity_namespace = NULL;
279 int fraction = 1;
280
281 if (data.unique_id_type_valid)
282 {
283 commodity_namespace = gnc_utf8_strip_invalid_strdup (data.unique_id_type);
284 }
285
286 g_warning("Creating a new commodity, cusip=%s", cusip);
287 /* Create the new commodity */
288 commodity = gnc_commodity_new(book,
289 default_fullname,
290 commodity_namespace,
291 default_mnemonic,
292 cusip,
293 fraction);
294
295 /* Also set a single quote source */
296 gnc_commodity_begin_edit(commodity);
297 gnc_commodity_user_set_quote_flag (commodity, TRUE);
298 source = gnc_quote_source_lookup_by_ti (SOURCE_SINGLE, source_selection);
299 gnc_commodity_set_quote_source(commodity, source);
300 gnc_commodity_commit_edit(commodity);
301
302 /* Remember the commodity */
303 gnc_commodity_table_insert(gnc_get_current_commodities(), commodity);
304
305 g_free (commodity_namespace);
306
307 }
308 }
309 else
310 {
312 TRUE,
313 default_fullname,
314 default_mnemonic);
315 }
316
317 g_free (cusip);
318 g_free (default_mnemonic);
319 g_free (default_fullname);
320 return 0;
321}
322
323static void gnc_ofx_set_split_memo(const OfxTransactionData* data, Split *split)
324{
325 g_assert(data);
326 g_assert(split);
327 /* Also put the ofx transaction name in
328 * the splits memo field, or ofx memo if
329 * name is unavailable */
330 if (data->name_valid)
331 {
332 xaccSplitSetMemo(split, data->name);
333 }
334 else if (data->memo_valid)
335 {
336 xaccSplitSetMemo(split, data->memo);
337 }
338}
339static gnc_numeric gnc_ofx_numeric_from_double(double value, const gnc_commodity *commodity)
340{
341 return double_to_gnc_numeric (value,
343 GNC_HOW_RND_ROUND_HALF_UP);
344}
345static gnc_numeric gnc_ofx_numeric_from_double_txn(double value, const Transaction* txn)
346{
347 return gnc_ofx_numeric_from_double(value, xaccTransGetCurrency(txn));
348}
349
350/* Opens the dialog to create a new account with given name, commodity, parent, type.
351 * Returns the new account, or NULL if it couldn't be created.. */
352static Account *gnc_ofx_new_account(GtkWindow* parent,
353 const char* name,
354 const gnc_commodity * account_commodity,
355 Account *parent_account,
356 GNCAccountType new_account_default_type)
357{
358 Account *result;
359 GList * valid_types = NULL;
360
361 g_assert(name);
362 g_assert(account_commodity);
363 g_assert(parent_account);
364
365 if (new_account_default_type != ACCT_TYPE_NONE)
366 {
367 // Passing the types as gpointer
368 valid_types =
369 g_list_prepend(valid_types,
370 GINT_TO_POINTER(new_account_default_type));
371 if (!xaccAccountTypesCompatible(xaccAccountGetType(parent_account), new_account_default_type))
372 {
373 // Need to add the parent's account type
374 valid_types =
375 g_list_prepend(valid_types,
376 GINT_TO_POINTER(xaccAccountGetType(parent_account)));
377 }
378 }
379 result = gnc_ui_new_accounts_from_name_with_defaults (parent, name,
380 valid_types,
381 account_commodity,
382 parent_account);
383 g_list_free(valid_types);
384 return result;
385}
386/* LibOFX has a daylight time handling bug,
387 * https://sourceforge.net/p/libofx/bugs/39/, which causes it to adjust the
388 * timestamp for daylight time even when daylight time is not in
389 * effect. HAVE_OFX_BUG_39 reflects the result of checking for this bug during
390 * configuration, and fix_ofx_bug_39() corrects for it.
391 */
392static time64
393fix_ofx_bug_39 (time64 t)
394{
395#if HAVE_OFX_BUG_39
396 struct tm stm;
397
398#ifdef __FreeBSD__
399 time64 now;
400 /*
401 * FreeBSD has it's own libc implementation which differs from glibc. In particular:
402 * There is no daylight global
403 * tzname members are set to the string " " (three spaces) when not explicitly populated
404 *
405 * To check that the current timezone does not observe DST I check if tzname[1] starts with a space.
406 */
407 now = gnc_time (NULL);
408 gnc_localtime_r(&now, &stm);
409 tzset();
410
411 if (tzname[1][0] != ' ' && !stm.tm_isdst)
412#else
413 gnc_localtime_r(&t, &stm);
414 if (daylight && !stm.tm_isdst)
415#endif
416 t += 3600;
417#endif
418 return t;
419}
420
421static void
422set_transaction_dates(Transaction *transaction, OfxTransactionData *data)
423{
424 /* Note: Unfortunately libofx <= 0.9.5 will not report a missing
425 * date field as an invalid one. Instead, it will report it as
426 * valid and return a completely bogus date. Starting with
427 * libofx-0.9.6 (not yet released as of 2012-09-09), it will still
428 * be reported as valid but at least the date integer itself is
429 * just plain zero. */
430
431 time64 current_time = gnc_time (NULL);
432
433 if (data->date_posted_valid && (data->date_posted != 0))
434 {
435 /* The hopeful case: We have a posted_date */
436 data->date_posted = fix_ofx_bug_39 (data->date_posted);
437 xaccTransSetDatePostedSecsNormalized(transaction, data->date_posted);
438 }
439 else if (data->date_initiated_valid && (data->date_initiated != 0))
440 {
441 /* No posted date? Maybe we have an initiated_date */
442 data->date_initiated = fix_ofx_bug_39 (data->date_initiated);
443 xaccTransSetDatePostedSecsNormalized(transaction, data->date_initiated);
444 }
445 else
446 {
447 /* Uh no, no valid date. As a workaround use today's date */
448 xaccTransSetDatePostedSecsNormalized(transaction, current_time);
449 }
450
451 xaccTransSetDateEnteredSecs(transaction, current_time);
452}
453
454static void
455fill_transaction_description(Transaction *transaction, OfxTransactionData *data)
456{
457 /* Put transaction name in Description, or memo if name unavailable */
458 if (data->name_valid)
459 {
460 xaccTransSetDescription(transaction, data->name);
461 }
462 else if (data->memo_valid)
463 {
464 xaccTransSetDescription(transaction, data->memo);
465 }
466}
467
468static void
469fill_transaction_notes(Transaction *transaction, OfxTransactionData *data)
470{
471 /* Put everything else in the Notes field */
472 char *notes = g_strdup_printf("OFX ext. info: ");
473
474 if (data->transactiontype_valid)
475 {
476 char *tmp = notes;
477 notes = g_strdup_printf("%s%s%s", tmp, "|Trans type:",
478 gnc_ofx_ttype_to_string(data->transactiontype));
479 g_free(tmp);
480 }
481
482 if (data->invtransactiontype_valid)
483 {
484 char *tmp = notes;
485 notes = g_strdup_printf("%s%s%s", tmp, "|Investment Trans type:",
486 gnc_ofx_invttype_to_str(data->invtransactiontype));
487 g_free(tmp);
488 }
489 if (data->memo_valid && data->name_valid) /* Copy only if memo wasn't put in Description */
490 {
491 char *tmp = notes;
492 notes = g_strdup_printf("%s%s%s", tmp, "|Memo:", data->memo);
493 g_free(tmp);
494 }
495 if (data->date_funds_available_valid)
496 {
497 char dest_string[MAX_DATE_LENGTH];
498 time64 time = data->date_funds_available;
499 char *tmp = notes;
500
501 gnc_time64_to_iso8601_buff (time, dest_string);
502 notes = g_strdup_printf("%s%s%s", tmp,
503 "|Date funds available:", dest_string);
504 g_free(tmp);
505 }
506 if (data->server_transaction_id_valid)
507 {
508 char *tmp = notes;
509 notes = g_strdup_printf("%s%s%s", tmp,
510 "|Server trans ID (conf. number):",
511 sanitize_string (data->server_transaction_id));
512 g_free(tmp);
513 }
514 if (data->standard_industrial_code_valid)
515 {
516 char *tmp = notes;
517 notes = g_strdup_printf("%s%s%ld", tmp,
518 "|Standard Industrial Code:",
519 data->standard_industrial_code);
520 g_free(tmp);
521
522 }
523 if (data->payee_id_valid)
524 {
525 char *tmp = notes;
526 notes = g_strdup_printf("%s%s%s", tmp, "|Payee ID:",
527 sanitize_string (data->payee_id));
528 g_free(tmp);
529 }
530 //PERR("WRITEME: GnuCash ofx_proc_transaction():Add PAYEE and ADDRESS here once supported by libofx! Notes=%s\n", notes);
531
532 /* Ideally, gnucash should process the corrected transactions */
533 if (data->fi_id_corrected_valid)
534 {
535 char *tmp = notes;
536 PERR("WRITEME: GnuCash ofx_proc_transaction(): WARNING: This transaction corrected a previous transaction, but we created a new one instead!\n");
537 notes = g_strdup_printf("%s%s%s%s", tmp,
538 "|This corrects transaction #",
539 sanitize_string (data->fi_id_corrected),
540 "but GnuCash didn't process the correction!");
541 g_free(tmp);
542 }
543 xaccTransSetNotes(transaction, notes);
544 g_free(notes);
545
546}
547
548static void
549process_bank_transaction(Transaction *transaction, Account *import_account,
550 OfxTransactionData *data, ofx_info *info)
551{
552 Split *split;
553 gnc_numeric gnc_amount;
554 QofBook *book = qof_instance_get_book(QOF_INSTANCE(transaction));
555 double amount = data->amount;
556#ifdef HAVE_LIBOFX_VERSION_0_10
557 if (data->currency_ratio_valid && data->currency_ratio != 0)
558 amount *= data->currency_ratio;
559#endif
560 /***** Process a normal transaction ******/
561 DEBUG("Adding split; Ordinary banking transaction, money flows from or into the source account");
562 split = xaccMallocSplit(book);
563 xaccTransAppendSplit(transaction, split);
564 xaccAccountInsertSplit(import_account, split);
565 gnc_amount = gnc_ofx_numeric_from_double_txn(amount, transaction);
566 xaccSplitSetBaseValue(split, gnc_amount, xaccTransGetCurrency(transaction));
567
568 /* set tran-num and/or split-action per book option */
569 if (data->check_number_valid)
570 {
571 /* SQL will correctly interpret the string "null", but
572 * the transaction num field is declared to be
573 * non-null so substitute the empty string.
574 */
575 const char *num_value =
576 strcasecmp (data->check_number, "null") == 0 ? "" :
577 data->check_number;
578 gnc_set_num_action(transaction, split, num_value, NULL);
579 }
580 else if (data->reference_number_valid)
581 {
582 const char *num_value =
583 strcasecmp (data->reference_number, "null") == 0 ? "" :
584 data->check_number;
585 gnc_set_num_action(transaction, split, num_value, NULL);
586 }
587 /* Also put the ofx transaction's memo in the
588 * split's memo field */
589 if (data->memo_valid)
590 {
591 xaccSplitSetMemo(split, data->memo);
592 }
593 if (data->fi_id_valid)
594 {
595 xaccSplitSetOnlineID(split, sanitize_string (data->fi_id));
596 }
597}
598
599typedef struct
600{
601 gnc_commodity *commodity;
602 char *online_id;
603 char *acct_text;
604 gboolean choosing;
606
607static Account*
608create_investment_subaccount(GtkWindow *parent, Account* parent_acct,
609 InvestmentAcctData *inv_data)
610{
611
612 Account *investment_account =
613 gnc_ofx_new_account(parent,
614 inv_data->acct_text,
615 inv_data->commodity,
616 parent_acct,
617 ACCT_TYPE_STOCK);
618 if (investment_account)
619 {
620 xaccAccountSetOnlineID(investment_account, inv_data->online_id);
621 inv_data->choosing = FALSE;
622 ofx_parent_account = parent_acct;
623 }
624 else
625 {
626 ofx_parent_account = NULL;
627 }
628 return investment_account;
629}
630
631static gboolean
632continue_account_selection(GtkWidget* parent, Account* account,
633 gnc_commodity* commodity)
634{
635 gboolean keep_going =
636 gnc_verify_dialog(
637 GTK_WINDOW (parent), TRUE,
638 "The chosen account \"%s\" does not have the correct "
639 "currency/security \"%s\" (it has \"%s\" instead). "
640 "This account cannot be used. "
641 "Do you want to choose again?",
642 xaccAccountGetName(account),
645 // We must also delete the online_id that was set in gnc_import_select_account()
646 xaccAccountSetOnlineID(account, "");
647 return keep_going;
648}
649
650static Account*
651choose_investment_account_helper(OfxTransactionData *data, ofx_info *info,
652 InvestmentAcctData *inv_data)
653{
654 Account *investment_account, *parent_account;
655
656 if (xaccAccountGetCommodity(info->last_investment_account) == inv_data->commodity)
657 parent_account = info->last_investment_account;
658 else
659 parent_account = ofx_parent_account;
660
661 investment_account =
662 gnc_import_select_account(GTK_WIDGET(info->parent),
663 inv_data->online_id,
664 TRUE, inv_data->acct_text,
665 inv_data->commodity, ACCT_TYPE_STOCK,
666 parent_account, &inv_data->choosing);
667 if (investment_account &&
668 xaccAccountGetCommodity(investment_account) == inv_data->commodity)
669 {
670 Account *parent_account = gnc_account_get_parent(investment_account);
671
672 if (!ofx_parent_account && parent_account &&
673 !gnc_account_is_root(parent_account) &&
675 ACCT_TYPE_STOCK))
676 ofx_parent_account = parent_account;
677
678 info->last_investment_account = investment_account;
679 return investment_account;
680 }
681
682 /* That didn't work out. Create a subaccount if we can. */
683 if (auto_create_commodity && ofx_parent_account)
684 {
685 investment_account =
686 create_investment_subaccount(GTK_WINDOW(info->parent),
687 ofx_parent_account,
688 inv_data);
689 }
690 else
691 {
692 // No account with matching commodity. Ask the user
693 // whether to continue or abort.
694 inv_data->choosing =
695 continue_account_selection(GTK_WIDGET(info->parent),
696 investment_account, inv_data->commodity);
697 investment_account = NULL;
698 }
699
700 return investment_account;
701}
702
703static Account*
704choose_investment_account(OfxTransactionData *data, ofx_info *info,
705 gnc_commodity *commodity)
706{
707 Account* investment_account = NULL;
708 InvestmentAcctData inv_data = {commodity, NULL, NULL, TRUE};
709
710 // As we now have the commodity, select the account with that commodity.
711
712 /* Translators: This string is a default account name. It MUST
713 * NOT contain the character ':' anywhere in it or in any
714 * translations. */
715 inv_data.acct_text = g_strdup_printf(
716 _("Stock account for security \"%s\""),
717 sanitize_string (data->security_data_ptr->secname));
718
719 inv_data.online_id =
720 g_strdup_printf("%s%s", data->account_id, data->unique_id);
721
722 // Loop until we either have an account, or the user pressed Cancel
723 while (!investment_account && inv_data.choosing)
724 investment_account = choose_investment_account_helper(data, info,
725 &inv_data);
726 if (!investment_account)
727 {
728 PERR("No investment account found for text: %s\n", inv_data.acct_text);
729 }
730 g_free (inv_data.acct_text);
731 g_free (inv_data.online_id);
732
733 return investment_account;
734}
735
736static Account*
737choose_income_account(Account* investment_account, Transaction *transaction,
738 OfxTransactionData *data, ofx_info *info)
739{
740 Account *income_account = NULL;
741 DEBUG("Now let's find an account for the destination split");
742 income_account =
743 get_associated_income_account(investment_account);
744
745 if (income_account == NULL)
746 {
747 char *income_account_text;
748 gnc_commodity *currency = xaccTransGetCurrency(transaction);
749 DEBUG("Couldn't find an associated income account");
750 /* Translators: This string is a default account
751 * name. It MUST NOT contain the character ':' anywhere
752 * in it or in any translations. */
753 income_account_text = g_strdup_printf(
754 _("Income account for security \"%s\""),
755 sanitize_string (data->security_data_ptr->secname));
756 income_account =
757 gnc_import_select_account(GTK_WIDGET(info->parent), NULL, TRUE,
758 income_account_text, currency,
759 ACCT_TYPE_INCOME,
760 info->last_income_account, NULL);
761
762 if (income_account != NULL)
763 {
764 info->last_income_account = income_account;
765 set_associated_income_account(investment_account,
766 income_account);
767 DEBUG("KVP written");
768 }
769 }
770 else
771 {
772 DEBUG("Found at least one associated income account");
773 }
774
775 return income_account;
776}
777
778static void
779add_investment_split(Transaction* transaction, Account* account,
780 OfxTransactionData *data)
781{
782 Split *split;
783 QofBook *book = gnc_account_get_book(account);
784 gnc_numeric gnc_amount, gnc_units;
785 gnc_commodity *commodity = xaccAccountGetCommodity(account);
786 DEBUG("Adding investment split; Money flows from or into the stock account");
787 split = xaccMallocSplit(book);
788 xaccTransAppendSplit(transaction, split);
789 xaccAccountInsertSplit(account, split);
790
791 gnc_amount =
792 gnc_ofx_numeric_from_double_txn(ofx_get_investment_amount(data),
793 transaction);
794 gnc_units = gnc_ofx_numeric_from_double (data->units, commodity);
795 xaccSplitSetAmount(split, gnc_units);
796 xaccSplitSetValue(split, gnc_amount);
797
798 /* set tran-num and/or split-action per book option */
799 if (data->check_number_valid)
800 {
801 gnc_set_num_action(transaction, split, data->check_number, NULL);
802 }
803 else if (data->reference_number_valid)
804 {
805 gnc_set_num_action(transaction, split,
806 data->reference_number, NULL);
807 }
808 if (data->security_data_ptr->memo_valid)
809 {
810 xaccSplitSetMemo(split,
811 sanitize_string (data->security_data_ptr->memo));
812 }
813 if (data->fi_id_valid &&
815 ACCT_TYPE_ASSET))
816 {
817 xaccSplitSetOnlineID(split, sanitize_string (data->fi_id));
818 }
819}
820
821static void
822add_currency_split(Transaction *transaction, Account* account,
823 double amount, OfxTransactionData *data)
824{
825 Split *split;
826 QofBook *book = gnc_account_get_book(account);
827 gnc_numeric gnc_amount;
828
829 split = xaccMallocSplit(book);
830 xaccTransAppendSplit(transaction, split);
831 xaccAccountInsertSplit(account, split);
832 gnc_amount = gnc_ofx_numeric_from_double_txn(amount, transaction);
833 xaccSplitSetBaseValue(split, gnc_amount, xaccTransGetCurrency(transaction));
834
835 // Set split memo from ofx transaction name or memo
836 gnc_ofx_set_split_memo(data, split);
837 if (data->fi_id_valid)
838 xaccSplitSetOnlineID (split, sanitize_string (data->fi_id));
839}
840
841/* ******** Process an investment transaction **********/
842/* Note that the ACCT_TYPE_STOCK account type
843 should be replaced with something derived from
844 data->invtranstype*/
845
846static void
847process_investment_transaction(Transaction *transaction, Account *import_account,
848 OfxTransactionData *data, ofx_info *info)
849{
850 Account *investment_account = NULL;
851 Account *income_account = NULL;
852 gnc_commodity *investment_commodity;
853 double amount = data->amount;
854
855 g_return_if_fail(data->invtransactiontype_valid);
856
857 gnc_utf8_strip_invalid (data->unique_id);
858
859
860 // Set the cash split unless it's a reinvestment, which doesn't have one.
861 if (data->invtransactiontype != OFX_REINVEST)
862 {
863 DEBUG("Adding investment cash split.");
864 add_currency_split(transaction, import_account,
865 -ofx_get_investment_amount(data), data);
866 }
867
868 investment_commodity = gnc_import_select_commodity(data->unique_id,
869 FALSE, NULL, NULL);
870 if (!investment_commodity)
871 {
872 PERR("Commodity not found for the investment transaction");
873 return;
874 }
875 investment_account = choose_investment_account(data, info,
876 investment_commodity);
877
878 if (!investment_account)
879 {
880 PERR("Failed to determine an investment asset account.");
881 return;
882 }
883
884 if (data->invtransactiontype != OFX_INCOME)
885 {
886 if (data->unitprice_valid && data->units_valid)
887 add_investment_split(transaction, investment_account, data);
888 else
889 PERR("Unable to add investment split, unit price or units were invalid.");
890 }
891
892 if (!(data->invtransactiontype == OFX_REINVEST
893 || data->invtransactiontype == OFX_INCOME))
894 //Done
895 return;
896
897#ifdef HAVE_LIBOFX_VERSION_0_10
898 if (data->currency_ratio_valid && data->currency_ratio != 0)
899 amount *= data->currency_ratio;
900#endif
901 income_account = choose_income_account(investment_account,
902 transaction, data, info);
903 g_return_if_fail(income_account);
904
905 DEBUG("Adding investment income split.");
906 if (data->invtransactiontype == OFX_REINVEST)
907 add_currency_split(transaction, income_account, amount, data);
908 else
909 add_currency_split(transaction, income_account, -amount, data);
910}
911
912int ofx_proc_transaction_cb(OfxTransactionData data, void *user_data)
913{
914 Account *import_account;
915 gnc_commodity *currency = NULL;
916 QofBook *book;
917 Transaction *transaction;
918 ofx_info* info = (ofx_info*) user_data;
919
920 g_assert(info->parent);
921
922 if (!data.amount_valid)
923 {
924 PERR("The transaction doesn't have a valid amount");
925 return 0;
926 }
927
928 if (!data.account_id_valid)
929 {
930 PERR("account ID for this transaction is unavailable!");
931 return 0;
932 }
933
934 gnc_utf8_strip_invalid (data.account_id);
935
936 import_account = gnc_import_select_account(GTK_WIDGET(info->parent),
937 data.account_id,
938 0, NULL, NULL, ACCT_TYPE_NONE,
939 info->last_import_account, NULL);
940 if (import_account == NULL)
941 {
942 PERR("Unable to find account for id %s", data.account_id);
943 return 0;
944 }
945 info->last_import_account = import_account;
946 /***** Validate the input strings to ensure utf8 *****/
947 if (data.name_valid)
948 gnc_utf8_strip_invalid(data.name);
949 if (data.memo_valid)
950 gnc_utf8_strip_invalid(data.memo);
951 if (data.check_number_valid)
952 gnc_utf8_strip_invalid(data.check_number);
953 if (data.reference_number_valid)
954 gnc_utf8_strip_invalid(data.reference_number);
955
956 /***** Create the transaction and setup transaction data *******/
957 book = gnc_account_get_book(import_account);
958 transaction = xaccMallocTransaction(book);
959 xaccTransBeginEdit(transaction);
960
961 set_transaction_dates(transaction, &data);
962 fill_transaction_description(transaction, &data);
963 fill_transaction_notes(transaction, &data);
964
965 if (data.account_ptr && data.account_ptr->currency_valid)
966 {
967 DEBUG("Currency from libofx: %s", data.account_ptr->currency);
968 currency = gnc_commodity_table_lookup( gnc_get_current_commodities (),
969 GNC_COMMODITY_NS_CURRENCY,
970 data.account_ptr->currency);
971 }
972 else
973 {
974 DEBUG("Currency from libofx unavailable, defaulting to account's default");
975 currency = xaccAccountGetCommodity(import_account);
976 }
977
978 xaccTransSetCurrency(transaction, currency);
979
980 if (!data.invtransactiontype_valid
981#ifdef HAVE_LIBOFX_VERSION_0_10
982 || data.invtransactiontype == OFX_INVBANKTRAN
983#endif
984 )
985 process_bank_transaction(transaction, import_account, &data, info);
986 else if (data.unique_id_valid
987 && data.security_data_valid
988 && data.security_data_ptr != NULL
989 && data.security_data_ptr->secname_valid)
990 process_investment_transaction(transaction, import_account,
991 &data, info);
992 else
993 {
994 PERR("Unsupported OFX transaction type.");
995 xaccTransDestroy(transaction);
996 xaccTransCommitEdit(transaction);
997 return 0;
998 }
999
1000 /* Send transaction to importer GUI. */
1001 if (xaccTransCountSplits(transaction) > 0)
1002 {
1003 DEBUG("%d splits sent to the importer gui",
1004 xaccTransCountSplits(transaction));
1005 info->trans_list = g_list_prepend (info->trans_list, transaction);
1006 }
1007 else
1008 {
1009 PERR("No splits in transaction (missing account?), ignoring.");
1010 xaccTransDestroy(transaction);
1011 xaccTransCommitEdit(transaction);
1012 }
1013
1014 info->num_trans_processed += 1;
1015 return 0;
1016}//end ofx_proc_transaction()
1017
1018
1019int ofx_proc_statement_cb (struct OfxStatementData data, void * statement_user_data)
1020{
1021 ofx_info* info = (ofx_info*) statement_user_data;
1022 struct OfxStatementData *statement = g_new (struct OfxStatementData, 1);
1023 *statement = data;
1024 info->statement = g_list_prepend (info->statement, statement);
1025 return 0;
1026}
1027
1028
1029int ofx_proc_account_cb(struct OfxAccountData data, void * account_user_data)
1030{
1031 gnc_commodity_table * commodity_table;
1032 gnc_commodity * default_commodity;
1033 GNCAccountType default_type = ACCT_TYPE_NONE;
1034 gchar * account_description;
1035 GtkWidget * main_widget;
1036 GtkWidget * parent;
1037 /* In order to trigger a book options display on the creation of a new book,
1038 * we need to detect when we are dealing with a new book. */
1039 gboolean new_book = gnc_is_new_book();
1040 ofx_info* info = (ofx_info*) account_user_data;
1041 Account* account = NULL;
1042
1043 const gchar * account_type_name = _("Unknown OFX account");
1044
1045 if (data.account_id_valid)
1046 {
1047 commodity_table = gnc_get_current_commodities ();
1048 if (data.currency_valid)
1049 {
1050 DEBUG("Currency from libofx: %s", data.currency);
1051 default_commodity = gnc_commodity_table_lookup(commodity_table,
1052 GNC_COMMODITY_NS_CURRENCY,
1053 data.currency);
1054 }
1055 else
1056 {
1057 default_commodity = NULL;
1058 }
1059
1060 if (data.account_type_valid)
1061 {
1062 switch (data.account_type)
1063 {
1064 case OfxAccountData::OFX_CHECKING:
1065 default_type = ACCT_TYPE_BANK;
1066 account_type_name = _("Unknown OFX checking account");
1067 break;
1068 case OfxAccountData::OFX_SAVINGS:
1069 default_type = ACCT_TYPE_BANK;
1070 account_type_name = _("Unknown OFX savings account");
1071 break;
1072 case OfxAccountData::OFX_MONEYMRKT:
1073 default_type = ACCT_TYPE_MONEYMRKT;
1074 account_type_name = _("Unknown OFX money market account");
1075 break;
1076 case OfxAccountData::OFX_CREDITLINE:
1077 default_type = ACCT_TYPE_CREDITLINE;
1078 account_type_name = _("Unknown OFX credit line account");
1079 break;
1080 case OfxAccountData::OFX_CMA:
1081 default_type = ACCT_TYPE_NONE;
1082 /* Cash Management Account */
1083 account_type_name = _("Unknown OFX CMA account");
1084 break;
1085 case OfxAccountData::OFX_CREDITCARD:
1086 default_type = ACCT_TYPE_CREDIT;
1087 account_type_name = _("Unknown OFX credit card account");
1088 break;
1089 case OfxAccountData::OFX_INVESTMENT:
1090 default_type = ACCT_TYPE_BANK;
1091 account_type_name = _("Unknown OFX investment account");
1092 break;
1093 default:
1094 PERR("WRITEME: ofx_proc_account() This is an unknown account type!");
1095 break;
1096 }
1097 }
1098
1099 /* If the OFX importer was started in Gnucash in a 'new_book' situation,
1100 * as described above, the first time the 'ofx_proc_account_cb' function
1101 * is called a book is created. (This happens after the 'new_book' flag
1102 * is set in 'gnc_get_current_commodities', called above.) So, before
1103 * calling 'gnc_import_select_account', allow the user to set book
1104 * options. */
1105 if (new_book)
1106 gnc_new_book_option_display (GTK_WIDGET (gnc_ui_get_main_window (NULL)));
1107
1108 gnc_utf8_strip_invalid(data.account_name);
1109 gnc_utf8_strip_invalid(data.account_id);
1110 account_description = g_strdup_printf (/* This string is a default account
1111 name. It MUST NOT contain the
1112 character ':' anywhere in it or
1113 in any translation. */
1114 "%s \"%s\"",
1115 account_type_name,
1116 data.account_name);
1117
1118 main_widget = gnc_gen_trans_list_widget (info->gnc_ofx_importer_gui);
1119
1120 /* On first use, the import-main-matcher is hidden / not realized so to
1121 * get a parent use the transient parent of the matcher */
1122 if (gtk_widget_get_realized (main_widget))
1123 parent = main_widget;
1124 else
1125 parent = GTK_WIDGET(gtk_window_get_transient_for (GTK_WINDOW(main_widget)));
1126
1127 account = gnc_import_select_account (parent,
1128 data.account_id, 1,
1129 account_description, default_commodity,
1130 default_type, NULL, NULL);
1131
1132 if (account)
1133 {
1134 info->last_import_account = account;
1135 }
1136
1137 g_free(account_description);
1138 }
1139 else
1140 {
1141 PERR("account online ID not available");
1142 }
1143
1144 return 0;
1145}
1146
1147double ofx_get_investment_amount(const OfxTransactionData* data)
1148{
1149 double amount = data->amount;
1150#ifdef HAVE_LIBOFX_VERSION_0_10
1151 if (data->invtransactiontype == OFX_INVBANKTRAN)
1152 return 0.0;
1153 if (data->currency_ratio_valid && data->currency_ratio != 0)
1154 amount *= data->currency_ratio;
1155#endif
1156 g_assert(data);
1157 switch (data->invtransactiontype)
1158 {
1159 case OFX_BUYDEBT:
1160 case OFX_BUYMF:
1161 case OFX_BUYOPT:
1162 case OFX_BUYOTHER:
1163 case OFX_BUYSTOCK:
1164 return fabs(amount);
1165 case OFX_SELLDEBT:
1166 case OFX_SELLMF:
1167 case OFX_SELLOPT:
1168 case OFX_SELLOTHER:
1169 case OFX_SELLSTOCK:
1170 return -1 * fabs(amount);
1171 default:
1172 return -1 * amount;
1173 }
1174}
1175
1176// Forward declaration, required because several static functions depend on one-another.
1177static void
1178gnc_file_ofx_import_process_file (ofx_info* info);
1179
1180// gnc_ofx_process_next_file processes the next file in the info->file_list.
1181static void
1182gnc_ofx_process_next_file (GtkDialog *dialog, gpointer user_data)
1183{
1184 ofx_info* info = (ofx_info*) user_data;
1185 // Free the statement (if it was allocated)
1186 g_list_free_full (info->statement, g_free);
1187 info->statement = NULL;
1188
1189 // Done with the previous OFX file, process the next one if any.
1190 info->file_list = g_slist_delete_link (info->file_list, info->file_list);
1191 if (info->file_list)
1192 gnc_file_ofx_import_process_file (info);
1193 else
1194 {
1195 // Final cleanup.
1196 g_free (info);
1197 }
1198}
1199
1200static void
1201gnc_ofx_on_match_click (GtkDialog *dialog, gint response_id, gpointer user_data)
1202{
1203 // Record the response of the user. If cancel we won't go to the next file, etc.
1204 ofx_info* info = (ofx_info*)user_data;
1205 info->response = response_id;
1206}
1207
1208static void
1209gnc_ofx_match_done (GtkDialog *dialog, gpointer user_data)
1210{
1211 ofx_info* info = (ofx_info*) user_data;
1212
1213 /* The the user did not click OK, don't process the rest of the
1214 * transaction, don't go to the next of xfile.
1215 */
1216 if (info->response != GTK_RESPONSE_OK)
1217 return;
1218
1219 if (info->trans_list)
1220 {
1221 /* Re-run the match dialog if there are transactions
1222 * remaining in our list (happens if several accounts exist
1223 * in the same ofx).
1224 */
1225 info->gnc_ofx_importer_gui = gnc_gen_trans_list_new (GTK_WIDGET (info->parent), NULL, FALSE, 42, FALSE);
1226 runMatcher (info, NULL, true);
1227 return;
1228 }
1229
1230 if (info->run_reconcile && info->statement && info->statement->data)
1231 {
1232 auto statement = static_cast<struct OfxStatementData*>(info->statement->data);
1233 // Open a reconcile window.
1234 Account* account = gnc_import_select_account (gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
1235 statement->account_id,
1236 0, NULL, NULL, ACCT_TYPE_NONE, NULL, NULL);
1237 if (account && statement->ledger_balance_valid)
1238 {
1239 gnc_numeric value = double_to_gnc_numeric (statement->ledger_balance,
1241 GNC_HOW_RND_ROUND_HALF_UP);
1242
1243 RecnWindow* rec_window = recnWindowWithBalance (GTK_WIDGET (info->parent), account, value,
1244 statement->ledger_balance_date);
1245
1246 // Connect to destroy, at which point we'll process the next OFX file..
1247 g_signal_connect (G_OBJECT (gnc_ui_reconcile_window_get_window (rec_window)), "destroy",
1248 G_CALLBACK (gnc_ofx_match_done), info);
1249 if (info->statement->next)
1250 info->statement = info->statement->next;
1251 else
1252 {
1253 g_list_free_full (g_list_first (info->statement), g_free);
1254 info->statement = NULL;
1255 }
1256 return;
1257 }
1258 }
1259 else
1260 {
1261 if (info->statement && info->statement->next)
1262 {
1263 info->statement = info->statement->next;
1264 gnc_ofx_match_done (dialog, user_data);
1265 return;
1266 }
1267 else
1268 {
1269 g_list_free_full (g_list_first (info->statement), g_free);
1270 info->statement = NULL;
1271 }
1272 }
1273 gnc_ofx_process_next_file (NULL, info);
1274}
1275
1276// This callback is triggered when the user checks or unchecks the reconcile after match
1277// check box in the matching dialog.
1278static void
1279reconcile_when_close_toggled_cb (GtkToggleButton *togglebutton, ofx_info* info)
1280{
1281 info->run_reconcile = gtk_toggle_button_get_active (togglebutton);
1282}
1283
1284static std::string
1285make_date_amount_key (const Split* split)
1286{
1287 std::ostringstream ss;
1288 auto _amount = gnc_numeric_reduce (gnc_numeric_abs (xaccSplitGetAmount (split)));
1289 ss << _amount.num << '/' << _amount.denom << ' ' << xaccTransGetDate (xaccSplitGetParent (split));
1290 return ss.str();
1291}
1292
1293static void
1294runMatcher (ofx_info* info, char * selected_filename, gboolean go_to_next_file)
1295{
1296 GtkWindow *parent = info->parent;
1297 GList* trans_list_remain = NULL;
1298 std::unordered_map <std::string,Account*> trans_map;
1299
1300 /* If we have multiple accounts in the ofx file, we need to
1301 * avoid processing transfers between accounts together because this will
1302 * create duplicate entries.
1303 */
1304 info->num_trans_processed = 0;
1305
1306 gnc_window_show_progress (_("Removing duplicate transactions…"), 100);
1307
1308 // Add transactions, but verify that there isn't one that was
1309 // already added with identical amounts and date, and a different
1310 // account. To do that, create a hash table whose key is a hash of
1311 // amount and date, and whose value is the account in which they
1312 // appear.
1313 for(GList* node = info->trans_list; node; node=node->next)
1314 {
1315 auto trans = static_cast<Transaction*>(node->data);
1316 Split* split = xaccTransGetSplit (trans, 0);
1317 Account* account = xaccSplitGetAccount (split);
1318 auto date_amount_key = make_date_amount_key (split);
1319
1320 auto it = trans_map.find (date_amount_key);
1321 if (it != trans_map.end() && it->second != account)
1322 {
1323 if (qof_log_check (G_LOG_DOMAIN, QOF_LOG_DEBUG))
1324 {
1325 // There is a transaction with identical amounts and
1326 // dates, but a different account. That's a potential
1327 // transfer so process this transaction in a later call.
1328 gchar *name1 = gnc_account_get_full_name (account);
1329 gchar *name2 = gnc_account_get_full_name (it->second);
1330 gchar *amtstr = gnc_numeric_to_string (xaccSplitGetAmount (split));
1331 gchar *datestr = qof_print_date (xaccTransGetDate (trans));
1332 DEBUG ("Potential transfer %s %s %s %s\n", name1, name2, amtstr, datestr);
1333 g_free (name1);
1334 g_free (name2);
1335 g_free (amtstr);
1336 g_free (datestr);
1337 }
1338 trans_list_remain = g_list_prepend (trans_list_remain, trans);
1339 }
1340 else
1341 {
1342 trans_map[date_amount_key] = account;
1343 gnc_gen_trans_list_add_trans (info->gnc_ofx_importer_gui, trans);
1344 info->num_trans_processed ++;
1345 }
1346 }
1347 g_list_free (info->trans_list);
1348 info->trans_list = g_list_reverse (trans_list_remain);
1349 DEBUG("%d transactions remaining to process in file %s\n", g_list_length (info->trans_list),
1350 selected_filename);
1351
1352 gnc_window_show_progress (nullptr, -1);
1353
1354 // See whether the view has anything in it and warn the user if not.
1355 if (gnc_gen_trans_list_empty (info->gnc_ofx_importer_gui))
1356 {
1357 gnc_gen_trans_list_delete (info->gnc_ofx_importer_gui);
1358 if (info->num_trans_processed)
1359 {
1360 gnc_info_dialog (parent, _("While importing transactions from OFX file '%s' found %d previously imported transactions, no new transactions."),
1361 selected_filename,
1362 info->num_trans_processed);
1363 // This is required to ensure we don't mistakenly assume the user canceled.
1364 info->response = GTK_RESPONSE_OK;
1365 gnc_ofx_match_done (NULL, info);
1366 return;
1367 }
1368 }
1369 else
1370 {
1371 /* Show the match dialog and connect to the "destroy" signal
1372 so we can trigger a reconcile when the user clicks OK when
1373 done matching transactions if required. Connecting to
1374 response isn't enough because only when the matcher is
1375 destroyed do imported transactions get recorded */
1376 g_signal_connect (G_OBJECT (gnc_gen_trans_list_widget (info->gnc_ofx_importer_gui)),
1377 "destroy",
1378 G_CALLBACK (gnc_ofx_match_done),
1379 info);
1380
1381 // Connect to response so we know if the user pressed "cancel".
1382 g_signal_connect (G_OBJECT (gnc_gen_trans_list_widget (info->gnc_ofx_importer_gui)),
1383 "response",
1384 G_CALLBACK (gnc_ofx_on_match_click),
1385 info);
1386
1387 gnc_gen_trans_list_show_all (info->gnc_ofx_importer_gui);
1388
1389 // Show or hide the check box for reconciling after match,
1390 // depending on whether a statement was received.
1391 gnc_gen_trans_list_show_reconcile_after_close_button (info->gnc_ofx_importer_gui,
1392 info->statement != NULL,
1393 info->run_reconcile);
1394
1395 // Finally connect to the reconcile after match check box so
1396 // we can be notified if the user wants/does not want to
1397 // reconcile.
1398 g_signal_connect (G_OBJECT (gnc_gen_trans_list_get_reconcile_after_close_button
1399 (info->gnc_ofx_importer_gui)),
1400 "toggled",
1401 G_CALLBACK (reconcile_when_close_toggled_cb),
1402 info);
1403 }
1404}
1405
1406// Aux function to process the OFX file in info->file_list
1407static void
1408gnc_file_ofx_import_process_file (ofx_info* info)
1409{
1410 LibofxContextPtr libofx_context;
1411 char* filename = NULL;
1412 char * selected_filename = NULL;
1413 GtkWindow *parent = info->parent;
1414
1415 if (info->file_list == NULL)
1416 return;
1417
1418 filename = static_cast<char*>(info->file_list->data);
1419 libofx_context = libofx_get_new_context();
1420
1421#ifdef G_OS_WIN32
1422 selected_filename = g_win32_locale_filename_from_utf8 (filename);
1423 g_free (filename);
1424#else
1425 selected_filename = filename;
1426#endif
1427 DEBUG("Filename found: %s", selected_filename);
1428
1429 // Reset the reconciliation information.
1430 info->num_trans_processed = 0;
1431 info->statement = NULL;
1432
1433 /* Initialize libofx and set the callbacks*/
1434 ofx_set_statement_cb (libofx_context, ofx_proc_statement_cb, info);
1435 ofx_set_account_cb (libofx_context, ofx_proc_account_cb, info);
1436 ofx_set_transaction_cb (libofx_context, ofx_proc_transaction_cb, info);
1437 ofx_set_security_cb (libofx_context, ofx_proc_security_cb, info);
1438 /*ofx_set_status_cb(libofx_context, ofx_proc_status_cb, 0);*/
1439
1440 // Create the match dialog, and run the ofx file through the importer.
1441 info->gnc_ofx_importer_gui = gnc_gen_trans_list_new (GTK_WIDGET(parent), NULL, FALSE, 42, FALSE);
1442 libofx_proc_file (libofx_context, selected_filename, AUTODETECT);
1443
1444 // Free the libofx context before recursing to process the next file
1445 libofx_free_context(libofx_context);
1446 runMatcher(info, selected_filename,true);
1447 g_free(selected_filename);
1448}
1449
1450// The main import function. Starts the chain of file imports (if there are several)
1451void gnc_file_ofx_import (GtkWindow *parent)
1452{
1453 extern int ofx_PARSER_msg;
1454 extern int ofx_DEBUG_msg;
1455 extern int ofx_WARNING_msg;
1456 extern int ofx_ERROR_msg;
1457 extern int ofx_INFO_msg;
1458 extern int ofx_STATUS_msg;
1459 GSList* selected_filenames = NULL;
1460 char *default_dir;
1461 GList *filters = NULL;
1462 ofx_info* info = NULL;
1463 GtkFileFilter* filter = gtk_file_filter_new ();
1464
1465
1466 ofx_PARSER_msg = false;
1467 ofx_DEBUG_msg = false;
1468 ofx_WARNING_msg = true;
1469 ofx_ERROR_msg = true;
1470 ofx_INFO_msg = true;
1471 ofx_STATUS_msg = false;
1472
1473 DEBUG("gnc_file_ofx_import(): Begin...\n");
1474
1475 default_dir = gnc_get_default_directory(GNC_PREFS_GROUP);
1476 gtk_file_filter_set_name (filter, _("Open/Quicken Financial Exchange file (*.ofx, *.qfx)"));
1477 gtk_file_filter_add_pattern (filter, "*.[oqOQ][fF][xX]");
1478 filters = g_list_prepend( filters, filter );
1479
1480 selected_filenames = gnc_file_dialog_multi (parent,
1481 _("Select one or multiple OFX/QFX file(s) to process"),
1482 filters,
1483 default_dir,
1484 GNC_FILE_DIALOG_IMPORT);
1485 g_free(default_dir);
1486
1487 if (selected_filenames)
1488 {
1489 /* Remember the directory as the default. */
1490 default_dir = g_path_get_dirname(static_cast<char*>(selected_filenames->data));
1491 gnc_set_default_directory(GNC_PREFS_GROUP, default_dir);
1492 g_free(default_dir);
1493
1494 /* Look up the needed preferences */
1495 auto_create_commodity =
1496 gnc_prefs_get_bool (GNC_PREFS_GROUP_IMPORT, GNC_PREF_AUTO_COMMODITY);
1497
1498 DEBUG("Opening selected file(s)");
1499 // Create the structure that holds the list of files to process and the statement info.
1500 info = g_new(ofx_info,1);
1501 info->num_trans_processed = 0;
1502 info->statement = NULL;
1503 info->last_investment_account = NULL;
1504 info->last_import_account = NULL;
1505 info->last_income_account = NULL;
1506 info->parent = parent;
1507 info->run_reconcile = FALSE;
1508 info->file_list = selected_filenames;
1509 info->trans_list = NULL;
1510 info->response = 0;
1511 // Call the aux import function.
1512 gnc_file_ofx_import_process_file (info);
1513 }
1514}
1515
1516
Account handling public routines.
API for Transactions and Splits (journal entries)
This file contains the functions to present a gui to the user for creating a new account or editing a...
All type declarations for the whole Gnucash engine.
GLib helper routines.
Ofx import module interface.
Generic api to store and retrieve preferences.
utility functions for the GnuCash UI
Functions that are supported by all types of windows.
const char * xaccAccountGetName(const Account *acc)
Get the account's name.
Definition Account.cpp:3289
void xaccAccountSetOnlineID(Account *acc, const char *id)
Set the account's online_id, the identifier (e.g.
Definition Account.cpp:2661
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
Definition Account.cpp:1516
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition Account.cpp:1475
GNCAccountType
The account types are used to determine how the transaction data in the account is displayed.
Definition Account.h:103
gchar * gnc_account_get_full_name(const Account *account)
The gnc_account_get_full_name routine returns the fully qualified name of the account using the given...
Definition Account.cpp:3305
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account's account type.
Definition Account.cpp:3267
int xaccAccountGetCommoditySCU(const Account *acc)
Return the SCU for the account.
Definition Account.cpp:2745
gboolean gnc_account_is_root(const Account *account)
This routine indicates whether the specified account is the root node of an account tree.
Definition Account.cpp:2953
#define xaccAccountGetGUID(X)
Definition Account.h:252
Account * gnc_account_get_parent(const Account *acc)
This routine returns a pointer to the parent of the specified account.
Definition Account.cpp:2935
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account's commodity
Definition Account.cpp:3408
Account * xaccAccountLookup(const GncGUID *guid, QofBook *book)
The xaccAccountLookup() subroutine will return the account associated with the given id,...
Definition Account.cpp:2050
@ ACCT_TYPE_CREDITLINE
line of credit – don't use this for now, see NUM_ACCOUNT_TYPES
Definition Account.h:171
@ ACCT_TYPE_BANK
The bank account type denotes a savings or checking account held at a bank.
Definition Account.h:107
@ ACCT_TYPE_MONEYMRKT
bank account type – don't use this for now, see NUM_ACCOUNT_TYPES
Definition Account.h:169
@ ACCT_TYPE_CREDIT
The Credit card account is used to denote credit (e.g.
Definition Account.h:113
@ ACCT_TYPE_NONE
Not a type.
Definition Account.h:105
const char * gnc_commodity_get_fullname(const gnc_commodity *cm)
Retrieve the full name for the specified commodity.
int gnc_commodity_get_fraction(const gnc_commodity *cm)
Retrieve the fraction for the specified commodity.
gnc_commodity * gnc_commodity_new(QofBook *book, const char *fullname, const char *name_space, const char *mnemonic, const char *cusip, int fraction)
Create a new commodity.
void gnc_commodity_set_quote_source(gnc_commodity *cm, gnc_quote_source *src)
Set the automatic price quote source for the specified commodity.
gnc_quote_source * gnc_quote_source_lookup_by_ti(QuoteSourceType type, gint index)
Given the type/index of a quote source, find the data structure identified by this pair.
gnc_commodity * gnc_commodity_table_insert(gnc_commodity_table *table, gnc_commodity *comm)
Add a new commodity to the commodity table.
void gnc_commodity_user_set_quote_flag(gnc_commodity *cm, const gboolean flag)
Set the automatic price quote flag for the specified commodity, based on user input.
@ SOURCE_SINGLE
This quote source pulls from a single specific web site.
char * gnc_time64_to_iso8601_buff(time64 time, char *buff)
The gnc_time64_to_iso8601_buff() routine takes the input UTC time64 value and prints it as an ISO-860...
#define MAX_DATE_LENGTH
The maximum length of a string created by the date printers.
Definition gnc-date.h:108
struct tm * gnc_localtime_r(const time64 *secs, struct tm *time)
fill out a time struct from a 64-bit time value adjusted for the current time zone.
Definition gnc-date.cpp:115
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
time64 gnc_time(time64 *tbuf)
get the current time
Definition gnc-date.cpp:262
void xaccTransSetDescription(Transaction *trans, const char *desc)
Sets the transaction Description.
gnc_commodity * xaccTransGetCurrency(const Transaction *trans)
Returns the valuation commodity of this transaction.
gboolean xaccAccountTypesCompatible(GNCAccountType parent_type, GNCAccountType child_type)
Return TRUE if accounts of type parent_type can have accounts of type child_type as children.
Definition Account.cpp:4454
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...
Split * xaccTransGetSplit(const Transaction *trans, int i)
Return a pointer to the indexed split in this transaction's split list.
void xaccTransSetDateEnteredSecs(Transaction *trans, time64 secs)
Modify the date of when the transaction was entered.
Transaction * xaccMallocTransaction(QofBook *book)
The xaccMallocTransaction() will malloc memory and initialize it.
void xaccTransSetDatePostedSecsNormalized(Transaction *trans, time64 time)
This function sets the posted date of the transaction, specified by a time64 (see ctime(3)).
void xaccTransSetCurrency(Transaction *trans, gnc_commodity *curr)
Set a new currency on a transaction.
int xaccTransCountSplits(const Transaction *trans)
Returns the number of splits in this transaction.
#define xaccAccountInsertSplit(acc, s)
The xaccAccountInsertSplit() method will insert the indicated split into the indicated account.
Definition Account.h:1069
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.
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
GtkWindow * gnc_ui_get_main_window(GtkWidget *widget)
Get a pointer to the final GncMainWindow widget is rooted in.
Account * gnc_ui_new_accounts_from_name_with_defaults(GtkWindow *parent, const char *name, GList *valid_types, const gnc_commodity *default_commodity, Account *parent_acct)
Display a modal window for creating a new account.
gchar * gnc_utf8_strip_invalid_strdup(const gchar *str)
Returns a newly allocated copy of the given string but with any non-UTF-8 character stripped from it.
void gnc_utf8_strip_invalid(gchar *str)
Strip any non-UTF-8 characters from a string.
void gnc_gen_trans_list_show_reconcile_after_close_button(GNCImportMainMatcher *info, bool reconcile_after_close, bool active)
Show and set the reconcile after close check button.
GNCImportMainMatcher * gnc_gen_trans_list_new(GtkWidget *parent, const gchar *heading, bool all_from_same_account, gint match_date_hardlimit, bool show_all)
Create a new generic transaction dialog window and return it.
gnc_commodity * gnc_import_select_commodity(const char *cusip, gboolean ask_on_unknown, const char *default_fullname, const char *default_mnemonic)
Must be called with a string containing a unique identifier for the commodity.
void gnc_file_ofx_import(GtkWindow *parent)
The gnc_file_ofx_import() routine will pop up a standard file selection dialogue asking the user to p...
bool gnc_gen_trans_list_empty(GNCImportMainMatcher *info)
Checks whether there are no transactions to match.
void gnc_gen_trans_list_add_trans(GNCImportMainMatcher *gui, Transaction *trans)
Add a newly imported Transaction to the Transaction Importer.
void gnc_gen_trans_list_show_all(GNCImportMainMatcher *info)
Shows widgets.
GtkWidget * gnc_gen_trans_list_get_reconcile_after_close_button(GNCImportMainMatcher *info)
Returns the reconcile after close check button.
Account * gnc_import_select_account(GtkWidget *parent, const gchar *account_online_id_value, gboolean prompt_on_no_match, const gchar *account_human_description, const gnc_commodity *new_account_default_commodity, GNCAccountType new_account_default_type, Account *default_selection, gboolean *ok_pressed)
Must be called with a string containing a unique identifier for the account.
GtkWidget * gnc_gen_trans_list_widget(GNCImportMainMatcher *info)
Returns the widget of this dialog.
void gnc_gen_trans_list_delete(GNCImportMainMatcher *info)
Deletes the given object.
#define GNC_PREFS_GROUP_IMPORT
The preferences used by the importer.
QofBook * qof_instance_get_book(gconstpointer inst)
Return the book pointer.
void qof_instance_set(QofInstance *inst, const gchar *first_prop,...)
Wrapper for g_object_set Group setting multiple parameters in a single begin/commit/rollback.
void qof_instance_get(const QofInstance *inst, const gchar *first_prop,...)
Wrapper for g_object_get.
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
gboolean qof_log_check(QofLogModule domain, QofLogLevel level)
Check to see if the given log_module is configured to log at the given log_level.
Definition qoflog.cpp:330
gchar * gnc_numeric_to_string(gnc_numeric n)
Convert to string.
gnc_numeric double_to_gnc_numeric(double in, gint64 denom, gint how)
Convert a floating-point number to a gnc_numeric.
gnc_numeric gnc_numeric_abs(gnc_numeric a)
Returns a newly created gnc_numeric that is the absolute value of the given gnc_numeric value.
gnc_numeric gnc_numeric_reduce(gnc_numeric in)
Return input after reducing it by Greater Common Factor (GCF) elimination.
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 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...
Account * xaccSplitGetAccount(const Split *split)
Returns the account of this split, which was set through xaccAccountInsertSplit().
void xaccSplitSetOnlineID(Split *split, const char *id)
The online_id is the OFX/HBCI "FITID" recorded on a split when it is imported.
void xaccSplitSetBaseValue(Split *s, gnc_numeric value, const gnc_commodity *base_currency)
Depending on the base_currency, set either the value or the amount of this split or both: If the base...
Definition Split.cpp:1355
gnc_numeric xaccSplitGetAmount(const Split *split)
Returns the amount of the split in the account's commodity.
Generic and very flexible account matcher/picker.
A Generic commodity matcher/picker.
Transaction matcher main window.
Preference keys for the generic importer.
STRUCTS.
The type used to store guids in C.
Definition guid.h:75
QofBook reference.
Definition qofbook-p.hpp:47