/***************************************************************************
ofxiimporterplugin . cpp
- - - - - - - - - - - - - - - - - - -
begin : Sat Jan 01 2005
copyright : ( C ) 2005 by Ace Jones
email : Ace Jones < acejones @ users . sourceforge . net >
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/***************************************************************************
* *
* This program 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 . *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// ----------------------------------------------------------------------------
// QT Includes
# include <tqfile.h>
# include <tqtextstream.h>
# include <tqradiobutton.h>
# include <tqspinbox.h>
# include <tqdatetimeedit.h>
// ----------------------------------------------------------------------------
// KDE Includes
# include <kgenericfactory.h>
# include <kdebug.h>
# include <tdefile.h>
# include <kurl.h>
# include <tdeaction.h>
# include <tdemessagebox.h>
// ----------------------------------------------------------------------------
// Project Includes
# include "ofximporterplugin.h"
# include "konlinebankingstatus.h"
# include "konlinebankingsetupwizard.h"
# include "kofxdirectconnectdlg.h"
K_EXPORT_COMPONENT_FACTORY ( kmm_ofximport ,
KGenericFactory < OfxImporterPlugin > ( " kmm_ofximport " ) )
OfxImporterPlugin : : OfxImporterPlugin ( TQObject * parent , const char * name , const TQStringList & ) :
KMyMoneyPlugin : : Plugin ( parent , name ) ,
KMyMoneyPlugin : : ImporterPlugin ( ) ,
m_valid ( false )
{
setInstance ( KGenericFactory < OfxImporterPlugin > : : instance ( ) ) ;
setXMLFile ( " kmm_ofximport.rc " ) ;
createActions ( ) ;
}
OfxImporterPlugin : : ~ OfxImporterPlugin ( )
{
}
void OfxImporterPlugin : : createActions ( void )
{
new TDEAction ( i18n ( " OFX... " ) , " " , 0 , this , TQT_SLOT ( slotImportFile ( ) ) , actionCollection ( ) , " file_import_ofx " ) ;
}
void OfxImporterPlugin : : slotImportFile ( void )
{
KURL url = importInterface ( ) - > selectFile ( i18n ( " OFX import file selection " ) ,
" " ,
" *.ofx *.qfx *.ofc|OFX files (*.ofx, *.qfx, *.ofc) \n *.*|All files (*.*) " ,
static_cast < KFile : : Mode > ( KFile : : File | KFile : : ExistingOnly ) ) ;
if ( url . isValid ( ) ) {
if ( isMyFormat ( url . path ( ) ) ) {
slotImportFile ( url . path ( ) ) ;
} else {
KMessageBox : : error ( 0 , i18n ( " Unable to import %1 using the OFX importer plugin. This file is not the correct format. " ) . arg ( url . prettyURL ( 0 , KURL : : StripFileProtocol ) ) , i18n ( " Incorrect format " ) ) ;
}
}
}
TQString OfxImporterPlugin : : formatName ( void ) const
{
return " OFX " ;
}
TQString OfxImporterPlugin : : formatFilenameFilter ( void ) const
{
return " *.ofx *.qfx *.ofc " ;
}
bool OfxImporterPlugin : : isMyFormat ( const TQString & filename ) const
{
// filename is considered an Ofx file if it contains
// the tag "<OFX>" or "<OFC>" in the first 20 lines
// which contain some data.
bool result = false ;
TQFile f ( filename ) ;
if ( f . open ( IO_ReadOnly ) )
{
TQTextStream ts ( & f ) ;
int lineCount = 20 ;
while ( ! ts . atEnd ( ) & & ! result & & lineCount ! = 0 )
{
// get a line of data and remove all unnecessary whitepace chars
TQString line = ts . readLine ( ) . simplifyWhiteSpace ( ) ;
if ( line . contains ( " <OFX> " , false )
| | line . contains ( " <OFC> " , false ) )
result = true ;
// count only lines that contains some non white space chars
if ( ! line . isEmpty ( ) )
lineCount - - ;
}
f . close ( ) ;
}
return result ;
}
bool OfxImporterPlugin : : import ( const TQString & filename )
{
m_fatalerror = i18n ( " Unable to parse file " ) ;
m_valid = false ;
m_errors . clear ( ) ;
m_warnings . clear ( ) ;
m_infos . clear ( ) ;
m_statementlist . clear ( ) ;
m_securitylist . clear ( ) ;
TQCString filename_deep ( filename . utf8 ( ) ) ;
LibofxContextPtr ctx = libofx_get_new_context ( ) ;
TQ_CHECK_PTR ( ctx ) ;
ofx_set_transaction_cb ( ctx , ofxTransactionCallback , this ) ;
ofx_set_statement_cb ( ctx , ofxStatementCallback , this ) ;
ofx_set_account_cb ( ctx , ofxAccountCallback , this ) ;
ofx_set_security_cb ( ctx , ofxSecurityCallback , this ) ;
ofx_set_status_cb ( ctx , ofxStatusCallback , this ) ;
libofx_proc_file ( ctx , filename_deep , AUTODETECT ) ;
libofx_free_context ( ctx ) ;
if ( m_valid )
{
m_fatalerror = TQString ( ) ;
m_valid = storeStatements ( m_statementlist ) ;
}
return m_valid ;
}
TQString OfxImporterPlugin : : lastError ( void ) const
{
if ( m_errors . count ( ) = = 0 )
return m_fatalerror ;
return m_errors . join ( " <p> " ) ;
}
/* __________________________________________________________________________
* AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
*
* Static callbacks for LibOFX
*
* YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
*/
int OfxImporterPlugin : : ofxTransactionCallback ( struct OfxTransactionData data , void * pv )
{
// kdDebug(2) << __func__ << endl;
OfxImporterPlugin * pofx = reinterpret_cast < OfxImporterPlugin * > ( pv ) ;
MyMoneyStatement & s = pofx - > back ( ) ;
MyMoneyStatement : : Transaction t ;
if ( data . date_posted_valid = = true )
{
TQDateTime dt ;
dt . setTime_t ( data . date_posted , Qt : : UTC ) ;
t . m_datePosted = dt . date ( ) ;
}
else if ( data . date_initiated_valid = = true )
{
TQDateTime dt ;
dt . setTime_t ( data . date_initiated , Qt : : UTC ) ;
t . m_datePosted = dt . date ( ) ;
}
if ( data . amount_valid = = true )
{
t . m_amount = MyMoneyMoney ( data . amount , 1000 ) ;
// if this is an investment statement, reverse the sign. not sure
// why this is needed, so I suppose it's a bit of a hack for the moment.
if ( data . invtransactiontype_valid = = true )
t . m_amount = - t . m_amount ;
}
if ( data . check_number_valid = = true )
{
t . m_strNumber = data . check_number ;
}
if ( data . fi_id_valid = = true )
{
t . m_strBankID = TQString ( " ID " ) + data . fi_id ;
}
else if ( data . reference_number_valid = = true )
{
t . m_strBankID = TQString ( " REF " ) + data . reference_number ;
}
// Decide whether to import NAME or PAYEEID if both are present in the download
if ( pofx - > m_preferName ) {
if ( data . name_valid = = true )
{
t . m_strPayee = data . name ;
}
else if ( data . payee_id_valid = = true )
{
t . m_strPayee = data . payee_id ;
}
}
else {
if ( data . payee_id_valid = = true )
{
t . m_strPayee = data . payee_id ;
}
else if ( data . name_valid = = true )
{
t . m_strPayee = data . name ;
}
}
if ( data . memo_valid = = true ) {
t . m_strMemo = data . memo ;
}
// If the payee or memo fields are blank, set them to
// the other one which is NOT blank. (acejones)
if ( t . m_strPayee . isEmpty ( ) )
{
// But we only create a payee for non-investment transactions (ipwizard)
if ( ! t . m_strMemo . isEmpty ( ) & & data . invtransactiontype_valid = = false )
t . m_strPayee = t . m_strMemo ;
}
else
{
if ( t . m_strMemo . isEmpty ( ) )
t . m_strMemo = t . m_strPayee ;
}
if ( data . security_data_valid = = true )
{
struct OfxSecurityData * secdata = data . security_data_ptr ;
if ( secdata - > ticker_valid = = true ) {
t . m_strSymbol = secdata - > ticker ;
}
if ( secdata - > secname_valid = = true ) {
t . m_strSecurity = secdata - > secname ;
}
}
t . m_shares = MyMoneyMoney ( ) ;
if ( data . units_valid = = true )
{
t . m_shares = MyMoneyMoney ( data . units , 100000 ) . reduce ( ) ;
}
t . m_price = MyMoneyMoney ( ) ;
if ( data . unitprice_valid = = true )
{
t . m_price = MyMoneyMoney ( data . unitprice , 100000 ) . reduce ( ) ;
}
t . m_fees = MyMoneyMoney ( ) ;
if ( data . fees_valid = = true )
{
t . m_fees + = MyMoneyMoney ( data . fees , 1000 ) . reduce ( ) ;
}
if ( data . commission_valid = = true )
{
t . m_fees + = MyMoneyMoney ( data . commission , 1000 ) . reduce ( ) ;
}
bool unhandledtype = false ;
TQString type ;
if ( data . invtransactiontype_valid = = true )
{
switch ( data . invtransactiontype )
{
case OFX_BUYDEBT :
case OFX_BUYMF :
case OFX_BUYOPT :
case OFX_BUYOTHER :
case OFX_BUYSTOCK :
t . m_eAction = MyMoneyStatement : : Transaction : : eaBuy ;
break ;
case OFX_REINVEST :
t . m_eAction = MyMoneyStatement : : Transaction : : eaReinvestDividend ;
break ;
case OFX_SELLDEBT :
case OFX_SELLMF :
case OFX_SELLOPT :
case OFX_SELLOTHER :
case OFX_SELLSTOCK :
t . m_eAction = MyMoneyStatement : : Transaction : : eaSell ;
break ;
case OFX_INCOME :
t . m_eAction = MyMoneyStatement : : Transaction : : eaCashDividend ;
// NOTE: With CashDividend, the amount of the dividend should
// be in data.amount. Since I've never seen an OFX file with
// cash dividends, this is an assumption on my part. (acejones)
break ;
//
// These types are all not handled. We will generate a warning for them.
//
case OFX_CLOSUREOPT :
unhandledtype = true ;
type = " CLOSUREOPT (Close a position for an option) " ;
break ;
case OFX_INVEXPENSE :
unhandledtype = true ;
type = " INVEXPENSE (Misc investment expense that is associated with a specific security) " ;
break ;
case OFX_JRNLFUND :
unhandledtype = true ;
type = " JRNLFUND (Journaling cash holdings between subaccounts within the same investment account) " ;
break ;
case OFX_MARGININTEREST :
unhandledtype = true ;
type = " MARGININTEREST (Margin interest expense) " ;
break ;
case OFX_RETOFCAP :
unhandledtype = true ;
type = " RETOFCAP (Return of capital) " ;
break ;
case OFX_SPLIT :
unhandledtype = true ;
type = " SPLIT (Stock or mutial fund split) " ;
break ;
case OFX_TRANSFER :
unhandledtype = true ;
type = " TRANSFER (Transfer holdings in and out of the investment account) " ;
break ;
default :
unhandledtype = true ;
type = TQString ( " UNKNOWN %1 " ) . arg ( data . invtransactiontype ) ;
break ;
}
}
else
t . m_eAction = MyMoneyStatement : : Transaction : : eaNone ;
// In the case of investment transactions, the 'total' is supposed to the total amount
// of the transaction. units * unitprice +/- commission. Easy, right? Sadly, it seems
// some ofx creators do not follow this in all circumstances. Therefore, we have to double-
// check the total here and adjust it if it's wrong.
#if 0
// Even more sadly, this logic is BROKEN. It consistently results in bogus total
// values, because of rounding errors in the price. A more through solution would
// be to test if the comission alone is causing a discrepency, and adjust in that case.
if ( data . invtransactiontype_valid = = true & & data . unitprice_valid )
{
double proper_total = t . m_dShares * data . unitprice + t . m_moneyFees ;
if ( proper_total ! = t . m_moneyAmount )
{
pofx - > addWarning ( TQString ( " Transaction %1 has an incorrect total of %2. Using calculated total of %3 instead. " ) . arg ( t . m_strBankID ) . arg ( t . m_moneyAmount ) . arg ( proper_total ) ) ;
t . m_moneyAmount = proper_total ;
}
}
# endif
if ( unhandledtype )
pofx - > addWarning ( TQString ( " Transaction %1 has an unsupported type (%2). " ) . arg ( t . m_strBankID , type ) ) ;
else
s . m_listTransactions + = t ;
// kdDebug(2) << __func__ << "return 0 " << endl;
return 0 ;
}
int OfxImporterPlugin : : ofxStatementCallback ( struct OfxStatementData data , void * pv )
{
// kdDebug(2) << __func__ << endl;
OfxImporterPlugin * pofx = reinterpret_cast < OfxImporterPlugin * > ( pv ) ;
MyMoneyStatement & s = pofx - > back ( ) ;
pofx - > setValid ( ) ;
if ( data . currency_valid = = true )
{
s . m_strCurrency = data . currency ;
}
if ( data . account_id_valid = = true )
{
s . m_strAccountNumber = data . account_id ;
}
if ( data . date_start_valid = = true )
{
TQDateTime dt ;
dt . setTime_t ( data . date_start , Qt : : UTC ) ;
s . m_dateBegin = dt . date ( ) ;
}
if ( data . date_end_valid = = true )
{
TQDateTime dt ;
dt . setTime_t ( data . date_end , Qt : : UTC ) ;
s . m_dateEnd = dt . date ( ) ;
}
if ( data . ledger_balance_valid = = true )
{
s . m_closingBalance = MyMoneyMoney ( data . ledger_balance ) ;
}
// kdDebug(2) << __func__ << " return 0" << endl;
return 0 ;
}
int OfxImporterPlugin : : ofxAccountCallback ( struct OfxAccountData data , void * pv )
{
// kdDebug(2) << __func__ << endl;
OfxImporterPlugin * pofx = reinterpret_cast < OfxImporterPlugin * > ( pv ) ;
pofx - > addnew ( ) ;
MyMoneyStatement & s = pofx - > back ( ) ;
// Having any account at all makes an ofx statement valid
pofx - > m_valid = true ;
if ( data . account_id_valid = = true )
{
s . m_strAccountName = data . account_name ;
s . m_strAccountNumber = data . account_id ;
}
if ( data . bank_id_valid = = true )
{
s . m_strRoutingNumber = data . bank_id ;
}
if ( data . broker_id_valid = = true )
{
s . m_strRoutingNumber = data . broker_id ;
}
if ( data . currency_valid = = true )
{
s . m_strCurrency = data . currency ;
}
if ( data . account_type_valid = = true )
{
switch ( data . account_type )
{
case OfxAccountData : : OFX_CHECKING : s . m_eType = MyMoneyStatement : : etCheckings ;
break ;
case OfxAccountData : : OFX_SAVINGS : s . m_eType = MyMoneyStatement : : etSavings ;
break ;
case OfxAccountData : : OFX_MONEYMRKT : s . m_eType = MyMoneyStatement : : etInvestment ;
break ;
case OfxAccountData : : OFX_CREDITLINE : s . m_eType = MyMoneyStatement : : etCreditCard ;
break ;
case OfxAccountData : : OFX_CMA : s . m_eType = MyMoneyStatement : : etCreditCard ;
break ;
case OfxAccountData : : OFX_CREDITCARD : s . m_eType = MyMoneyStatement : : etCreditCard ;
break ;
case OfxAccountData : : OFX_INVESTMENT : s . m_eType = MyMoneyStatement : : etInvestment ;
break ;
}
}
// ask KMyMoney for an account id
s . m_accountId = pofx - > account ( " kmmofx-acc-ref " , TQString ( " %1-%2 " ) . arg ( s . m_strRoutingNumber , s . m_strAccountNumber ) ) . id ( ) ;
// copy over the securities
s . m_listSecurities = pofx - > m_securitylist ;
// kdDebug(2) << __func__ << " return 0" << endl;
return 0 ;
}
int OfxImporterPlugin : : ofxSecurityCallback ( struct OfxSecurityData data , void * pv )
{
// kdDebug(2) << __func__ << endl;
OfxImporterPlugin * pofx = reinterpret_cast < OfxImporterPlugin * > ( pv ) ;
MyMoneyStatement : : Security sec ;
if ( data . unique_id_valid = = true ) {
sec . m_strId = data . unique_id ;
}
if ( data . secname_valid = = true ) {
sec . m_strName = data . secname ;
}
if ( data . ticker_valid = = true ) {
sec . m_strSymbol = data . ticker ;
}
pofx - > m_securitylist + = sec ;
return 0 ;
}
int OfxImporterPlugin : : ofxStatusCallback ( struct OfxStatusData data , void * pv )
{
// kdDebug(2) << __func__ << endl;
OfxImporterPlugin * pofx = reinterpret_cast < OfxImporterPlugin * > ( pv ) ;
TQString message ;
// if we got this far, we know we were able to parse the file.
// so if it fails after here it can only because there were no actual
// accounts in the file!
pofx - > m_fatalerror = " No accounts found. " ;
if ( data . ofx_element_name_valid = = true )
message . prepend ( TQString ( " %1: " ) . arg ( data . ofx_element_name ) ) ;
if ( data . code_valid = = true )
message + = TQString ( " %1 (Code %2): %3 " ) . arg ( data . name ) . arg ( data . code ) . arg ( data . description ) ;
if ( data . server_message_valid = = true )
message + = TQString ( " (%1) " ) . arg ( data . server_message ) ;
if ( data . severity_valid = = true ) {
switch ( data . severity ) {
case OfxStatusData : : INFO :
pofx - > addInfo ( message ) ;
break ;
case OfxStatusData : : ERROR :
pofx - > addError ( message ) ;
break ;
case OfxStatusData : : WARN :
pofx - > addWarning ( message ) ;
break ;
default :
pofx - > addWarning ( message ) ;
pofx - > addWarning ( " Previous message was an unknown type. 'WARNING' was assumed. " ) ;
break ;
}
}
// kdDebug(2) << __func__ << " return 0 " << endl;
return 0 ;
}
bool OfxImporterPlugin : : importStatement ( const MyMoneyStatement & s )
{
tqDebug ( " OfxImporterPlugin::importStatement start " ) ;
return statementInterface ( ) - > import ( s ) ;
}
const MyMoneyAccount & OfxImporterPlugin : : account ( const TQString & key , const TQString & value ) const
{
return statementInterface ( ) - > account ( key , value ) ;
}
void OfxImporterPlugin : : protocols ( TQStringList & protocolList ) const
{
protocolList . clear ( ) ;
protocolList < < " OFX " ;
}
TQWidget * OfxImporterPlugin : : accountConfigTab ( const MyMoneyAccount & acc , TQString & name )
{
name = i18n ( " Online settings " ) ;
m_statusDlg = new KOnlineBankingStatus ( acc , 0 , 0 ) ;
return m_statusDlg ;
}
MyMoneyKeyValueContainer OfxImporterPlugin : : onlineBankingSettings ( const MyMoneyKeyValueContainer & current )
{
MyMoneyKeyValueContainer kvp ( current ) ;
// keep the provider name in sync with the one found in kmm_ofximport.desktop
kvp [ " provider " ] = " KMyMoney OFX " ;
if ( m_statusDlg ) {
kvp . deletePair ( " appId " ) ;
kvp . deletePair ( " kmmofx-headerVersion " ) ;
if ( ! m_statusDlg - > appId ( ) . isEmpty ( ) )
kvp . setValue ( " appId " , m_statusDlg - > appId ( ) ) ;
kvp . setValue ( " kmmofx-headerVersion " , m_statusDlg - > headerVersion ( ) ) ;
kvp . setValue ( " kmmofx-numRequestDays " , TQString : : number ( m_statusDlg - > m_numdaysSpin - > value ( ) ) ) ;
kvp . setValue ( " kmmofx-todayMinus " , TQString : : number ( m_statusDlg - > m_todayRB - > isChecked ( ) ) ) ;
kvp . setValue ( " kmmofx-lastUpdate " , TQString : : number ( m_statusDlg - > m_lastUpdateRB - > isChecked ( ) ) ) ;
kvp . setValue ( " kmmofx-pickDate " , TQString : : number ( m_statusDlg - > m_pickDateRB - > isChecked ( ) ) ) ;
kvp . setValue ( " kmmofx-specificDate " , m_statusDlg - > m_specificDate - > date ( ) . toString ( ) ) ;
kvp . setValue ( " kmmofx-preferPayeeid " , TQString : : number ( m_statusDlg - > m_payeeidRB - > isChecked ( ) ) ) ;
kvp . setValue ( " kmmofx-preferName " , TQString : : number ( m_statusDlg - > m_nameRB - > isChecked ( ) ) ) ;
}
return kvp ;
}
bool OfxImporterPlugin : : mapAccount ( const MyMoneyAccount & acc , MyMoneyKeyValueContainer & settings )
{
Q_UNUSED ( acc ) ;
bool rc = false ;
KOnlineBankingSetupWizard wiz ( 0 , " onlinebankingsetup " ) ;
if ( wiz . isInit ( ) ) {
if ( wiz . exec ( ) = = TQDialog : : Accepted ) {
rc = wiz . chosenSettings ( settings ) ;
}
}
return rc ;
}
bool OfxImporterPlugin : : updateAccount ( const MyMoneyAccount & acc , bool moreAccounts )
{
Q_UNUSED ( moreAccounts ) ;
try {
if ( ! acc . id ( ) . isEmpty ( ) ) {
// Save the value of preferName to be used by ofxTransactionCallback
m_preferName = acc . onlineBankingSettings ( ) . value ( " kmmofx-preferName " ) . toInt ( ) ! = 0 ;
KOfxDirectConnectDlg dlg ( acc ) ;
connect ( & dlg , TQT_SIGNAL ( statementReady ( const TQString & ) ) ,
this , TQT_SLOT ( slotImportFile ( const TQString & ) ) ) ;
dlg . init ( ) ;
dlg . exec ( ) ;
}
} catch ( MyMoneyException * e ) {
KMessageBox : : information ( 0 , i18n ( " Error connecting to bank: %1 " ) . arg ( e - > what ( ) ) ) ;
delete e ;
}
return false ;
}
void OfxImporterPlugin : : slotImportFile ( const TQString & url )
{
if ( ! import ( url ) ) {
KMessageBox : : error ( 0 , TQString ( " <qt>%1</qt> " ) . arg ( i18n ( " Unable to import %1 using the OFX importer plugin. The plugin returned the following error:<p>%2 " ) . arg ( url , lastError ( ) ) ) , i18n ( " Importing error " ) ) ;
}
}
bool OfxImporterPlugin : : storeStatements ( TQValueList < MyMoneyStatement > & statements )
{
bool hasstatements = ( statements . count ( ) > 0 ) ;
bool ok = true ;
bool abort = false ;
// FIXME Deal with warnings/errors coming back from plugins
/*if ( ofx.errors().count() )
{
if ( KMessageBox : : warningContinueCancelList ( this , i18n ( " The following errors were returned from your bank " ) , ofx . errors ( ) , i18n ( " OFX Errors " ) ) = = KMessageBox : : Cancel )
abort = true ;
}
if ( ofx . warnings ( ) . count ( ) )
{
if ( KMessageBox : : warningContinueCancelList ( this , i18n ( " The following warnings were returned from your bank " ) , ofx . warnings ( ) , i18n ( " OFX Warnings " ) , KStdGuiItem : : cont ( ) , " ofxwarnings " ) = = KMessageBox : : Cancel )
abort = true ;
} */
tqDebug ( " OfxImporterPlugin::storeStatements() with %d statements called " , static_cast < int > ( statements . count ( ) ) ) ;
TQValueList < MyMoneyStatement > : : const_iterator it_s = statements . begin ( ) ;
while ( it_s ! = statements . end ( ) & & ! abort ) {
ok = ok & & importStatement ( ( * it_s ) ) ;
+ + it_s ;
}
if ( hasstatements & & ! ok ) {
KMessageBox : : error ( 0 , i18n ( " Importing process terminated unexpectedly. " ) , i18n ( " Failed to import all statements. " ) ) ;
}
return ( ! hasstatements | | ok ) ;
}
# include "ofximporterplugin.moc"