summaryrefslogtreecommitdiffstats
path: root/kmymoney2/converter/mymoneystatementreader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kmymoney2/converter/mymoneystatementreader.cpp')
-rw-r--r--kmymoney2/converter/mymoneystatementreader.cpp1354
1 files changed, 1354 insertions, 0 deletions
diff --git a/kmymoney2/converter/mymoneystatementreader.cpp b/kmymoney2/converter/mymoneystatementreader.cpp
new file mode 100644
index 0000000..b804a59
--- /dev/null
+++ b/kmymoney2/converter/mymoneystatementreader.cpp
@@ -0,0 +1,1354 @@
+/***************************************************************************
+ mymoneystatementreader.cpp
+ -------------------
+ begin : Mon Aug 30 2004
+ copyright : (C) 2000-2004 by Michael Edwardes
+ email : mte@users.sourceforge.net
+ Javier Campos Morales <javi_c@users.sourceforge.net>
+ Felix Rodriguez <frodriguez@users.sourceforge.net>
+ John C <thetacoturtle@users.sourceforge.net>
+ Thomas Baumgart <ipwizard@users.sourceforge.net>
+ Kevin Tambascio <ktambascio@users.sourceforge.net>
+ 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. *
+ * *
+ ***************************************************************************/
+
+#include <typeinfo>
+
+// ----------------------------------------------------------------------------
+// QT Headers
+
+#include <qfile.h>
+#include <qstringlist.h>
+#include <qtimer.h>
+#include <qtextedit.h>
+
+// ----------------------------------------------------------------------------
+// KDE Headers
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kdialogbase.h>
+#include <qvbox.h>
+#include <qlabel.h>
+
+// ----------------------------------------------------------------------------
+// Project Headers
+
+#include "mymoneystatementreader.h"
+#include <kmymoney/mymoneyfile.h>
+#include <kmymoney/mymoneystatement.h>
+#include <kmymoney/kmymoneyglobalsettings.h>
+#include <kmymoney/transactioneditor.h>
+#include <kmymoney/kmymoneyedit.h>
+#include "../dialogs/kaccountselectdlg.h"
+#include "../dialogs/transactionmatcher.h"
+#include "../dialogs/kenterscheduledlg.h"
+#include "../kmymoney2.h"
+#include <kmymoney/kmymoneyaccountcombo.h>
+
+class MyMoneyStatementReader::Private
+{
+ public:
+ Private() :
+ transactionsCount(0),
+ transactionsAdded(0),
+ transactionsMatched(0),
+ transactionsDuplicate(0),
+ scannedCategories(false)
+ {}
+
+ const QString& feeId(const MyMoneyAccount& invAcc);
+ const QString& interestId(const MyMoneyAccount& invAcc);
+ QString interestId(const QString& name);
+ QString feeId(const QString& name);
+ void assignUniqueBankID(MyMoneySplit& s, const MyMoneyStatement::Transaction& t_in);
+
+ MyMoneyAccount lastAccount;
+ QValueList<MyMoneyTransaction> transactions;
+ QValueList<MyMoneyPayee> payees;
+ int transactionsCount;
+ int transactionsAdded;
+ int transactionsMatched;
+ int transactionsDuplicate;
+ QMap<QString, bool> uniqIds;
+ QMap<QString, MyMoneySecurity> securitiesBySymbol;
+ QMap<QString, MyMoneySecurity> securitiesByName;
+ bool m_skipCategoryMatching;
+ private:
+ void scanCategories(QString& id, const MyMoneyAccount& invAcc, const MyMoneyAccount& parentAccount, const QString& defaultName);
+ QString nameToId(const QString&name, MyMoneyAccount& parent);
+ private:
+ QString m_feeId;
+ QString m_interestId;
+ bool scannedCategories;
+};
+
+
+const QString& MyMoneyStatementReader::Private::feeId(const MyMoneyAccount& invAcc)
+{
+ scanCategories(m_feeId, invAcc, MyMoneyFile::instance()->expense(), i18n("_Fees"));
+ return m_feeId;
+}
+
+const QString& MyMoneyStatementReader::Private::interestId(const MyMoneyAccount& invAcc)
+{
+ scanCategories(m_interestId, invAcc, MyMoneyFile::instance()->income(), i18n("_Dividend"));
+ return m_interestId;
+}
+
+QString MyMoneyStatementReader::Private::nameToId(const QString&name, MyMoneyAccount& parent)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+ MyMoneyAccount acc = file->accountByName(name);
+ // if it does not exist, we have to create it
+ if(acc.id().isEmpty()) {
+ acc.setName( name );
+ acc.setAccountType( parent.accountType() );
+ acc.setCurrencyId(parent.currencyId());
+ file->addAccount(acc, parent);
+ }
+ return acc.id();
+}
+
+QString MyMoneyStatementReader::Private::interestId(const QString& name)
+{
+ MyMoneyAccount parent = MyMoneyFile::instance()->income();
+ return nameToId(name, parent);
+}
+
+QString MyMoneyStatementReader::Private::feeId(const QString& name)
+{
+ MyMoneyAccount parent = MyMoneyFile::instance()->expense();
+ return nameToId(name, parent);
+}
+
+
+void MyMoneyStatementReader::Private::scanCategories(QString& id, const MyMoneyAccount& invAcc, const MyMoneyAccount& parentAccount, const QString& defaultName)
+{
+ if(!scannedCategories) {
+ KMyMoneyUtils::previouslyUsedCategories(invAcc.id(), m_feeId, m_interestId);
+ scannedCategories = true;
+ }
+
+ if(id.isEmpty()) {
+ MyMoneyFile* file = MyMoneyFile::instance();
+ MyMoneyAccount acc = file->accountByName(defaultName);
+ // if it does not exist, we have to create it
+ if(acc.id().isEmpty()) {
+ MyMoneyAccount parent = parentAccount;
+ acc.setName( defaultName );
+ acc.setAccountType( parent.accountType() );
+ acc.setCurrencyId(parent.currencyId());
+ file->addAccount(acc, parent);
+ }
+ id = acc.id();
+ }
+}
+
+void MyMoneyStatementReader::Private::assignUniqueBankID(MyMoneySplit& s, const MyMoneyStatement::Transaction& t_in)
+{
+ if( ! t_in.m_strBankID.isEmpty() ) {
+ // make sure that id's are unique from this point on by appending a -#
+ // postfix if needed
+ QString base(t_in.m_strBankID);
+ QString hash(base);
+ int idx = 1;
+ for(;;) {
+ QMap<QString, bool>::const_iterator it;
+ it = uniqIds.find(hash);
+ if(it == uniqIds.end()) {
+ uniqIds[hash] = true;
+ break;
+ }
+ hash = QString("%1-%2").arg(base).arg(idx);
+ ++idx;
+ }
+
+ s.setBankID(hash);
+ }
+}
+
+
+MyMoneyStatementReader::MyMoneyStatementReader() :
+ d(new Private),
+ m_userAbort(false),
+ m_autoCreatePayee(false),
+ m_ft(0),
+ m_progressCallback(0)
+{
+ m_askPayeeCategory = KMyMoneyGlobalSettings::askForPayeeCategory();
+}
+
+MyMoneyStatementReader::~MyMoneyStatementReader()
+{
+ delete d;
+}
+
+bool MyMoneyStatementReader::anyTransactionAdded(void) const
+{
+ return (d->transactionsAdded != 0) ? true : false;
+}
+
+void MyMoneyStatementReader::setAutoCreatePayee(bool create)
+{
+ m_autoCreatePayee = create;
+}
+
+void MyMoneyStatementReader::setAskPayeeCategory(bool ask)
+{
+ m_askPayeeCategory = ask;
+}
+
+bool MyMoneyStatementReader::import(const MyMoneyStatement& s, QStringList& messages)
+{
+ //
+ // For testing, save the statement to an XML file
+ // (uncomment this line)
+ //
+ //MyMoneyStatement::writeXMLFile(s,"Imported.Xml");
+
+ //
+ // Select the account
+ //
+
+ m_account = MyMoneyAccount();
+
+ m_ft = new MyMoneyFileTransaction();
+ d->m_skipCategoryMatching = s.m_skipCategoryMatching;
+
+ // if the statement source left some information about
+ // the account, we use it to get the current data of it
+ if(!s.m_accountId.isEmpty()) {
+ try {
+ m_account = MyMoneyFile::instance()->account(s.m_accountId);
+ } catch(MyMoneyException* e) {
+ qDebug("Received reference '%s' to unknown account in statement", s.m_accountId.data());
+ delete e;
+ }
+ }
+
+ if(m_account.id().isEmpty())
+ {
+ m_account.setName(s.m_strAccountName);
+ m_account.setNumber(s.m_strAccountNumber);
+
+ switch ( s.m_eType )
+ {
+ case MyMoneyStatement::etCheckings:
+ m_account.setAccountType(MyMoneyAccount::Checkings);
+ break;
+ case MyMoneyStatement::etSavings:
+ m_account.setAccountType(MyMoneyAccount::Savings);
+ break;
+ case MyMoneyStatement::etInvestment:
+ //testing support for investment statements!
+ //m_userAbort = true;
+ //KMessageBox::error(kmymoney2, i18n("This is an investment statement. These are not supported currently."), i18n("Critical Error"));
+ m_account.setAccountType(MyMoneyAccount::Investment);
+ break;
+ case MyMoneyStatement::etCreditCard:
+ m_account.setAccountType(MyMoneyAccount::CreditCard);
+ break;
+ default:
+ m_account.setAccountType(MyMoneyAccount::Checkings);
+ break;
+ }
+
+
+ // we ask the user only if we have some transactions to process
+ if ( !m_userAbort && s.m_listTransactions.count() > 0)
+ m_userAbort = ! selectOrCreateAccount(Select, m_account);
+ }
+
+ // see if we need to update some values stored with the account
+ if(m_account.value("lastStatementBalance") != s.m_closingBalance.toString()
+ || m_account.value("lastImportedTransactionDate") != s.m_dateEnd.toString(Qt::ISODate)) {
+ if(s.m_closingBalance != MyMoneyMoney::autoCalc) {
+ m_account.setValue("lastStatementBalance", s.m_closingBalance.toString());
+ if ( s.m_dateEnd.isValid() ) {
+ m_account.setValue("lastImportedTransactionDate", s.m_dateEnd.toString(Qt::ISODate));
+ }
+ }
+
+ try {
+ MyMoneyFile::instance()->modifyAccount(m_account);
+ } catch(MyMoneyException* e) {
+ qDebug("Updating account in MyMoneyStatementReader::startImport failed");
+ delete e;
+ }
+ }
+
+
+ if(!m_account.name().isEmpty())
+ messages += i18n("Importing statement for account %1").arg(m_account.name());
+ else if(s.m_listTransactions.count() == 0)
+ messages += i18n("Importing statement without transactions");
+
+ qDebug("Importing statement for '%s'", m_account.name().data());
+
+ //
+ // Process the securities
+ //
+ signalProgress(0, s.m_listSecurities.count(), "Importing Statement ...");
+ int progress = 0;
+ QValueList<MyMoneyStatement::Security>::const_iterator it_s = s.m_listSecurities.begin();
+ while ( it_s != s.m_listSecurities.end() )
+ {
+ processSecurityEntry(*it_s);
+ signalProgress(++progress, 0);
+ ++it_s;
+ }
+ signalProgress(-1, -1);
+
+ //
+ // Process the transactions
+ //
+
+ if ( !m_userAbort )
+ {
+ try {
+ qDebug("Processing transactions (%s)", m_account.name().data());
+ signalProgress(0, s.m_listTransactions.count(), "Importing Statement ...");
+ int progress = 0;
+ QValueList<MyMoneyStatement::Transaction>::const_iterator it_t = s.m_listTransactions.begin();
+ while ( it_t != s.m_listTransactions.end() )
+ {
+ processTransactionEntry(*it_t);
+ signalProgress(++progress, 0);
+ ++it_t;
+ }
+ qDebug("Processing transactions done (%s)", m_account.name().data());
+
+ } catch(MyMoneyException* e) {
+ if(e->what() == "USERABORT")
+ m_userAbort = true;
+ else
+ qDebug("Caught exception from processTransactionEntry() not caused by USERABORT: %s", e->what().data());
+ delete e;
+ }
+ signalProgress(-1, -1);
+ }
+
+ //
+ // process price entries
+ //
+ if ( !m_userAbort )
+ {
+ try {
+ signalProgress(0, s.m_listPrices.count(), "Importing Statement ...");
+ QValueList<MyMoneySecurity> slist = MyMoneyFile::instance()->securityList();
+ QValueList<MyMoneySecurity>::const_iterator it_s;
+ for(it_s = slist.begin(); it_s != slist.end(); ++it_s) {
+ d->securitiesBySymbol[(*it_s).tradingSymbol()] = *it_s;
+ d->securitiesByName[(*it_s).name()] = *it_s;
+ }
+
+ int progress = 0;
+ QValueList<MyMoneyStatement::Price>::const_iterator it_p = s.m_listPrices.begin();
+ while(it_p != s.m_listPrices.end()) {
+ processPriceEntry(*it_p);
+ signalProgress(++progress, 0);
+ ++it_p;
+ }
+ } catch(MyMoneyException* e) {
+ if(e->what() == "USERABORT")
+ m_userAbort = true;
+ else
+ qDebug("Caught exception from processPriceEntry() not caused by USERABORT: %s", e->what().data());
+ delete e;
+ }
+ signalProgress(-1, -1);
+ }
+
+ bool rc = false;
+
+ // delete all payees created in vain
+ int payeeCount = d->payees.count();
+ QValueList<MyMoneyPayee>::const_iterator it_p;
+ for(it_p = d->payees.begin(); it_p != d->payees.end(); ++it_p) {
+ try {
+ MyMoneyFile::instance()->removePayee(*it_p);
+ --payeeCount;
+ } catch(MyMoneyException* e) {
+ // if we can't delete it, it must be in use which is ok for us
+ delete e;
+ }
+ }
+
+ if(s.m_closingBalance.isAutoCalc()) {
+ messages += i18n(" Statement balance is not contained in statement.");
+ } else {
+ messages += i18n(" Statement balance on %1 is reported to be %2").arg(s.m_dateEnd.toString(Qt::ISODate)).arg(s.m_closingBalance.formatMoney("",2));
+ }
+ messages += i18n(" Transactions");
+ messages += i18n(" %1 processed").arg(d->transactionsCount);
+ messages += i18n(" %1 added").arg(d->transactionsAdded);
+ messages += i18n(" %1 matched").arg(d->transactionsMatched);
+ messages += i18n(" %1 duplicates").arg(d->transactionsDuplicate);
+ messages += i18n(" Payees");
+ messages += i18n(" %1 created").arg(payeeCount);
+ messages += QString();
+
+ // remove the Don't ask again entries
+ KConfig* config = KGlobal::config();
+ config->setGroup(QString::fromLatin1("Notification Messages"));
+ QStringList::ConstIterator it;
+
+ for(it = m_dontAskAgain.begin(); it != m_dontAskAgain.end(); ++it) {
+ config->deleteEntry(*it);
+ }
+ config->sync();
+ m_dontAskAgain.clear();
+
+ rc = !m_userAbort;
+
+ // finish the transaction
+ if(rc)
+ m_ft->commit();
+ delete m_ft;
+ m_ft = 0;
+
+ qDebug("Importing statement for '%s' done", m_account.name().data());
+
+ return rc;
+}
+
+void MyMoneyStatementReader::processPriceEntry(const MyMoneyStatement::Price& p_in)
+{
+ if(d->securitiesBySymbol.contains(p_in.m_strSecurity)) {
+
+ MyMoneyPrice price(d->securitiesBySymbol[p_in.m_strSecurity].id(),
+ MyMoneyFile::instance()->baseCurrency().id(),
+ p_in.m_date,
+ p_in.m_amount, "QIF");
+ MyMoneyFile::instance()->addPrice(price);
+
+ } else if(d->securitiesByName.contains(p_in.m_strSecurity)) {
+
+ MyMoneyPrice price(d->securitiesByName[p_in.m_strSecurity].id(),
+ MyMoneyFile::instance()->baseCurrency().id(),
+ p_in.m_date,
+ p_in.m_amount, "QIF");
+ MyMoneyFile::instance()->addPrice(price);
+ }
+
+}
+
+void MyMoneyStatementReader::processSecurityEntry(const MyMoneyStatement::Security& sec_in)
+{
+ // For a security entry, we will just make sure the security exists in the
+ // file. It will not get added to the investment account until it's called
+ // for in a transaction.
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ // check if we already have the security
+ // In a statement, we do not know what type of security this is, so we will
+ // not use type as a matching factor.
+ MyMoneySecurity security;
+ QValueList<MyMoneySecurity> list = file->securityList();
+ QValueList<MyMoneySecurity>::ConstIterator it = list.begin();
+ while ( it != list.end() && security.id().isEmpty() )
+ {
+ if(sec_in.m_strSymbol.isEmpty()) {
+ if((*it).name() == sec_in.m_strName)
+ security = *it;
+ } else if((*it).tradingSymbol() == sec_in.m_strSymbol)
+ security = *it;
+ ++it;
+ }
+
+ // if the security was not found, we have to create it while not forgetting
+ // to setup the type
+ if(security.id().isEmpty())
+ {
+ security.setName(sec_in.m_strName);
+ security.setTradingSymbol(sec_in.m_strSymbol);
+ security.setSmallestAccountFraction(1000);
+ security.setTradingCurrency(file->baseCurrency().id());
+ security.setValue("kmm-security-id", sec_in.m_strId);
+ security.setValue("kmm-online-source", "Yahoo");
+ security.setSecurityType(MyMoneySecurity::SECURITY_STOCK);
+ MyMoneyFileTransaction ft;
+ try {
+ file->addSecurity(security);
+ ft.commit();
+ kdDebug(0) << "Created " << security.name() << " with id " << security.id() << endl;
+ } catch(MyMoneyException *e) {
+ KMessageBox::error(0, i18n("Error creating security record: %1").arg(e->what()), i18n("Error"));
+ }
+ } else {
+ kdDebug(0) << "Found " << security.name() << " with id " << security.id() << endl;
+ }
+}
+
+void MyMoneyStatementReader::processTransactionEntry(const MyMoneyStatement::Transaction& t_in)
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ MyMoneyTransaction t;
+
+#if 0
+ QString dbgMsg;
+ dbgMsg = QString("Process %1, '%3', %2").arg(t_in.m_datePosted.toString(Qt::ISODate)).arg(t_in.m_amount.formatMoney("", 2)).arg(t_in.m_strBankID);
+ qDebug("%s", dbgMsg.data());
+#endif
+
+ // mark it imported for the view
+ t.setImported();
+
+ // TODO (Ace) We can get the commodity from the statement!!
+ // Although then we would need UI to verify
+ t.setCommodity(m_account.currencyId());
+
+ t.setPostDate(t_in.m_datePosted);
+ t.setMemo(t_in.m_strMemo);
+
+#if 0
+ // (acejones) removing this code. keeping it around for reference.
+ //
+ // this is the OLD way of handling bank ID's, which unfortunately was wrong.
+ // bank ID's actually need to go on the split which corresponds with the
+ // account we're importing into.
+ //
+ // thus anywhere "this account" is put into a split is also where we need
+ // to put the bank ID in.
+ //
+ if ( ! t_in.m_strBankID.isEmpty() )
+ t.setBankID(t_in.m_strBankID);
+#endif
+
+ MyMoneySplit s1;
+
+ s1.setMemo(t_in.m_strMemo);
+ s1.setValue(t_in.m_amount - t_in.m_fees);
+ s1.setShares(s1.value());
+ s1.setNumber(t_in.m_strNumber);
+
+ // set these values if a transfer split is needed at the very end.
+ MyMoneyMoney transfervalue;
+
+ // If the user has chosen to import into an investment account, determine the correct account to use
+ MyMoneyAccount thisaccount = m_account;
+ QString brokerageactid;
+
+ if ( thisaccount.accountType() == MyMoneyAccount::Investment )
+ {
+ // determine the brokerage account
+ brokerageactid = m_account.value("kmm-brokerage-account").utf8();
+ if (brokerageactid.isEmpty() )
+ {
+ brokerageactid = file->accountByName(m_account.brokerageName()).id();
+ }
+
+ // find the security transacted, UNLESS this transaction didn't
+ // involve any security.
+ if ( (t_in.m_eAction != MyMoneyStatement::Transaction::eaNone)
+ && (t_in.m_eAction != MyMoneyStatement::Transaction::eaInterest)
+ && (t_in.m_eAction != MyMoneyStatement::Transaction::eaFees))
+ {
+ // the correct account is the stock account which matches two criteria:
+ // (1) it is a sub-account of the selected investment account, and
+ // (2a) the symbol of the underlying security matches the security of the
+ // transaction, or
+ // (2b) the name of the security matches the name of the security of the transaction.
+
+ // search through each subordinate account
+ bool found = false;
+ QStringList accounts = thisaccount.accountList();
+ QStringList::const_iterator it_account = accounts.begin();
+ while( !found && it_account != accounts.end() )
+ {
+ QString currencyid = file->account(*it_account).currencyId();
+ MyMoneySecurity security = file->security( currencyid );
+ if((t_in.m_strSymbol.lower() == security.tradingSymbol().lower())
+ || (t_in.m_strSecurity.lower() == security.name().lower()))
+ {
+ thisaccount = file->account(*it_account);
+ found = true;
+
+ // Don't update price if there is no price information contained in the transaction
+ if(t_in.m_eAction != MyMoneyStatement::Transaction::eaCashDividend
+ && t_in.m_eAction != MyMoneyStatement::Transaction::eaShrsin
+ && t_in.m_eAction != MyMoneyStatement::Transaction::eaShrsout)
+ {
+ // update the price, while we're here. in the future, this should be
+ // an option
+ QString basecurrencyid = file->baseCurrency().id();
+ MyMoneyPrice price = file->price( currencyid, basecurrencyid, t_in.m_datePosted, true );
+ if ( !price.isValid() && ((!t_in.m_amount.isZero() && !t_in.m_shares.isZero()) || !t_in.m_price.isZero()))
+ {
+ MyMoneyPrice newprice;
+ if(!t_in.m_price.isZero()) {
+ newprice = MyMoneyPrice( currencyid, basecurrencyid, t_in.m_datePosted,
+ t_in.m_price.abs(), i18n("Statement Importer") );
+ } else {
+ newprice = MyMoneyPrice( currencyid, basecurrencyid, t_in.m_datePosted,
+ (t_in.m_amount / t_in.m_shares).abs(), i18n("Statement Importer") );
+ }
+ file->addPrice(newprice);
+ }
+ }
+ }
+
+ ++it_account;
+ }
+
+ // If there was no stock account under the m_acccount investment account,
+ // add one using the security.
+ if (!found)
+ {
+ // The security should always be available, because the statement file
+ // should separately list all the securities referred to in the file,
+ // and when we found a security, we added it to the file.
+
+ if ( t_in.m_strSecurity.isEmpty() )
+ {
+ KMessageBox::information(0, i18n("This imported statement contains investment transactions with no security. These transactions will be ignored.").arg(t_in.m_strSecurity),i18n("Security not found"),QString("BlankSecurity"));
+ return;
+ }
+ else
+ {
+ MyMoneySecurity security;
+ QValueList<MyMoneySecurity> list = MyMoneyFile::instance()->securityList();
+ QValueList<MyMoneySecurity>::ConstIterator it = list.begin();
+ while ( it != list.end() && security.id().isEmpty() )
+ {
+ if(t_in.m_strSecurity.lower() == (*it).tradingSymbol().lower()
+ || t_in.m_strSecurity.lower() == (*it).name().lower()) {
+ security = *it;
+ }
+ ++it;
+ }
+ if(!security.id().isEmpty())
+ {
+ thisaccount = MyMoneyAccount();
+ thisaccount.setName(security.name());
+ thisaccount.setAccountType(MyMoneyAccount::Stock);
+ thisaccount.setCurrencyId(security.id());
+
+ file->addAccount(thisaccount, m_account);
+ kdDebug(0) << __func__ << ": created account " << thisaccount.id() << " for security " << t_in.m_strSecurity << " under account " << m_account.id() << endl;
+ }
+ // this security does not exist in the file.
+ else
+ {
+ // This should be rare. A statement should have a security entry for any
+ // of the securities referred to in the transactions. The only way to get
+ // here is if that's NOT the case.
+ KMessageBox::information(0, i18n("This investment account does not contain the \"%1\" security. Transactions involving this security will be ignored.").arg(t_in.m_strSecurity),i18n("Security not found"),QString("MissingSecurity%1").arg(t_in.m_strSecurity.stripWhiteSpace()));
+ return;
+ }
+ }
+ }
+ }
+
+ s1.setAccountId(thisaccount.id());
+ d->assignUniqueBankID(s1, t_in);
+
+ if (t_in.m_eAction==MyMoneyStatement::Transaction::eaReinvestDividend)
+ {
+ s1.setAction(MyMoneySplit::ActionReinvestDividend);
+ s1.setShares(t_in.m_shares);
+
+ if(!t_in.m_price.isZero()) {
+ s1.setPrice(t_in.m_price);
+ } else {
+ s1.setPrice(((t_in.m_amount - t_in.m_fees) / t_in.m_shares).convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())));
+ }
+
+
+ MyMoneySplit s2;
+ s2.setMemo(t_in.m_strMemo);
+ if(t_in.m_strInterestCategory.isEmpty())
+ s2.setAccountId(d->interestId(thisaccount));
+ else
+ s2.setAccountId(d->interestId(t_in.m_strInterestCategory));
+
+ s2.setShares(-t_in.m_amount - t_in.m_fees);
+ s2.setValue(s2.shares());
+ t.addSplit(s2);
+ }
+ else if (t_in.m_eAction==MyMoneyStatement::Transaction::eaCashDividend)
+ {
+ // Cash dividends require setting 2 splits to get all of the information
+ // in. Split #1 will be the income split, and we'll set it to the first
+ // income account. This is a hack, but it's needed in order to get the
+ // amount into the transaction.
+
+ // There are some sign issues. The OFX plugin universally reverses the sign
+ // for investment transactions.
+ //
+ // The way we interpret the sign on 'amount' is the s1 split, which is always
+ // the thing that's NOT the cash account. For dividends, it's the income
+ // category, for buy/sell it's the stock account.
+ //
+ // For cash account transactions, the s1 split IS the cash account split,
+ // which explains why they have to be reversed for investment transactions
+ //
+ // Ergo, the 'amount' is negative at this point and needs to stay negative.
+ // The 'fees' is positive.
+ //
+ // This should probably change. It would be more consistent to ALWAYS
+ // interpret the 'amount' as the cash account part.
+
+ if(t_in.m_strInterestCategory.isEmpty())
+ s1.setAccountId(d->interestId(thisaccount));
+ else
+ s1.setAccountId(d->interestId(t_in.m_strInterestCategory));
+ s1.setShares(t_in.m_amount);
+ s1.setValue(t_in.m_amount);
+
+ // Split 2 will be the zero-amount investment split that serves to
+ // mark this transaction as a cash dividend and note which stock account
+ // it belongs to.
+ MyMoneySplit s2;
+ s2.setMemo(t_in.m_strMemo);
+ s2.setAction(MyMoneySplit::ActionDividend);
+ s2.setAccountId(thisaccount.id());
+ t.addSplit(s2);
+
+ transfervalue = -t_in.m_amount - t_in.m_fees;
+ }
+ else if (t_in.m_eAction==MyMoneyStatement::Transaction::eaInterest)
+ {
+ if(t_in.m_strInterestCategory.isEmpty())
+ s1.setAccountId(d->interestId(thisaccount));
+ else
+ s1.setAccountId(d->interestId(t_in.m_strInterestCategory));
+ s1.setShares(t_in.m_amount);
+ s1.setValue(t_in.m_amount);
+
+ transfervalue = -t_in.m_amount;
+
+ }
+ else if (t_in.m_eAction==MyMoneyStatement::Transaction::eaFees)
+ {
+ if(t_in.m_strInterestCategory.isEmpty())
+ s1.setAccountId(d->feeId(thisaccount));
+ else
+ s1.setAccountId(d->feeId(t_in.m_strInterestCategory));
+ s1.setShares(t_in.m_amount);
+ s1.setValue(t_in.m_amount);
+
+ transfervalue = -t_in.m_amount;
+
+ }
+ else if ((t_in.m_eAction==MyMoneyStatement::Transaction::eaBuy ) ||
+ (t_in.m_eAction==MyMoneyStatement::Transaction::eaSell))
+ {
+ if(!t_in.m_price.isZero()) {
+ s1.setPrice(t_in.m_price.abs());
+ } else {
+ MyMoneyMoney total;
+ total = t_in.m_amount - t_in.m_fees;
+ if(!t_in.m_shares.isZero())
+ s1.setPrice((total / t_in.m_shares).abs().convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())));
+ }
+
+ s1.setAction(MyMoneySplit::ActionBuyShares);
+
+ // Make sure to setup the sign correctly
+ if(t_in.m_eAction==MyMoneyStatement::Transaction::eaBuy ) {
+ s1.setShares(t_in.m_shares.abs());
+ s1.setValue(s1.value().abs());
+ transfervalue = -(t_in.m_amount.abs());
+ } else {
+ s1.setShares(-(t_in.m_shares.abs()));
+ s1.setValue(-(s1.value().abs()));
+ transfervalue = t_in.m_amount.abs();
+ }
+
+ }
+ else if ((t_in.m_eAction==MyMoneyStatement::Transaction::eaShrsin) ||
+ (t_in.m_eAction==MyMoneyStatement::Transaction::eaShrsout))
+ {
+ s1.setValue(MyMoneyMoney());
+ s1.setShares(t_in.m_shares);
+ s1.setAction(MyMoneySplit::ActionAddShares);
+ }
+ else if (t_in.m_eAction==MyMoneyStatement::Transaction::eaNone)
+ {
+ // User is attempting to import a non-investment transaction into this
+ // investment account. This is not supportable the way KMyMoney is
+ // written. However, if a user has an associated brokerage account,
+ // we can stuff the transaction there.
+
+ QString brokerageactid = m_account.value("kmm-brokerage-account").utf8();
+ if (brokerageactid.isEmpty() )
+ {
+ brokerageactid = file->accountByName(m_account.brokerageName()).id();
+ }
+ if ( ! brokerageactid.isEmpty() )
+ {
+ s1.setAccountId(brokerageactid);
+ d->assignUniqueBankID(s1, t_in);
+
+ // Needed to satisfy the bankid check below.
+ thisaccount = file->account(brokerageactid);
+ }
+ else
+ {
+ // Warning!! Your transaction is being thrown away.
+ }
+ }
+ if ( !t_in.m_fees.isZero() )
+ {
+ MyMoneySplit s;
+ s.setMemo(i18n("(Fees) ") + t_in.m_strMemo);
+ s.setValue(t_in.m_fees);
+ s.setShares(t_in.m_fees);
+ s.setAccountId(d->feeId(thisaccount));
+ t.addSplit(s);
+ }
+ }
+ else
+ {
+ // For non-investment accounts, just use the selected account
+ // Note that it is perfectly reasonable to import an investment statement into a non-investment account
+ // if you really want. The investment-specific information, such as number of shares and action will
+ // be discarded in that case.
+ s1.setAccountId(m_account.id());
+ d->assignUniqueBankID(s1, t_in);
+ }
+
+
+ QString payeename = t_in.m_strPayee;
+ if(!payeename.isEmpty())
+ {
+ QString payeeid;
+ try {
+ QValueList<MyMoneyPayee> pList = file->payeeList();
+ QValueList<MyMoneyPayee>::const_iterator it_p;
+ QMap<int, QString> matchMap;
+ for(it_p = pList.begin(); it_p != pList.end(); ++it_p) {
+ bool ignoreCase;
+ QStringList keys;
+ QStringList::const_iterator it_s;
+ switch((*it_p).matchData(ignoreCase, keys)) {
+ case MyMoneyPayee::matchDisabled:
+ break;
+
+ case MyMoneyPayee::matchName:
+ keys << QString("%1").arg(QRegExp::escape((*it_p).name()));
+ // tricky fall through here
+
+ case MyMoneyPayee::matchKey:
+ for(it_s = keys.begin(); it_s != keys.end(); ++it_s) {
+ QRegExp exp(*it_s, !ignoreCase);
+ if(exp.search(payeename) != -1) {
+ matchMap[exp.matchedLength()] = (*it_p).id();
+ }
+ }
+ break;
+ }
+ }
+
+ // at this point we can have several scenarios:
+ // a) multiple matches
+ // b) a single match
+ // c) no match at all
+ //
+ // for c) we just do nothing, for b) we take the one we found
+ // in case of a) we take the one with the largest matchedLength()
+ // which happens to be the last one in the map
+ if(matchMap.count() > 1) {
+ QMap<int, QString>::const_iterator it_m = matchMap.end();
+ --it_m;
+ payeeid = *it_m;
+ } else if(matchMap.count() == 1)
+ payeeid = *(matchMap.begin());
+
+ // if we did not find a matching payee, we throw an exception and try to create it
+ if(payeeid.isEmpty())
+ throw new MYMONEYEXCEPTION("payee not matched");
+
+ s1.setPayeeId(payeeid);
+ }
+ catch (MyMoneyException *e)
+ {
+ MyMoneyPayee payee;
+ int rc = KMessageBox::Yes;
+
+ if(m_autoCreatePayee == false) {
+ // Ask the user if that is what he intended to do?
+ QString msg = i18n("Do you want to add \"%1\" as payee/receiver?\n\n").arg(payeename);
+ msg += i18n("Selecting \"Yes\" will create the payee, \"No\" will skip "
+ "creation of a payee record and remove the payee information "
+ "from this transaction. Selecting \"Cancel\" aborts the import "
+ "operation.\n\nIf you select \"No\" here and mark the \"Don't ask "
+ "again\" checkbox, the payee information for all following transactions "
+ "referencing \"%1\" will be removed.").arg(payeename);
+
+ QString askKey = QString("Statement-Import-Payee-")+payeename;
+ if(!m_dontAskAgain.contains(askKey)) {
+ m_dontAskAgain += askKey;
+ }
+ rc = KMessageBox::questionYesNoCancel(0, msg, i18n("New payee/receiver"),
+ KStdGuiItem::yes(), KStdGuiItem::no(), askKey);
+ }
+ delete e;
+
+ if(rc == KMessageBox::Yes) {
+ // for now, we just add the payee to the pool and turn
+ // on simple name matching, so that future transactions
+ // with the same name don't get here again.
+ //
+ // In the future, we could open a dialog and ask for
+ // all the other attributes of the payee, but since this
+ // is called in the context of an automatic procedure it
+ // might distract the user.
+ payee.setName(payeename);
+ payee.setMatchData(MyMoneyPayee::matchName, true, QStringList());
+ if (m_askPayeeCategory) {
+ // We use a QGuardedPtr because the dialog may get deleted
+ // during exec() if the parent of the dialog gets deleted.
+ // In that case the guarded ptr will reset to 0.
+ QGuardedPtr<KDialogBase> dialog = new KDialogBase(
+ "Default Category for Payee",
+ KDialogBase::Yes | KDialogBase::No | KDialogBase::Cancel,
+ KDialogBase::Yes, KDialogBase::Cancel,
+ 0, "questionYesNoCancel", true, true,
+ KGuiItem(i18n("Save Category")),
+ KGuiItem(i18n("No Category")),
+ KGuiItem(i18n("Abort")));
+ QVBox *topcontents = new QVBox (dialog);
+ topcontents->setSpacing(KDialog::spacingHint()*2);
+ topcontents->setMargin(KDialog::marginHint());
+
+ //add in caption? and account combo here
+ QLabel *label1 = new QLabel( topcontents);
+ label1->setText(i18n("Please select a default category for payee '%1':").arg(payee.name().data()));
+
+ QGuardedPtr<KMyMoneyAccountCombo> accountCombo = new KMyMoneyAccountCombo(topcontents);
+ dialog->setMainWidget(topcontents);
+
+ int result = dialog->exec();
+
+ QString accountId;
+ if (accountCombo && !accountCombo->selectedAccounts().isEmpty()) {
+ accountId = accountCombo->selectedAccounts().front();
+ }
+ if (dialog) {
+ delete dialog;
+ }
+ //if they hit yes instead of no, then grab setting of account combo
+ if (result == KDialogBase::Yes) {
+ payee.setDefaultAccountId(accountId);
+ }
+ else if (result != KDialogBase::No) {
+ //add cancel button? and throw exception like below
+ throw new MYMONEYEXCEPTION("USERABORT");
+ }
+ }
+
+ try {
+ file->addPayee(payee);
+ qDebug("Payee '%s' created", payee.name().data());
+ d->payees << payee;
+ payeeid = payee.id();
+ s1.setPayeeId(payeeid);
+
+ } catch(MyMoneyException *e) {
+ KMessageBox::detailedSorry(0, i18n("Unable to add payee/receiver"),
+ (e->what() + " " + i18n("thrown in") + " " + e->file()+ ":%1").arg(e->line()));
+ delete e;
+
+ }
+
+ } else if(rc == KMessageBox::No) {
+ s1.setPayeeId(QString());
+
+ } else {
+ throw new MYMONEYEXCEPTION("USERABORT");
+
+ }
+ }
+
+ if(thisaccount.accountType() != MyMoneyAccount::Stock ) {
+ //
+ // Fill in other side of the transaction (category/etc) based on payee
+ //
+ // Note, this logic is lifted from KLedgerView::slotPayeeChanged(),
+ // however this case is more complicated, because we have an amount and
+ // a memo. We just don't have the other side of the transaction.
+ //
+ // We'll search for the most recent transaction in this account with
+ // this payee. If this reference transaction is a simple 2-split
+ // transaction, it's simple. If it's a complex split, and the amounts
+ // are different, we have a problem. Somehow we have to balance the
+ // transaction. For now, we'll leave it unbalanced, and let the user
+ // handle it.
+ //
+ const MyMoneyPayee& payeeObj = MyMoneyFile::instance()->payee(payeeid);
+ if (t_in.m_listSplits.isEmpty() && payeeObj.defaultAccountEnabled()) {
+ MyMoneySplit s;
+ s.setReconcileFlag(MyMoneySplit::Cleared);
+ s.clearId();
+ s.setBankID(QString());
+ s.setShares(-s1.shares());
+ s.setValue(-s1.value());
+ s.setAccountId(payeeObj.defaultAccountId());
+ t.addSplit(s);
+ }
+ else if (t_in.m_listSplits.isEmpty() && !d->m_skipCategoryMatching) {
+ MyMoneyTransactionFilter filter(thisaccount.id());
+ filter.addPayee(payeeid);
+ QValueList<MyMoneyTransaction> list = file->transactionList(filter);
+ if(!list.empty())
+ {
+ // Default to using the most recent transaction as the reference
+ MyMoneyTransaction t_old = list.last();
+
+ // if there is more than one matching transaction, try to be a little
+ // smart about which one we take. for now, we'll see if there's one
+ // with the same VALUE as our imported transaction, and if so take that one.
+ if ( list.count() > 1 )
+ {
+ QValueList<MyMoneyTransaction>::ConstIterator it_trans = list.fromLast();
+ while ( it_trans != list.end() )
+ {
+ MyMoneySplit s = (*it_trans).splitByAccount(thisaccount.id());
+ if ( s.value() == s1.value() )
+ {
+ t_old = *it_trans;
+ break;
+ }
+ --it_trans;
+ }
+ }
+
+ QValueList<MyMoneySplit>::ConstIterator it_split;
+ for(it_split = t_old.splits().begin(); it_split != t_old.splits().end(); ++it_split)
+ {
+ // We don't need the split that covers this account,
+ // we just need the other ones.
+ if ( (*it_split).accountId() != thisaccount.id() )
+ {
+ MyMoneySplit s(*it_split);
+ s.setReconcileFlag(MyMoneySplit::NotReconciled);
+ s.clearId();
+ s.setBankID(QString());
+
+ if ( t_old.splits().count() == 2 )
+ {
+ s.setShares(-s1.shares());
+ s.setValue(-s1.value());
+ s.setMemo(s1.memo());
+ }
+ t.addSplit(s);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ s1.setReconcileFlag(t_in.m_reconcile);
+ t.addSplit(s1);
+
+ // Add the 'account' split if it's needed
+ if ( ! transfervalue.isZero() )
+ {
+ // in case the transaction has a reference to the brokerage account, we use it
+ if(!t_in.m_strBrokerageAccount.isEmpty()) {
+ brokerageactid = file->accountByName(t_in.m_strBrokerageAccount).id();
+ }
+
+ if ( !brokerageactid.isEmpty() )
+ {
+ // FIXME This may not deal with foreign currencies properly
+ MyMoneySplit s;
+ s.setMemo(t_in.m_strMemo);
+ s.setValue(transfervalue);
+ s.setShares(transfervalue);
+ s.setAccountId(brokerageactid);
+ s.setReconcileFlag(t_in.m_reconcile);
+ t.addSplit(s);
+ }
+ }
+
+ if ((t_in.m_eAction != MyMoneyStatement::Transaction::eaReinvestDividend) && (t_in.m_eAction!=MyMoneyStatement::Transaction::eaCashDividend)
+ )
+ {
+ //******************************************
+ // process splits
+ //******************************************
+
+ QValueList<MyMoneyStatement::Split>::const_iterator it_s;
+ for(it_s = t_in.m_listSplits.begin(); it_s != t_in.m_listSplits.end(); ++it_s) {
+ MyMoneySplit s2;
+ s2.setAccountId((*it_s).m_accountId);
+ MyMoneyAccount acc = file->account(s2.accountId());
+ if(acc.isAssetLiability()) {
+ s2.setPayeeId(s1.payeeId());
+ }
+ s2.setMemo((*it_s).m_strMemo);
+ s2.setShares((*it_s).m_amount);
+ s2.setValue((*it_s).m_amount);
+ s2.setReconcileFlag((*it_s).m_reconcile);
+ t.addSplit(s2);
+ }
+
+#if 0
+ QString accountId;
+ int count;
+ int cnt = 0;
+ count = t_in.m_listSplits.count();
+
+ for(cnt = 0; cnt < count; ++cnt )
+ {
+ MyMoneySplit s2 = s1;
+ s2.setMemo(t_in.m_listSplits[cnt].m_strMemo);
+ s2.clearId();
+ s2.setValue(t_in.m_listSplits[cnt].m_amount);
+ s2.setShares(t_in.m_listSplits[cnt].m_amount);
+ s2.setAccountId(QString(t_in.m_listSplits[cnt].m_accountId));
+#if 0
+ accountId = file->nameToAccount(t_in.m_listSplits[cnt].m_strCategoryName);
+ if (accountId.isEmpty())
+ accountId = checkCategory(t_in.m_listSplits[cnt].m_strCategoryName, t_in.m_listSplits[0].m_amount, t_in.m_listSplits[cnt].m_amount);
+
+ s2.setAccountId(accountId);
+#endif
+ t.addSplit(s2);
+ }
+#endif
+ }
+
+ // Add the transaction
+ try {
+
+ // check for matches already stored in the engine
+ MyMoneySplit matchedSplit;
+ TransactionMatcher::autoMatchResultE result;
+ TransactionMatcher matcher(thisaccount);
+ matcher.setMatchWindow(KMyMoneyGlobalSettings::matchInterval());
+ const MyMoneyObject *o = matcher.findMatch(t, s1, matchedSplit, result);
+ d->transactionsCount++;
+
+ // if we did not already find this one, we need to process it
+ if(result != TransactionMatcher::matchedDuplicate) {
+ d->transactionsAdded++;
+ file->addTransaction(t);
+
+ if(o) {
+ if(typeid(*o) == typeid(MyMoneyTransaction)) {
+ // it matched a simple transaction. that's the easy case
+ MyMoneyTransaction tm(*(dynamic_cast<const MyMoneyTransaction*>(o)));
+ switch(result) {
+ case TransactionMatcher::notMatched:
+ case TransactionMatcher::matchedDuplicate:
+ // no need to do anything here
+ break;
+ case TransactionMatcher::matched:
+ case TransactionMatcher::matchedExact:
+ qDebug("Detected as match to transaction '%s'", tm.id().data());
+ matcher.match(tm, matchedSplit, t, s1, true);
+ d->transactionsMatched++;
+ break;
+ }
+
+ } else if(typeid(*o) == typeid(MyMoneySchedule)) {
+ // a match has been found in a pending schedule. We'll ask the user if she wants
+ // to enter the schedule and match it agains the new transaction. Otherwise, we
+ // just leave the transaction as imported.
+ MyMoneySchedule schedule(*(dynamic_cast<const MyMoneySchedule*>(o)));
+ if(KMessageBox::questionYesNo(0, QString("<qt>%1</qt>").arg(i18n("KMyMoney has found a scheduled transaction named <b>%1</b> which matches an imported transaction. Do you want KMyMoney to enter this schedule now so that the transaction can be matched? ").arg(schedule.name())), i18n("Schedule found")) == KMessageBox::Yes) {
+ KEnterScheduleDlg dlg(0, schedule);
+ TransactionEditor* editor = dlg.startEdit();
+ if(editor) {
+ MyMoneyTransaction torig;
+ // in case the amounts of the scheduled transaction and the
+ // imported transaction differ, we need to update the amount
+ // using the transaction editor.
+ if(matchedSplit.shares() != s1.shares() && !schedule.isFixed()) {
+ // for now this only works with regular transactions and not
+ // for investment transactions. As of this, we don't have
+ // scheduled investment transactions anyway.
+ StdTransactionEditor* se = dynamic_cast<StdTransactionEditor*>(editor);
+ if(se) {
+ // the following call will update the amount field in the
+ // editor and also adjust a possible VAT assignment. Make
+ // sure to use only the absolute value of the amount, because
+ // the editor keeps the sign in a different position (deposit,
+ // withdrawal tab)
+ kMyMoneyEdit* amount = dynamic_cast<kMyMoneyEdit*>(se->haveWidget("amount"));
+ if(amount) {
+ amount->setValue(s1.shares().abs());
+ se->slotUpdateAmount(s1.shares().abs().toString());
+
+ // we also need to update the matchedSplit variable to
+ // have the modified share/value.
+ matchedSplit.setShares(s1.shares());
+ matchedSplit.setValue(s1.value());
+ }
+ }
+ }
+
+ editor->createTransaction(torig, dlg.transaction(), dlg.transaction().splits()[0], true);
+ QString newId;
+ if(editor->enterTransactions(newId, false, true)) {
+ if(!newId.isEmpty()) {
+ torig = MyMoneyFile::instance()->transaction(newId);
+ schedule.setLastPayment(torig.postDate());
+ }
+ schedule.setNextDueDate(schedule.nextPayment(schedule.nextDueDate()));
+ MyMoneyFile::instance()->modifySchedule(schedule);
+ }
+
+ // now match the two transactions
+ matcher.match(torig, matchedSplit, t, s1);
+ d->transactionsMatched++;
+ }
+ delete editor;
+ }
+ }
+ }
+ } else {
+ d->transactionsDuplicate++;
+ qDebug("Detected as duplicate");
+ }
+ delete o;
+ } catch (MyMoneyException *e) {
+ QString message(i18n("Problem adding or matching imported transaction with id '%1': %2").arg(t_in.m_strBankID).arg(e->what()));
+ qDebug("%s", message.data());
+ delete e;
+
+ int result = KMessageBox::warningContinueCancel(0, message);
+ if ( result == KMessageBox::Cancel )
+ throw new MYMONEYEXCEPTION("USERABORT");
+ }
+}
+
+bool MyMoneyStatementReader::selectOrCreateAccount(const SelectCreateMode /*mode*/, MyMoneyAccount& account)
+{
+ bool result = false;
+
+ MyMoneyFile* file = MyMoneyFile::instance();
+
+ QString accountId;
+
+ // Try to find an existing account in the engine which matches this one.
+ // There are two ways to be a "matching account". The account number can
+ // match the statement account OR the "StatementKey" property can match.
+ // Either way, we'll update the "StatementKey" property for next time.
+
+ QString accountNumber = account.number();
+ if ( ! accountNumber.isEmpty() )
+ {
+ // Get a list of all accounts
+ QValueList<MyMoneyAccount> accounts;
+ file->accountList(accounts);
+
+ // Iterate through them
+ QValueList<MyMoneyAccount>::const_iterator it_account = accounts.begin();
+ while ( it_account != accounts.end() )
+ {
+ if (
+ ( (*it_account).value("StatementKey") == accountNumber ) ||
+ ( (*it_account).number() == accountNumber )
+ )
+ {
+ MyMoneyAccount newAccount((*it_account).id(), account);
+ account = newAccount;
+ accountId = (*it_account).id();
+ break;
+ }
+
+ ++it_account;
+ }
+ }
+
+ QString msg = i18n("<b>You have downloaded a statement for the following account:</b><br><br>");
+ msg += i18n(" - Account Name: %1").arg(account.name()) + "<br>";
+ msg += i18n(" - Account Type: %1").arg(KMyMoneyUtils::accountTypeToString(account.accountType())) + "<br>";
+ msg += i18n(" - Account Number: %1").arg(account.number()) + "<br>";
+ msg += "<br>";
+
+ QString header;
+
+ if(!account.name().isEmpty())
+ {
+ if(!accountId.isEmpty())
+ msg += i18n("Do you want to import transactions to this account?");
+ else
+ msg += i18n("KMyMoney cannot determine which of your accounts to use. You can "
+ "create a new account by pressing the <b>Create</b> button "
+ "or select another one manually from the selection box below.");
+ }
+ else
+ {
+ msg += i18n("No account information has been found in the selected statement file. "
+ "Please select an account using the selection box in the dialog or "
+ "create a new account by pressing the <b>Create</b> button.");
+ }
+
+ KMyMoneyUtils::categoryTypeE type = static_cast<KMyMoneyUtils::categoryTypeE>(KMyMoneyUtils::asset|KMyMoneyUtils::liability);
+ KAccountSelectDlg accountSelect(type, "StatementImport", kmymoney2);
+ accountSelect.setHeader(i18n("Import transactions"));
+ accountSelect.setDescription(msg);
+ accountSelect.setAccount(account, accountId);
+ accountSelect.setMode(false);
+ accountSelect.showAbortButton(true);
+ accountSelect.m_qifEntry->hide();
+ QString accname;
+ bool done = false;
+ while ( !done )
+ {
+ if ( accountSelect.exec() == QDialog::Accepted && !accountSelect.selectedAccount().isEmpty() )
+ {
+ result = true;
+ done = true;
+ accountId = accountSelect.selectedAccount();
+ account = file->account(accountId);
+ if ( ! accountNumber.isEmpty() && account.value("StatementKey") != accountNumber )
+ {
+ account.setValue("StatementKey", accountNumber);
+ MyMoneyFileTransaction ft;
+ try {
+ MyMoneyFile::instance()->modifyAccount(account);
+ ft.commit();
+ accname = account.name();
+ } catch(MyMoneyException* e) {
+ qDebug("Updating account in MyMoneyStatementReader::selectOrCreateAccount failed");
+ delete e;
+ }
+ }
+ }
+ else
+ {
+ if(accountSelect.aborted())
+ //throw new MYMONEYEXCEPTION("USERABORT");
+ done = true;
+ else
+ KMessageBox::error(0, QString("<qt>%1</qt>").arg(i18n("You must select an account, create a new one, or press the <b>Abort</b> button.")));
+ }
+ }
+ return result;
+}
+
+void MyMoneyStatementReader::setProgressCallback(void(*callback)(int, int, const QString&))
+{
+ m_progressCallback = callback;
+}
+
+void MyMoneyStatementReader::signalProgress(int current, int total, const QString& msg)
+{
+ if(m_progressCallback != 0)
+ (*m_progressCallback)(current, total, msg);
+}
+
+
+#include "mymoneystatementreader.moc"
+// vim:cin:si:ai:et:ts=2:sw=2: