You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1355 lines
49 KiB
1355 lines
49 KiB
/***************************************************************************
|
|
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 <tqfile.h>
|
|
#include <tqstringlist.h>
|
|
#include <tqtimer.h>
|
|
#include <tqtextedit.h>
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// KDE Headers
|
|
|
|
#include <klocale.h>
|
|
#include <kmessagebox.h>
|
|
#include <kconfig.h>
|
|
#include <kdebug.h>
|
|
#include <kdialogbase.h>
|
|
#include <tqvbox.h>
|
|
#include <tqlabel.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 TQString& feeId(const MyMoneyAccount& invAcc);
|
|
const TQString& interestId(const MyMoneyAccount& invAcc);
|
|
TQString interestId(const TQString& name);
|
|
TQString feeId(const TQString& name);
|
|
void assignUniqueBankID(MyMoneySplit& s, const MyMoneyStatement::Transaction& t_in);
|
|
|
|
MyMoneyAccount lastAccount;
|
|
TQValueList<MyMoneyTransaction> transactions;
|
|
TQValueList<MyMoneyPayee> payees;
|
|
int transactionsCount;
|
|
int transactionsAdded;
|
|
int transactionsMatched;
|
|
int transactionsDuplicate;
|
|
TQMap<TQString, bool> uniqIds;
|
|
TQMap<TQString, MyMoneySecurity> securitiesBySymbol;
|
|
TQMap<TQString, MyMoneySecurity> securitiesByName;
|
|
bool m_skipCategoryMatching;
|
|
private:
|
|
void scanCategories(TQString& id, const MyMoneyAccount& invAcc, const MyMoneyAccount& parentAccount, const TQString& defaultName);
|
|
TQString nameToId(const TQString&name, MyMoneyAccount& tqparent);
|
|
private:
|
|
TQString m_feeId;
|
|
TQString m_interestId;
|
|
bool scannedCategories;
|
|
};
|
|
|
|
|
|
const TQString& MyMoneyStatementReader::Private::feeId(const MyMoneyAccount& invAcc)
|
|
{
|
|
scanCategories(m_feeId, invAcc, MyMoneyFile::instance()->expense(), i18n("_Fees"));
|
|
return m_feeId;
|
|
}
|
|
|
|
const TQString& MyMoneyStatementReader::Private::interestId(const MyMoneyAccount& invAcc)
|
|
{
|
|
scanCategories(m_interestId, invAcc, MyMoneyFile::instance()->income(), i18n("_Dividend"));
|
|
return m_interestId;
|
|
}
|
|
|
|
TQString MyMoneyStatementReader::Private::nameToId(const TQString&name, MyMoneyAccount& tqparent)
|
|
{
|
|
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( tqparent.accountType() );
|
|
acc.setCurrencyId(tqparent.currencyId());
|
|
file->addAccount(acc, tqparent);
|
|
}
|
|
return acc.id();
|
|
}
|
|
|
|
TQString MyMoneyStatementReader::Private::interestId(const TQString& name)
|
|
{
|
|
MyMoneyAccount tqparent = MyMoneyFile::instance()->income();
|
|
return nameToId(name, tqparent);
|
|
}
|
|
|
|
TQString MyMoneyStatementReader::Private::feeId(const TQString& name)
|
|
{
|
|
MyMoneyAccount tqparent = MyMoneyFile::instance()->expense();
|
|
return nameToId(name, tqparent);
|
|
}
|
|
|
|
|
|
void MyMoneyStatementReader::Private::scanCategories(TQString& id, const MyMoneyAccount& invAcc, const MyMoneyAccount& parentAccount, const TQString& 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 tqparent = parentAccount;
|
|
acc.setName( defaultName );
|
|
acc.setAccountType( tqparent.accountType() );
|
|
acc.setCurrencyId(tqparent.currencyId());
|
|
file->addAccount(acc, tqparent);
|
|
}
|
|
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
|
|
TQString base(t_in.m_strBankID);
|
|
TQString hash(base);
|
|
int idx = 1;
|
|
for(;;) {
|
|
TQMap<TQString, bool>::const_iterator it;
|
|
it = uniqIds.find(hash);
|
|
if(it == uniqIds.end()) {
|
|
uniqIds[hash] = true;
|
|
break;
|
|
}
|
|
hash = TQString("%1-%2").tqarg(base).tqarg(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, TQStringList& 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").tqarg(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;
|
|
TQValueList<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;
|
|
TQValueList<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 ...");
|
|
TQValueList<MyMoneySecurity> slist = MyMoneyFile::instance()->securityList();
|
|
TQValueList<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;
|
|
TQValueList<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();
|
|
TQValueList<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").tqarg(s.m_dateEnd.toString(Qt::ISODate)).tqarg(s.m_closingBalance.formatMoney("",2));
|
|
}
|
|
messages += i18n(" Transactions");
|
|
messages += i18n(" %1 processed").tqarg(d->transactionsCount);
|
|
messages += i18n(" %1 added").tqarg(d->transactionsAdded);
|
|
messages += i18n(" %1 matched").tqarg(d->transactionsMatched);
|
|
messages += i18n(" %1 duplicates").tqarg(d->transactionsDuplicate);
|
|
messages += i18n(" Payees");
|
|
messages += i18n(" %1 created").tqarg(payeeCount);
|
|
messages += TQString();
|
|
|
|
// remove the Don't ask again entries
|
|
KConfig* config = KGlobal::config();
|
|
config->setGroup(TQString::tqfromLatin1("Notification Messages"));
|
|
TQStringList::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, "TQIF");
|
|
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, "TQIF");
|
|
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;
|
|
TQValueList<MyMoneySecurity> list = file->securityList();
|
|
TQValueList<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").tqarg(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
|
|
TQString dbgMsg;
|
|
dbgMsg = TQString("Process %1, '%3', %2").tqarg(t_in.m_datePosted.toString(Qt::ISODate)).tqarg(t_in.m_amount.formatMoney("", 2)).tqarg(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;
|
|
TQString 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;
|
|
TQStringList accounts = thisaccount.accountList();
|
|
TQStringList::const_iterator it_account = accounts.begin();
|
|
while( !found && it_account != accounts.end() )
|
|
{
|
|
TQString 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
|
|
TQString 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.").tqarg(t_in.m_strSecurity),i18n("Security not found"),TQString("BlankSecurity"));
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
MyMoneySecurity security;
|
|
TQValueList<MyMoneySecurity> list = MyMoneyFile::instance()->securityList();
|
|
TQValueList<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.").tqarg(t_in.m_strSecurity),i18n("Security not found"),TQString("MissingSecurity%1").tqarg(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.
|
|
|
|
TQString 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);
|
|
}
|
|
|
|
|
|
TQString payeename = t_in.m_strPayee;
|
|
if(!payeename.isEmpty())
|
|
{
|
|
TQString payeeid;
|
|
try {
|
|
TQValueList<MyMoneyPayee> pList = file->payeeList();
|
|
TQValueList<MyMoneyPayee>::const_iterator it_p;
|
|
TQMap<int, TQString> matchMap;
|
|
for(it_p = pList.begin(); it_p != pList.end(); ++it_p) {
|
|
bool ignoreCase;
|
|
TQStringList keys;
|
|
TQStringList::const_iterator it_s;
|
|
switch((*it_p).matchData(ignoreCase, keys)) {
|
|
case MyMoneyPayee::matchDisabled:
|
|
break;
|
|
|
|
case MyMoneyPayee::matchName:
|
|
keys << TQString("%1").tqarg(TQRegExp::escape((*it_p).name()));
|
|
// tricky fall through here
|
|
|
|
case MyMoneyPayee::matchKey:
|
|
for(it_s = keys.begin(); it_s != keys.end(); ++it_s) {
|
|
TQRegExp 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) {
|
|
TQMap<int, TQString>::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?
|
|
TQString msg = i18n("Do you want to add \"%1\" as payee/receiver?\n\n").tqarg(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.").tqarg(payeename);
|
|
|
|
TQString askKey = TQString("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, TQStringList());
|
|
if (m_askPayeeCategory) {
|
|
// We use a TQGuardedPtr because the dialog may get deleted
|
|
// during exec() if the tqparent of the dialog gets deleted.
|
|
// In that case the guarded ptr will reset to 0.
|
|
TQGuardedPtr<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")));
|
|
TQVBox *topcontents = new TQVBox (dialog);
|
|
topcontents->setSpacing(KDialog::spacingHint()*2);
|
|
topcontents->setMargin(KDialog::marginHint());
|
|
|
|
//add in caption? and account combo here
|
|
TQLabel *label1 = new TQLabel( topcontents);
|
|
label1->setText(i18n("Please select a default category for payee '%1':").tqarg(payee.name()));
|
|
|
|
TQGuardedPtr<KMyMoneyAccountCombo> accountCombo = new KMyMoneyAccountCombo(topcontents);
|
|
dialog->setMainWidget(topcontents);
|
|
|
|
int result = dialog->exec();
|
|
|
|
TQString 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").tqarg(e->line()));
|
|
delete e;
|
|
|
|
}
|
|
|
|
} else if(rc == KMessageBox::No) {
|
|
s1.setPayeeId(TQString());
|
|
|
|
} 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(TQString());
|
|
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);
|
|
TQValueList<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 )
|
|
{
|
|
TQValueList<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;
|
|
}
|
|
}
|
|
|
|
TQValueList<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(TQString());
|
|
|
|
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
|
|
//******************************************
|
|
|
|
TQValueList<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
|
|
TQString 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(TQString(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, TQString("<qt>%1</qt>").tqarg(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? ").tqarg(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);
|
|
TQString 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) {
|
|
TQString message(i18n("Problem adding or matching imported transaction with id '%1': %2").tqarg(t_in.m_strBankID).tqarg(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();
|
|
|
|
TQString 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.
|
|
|
|
TQString accountNumber = account.number();
|
|
if ( ! accountNumber.isEmpty() )
|
|
{
|
|
// Get a list of all accounts
|
|
TQValueList<MyMoneyAccount> accounts;
|
|
file->accountList(accounts);
|
|
|
|
// Iterate through them
|
|
TQValueList<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;
|
|
}
|
|
}
|
|
|
|
TQString msg = i18n("<b>You have downloaded a statement for the following account:</b><br><br>");
|
|
msg += i18n(" - Account Name: %1").tqarg(account.name()) + "<br>";
|
|
msg += i18n(" - Account Type: %1").tqarg(KMyMoneyUtils::accountTypeToString(account.accountType())) + "<br>";
|
|
msg += i18n(" - Account Number: %1").tqarg(account.number()) + "<br>";
|
|
msg += "<br>";
|
|
|
|
TQString 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();
|
|
TQString accname;
|
|
bool done = false;
|
|
while ( !done )
|
|
{
|
|
if ( accountSelect.exec() == TQDialog::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, TQString("<qt>%1</qt>").tqarg(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 TQString&))
|
|
{
|
|
m_progressCallback = callback;
|
|
}
|
|
|
|
void MyMoneyStatementReader::signalProgress(int current, int total, const TQString& msg)
|
|
{
|
|
if(m_progressCallback != 0)
|
|
(*m_progressCallback)(current, total, msg);
|
|
}
|
|
|
|
|
|
#include "mymoneystatementreader.moc"
|
|
// vim:cin:si:ai:et:ts=2:sw=2:
|