diff --git a/kmymoney/plugins/kbanking/banking.cpp b/kmymoney/plugins/kbanking/banking.cpp index 0c100bdb5..3e8588218 100644 --- a/kmymoney/plugins/kbanking/banking.cpp +++ b/kmymoney/plugins/kbanking/banking.cpp @@ -1,304 +1,301 @@ /* * Copyright 2018 Martin Preuss * Copyright 2004-2019 Thomas Baumgart * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include "banking.hpp" #include #include #include #include AB_Banking::AB_Banking(const char *appname, const char *fname) { assert(appname); _banking = AB_Banking_new(appname, fname, 0); } void AB_Banking::registerFinTs(const char* regKey, const char* version) const { #ifdef ENABLE_FINTS_REGISTRATION AB_Banking_RuntimeConfig_SetCharValue(_banking, "fintsRegistrationKey", regKey); AB_Banking_RuntimeConfig_SetCharValue(_banking, "fintsApplicationVersionString", version); #endif } AB_Banking::~AB_Banking() { DBG_NOTICE(AQBANKING_LOGDOMAIN, "~AB_Banking: Freeing AB_Banking"); AB_Banking_free(_banking); } int AB_Banking::init() { return AB_Banking_Init(_banking); } int AB_Banking::fini() { return AB_Banking_Fini(_banking); } const char *AB_Banking::getAppName() { return AB_Banking_GetAppName(_banking); } AB_ACCOUNT_SPEC *AB_Banking::getAccount(uint32_t uniqueId) { int rv; AB_ACCOUNT_SPEC *as=NULL; rv=AB_Banking_GetAccountSpecByUniqueId(_banking, uniqueId, &as); if (rv<0) { DBG_ERROR(0, "Account spec not found (%d)", rv); return NULL; } return as; } std::list AB_Banking::getAccounts() { std::list accountSpecList; AB_ACCOUNT_SPEC_LIST *abAccountSpecList=NULL; int rv; rv=AB_Banking_GetAccountSpecList(_banking, &abAccountSpecList); if (rv>=0) { AB_ACCOUNT_SPEC *as; while( (as=AB_AccountSpec_List_First(abAccountSpecList)) ) { AB_AccountSpec_List_Del(as); accountSpecList.push_back(as); as=AB_AccountSpec_List_Next(as); } } AB_AccountSpec_List_free(abAccountSpecList); return accountSpecList; } int AB_Banking::getUserDataDir(GWEN_BUFFER *buf) const { return AB_Banking_GetUserDataDir(_banking, buf); } AB_BANKING *AB_Banking::getCInterface() { return _banking; } bool AB_Banking::importContext(AB_IMEXPORTER_CONTEXT *ctx, uint32_t flags) { AB_IMEXPORTER_ACCOUNTINFO *ai; ai = AB_ImExporterContext_GetFirstAccountInfo(ctx); while (ai) { - if (!importAccountInfo(ai, flags)) + if (!importAccountInfo(ctx, ai, flags)) return false; ai = AB_ImExporterAccountInfo_List_Next(ai); } return true; } -bool AB_Banking::importAccountInfo(AB_IMEXPORTER_ACCOUNTINFO*, uint32_t) +bool AB_Banking::importAccountInfo(AB_IMEXPORTER_CONTEXT* ctx, + AB_IMEXPORTER_ACCOUNTINFO*, + uint32_t) { return false; } int AB_Banking::executeJobs(AB_TRANSACTION_LIST2 *jl, AB_IMEXPORTER_CONTEXT *ctx) { return AB_Banking_SendCommands(_banking, jl, ctx); } std::list AB_Banking::getActiveProviders() { std::list stringList; GWEN_PLUGIN_DESCRIPTION_LIST2 *pdl; pdl=AB_Banking_GetProviderDescrs(_banking); if (pdl) { GWEN_PLUGIN_DESCRIPTION_LIST2_ITERATOR *it; it=GWEN_PluginDescription_List2_First(pdl); if (it) { GWEN_PLUGIN_DESCRIPTION *pd; pd=GWEN_PluginDescription_List2Iterator_Data(it); while(pd) { const char *s; s=GWEN_PluginDescription_GetName(pd); if (s && *s) stringList.push_back(s); pd=GWEN_PluginDescription_List2Iterator_Next(it); } GWEN_PluginDescription_List2Iterator_free(it); } GWEN_PluginDescription_List2_freeAll(pdl); } return stringList; } int AB_Banking::loadSharedConfig(const char *name, GWEN_DB_NODE **pDb) { return AB_Banking_LoadSharedConfig(_banking, name, pDb); } int AB_Banking::saveSharedConfig(const char *name, GWEN_DB_NODE *db) { return AB_Banking_SaveSharedConfig(_banking, name, db); } int AB_Banking::lockSharedConfig(const char *name) { return AB_Banking_LockSharedConfig(_banking, name); } int AB_Banking::unlockSharedConfig(const char *name) { return AB_Banking_UnlockSharedConfig(_banking, name); } int AB_Banking::loadSharedSubConfig(const char *name, const char *subGroup, GWEN_DB_NODE **pDb) { GWEN_DB_NODE *dbShared = NULL; int rv; rv = loadSharedConfig(name, &dbShared); if (rv < 0) { DBG_ERROR(0, "Unable to load config (%d)", rv); GWEN_DB_Group_free(dbShared); return rv; } else { GWEN_DB_NODE *dbSrc; dbSrc = GWEN_DB_GetGroup(dbShared, GWEN_PATH_FLAGS_NAMEMUSTEXIST, subGroup); if (dbSrc) { *pDb = GWEN_DB_Group_dup(dbSrc); } else { *pDb = GWEN_DB_Group_new("config"); } GWEN_DB_Group_free(dbShared); return 0; } } int AB_Banking::saveSharedSubConfig(const char *name, const char *subGroup, GWEN_DB_NODE *dbSrc) { GWEN_DB_NODE *dbShared = NULL; int rv; rv = lockSharedConfig(name); if (rv < 0) { DBG_ERROR(0, "Unable to lock config"); return rv; } else { rv = loadSharedConfig(name, &dbShared); if (rv < 0) { DBG_ERROR(0, "Unable to load config (%d)", rv); unlockSharedConfig(name); return rv; } else { GWEN_DB_NODE *dbDst; dbDst = GWEN_DB_GetGroup(dbShared, GWEN_DB_FLAGS_OVERWRITE_GROUPS, subGroup); assert(dbDst); if (dbSrc) GWEN_DB_AddGroupChildren(dbDst, dbSrc); rv = saveSharedConfig(name, dbShared); if (rv < 0) { DBG_ERROR(0, "Unable to store config (%d)", rv); unlockSharedConfig(name); GWEN_DB_Group_free(dbShared); return rv; } GWEN_DB_Group_free(dbShared); } rv = unlockSharedConfig(name); if (rv < 0) { DBG_ERROR(0, "Unable to unlock config (%d)", rv); return rv; } } return 0; } void AB_Banking::setAccountAlias(AB_ACCOUNT_SPEC *a, const char *alias) { AB_Banking_SetAccountSpecAlias(_banking, a, alias); } - - - - - diff --git a/kmymoney/plugins/kbanking/banking.hpp b/kmymoney/plugins/kbanking/banking.hpp index d9987a048..a956b8e79 100644 --- a/kmymoney/plugins/kbanking/banking.hpp +++ b/kmymoney/plugins/kbanking/banking.hpp @@ -1,152 +1,152 @@ /* * Copyright 2018 Martin Preuss * Copyright 2004-2019 Thomas Baumgart * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** @file * @short A C++ wrapper of the main aqbanking interface */ // krazy:excludeall=license #ifndef AQ_BANKING_CPP_H #define AQ_BANKING_CPP_H #include #include #include #include /** * @brief A C++ binding for the C module @ref AB_BANKING * * This class simply is a C++ binding for the C module @ref AB_BANKING. * It redirects C callbacks used by AB_BANKING to virtual functions in * this class. It als transforms some return values inconveniant for * C++ into STL objects (such as "list"). * * @ingroup G_AB_CPP_INTERFACE * * @author Martin Preuss */ class AB_Banking { private: AB_BANKING *_banking; public: AB_Banking(const char *appname, const char *fname); virtual ~AB_Banking(); AB_BANKING *getCInterface(); /** * See @ref AB_Banking_Init */ virtual int init(); /** * See @ref AB_Banking_Fini */ virtual int fini(); /** * Returns the application name as given to @ref AB_Banking_new. */ const char *getAppName(); /** * Returns a list of pointers to currently known accounts. * Please note that the pointers in this list are still owned by * AqBanking, so you MUST NOT free them. * However, destroying the list will not free the accounts, so it is * safe to do that. */ std::list getAccounts(); /** * This function does an account lookup based on the given unique id. * This id is assigned by AqBanking when an account is created. * The pointer returned is still owned by AqBanking, so you MUST NOT free * it. */ AB_ACCOUNT_SPEC *getAccount(uint32_t uniqueId); std::list getActiveProviders(); int getUserDataDir(GWEN_BUFFER *buf) const ; int loadSharedConfig(const char *name, GWEN_DB_NODE **pDb); int saveSharedConfig(const char *name, GWEN_DB_NODE *db); int lockSharedConfig(const char *name); int unlockSharedConfig(const char *name); int loadSharedSubConfig(const char *name, const char *subGroup, GWEN_DB_NODE **pDb); int saveSharedSubConfig(const char *name, const char *subGroup, GWEN_DB_NODE *dbSrc); void setAccountAlias(AB_ACCOUNT_SPEC *a, const char *alias); /** * Provide interface to setup ZKA FinTS registration */ void registerFinTs(const char* regKey, const char* version) const; /** @name Enqueueing, Dequeueing and Executing Jobs * * Enqueued jobs are preserved across shutdowns. As soon as a job has been * sent to the appropriate backend it will be removed from the queue. * Only those jobs are saved/reloaded which have been enqueued but never * presented to the backend. This means after calling * @ref AB_Banking_ExecuteQueue only those jobs are still in the queue which * have not been processed (e.g. because they belonged to a second backend * but the user aborted while the jobs for a first backend were in process). */ /*@{*/ /** * This function sends all jobs in the list to their corresponding backends * and allows that backend to process it. */ virtual int executeJobs(AB_TRANSACTION_LIST2 *jl, AB_IMEXPORTER_CONTEXT *ctx); /*@}*/ /** * Let the application import a given statement context. */ virtual bool importContext(AB_IMEXPORTER_CONTEXT *ctx, uint32_t flags); - virtual bool importAccountInfo(AB_IMEXPORTER_ACCOUNTINFO *ai, uint32_t flags); + virtual bool importAccountInfo(AB_IMEXPORTER_CONTEXT *ctx, AB_IMEXPORTER_ACCOUNTINFO *ai, uint32_t flags); }; #endif /* AQ_BANKING_CPP_H */ diff --git a/kmymoney/plugins/kbanking/kbanking.cpp b/kmymoney/plugins/kbanking/kbanking.cpp index 0bf7b8290..865c58547 100644 --- a/kmymoney/plugins/kbanking/kbanking.cpp +++ b/kmymoney/plugins/kbanking/kbanking.cpp @@ -1,1532 +1,1526 @@ /* * Copyright 2004 Martin Preuss aquamaniac@users.sourceforge.net * Copyright 2009 Cristian Onet onet.cristian@gmail.com * Copyright 2010-2019 Thomas Baumgart tbaumgart@kde.org * Copyright 2015 Christian David christian-david@web.de * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "kbanking.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include //! @todo remove @c #include #ifdef IS_APPIMAGE #include #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Library Includes #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoney/onlinejob.h" #include "kbaccountsettings.h" #include "kbmapaccount.h" #include "mymoneyfile.h" #include "onlinejobadministration.h" #include "kmymoneyview.h" #include "kbpickstartdate.h" #include "mymoneyinstitution.h" +#include "mymoneytransactionfilter.h" #include "mymoneyexception.h" +#include "mymoneysecurity.h" #include "gwenkdegui.h" #include "gwenhywfarqtoperators.h" #include "aqbankingkmmoperators.h" #include "mymoneystatement.h" #include "statementinterface.h" #include "viewinterface.h" #ifdef KMM_DEBUG #include "chiptandialog.h" #include "phototandialog.h" #include "phototan-demo.cpp" #endif class KBanking::Private { public: Private() : passwordCacheTimer(nullptr), jobList(), fileId() { QString gwenProxy = QString::fromLocal8Bit(qgetenv("GWEN_PROXY")); if (gwenProxy.isEmpty()) { std::unique_ptr cfg = std::unique_ptr(new KConfig("kioslaverc")); QRegExp exp("(\\w+://)?([^/]{2}.+:\\d+)"); QString proxy; KConfigGroup grp = cfg->group("Proxy Settings"); int type = grp.readEntry("ProxyType", 0); switch (type) { case 0: // no proxy break; case 1: // manual specified proxy = grp.readEntry("httpsProxy"); qDebug("KDE https proxy setting is '%s'", qPrintable(proxy)); if (exp.exactMatch(proxy)) { proxy = exp.cap(2); qDebug("Setting GWEN_PROXY to '%s'", qPrintable(proxy)); if (!qputenv("GWEN_PROXY", qPrintable(proxy))) { qDebug("Unable to setup GWEN_PROXY"); } } break; default: // other currently not supported qDebug("KDE proxy setting of type %d not supported", type); break; } } } QString libVersion(void (*version)(int*, int*, int*, int*)) { int major, minor, patch, build; version(&major, &minor, &patch, &build); return QString("%1.%2.%3.%4").arg(major).arg(minor).arg(patch).arg(build); } /** * KMyMoney asks for accounts over and over again which causes a lot of "Job not supported with this account" error messages. * This function filters messages with that string. */ static int gwenLogHook(GWEN_GUI* gui, const char* domain, GWEN_LOGGER_LEVEL level, const char* message) { Q_UNUSED(gui); Q_UNUSED(domain); Q_UNUSED(level); const char* messageToFilter = "Job not supported with this account"; if (strstr(message, messageToFilter) != 0) return 1; return 0; } QTimer *passwordCacheTimer; QMap jobList; QString fileId; }; KBanking::KBanking(QObject *parent, const QVariantList &args) : OnlinePluginExtended(parent, "kbanking") , d(new Private) , m_configAction(nullptr) , m_importAction(nullptr) , m_kbanking(nullptr) , m_accountSettings(nullptr) , m_statementCount(0) { Q_UNUSED(args) QString compileVersionSet = QLatin1String(GWENHYWFAR_VERSION_FULL_STRING "/" AQBANKING_VERSION_FULL_STRING); QString runtimeVersionSet = QString("%1/%2").arg(d->libVersion(&GWEN_Version), d->libVersion(&AB_Banking_GetVersion)); qDebug() << QString("Plugins: kbanking loaded, build with (%1), run with (%2)").arg(compileVersionSet, runtimeVersionSet); } KBanking::~KBanking() { delete d; qDebug("Plugins: kbanking unloaded"); } void KBanking::plug() { m_kbanking = new KBankingExt(this, "KMyMoney"); d->passwordCacheTimer = new QTimer(this); d->passwordCacheTimer->setSingleShot(true); d->passwordCacheTimer->setInterval(60000); connect(d->passwordCacheTimer, &QTimer::timeout, this, &KBanking::slotClearPasswordCache); if (m_kbanking) { //! @todo when is gwenKdeGui deleted? gwenKdeGui *gui = new gwenKdeGui(); GWEN_Gui_SetGui(gui->getCInterface()); GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Warning); if (m_kbanking->init() == 0) { // Tell the host application to load my GUI component const auto componentName = QLatin1String("kbanking"); const auto rcFileName = QLatin1String("kbanking.rc"); setComponentName(componentName, "KBanking"); #ifdef IS_APPIMAGE const QString rcFilePath = QString("%1/../share/kxmlgui5/%2/%3").arg(QCoreApplication::applicationDirPath(), componentName, rcFileName); setXMLFile(rcFilePath); const QString localRcFilePath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).first() + QLatin1Char('/') + componentName + QLatin1Char('/') + rcFileName; setLocalXMLFile(localRcFilePath); #else setXMLFile(rcFileName); #endif // get certificate handling and dialog settings management AB_Gui_Extend(gui->getCInterface(), m_kbanking->getCInterface()); // create actions createActions(); // load protocol conversion list loadProtocolConversion(); GWEN_Logger_SetLevel(AQBANKING_LOGDOMAIN, GWEN_LoggerLevel_Warning); GWEN_Gui_SetLogHookFn(GWEN_Gui_GetGui(), &KBanking::Private::gwenLogHook); } else { qWarning("Could not initialize KBanking online banking interface"); delete m_kbanking; m_kbanking = 0; } } } void KBanking::unplug() { d->passwordCacheTimer->deleteLater(); if (m_kbanking) { m_kbanking->fini(); delete m_kbanking; qDebug("Plugins: kbanking unplugged"); } } void KBanking::loadProtocolConversion() { if (m_kbanking) { m_protocolConversionMap = { {"aqhbci", "HBCI"}, {"aqofxconnect", "OFX"}, {"aqyellownet", "YellowNet"}, {"aqgeldkarte", "Geldkarte"}, {"aqdtaus", "DTAUS"} }; } } void KBanking::protocols(QStringList& protocolList) const { if (m_kbanking) { std::list list = m_kbanking->getActiveProviders(); std::list::iterator it; for (it = list.begin(); it != list.end(); ++it) { // skip the dummy if (*it == "aqnone") continue; QMap::const_iterator it_m; it_m = m_protocolConversionMap.find((*it).c_str()); if (it_m != m_protocolConversionMap.end()) protocolList << (*it_m); else protocolList << (*it).c_str(); } } } QWidget* KBanking::accountConfigTab(const MyMoneyAccount& acc, QString& name) { const MyMoneyKeyValueContainer& kvp = acc.onlineBankingSettings(); name = i18n("Online settings"); if (m_kbanking) { m_accountSettings = new KBAccountSettings(acc, 0); m_accountSettings->loadUi(kvp); return m_accountSettings; } QLabel* label = new QLabel(i18n("KBanking module not correctly initialized"), 0); label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); return label; } MyMoneyKeyValueContainer KBanking::onlineBankingSettings(const MyMoneyKeyValueContainer& current) { MyMoneyKeyValueContainer kvp(current); kvp["provider"] = objectName().toLower(); if (m_accountSettings) { m_accountSettings->loadKvp(kvp); } return kvp; } void KBanking::createActions() { QAction *settings_aqbanking = actionCollection()->addAction("settings_aqbanking"); settings_aqbanking->setText(i18n("Configure Aq&Banking...")); connect(settings_aqbanking, &QAction::triggered, this, &KBanking::slotSettings); QAction *file_import_aqbanking = actionCollection()->addAction("file_import_aqbanking"); file_import_aqbanking->setText(i18n("AqBanking importer...")); connect(file_import_aqbanking, &QAction::triggered, this, &KBanking::slotImport); Q_CHECK_PTR(viewInterface()); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action("file_import_aqbanking"), &QAction::setEnabled); #ifdef KMM_DEBUG QAction *openChipTanDialog = actionCollection()->addAction("open_chiptan_dialog"); openChipTanDialog->setText("Open ChipTan Dialog"); connect(openChipTanDialog, &QAction::triggered, [&](){ auto dlg = new chipTanDialog(); dlg->setHhdCode("0F04871100030333555414312C32331D"); dlg->setInfoText("

Test Graphic for debugging

The encoded data is

Account Number: 335554
Amount: 1,23

"); connect(dlg, &QDialog::accepted, dlg, &chipTanDialog::deleteLater); connect(dlg, &QDialog::rejected, dlg, &chipTanDialog::deleteLater); dlg->show(); }); QAction *openPhotoTanDialog = actionCollection()->addAction("open_phototan_dialog"); openPhotoTanDialog->setText("Open PhotoTan Dialog"); connect(openPhotoTanDialog, &QAction::triggered, [&](){ auto dlg = new photoTanDialog(); QImage img; img.loadFromData(photoTan, sizeof(photoTan), "PNG"); img = img.scaled(300, 300, Qt::KeepAspectRatio); dlg->setPicture(QPixmap::fromImage(img)); dlg->setInfoText("

Test Graphic for debugging

The encoded data is

unknown

"); connect(dlg, &QDialog::accepted, dlg, &chipTanDialog::deleteLater); connect(dlg, &QDialog::rejected, dlg, &chipTanDialog::deleteLater); dlg->show(); }); #endif } void KBanking::slotSettings() { if (m_kbanking) { GWEN_DIALOG* dlg = AB_Banking_CreateSetupDialog(m_kbanking->getCInterface()); if (dlg == NULL) { DBG_ERROR(0, "Could not create setup dialog."); return; } if (GWEN_Gui_ExecDialog(dlg, 0) == 0) { DBG_ERROR(0, "Aborted by user"); GWEN_Dialog_free(dlg); return; } GWEN_Dialog_free(dlg); } } bool KBanking::mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& settings) { bool rc = false; if (m_kbanking && !acc.id().isEmpty()) { m_kbanking->askMapAccount(acc); // at this point, the account should be mapped // so we search it and setup the account reference in the KMyMoney object AB_ACCOUNT_SPEC* ab_acc; ab_acc = aqbAccount(acc); if (ab_acc) { MyMoneyAccount a(acc); setupAccountReference(a, ab_acc); settings = a.onlineBankingSettings(); rc = true; } } return rc; } AB_ACCOUNT_SPEC* KBanking::aqbAccount(const MyMoneyAccount& acc) const { if (m_kbanking == 0) { return 0; } // certainly looking for an expense or income account does not make sense at this point // so we better get out right away if (acc.isIncomeExpense()) { return 0; } AB_ACCOUNT_SPEC *ab_acc = AB_Banking_GetAccountSpecByAlias(m_kbanking->getCInterface(), m_kbanking->mappingId(acc).toUtf8().data()); // if the account is not found, we temporarily scan for the 'old' mapping (the one w/o the file id) // in case we find it, we setup the new mapping in addition on the fly. if (!ab_acc && acc.isAssetLiability()) { ab_acc = AB_Banking_GetAccountSpecByAlias(m_kbanking->getCInterface(), acc.id().toUtf8().data()); if (ab_acc) { qDebug("Found old mapping for '%s' but not new. Setup new mapping", qPrintable(acc.name())); m_kbanking->setAccountAlias(ab_acc, m_kbanking->mappingId(acc).toUtf8().constData()); // TODO at some point in time, we should remove the old mapping } } return ab_acc; } AB_ACCOUNT_SPEC* KBanking::aqbAccount(const QString& accountId) const { MyMoneyAccount account = MyMoneyFile::instance()->account(accountId); return aqbAccount(account); } QString KBanking::stripLeadingZeroes(const QString& s) const { QString rc(s); QRegExp exp("^(0*)([^0].*)"); if (exp.exactMatch(s)) { rc = exp.cap(2); } return rc; } void KBanking::setupAccountReference(const MyMoneyAccount& acc, AB_ACCOUNT_SPEC* ab_acc) { MyMoneyKeyValueContainer kvp; if (ab_acc) { QString accountNumber = stripLeadingZeroes(AB_AccountSpec_GetAccountNumber(ab_acc)); QString routingNumber = stripLeadingZeroes(AB_AccountSpec_GetBankCode(ab_acc)); - QString val = QString("%1-%2").arg(routingNumber, accountNumber); + QString val = QString("%1-%2-%3").arg(routingNumber, accountNumber).arg(AB_AccountSpec_GetType(ab_acc)); + if (val != acc.onlineBankingSettings().value("kbanking-acc-ref")) { kvp.clear(); // make sure to keep our own previous settings const QMap& vals = acc.onlineBankingSettings().pairs(); QMap::const_iterator it_p; for (it_p = vals.begin(); it_p != vals.end(); ++it_p) { if (QString(it_p.key()).startsWith("kbanking-")) { kvp.setValue(it_p.key(), *it_p); } } kvp.setValue("kbanking-acc-ref", val); kvp.setValue("provider", objectName().toLower()); setAccountOnlineParameters(acc, kvp); } } else { // clear the connection setAccountOnlineParameters(acc, kvp); } } bool KBanking::accountIsMapped(const MyMoneyAccount& acc) { return aqbAccount(acc) != 0; } bool KBanking::updateAccount(const MyMoneyAccount& acc) { return updateAccount(acc, false); } bool KBanking::updateAccount(const MyMoneyAccount& acc, bool moreAccounts) { if (!m_kbanking) return false; bool rc = false; if (!acc.id().isEmpty()) { AB_TRANSACTION *job = 0; int rv; /* get AqBanking account */ AB_ACCOUNT_SPEC *ba = aqbAccount(acc); // Update the connection between the KMyMoney account and the AqBanking equivalent. // If the account is not found anymore ba == 0 and the connection is removed. setupAccountReference(acc, ba); if (!ba) { KMessageBox::error(0, i18n("" "The given application account %1 " "has not been mapped to an online " "account." "", acc.name()), i18n("Account Not Mapped")); } else { bool enqueJob = true; if (acc.onlineBankingSettings().value("kbanking-txn-download") != "no") { /* create getTransactions job */ if (AB_AccountSpec_GetTransactionLimitsForCommand(ba, AB_Transaction_CommandGetTransactions)) { /* there are transaction limits for this job, so it is allowed */ job = AB_Transaction_new(); AB_Transaction_SetUniqueAccountId(job, AB_AccountSpec_GetUniqueId(ba)); AB_Transaction_SetCommand(job, AB_Transaction_CommandGetTransactions); } if (job) { int days = 0 /* TODO in AqBanking AB_JobGetTransactions_GetMaxStoreDays(job)*/; QDate qd; if (days > 0) { GWEN_DATE *dt; dt=GWEN_Date_CurrentDate(); GWEN_Date_SubDays(dt, days); qd = QDate(GWEN_Date_GetYear(dt), GWEN_Date_GetMonth(dt), GWEN_Date_GetDay(dt)); GWEN_Date_free(dt); } // 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(acc.value("lastImportedTransactionDate"), Qt::ISODate); if (lastUpdate.isValid()) lastUpdate = lastUpdate.addDays(-3); int dateOption = acc.onlineBankingSettings().value("kbanking-statementDate").toInt(); switch (dateOption) { case 0: // Ask user break; case 1: // No date qd = QDate(); break; case 2: // Last download qd = lastUpdate; break; case 3: // First possible // qd is already setup break; } // the pick start date option dialog is needed in // case the dateOption is 0 or the date option is > 1 // and the qd is invalid if (dateOption == 0 || (dateOption > 1 && !qd.isValid())) { QPointer psd = new KBPickStartDate(m_kbanking, qd, lastUpdate, acc.name(), lastUpdate.isValid() ? 2 : 3, 0, true); if (psd->exec() == QDialog::Accepted) { qd = psd->date(); } else { enqueJob = false; } delete psd; } if (enqueJob) { if (qd.isValid()) { GWEN_DATE *dt; dt=GWEN_Date_fromGregorian(qd.year(), qd.month(), qd.day()); AB_Transaction_SetFirstDate(job, dt); GWEN_Date_free(dt); } rv = m_kbanking->enqueueJob(job); if (rv) { DBG_ERROR(0, "Error %d", rv); KMessageBox::error(0, i18n("" "Could not enqueue the job.\n" ""), i18n("Error")); } } AB_Transaction_free(job); } } if (enqueJob) { /* create getBalance job */ if (AB_AccountSpec_GetTransactionLimitsForCommand(ba, AB_Transaction_CommandGetBalance)) { /* there are transaction limits for this job, so it is allowed */ job = AB_Transaction_new(); AB_Transaction_SetUniqueAccountId(job, AB_AccountSpec_GetUniqueId(ba)); AB_Transaction_SetCommand(job, AB_Transaction_CommandGetBalance); rv = m_kbanking->enqueueJob(job); AB_Transaction_free(job); if (rv) { DBG_ERROR(0, "Error %d", rv); KMessageBox::error(0, i18n("" "Could not enqueue the job.\n" ""), i18n("Error")); } else { rc = true; emit queueChanged(); } } } } } // make sure we have at least one job in the queue before sending it if (!moreAccounts && m_kbanking->getEnqueuedJobs().size() > 0) executeQueue(); return rc; } void KBanking::executeQueue() { if (m_kbanking && m_kbanking->getEnqueuedJobs().size() > 0) { AB_IMEXPORTER_CONTEXT *ctx; ctx = AB_ImExporterContext_new(); int rv = m_kbanking->executeQueue(ctx); if (!rv) { m_kbanking->importContext(ctx, 0); } else { DBG_ERROR(0, "Error: %d", rv); } AB_ImExporterContext_free(ctx); } } /** @todo improve error handling, e.g. by adding a .isValid to nationalTransfer * @todo use new onlineJob system */ void KBanking::sendOnlineJob(QList& jobs) { Q_CHECK_PTR(m_kbanking); m_onlineJobQueue.clear(); QList unhandledJobs; if (!jobs.isEmpty()) { foreach (onlineJob job, jobs) { if (sepaOnlineTransfer::name() == job.task()->taskName()) { onlineJobTyped typedJob(job); enqueTransaction(typedJob); job = typedJob; } else { job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Error, "KBanking", "Cannot handle this request")); unhandledJobs.append(job); } m_onlineJobQueue.insert(m_kbanking->mappingId(job), job); } executeQueue(); } jobs = m_onlineJobQueue.values() + unhandledJobs; m_onlineJobQueue.clear(); } QStringList KBanking::availableJobs(QString accountId) { try { MyMoneyAccount acc = MyMoneyFile::instance()->account(accountId); QString id = MyMoneyFile::instance()->value("kmm-id"); if(id != d->fileId) { d->jobList.clear(); d->fileId = id; } } catch (const MyMoneyException &) { // Exception usually means account was not found return QStringList(); } if(d->jobList.contains(accountId)) { return d->jobList[accountId]; } QStringList list; AB_ACCOUNT_SPEC* abAccount = aqbAccount(accountId); if (!abAccount) { return list; } // Check availableJobs // sepa transfer if (AB_AccountSpec_GetTransactionLimitsForCommand(abAccount, AB_Transaction_CommandSepaTransfer)) { list.append(sepaOnlineTransfer::name()); } d->jobList[accountId] = list; return list; } /** @brief experimenting with QScopedPointer and aqBanking pointers */ class QScopedPointerAbJobDeleter { public: static void cleanup(AB_TRANSACTION* job) { AB_Transaction_free(job); } }; /** @brief experimenting with QScopedPointer and aqBanking pointers */ class QScopedPointerAbAccountDeleter { public: static void cleanup(AB_ACCOUNT_SPEC* account) { AB_AccountSpec_free(account); } }; IonlineTaskSettings::ptr KBanking::settings(QString accountId, QString taskName) { AB_ACCOUNT_SPEC* abAcc = aqbAccount(accountId); if (abAcc == 0) return IonlineTaskSettings::ptr(); if (sepaOnlineTransfer::name() == taskName) { // Get limits for sepaonlinetransfer const AB_TRANSACTION_LIMITS *limits=AB_AccountSpec_GetTransactionLimitsForCommand(abAcc, AB_Transaction_CommandSepaTransfer); if (limits==NULL) return IonlineTaskSettings::ptr(); return AB_TransactionLimits_toSepaOnlineTaskSettings(limits).dynamicCast(); } return IonlineTaskSettings::ptr(); } bool KBanking::enqueTransaction(onlineJobTyped& job) { /* get AqBanking account */ const QString accId = job.constTask()->responsibleAccount(); AB_ACCOUNT_SPEC *abAccount = aqbAccount(accId); if (!abAccount) { job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Warning, "KBanking", i18n("" "The given application account %1 " "has not been mapped to an online " "account." "", MyMoneyFile::instance()->account(accId).name()))); return false; } //setupAccountReference(acc, ba); // needed? if (AB_AccountSpec_GetTransactionLimitsForCommand(abAccount, AB_Transaction_CommandSepaTransfer)==NULL) { qDebug("AB_ERROR_OFFSET is %i", AB_ERROR_OFFSET); job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Error, "AqBanking", QString("Sepa credit transfers for account \"%1\" are not available.").arg(MyMoneyFile::instance()->account(accId).name()) ) ); return false; } AB_TRANSACTION *abJob = AB_Transaction_new(); /* command */ AB_Transaction_SetCommand(abJob, AB_Transaction_CommandSepaTransfer); // Origin Account AB_Transaction_SetUniqueAccountId(abJob, AB_AccountSpec_GetUniqueId(abAccount)); // Recipient payeeIdentifiers::ibanBic beneficiaryAcc = job.constTask()->beneficiaryTyped(); AB_Transaction_SetRemoteName(abJob, beneficiaryAcc.ownerName().toUtf8().constData()); AB_Transaction_SetRemoteIban(abJob, beneficiaryAcc.electronicIban().toUtf8().constData()); AB_Transaction_SetRemoteBic(abJob, beneficiaryAcc.fullStoredBic().toUtf8().constData()); // Origin Account AB_Transaction_SetLocalAccount(abJob, abAccount); // Purpose AB_Transaction_SetPurpose(abJob, job.constTask()->purpose().toUtf8().constData()); // Reference // AqBanking duplicates the string. This should be safe. AB_Transaction_SetEndToEndReference(abJob, job.constTask()->endToEndReference().toUtf8().constData()); // Other Fields AB_Transaction_SetTextKey(abJob, job.constTask()->textKey()); AB_Transaction_SetValue(abJob, AB_Value_fromMyMoneyMoney(job.constTask()->value())); /** @todo LOW remove Debug info */ AB_Transaction_SetStringIdForApplication(abJob, m_kbanking->mappingId(job).toUtf8().constData()); qDebug() << "Enqueue: " << m_kbanking->enqueueJob(abJob); AB_Transaction_free(abJob); //delete localAcc; return true; } void KBanking::startPasswordTimer() { if (d->passwordCacheTimer->isActive()) d->passwordCacheTimer->stop(); d->passwordCacheTimer->start(); } void KBanking::slotClearPasswordCache() { m_kbanking->clearPasswordCache(); } void KBanking::slotImport() { m_statementCount = 0; statementInterface()->resetMessages(); if (!m_kbanking->interactiveImport()) qWarning("Error on import dialog"); else statementInterface()->showMessages(m_statementCount); } bool KBanking::importStatement(const MyMoneyStatement& s) { m_statementCount++; return !statementInterface()->import(s).isEmpty(); } MyMoneyAccount KBanking::account(const QString& key, const QString& value) const { return statementInterface()->account(key, value); } void KBanking::setAccountOnlineParameters(const MyMoneyAccount& acc, const MyMoneyKeyValueContainer& kvps) const { return statementInterface()->setAccountOnlineParameters(acc, kvps); } KBankingExt::KBankingExt(KBanking* parent, const char* appname, const char* fname) : AB_Banking(appname, fname) , m_parent(parent) , _jobQueue(0) { m_sepaKeywords = {QString::fromUtf8("SEPA-BASISLASTSCHRIFT"), QString::fromUtf8("SEPA-ÜBERWEISUNG")}; QRegularExpression exp("(\\d+\\.\\d+\\.\\d+).*"); QRegularExpressionMatch match = exp.match(KAboutData::applicationData().version()); QByteArray regkey; const char *p = "\x08\x0f\x41\x0f\x58\x5b\x56\x4a\x09\x7b\x40\x0e\x5f\x2a\x56\x3f\x0e\x7f\x3f\x7d\x5b\x56\x56\x4b\x0a\x4d"; const char* q = appname; while (*p) { regkey += (*q++ ^ *p++) & 0xff; if (!*q) q = appname; } registerFinTs(regkey.data(), match.captured(1).toLatin1()); } int KBankingExt::init() { int rv = AB_Banking::init(); if (rv < 0) return rv; _jobQueue = AB_Transaction_List2_new(); return 0; } int KBankingExt::fini() { if (_jobQueue) { AB_Transaction_List2_freeAll(_jobQueue); _jobQueue = 0; } return AB_Banking::fini(); } int KBankingExt::executeQueue(AB_IMEXPORTER_CONTEXT *ctx) { m_parent->startPasswordTimer(); int rv = AB_Banking::executeJobs(_jobQueue, ctx); if (rv != 0) { qDebug() << "Sending queue by aqbanking got error no " << rv; } /** check result of each job */ AB_TRANSACTION_LIST2_ITERATOR* jobIter = AB_Transaction_List2_First(_jobQueue); if (jobIter) { AB_TRANSACTION* abJob = AB_Transaction_List2Iterator_Data(jobIter); while (abJob) { const char *stringIdForApp=AB_Transaction_GetStringIdForApplication(abJob); if (!(stringIdForApp && *stringIdForApp)) { qWarning("Executed AB_Job without KMyMoney id"); abJob = AB_Transaction_List2Iterator_Next(jobIter); continue; } QString jobIdent = QString::fromUtf8(stringIdForApp); onlineJob job = m_parent->m_onlineJobQueue.value(jobIdent); if (job.isNull()) { // It should not be possible that this will happen (only if AqBanking fails heavily). //! @todo correct exception text qWarning("Executed a job which was not in queue. Please inform the KMyMoney developers."); abJob = AB_Transaction_List2Iterator_Next(jobIter); continue; } AB_TRANSACTION_STATUS abStatus = AB_Transaction_GetStatus(abJob); if (abStatus == AB_Transaction_StatusSent || abStatus == AB_Transaction_StatusPending || abStatus == AB_Transaction_StatusAccepted || abStatus == AB_Transaction_StatusRejected || abStatus == AB_Transaction_StatusError || abStatus == AB_Transaction_StatusUnknown) job.setJobSend(); if (abStatus == AB_Transaction_StatusAccepted) job.setBankAnswer(eMyMoney::OnlineJob::sendingState::acceptedByBank); else if (abStatus == AB_Transaction_StatusError || abStatus == AB_Transaction_StatusRejected || abStatus == AB_Transaction_StatusUnknown) job.setBankAnswer(eMyMoney::OnlineJob::sendingState::sendingError); job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Debug, "KBanking", "Job was processed")); m_parent->m_onlineJobQueue.insert(jobIdent, job); abJob = AB_Transaction_List2Iterator_Next(jobIter); } AB_Transaction_List2Iterator_free(jobIter); } AB_TRANSACTION_LIST2 *oldQ = _jobQueue; _jobQueue = AB_Transaction_List2_new(); AB_Transaction_List2_freeAll(oldQ); emit m_parent->queueChanged(); m_parent->startPasswordTimer(); return rv; } void KBankingExt::clearPasswordCache() { /* clear password DB */ GWEN_Gui_SetPasswordStatus(NULL, NULL, GWEN_Gui_PasswordStatus_Remove, 0); } std::list KBankingExt::getEnqueuedJobs() { AB_TRANSACTION_LIST2 *ll; std::list rl; ll = _jobQueue; if (ll && AB_Transaction_List2_GetSize(ll)) { AB_TRANSACTION *j; AB_TRANSACTION_LIST2_ITERATOR *it; it = AB_Transaction_List2_First(ll); assert(it); j = AB_Transaction_List2Iterator_Data(it); assert(j); while (j) { rl.push_back(j); j = AB_Transaction_List2Iterator_Next(it); } AB_Transaction_List2Iterator_free(it); } return rl; } int KBankingExt::enqueueJob(AB_TRANSACTION *j) { assert(_jobQueue); assert(j); AB_Transaction_Attach(j); AB_Transaction_List2_PushBack(_jobQueue, j); return 0; } int KBankingExt::dequeueJob(AB_TRANSACTION *j) { assert(_jobQueue); AB_Transaction_List2_Remove(_jobQueue, j); AB_Transaction_free(j); emit m_parent->queueChanged(); return 0; } void KBankingExt::transfer() { //m_parent->transfer(); } bool KBankingExt::askMapAccount(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); QString bankId; QString accountId; // extract some information about the bank. if we have a sortcode // (BLZ) we display it, otherwise the name is enough. try { const MyMoneyInstitution &bank = file->institution(acc.institutionId()); bankId = bank.name(); if (!bank.sortcode().isEmpty()) bankId = bank.sortcode(); } catch (const MyMoneyException &e) { // no bank assigned, we just leave the field empty } // extract account information. if we have an account number // we show it, otherwise the name will be displayed accountId = acc.number(); if (accountId.isEmpty()) accountId = acc.name(); // do the mapping. the return value of this method is either // true, when the user mapped the account or false, if he // decided to quit the dialog. So not really a great thing // to present some more information. KBMapAccount *w; w = new KBMapAccount(this, bankId.toUtf8().constData(), accountId.toUtf8().constData()); if (w->exec() == QDialog::Accepted) { AB_ACCOUNT_SPEC *a; a = w->getAccount(); assert(a); DBG_NOTICE(0, "Mapping application account \"%s\" to " "online account \"%s/%s\"", qPrintable(acc.name()), AB_AccountSpec_GetBankCode(a), AB_AccountSpec_GetAccountNumber(a)); // TODO remove the following line once we don't need backward compatibility setAccountAlias(a, acc.id().toUtf8().constData()); qDebug("Setup mapping to '%s'", acc.id().toUtf8().constData()); setAccountAlias(a, mappingId(acc).toUtf8().constData()); qDebug("Setup mapping to '%s'", mappingId(acc).toUtf8().constData()); delete w; return true; } delete w; return false; } QString KBankingExt::mappingId(const MyMoneyObject& object) const { QString id = MyMoneyFile::instance()->storageId() + QLatin1Char('-') + object.id(); // AqBanking does not handle the enclosing parens, so we remove it id.remove('{'); id.remove('}'); return id; } bool KBankingExt::interactiveImport() { AB_IMEXPORTER_CONTEXT *ctx; GWEN_DIALOG *dlg; int rv; ctx = AB_ImExporterContext_new(); dlg = AB_Banking_CreateImporterDialog(getCInterface(), ctx, NULL); if (dlg == NULL) { DBG_ERROR(0, "Could not create importer dialog."); AB_ImExporterContext_free(ctx); return false; } rv = GWEN_Gui_ExecDialog(dlg, 0); if (rv == 0) { DBG_ERROR(0, "Aborted by user"); GWEN_Dialog_free(dlg); AB_ImExporterContext_free(ctx); return false; } if (!importContext(ctx, 0)) { DBG_ERROR(0, "Error on importContext"); GWEN_Dialog_free(dlg); AB_ImExporterContext_free(ctx); return false; } GWEN_Dialog_free(dlg); AB_ImExporterContext_free(ctx); return true; } void KBankingExt::_xaToStatement(MyMoneyStatement &ks, - const MyMoneyAccount& acc, - const AB_TRANSACTION *t) + const MyMoneyAccount& acc, + const AB_TRANSACTION *t) { QString s; QString memo; const char *p; const AB_VALUE *val; const GWEN_DATE *dt; const GWEN_DATE *startDate = 0; MyMoneyStatement::Transaction kt; unsigned long h; kt.m_fees = MyMoneyMoney(); // bank's transaction id p = AB_Transaction_GetFiId(t); if (p) kt.m_strBankID = QString("ID ") + QString::fromUtf8(p); // payee p = AB_Transaction_GetRemoteName(t); if (p) kt.m_strPayee = QString::fromUtf8(p); // memo -#if 1 + p = AB_Transaction_GetPurpose(t); if (p && *p) { QString tmpMemo; s=QString::fromUtf8(p).trimmed(); tmpMemo=QString::fromUtf8(p).trimmed(); tmpMemo.replace('\n', ' '); memo=tmpMemo; } // in case we have some SEPA fields filled with information // we add them to the memo field p = AB_Transaction_GetEndToEndReference(t); if (p) { s += QString(", EREF: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("EREF: %1").arg(p)); } p = AB_Transaction_GetCustomerReference(t); if (p) { s += QString(", CREF: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("CREF: %1").arg(p)); } p = AB_Transaction_GetMandateId(t); if (p) { s += QString(", MREF: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("MREF: %1").arg(p)); } p = AB_Transaction_GetCreditorSchemeId(t); if (p) { s += QString(", CRED: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("CRED: %1").arg(p)); } p = AB_Transaction_GetOriginatorId(t); if (p) { s += QString(", DEBT: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("DEBT: %1").arg(p)); } -#else - // The variable 's' contains the old method of extracting - // the memo which added a linefeed after each part received - // from AqBanking. The new variable 'memo' does not have - // this inserted linefeed. We keep the variable 's' to - // construct the hash-value to retrieve the reference - s.truncate(0); - sl = AB_Transaction_GetPurpose(t); - if (sl) { - GWEN_STRINGLISTENTRY *se; - bool insertLineSep = false; - - se = GWEN_StringList_FirstEntry(sl); - while (se) { - p = GWEN_StringListEntry_Data(se); - assert(p); - if (insertLineSep) - s += '\n'; - insertLineSep = true; - s += QString::fromUtf8(p).trimmed(); - memo += QString::fromUtf8(p).trimmed(); - se = GWEN_StringListEntry_Next(se); - } // while - - // Sparda / Netbank hack: the software these banks use stores - // parts of the payee name in the beginning of the purpose field - // in case the payee name exceeds the 27 character limit. This is - // the case, when one of the strings listed in m_sepaKeywords is part - // of the purpose fields but does not start at the beginning. In this - // case, the part leading up to the keyword is to be treated as the - // tail of the payee. Also, a blank is inserted after the keyword. - QSet::const_iterator itk; - for (itk = m_sepaKeywords.constBegin(); itk != m_sepaKeywords.constEnd(); ++itk) { - int idx = s.indexOf(*itk); - if (idx >= 0) { - if (idx > 0) { - // re-add a possibly removed blank to name - if (kt.m_strPayee.length() < 27) - kt.m_strPayee += ' '; - kt.m_strPayee += s.left(idx); - s = s.mid(idx); - } - s = QString("%1 %2").arg(*itk).arg(s.mid((*itk).length())); - - // now do the same for 'memo' except for updating the payee - idx = memo.indexOf(*itk); - if (idx >= 0) { - if (idx > 0) { - memo = memo.mid(idx); - } - } - memo = QString("%1 %2").arg(*itk).arg(memo.mid((*itk).length())); - break; - } - } - - // in case we have some SEPA fields filled with information - // we add them to the memo field - p = AB_Transaction_GetEndToEndReference(t); - if (p) { - s += QString(", EREF: %1").arg(p); - if(memo.length()) - memo.append('\n'); - memo.append(QString("EREF: %1").arg(p)); - } - p = AB_Transaction_GetCustomerReference(t); - if (p) { - s += QString(", CREF: %1").arg(p); - if(memo.length()) - memo.append('\n'); - memo.append(QString("CREF: %1").arg(p)); - } - p = AB_Transaction_GetMandateId(t); - if (p) { - s += QString(", MREF: %1").arg(p); - if(memo.length()) - memo.append('\n'); - memo.append(QString("MREF: %1").arg(p)); - } - p = AB_Transaction_GetCreditorSchemeId(t); - if (p) { - s += QString(", CRED: %1").arg(p); - if(memo.length()) - memo.append('\n'); - memo.append(QString("CRED: %1").arg(p)); - } - p = AB_Transaction_GetOriginatorId(t); - if (p) { - s += QString(", DEBT: %1").arg(p); - if(memo.length()) - memo.append('\n'); - memo.append(QString("DEBT: %1").arg(p)); - } - } -#endif - const MyMoneyKeyValueContainer& kvp = acc.onlineBankingSettings(); // check if we need the version with or without linebreaks if (kvp.value("kbanking-memo-removelinebreaks").compare(QLatin1String("no"))) { kt.m_strMemo = memo; } else { kt.m_strMemo = s; } // calculate the hash code and start with the payee info // and append the memo field h = MyMoneyTransaction::hash(kt.m_strPayee.trimmed()); h = MyMoneyTransaction::hash(s, h); // see, if we need to extract the payee from the memo field QString rePayee = kvp.value("kbanking-payee-regexp"); if (!rePayee.isEmpty() && kt.m_strPayee.isEmpty()) { QString reMemo = kvp.value("kbanking-memo-regexp"); QStringList exceptions = kvp.value("kbanking-payee-exceptions").split(';', QString::SkipEmptyParts); bool needExtract = true; QStringList::const_iterator it_s; for (it_s = exceptions.constBegin(); needExtract && it_s != exceptions.constEnd(); ++it_s) { QRegExp exp(*it_s, Qt::CaseInsensitive); if (exp.indexIn(kt.m_strMemo) != -1) { needExtract = false; } } if (needExtract) { QRegExp expPayee(rePayee, Qt::CaseInsensitive); QRegExp expMemo(reMemo, Qt::CaseInsensitive); if (expPayee.indexIn(kt.m_strMemo) != -1) { kt.m_strPayee = expPayee.cap(1); if (expMemo.indexIn(kt.m_strMemo) != -1) { kt.m_strMemo = expMemo.cap(1); } } } } kt.m_strPayee = kt.m_strPayee.trimmed(); // date dt = AB_Transaction_GetDate(t); if (!dt) dt = AB_Transaction_GetValutaDate(t); if (dt) { if (!startDate) startDate = dt; - /*else { dead code - if (GWEN_Time_Diff(ti, startTime) < 0) - startTime = ti; - }*/ kt.m_datePosted = QDate(GWEN_Date_GetYear(dt), GWEN_Date_GetMonth(dt), GWEN_Date_GetDay(dt)); } else { DBG_WARN(0, "No date for transaction"); } // value val = AB_Transaction_GetValue(t); if (val) { if (ks.m_strCurrency.isEmpty()) { p = AB_Value_GetCurrency(val); if (p) ks.m_strCurrency = p; } else { p = AB_Value_GetCurrency(val); if (p) s = p; if (ks.m_strCurrency.toLower() != s.toLower()) { // TODO: handle currency difference DBG_ERROR(0, "Mixed currencies currently not allowed"); } } kt.m_amount = MyMoneyMoney(AB_Value_GetValueAsDouble(val)); // The initial implementation of this feature was based on // a denominator of 100. Since the denominator might be // different nowadays, we make sure to use 100 for the // duplicate detection QString tmpVal = kt.m_amount.formatMoney(100, false); tmpVal.remove(QRegExp("[,\\.]")); tmpVal += QLatin1String("/100"); h = MyMoneyTransaction::hash(tmpVal, h); } else { DBG_WARN(0, "No value for transaction"); } if (startDate) { QDate d(QDate(GWEN_Date_GetYear(startDate), GWEN_Date_GetMonth(startDate), GWEN_Date_GetDay(startDate))); if (!ks.m_dateBegin.isValid()) ks.m_dateBegin = d; else if (d < ks.m_dateBegin) ks.m_dateBegin = d; if (!ks.m_dateEnd.isValid()) ks.m_dateEnd = d; else if (d > ks.m_dateEnd) ks.m_dateEnd = d; } else { DBG_WARN(0, "No date in current transaction"); } // add information about remote account to memo in case we have something const char *remoteAcc = AB_Transaction_GetRemoteAccountNumber(t); const char *remoteBankCode = AB_Transaction_GetRemoteBankCode(t); if (remoteAcc && remoteBankCode) { kt.m_strMemo += QString("\n%1/%2").arg(remoteBankCode, remoteAcc); } // make hash value unique in case we don't have one already if (kt.m_strBankID.isEmpty()) { QString hashBase; hashBase.sprintf("%s-%07lx", qPrintable(kt.m_datePosted.toString(Qt::ISODate)), h); int idx = 1; QString hash; for (;;) { hash = QString("%1-%2").arg(hashBase).arg(idx); QMap::const_iterator it; it = m_hashMap.constFind(hash); if (it == m_hashMap.constEnd()) { m_hashMap[hash] = true; break; } ++idx; } kt.m_strBankID = QString("%1-%2").arg(acc.id()).arg(hash); } // store transaction ks.m_listTransactions += kt; } -bool KBankingExt::importAccountInfo(AB_IMEXPORTER_ACCOUNTINFO *ai, - uint32_t /*flags*/) +void KBankingExt::_slToStatement(MyMoneyStatement &ks, + const MyMoneyAccount& acc, + const AB_SECURITY *sy) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + QString s; + QString memo; + const char *p; + const AB_VALUE *val; + const GWEN_TIME *ti; + MyMoneyStatement::Security ksy; + MyMoneyStatement::Price kpr; + MyMoneyStatement::Transaction kt; + + // security name + p = AB_Security_GetName(sy); + if (p) + ksy.m_strName = QString::fromUtf8(p); + + // security id + p = AB_Security_GetUniqueId(sy); + if (p) { + ksy.m_strId = QString::fromUtf8(p); + ksy.m_strSymbol = QString::fromUtf8(p); + kpr.m_strSecurity = QString::fromUtf8(p); + } + + // date + ti = AB_Security_GetUnitPriceDate(sy); + if (ti) { + int year, month, day; + + if (!GWEN_Time_GetBrokenDownDate(ti, &day, &month, &year)) { + kpr.m_date = QDate(year, month + 1, day); + } + } + + // value + val = AB_Security_GetUnitPriceValue(sy); + if (val) + kpr.m_amount = MyMoneyMoney(AB_Value_GetValueAsDouble(val)); + + // generate dummy booking in case online balance does not match + MyMoneySecurity security; + MyMoneyAccount sacc; + foreach (const auto sAccount, file->account(acc.id()).accountList()) { + sacc=file->account(sAccount); + security=file->security(sacc.currencyId()); + if ((!ksy.m_strSymbol.isEmpty() && QString::compare(ksy.m_strSymbol, security.tradingSymbol(), Qt::CaseInsensitive) == 0) || + (!ksy.m_strName.isEmpty() && QString::compare(ksy.m_strName, security.name(), Qt::CaseInsensitive) == 0)) { + if (sacc.balance().toDouble() != AB_Value_GetValueAsDouble(AB_Security_GetUnits(sy))) { + qDebug("Creating dummy correction booking for '%s' with %f shares", qPrintable(security.tradingSymbol()), + AB_Value_GetValueAsDouble(AB_Security_GetUnits(sy))-sacc.balance().toDouble()); + kt.m_fees = MyMoneyMoney(); + kt.m_strMemo = "Dummy booking added by KMyMoney to reflect online balance - please adjust"; + kt.m_datePosted = QDate::currentDate(); + kt.m_strSymbol=security.tradingSymbol(); + kt.m_strSecurity=security.name(); + kt.m_strBrokerageAccount=acc.name(); + + kt.m_shares=MyMoneyMoney(AB_Value_GetValueAsDouble(AB_Security_GetUnits(sy))-sacc.balance().toDouble()); + if (AB_Value_GetValueAsDouble(AB_Security_GetUnits(sy)) > sacc.balance().toDouble()) + kt.m_eAction = eMyMoney::Transaction::Action::Shrsin; + else + kt.m_eAction = eMyMoney::Transaction::Action::Shrsout; + + // store transaction + ks.m_listTransactions += kt; + } + else + qDebug("Online balance matches balance in KMyMoney account!"); + } + } + + // store security + ks.m_listSecurities += ksy; + + // store prices + ks.m_listPrices += kpr; +} + + +bool KBankingExt::importAccountInfo(AB_IMEXPORTER_CONTEXT *ctx, + AB_IMEXPORTER_ACCOUNTINFO *ai, + uint32_t /*flags*/) { const char *p; DBG_INFO(0, "Importing account..."); // account number MyMoneyStatement ks; p = AB_ImExporterAccountInfo_GetAccountNumber(ai); if (p) { ks.m_strAccountNumber = m_parent->stripLeadingZeroes(p); } p = AB_ImExporterAccountInfo_GetBankCode(ai); if (p) { ks.m_strRoutingNumber = m_parent->stripLeadingZeroes(p); } MyMoneyAccount kacc; if (!ks.m_strAccountNumber.isEmpty() || !ks.m_strRoutingNumber.isEmpty()) { - kacc = m_parent->account("kbanking-acc-ref", QString("%1-%2").arg(ks.m_strRoutingNumber, ks.m_strAccountNumber)); + kacc = m_parent->account("kbanking-acc-ref", QString("%1-%2-%3").arg(ks.m_strRoutingNumber, ks.m_strAccountNumber).arg(AB_ImExporterAccountInfo_GetAccountType(ai))); ks.m_accountId = kacc.id(); } // account name p = AB_ImExporterAccountInfo_GetAccountName(ai); if (p) ks.m_strAccountName = p; // account type switch (AB_ImExporterAccountInfo_GetAccountType(ai)) { case AB_AccountType_Bank: ks.m_eType = eMyMoney::Statement::Type::Savings; break; case AB_AccountType_CreditCard: ks.m_eType = eMyMoney::Statement::Type::CreditCard; break; case AB_AccountType_Checking: ks.m_eType = eMyMoney::Statement::Type::Checkings; break; case AB_AccountType_Savings: ks.m_eType = eMyMoney::Statement::Type::Savings; break; case AB_AccountType_Investment: ks.m_eType = eMyMoney::Statement::Type::Investment; break; case AB_AccountType_Cash: default: ks.m_eType = eMyMoney::Statement::Type::None; } // account status const AB_BALANCE *bal = AB_Balance_List_GetLatestByType(AB_ImExporterAccountInfo_GetBalanceList(ai), AB_Balance_TypeBooked); if (!bal) bal = AB_Balance_List_GetLatestByType(AB_ImExporterAccountInfo_GetBalanceList(ai), AB_Balance_TypeNoted); if (bal) { const AB_VALUE* val = AB_Balance_GetValue(bal); if (val) { DBG_INFO(0, "Importing balance"); ks.m_closingBalance = AB_Value_toMyMoneyMoney(val); p = AB_Value_GetCurrency(val); if (p) ks.m_strCurrency = p; } const GWEN_DATE* dt = AB_Balance_GetDate(bal); if (dt) { ks.m_dateEnd = QDate(GWEN_Date_GetYear(dt), GWEN_Date_GetMonth(dt) , GWEN_Date_GetDay(dt)); } else { DBG_WARN(0, "No date for balance"); } } else { DBG_WARN(0, "No account balance"); } // clear hash map m_hashMap.clear(); + // get all securities + const AB_SECURITY* s = AB_ImExporterContext_GetFirstSecurity(ctx); + while (s) { + qDebug("Found security '%s'", AB_Security_GetName(s)); + _slToStatement(ks, kacc, s); + s = AB_Security_List_Next(s); + } + // get all transactions const AB_TRANSACTION* t = AB_ImExporterAccountInfo_GetFirstTransaction(ai, AB_Transaction_TypeStatement, 0); while (t) { _xaToStatement(ks, kacc, t); t = AB_Transaction_List_FindNextByType(t, AB_Transaction_TypeStatement, 0); } // import them if (!m_parent->importStatement(ks)) { if (KMessageBox::warningYesNo(0, i18n("Error importing statement. Do you want to continue?"), i18n("Critical Error")) == KMessageBox::No) { DBG_ERROR(0, "User aborted"); return false; } } return true; } K_PLUGIN_FACTORY_WITH_JSON(KBankingFactory, "kbanking.json", registerPlugin();) #include "kbanking.moc" diff --git a/kmymoney/plugins/kbanking/kbanking.h b/kmymoney/plugins/kbanking/kbanking.h index e5ef99f35..07c04ee0a 100644 --- a/kmymoney/plugins/kbanking/kbanking.h +++ b/kmymoney/plugins/kbanking/kbanking.h @@ -1,252 +1,252 @@ -/*************************************************************************** - * Copyright 2004 Martin Preuss aquamaniac@users.sourceforge.net * - * Copyright 2009 Cristian Onet onet.cristian@gmail.com * - * Copyright 2010 Thomas Baumgart ipwizard@users.sourceforge.net * - * Copyright 2016 Christian David christian-david@web.de * - * * - * 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) version 3 or any later version * - * accepted by the membership of KDE e.V. (or its successor approved * - * by the membership of KDE e.V.), which shall act as a proxy * - * defined in Section 14 of version 3 of the license. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see * - ***************************************************************************/ +/* + * Copyright 2004 Martin Preuss aquamaniac@users.sourceforge.net + * Copyright 2009 Cristian Onet onet.cristian@gmail.com + * Copyright 2010-2019 Thomas Baumgart tbaumgart@kde.org + * Copyright 2016 Christian David christian-david@web.de + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef KBANKING_H #define KBANKING_H #ifdef HAVE_CONFIG_H #include #endif // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE & Library Includes class KAction; class QBanking; class KBankingExt; class KBAccountSettings; #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyplugin.h" #include "onlinepluginextended.h" #include "mymoneyaccount.h" #include "mymoneykeyvaluecontainer.h" #include "mymoney/onlinejobtyped.h" #include "onlinetasks/sepa/sepaonlinetransfer.h" #include "banking.hpp" /** * This class represents the KBanking plugin towards KMymoney. * All GUI related issues are handled in this object. */ class MyMoneyStatement; class KBanking : public KMyMoneyPlugin::OnlinePluginExtended { friend class KBankingExt; Q_OBJECT Q_INTERFACES(KMyMoneyPlugin::OnlinePluginExtended KMyMoneyPlugin::OnlinePlugin) public: explicit KBanking(QObject *parent, const QVariantList &args); ~KBanking() override; bool importStatement(const MyMoneyStatement& s); MyMoneyAccount account(const QString& key, const QString& value) const; void setAccountOnlineParameters(const MyMoneyAccount& acc, const MyMoneyKeyValueContainer& kvps) const; void protocols(QStringList& protocolList) const override; QStringList availableJobs(QString accountId) override; IonlineTaskSettings::ptr settings(QString accountId, QString taskName) override; void sendOnlineJob(QList& jobs) override; void plug() override; void unplug() override; private: /** * creates the action objects available through the application menus */ void createActions(); /** * creates the context menu */ void createContextMenu(); /** * checks whether a given KMyMoney account with id @p id is * already mapped or not. * * @param acc reference to KMyMoney account object * @retval false account is not mapped to an AqBanking account * @retval true account is mapped to an AqBanking account */ bool accountIsMapped(const MyMoneyAccount& acc); /** * sets up the reference string consisting out of BLZ and account number * in the KMyMoney object so that we can find it later on when importing data. */ void setupAccountReference(const MyMoneyAccount& acc, AB_ACCOUNT_SPEC* ab_acc); /** * Returns the value of the parameter @a s with all leading 0's stripped. */ QString stripLeadingZeroes(const QString& s) const; /** * Prefills the protocol conversion list to allow mapping * of AqBanking internal names to external names */ void loadProtocolConversion(); /** * Creates an additional tab widget for the account edit dialog * to represent the necessary parameters for online banking * through AqBanking. */ QWidget* accountConfigTab(const MyMoneyAccount& acc, QString& name) override; /** * Stores the configuration data kept in the widgets created * in accountConfigTab() and returns them in a key value container * The current settings are accessible through the reference to * @a current. */ MyMoneyKeyValueContainer onlineBankingSettings(const MyMoneyKeyValueContainer& current) override; /** * Called by the application to map the KMyMoney account @a acc * to an AqBanking account. Calls KBanking to set up AqBanking mappings. * Returns the necessary settings for the plugin in @a settings and * @a true if the mapping was successful. */ bool mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& settings) override; /** * This method translates a MyMoneyAccount to the corresponding AB_ACCOUNT object pointer. * If no mapped account can be detected, it returns 0. */ AB_ACCOUNT_SPEC* aqbAccount(const MyMoneyAccount& acc) const; /** * This is a convenient method for aqbAccount if you have KMyMoney's account id only. */ AB_ACCOUNT_SPEC* aqbAccount(const QString& accountId) const; /** * Called by the application framework to update the * KMyMoney account @a acc with data from the online source. * Store the jobs in the outbox in case @a moreAccounts is true */ bool updateAccount(const MyMoneyAccount& acc, bool moreAccounts) override; /** * Kept for backward compatibility. Use * updateAccount(const MyMoneyAccount& acc, bool moreAccounts) instead. * * @deprecated */ bool updateAccount(const MyMoneyAccount& acc) DEPRECATED; /** * Trigger the password cache timer */ void startPasswordTimer(); bool enqueTransaction(onlineJobTyped& job); protected Q_SLOTS: void slotSettings(); void slotImport(); void slotClearPasswordCache(); void executeQueue(); Q_SIGNALS: void queueChanged(); private: class Private; Private* const d; KAction* m_configAction; KAction* m_importAction; KBankingExt* m_kbanking; QMap m_protocolConversionMap; KBAccountSettings* m_accountSettings; int m_statementCount; /** * @brief @ref onlineJob "onlineJobs" which are executed at the moment * Key is onlineJob->id(). This container is used during execution of jobs. */ QMap m_onlineJobQueue; }; /** * This class is the special implementation to glue the AB_Banking class * with the KMyMoneyPlugin structure. */ class KBankingExt : public AB_Banking { friend class KBanking; public: explicit KBankingExt(KBanking* parent, const char* appname, const char* fname = 0); virtual ~KBankingExt() {} int executeQueue(AB_IMEXPORTER_CONTEXT *ctx); int enqueueJob(AB_TRANSACTION *j); int dequeueJob(AB_TRANSACTION *j); std::list getEnqueuedJobs(); void transfer(); virtual bool interactiveImport(); protected: int init() final override; int fini() final override; bool askMapAccount(const MyMoneyAccount& acc); QString mappingId(const MyMoneyObject& object) const; - bool importAccountInfo(AB_IMEXPORTER_ACCOUNTINFO *ai, uint32_t flags) final override; + bool importAccountInfo(AB_IMEXPORTER_CONTEXT *ctx, AB_IMEXPORTER_ACCOUNTINFO *ai, uint32_t flags) final override; + void _slToStatement(MyMoneyStatement &ks, + const MyMoneyAccount&, + const AB_SECURITY *sy); void _xaToStatement(MyMoneyStatement &ks, const MyMoneyAccount&, const AB_TRANSACTION *t); void clearPasswordCache(); private: KBanking* m_parent; QMap m_hashMap; AB_TRANSACTION_LIST2 *_jobQueue; QSet m_sepaKeywords; }; #endif // KBANKING