1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2018 Maxime DOYEN
4 * This file is part of HomeBank.
6 * HomeBank is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * HomeBank is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "hb-import.h"
25 #include <libofx/libofx.h>
29 /****************************************************************************/
31 /****************************************************************************/
40 /* our global datas */
41 extern struct HomeBank
*GLOBALS
;
42 extern struct Preferences
*PREFS
;
50 **** this part is quite weird,but works
55 static Account
* ofx_get_account_by_id(gchar
*id
)
59 DB( g_print("\n[import] ofx_get_account_by_id\n") );
60 DB( g_print(" -> searching for '%s'\n",id
) );
62 lacc
= list
= g_hash_table_get_values(GLOBALS
->h_acc
);
65 Account
*accitem
= list
->data
;
67 if( accitem
->imported
== FALSE
)
69 if(accitem
->name
&& accitem
->number
&& strlen(accitem
->number
) )
71 // todo: maybe smartness should be done here
72 if(g_strstr_len(id
, -1, accitem
->number
) != NULL
)
78 list
= g_list_next(list
);
84 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
87 * ofx_proc_account_cb:
89 * The ofx_proc_account_cb event is always generated first, to allow the application to create accounts
90 * or ask the user to match an existing account before the ofx_proc_statement and ofx_proc_transaction
91 * event are received. An OfxAccountData is passed to this event.
94 static LibofxProcStatementCallback
95 ofx_proc_account_cb(const struct OfxAccountData data
, OfxContext
*ctx
)
97 Account
*tmp_acc
, *dst_acc
;
99 DB( g_print("** ofx_proc_account_cb()\n") );
101 if(data
.account_id_valid
==true)
103 DB( g_print(" account_id: '%s'\n", data
.account_id
) );
104 DB( g_print(" account_name: '%s'\n", data
.account_name
) );
107 //if(data.account_number_valid==true)
109 DB( g_print(" account_number: '%s'\n", data
.account_number
) );
113 if(data
.account_type_valid
==true)
115 DB( g_print(" account_type: '%d'\n", data
.account_type
) );
118 OFX_CHECKING A standard checking account
119 OFX_SAVINGS A standard savings account
120 OFX_MONEYMRKT A money market account
121 OFX_CREDITLINE A line of credit
122 OFX_CMA Cash Management Account
123 OFX_CREDITCARD A credit card account
124 OFX_INVESTMENT An investment account
128 if(data
.currency_valid
==true)
130 DB( g_print(" currency: '%s'\n", data
.currency
) );
134 //find target account
135 dst_acc
= ofx_get_account_by_id( (gchar
*)data
.account_id
);
136 DB( g_print(" ** hb account found result is %x\n", (unsigned int)dst_acc
) );
139 // in every case we create an account here
140 tmp_acc
= import_create_account((gchar
*)data
.account_name
, (gchar
*)data
.account_id
);
141 DB( g_print(" -> creating tmp account: %d %s - %x\n", tmp_acc
->key
, data
.account_id
, (unsigned int)tmp_acc
) );
143 if( dst_acc
!= NULL
)
145 tmp_acc
->imp_key
= dst_acc
->key
;
149 ctx
->curr_acc
= tmp_acc
;
150 ctx
->curr_acc_isnew
= TRUE
;
159 DB( fputs("\n",stdout
) );
165 * ofx_proc_statement_cb:
167 * The ofx_proc_statement_cb event is sent after all ofx_proc_transaction events have been sent.
168 * An OfxStatementData is passed to this event.
171 static LibofxProcStatementCallback
172 ofx_proc_statement_cb(const struct OfxStatementData data
, OfxContext
*ctx
)
174 DB( g_print("** ofx_proc_statement_cb()\n") );
177 if(data
.ledger_balance_date_valid
==true)
181 temp_tm
= *localtime(&(data
.ledger_balance_date
));
182 g_print("ledger_balance_date : %d%s%d%s%d%s", temp_tm
.tm_mday
, "/", temp_tm
.tm_mon
+1, "/", temp_tm
.tm_year
+1900, "\n");
186 if(data
.ledger_balance_valid
==true)
188 if( ctx
->curr_acc
!= NULL
&& ctx
->curr_acc_isnew
== TRUE
)
190 ctx
->curr_acc
->initial
= data
.ledger_balance
;
192 DB( g_print("ledger_balance: $%.2f%s",data
.ledger_balance
,"\n") );
199 * ofx_proc_statement_cb:
201 * An ofx_proc_transaction_cb event is generated for every transaction in the ofx response,
202 * after ofx_proc_statement (and possibly ofx_proc_security is generated.
203 * An OfxTransactionData structure is passed to this event.
206 static LibofxProcStatementCallback
207 ofx_proc_transaction_cb(const struct OfxTransactionData data
, OfxContext
*ctx
)
213 DB( g_print("\n** ofx_proc_transaction_cb()\n") );
215 newope
= da_transaction_malloc();
219 if(data
.date_posted_valid
&& (data
.date_posted
!= 0))
221 temp_tm
= localtime(&data
.date_posted
);
224 g_date_set_dmy(&date
, temp_tm
->tm_mday
, temp_tm
->tm_mon
+1, temp_tm
->tm_year
+1900);
225 newope
->date
= g_date_get_julian(&date
);
228 else if (data
.date_initiated_valid
&& (data
.date_initiated
!= 0))
230 temp_tm
= localtime(&data
.date_initiated
);
231 g_date_set_dmy(&date
, temp_tm
->tm_mday
, temp_tm
->tm_mon
+1, temp_tm
->tm_year
+1900);
232 newope
->date
= g_date_get_julian(&date
);
236 if(data
.amount_valid
==true)
238 newope
->amount
= data
.amount
;
241 // check number :: The check number is most likely an integer and can probably be converted properly with atoi().
242 //However the spec allows for up to 12 digits, so it is not garanteed to work
243 if(data
.check_number_valid
==true)
245 newope
->info
= g_strdup(data
.check_number
);
247 //todo: reference_number ?Might present in addition to or instead of a check_number. Not necessarily a number
254 // ofx:name = Can be the name of the payee or the description of the transaction
255 if(data
.name_valid
==true)
260 //#462919 name to payee or memo
261 DB( g_print(" -> ofxname option: '%d'\n", PREFS
->dtex_ofxname
) );
262 switch(PREFS
->dtex_ofxname
)
265 DB( g_print(" -> name to memo: '%s'\n", data
.name
) );
266 newope
->memo
= g_strdup(data
.name
);
269 //strip_extra_spaces(newope->wording);
273 //manage memo append to payee as well
274 if( (data
.memo_valid
==true) && (PREFS
->dtex_ofxmemo
== 3) )
276 name
= g_strjoin(" ", data
.name
, data
.memo
, NULL
);
279 name
= g_strdup(data
.name
);
283 //strip_extra_spaces(name);
286 DB( g_print(" ensure UTF-8\n") );
288 name
= homebank_utf8_ensure(name
);
291 DB( g_print(" -> name to payee: '%s'\n", name
) );
293 payitem
= da_pay_get_by_name(name
);
296 DB( g_print(" -> create new payee\n") );
298 payitem
= da_pay_malloc();
299 payitem
->name
= name
;
300 payitem
->imported
= TRUE
;
301 da_pay_append(payitem
);
303 if( payitem
->imported
== TRUE
)
304 ctx
->ictx
->cnt_new_pay
+= 1;
311 newope
->kpay
= payitem
->key
;
316 //memo ( new for v4.2) Extra information not included in name
318 DB( g_print(" -> memo is='%d'\n", data
.memo_valid
) );
320 if(data
.memo_valid
==true)
324 DB( g_print(" -> oxfmemo option: '%d'\n", PREFS
->dtex_ofxmemo
) );
325 switch(PREFS
->dtex_ofxmemo
)
327 case 1: //add to info
330 newope
->info
= g_strdup(data
.memo
);
333 newope
->info
= g_strjoin(" ", old
, data
.memo
, NULL
);
338 case 2: //add to description
341 newope
->memo
= g_strdup(data
.memo
);
344 newope
->memo
= g_strjoin(" ", old
, data
.memo
, NULL
);
348 DB( g_print(" -> should concatenate ='%s'\n", data
.memo
) );
349 DB( g_print(" -> old='%s', new ='%s'\n", old
, newope
->wording
) );
352 //case 3 add to payee is managed above
358 if(data
.transactiontype_valid
==true)
360 switch(data
.transactiontype
)
364 if(newope
->amount
< 0)
365 newope
->amount
*= -1;
368 if(newope
->amount
> 0)
369 newope
->amount
*= -1;
372 newope
->paymode
= PAYMODE_XFER
;
375 newope
->paymode
= PAYMODE_XFER
;
378 newope
->paymode
= PAYMODE_FEE
;
381 newope
->paymode
= PAYMODE_XFER
;
384 newope
->paymode
= PAYMODE_DEPOSIT
;
387 newope
->paymode
= PAYMODE_CASH
;
390 if(ctx
->curr_acc
&& ctx
->curr_acc
->type
== ACC_TYPE_CREDITCARD
)
391 newope
->paymode
= PAYMODE_CCARD
;
393 newope
->paymode
= PAYMODE_DCARD
;
396 newope
->paymode
= PAYMODE_XFER
;
399 newope
->paymode
= PAYMODE_CHECK
;
402 newope
->paymode
= PAYMODE_EPAYMENT
;
405 newope
->paymode
= PAYMODE_CASH
;
408 newope
->paymode
= PAYMODE_DEPOSIT
;
410 case OFX_DIRECTDEBIT
:
411 newope
->paymode
= PAYMODE_XFER
;
414 newope
->paymode
= PAYMODE_REPEATPMT
;
428 newope
->kacc
= ctx
->curr_acc
->key
;
429 newope
->flags
|= OF_ADDED
;
431 if( newope
->amount
> 0)
432 newope
->flags
|= OF_INCOME
;
434 /* ensure utf-8 here, has under windows, libofx not always return utf-8 as it should */
436 DB( g_print(" ensure UTF-8\n") );
438 newope
->info
= homebank_utf8_ensure(newope
->info
);
439 newope
->wording
= homebank_utf8_ensure(newope
->wording
);
442 ctx
->trans_list
= g_list_append(ctx
->trans_list
, newope
);
444 DB( g_print(" insert newope: acc=%d\n", newope
->kacc
) );
446 if( ctx
->curr_acc_isnew
== TRUE
)
448 DB( g_print(" sub amount from initial\n") );
449 ctx
->curr_acc
->initial
-= data
.amount
;
454 da_transaction_free(newope
);
462 static LibofxProcStatusCallback
463 ofx_proc_status_cb(const struct OfxStatusData data
, OfxContext
*ctx
)
465 DB( g_print("** ofx_proc_status_cb()\n") );
467 if(data
.ofx_element_name_valid
==true){
468 DB( g_print(" Ofx entity this status is relevent to: '%s'\n", data
.ofx_element_name
) );
470 if(data
.severity_valid
==true){
471 DB( g_print(" Severity: ") );
472 switch(data
.severity
){
473 case INFO
: DB( g_print("INFO\n") );
475 case WARN
: DB( g_print("WARN\n") );
477 case ERROR
: DB( g_print("ERROR\n") );
479 default: DB( g_print("WRITEME: Unknown status severity!\n") );
482 if(data
.code_valid
==true){
483 DB( g_print(" Code: %d, name: %s\n Description: %s\n", data
.code
, data
.name
, data
.description
) );
485 if(data
.server_message_valid
==true){
486 DB( g_print(" Server Message: %s\n", data
.server_message
) );
494 GList
*homebank_ofx_import(gchar
*filename
, ImportContext
*ictx
)
496 OfxContext ctx
= { 0 };
498 /*extern int ofx_PARSER_msg;
499 extern int ofx_DEBUG_msg;
500 extern int ofx_WARNING_msg;
501 extern int ofx_ERROR_msg;
502 extern int ofx_INFO_msg;
503 extern int ofx_STATUS_msg;*/
505 DB( g_print("\n[import] ofx import (libofx=%s) \n", LIBOFX_VERSION_RELEASE_STRING
) );
507 /*ofx_PARSER_msg = false;
508 ofx_DEBUG_msg = false;
509 ofx_WARNING_msg = false;
510 ofx_ERROR_msg = false;
511 ofx_INFO_msg = false;
512 ofx_STATUS_msg = false;*/
516 LibofxContextPtr libofx_context
= libofx_get_new_context();
518 ofx_set_status_cb (libofx_context
, (LibofxProcStatusCallback
) ofx_proc_status_cb
, &ctx
);
519 ofx_set_statement_cb (libofx_context
, (LibofxProcStatementCallback
) ofx_proc_statement_cb
, &ctx
);
520 ofx_set_account_cb (libofx_context
, (LibofxProcAccountCallback
) ofx_proc_account_cb
, &ctx
);
521 ofx_set_transaction_cb(libofx_context
, (LibofxProcTransactionCallback
)ofx_proc_transaction_cb
, &ctx
);
524 //#932959: windows don't like utf8 path, so convert
525 gchar
*file
= g_win32_locale_filename_from_utf8(filename
);
526 libofx_proc_file(libofx_context
, file
, AUTODETECT
);
529 libofx_proc_file(libofx_context
, filename
, AUTODETECT
);
532 libofx_free_context(libofx_context
);
534 return ctx
.trans_list
;