diff --git a/kmymoney/plugins/csv/export/csvexporter.cpp b/kmymoney/plugins/csv/export/csvexporter.cpp index 713e621cc..9effffee1 100644 --- a/kmymoney/plugins/csv/export/csvexporter.cpp +++ b/kmymoney/plugins/csv/export/csvexporter.cpp @@ -1,126 +1,128 @@ /* * Copyright 2013-2014 Allan Anderson + * Copyright 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 . */ +#include #include "csvexporter.h" // ---------------------------------------------------------------------------- // QT Includes #include #ifdef IS_APPIMAGE #include #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "csvexportdlg.h" #include "csvwriter.h" #include "viewinterface.h" CSVExporter::CSVExporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "csvexporter"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args); const auto componentName = QLatin1String("csvexporter"); const auto rcFileName = QLatin1String("csvexporter.rc"); setComponentName(componentName, i18n("CSV exporter")); #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 createActions(); // For information, announce that we have been loaded. qDebug("Plugins: csvexporter loaded"); } CSVExporter::~CSVExporter() { qDebug("Plugins: csvexporter unloaded"); } void CSVExporter::createActions() { const auto &kpartgui = QStringLiteral("file_export_csv"); m_action = actionCollection()->addAction(kpartgui); m_action->setText(i18n("&CSV...")); connect(m_action, &QAction::triggered, this, &CSVExporter::slotCsvExport); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } void CSVExporter::slotCsvExport() { m_dlg = new CsvExportDlg(); if (m_dlg->exec()) { if (okToWriteFile(QUrl::fromUserInput(m_dlg->filename()))) { m_dlg->setWindowTitle(i18nc("CSV Exporter dialog title", "CSV Exporter")); CsvWriter* writer = new CsvWriter; writer->m_plugin = this; connect(writer, &CsvWriter::signalProgress, m_dlg, &CsvExportDlg::slotStatusProgressBar); writer->write(m_dlg->filename(), m_dlg->accountId(), m_dlg->accountSelected(), m_dlg->categorySelected(), m_dlg->startDate(), m_dlg->endDate(), m_dlg->separator()); } } } bool CSVExporter::okToWriteFile(const QUrl &url) { // check if the file exists and warn the user bool reallySaveFile = true; bool fileExists = false; if (url.isValid()) { short int detailLevel = 0; // Lowest level: file/dir/symlink/none KIO::StatJob* statjob = KIO::stat(url, KIO::StatJob::SourceSide, detailLevel); bool noerror = statjob->exec(); if (noerror) { // We want a file fileExists = !statjob->statResult().isDir(); } } if (fileExists) { if (KMessageBox::warningYesNo(0, i18n("The file %1 already exists. Do you really want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile)), i18n("File already exists")) != KMessageBox::Yes) reallySaveFile = false; } return reallySaveFile; } K_PLUGIN_FACTORY_WITH_JSON(CSVExporterFactory, "csvexporter.json", registerPlugin();) #include "csvexporter.moc" diff --git a/kmymoney/plugins/csv/import/csvimporter.cpp b/kmymoney/plugins/csv/import/csvimporter.cpp index a546cf126..8aba046b1 100644 --- a/kmymoney/plugins/csv/import/csvimporter.cpp +++ b/kmymoney/plugins/csv/import/csvimporter.cpp @@ -1,128 +1,129 @@ /* * Copyright 2010-2014 Allan Anderson * Copyright 2016-2018 Łukasz Wojniłowicz * Copyright 2018-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 . */ +#include #include "csvimporter.h" // ---------------------------------------------------------------------------- // QT Includes #include #ifdef IS_APPIMAGE #include #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "core/csvimportercore.h" #include "csvwizard.h" #include "statementinterface.h" #include "viewinterface.h" CSVImporter::CSVImporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "csvimporter"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args); const auto componentName = QLatin1String("csvimporter"); const auto rcFileName = QLatin1String("csvimporter.rc"); setComponentName(componentName, i18n("CSV importer")); #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 createActions(); // For information, announce that we have been loaded. qDebug("Plugins: csvimporter loaded"); } CSVImporter::~CSVImporter() { qDebug("Plugins: csvimporter unloaded"); } void CSVImporter::createActions() { const auto &kpartgui = QStringLiteral("file_import_csv"); auto importAction = actionCollection()->addAction(kpartgui); importAction->setText(i18n("CSV...")); connect(importAction, &QAction::triggered, this, &CSVImporter::startWizardRun); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } QString CSVImporter::formatName() const { return QLatin1String("CSV"); } QString CSVImporter::formatFilenameFilter() const { return "*.csv"; } bool CSVImporter::isMyFormat(const QString& filename) const { // filename is considered a CSV file if it can be opened // and the filename ends in ".csv" (case does not matter). QFile f(filename); return filename.endsWith(QLatin1String(".csv"), Qt::CaseInsensitive) && f.open(QIODevice::ReadOnly | QIODevice::Text); } void CSVImporter::startWizardRun() { import(QString()); } bool CSVImporter::import(const QString& filename) { QPointer wizard = new CSVWizard(this); wizard->presetFilename(filename); auto rc = false; if ((wizard->exec() == QDialog::Accepted) && wizard) { rc = !statementInterface()->import(wizard->statement(), false).isEmpty(); } wizard->deleteLater(); return rc; } QString CSVImporter::lastError() const { return QString(); } K_PLUGIN_FACTORY_WITH_JSON(CSVImporterFactory, "csvimporter.json", registerPlugin();) #include "csvimporter.moc" diff --git a/kmymoney/plugins/icalendar/export/icalendarexporter.cpp b/kmymoney/plugins/icalendar/export/icalendarexporter.cpp index 684494808..1324dbec1 100644 --- a/kmymoney/plugins/icalendar/export/icalendarexporter.cpp +++ b/kmymoney/plugins/icalendar/export/icalendarexporter.cpp @@ -1,158 +1,160 @@ /* * Copyright 2009 Cristian Onet * Copyright 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 . */ +#include #include "icalendarexporter.h" +// Qt includes #include #include #include #ifdef IS_APPIMAGE #include #include #endif // KDE includes #include #include #include #include #include // KMyMoney includes #include "mymoneyfile.h" #include "pluginloader.h" #include "schedulestoicalendar.h" #include "pluginsettings.h" #include "viewinterface.h" struct iCalendarExporter::Private { QAction* m_action; QString m_profileName; QString m_iCalendarFileEntryName; KMMSchedulesToiCalendar m_exporter; }; iCalendarExporter::iCalendarExporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "icalendarexporter"/*must be the same as X-KDE-PluginInfo-Name*/), d(std::unique_ptr(new Private)) { Q_UNUSED(args); d->m_profileName = "iCalendarPlugin"; d->m_iCalendarFileEntryName = "iCalendarFile"; // Tell the host application to load my GUI component const auto componentName = QLatin1String("icalendarexporter"); const auto rcFileName = QLatin1String("icalendarexporter.rc"); setComponentName(componentName, i18n("iCalendar exporter")); #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 // For ease announce that we have been loaded. qDebug("Plugins: icalendarexporter loaded"); // Create the actions of this plugin QString actionName = i18n("Schedules to iCalendar"); QString icalFilePath; // Note the below code only exists to move existing settings to the new plugin specific config KConfigGroup config = KSharedConfig::openConfig()->group(d->m_profileName); icalFilePath = config.readEntry(d->m_iCalendarFileEntryName, icalFilePath); // read the settings PluginSettings::self()->load(); if (!icalFilePath.isEmpty()) { // move the old setting to the new config PluginSettings::setIcalendarFile(icalFilePath); PluginSettings::self()->save(); KSharedConfig::openConfig()->deleteGroup(d->m_profileName); } else { // read it from the new config icalFilePath = PluginSettings::icalendarFile(); } if (!icalFilePath.isEmpty()) actionName = i18n("Schedules to iCalendar [%1]", icalFilePath); const auto &kpartgui = QStringLiteral("file_export_icalendar"); d->m_action = actionCollection()->addAction(kpartgui); d->m_action->setText(actionName); connect(d->m_action, &QAction::triggered, this, &iCalendarExporter::slotFirstExport); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } iCalendarExporter::~iCalendarExporter() { qDebug("Plugins: icalendarexporter unloaded"); } void iCalendarExporter::slotFirstExport() { QPointer fileDialog = new QFileDialog(d->m_action->parentWidget(), QString(), QString(), QString("%1|%2\n").arg("*.ics").arg(i18nc("ICS (Filefilter)", "iCalendar files"))); fileDialog->setAcceptMode(QFileDialog::AcceptSave); fileDialog->setWindowTitle(i18n("Export as")); if (fileDialog->exec() == QDialog::Accepted) { QUrl newURL = fileDialog->selectedUrls().front(); if (newURL.isLocalFile()) { PluginSettings::setIcalendarFile(newURL.toLocalFile()); PluginSettings::self()->save(); slotExport(); } } delete fileDialog; } void iCalendarExporter::slotExport() { QString icalFilePath = PluginSettings::icalendarFile(); if (!icalFilePath.isEmpty()) d->m_exporter.exportToFile(icalFilePath); } void iCalendarExporter::plug() { connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &iCalendarExporter::slotExport); } void iCalendarExporter::unplug() { disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &iCalendarExporter::slotExport); } void iCalendarExporter::configurationChanged() { PluginSettings::self()->load(); // export the schedules because the configuration has changed QString icalFilePath = PluginSettings::icalendarFile(); if (!icalFilePath.isEmpty()) d->m_exporter.exportToFile(icalFilePath); } K_PLUGIN_FACTORY_WITH_JSON(iCalendarExporterFactory, "icalendarexporter.json", registerPlugin();) #include "icalendarexporter.moc" diff --git a/kmymoney/plugins/ofx/import/ofximporter.cpp b/kmymoney/plugins/ofx/import/ofximporter.cpp index f08df3ee8..199c2ec71 100644 --- a/kmymoney/plugins/ofx/import/ofximporter.cpp +++ b/kmymoney/plugins/ofx/import/ofximporter.cpp @@ -1,992 +1,993 @@ /* * Copyright 2005 Ace Jones acejones@users.sourceforge.net * Copyright 2010-2019 Thomas Baumgart tbaumgart@kde.org * * 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 "ofximporter.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #ifdef IS_APPIMAGE #include #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include "konlinebankingstatus.h" #include "konlinebankingsetupwizard.h" #include "kofxdirectconnectdlg.h" #include "mymoneyaccount.h" #include "mymoneyexception.h" #include "mymoneystatement.h" #include "mymoneystatementreader.h" #include "statementinterface.h" #include "importinterface.h" #include "viewinterface.h" #include "ui_importoption.h" #include "kmymoneyutils.h" //#define DEBUG_LIBOFX using KWallet::Wallet; typedef enum { UniqueIdUnknown = -1, UniqueIdOfx = 0, UniqueIdKMyMoney } UniqueTransactionIdSource; class OFXImporter::Private { public: Private() : m_valid(false), m_preferName(PreferId), m_uniqueIdSource(UniqueIdUnknown), m_walletIsOpen(false), m_statusDlg(0), m_wallet(0), m_updateStartDate(QDate(1900,1,1)), m_timestampOffset(0) {} bool m_valid; enum NamePreference { PreferId = 0, PreferName, PreferMemo } m_preferName; UniqueTransactionIdSource m_uniqueIdSource; bool m_walletIsOpen; QList m_statementlist; QList m_securitylist; QString m_fatalerror; QStringList m_infos; QStringList m_warnings; QStringList m_errors; KOnlineBankingStatus* m_statusDlg; Wallet *m_wallet; QDate m_updateStartDate; int m_timestampOffset; QSet m_hashes; int constructTimeOffset(const QTimeEdit* timestampOffset, const KComboBox* timestampOffsetSign) const { // get offset in minutes int offset = timestampOffset->time().msecsSinceStartOfDay() / 1000 / 60; if (timestampOffsetSign->currentText() == QStringLiteral("-")) { offset = -offset; } return offset; } }; static UniqueTransactionIdSource defaultIdSource() { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kmymoney/ofximporterrc")); KConfigGroup grp = config->group("General"); return (grp.readEntry("useOwnFITID", false) == true) ? UniqueIdKMyMoney : UniqueIdOfx; } OFXImporter::OFXImporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "ofximporter"), /* * the string in the line above must be the same as * X-KDE-PluginInfo-Name and the provider name assigned in * OfxImporterPlugin::onlineBankingSettings() */ KMyMoneyPlugin::ImporterPlugin(), d(new Private) { Q_UNUSED(args) const auto componentName = QLatin1String("ofximporter"); const auto rcFileName = QLatin1String("ofximporter.rc"); setComponentName(componentName, i18n("OFX Importer")); #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 createActions(); // For ease announce that we have been loaded. qDebug("Plugins: ofximporter loaded"); } OFXImporter::~OFXImporter() { delete d; qDebug("Plugins: ofximporter unloaded"); } void OFXImporter::createActions() { const auto &kpartgui = QStringLiteral("file_import_ofx"); auto a = actionCollection()->addAction(kpartgui); a->setText(i18n("OFX...")); connect(a, &QAction::triggered, this, static_cast(&OFXImporter::slotImportFile)); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } void OFXImporter::slotImportFile() { QWidget * widget = new QWidget; Ui_ImportOption* option = new Ui_ImportOption; option->setupUi(widget); // initially set to global default option option->m_uniqueIdSource->setCurrentIndex(defaultIdSource()); QUrl url = importInterface()->selectFile(i18n("OFX import file selection"), QString(), QStringLiteral("*.ofx *.qfx *.ofc|OFX files (*.ofx *.qfx *.ofc);;*|All files (*)"), QFileDialog::ExistingFile, widget); d->m_preferName = static_cast(option->m_preferName->currentIndex()); d->m_uniqueIdSource = static_cast(option->m_uniqueIdSource->currentIndex()); d->m_timestampOffset = d->constructTimeOffset(option->m_timestampOffset, option->m_timestampOffsetSign); if (url.isValid()) { const QString filename(url.toLocalFile()); if (isMyFormat(filename)) { statementInterface()->resetMessages(); slotImportFile(filename); statementInterface()->showMessages(d->m_statementlist.count()); } else { KMessageBox::error(0, i18n("Unable to import %1 using the OFX importer plugin. This file is not the correct format.", url.toDisplayString()), i18n("Incorrect format")); } } delete option; delete widget; } QString OFXImporter::formatName() const { return QStringLiteral("OFX"); } QString OFXImporter::formatFilenameFilter() const { return QStringLiteral("*.ofx *.qfx *.ofc"); } bool OFXImporter::isMyFormat(const QString& filename) const { // filename is considered an Ofx file if it contains // the tag "" or "" in the first 20 lines. // which contain some data bool result = false; QFile f(filename); if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream ts(&f); int lineCount = 20; while (!ts.atEnd() && !result && lineCount != 0) { // get a line of data and remove all unnecessary whitepace chars QString line = ts.readLine().simplified(); if (line.contains(QStringLiteral(""), Qt::CaseInsensitive) || line.contains(QStringLiteral(""), Qt::CaseInsensitive)) result = true; // count only lines that contain some non white space chars if (!line.isEmpty()) lineCount--; } f.close(); } else { qDebug() << "OFXImporter::isMyFormat: unable to open" << filename << "with" << f.errorString(); } return result; } bool OFXImporter::import(const QString& filename) { d->m_fatalerror = i18n("Unable to parse file"); d->m_valid = false; d->m_errors.clear(); d->m_warnings.clear(); d->m_infos.clear(); d->m_statementlist.clear(); d->m_securitylist.clear(); QByteArray filename_deep = QFile::encodeName(filename); ofx_STATUS_msg = true; ofx_INFO_msg = true; ofx_WARNING_msg = true; ofx_ERROR_msg = true; #ifdef DEBUG_LIBOFX ofx_PARSER_msg = true; ofx_DEBUG_msg = true; ofx_DEBUG1_msg = true; ofx_DEBUG2_msg = true; ofx_DEBUG3_msg = true; ofx_DEBUG4_msg = true; ofx_DEBUG5_msg = true; #endif LibofxContextPtr ctx = libofx_get_new_context(); Q_CHECK_PTR(ctx); // Don't show the position that caused a message to be shown // This has no setter (see libofx.h) ofx_show_position = false; d->m_hashes.clear(); qDebug("setup callback routines"); ofx_set_transaction_cb(ctx, ofxTransactionCallback, this); ofx_set_statement_cb(ctx, ofxStatementCallback, this); ofx_set_account_cb(ctx, ofxAccountCallback, this); ofx_set_security_cb(ctx, ofxSecurityCallback, this); ofx_set_status_cb(ctx, ofxStatusCallback, this); qDebug("process data"); libofx_proc_file(ctx, filename_deep, AUTODETECT); qDebug("process data done"); libofx_free_context(ctx); if (d->m_valid) { d->m_fatalerror.clear(); d->m_valid = storeStatements(d->m_statementlist); } return d->m_valid; } QString OFXImporter::lastError() const { if (d->m_errors.count() == 0) return d->m_fatalerror; return d->m_errors.join(QStringLiteral("

")); } /* __________________________________________________________________________ * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA * * Static callbacks for LibOFX * * YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY */ int OFXImporter::ofxTransactionCallback(struct OfxTransactionData data, void * pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); MyMoneyStatement& s = pofx->back(); MyMoneyStatement::Transaction t; if (data.date_posted_valid) { QDateTime dt; dt.setTime_t(data.date_posted - pofx->d->m_timestampOffset * 60); t.m_datePosted = dt.date(); } else if (data.date_initiated_valid) { QDateTime dt; dt.setTime_t(data.date_initiated - pofx->d->m_timestampOffset * 60); t.m_datePosted = dt.date(); } if (t.m_datePosted.isValid()) { // verify the transaction date is one we want if (t.m_datePosted < pofx->d->m_updateStartDate) { //kDebug(0) << "discarding transaction dated" << qPrintable(t.m_datePosted.toString(Qt::ISODate)); return 0; } } if (data.amount_valid) { t.m_amount = MyMoneyMoney(data.amount, 1000); } if (data.check_number_valid) { t.m_strNumber = QString::fromUtf8(data.check_number); } unsigned long h; QString tmpString; // in case the unique transaction id source is yet unknown we // use the global preset UniqueTransactionIdSource idSource = pofx->d->m_uniqueIdSource; if (idSource == UniqueIdUnknown) { idSource = defaultIdSource(); } switch (idSource) { default: case UniqueIdOfx: if (data.fi_id_valid) { t.m_strBankID = QStringLiteral("ID ") + QString::fromUtf8(data.fi_id); } else if (data.reference_number_valid) { t.m_strBankID = QStringLiteral("REF ") + QString::fromUtf8(data.reference_number); } break; case UniqueIdKMyMoney: if (data.payee_id_valid) { tmpString = QString::fromUtf8(data.payee_id); } else if (data.name_valid) { tmpString = QString::fromUtf8(data.name); } else if (data.memo_valid) { tmpString = QString::fromUtf8(data.memo); } h = MyMoneyTransaction::hash(tmpString.trimmed()); if (data.memo_valid) h = MyMoneyTransaction::hash(QString::fromUtf8(data.memo), h); h = MyMoneyTransaction::hash(t.m_amount.toString(), h); // make hash value unique in case we don't have one already QString hashBase; hashBase.sprintf("%s-%07lx", qPrintable(t.m_datePosted.toString(Qt::ISODate)), h); int idx = 1; QString hash; for (;;) { hash = QString("%1-%2").arg(hashBase).arg(idx); if (!pofx->d->m_hashes.contains(hash)) { pofx->d->m_hashes += hash; break; } ++idx; } t.m_strBankID = QString("KMM %1").arg(hash); break; } // Decide whether to use NAME, PAYEEID or MEMO to construct the payee bool validity[3] = {false, false, false}; QStringList values; switch (pofx->d->m_preferName) { case OFXImporter::Private::PreferId: // PAYEEID default: validity[0] = data.payee_id_valid; validity[1] = data.name_valid; validity[2] = data.memo_valid; values += QString::fromUtf8(data.payee_id); values += QString::fromUtf8(data.name); values += QString::fromUtf8(data.memo); break; case OFXImporter::Private::PreferName: // NAME validity[0] = data.name_valid; validity[1] = data.payee_id_valid; validity[2] = data.memo_valid; values += QString::fromUtf8(data.name); values += QString::fromUtf8(data.payee_id); values += QString::fromUtf8(data.memo); break; case OFXImporter::Private::PreferMemo: // MEMO validity[0] = data.memo_valid; validity[1] = data.payee_id_valid; validity[2] = data.name_valid; values += QString::fromUtf8(data.memo); values += QString::fromUtf8(data.payee_id); values += QString::fromUtf8(data.name); break; } // for investment transactions we don't use the meme as payee if (data.invtransactiontype_valid) { values.clear(); validity[0] = data.payee_id_valid; validity[1] = data.name_valid; validity[2] = false; values += QString::fromUtf8(data.payee_id); values += QString::fromUtf8(data.name); } for (int idx = 0; idx < 3; ++idx) { if (validity[idx]) { t.m_strPayee = values[idx]; break; } } // extract memo field if we haven't used it as payee if ((data.memo_valid) && (pofx->d->m_preferName != OFXImporter::Private::PreferMemo)) { t.m_strMemo = QString::fromUtf8(data.memo); } // If the payee or memo fields are blank, set them to // the other one which is NOT blank. (acejones) if (t.m_strPayee.isEmpty()) { // But we only create a payee for non-investment transactions (ipwizard) if (! t.m_strMemo.isEmpty() && data.invtransactiontype_valid == false) t.m_strPayee = t.m_strMemo; } else { if (t.m_strMemo.isEmpty()) t.m_strMemo = t.m_strPayee; } if (data.security_data_valid) { struct OfxSecurityData* secdata = data.security_data_ptr; if (secdata->ticker_valid) { t.m_strSymbol = QString::fromUtf8(secdata->ticker); } if (secdata->secname_valid) { t.m_strSecurity = QString::fromUtf8(secdata->secname); } } t.m_shares = MyMoneyMoney(); if (data.units_valid) { t.m_shares = MyMoneyMoney(data.units, 100000).reduce(); } t.m_price = MyMoneyMoney(); if (data.unitprice_valid) { t.m_price = MyMoneyMoney(data.unitprice, 100000).reduce(); } t.m_fees = MyMoneyMoney(); if (data.fees_valid) { t.m_fees += MyMoneyMoney(data.fees, 1000).reduce(); } if (data.commission_valid) { t.m_fees += MyMoneyMoney(data.commission, 1000).reduce(); } bool unhandledtype = false; QString type; if (data.invtransactiontype_valid) { switch (data.invtransactiontype) { case OFX_BUYDEBT: case OFX_BUYMF: case OFX_BUYOPT: case OFX_BUYOTHER: case OFX_BUYSTOCK: t.m_eAction = eMyMoney::Transaction::Action::Buy; break; case OFX_REINVEST: t.m_eAction = eMyMoney::Transaction::Action::ReinvestDividend; break; case OFX_SELLDEBT: case OFX_SELLMF: case OFX_SELLOPT: case OFX_SELLOTHER: case OFX_SELLSTOCK: t.m_eAction = eMyMoney::Transaction::Action::Sell; break; case OFX_INCOME: t.m_eAction = eMyMoney::Transaction::Action::CashDividend; // NOTE: With CashDividend, the amount of the dividend should // be in data.amount. Since I've never seen an OFX file with // cash dividends, this is an assumption on my part. (acejones) break; // // These types are all not handled. We will generate a warning for them. // case OFX_CLOSUREOPT: unhandledtype = true; type = QStringLiteral("CLOSUREOPT (Close a position for an option)"); break; case OFX_INVEXPENSE: unhandledtype = true; type = QStringLiteral("INVEXPENSE (Misc investment expense that is associated with a specific security)"); break; case OFX_JRNLFUND: unhandledtype = true; type = QStringLiteral("JRNLFUND (Journaling cash holdings between subaccounts within the same investment account)"); break; case OFX_MARGININTEREST: unhandledtype = true; type = QStringLiteral("MARGININTEREST (Margin interest expense)"); break; case OFX_RETOFCAP: unhandledtype = true; type = QStringLiteral("RETOFCAP (Return of capital)"); break; case OFX_SPLIT: unhandledtype = true; type = QStringLiteral("SPLIT (Stock or mutial fund split)"); break; case OFX_TRANSFER: unhandledtype = true; type = QStringLiteral("TRANSFER (Transfer holdings in and out of the investment account)"); break; default: unhandledtype = true; type = QString("UNKNOWN %1").arg(data.invtransactiontype); break; } } else t.m_eAction = eMyMoney::Transaction::Action::None; // In the case of investment transactions, the 'total' is supposed to the total amount // of the transaction. units * unitprice +/- commission. Easy, right? Sadly, it seems // some ofx creators do not follow this in all circumstances. Therefore, we have to double- // check the total here and adjust it if it's wrong. #if 0 // Even more sadly, this logic is BROKEN. It consistently results in bogus total // values, because of rounding errors in the price. A more through solution would // be to test if the comission alone is causing a discrepency, and adjust in that case. if (data.invtransactiontype_valid && data.unitprice_valid) { double proper_total = t.m_dShares * data.unitprice + t.m_moneyFees; if (proper_total != t.m_moneyAmount) { pofx->addWarning(QString("Transaction %1 has an incorrect total of %2. Using calculated total of %3 instead.").arg(t.m_strBankID).arg(t.m_moneyAmount).arg(proper_total)); t.m_moneyAmount = proper_total; } } #endif if (unhandledtype) pofx->addWarning(QString("Transaction %1 has an unsupported type (%2).").arg(t.m_strBankID, type)); else s.m_listTransactions += t; // kDebug(2) << Q_FUNC_INFO << "return 0 "; return 0; } int OFXImporter::ofxStatementCallback(struct OfxStatementData data, void* pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); MyMoneyStatement& s = pofx->back(); pofx->setValid(); if (data.currency_valid) { s.m_strCurrency = QString::fromUtf8(data.currency); } if (data.account_id_valid) { s.m_strAccountNumber = QString::fromUtf8(data.account_id); } if (data.date_start_valid) { QDateTime dt; dt.setTime_t(data.date_start - pofx->d->m_timestampOffset * 60); s.m_dateBegin = dt.date(); } if (data.date_end_valid) { QDateTime dt; dt.setTime_t(data.date_end - pofx->d->m_timestampOffset * 60); s.m_dateEnd = dt.date(); } if (data.ledger_balance_valid && data.ledger_balance_date_valid) { s.m_closingBalance = MyMoneyMoney(data.ledger_balance); QDateTime dt; dt.setTime_t(data.ledger_balance_date); s.m_dateEnd = dt.date(); } // kDebug(2) << Q_FUNC_INFO << " return 0"; return 0; } int OFXImporter::ofxAccountCallback(struct OfxAccountData data, void * pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); pofx->addnew(); MyMoneyStatement& s = pofx->back(); // Having any account at all makes an ofx statement valid pofx->d->m_valid = true; // new account means new hashes pofx->d->m_hashes.clear(); if (data.account_id_valid) { s.m_strAccountName = QString::fromUtf8(data.account_name); s.m_strAccountNumber = QString::fromUtf8(data.account_id); } if (data.bank_id_valid) { s.m_strRoutingNumber = QString::fromUtf8(data.bank_id); } if (data.broker_id_valid) { s.m_strRoutingNumber = QString::fromUtf8(data.broker_id); } if (data.currency_valid) { s.m_strCurrency = QString::fromUtf8(data.currency); } if (data.account_type_valid) { switch (data.account_type) { case OfxAccountData::OFX_CHECKING : s.m_eType = eMyMoney::Statement::Type::Checkings; break; case OfxAccountData::OFX_SAVINGS : s.m_eType = eMyMoney::Statement::Type::Savings; break; case OfxAccountData::OFX_MONEYMRKT : s.m_eType = eMyMoney::Statement::Type::Investment; break; case OfxAccountData::OFX_CREDITLINE : s.m_eType = eMyMoney::Statement::Type::CreditCard; break; case OfxAccountData::OFX_CMA : s.m_eType = eMyMoney::Statement::Type::CreditCard; break; case OfxAccountData::OFX_CREDITCARD : s.m_eType = eMyMoney::Statement::Type::CreditCard; break; case OfxAccountData::OFX_INVESTMENT : s.m_eType = eMyMoney::Statement::Type::Investment; break; } } // ask KMyMoney for an account id // but only if we have any information to ask for if (!s.m_strAccountNumber.isEmpty() || !s.m_strRoutingNumber.isEmpty()) { s.m_accountId = pofx->account(QStringLiteral("kmmofx-acc-ref"), QString("%1-%2").arg(s.m_strRoutingNumber, s.m_strAccountNumber)).id(); } // copy over the securities s.m_listSecurities = pofx->d->m_securitylist; // kDebug(2) << Q_FUNC_INFO << " return 0"; return 0; } int OFXImporter::ofxSecurityCallback(struct OfxSecurityData data, void* pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); MyMoneyStatement::Security sec; if (data.unique_id_valid) { sec.m_strId = QString::fromUtf8(data.unique_id); } if (data.secname_valid) { sec.m_strName = QString::fromUtf8(data.secname); } if (data.ticker_valid) { sec.m_strSymbol = QString::fromUtf8(data.ticker); } pofx->d->m_securitylist += sec; return 0; } int OFXImporter::ofxStatusCallback(struct OfxStatusData data, void * pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); QString message; // if we got this far, we know we were able to parse the file. // so if it fails after here it can only because there were no actual // accounts in the file! pofx->d->m_fatalerror = i18n("No accounts found."); if (data.ofx_element_name_valid) message.prepend(QString("%1: ").arg(QString::fromUtf8(data.ofx_element_name))); if (data.code_valid) message += QString("%1 (Code %2): %3").arg(QString::fromUtf8(data.name)).arg(data.code).arg(QString::fromUtf8(data.description)); if (data.server_message_valid) message += QString(" (%1)").arg(QString::fromUtf8(data.server_message)); if (data.severity_valid) { switch (data.severity) { case OfxStatusData::INFO: pofx->addInfo(message); break; case OfxStatusData::ERROR: pofx->addError(message); break; case OfxStatusData::WARN: pofx->addWarning(message); break; default: pofx->addWarning(message); pofx->addWarning(QStringLiteral("Previous message was an unknown type. 'WARNING' was assumed.")); break; } } // kDebug(2) << Q_FUNC_INFO << " return 0 "; return 0; } QStringList OFXImporter::importStatement(const MyMoneyStatement &s) { qDebug("OfxImporterPlugin::importStatement start"); return statementInterface()->import(s, false); } MyMoneyAccount OFXImporter::account(const QString& key, const QString& value) const { return statementInterface()->account(key, value); } void OFXImporter::protocols(QStringList& protocolList) const { protocolList.clear(); protocolList << QStringLiteral("OFX"); } QWidget* OFXImporter::accountConfigTab(const MyMoneyAccount& acc, QString& name) { name = i18n("Online settings"); d->m_statusDlg = new KOnlineBankingStatus(acc, 0); return d->m_statusDlg; } MyMoneyKeyValueContainer OFXImporter::onlineBankingSettings(const MyMoneyKeyValueContainer& current) { MyMoneyKeyValueContainer kvp(current); // keep the provider name in sync with the one found in kmm_ofximport.desktop kvp[QStringLiteral("provider")] = objectName().toLower(); if (d->m_statusDlg) { kvp.deletePair(QStringLiteral("appId")); kvp.deletePair(QStringLiteral("kmmofx-headerVersion")); kvp.deletePair(QStringLiteral("password")); d->m_wallet = openSynchronousWallet(); if (d->m_wallet && (d->m_wallet->hasFolder(KWallet::Wallet::PasswordFolder()) || d->m_wallet->createFolder(KWallet::Wallet::PasswordFolder())) && d->m_wallet->setFolder(KWallet::Wallet::PasswordFolder())) { QString key = OFX_PASSWORD_KEY(kvp.value(QStringLiteral("url")), kvp.value(QStringLiteral("uniqueId"))); if (d->m_statusDlg->m_storePassword->isChecked()) { d->m_wallet->writePassword(key, d->m_statusDlg->m_password->text()); } else { if (d->m_wallet->hasEntry(key)) { d->m_wallet->removeEntry(key); } } } else { if (d->m_statusDlg->m_storePassword->isChecked()) { kvp.setValue(QStringLiteral("password"), d->m_statusDlg->m_password->text()); } } if (!d->m_statusDlg->appId().isEmpty()) kvp.setValue(QStringLiteral("appId"), d->m_statusDlg->appId()); kvp.setValue(QStringLiteral("kmmofx-headerVersion"), d->m_statusDlg->headerVersion()); kvp.setValue(QStringLiteral("kmmofx-numRequestDays"), QString::number(d->m_statusDlg->m_numdaysSpin->value())); kvp.setValue(QStringLiteral("kmmofx-todayMinus"), QString::number(d->m_statusDlg->m_todayRB->isChecked())); kvp.setValue(QStringLiteral("kmmofx-lastUpdate"), QString::number(d->m_statusDlg->m_lastUpdateRB->isChecked())); kvp.setValue(QStringLiteral("kmmofx-pickDate"), QString::number(d->m_statusDlg->m_pickDateRB->isChecked())); kvp.setValue(QStringLiteral("kmmofx-specificDate"), d->m_statusDlg->m_specificDate->date().toString()); kvp.setValue(QStringLiteral("kmmofx-preferName"), QString::number(d->m_statusDlg->m_preferredPayee->currentIndex())); kvp.setValue(QStringLiteral("kmmofx-uniqueIdSource"), QString::number(d->m_statusDlg->m_uniqueTransactionId->currentIndex())); if (!d->m_statusDlg->m_clientUidEdit->text().isEmpty()) kvp.setValue(QStringLiteral("clientUid"), d->m_statusDlg->m_clientUidEdit->text()); else kvp.deletePair(QStringLiteral("clientUid")); int offset = d->constructTimeOffset(d->m_statusDlg->m_timestampOffset, d->m_statusDlg->m_timestampOffsetSign); if (offset == 0) { kvp.deletePair(QStringLiteral("kmmofx-timestampOffset")); } else { kvp.setValue(QStringLiteral("kmmofx-timestampOffset"), QString::number(offset)); } // get rid of pre 4.6 values kvp.deletePair(QStringLiteral("kmmofx-preferPayeeid")); } return kvp; } bool OFXImporter::mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& settings) { Q_UNUSED(acc); bool rc = false; QPointer wiz = new KOnlineBankingSetupWizard(0); if (wiz->isInit()) { if (wiz->exec() == QDialog::Accepted) { rc = wiz->chosenSettings(settings); } } delete wiz; return rc; } bool OFXImporter::updateAccount(const MyMoneyAccount& acc, bool moreAccounts) { Q_UNUSED(moreAccounts); qDebug("OfxImporterPlugin::updateAccount"); try { d->m_uniqueIdSource = UniqueIdUnknown; if (!acc.id().isEmpty()) { // Save the value of preferName to be used by ofxTransactionCallback d->m_preferName = static_cast(acc.onlineBankingSettings().value(QStringLiteral("kmmofx-preferName")).toInt()); if (acc.onlineBankingSettings().value(QStringLiteral("kmmofx-uniqueIdSource")).isEmpty()) d->m_uniqueIdSource = defaultIdSource(); else d->m_uniqueIdSource = static_cast(acc.onlineBankingSettings().value(QStringLiteral("kmmofx-uniqueIdSource")).toInt()); QPointer dlg = new KOfxDirectConnectDlg(acc); connect(dlg.data(), &KOfxDirectConnectDlg::statementReady, this, static_cast(&OFXImporter::slotImportFile)); // get the date of the earliest transaction that we are interested in // from the settings for this account MyMoneyKeyValueContainer settings = acc.onlineBankingSettings(); if (!settings.value(QStringLiteral("provider")).isEmpty()) { if ((settings.value(QStringLiteral("kmmofx-todayMinus")).toInt() != 0) && !settings.value(QStringLiteral("kmmofx-numRequestDays")).isEmpty()) { //kDebug(0) << "start date = today minus"; d->m_updateStartDate = QDate::currentDate().addDays(-settings.value(QStringLiteral("kmmofx-numRequestDays")).toInt()); } else if ((settings.value(QStringLiteral("kmmofx-lastUpdate")).toInt() != 0) && !acc.value(QStringLiteral("lastImportedTransactionDate")).isEmpty()) { //kDebug(0) << "start date = last update"; d->m_updateStartDate = QDate::fromString(acc.value(QStringLiteral("lastImportedTransactionDate")), Qt::ISODate); } else if ((settings.value(QStringLiteral("kmmofx-pickDate")).toInt() != 0) && !settings.value(QStringLiteral("kmmofx-specificDate")).isEmpty()) { //kDebug(0) << "start date = pick date"; d->m_updateStartDate = QDate::fromString(settings.value(QStringLiteral("kmmofx-specificDate"))); } else { //kDebug(0) << "start date = today - 2 months"; d->m_updateStartDate = QDate::currentDate().addMonths(-2); } } d->m_timestampOffset = settings.value("kmmofx-timestampOffset").toInt(); //kDebug(0) << "ofx plugin: account" << acc.name() << "earliest transaction date to process =" << qPrintable(d->m_updateStartDate.toString(Qt::ISODate)); if (dlg->init()) dlg->exec(); delete dlg; // reset the earliest-interesting-transaction date to the non-specific account setting d->m_updateStartDate = QDate(1900,1,1); d->m_timestampOffset = 0; } } catch (const MyMoneyException &e) { KMessageBox::information(0 , i18n("Error connecting to bank: %1", QString::fromLatin1(e.what()))); } return false; } void OFXImporter::slotImportFile(const QString& url) { qDebug("OfxImporterPlugin::slotImportFile"); if (!import(url)) { KMessageBox::error(0, QString("%1").arg(i18n("

Unable to import '%1' using the OFX importer plugin. The plugin returned the following error:

%2

", url, lastError())), i18n("Importing error")); } } bool OFXImporter::storeStatements(const QList &statements) { if (statements.isEmpty()) return true; auto ok = true; auto abort = false; // FIXME Deal with warnings/errors coming back from plugins /*if ( ofx.errors().count() ) { if ( KMessageBox::warningContinueCancelList(this,i18n("The following errors were returned from your bank"),ofx.errors(),i18n("OFX Errors")) == KMessageBox::Cancel ) abort = true; } if ( ofx.warnings().count() ) { if ( KMessageBox::warningContinueCancelList(this,i18n("The following warnings were returned from your bank"),ofx.warnings(),i18n("OFX Warnings"),KStandardGuiItem::cont(),"ofxwarnings") == KMessageBox::Cancel ) abort = true; }*/ qDebug("OfxImporterPlugin::storeStatements() with %d statements called", statements.count()); for (const auto& statement : statements) { if (abort) break; if (importStatement(statement).isEmpty()) ok = false; } if (!ok) KMessageBox::error(nullptr, i18n("Importing process terminated unexpectedly."), i18n("Failed to import all statements.")); return ok; } void OFXImporter::addnew() { d->m_statementlist.push_back(MyMoneyStatement()); } MyMoneyStatement& OFXImporter::back() { return d->m_statementlist.back(); } bool OFXImporter::isValid() const { return d->m_valid; } void OFXImporter::setValid() { d->m_valid = true; } void OFXImporter::addInfo(const QString& _msg) { d->m_infos += _msg; } void OFXImporter::addWarning(const QString& _msg) { d->m_warnings += _msg; } void OFXImporter::addError(const QString& _msg) { d->m_errors += _msg; } const QStringList& OFXImporter::infos() const // krazy:exclude=spelling { return d->m_infos; } const QStringList& OFXImporter::warnings() const { return d->m_warnings; } const QStringList& OFXImporter::errors() const { return d->m_errors; } K_PLUGIN_FACTORY_WITH_JSON(OFXImporterFactory, "ofximporter.json", registerPlugin();) #include "ofximporter.moc" diff --git a/kmymoney/plugins/qif/export/qifexporter.cpp b/kmymoney/plugins/qif/export/qifexporter.cpp index ad73d93db..a2cd72325 100644 --- a/kmymoney/plugins/qif/export/qifexporter.cpp +++ b/kmymoney/plugins/qif/export/qifexporter.cpp @@ -1,98 +1,99 @@ /* * Copyright 2017 Łukasz Wojniłowicz * Copyright 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 . */ +#include #include "qifexporter.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kexportdlg.h" #include "mymoneyqifwriter.h" #include "viewinterface.h" QIFExporter::QIFExporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "qifexporter"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args); const auto componentName = QLatin1String("qifexporter"); const auto rcFileName = QLatin1String("qifexporter.rc"); setComponentName(componentName, i18n("QIF exporter")); #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 createActions(); // For information, announce that we have been loaded. qDebug("Plugins: qifexporter loaded"); } QIFExporter::~QIFExporter() { qDebug("Plugins: qifexporter unloaded"); } void QIFExporter::createActions() { const auto &kpartgui = QStringLiteral("file_export_qif"); m_action = actionCollection()->addAction(kpartgui); m_action->setText(i18n("QIF...")); connect(m_action, &QAction::triggered, this, &QIFExporter::slotQifExport); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } void QIFExporter::slotQifExport() { m_action->setEnabled(false); QPointer dlg = new KExportDlg(nullptr); if (dlg->exec() == QDialog::Accepted && dlg != nullptr) { // if (okToWriteFile(QUrl::fromLocalFile(dlg->filename()))) { MyMoneyQifWriter writer; connect(&writer, SIGNAL(signalProgress(int,int)), this, SLOT(slotStatusProgressBar(int,int))); writer.write(dlg->filename(), dlg->profile(), dlg->accountId(), dlg->accountSelected(), dlg->categorySelected(), dlg->startDate(), dlg->endDate()); // } } delete dlg; m_action->setEnabled(true); } K_PLUGIN_FACTORY_WITH_JSON(QIFExporterFactory, "qifexporter.json", registerPlugin();) #include "qifexporter.moc" diff --git a/kmymoney/plugins/qif/import/qifimporter.cpp b/kmymoney/plugins/qif/import/qifimporter.cpp index 082c15447..3491386ff 100644 --- a/kmymoney/plugins/qif/import/qifimporter.cpp +++ b/kmymoney/plugins/qif/import/qifimporter.cpp @@ -1,123 +1,124 @@ /* * Copyright 2017 Łukasz Wojniłowicz * Copyright 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 . */ +#include #include "qifimporter.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kimportdlg.h" #include "mymoneyqifreader.h" #include "statementinterface.h" #include "viewinterface.h" class MyMoneyStatement; QIFImporter::QIFImporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "qifimporter"/*must be the same as X-KDE-PluginInfo-Name*/), m_qifReader(nullptr) { Q_UNUSED(args); const auto componentName = QLatin1String("qifimporter"); const auto rcFileName = QLatin1String("qifimporter.rc"); setComponentName(componentName, i18n("QIF importer")); #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('/') + rcFileName; setLocalXMLFile(localRcFilePath); #else setXMLFile(rcFileName); #endif createActions(); // For information, announce that we have been loaded. qDebug("Plugins: qifimporter loaded"); } QIFImporter::~QIFImporter() { delete m_qifReader; qDebug("Plugins: qifimporter unloaded"); } void QIFImporter::createActions() { const auto &kpartgui = QStringLiteral("file_import_qif"); m_action = actionCollection()->addAction(kpartgui); m_action->setText(i18n("QIF...")); connect(m_action, &QAction::triggered, this, &QIFImporter::slotQifImport); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } void QIFImporter::slotQifImport() { QPointer dlg = new KImportDlg(nullptr); if (dlg->exec() == QDialog::Accepted && dlg != nullptr) { m_action->setEnabled(false); delete m_qifReader; m_qifReader = new MyMoneyQifReader; statementInterface()->resetMessages(); connect(m_qifReader, &MyMoneyQifReader::statementsReady, this, &QIFImporter::slotGetStatements); m_qifReader->setURL(dlg->file()); m_qifReader->setProfile(dlg->profile()); m_qifReader->setCategoryMapping(dlg->m_typeComboBox->currentIndex() == 0); if (!m_qifReader->startImport()) { delete m_qifReader; statementInterface()->showMessages(0); m_action->setEnabled(true); } } delete dlg; } bool QIFImporter::slotGetStatements(const QList &statements) { auto ret = true; for (const auto& statement : statements) { const auto singleImportSummary = statementInterface()->import(statement); if (singleImportSummary.isEmpty()) ret = false; } // inform the user about the result of the operation statementInterface()->showMessages(statements.count()); // allow further QIF imports m_action->setEnabled(true); return ret; } K_PLUGIN_FACTORY_WITH_JSON(QIFImporterFactory, "qifimporter.json", registerPlugin();) #include "qifimporter.moc" diff --git a/kmymoney/plugins/weboob/weboob.cpp b/kmymoney/plugins/weboob/weboob.cpp index d23cd7427..a778c4536 100644 --- a/kmymoney/plugins/weboob/weboob.cpp +++ b/kmymoney/plugins/weboob/weboob.cpp @@ -1,238 +1,239 @@ /* * Copyright 2014-2015 Romain Bignon * Copyright 2014-2015 Florent Fourcot * Copyright 2016 Christian David * Copyright 2017 Łukasz Wojniłowicz * Copyright 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 . */ +#include #include "weboob.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #ifdef IS_APPIMAGE #include #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mapaccountwizard.h" #include "accountsettings.h" #include "weboobinterface.h" #include "mymoneyaccount.h" #include "mymoneykeyvaluecontainer.h" #include "mymoneystatement.h" #include "statementinterface.h" class WeboobPrivate { public: WeboobPrivate() { } ~WeboobPrivate() { } WeboobInterface weboob; QFutureWatcher watcher; std::unique_ptr progress; AccountSettings* accountSettings; }; Weboob::Weboob(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "weboob"), d_ptr(new WeboobPrivate) { Q_UNUSED(args) const auto componentName = QLatin1String("weboob"); const auto rcFileName = QLatin1String("weboob.rc"); setComponentName(componentName, i18n("Weboob")); #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 qDebug("Plugins: weboob loaded"); } Weboob::~Weboob() { Q_D(Weboob); delete d; qDebug("Plugins: weboob unloaded"); } void Weboob::plug() { Q_D(Weboob); connect(&d->watcher, &QFutureWatcher::finished, this, &Weboob::gotAccount); } void Weboob::unplug() { Q_D(Weboob); disconnect(&d->watcher, &QFutureWatcher::finished, this, &Weboob::gotAccount); } void Weboob::protocols(QStringList& protocolList) const { protocolList << "weboob"; } QWidget* Weboob::accountConfigTab(const MyMoneyAccount& account, QString& tabName) { Q_D(Weboob); const MyMoneyKeyValueContainer& kvp = account.onlineBankingSettings(); tabName = i18n("Weboob configuration"); d->accountSettings = new AccountSettings(account, 0); d->accountSettings->loadUi(kvp); return d->accountSettings; } MyMoneyKeyValueContainer Weboob::onlineBankingSettings(const MyMoneyKeyValueContainer& current) { Q_D(Weboob); MyMoneyKeyValueContainer kvp(current); kvp["provider"] = objectName().toLower(); if (d->accountSettings) { d->accountSettings->loadKvp(kvp); } return kvp; } bool Weboob::mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& onlineBankingSettings) { Q_D(Weboob); Q_UNUSED(acc); bool rc = false; QPointer w = new MapAccountWizard(nullptr, &d->weboob); if (w->exec() == QDialog::Accepted && w != nullptr) { onlineBankingSettings.setValue("wb-backend", w->currentBackend()); onlineBankingSettings.setValue("wb-id", w->currentAccount()); onlineBankingSettings.setValue("wb-max", "0"); rc = true; } delete w; return rc; } bool Weboob::updateAccount(const MyMoneyAccount& kacc, bool moreAccounts) { Q_D(Weboob); Q_UNUSED(moreAccounts); QString bname = kacc.onlineBankingSettings().value("wb-backend"); QString id = kacc.onlineBankingSettings().value("wb-id"); QString max = kacc.onlineBankingSettings().value("wb-max"); d->progress = std::make_unique(nullptr); d->progress->setWindowTitle(i18n("Connecting to bank...")); d->progress->setLabelText(i18n("Retrieving transactions...")); d->progress->setModal(true); d->progress->setCancelButton(nullptr); d->progress->setMinimum(0); d->progress->setMaximum(0); d->progress->setMinimumDuration(0); QFuture future = QtConcurrent::run(&d->weboob, &WeboobInterface::getAccount, bname, id, max); d->watcher.setFuture(future); d->progress->exec(); d->progress.reset(); return true; } void Weboob::gotAccount() { Q_D(Weboob); WeboobInterface::Account acc = d->watcher.result(); MyMoneyAccount kacc = statementInterface()->account("wb-id", acc.id); MyMoneyStatement ks; ks.m_accountId = kacc.id(); ks.m_strAccountName = acc.name; ks.m_closingBalance = acc.balance; if (acc.transactions.length() > 0) ks.m_dateEnd = acc.transactions.front().date; #if 0 switch (acc.type) { case Weboob::Account::TYPE_CHECKING: ks.m_eType = MyMoneyStatement::etCheckings; break; case Weboob::Account::TYPE_SAVINGS: ks.m_eType = MyMoneyStatement::etSavings; break; case Weboob::Account::TYPE_MARKET: ks.m_eType = MyMoneyStatement::etInvestment; break; case Weboob::Account::TYPE_DEPOSIT: case Weboob::Account::TYPE_LOAN: case Weboob::Account::TYPE_JOINT: case Weboob::Account::TYPE_UNKNOWN: break; } #endif for (QListIterator it(acc.transactions); it.hasNext();) { WeboobInterface::Transaction tr = it.next(); MyMoneyStatement::Transaction kt; kt.m_strBankID = QLatin1String("ID ") + tr.id; kt.m_datePosted = tr.rdate; kt.m_amount = tr.amount; kt.m_strMemo = tr.raw; kt.m_strPayee = tr.label; ks.m_listTransactions += kt; } statementInterface()->import(ks); d->progress->hide(); } K_PLUGIN_FACTORY_WITH_JSON(WeboobFactory, "weboob.json", registerPlugin();) #include "weboob.moc"