diff --git a/kmymoney/plugins/ofx/import/dialogs/mymoneyofxconnector.cpp b/kmymoney/plugins/ofx/import/dialogs/mymoneyofxconnector.cpp index f7977e694..75a8261db 100644 --- a/kmymoney/plugins/ofx/import/dialogs/mymoneyofxconnector.cpp +++ b/kmymoney/plugins/ofx/import/dialogs/mymoneyofxconnector.cpp @@ -1,773 +1,780 @@ /*************************************************************************** mymoneyofxconnector.cpp ------------------- begin : Sat Nov 13 2004 copyright : (C) 2002 by Ace Jones email : 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 "mymoneyofxconnector.h" // ---------------------------------------------------------------------------- // System Includes #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyaccount.h" #include "mymoneykeyvaluecontainer.h" #include "mymoneyenums.h" using KWallet::Wallet; OfxHeaderVersion::OfxHeaderVersion(KComboBox* combo, const QString& headerVersion) : m_combo(combo) { combo->clear(); combo->addItem("102"); combo->addItem("103"); if (!headerVersion.isEmpty()) { combo->setCurrentItem(headerVersion); } else { combo->setCurrentItem("102"); } } QString OfxHeaderVersion::headerVersion() const { return m_combo->currentText(); } OfxAppVersion::OfxAppVersion(KComboBox* combo, KLineEdit* versionEdit, const QString& appId) : m_combo(combo), m_versionEdit(versionEdit) { // http://ofxblog.wordpress.com/2007/06/06/ofx-appid-and-appver-for-intuit-products/ // http://ofxblog.wordpress.com/2007/06/06/ofx-appid-and-appver-for-microsoft-money/ // Quicken m_appMap[i18n("Quicken Windows 2003")] = "QWIN:1200"; m_appMap[i18n("Quicken Windows 2004")] = "QWIN:1300"; m_appMap[i18n("Quicken Windows 2005")] = "QWIN:1400"; m_appMap[i18n("Quicken Windows 2006")] = "QWIN:1500"; m_appMap[i18n("Quicken Windows 2007")] = "QWIN:1600"; m_appMap[i18n("Quicken Windows 2008")] = "QWIN:1700"; // the following three added as found on // https://microsoftmoneyoffline.wordpress.com/appid-appver/ on 2013-02-28 m_appMap[i18n("Quicken Windows 2010")] = "QWIN:1800"; m_appMap[i18n("Quicken Windows 2011")] = "QWIN:1900"; m_appMap[i18n("Quicken Windows 2012")] = "QWIN:2100"; m_appMap[i18n("Quicken Windows 2013")] = "QWIN:2200"; m_appMap[i18n("Quicken Windows 2014")] = "QWIN:2300"; // following two added as found in previous URL on 2017-10-01 m_appMap[i18n("Quicken Windows 2015")] = "QWIN:2400"; m_appMap[i18n("Quicken Windows 2016")] = "QWIN:2500"; m_appMap[i18n("Quicken Windows (Expert)")] = "QWIN:"; // MS-Money m_appMap[i18n("MS-Money 2003")] = "Money:1100"; m_appMap[i18n("MS-Money 2004")] = "Money:1200"; m_appMap[i18n("MS-Money 2005")] = "Money:1400"; m_appMap[i18n("MS-Money 2006")] = "Money:1500"; m_appMap[i18n("MS-Money 2007")] = "Money:1600"; m_appMap[i18n("MS-Money Plus")] = "Money:1700"; m_appMap[i18n("MS-Money (Expert)")] = "Money:"; // KMyMoney m_appMap["KMyMoney"] = "KMyMoney:1000"; combo->clear(); combo->addItems(m_appMap.keys()); if (versionEdit) versionEdit->hide(); QMap::const_iterator it_a; // check for an exact match for (it_a = m_appMap.constBegin(); it_a != m_appMap.constEnd(); ++it_a) { if (*it_a == appId) break; } // not found, check if we have a manual version of this product QRegExp appExp("(\\w+:)(\\d+)"); if (it_a == m_appMap.constEnd()) { if (appExp.exactMatch(appId)) { for (it_a = m_appMap.constBegin(); it_a != m_appMap.constEnd(); ++it_a) { if (*it_a == appExp.cap(1)) break; } } } // if we still haven't found it, we use a default as last resort if (it_a != m_appMap.constEnd()) { combo->setCurrentItem(it_a.key()); if ((*it_a).endsWith(':')) { if (versionEdit) { versionEdit->show(); versionEdit->setText(appExp.cap(2)); } else { combo->setCurrentItem(i18n("Quicken Windows 2008")); } } } else { combo->setCurrentItem(i18n("Quicken Windows 2008")); } } const QString OfxAppVersion::appId() const { static QString defaultAppId("QWIN:1700"); QString app = m_combo->currentText(); if (m_appMap[app] != defaultAppId) { if (m_appMap[app].endsWith(':')) { if (m_versionEdit) { return m_appMap[app] + m_versionEdit->text(); } else { return QString(); } } return m_appMap[app]; } return QString(); } bool OfxAppVersion::isValid() const { QRegExp exp(".+:\\d+"); QString app = m_combo->currentText(); if (m_appMap[app].endsWith(':')) { if (m_versionEdit) { app = m_appMap[app] + m_versionEdit->text(); } else { app.clear(); } } else { app = m_appMap[app]; } return exp.exactMatch(app); } MyMoneyOfxConnector::MyMoneyOfxConnector(const MyMoneyAccount& _account): m_account(_account) { m_fiSettings = m_account.onlineBankingSettings(); } QString MyMoneyOfxConnector::iban() const { return m_fiSettings.value("bankid"); } QString MyMoneyOfxConnector::fiorg() const { return m_fiSettings.value("org"); } QString MyMoneyOfxConnector::fiid() const { return m_fiSettings.value("fid"); } QString MyMoneyOfxConnector::clientUid() const { return m_fiSettings.value("clientUid"); } QString MyMoneyOfxConnector::username() const { return m_fiSettings.value("username"); } QString MyMoneyOfxConnector::password() const { // if we don't find a password in the wallet, we use the old method // and retrieve it from the settings stored in the KMyMoney data storage. // in case we don't have a password on file, we ask the user QString key = OFX_PASSWORD_KEY(m_fiSettings.value("url"), m_fiSettings.value("uniqueId")); QString pwd = m_fiSettings.value("password"); // now check for the wallet Wallet *wallet = openSynchronousWallet(); if (wallet && !Wallet::keyDoesNotExist(Wallet::NetworkWallet(), Wallet::PasswordFolder(), key)) { wallet->setFolder(Wallet::PasswordFolder()); wallet->readPassword(key, pwd); } if (pwd.isEmpty()) { QPointer dlg = new KPasswordDialog(0); dlg->setPrompt(i18n("Enter your password for account %1", m_account.name())); if (dlg->exec()) pwd = dlg->password(); delete dlg; } return pwd; } QString MyMoneyOfxConnector::accountnum() const { return m_fiSettings.value("accountid"); } QString MyMoneyOfxConnector::url() const { return m_fiSettings.value("url"); } QDate MyMoneyOfxConnector::statementStartDate() const { if ((m_fiSettings.value("kmmofx-todayMinus").toInt() != 0) && !m_fiSettings.value("kmmofx-numRequestDays").isEmpty()) { return QDate::currentDate().addDays(-m_fiSettings.value("kmmofx-numRequestDays").toInt()); + } else if ((m_fiSettings.value("kmmofx-lastUpdate").toInt() != 0) && !m_account.value("lastImportedTransactionDate").isEmpty()) { - return QDate::fromString(m_account.value("lastImportedTransactionDate"), Qt::ISODate); + // get last statement request date from application account object + // and start from a few days before if the date is valid + QDate lastUpdate = QDate::fromString(m_account.value("lastImportedTransactionDate"), Qt::ISODate); + if (lastUpdate.isValid()) { + return lastUpdate.addDays(-3); + } + } else if ((m_fiSettings.value("kmmofx-pickDate").toInt() != 0) && !m_fiSettings.value("kmmofx-specificDate").isEmpty()) { return QDate::fromString(m_fiSettings.value("kmmofx-specificDate")); } return QDate::currentDate().addMonths(-2); } OfxAccountData::AccountType MyMoneyOfxConnector::accounttype() const { OfxAccountData::AccountType result = OfxAccountData::OFX_CHECKING; QString type = m_account.onlineBankingSettings()["type"]; if (type == "CHECKING") result = OfxAccountData::OFX_CHECKING; else if (type == "SAVINGS") result = OfxAccountData::OFX_SAVINGS; else if (type == "MONEY MARKET") result = OfxAccountData::OFX_MONEYMRKT; else if (type == "CREDIT LINE") result = OfxAccountData::OFX_CREDITLINE; else if (type == "CMA") result = OfxAccountData::OFX_CMA; else if (type == "CREDIT CARD") result = OfxAccountData::OFX_CREDITCARD; else if (type == "INVESTMENT") result = OfxAccountData::OFX_INVESTMENT; else { switch (m_account.accountType()) { case eMyMoney::Account::Type::Investment: result = OfxAccountData::OFX_INVESTMENT; break; case eMyMoney::Account::Type::CreditCard: result = OfxAccountData::OFX_CREDITCARD; break; case eMyMoney::Account::Type::Savings: result = OfxAccountData::OFX_SAVINGS; break; default: break; } } // This is a bit of a personalized hack. Sometimes we may want to override the // ofx type for an account. For now, I will stash it in the notes! QRegExp rexp("OFXTYPE:([A-Z]*)"); if (rexp.indexIn(m_account.description()) != -1) { QString override = rexp.cap(1); qDebug() << "MyMoneyOfxConnector::accounttype() overriding to " << result; if (override == "BANK") result = OfxAccountData::OFX_CHECKING; else if (override == "CC") result = OfxAccountData::OFX_CREDITCARD; else if (override == "INV") result = OfxAccountData::OFX_INVESTMENT; else if (override == "MONEYMARKET") result = OfxAccountData::OFX_MONEYMRKT; } return result; } void MyMoneyOfxConnector::initRequest(OfxFiLogin* fi) const { memset(fi, 0, sizeof(OfxFiLogin)); strncpy(fi->fid, fiid().toLatin1(), OFX_FID_LENGTH - 1); strncpy(fi->org, fiorg().toLatin1(), OFX_ORG_LENGTH - 1); strncpy(fi->userid, username().toLatin1(), OFX_USERID_LENGTH - 1); strncpy(fi->userpass, password().toLatin1(), OFX_USERPASS_LENGTH - 1); #ifdef LIBOFX_HAVE_CLIENTUID strncpy(fi->clientuid, clientUid().toLatin1(), OFX_CLIENTUID_LENGTH - 1); #endif // If we don't know better, we pretend to be Quicken 2008 // http://ofxblog.wordpress.com/2007/06/06/ofx-appid-and-appver-for-intuit-products/ // http://ofxblog.wordpress.com/2007/06/06/ofx-appid-and-appver-for-microsoft-money/ QString appId = m_account.onlineBankingSettings().value("appId"); QRegExp exp("(.*):(.*)"); if (exp.indexIn(appId) != -1) { strncpy(fi->appid, exp.cap(1).toLatin1(), OFX_APPID_LENGTH - 1); strncpy(fi->appver, exp.cap(2).toLatin1(), OFX_APPVER_LENGTH - 1); } else { strncpy(fi->appid, "QWIN", OFX_APPID_LENGTH - 1); strncpy(fi->appver, "1700", OFX_APPVER_LENGTH - 1); } QString headerVersion = m_account.onlineBankingSettings().value("kmmofx-headerVersion"); if (!headerVersion.isEmpty()) { strncpy(fi->header_version, headerVersion.toLatin1(), OFX_HEADERVERSION_LENGTH - 1); } } const QByteArray MyMoneyOfxConnector::statementRequest() const { OfxFiLogin fi; initRequest(&fi); OfxAccountData account; memset(&account, 0, sizeof(OfxAccountData)); if (!iban().toLatin1().isEmpty()) { strncpy(account.bank_id, iban().toLatin1(), OFX_BANKID_LENGTH - 1); strncpy(account.broker_id, iban().toLatin1(), OFX_BROKERID_LENGTH - 1); } strncpy(account.account_number, accountnum().toLatin1(), OFX_ACCTID_LENGTH - 1); account.account_type = accounttype(); QByteArray result; if (fi.userpass[0]) { char *szrequest = libofx_request_statement(&fi, &account, QDateTime(statementStartDate()).toTime_t()); QString request = szrequest; // remove the trailing zero result = request.toUtf8(); if(result.at(result.size()-1) == 0) result.truncate(result.size() - 1); free(szrequest); } return result; } #if 0 MyMoneyOfxConnector::Tag MyMoneyOfxConnector::message(const QString& _msgType, const QString& _trnType, const Tag& _request) { return Tag(_msgType + "MSGSRQV1") .subtag(Tag(_trnType + "TRNRQ") .element("TRNUID", uuid()) .element("CLTCOOKIE", "1") .subtag(_request)); } MyMoneyOfxConnector::Tag MyMoneyOfxConnector::investmentRequest() const { QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]")); QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]")); return message("INVSTMT", "INVSTMT", Tag("INVSTMTRQ") .subtag(Tag("INVACCTFROM").element("BROKERID", fiorg()).element("ACCTID", accountnum())) .subtag(Tag("INCTRAN").element("DTSTART", dtstart_string).element("INCLUDE", "Y")) .element("INCOO", "Y") .subtag(Tag("INCPOS").element("DTASOF", dtnow_string).element("INCLUDE", "Y")) .element("INCBAL", "Y")); } MyMoneyOfxConnector::Tag MyMoneyOfxConnector::bankStatementRequest(const QDate& _dtstart) const { QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]")); return message("BANK", "STMT", Tag("STMTRQ") .subtag(Tag("BANKACCTFROM").element("BANKID", iban()).element("ACCTID", accountnum()).element("ACCTTYPE", "CHECKING")) .subtag(Tag("INCTRAN").element("DTSTART", dtstart_string).element("INCLUDE", "Y"))); } MyMoneyOfxConnector::Tag MyMoneyOfxConnector::creditCardRequest(const QDate& _dtstart) const { QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]")); return message("CREDITCARD", "CCSTMT", Tag("CCSTMTRQ") .subtag(Tag("CCACCTFROM").element("ACCTID", accountnum())) .subtag(Tag("INCTRAN").element("DTSTART", dtstart_string).element("INCLUDE", "Y"))); } MyMoneyOfxConnector::Tag MyMoneyOfxConnector::signOn() const { QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]")); Tag fi("FI"); fi.element("ORG", fiorg()); if (!fiid().isEmpty()) fi.element("FID", fiid()); return Tag("SIGNONMSGSRQV1") .subtag(Tag("SONRQ") .element("DTCLIENT", dtnow_string) .element("USERID", username()) .element("USERPASS", password()) .element("LANGUAGE", "ENG") .subtag(fi) .element("APPID", "QWIN") .element("APPVER", "1100")); } QString MyMoneyOfxConnector::header() { return QString("OFXHEADER:100\r\n" "DATA:OFXSGML\r\n" "VERSION:102\r\n" "SECURITY:NONE\r\n" "ENCODING:USASCII\r\n" "CHARSET:1252\r\n" "COMPRESSION:NONE\r\n" "OLDFILEUID:NONE\r\n" "NEWFILEUID:%1\r\n" "\r\n").arg(uuid()); } QString MyMoneyOfxConnector::uuid() { static int id = 1; return QDateTime::currentDateTime().toString("yyyyMMdd-hhmmsszzz-") + QString::number(id++); } // // Methods to provide RESPONSES to OFX requests. This has no real use in // KMyMoney, but it's included for the purposes of unit testing. This way, I // can create a MyMoneyAccount, write it to an OFX file, import that OFX file, // and check that everything made it through the importer. // // It's also a far-off dream to write an OFX server using KMyMoney as a // backend. It really should not be that hard, and it would fill a void in // the open source software community. // const QByteArray MyMoneyOfxConnector::statementResponse(const QDate& _dtstart) const { QString request; if (accounttype() == "CC") request = header() + Tag("OFX").subtag(signOnResponse()).subtag(creditCardStatementResponse(_dtstart)); else if (accounttype() == "INV") request = header() + Tag("OFX").subtag(signOnResponse()).data(investmentStatementResponse(_dtstart)); else request = header() + Tag("OFX").subtag(signOnResponse()).subtag(bankStatementResponse(_dtstart)); // remove the trailing zero QByteArray result = request.utf8(); result.truncate(result.size() - 1); return result; } MyMoneyOfxConnector::Tag MyMoneyOfxConnector::signOnResponse() const { QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]")); Tag sonrs("SONRS"); sonrs .subtag(Tag("STATUS") .element("CODE", "0") .element("SEVERITY", "INFO") .element("MESSAGE", "The operation succeeded.") ) .element("DTSERVER", dtnow_string) .element("LANGUAGE", "ENG"); Tag fi("FI"); if (!fiorg().isEmpty()) fi.element("ORG", fiorg()); if (!fiid().isEmpty()) fi.element("FID", fiid()); if (!fi.isEmpty()) sonrs.subtag(fi); return Tag("SIGNONMSGSRSV1").subtag(sonrs); } MyMoneyOfxConnector::Tag MyMoneyOfxConnector::messageResponse(const QString& _msgType, const QString& _trnType, const Tag& _response) { return Tag(_msgType + "MSGSRSV1") .subtag(Tag(_trnType + "TRNRS") .element("TRNUID", uuid()) .subtag(Tag("STATUS").element("CODE", "0").element("SEVERITY", "INFO")) .element("CLTCOOKIE", "1") .subtag(_response)); } MyMoneyOfxConnector::Tag MyMoneyOfxConnector::bankStatementResponse(const QDate& _dtstart) const { MyMoneyFile* file = MyMoneyFile::instance(); QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]")); QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]")); QString transactionlist; MyMoneyTransactionFilter filter; filter.setDateFilter(_dtstart, QDate::currentDate()); filter.addAccount(m_account.id()); QList transactions = file->transactionList(filter); QList::const_iterator it_transaction = transactions.begin(); while (it_transaction != transactions.end()) { transactionlist += transaction(*it_transaction); ++it_transaction; } return messageResponse("BANK", "STMT", Tag("STMTRS") .element("CURDEF", "USD") .subtag(Tag("BANKACCTFROM").element("BANKID", iban()).element("ACCTID", accountnum()).element("ACCTTYPE", "CHECKING")) .subtag(Tag("BANKTRANLIST").element("DTSTART", dtstart_string).element("DTEND", dtnow_string).data(transactionlist)) .subtag(Tag("LEDGERBAL").element("BALAMT", file->balance(m_account.id()).formatMoney(QString(), 2, false)).element("DTASOF", dtnow_string))); } MyMoneyOfxConnector::Tag MyMoneyOfxConnector::creditCardStatementResponse(const QDate& _dtstart) const { MyMoneyFile* file = MyMoneyFile::instance(); QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]")); QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]")); QString transactionlist; MyMoneyTransactionFilter filter; filter.setDateFilter(_dtstart, QDate::currentDate()); filter.addAccount(m_account.id()); QList transactions = file->transactionList(filter); QList::const_iterator it_transaction = transactions.begin(); while (it_transaction != transactions.end()) { transactionlist += transaction(*it_transaction); ++it_transaction; } return messageResponse("CREDITCARD", "CCSTMT", Tag("CCSTMTRS") .element("CURDEF", "USD") .subtag(Tag("CCACCTFROM").element("ACCTID", accountnum())) .subtag(Tag("BANKTRANLIST").element("DTSTART", dtstart_string).element("DTEND", dtnow_string).data(transactionlist)) .subtag(Tag("LEDGERBAL").element("BALAMT", file->balance(m_account.id()).formatMoney(QString(), 2, false)).element("DTASOF", dtnow_string))); } QString MyMoneyOfxConnector::investmentStatementResponse(const QDate& _dtstart) const { MyMoneyFile* file = MyMoneyFile::instance(); QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]")); QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]")); QString transactionlist; MyMoneyTransactionFilter filter; filter.setDateFilter(_dtstart, QDate::currentDate()); filter.addAccount(m_account.id()); filter.addAccount(m_account.accountList()); QList transactions = file->transactionList(filter); QList::const_iterator it_transaction = transactions.begin(); while (it_transaction != transactions.end()) { transactionlist += investmentTransaction(*it_transaction); ++it_transaction; } Tag securitylist("SECLIST"); QCStringList accountids = m_account.accountList(); QCStringList::const_iterator it_accountid = accountids.begin(); while (it_accountid != accountids.end()) { MyMoneySecurity equity = file->security(file->account(*it_accountid).currencyId()); securitylist.subtag(Tag("STOCKINFO") .subtag(Tag("SECINFO") .subtag(Tag("SECID") .element("UNIQUEID", equity.id()) .element("UNIQUEIDTYPE", "KMYMONEY")) .element("SECNAME", equity.name()) .element("TICKER", equity.tradingSymbol()) .element("FIID", equity.id()))); ++it_accountid; } return messageResponse("INVSTMT", "INVSTMT", Tag("INVSTMTRS") .element("DTASOF", dtstart_string) .element("CURDEF", "USD") .subtag(Tag("INVACCTFROM").element("BROKERID", fiorg()).element("ACCTID", accountnum())) .subtag(Tag("INVTRANLIST").element("DTSTART", dtstart_string).element("DTEND", dtnow_string).data(transactionlist)) ) + Tag("SECLISTMSGSRSV1").subtag(securitylist); } MyMoneyOfxConnector::Tag MyMoneyOfxConnector::transaction(const MyMoneyTransaction& _t) const { // This method creates a transaction tag using ONLY the elements that importer uses MyMoneyFile* file = MyMoneyFile::instance(); //Use this version for bank/cc transactions MyMoneySplit s = _t.splitByAccount(m_account.id(), true); //TODO (Ace) Write "investmentTransaction()"... //Use this version for inv transactions //MyMoneySplit s = _t.splitByAccount( m_account.accountList(), true ); Tag result("STMTTRN"); result // This is a temporary hack. I don't use the trntype field in importing at all, // but libofx requires it to be there in order to import the file. .element("TRNTYPE", "DEBIT") .element("DTPOSTED", _t.postDate().toString(Qt::ISODate).remove(QRegExp("[^0-9]"))) .element("TRNAMT", s.value().formatMoney(QString(), 2, false)); if (! _t.bankID().isEmpty()) result.element("FITID", _t.bankID()); else result.element("FITID", _t.id()); if (! s.number().isEmpty()) result.element("CHECKNUM", s.number()); if (! s.payeeId().isEmpty()) result.element("NAME", file->payee(s.payeeId()).name()); if (! _t.memo().isEmpty()) result.element("MEMO", _t.memo()); return result; } MyMoneyOfxConnector::Tag MyMoneyOfxConnector::investmentTransaction(const MyMoneyTransaction& _t) const { MyMoneyFile* file = MyMoneyFile::instance(); //Use this version for inv transactions MyMoneySplit s = _t.splitByAccount(m_account.accountList(), true); QByteArray stockid = file->account(s.accountId()).currencyId(); Tag invtran("INVTRAN"); invtran.element("FITID", _t.id()).element("DTTRADE", _t.postDate().toString(Qt::ISODate).remove(QRegExp("[^0-9]"))); if (!_t.memo().isEmpty()) invtran.element("MEMO", _t.memo()); if (s.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares)) { if (s.shares().isNegative()) { return Tag("SELLSTOCK") .subtag(Tag("INVSELL") .subtag(invtran) .subtag(Tag("SECID").element("UNIQUEID", stockid).element("UNIQUEIDTYPE", "KMYMONEY")) .element("UNITS", QString(((s.shares())).formatMoney(QString(), 2, false)).remove(QRegExp("[^0-9.\\-]"))) .element("UNITPRICE", QString((s.value() / s.shares()).formatMoney(QString(), 2, false)).remove(QRegExp("[^0-9.]"))) .element("TOTAL", QString((-s.value()).formatMoney(QString(), 2, false)).remove(QRegExp("[^0-9.\\-]"))) .element("SUBACCTSEC", "CASH") .element("SUBACCTFUND", "CASH")) .element("SELLTYPE", "SELL"); } else { return Tag("BUYSTOCK") .subtag(Tag("INVBUY") .subtag(invtran) .subtag(Tag("SECID").element("UNIQUEID", stockid).element("UNIQUEIDTYPE", "KMYMONEY")) .element("UNITS", QString((s.shares()).formatMoney(QString(), 2, false)).remove(QRegExp("[^0-9.\\-]"))) .element("UNITPRICE", QString((s.value() / s.shares()).formatMoney(QString(), 2, false)).remove(QRegExp("[^0-9.]"))) .element("TOTAL", QString((-(s.value())).formatMoney(QString(), 2, false)).remove(QRegExp("[^0-9.\\-]"))) .element("SUBACCTSEC", "CASH") .element("SUBACCTFUND", "CASH")) .element("BUYTYPE", "BUY"); } } else if (s.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::ReinvestDividend)) { // Should the TOTAL tag really be negative for a REINVEST? That's very strange, but // it's what they look like coming from my bank, and I can't find any information to refute it. return Tag("REINVEST") .subtag(invtran) .subtag(Tag("SECID").element("UNIQUEID", stockid).element("UNIQUEIDTYPE", "KMYMONEY")) .element("INCOMETYPE", "DIV") .element("TOTAL", QString((-s.value()).formatMoney(QString(), 2, false)).remove(QRegExp("[^0-9.\\-]"))) .element("SUBACCTSEC", "CASH") .element("UNITS", QString((s.shares()).formatMoney(QString(), 2, false)).remove(QRegExp("[^0-9.\\-]"))) .element("UNITPRICE", QString((s.value() / s.shares()).formatMoney(QString(), 2, false)).remove(QRegExp("[^0-9.]"))); } else if (s.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Dividend)) { // find the split with the category, which has the actual amount of the dividend QList splits = _t.splits(); QList::const_iterator it_split = splits.begin(); bool found = false; while (it_split != splits.end()) { QByteArray accid = (*it_split).accountId(); MyMoneyAccount acc = file->account(accid); if (acc.accountType() == eMyMoney::Account::Type::Income || acc.accountType() == eMyMoney::Account::Type::Expense) { found = true; break; } ++it_split; } if (found) return Tag("INCOME") .subtag(invtran) .subtag(Tag("SECID").element("UNIQUEID", stockid).element("UNIQUEIDTYPE", "KMYMONEY")) .element("INCOMETYPE", "DIV") .element("TOTAL", QString((-(*it_split).value()).formatMoney(QString(), 2, false)).remove(QRegExp("[^0-9\\.\\-]"))) .element("SUBACCTSEC", "CASH") .element("SUBACCTFUND", "CASH"); else return Tag("ERROR").element("DETAILS", "Unable to determine the amount of this income transaction."); } //FIXME: Do something useful with these errors return Tag("ERROR").element("DETAILS", "This transaction contains an unsupported action type"); } #endif KWallet::Wallet *openSynchronousWallet() { using KWallet::Wallet; // first handle the simple case in which we already use the wallet but need the object again in // this case the wallet access permission dialog will no longer appear so we don't need to pass // a valid window id or do anything special since the function call should return immediately const bool alreadyUsingTheWallet = Wallet::users(Wallet::NetworkWallet()).contains("KMyMoney"); if (alreadyUsingTheWallet) { return Wallet::openWallet(Wallet::NetworkWallet(), 0, Wallet::Synchronous); } // search for a suitable parent for the wallet that needs to be deactivated while the // wallet access permission dialog is not dismissed with either accept or reject KWallet::Wallet *wallet = 0; QWidget *parentWidgetForWallet = 0; if (qApp->activeModalWidget()) { parentWidgetForWallet = qApp->activeModalWidget(); } else if (qApp->activeWindow()) { parentWidgetForWallet = qApp->activeWindow(); } else { QList mainWindowList = KMainWindow::memberList(); if (!mainWindowList.isEmpty()) parentWidgetForWallet = mainWindowList.front(); } // only open the wallet synchronously if we have a valid parent otherwise crashes could occur if (parentWidgetForWallet) { // while the wallet is being opened disable the widget to prevent input processing const bool enabled = parentWidgetForWallet->isEnabled(); parentWidgetForWallet->setEnabled(false); wallet = Wallet::openWallet(Wallet::NetworkWallet(), parentWidgetForWallet->winId(), Wallet::Synchronous); parentWidgetForWallet->setEnabled(enabled); } return wallet; }