]> Dogcows Code - chaz/homebank/blob - src/hb-import-ofx.c
Merge branch 'upstream'
[chaz/homebank] / src / hb-import-ofx.c
1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2019 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
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
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
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
201 // check number :: The check number is most likely an integer and can probably be converted properly with atoi().
202 //However the spec allows for up to 12 digits, so it is not garanteed to work
203 if(data.check_number_valid==true)
204 {
205 gentxn->rawinfo = g_strdup(data.check_number);
206 }
207 //todo: reference_number ?Might present in addition to or instead of a check_number. Not necessarily a number
208
209 // ofx:name = Can be the name of the payee or the description of the transaction
210 if(data.name_valid==true)
211 {
212 gentxn->rawpayee = g_strdup(data.name);
213 }
214
215 //memo ( new for v4.2) #319202 Extra information not included in name
216
217 DB( g_print(" -> memo is='%d'\n", data.memo_valid) );
218
219
220 if(data.memo_valid==true)
221 {
222 gentxn->rawmemo = g_strdup(data.memo);
223 }
224
225 // payment
226 if(data.transactiontype_valid==true)
227 {
228 switch(data.transactiontype)
229 {
230 //#740373
231 case OFX_CREDIT:
232 if(gentxn->amount < 0)
233 gentxn->amount *= -1;
234 break;
235 case OFX_DEBIT:
236 if(gentxn->amount > 0)
237 gentxn->amount *= -1;
238 break;
239 case OFX_INT:
240 gentxn->paymode = PAYMODE_XFER;
241 break;
242 case OFX_DIV:
243 gentxn->paymode = PAYMODE_XFER;
244 break;
245 case OFX_FEE:
246 gentxn->paymode = PAYMODE_FEE;
247 break;
248 case OFX_SRVCHG:
249 gentxn->paymode = PAYMODE_XFER;
250 break;
251 case OFX_DEP:
252 gentxn->paymode = PAYMODE_DEPOSIT;
253 break;
254 case OFX_ATM:
255 gentxn->paymode = PAYMODE_CASH;
256 break;
257 case OFX_POS:
258 if(ctx->curr_acc && ctx->curr_acc->is_ccard == TRUE)
259 gentxn->paymode = PAYMODE_CCARD;
260 else
261 gentxn->paymode = PAYMODE_DCARD;
262 break;
263 case OFX_XFER:
264 gentxn->paymode = PAYMODE_XFER;
265 break;
266 case OFX_CHECK:
267 gentxn->paymode = PAYMODE_CHECK;
268 break;
269 case OFX_PAYMENT:
270 gentxn->paymode = PAYMODE_EPAYMENT;
271 break;
272 case OFX_CASH:
273 gentxn->paymode = PAYMODE_CASH;
274 break;
275 case OFX_DIRECTDEP:
276 gentxn->paymode = PAYMODE_DEPOSIT;
277 break;
278 case OFX_DIRECTDEBIT:
279 gentxn->paymode = PAYMODE_XFER;
280 break;
281 case OFX_REPEATPMT:
282 gentxn->paymode = PAYMODE_REPEATPMT;
283 break;
284 case OFX_OTHER:
285
286 break;
287 default :
288
289 break;
290 }
291 }
292
293 if( ctx->curr_acc )
294 {
295 gentxn->account = g_strdup(ctx->curr_acc->name);
296
297 /* ensure utf-8 here, has under windows, libofx not always return utf-8 as it should */
298 #ifndef G_OS_UNIX
299 DB( g_print(" ensure UTF-8\n") );
300
301 gentxn->rawinfo = homebank_utf8_ensure(gentxn->rawinfo);
302 gentxn->rawmemo = homebank_utf8_ensure(gentxn->rawmemo);
303 gentxn->rawpayee = homebank_utf8_ensure(gentxn->rawpayee);
304 #endif
305
306 da_gen_txn_append(ctx, gentxn);
307
308 DB( g_print(" insert gentxn: acc=%s\n", gentxn->account) );
309
310 if( ctx->curr_acc_isnew == TRUE )
311 {
312 DB( g_print(" sub amount from initial\n") );
313 ctx->curr_acc->initial -= data.amount;
314 }
315 }
316 else
317 {
318 da_gen_txn_free(gentxn);
319 DB( g_print(" no account, insert txn skipped\n") );
320 }
321
322 return 0;
323 }
324
325
326
327 static LibofxProcStatusCallback
328 ofx_proc_status_cb(const struct OfxStatusData data, ImportContext *ctx)
329 {
330 DB( g_print("** ofx_proc_status_cb()\n") );
331
332 if(data.ofx_element_name_valid==true){
333 DB( g_print(" Ofx entity this status is relevent to: '%s'\n", data.ofx_element_name) );
334 }
335 if(data.severity_valid==true){
336 DB( g_print(" Severity: ") );
337 switch(data.severity){
338 case INFO : DB( g_print("INFO\n") );
339 break;
340 case WARN : DB( g_print("WARN\n") );
341 break;
342 case ERROR : DB( g_print("ERROR\n") );
343 break;
344 default: DB( g_print("WRITEME: Unknown status severity!\n") );
345 }
346 }
347 if(data.code_valid==true){
348 DB( g_print(" Code: %d, name: %s\n Description: %s\n", data.code, data.name, data.description) );
349 }
350 if(data.server_message_valid==true){
351 DB( g_print(" Server Message: %s\n", data.server_message) );
352 }
353 DB( g_print("\n") );
354
355 return 0;
356 }
357
358
359 GList *homebank_ofx_import(ImportContext *ictx, GenFile *genfile)
360 {
361 /*extern int ofx_PARSER_msg;
362 extern int ofx_DEBUG_msg;
363 extern int ofx_WARNING_msg;
364 extern int ofx_ERROR_msg;
365 extern int ofx_INFO_msg;
366 extern int ofx_STATUS_msg;*/
367
368 DB( g_print("\n[import] ofx import (libofx=%s) \n", LIBOFX_VERSION_RELEASE_STRING) );
369
370 /*ofx_PARSER_msg = false;
371 ofx_DEBUG_msg = false;
372 ofx_WARNING_msg = false;
373 ofx_ERROR_msg = false;
374 ofx_INFO_msg = false;
375 ofx_STATUS_msg = false;*/
376
377 LibofxContextPtr libofx_context = libofx_get_new_context();
378
379 ofx_set_status_cb (libofx_context, (LibofxProcStatusCallback) ofx_proc_status_cb , ictx);
380 ofx_set_statement_cb (libofx_context, (LibofxProcStatementCallback) ofx_proc_statement_cb , ictx);
381 ofx_set_account_cb (libofx_context, (LibofxProcAccountCallback) ofx_proc_account_cb , ictx);
382 ofx_set_transaction_cb(libofx_context, (LibofxProcTransactionCallback)ofx_proc_transaction_cb, ictx);
383
384 #ifdef G_OS_WIN32
385 //#932959: windows don't like utf8 path, so convert
386 gchar *filepath = g_win32_locale_filename_from_utf8(genfile->filepath);
387 libofx_proc_file(libofx_context, filepath, AUTODETECT);
388 g_free(filepath);
389 #else
390 libofx_proc_file(libofx_context, genfile->filepath, AUTODETECT);
391 #endif
392
393 libofx_free_context(libofx_context);
394
395 DB( g_print("ofx nb txn=%d\n", g_list_length(ictx->gen_lst_txn) ));
396
397 return ictx->gen_lst_txn;
398 }
399
400 #endif
This page took 0.050945 seconds and 4 git commands to generate.