1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2021 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 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
49
50 /**
51 * ofx_proc_account_cb:
52 *
53 * The ofx_proc_account_cb event is always generated first, to allow the application to create accounts
54 * or ask the user to match an existing account before the ofx_proc_statement and ofx_proc_transaction
55 * event are received. An OfxAccountData is passed to this event.
56 *
57 */
58 static LibofxProcStatementCallback
ofx_proc_account_cb(const struct OfxAccountData data,ImportContext * ctx)59 ofx_proc_account_cb(const struct OfxAccountData data, ImportContext *ctx)
60 {
61 GenAcc *genacc;
62 Account *dst_acc;
63
64 DB( g_print("** ofx_proc_account_cb()\n") );
65
66 if(data.account_id_valid==true)
67 {
68 DB( g_print(" account_id: %s\n", data.account_id) );
69 DB( g_print(" account_name: %s\n", data.account_name) );
70 }
71
72 //if(data.account_number_valid==true)
73 //{
74 DB( g_print(" account_number: %s\n", data.account_number) );
75 //}
76
77
78 if(data.account_type_valid==true)
79 {
80 DB( g_print(" account_type: %d\n", data.account_type) );
81 /*
82 enum:
83 OFX_CHECKING A standard checking account
84 OFX_SAVINGS A standard savings account
85 OFX_MONEYMRKT A money market account
86 OFX_CREDITLINE A line of credit
87 OFX_CMA Cash Management Account
88 OFX_CREDITCARD A credit card account
89 OFX_INVESTMENT An investment account
90 */
91 }
92
93 if(data.currency_valid==true)
94 {
95 DB( g_print(" currency: %s\n", data.currency) );
96 }
97
98 //todo: normally should check for validity here
99 // in every case we create an account here
100 DB( g_print(" -> create generic account: '%s':'%s'\n", data.account_id, data.account_name) );
101 genacc = hb_import_gen_acc_get_next (ctx, FILETYPE_OFX, (gchar *)data.account_name, (gchar *)data.account_id);
102 ctx->curr_acc_isnew = TRUE;
103
104 dst_acc = hb_import_acc_find_existing((gchar *)data.account_name, (gchar *)data.account_id );
105 if( dst_acc != NULL )
106 {
107 genacc->kacc = dst_acc->key;
108 ctx->curr_acc_isnew = FALSE;
109 if(dst_acc->type == ACC_TYPE_CREDITCARD)
110 genacc->is_ccard = TRUE;
111 }
112
113 ctx->curr_acc = genacc;
114
115 DB( fputs("\n",stdout) );
116 return 0;
117 }
118
119
120 /**
121 * ofx_proc_statement_cb:
122 *
123 * The ofx_proc_statement_cb event is sent after all ofx_proc_transaction events have been sent.
124 * An OfxStatementData is passed to this event.
125 *
126 */
127 static LibofxProcStatementCallback
ofx_proc_statement_cb(const struct OfxStatementData data,ImportContext * ctx)128 ofx_proc_statement_cb(const struct OfxStatementData data, ImportContext *ctx)
129 {
130 DB( g_print("** ofx_proc_statement_cb()\n") );
131
132 #if MYDEBUG == 1
133 if(data.ledger_balance_date_valid==true)
134 {
135 struct tm temp_tm;
136
137 temp_tm = *localtime(&(data.ledger_balance_date));
138 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");
139 }
140 #endif
141
142 if(data.ledger_balance_valid==true)
143 {
144 if( ctx->curr_acc != NULL && ctx->curr_acc_isnew == TRUE )
145 {
146 ctx->curr_acc->initial = data.ledger_balance;
147 }
148 DB( g_print("ledger_balance: $%.2f%s",data.ledger_balance,"\n") );
149 }
150
151 return 0;
152 }
153
154 /**
155 * ofx_proc_statement_cb:
156 *
157 * An ofx_proc_transaction_cb event is generated for every transaction in the ofx response,
158 * after ofx_proc_statement (and possibly ofx_proc_security is generated.
159 * An OfxTransactionData structure is passed to this event.
160 *
161 */
162 static LibofxProcStatementCallback
ofx_proc_transaction_cb(const struct OfxTransactionData data,ImportContext * ctx)163 ofx_proc_transaction_cb(const struct OfxTransactionData data, ImportContext *ctx)
164 {
165 struct tm *temp_tm;
166 GDate date;
167 GenTxn *gentxn;
168
169 DB( g_print("** ofx_proc_transaction_cb()\n") );
170
171 gentxn = da_gen_txn_malloc();
172
173 // date
174 gentxn->julian = 0;
175 if(data.date_posted_valid && (data.date_posted != 0))
176 {
177 temp_tm = localtime(&data.date_posted);
178 if( temp_tm != 0)
179 {
180 g_date_set_dmy(&date, temp_tm->tm_mday, temp_tm->tm_mon+1, temp_tm->tm_year+1900);
181 gentxn->julian = g_date_get_julian(&date);
182 }
183 }
184 else if (data.date_initiated_valid && (data.date_initiated != 0))
185 {
186 temp_tm = localtime(&data.date_initiated);
187 if( temp_tm != 0)
188 {
189 g_date_set_dmy(&date, temp_tm->tm_mday, temp_tm->tm_mon+1, temp_tm->tm_year+1900);
190 gentxn->julian = g_date_get_julian(&date);
191 }
192 }
193
194 // amount
195 if(data.amount_valid==true)
196 {
197 gentxn->amount = data.amount;
198 }
199
200 // 5.5.1 add fitid
201 if(data.fi_id_valid==true)
202 {
203 gentxn->fitid = g_strdup(data.fi_id);
204 }
205
206 // check number :: The check number is most likely an integer and can probably be converted properly with atoi().
207 //However the spec allows for up to 12 digits, so it is not garanteed to work
208 if(data.check_number_valid==true)
209 {
210 gentxn->rawinfo = g_strdup(data.check_number);
211 }
212 //todo: reference_number ?Might present in addition to or instead of a check_number. Not necessarily a number
213
214 // ofx:name = Can be the name of the payee or the description of the transaction
215 if(data.name_valid==true)
216 {
217 gentxn->rawpayee = g_strdup(data.name);
218 }
219
220 //memo ( new for v4.2) #319202 Extra information not included in name
221
222 DB( g_print(" -> memo is='%d'\n", data.memo_valid) );
223
224
225 if(data.memo_valid==true)
226 {
227 gentxn->rawmemo = g_strdup(data.memo);
228 }
229
230 // payment
231 if(data.transactiontype_valid==true)
232 {
233 switch(data.transactiontype)
234 {
235 //#740373
236 case OFX_CREDIT:
237 if(gentxn->amount < 0)
238 gentxn->amount *= -1;
239 break;
240 case OFX_DEBIT:
241 if(gentxn->amount > 0)
242 gentxn->amount *= -1;
243 break;
244 case OFX_INT:
245 gentxn->paymode = PAYMODE_XFER;
246 break;
247 case OFX_DIV:
248 gentxn->paymode = PAYMODE_XFER;
249 break;
250 case OFX_FEE:
251 gentxn->paymode = PAYMODE_FEE;
252 break;
253 case OFX_SRVCHG:
254 gentxn->paymode = PAYMODE_XFER;
255 break;
256 case OFX_DEP:
257 gentxn->paymode = PAYMODE_DEPOSIT;
258 break;
259 case OFX_ATM:
260 gentxn->paymode = PAYMODE_CASH;
261 break;
262 case OFX_POS:
263 if(ctx->curr_acc && ctx->curr_acc->is_ccard == TRUE)
264 gentxn->paymode = PAYMODE_CCARD;
265 else
266 gentxn->paymode = PAYMODE_DCARD;
267 break;
268 case OFX_XFER:
269 gentxn->paymode = PAYMODE_XFER;
270 break;
271 case OFX_CHECK:
272 gentxn->paymode = PAYMODE_CHECK;
273 break;
274 case OFX_PAYMENT:
275 gentxn->paymode = PAYMODE_EPAYMENT;
276 break;
277 case OFX_CASH:
278 gentxn->paymode = PAYMODE_CASH;
279 break;
280 case OFX_DIRECTDEP:
281 gentxn->paymode = PAYMODE_DEPOSIT;
282 break;
283 case OFX_DIRECTDEBIT:
284 //1854953: directdebit not adding in 4.6
285 //gentxn->paymode = PAYMODE_XFER;
286 gentxn->paymode = PAYMODE_DIRECTDEBIT;
287 break;
288 case OFX_REPEATPMT:
289 gentxn->paymode = PAYMODE_REPEATPMT;
290 break;
291 case OFX_OTHER:
292
293 break;
294 default :
295
296 break;
297 }
298 }
299
300 if( ctx->curr_acc )
301 {
302 gentxn->account = g_strdup(ctx->curr_acc->name);
303
304 #if MYDEBUG == 1
305 if(gentxn->rawinfo)
306 g_print(" len info %d %ld\n", (int)strlen(gentxn->rawinfo) , g_utf8_strlen(gentxn->rawinfo, -1));
307 if(gentxn->rawmemo)
308 g_print(" len memo %d %ld\n", (int)strlen(gentxn->rawmemo) , g_utf8_strlen(gentxn->rawmemo, -1));
309 if(gentxn->rawpayee)
310 g_print(" len name %d %ld\n", (int)strlen(gentxn->rawpayee), g_utf8_strlen(gentxn->rawpayee, -1));
311 #endif
312
313 //#1842935 workaround for libofx truncate bug that can leave invalid UTF-8 string
314 //NAME = A-32 (96 allowed)
315 #if( (GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION >= 52) )
316 if( gentxn->rawpayee && g_utf8_strlen(gentxn->rawpayee, -1) > 32 )
317 {
318 gchar *oldtxt = gentxn->rawpayee;
319 DB( g_print(" ensure UTF-8 for truncated NAME='%s'\n", oldtxt) );
320 gentxn->rawpayee = g_utf8_make_valid(oldtxt, -1);
321 g_free(oldtxt);
322 }
323 #endif
324 //TODO: maybe MEMO = A-255
325
326 /* ensure utf-8 here, has under windows, libofx not always return utf-8 as it should */
327 #ifndef G_OS_UNIX
328 DB( g_print(" ensure UTF-8\n") );
329
330 gentxn->rawinfo = homebank_utf8_ensure(gentxn->rawinfo);
331 gentxn->rawmemo = homebank_utf8_ensure(gentxn->rawmemo);
332 gentxn->rawpayee = homebank_utf8_ensure(gentxn->rawpayee);
333 #endif
334
335 da_gen_txn_append(ctx, gentxn);
336
337 DB( g_print(" insert gentxn: acc=%s\n", gentxn->account) );
338
339 if( ctx->curr_acc_isnew == TRUE )
340 {
341 DB( g_print(" sub amount from initial\n") );
342 ctx->curr_acc->initial -= data.amount;
343 }
344 }
345 else
346 {
347 da_gen_txn_free(gentxn);
348 DB( g_print(" no account, insert txn skipped\n") );
349 }
350
351 return 0;
352 }
353
354
355 static LibofxProcStatusCallback
ofx_proc_status_cb(const struct OfxStatusData data,ImportContext * ctx)356 ofx_proc_status_cb(const struct OfxStatusData data, ImportContext *ctx)
357 {
358 DB( g_print("** ofx_proc_status_cb()\n") );
359
360 if(data.ofx_element_name_valid==true){
361 DB( g_print(" Ofx entity this status is relevent to: '%s'\n", data.ofx_element_name) );
362 }
363 if(data.severity_valid==true){
364 DB( g_print(" Severity: ") );
365 switch(data.severity){
366 case INFO : DB( g_print("INFO\n") );
367 break;
368 case WARN : DB( g_print("WARN\n") );
369 break;
370 case ERROR : DB( g_print("ERROR\n") );
371 break;
372 default: DB( g_print("WRITEME: Unknown status severity!\n") );
373 }
374 }
375 if(data.code_valid==true){
376 DB( g_print(" Code: %d, name: %s\n Description: %s\n", data.code, data.name, data.description) );
377 }
378 if(data.server_message_valid==true){
379 DB( g_print(" Server Message: %s\n", data.server_message) );
380 }
381 DB( g_print("\n") );
382
383 return 0;
384 }
385
386
homebank_ofx_import(ImportContext * ictx,GenFile * genfile)387 GList *homebank_ofx_import(ImportContext *ictx, GenFile *genfile)
388 {
389 /*extern int ofx_PARSER_msg;
390 extern int ofx_DEBUG_msg;
391 extern int ofx_WARNING_msg;
392 extern int ofx_ERROR_msg;
393 extern int ofx_INFO_msg;
394 extern int ofx_STATUS_msg;*/
395
396 DB( g_print("\n[import] ofx import (libofx=%s) \n", LIBOFX_VERSION_RELEASE_STRING) );
397
398 /*ofx_PARSER_msg = false;
399 ofx_DEBUG_msg = false;
400 ofx_WARNING_msg = false;
401 ofx_ERROR_msg = false;
402 ofx_INFO_msg = false;
403 ofx_STATUS_msg = false;*/
404
405 LibofxContextPtr libofx_context = libofx_get_new_context();
406
407 ofx_set_status_cb (libofx_context, (LibofxProcStatusCallback) ofx_proc_status_cb , ictx);
408 ofx_set_statement_cb (libofx_context, (LibofxProcStatementCallback) ofx_proc_statement_cb , ictx);
409 ofx_set_account_cb (libofx_context, (LibofxProcAccountCallback) ofx_proc_account_cb , ictx);
410 ofx_set_transaction_cb(libofx_context, (LibofxProcTransactionCallback)ofx_proc_transaction_cb, ictx);
411
412 #ifdef G_OS_WIN32
413 //#932959: windows don't like utf8 path, so convert
414 gchar *filepath = g_win32_locale_filename_from_utf8(genfile->filepath);
415 libofx_proc_file(libofx_context, filepath, AUTODETECT);
416 g_free(filepath);
417 #else
418 libofx_proc_file(libofx_context, genfile->filepath, AUTODETECT);
419 #endif
420
421 libofx_free_context(libofx_context);
422
423 DB( g_print("ofx nb txn=%d\n", g_list_length(ictx->gen_lst_txn) ));
424
425 return ictx->gen_lst_txn;
426 }
427
428 #endif
429