]> Dogcows Code - chaz/homebank/blob - src/hb-import-ofx.c
701d7326aa0d4bbdc4dbcff0c8d32ff6844eda2a
[chaz/homebank] / src / hb-import-ofx.c
1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2018 Maxime DOYEN
3 *
4 * This file is part of HomeBank.
5 *
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.
10 *
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.
15 *
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/>.
18 */
19
20 #include "homebank.h"
21
22 #include "hb-import.h"
23
24 #ifndef NOOFX
25 #include <libofx/libofx.h>
26 #endif
27
28
29 /****************************************************************************/
30 /* Debug macros */
31 /****************************************************************************/
32 #define MYDEBUG 0
33
34 #if MYDEBUG
35 #define DB(x) (x);
36 #else
37 #define DB(x);
38 #endif
39
40 /* our global datas */
41 extern struct HomeBank *GLOBALS;
42 extern struct Preferences *PREFS;
43
44
45
46 #ifndef NOOFX
47 /*
48 **** OFX part
49 ****
50 **** this part is quite weird,but works
51 ** id is ACCTID
52
53 */
54
55 static Account * ofx_get_account_by_id(gchar *id)
56 {
57 GList *lacc, *list;
58
59 DB( g_print("\n[import] ofx_get_account_by_id\n") );
60 DB( g_print(" -> searching for '%s'\n",id) );
61
62 lacc = list = g_hash_table_get_values(GLOBALS->h_acc);
63 while (list != NULL)
64 {
65 Account *accitem = list->data;
66
67 if( accitem->imported == FALSE)
68 {
69 if(accitem->name && accitem->number && strlen(accitem->number) )
70 {
71 // todo: maybe smartness should be done here
72 if(g_strstr_len(id, -1, accitem->number) != NULL)
73 {
74 return accitem;
75 }
76 }
77 }
78 list = g_list_next(list);
79 }
80 g_list_free(lacc);
81 return NULL;
82 }
83
84 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
85
86 /**
87 * ofx_proc_account_cb:
88 *
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.
92 *
93 */
94 static LibofxProcStatementCallback
95 ofx_proc_account_cb(const struct OfxAccountData data, OfxContext *ctx)
96 {
97 Account *tmp_acc, *dst_acc;
98
99 DB( g_print("** ofx_proc_account_cb()\n") );
100
101 if(data.account_id_valid==true)
102 {
103 DB( g_print(" account_id: '%s'\n", data.account_id) );
104 DB( g_print(" account_name: '%s'\n", data.account_name) );
105 }
106
107 //if(data.account_number_valid==true)
108 //{
109 DB( g_print(" account_number: '%s'\n", data.account_number) );
110 //}
111
112
113 if(data.account_type_valid==true)
114 {
115 DB( g_print(" account_type: '%d'\n", data.account_type) );
116 /*
117 enum:
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
125 */
126 }
127
128 if(data.currency_valid==true)
129 {
130 DB( g_print(" currency: '%s'\n", data.currency) );
131 }
132
133
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) );
137
138
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) );
142
143 if( dst_acc != NULL )
144 {
145 tmp_acc->imp_key = dst_acc->key;
146 }
147
148
149 ctx->curr_acc = tmp_acc;
150 ctx->curr_acc_isnew = TRUE;
151
152
153
154
155
156
157
158
159 DB( fputs("\n",stdout) );
160 return 0;
161 }
162
163
164 /**
165 * ofx_proc_statement_cb:
166 *
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.
169 *
170 */
171 static LibofxProcStatementCallback
172 ofx_proc_statement_cb(const struct OfxStatementData data, OfxContext *ctx)
173 {
174 DB( g_print("** ofx_proc_statement_cb()\n") );
175
176 #ifdef MYDEBUG
177 if(data.ledger_balance_date_valid==true)
178 {
179 struct tm temp_tm;
180
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");
183 }
184 #endif
185
186 if(data.ledger_balance_valid==true)
187 {
188 if( ctx->curr_acc != NULL && ctx->curr_acc_isnew == TRUE )
189 {
190 ctx->curr_acc->initial = data.ledger_balance;
191 }
192 DB( g_print("ledger_balance: $%.2f%s",data.ledger_balance,"\n") );
193 }
194
195 return 0;
196 }
197
198 /**
199 * ofx_proc_statement_cb:
200 *
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.
204 *
205 */
206 static LibofxProcStatementCallback
207 ofx_proc_transaction_cb(const struct OfxTransactionData data, OfxContext *ctx)
208 {
209 struct tm *temp_tm;
210 GDate date;
211 Transaction *newope;
212
213 DB( g_print("\n** ofx_proc_transaction_cb()\n") );
214
215 newope = da_transaction_malloc();
216
217 // date
218 newope->date = 0;
219 if(data.date_posted_valid && (data.date_posted != 0))
220 {
221 temp_tm = localtime(&data.date_posted);
222 if( temp_tm != 0)
223 {
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);
226 }
227 }
228 else if (data.date_initiated_valid && (data.date_initiated != 0))
229 {
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);
233 }
234
235 // amount
236 if(data.amount_valid==true)
237 {
238 newope->amount = data.amount;
239 }
240
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)
244 {
245 newope->info = g_strdup(data.check_number);
246 }
247 //todo: reference_number ?Might present in addition to or instead of a check_number. Not necessarily a number
248
249 //ucfirst
250 //ucword
251
252
253
254 // ofx:name = Can be the name of the payee or the description of the transaction
255 if(data.name_valid==true)
256 {
257 Payee *payitem;
258 gchar *name = NULL;
259
260 //#462919 name to payee or memo
261 DB( g_print(" -> ofxname option: '%d'\n", PREFS->dtex_ofxname) );
262 switch(PREFS->dtex_ofxname)
263 {
264 case 1: //to memo
265 DB( g_print(" -> name to memo: '%s'\n", data.name) );
266 newope->memo = g_strdup(data.name);
267
268 //test
269 //strip_extra_spaces(newope->wording);
270
271 break;
272 case 2: //to payee
273 //manage memo append to payee as well
274 if( (data.memo_valid==true) && (PREFS->dtex_ofxmemo == 3) )
275 {
276 name = g_strjoin(" ", data.name, data.memo, NULL);
277 }
278 else
279 name = g_strdup(data.name);
280
281 g_strstrip(name);
282 //test
283 //strip_extra_spaces(name);
284
285 #ifndef G_OS_UNIX
286 DB( g_print(" ensure UTF-8\n") );
287
288 name = homebank_utf8_ensure(name);
289 #endif
290
291 DB( g_print(" -> name to payee: '%s'\n", name) );
292
293 payitem = da_pay_get_by_name(name);
294 if(payitem == NULL)
295 {
296 DB( g_print(" -> create new payee\n") );
297
298 payitem = da_pay_malloc();
299 payitem->name = name;
300 payitem->imported = TRUE;
301 da_pay_append(payitem);
302
303 if( payitem->imported == TRUE )
304 ctx->ictx->cnt_new_pay += 1;
305 }
306 else
307 {
308 g_free(name);
309 }
310
311 newope->kpay = payitem->key;
312 break;
313 }
314 }
315
316 //memo ( new for v4.2) Extra information not included in name
317
318 DB( g_print(" -> memo is='%d'\n", data.memo_valid) );
319
320 if(data.memo_valid==true)
321 {
322 gchar *old = NULL;
323
324 DB( g_print(" -> oxfmemo option: '%d'\n", PREFS->dtex_ofxmemo) );
325 switch(PREFS->dtex_ofxmemo)
326 {
327 case 1: //add to info
328 old = newope->info;
329 if(old == NULL)
330 newope->info = g_strdup(data.memo);
331 else
332 {
333 newope->info = g_strjoin(" ", old, data.memo, NULL);
334 g_free(old);
335 }
336 break;
337
338 case 2: //add to description
339 old = newope->memo;
340 if(old == NULL)
341 newope->memo = g_strdup(data.memo);
342 else
343 {
344 newope->memo = g_strjoin(" ", old, data.memo, NULL);
345 g_free(old);
346 }
347
348 DB( g_print(" -> should concatenate ='%s'\n", data.memo) );
349 DB( g_print(" -> old='%s', new ='%s'\n", old, newope->wording) );
350
351 break;
352 //case 3 add to payee is managed above
353 }
354
355 }
356
357 // payment
358 if(data.transactiontype_valid==true)
359 {
360 switch(data.transactiontype)
361 {
362 //#740373
363 case OFX_CREDIT:
364 if(newope->amount < 0)
365 newope->amount *= -1;
366 break;
367 case OFX_DEBIT:
368 if(newope->amount > 0)
369 newope->amount *= -1;
370 break;
371 case OFX_INT:
372 newope->paymode = PAYMODE_XFER;
373 break;
374 case OFX_DIV:
375 newope->paymode = PAYMODE_XFER;
376 break;
377 case OFX_FEE:
378 newope->paymode = PAYMODE_FEE;
379 break;
380 case OFX_SRVCHG:
381 newope->paymode = PAYMODE_XFER;
382 break;
383 case OFX_DEP:
384 newope->paymode = PAYMODE_DEPOSIT;
385 break;
386 case OFX_ATM:
387 newope->paymode = PAYMODE_CASH;
388 break;
389 case OFX_POS:
390 if(ctx->curr_acc && ctx->curr_acc->type == ACC_TYPE_CREDITCARD)
391 newope->paymode = PAYMODE_CCARD;
392 else
393 newope->paymode = PAYMODE_DCARD;
394 break;
395 case OFX_XFER:
396 newope->paymode = PAYMODE_XFER;
397 break;
398 case OFX_CHECK:
399 newope->paymode = PAYMODE_CHECK;
400 break;
401 case OFX_PAYMENT:
402 newope->paymode = PAYMODE_EPAYMENT;
403 break;
404 case OFX_CASH:
405 newope->paymode = PAYMODE_CASH;
406 break;
407 case OFX_DIRECTDEP:
408 newope->paymode = PAYMODE_DEPOSIT;
409 break;
410 case OFX_DIRECTDEBIT:
411 newope->paymode = PAYMODE_XFER;
412 break;
413 case OFX_REPEATPMT:
414 newope->paymode = PAYMODE_REPEATPMT;
415 break;
416 case OFX_OTHER:
417
418 break;
419 default :
420
421 break;
422 }
423 }
424
425 if( ctx->curr_acc )
426 {
427
428 newope->kacc = ctx->curr_acc->key;
429 newope->flags |= OF_ADDED;
430
431 if( newope->amount > 0)
432 newope->flags |= OF_INCOME;
433
434 /* ensure utf-8 here, has under windows, libofx not always return utf-8 as it should */
435 #ifndef G_OS_UNIX
436 DB( g_print(" ensure UTF-8\n") );
437
438 newope->info = homebank_utf8_ensure(newope->info);
439 newope->wording = homebank_utf8_ensure(newope->wording);
440 #endif
441
442 ctx->trans_list = g_list_append(ctx->trans_list, newope);
443
444 DB( g_print(" insert newope: acc=%d\n", newope->kacc) );
445
446 if( ctx->curr_acc_isnew == TRUE )
447 {
448 DB( g_print(" sub amount from initial\n") );
449 ctx->curr_acc->initial -= data.amount;
450 }
451 }
452 else
453 {
454 da_transaction_free(newope);
455 }
456
457 return 0;
458 }
459
460
461
462 static LibofxProcStatusCallback
463 ofx_proc_status_cb(const struct OfxStatusData data, OfxContext *ctx)
464 {
465 DB( g_print("** ofx_proc_status_cb()\n") );
466
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) );
469 }
470 if(data.severity_valid==true){
471 DB( g_print(" Severity: ") );
472 switch(data.severity){
473 case INFO : DB( g_print("INFO\n") );
474 break;
475 case WARN : DB( g_print("WARN\n") );
476 break;
477 case ERROR : DB( g_print("ERROR\n") );
478 break;
479 default: DB( g_print("WRITEME: Unknown status severity!\n") );
480 }
481 }
482 if(data.code_valid==true){
483 DB( g_print(" Code: %d, name: %s\n Description: %s\n", data.code, data.name, data.description) );
484 }
485 if(data.server_message_valid==true){
486 DB( g_print(" Server Message: %s\n", data.server_message) );
487 }
488 DB( g_print("\n") );
489
490 return 0;
491 }
492
493
494 GList *homebank_ofx_import(gchar *filename, ImportContext *ictx)
495 {
496 OfxContext ctx = { 0 };
497
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;*/
504
505 DB( g_print("\n[import] ofx import (libofx=%s) \n", LIBOFX_VERSION_RELEASE_STRING) );
506
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;*/
513
514 ctx.ictx = ictx;
515
516 LibofxContextPtr libofx_context = libofx_get_new_context();
517
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);
522
523 #ifdef G_OS_WIN32
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);
527 g_free(file);
528 #else
529 libofx_proc_file(libofx_context, filename, AUTODETECT);
530 #endif
531
532 libofx_free_context(libofx_context);
533
534 return ctx.trans_list;
535 }
536
537 #endif
This page took 0.059638 seconds and 4 git commands to generate.