/* HomeBank -- Free, easy, personal accounting for everyone.
* Copyright (C) 1995-2019 Maxime DOYEN
*
* This file is part of HomeBank.
*
* HomeBank is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* HomeBank is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "homebank.h"
#include "hb-import.h"
#ifndef NOOFX
#include
#endif
/****************************************************************************/
/* Debug macros */
/****************************************************************************/
#define MYDEBUG 0
#if MYDEBUG
#define DB(x) (x);
#else
#define DB(x);
#endif
/* our global datas */
extern struct HomeBank *GLOBALS;
extern struct Preferences *PREFS;
#ifndef NOOFX
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/**
* ofx_proc_account_cb:
*
* The ofx_proc_account_cb event is always generated first, to allow the application to create accounts
* or ask the user to match an existing account before the ofx_proc_statement and ofx_proc_transaction
* event are received. An OfxAccountData is passed to this event.
*
*/
static LibofxProcStatementCallback
ofx_proc_account_cb(const struct OfxAccountData data, ImportContext *ctx)
{
GenAcc *genacc;
Account *dst_acc;
DB( g_print("** ofx_proc_account_cb()\n") );
if(data.account_id_valid==true)
{
DB( g_print(" account_id: %s\n", data.account_id) );
DB( g_print(" account_name: %s\n", data.account_name) );
}
//if(data.account_number_valid==true)
//{
DB( g_print(" account_number: %s\n", data.account_number) );
//}
if(data.account_type_valid==true)
{
DB( g_print(" account_type: %d\n", data.account_type) );
/*
enum:
OFX_CHECKING A standard checking account
OFX_SAVINGS A standard savings account
OFX_MONEYMRKT A money market account
OFX_CREDITLINE A line of credit
OFX_CMA Cash Management Account
OFX_CREDITCARD A credit card account
OFX_INVESTMENT An investment account
*/
}
if(data.currency_valid==true)
{
DB( g_print(" currency: %s\n", data.currency) );
}
//todo: normally should check for validity here
// in every case we create an account here
DB( g_print(" -> create generic account: '%s':'%s'\n", data.account_id, data.account_name) );
genacc = hb_import_gen_acc_get_next (ctx, FILETYPE_OFX, (gchar *)data.account_name, (gchar *)data.account_id);
ctx->curr_acc_isnew = TRUE;
dst_acc = hb_import_acc_find_existing((gchar *)data.account_name, (gchar *)data.account_id );
if( dst_acc != NULL )
{
genacc->kacc = dst_acc->key;
ctx->curr_acc_isnew = FALSE;
if(dst_acc->type == ACC_TYPE_CREDITCARD)
genacc->is_ccard = TRUE;
}
ctx->curr_acc = genacc;
DB( fputs("\n",stdout) );
return 0;
}
/**
* ofx_proc_statement_cb:
*
* The ofx_proc_statement_cb event is sent after all ofx_proc_transaction events have been sent.
* An OfxStatementData is passed to this event.
*
*/
static LibofxProcStatementCallback
ofx_proc_statement_cb(const struct OfxStatementData data, ImportContext *ctx)
{
DB( g_print("** ofx_proc_statement_cb()\n") );
#if MYDEBUG == 1
if(data.ledger_balance_date_valid==true)
{
struct tm temp_tm;
temp_tm = *localtime(&(data.ledger_balance_date));
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");
}
#endif
if(data.ledger_balance_valid==true)
{
if( ctx->curr_acc != NULL && ctx->curr_acc_isnew == TRUE )
{
ctx->curr_acc->initial = data.ledger_balance;
}
DB( g_print("ledger_balance: $%.2f%s",data.ledger_balance,"\n") );
}
return 0;
}
/**
* ofx_proc_statement_cb:
*
* An ofx_proc_transaction_cb event is generated for every transaction in the ofx response,
* after ofx_proc_statement (and possibly ofx_proc_security is generated.
* An OfxTransactionData structure is passed to this event.
*
*/
static LibofxProcStatementCallback
ofx_proc_transaction_cb(const struct OfxTransactionData data, ImportContext *ctx)
{
struct tm *temp_tm;
GDate date;
GenTxn *gentxn;
DB( g_print("** ofx_proc_transaction_cb()\n") );
gentxn = da_gen_txn_malloc();
// date
gentxn->julian = 0;
if(data.date_posted_valid && (data.date_posted != 0))
{
temp_tm = localtime(&data.date_posted);
if( temp_tm != 0)
{
g_date_set_dmy(&date, temp_tm->tm_mday, temp_tm->tm_mon+1, temp_tm->tm_year+1900);
gentxn->julian = g_date_get_julian(&date);
}
}
else if (data.date_initiated_valid && (data.date_initiated != 0))
{
temp_tm = localtime(&data.date_initiated);
if( temp_tm != 0)
{
g_date_set_dmy(&date, temp_tm->tm_mday, temp_tm->tm_mon+1, temp_tm->tm_year+1900);
gentxn->julian = g_date_get_julian(&date);
}
}
// amount
if(data.amount_valid==true)
{
gentxn->amount = data.amount;
}
// check number :: The check number is most likely an integer and can probably be converted properly with atoi().
//However the spec allows for up to 12 digits, so it is not garanteed to work
if(data.check_number_valid==true)
{
gentxn->rawinfo = g_strdup(data.check_number);
}
//todo: reference_number ?Might present in addition to or instead of a check_number. Not necessarily a number
// ofx:name = Can be the name of the payee or the description of the transaction
if(data.name_valid==true)
{
gentxn->rawpayee = g_strdup(data.name);
}
//memo ( new for v4.2) #319202 Extra information not included in name
DB( g_print(" -> memo is='%d'\n", data.memo_valid) );
if(data.memo_valid==true)
{
gentxn->rawmemo = g_strdup(data.memo);
}
// payment
if(data.transactiontype_valid==true)
{
switch(data.transactiontype)
{
//#740373
case OFX_CREDIT:
if(gentxn->amount < 0)
gentxn->amount *= -1;
break;
case OFX_DEBIT:
if(gentxn->amount > 0)
gentxn->amount *= -1;
break;
case OFX_INT:
gentxn->paymode = PAYMODE_XFER;
break;
case OFX_DIV:
gentxn->paymode = PAYMODE_XFER;
break;
case OFX_FEE:
gentxn->paymode = PAYMODE_FEE;
break;
case OFX_SRVCHG:
gentxn->paymode = PAYMODE_XFER;
break;
case OFX_DEP:
gentxn->paymode = PAYMODE_DEPOSIT;
break;
case OFX_ATM:
gentxn->paymode = PAYMODE_CASH;
break;
case OFX_POS:
if(ctx->curr_acc && ctx->curr_acc->is_ccard == TRUE)
gentxn->paymode = PAYMODE_CCARD;
else
gentxn->paymode = PAYMODE_DCARD;
break;
case OFX_XFER:
gentxn->paymode = PAYMODE_XFER;
break;
case OFX_CHECK:
gentxn->paymode = PAYMODE_CHECK;
break;
case OFX_PAYMENT:
gentxn->paymode = PAYMODE_EPAYMENT;
break;
case OFX_CASH:
gentxn->paymode = PAYMODE_CASH;
break;
case OFX_DIRECTDEP:
gentxn->paymode = PAYMODE_DEPOSIT;
break;
case OFX_DIRECTDEBIT:
gentxn->paymode = PAYMODE_XFER;
break;
case OFX_REPEATPMT:
gentxn->paymode = PAYMODE_REPEATPMT;
break;
case OFX_OTHER:
break;
default :
break;
}
}
if( ctx->curr_acc )
{
gentxn->account = g_strdup(ctx->curr_acc->name);
/* ensure utf-8 here, has under windows, libofx not always return utf-8 as it should */
#ifndef G_OS_UNIX
DB( g_print(" ensure UTF-8\n") );
gentxn->rawinfo = homebank_utf8_ensure(gentxn->rawinfo);
gentxn->rawmemo = homebank_utf8_ensure(gentxn->rawmemo);
gentxn->rawpayee = homebank_utf8_ensure(gentxn->rawpayee);
#endif
da_gen_txn_append(ctx, gentxn);
DB( g_print(" insert gentxn: acc=%s\n", gentxn->account) );
if( ctx->curr_acc_isnew == TRUE )
{
DB( g_print(" sub amount from initial\n") );
ctx->curr_acc->initial -= data.amount;
}
}
else
{
da_gen_txn_free(gentxn);
DB( g_print(" no account, insert txn skipped\n") );
}
return 0;
}
static LibofxProcStatusCallback
ofx_proc_status_cb(const struct OfxStatusData data, ImportContext *ctx)
{
DB( g_print("** ofx_proc_status_cb()\n") );
if(data.ofx_element_name_valid==true){
DB( g_print(" Ofx entity this status is relevent to: '%s'\n", data.ofx_element_name) );
}
if(data.severity_valid==true){
DB( g_print(" Severity: ") );
switch(data.severity){
case INFO : DB( g_print("INFO\n") );
break;
case WARN : DB( g_print("WARN\n") );
break;
case ERROR : DB( g_print("ERROR\n") );
break;
default: DB( g_print("WRITEME: Unknown status severity!\n") );
}
}
if(data.code_valid==true){
DB( g_print(" Code: %d, name: %s\n Description: %s\n", data.code, data.name, data.description) );
}
if(data.server_message_valid==true){
DB( g_print(" Server Message: %s\n", data.server_message) );
}
DB( g_print("\n") );
return 0;
}
GList *homebank_ofx_import(ImportContext *ictx, GenFile *genfile)
{
/*extern int ofx_PARSER_msg;
extern int ofx_DEBUG_msg;
extern int ofx_WARNING_msg;
extern int ofx_ERROR_msg;
extern int ofx_INFO_msg;
extern int ofx_STATUS_msg;*/
DB( g_print("\n[import] ofx import (libofx=%s) \n", LIBOFX_VERSION_RELEASE_STRING) );
/*ofx_PARSER_msg = false;
ofx_DEBUG_msg = false;
ofx_WARNING_msg = false;
ofx_ERROR_msg = false;
ofx_INFO_msg = false;
ofx_STATUS_msg = false;*/
LibofxContextPtr libofx_context = libofx_get_new_context();
ofx_set_status_cb (libofx_context, (LibofxProcStatusCallback) ofx_proc_status_cb , ictx);
ofx_set_statement_cb (libofx_context, (LibofxProcStatementCallback) ofx_proc_statement_cb , ictx);
ofx_set_account_cb (libofx_context, (LibofxProcAccountCallback) ofx_proc_account_cb , ictx);
ofx_set_transaction_cb(libofx_context, (LibofxProcTransactionCallback)ofx_proc_transaction_cb, ictx);
#ifdef G_OS_WIN32
//#932959: windows don't like utf8 path, so convert
gchar *filepath = g_win32_locale_filename_from_utf8(genfile->filepath);
libofx_proc_file(libofx_context, filepath, AUTODETECT);
g_free(filepath);
#else
libofx_proc_file(libofx_context, genfile->filepath, AUTODETECT);
#endif
libofx_free_context(libofx_context);
DB( g_print("ofx nb txn=%d\n", g_list_length(ictx->gen_lst_txn) ));
return ictx->gen_lst_txn;
}
#endif