diff --git a/kmymoney/CMakeLists.txt b/kmymoney/CMakeLists.txt index 7c77dd2d0..3860461da 100644 --- a/kmymoney/CMakeLists.txt +++ b/kmymoney/CMakeLists.txt @@ -1,213 +1,209 @@ include(ECMAddAppIcon) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/settings/ ${CMAKE_CURRENT_BINARY_DIR}/settings/ ${CMAKE_CURRENT_BINARY_DIR}/dialogs/ ${CMAKE_CURRENT_SOURCE_DIR}/widgets/ ${CMAKE_CURRENT_BINARY_DIR}/widgets/ ${CMAKE_CURRENT_SOURCE_DIR}/mymoney/ ${CMAKE_CURRENT_SOURCE_DIR}/mymoney/storage/ ${CMAKE_CURRENT_SOURCE_DIR}/plugins/ ${CMAKE_CURRENT_BINARY_DIR}/plugins/ ${CMAKE_CURRENT_SOURCE_DIR}/views/ ${CMAKE_CURRENT_SOURCE_DIR}/dialogs/ ${CMAKE_CURRENT_SOURCE_DIR}/converter/ ${CMAKE_CURRENT_BINARY_DIR}/dialogs/settings/ ${CMAKE_CURRENT_BINARY_DIR}/mymoney/storage/ ${CMAKE_CURRENT_BINARY_DIR}/mymoney/ ${CMAKE_CURRENT_SOURCE_DIR}/wizards/endingbalancedlg/ ${CMAKE_CURRENT_BINARY_DIR}/wizards/endingbalancedlg/ ${CMAKE_CURRENT_SOURCE_DIR}/wizards/newinvestmentwizard/ ${CMAKE_CURRENT_BINARY_DIR}/wizards/newinvestmentwizard/ ${CMAKE_CURRENT_SOURCE_DIR}/wizards/newloanwizard/ ${CMAKE_CURRENT_BINARY_DIR}/wizards/newloanwizard/ ${CMAKE_CURRENT_SOURCE_DIR}/wizards/wizardpages/ ${CMAKE_CURRENT_SOURCE_DIR}/models/ ${CMAKE_CURRENT_BINARY_DIR}/models/ ${CMAKE_CURRENT_SOURCE_DIR}/icons/ ${CMAKE_CURRENT_BINARY_DIR}/icons/ ${CMAKE_CURRENT_SOURCE_DIR}/menus/ ${CMAKE_CURRENT_BINARY_DIR}/menus/ - ${CMAKE_CURRENT_BINARY_DIR}/payeeidentifier/ibanandbic/widgets/ # TODO: this line should be moved to the target it belongs - ${CMAKE_CURRENT_BINARY_DIR}/payeeidentifier/ibanandbic/ - ${CMAKE_CURRENT_BINARY_DIR}/payeeidentifier/nationalaccount/ ) add_subdirectory( mymoney ) add_subdirectory( settings ) add_subdirectory( models ) add_subdirectory( plugins ) add_subdirectory( widgets ) add_subdirectory( dialogs ) add_subdirectory( views ) add_subdirectory( converter ) add_subdirectory( wizards ) add_subdirectory( pics ) add_subdirectory( html ) add_subdirectory( templates ) add_subdirectory( misc ) -add_subdirectory( payeeidentifier ) add_subdirectory( icons ) add_subdirectory( menus ) if(BUILD_TESTING) add_subdirectory( tests ) endif() set( _HEADERS kmymoneyutils.h ) ########### common code (kmymoney_common) STATIC ############### # will be linked into kmymoney, kmymoneytest, and libkmymoney.so set( kmymoney_common_SRCS kmymoneyutils.cpp kstartuplogo.cpp kcreditswindow.cpp ) add_library(kmymoney_common STATIC ${kmymoney_common_SRCS}) target_link_libraries(kmymoney_common PUBLIC Qt5::Core KF5::ConfigGui KF5::WidgetsAddons KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets KF5::KIONTLM Alkimia::alkimia kmm_mymoney kmm_utils_webconnect kmm_utils_platformtools PRIVATE KF5::I18n kmm_settings ) # must build kmymoney/transactionsortoption.h # from transactionsortoption.ui first add_dependencies(kmymoney_common generate_base_ui_srcs kmm_settings) add_dependencies(wizardpages widgets) add_dependencies(dialogs widgets) if(USE_MODELTEST) set( kmymoney_common_LIBS ${kmymoney_common_LIBS} ${QT_QTTEST_LIBRARY}) endif(USE_MODELTEST) # remove these generated files, they are in the way and leftovers from 5.0 if(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/kmymoneysettings.h) file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/kmymoneysettings.h) endif() if(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/kmymoneysettings.cpp) file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/kmymoneysettings.cpp) endif() ########### kmymoney executable ############### set( kmymoney_SRCS main.cpp kmymoney.cpp pluginloader.cpp ) qt5_add_dbus_adaptor(kmymoney_SRCS org.kde.kmymoney.xml kmymoney.h KMyMoneyApp) qt5_add_resources(kmymoney_SRCS kmymoney.qrc) # collect application icons file(GLOB_RECURSE KMYMONEY_APP_ICONS "${CMAKE_CURRENT_SOURCE_DIR}/icons/kmymoney/apps/*.png") # add icons to application sources, to have them bundled ecm_add_app_icon(kmymoney_SRCS ICONS ${KMYMONEY_APP_ICONS}) add_executable( kmymoney ${kmymoney_SRCS} ) if (ENABLE_SQLCIPHER) message( STATUS " SQLCIPHER INCLUDE : " ${SQLCIPHER_INCLUDE_DIRS}) #just for testing it on MS Windows target_compile_definitions(kmymoney PRIVATE SQLITE_HAS_CODEC SQLITE_TEMP_STORE=2) #otherwise "fatal error: 'sqlite3.h' file not found" if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD" OR CMAKE_SYSTEM_NAME MATCHES "Windows") target_include_directories(kmymoney PRIVATE ${SQLCIPHER_INCLUDE_DIRS}) endif() endif() target_link_libraries(kmymoney PUBLIC views kmymoney_base kmymoney_common newuserwizard newaccountwizard newinvestmentwizard newloanwizard endingbalancedlg wizardpages dialogs widgets settings converter kmm_models kmm_settings kmm_menus kmm_widgets kmm_mymoney interfaces kmm_plugin Qt5::Core KF5::Archive KF5::ConfigGui KF5::WidgetsAddons KF5::KIOCore KF5::CoreAddons KChart $<$:Qt5::Test> $<$:KF5::Holidays> $<$:KF5::Activities> PRIVATE $<$:sqlcipher> ) # own plist magic for mac os if(APPLE) # own plist template set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/MacOSXBundleInfo.plist.in) # the MacOSX bundle display name property (CFBundleDisplayName) is not currently supported by cmake, # so has to be set for all targets in this cmake file set(MACOSX_BUNDLE_DISPLAY_NAME KMyMoney) set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.KMyMoney") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KMyMoney") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_DISPLAY_NAME "KMyMoney") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_INFO_STRING "KMyMoney - Personal Finances Manager") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_LONG_VERSION_STRING "KMyMoney ${KDE_APPLICATIONS_VERSION}") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION "${KDE_APPLICATIONS_VERSION}") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_COPYRIGHT "2000-2016 The KMyMoney Authors") endif() ########### install files ############### install(TARGETS kmymoney ${INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney ) install(FILES org.kde.kmymoney.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install(FILES org.kde.kmymoney.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) install(FILES x-kmymoney.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) install(FILES tips DESTINATION ${CMAKE_INSTALL_DATADIR}/kmymoney) #UPDATE_XDG_MIMETYPES(${XDG_MIME_INSTALL_DIR}) diff --git a/kmymoney/models/payeeidentifiercontainermodel.cpp b/kmymoney/models/payeeidentifiercontainermodel.cpp index 79d458e90..1bace1e1b 100644 --- a/kmymoney/models/payeeidentifiercontainermodel.cpp +++ b/kmymoney/models/payeeidentifiercontainermodel.cpp @@ -1,132 +1,138 @@ /* * Copyright 2014-2015 Christian Dávid * * 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 "payeeidentifiercontainermodel.h" -#include "payeeidentifier/payeeidentifierloader.h" #include "payeeidentifier/payeeidentifier.h" +#include "payeeidentifier/ibanbic/ibanbic.h" +#include "payeeidentifier/nationalaccount/nationalaccount.h" #include payeeIdentifierContainerModel::payeeIdentifierContainerModel(QObject* parent) : QAbstractListModel(parent), m_data(QSharedPointer()) { } QVariant payeeIdentifierContainerModel::data(const QModelIndex& index, int role) const { // Needed for the selection box and it prevents a crash if index is out of range if (m_data.isNull() || index.row() >= rowCount(index.parent()) - 1) return QVariant(); const ::payeeIdentifier ident = m_data->payeeIdentifiers().at(index.row()); if (role == payeeIdentifier) { return QVariant::fromValue< ::payeeIdentifier >(ident); } else if (ident.isNull()) { return QVariant(); } else if (role == payeeIdentifierType) { return ident.iid(); } else if (role == Qt::DisplayRole) { // The custom delegates won't ask for this role return QVariant::fromValue(i18n("The plugin to show this information could not be found.")); } return QVariant(); } bool payeeIdentifierContainerModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!m_data.isNull() && role == payeeIdentifier) { ::payeeIdentifier ident = value.value< ::payeeIdentifier >(); if (index.row() == rowCount(index.parent()) - 1) { // The new row will be the last but one beginInsertRows(index.parent(), index.row() - 1, index.row() - 1); m_data->addPayeeIdentifier(ident); endInsertRows(); } else { m_data->modifyPayeeIdentifier(index.row(), ident); emit dataChanged(createIndex(index.row(), 0), createIndex(index.row(), 0)); } return true; } return QAbstractItemModel::setData(index, value, role); } Qt::ItemFlags payeeIdentifierContainerModel::flags(const QModelIndex& index) const { - Qt::ItemFlags flags = QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled; - const QString type = data(index, payeeIdentifierType).toString(); + static const QVector editableDelegates { + payeeIdentifiers::ibanBic::staticPayeeIdentifierIid(), + payeeIdentifiers::nationalAccount::staticPayeeIdentifierIid() + }; + auto flags = QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled; + const auto type = data(index, payeeIdentifierType).toString(); + // type.isEmpty() means the type selection can be shown - if (!type.isEmpty() && payeeIdentifierLoader::instance()->hasItemEditDelegate(type)) + if (!type.isEmpty() && editableDelegates.contains(type)) flags |= Qt::ItemIsEditable; return flags; } int payeeIdentifierContainerModel::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent); if (m_data.isNull()) return 0; // Always a row more which creates new entries return m_data->payeeIdentifiers().count() + 1; } /** @brief unused at the moment */ bool payeeIdentifierContainerModel::insertRows(int row, int count, const QModelIndex& parent) { Q_UNUSED(row); Q_UNUSED(count); Q_UNUSED(parent); return false; } bool payeeIdentifierContainerModel::removeRows(int row, int count, const QModelIndex& parent) { if (m_data.isNull()) return false; if (count < 1 || row + count >= rowCount(parent)) return false; beginRemoveRows(parent, row, row + count - 1); for (int i = row; i < row + count; ++i) { m_data->removePayeeIdentifier(i); } endRemoveRows(); return true; } void payeeIdentifierContainerModel::setSource(const MyMoneyPayeeIdentifierContainer data) { beginResetModel(); m_data = QSharedPointer(new MyMoneyPayeeIdentifierContainer(data)); endResetModel(); } void payeeIdentifierContainerModel::closeSource() { beginResetModel(); m_data = QSharedPointer(); endResetModel(); } QList< ::payeeIdentifier > payeeIdentifierContainerModel::identifiers() const { if (m_data.isNull()) return QList< ::payeeIdentifier >(); return m_data->payeeIdentifiers(); } diff --git a/kmymoney/mymoney/CMakeLists.txt b/kmymoney/mymoney/CMakeLists.txt index 8f7bb45dc..5d7fa1558 100644 --- a/kmymoney/mymoney/CMakeLists.txt +++ b/kmymoney/mymoney/CMakeLists.txt @@ -1,118 +1,117 @@ add_subdirectory( storage ) add_subdirectory( payeeidentifier ) ########### next target ############### set(kmm_mymoney_LIB_SRCS mymoneymoney.cpp mymoneyfinancialcalculator.cpp mymoneytransactionfilter.cpp mymoneyfile.cpp mymoneykeyvaluecontainer.cpp mymoneyobject.cpp mymoneypayeeidentifiercontainer.cpp mymoneysplit.cpp mymoneyinstitution.cpp mymoneyinvesttransaction.cpp mymoneyutils.cpp mymoneysecurity.cpp mymoneytransaction.cpp mymoneyschedule.cpp mymoneypayee.cpp mymoneytracer.cpp mymoneytag.cpp mymoneycategory.cpp mymoneycostcenter.cpp mymoneyaccount.cpp mymoneyaccountloan.cpp mymoneyreport.cpp mymoneystatement.cpp mymoneyprice.cpp mymoneybudget.cpp mymoneyforecast.cpp mymoneybalancecache.cpp onlinejob.cpp onlinejobadministration.cpp onlinejobmessage.cpp onlinejobfolder.cpp mymoneycontact.cpp payeeidentifiermodel.cpp ) # storage_a_SOURCES cannot be set in storage directory on MS Windows # because, while building kmm_storage, linker reports many undefined symbols # which are in fact available in kmm_mymoney set(storage_a_SOURCES ./storage/imymoneystorageformat.cpp ./storage/mymoneystoragemgr.cpp ) set(onlineTask_a_SOURCES ./onlinetasks/sepa/sepaonlinetransferimpl.cpp ./onlinetasks/sepa/sepaonlinetransfer.cpp ./onlinetasks/unavailabletask/tasks/unavailabletask.cpp ) # NOTE: this payeeIdentifier and its cmake file cannot be used as a template # This payeeIdentifier must be linked with payeeIdentifierLoader because it # is a fallback if something failed during loading of plugins (for xml files only) set(payeeidentifier_a_SOURCES ./payeeidentifier/ibanbic/ibanbic.cpp ./payeeidentifier/nationalaccount/nationalaccount.cpp ./payeeidentifier/unavailableplugin/unavailableplugin.cpp ) list(APPEND storage_a_SOURCES $<$,$,$>: ./storage/mymoneystoragedump.cpp>) list(APPEND kmm_mymoney_LIB_SRCS ${storage_a_SOURCES}) list(APPEND kmm_mymoney_LIB_SRCS ${onlineTask_a_SOURCES}) list(APPEND kmm_mymoney_LIB_SRCS ${payeeidentifier_a_SOURCES}) set(mymoney_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/kmm_mymoney_export.h mymoneyobject.h mymoneyaccount.h mymoneycategory.h mymoneyexception.h mymoneyfile.h mymoneyfinancialcalculator.h mymoneyinstitution.h mymoneyinvesttransaction.h mymoneykeyvaluecontainer.h mymoneymoney.h mymoneypayee.h mymoneytag.h mymoneyprice.h mymoneyreport.h mymoneyschedule.h mymoneysecurity.h mymoneysplit.h mymoneystatement.h mymoneytransactionfilter.h mymoneytransaction.h mymoneyutils.h mymoneybudget.h mymoneyforecast.h imymoneyprocessingcalendar.h mymoneycostcenter.h mymoneyunittestable.h mymoneypayeeidentifiercontainer.h onlinejob.h onlinejobtyped.h onlinejobmessage.h onlinejobfolder.h ) add_library(kmm_mymoney SHARED ${kmm_mymoney_LIB_SRCS}) generate_export_header(kmm_mymoney BASE_NAME kmm_mymoney) target_link_libraries(kmm_mymoney PUBLIC kmm_icons Qt5::Xml Qt5::Core Qt5::Gui KF5::Service KF5::I18n Alkimia::alkimia - kmm_payeeidentifier_loader kmm_payeeidentifier kmm_plugin # TODO: fix this KF5::XmlGui PRIVATE onlinetask_interfaces ) if(KMM_ADDRESSBOOK_FOUND) target_link_libraries(kmm_mymoney PUBLIC KF5::IdentityManagement KF5::AkonadiCore KF5::Contacts) endif() set_target_properties(kmm_mymoney PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) ########### install files ############### install(TARGETS kmm_mymoney ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES ${mymoney_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel) ############## tests #################### if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/kmymoney/mymoney/mymoneypayeeidentifiercontainer.cpp b/kmymoney/mymoney/mymoneypayeeidentifiercontainer.cpp index 1a65e0734..63c0fbca6 100644 --- a/kmymoney/mymoney/mymoneypayeeidentifiercontainer.cpp +++ b/kmymoney/mymoney/mymoneypayeeidentifiercontainer.cpp @@ -1,131 +1,130 @@ /* * Copyright 2014-2016 Christian Dávid * * 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 "mymoneypayeeidentifiercontainer.h" #include -#include "payeeidentifier/payeeidentifierloader.h" #include "payeeidentifier/ibanbic/ibanbic.h" #include "payeeidentifier/nationalaccount/nationalaccount.h" #include "payeeidentifier/unavailableplugin/unavailableplugin.h" #include "payeeidentifier.h" MyMoneyPayeeIdentifierContainer::MyMoneyPayeeIdentifierContainer() : m_payeeIdentifiers(QList< ::payeeIdentifier >()) { } unsigned int MyMoneyPayeeIdentifierContainer::payeeIdentifierCount() const { return m_payeeIdentifiers.count(); } payeeIdentifier MyMoneyPayeeIdentifierContainer::payeeIdentifier(unsigned int index) const { return m_payeeIdentifiers.at(index); } QList MyMoneyPayeeIdentifierContainer::payeeIdentifiers() const { return m_payeeIdentifiers; } void MyMoneyPayeeIdentifierContainer::addPayeeIdentifier(const ::payeeIdentifier& ident) { m_payeeIdentifiers.append(ident); } void MyMoneyPayeeIdentifierContainer::addPayeeIdentifier(const unsigned int position, const ::payeeIdentifier& ident) { m_payeeIdentifiers.insert(position, ident); } void MyMoneyPayeeIdentifierContainer::removePayeeIdentifier(const ::payeeIdentifier& ident) { m_payeeIdentifiers.removeOne(ident); } void MyMoneyPayeeIdentifierContainer::removePayeeIdentifier(const int index) { Q_ASSERT(m_payeeIdentifiers.count() > index && index >= 0); m_payeeIdentifiers.removeAt(index); } void MyMoneyPayeeIdentifierContainer::modifyPayeeIdentifier(const ::payeeIdentifier& ident) { QList< ::payeeIdentifier >::Iterator end = m_payeeIdentifiers.end(); for (QList< ::payeeIdentifier >::Iterator iter = m_payeeIdentifiers.begin(); iter != end; ++iter) { if (iter->id() == ident.id()) { *iter = ident; return; } } } void MyMoneyPayeeIdentifierContainer::modifyPayeeIdentifier(const int index, const ::payeeIdentifier& ident) { Q_ASSERT(m_payeeIdentifiers.count() > index && index >= 0); m_payeeIdentifiers[index] = ident; } void MyMoneyPayeeIdentifierContainer::resetPayeeIdentifiers(const QList< ::payeeIdentifier >& list) { m_payeeIdentifiers = list; } void MyMoneyPayeeIdentifierContainer::loadXML(QDomElement node) { // Load identifiers QDomNodeList identifierNodes = node.elementsByTagName("payeeIdentifier"); const uint identifierNodesLength = identifierNodes.length(); for (uint i = 0; i < identifierNodesLength; ++i) { const QDomElement element = identifierNodes.item(i).toElement(); const auto payeeIdentifierId = element.attribute("type"); payeeIdentifierData* identData; if (payeeIdentifierId == payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()) { auto creator = new payeeIdentifiers::ibanBic(); identData = creator->createFromXml(element); delete creator; } else if (payeeIdentifierId == payeeIdentifiers::nationalAccount::staticPayeeIdentifierIid()) { auto creator = new payeeIdentifiers::nationalAccount(); identData = creator->createFromXml(element); delete creator; } else { identData = new payeeIdentifiers::payeeIdentifierUnavailable(element); } ::payeeIdentifier ident(identData); ident.m_id = element.attribute("id", 0).toUInt(); if (ident.isNull()) { qWarning() << "Could not load payee identifier" << element.attribute("type", "*no pidid set*"); continue; } addPayeeIdentifier(ident); } } void MyMoneyPayeeIdentifierContainer::writeXML(QDomDocument document, QDomElement parent) const { // Add payee identifiers foreach (const ::payeeIdentifier& ident, m_payeeIdentifiers) { if (!ident.isNull()) { ident.writeXML(document, parent); } } } diff --git a/kmymoney/mymoney/payeeidentifier/CMakeLists.txt b/kmymoney/mymoney/payeeidentifier/CMakeLists.txt index 1032f0a4e..012ac6cef 100644 --- a/kmymoney/mymoney/payeeidentifier/CMakeLists.txt +++ b/kmymoney/mymoney/payeeidentifier/CMakeLists.txt @@ -1,58 +1,33 @@ set ( PAYEEIDENTIFIER_HEADER payeeidentifier.h payeeidentifiertyped.h payeeidentifierdata.h ) set ( PAYEEIDENTIFIER_SCRS payeeidentifier.cpp ) add_library( kmm_payeeidentifier SHARED ${PAYEEIDENTIFIER_SCRS} ) set_target_properties(kmm_payeeidentifier PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) generate_export_header( kmm_payeeidentifier ) target_link_libraries( kmm_payeeidentifier Qt5::Core Qt5::Xml ) target_include_directories( kmm_payeeidentifier PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ) install( TARGETS kmm_payeeidentifier ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) -## Plugin loader - -set ( PAYEEIDENTIFIER_LOADER_SCRS - payeeidentifierloader.cpp -) - -add_library( kmm_payeeidentifier_loader SHARED - ${PAYEEIDENTIFIER_LOADER_SCRS} -) - -generate_export_header(kmm_payeeidentifier_loader BASE_NAME kmm_payeeidentifier_loader) - -target_link_libraries( kmm_payeeidentifier_loader - PUBLIC - Qt5::Core - PRIVATE - Qt5::Widgets - KF5::Service -) - -set_target_properties(kmm_payeeidentifier_loader PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) - -install(TARGETS kmm_payeeidentifier_loader - ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) - if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/kmymoney/mymoney/payeeidentifier/payeeidentifiercontainermodel.cpp b/kmymoney/mymoney/payeeidentifier/payeeidentifiercontainermodel.cpp deleted file mode 100644 index 86784a6e3..000000000 --- a/kmymoney/mymoney/payeeidentifier/payeeidentifiercontainermodel.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2014-2015 Christian Dávid - * - * 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 "payeeidentifiermodel.h" -#include "mymoney/mymoneyfile.h" -#include "payeeidentifier/payeeidentifierloader.h" -#include "payeeidentifier/payeeidentifier.h" - -#include - -#include - -payeeIdentifierContainerModel::payeeIdentifierModel(QObject* parent) - : QAbstractListModel(parent), - m_data(QSharedPointer()) -{ -} - -QVariant payeeIdentifierContainerModel::data(const QModelIndex& index, int role) const -{ - // Needed for the selection box and it prevents a crash if index is out of range - if (m_data.isNull() || index.row() >= rowCount(index.parent()) - 1) - return QVariant(); - - const ::payeeIdentifier ident = m_data->payeeIdentifiers().at(index.row()); - - if (role == payeeIdentifier) { - return QVariant::fromValue< ::payeeIdentifier >(ident); - } else if (ident.isNull()) { - return QVariant(); - } else if (role == payeeIdentifierType) { - return ident.iid(); - } else if (role == Qt::DisplayRole) { - // The custom delegates won't ask for this role - return QVariant::fromValue(i18n("The plugin to show this information could not be found.")); - } - return QVariant(); -} - -bool payeeIdentifierContainerModel::setData(const QModelIndex& index, const QVariant& value, int role) -{ - if (!m_data.isNull() && role == payeeIdentifier) { - ::payeeIdentifier ident = value.value< ::payeeIdentifier >(); - if (index.row() == rowCount(index.parent()) - 1) { - // The new row will be the last but one - beginInsertRows(index.parent(), index.row() - 1, index.row() - 1); - m_data->addPayeeIdentifier(ident); - endInsertRows(); - } else { - m_data->modifyPayeeIdentifier(index.row(), ident); - emit dataChanged(createIndex(index.row(), 0), createIndex(index.row(), 0)); - } - return true; - } - return QAbstractItemModel::setData(index, value, role); -} - -Qt::ItemFlags payeeIdentifierContainerModel::flags(const QModelIndex& index) const -{ - Qt::ItemFlags flags = QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled; - const QString type = data(index, payeeIdentifierType).toString(); - // type.isEmpty() means the type selection can be shown - if (!type.isEmpty() && payeeIdentifierLoader::instance()->hasItemEditDelegate(type)) - flags |= Qt::ItemIsEditable; - return flags; -} - -int payeeIdentifierContainerModel::rowCount(const QModelIndex& parent) const -{ - Q_UNUSED(parent); - if (m_data.isNull()) - return 0; - // Always a row more which creates new entries - return m_data->payeeIdentifiers().count() + 1; -} - -/** @brief unused at the moment */ -bool payeeIdentifierContainerModel::insertRows(int row, int count, const QModelIndex& parent) -{ - Q_UNUSED(row); - Q_UNUSED(count); - Q_UNUSED(parent); - return false; -} - -bool payeeIdentifierContainerModel::removeRows(int row, int count, const QModelIndex& parent) -{ - if (m_data.isNull()) - return false; - - if (count < 1 || row + count >= rowCount(parent)) - return false; - - beginRemoveRows(parent, row, row + count - 1); - for (int i = row; i < row + count; ++i) { - m_data->removePayeeIdentifier(i); - } - endRemoveRows(); - return true; -} - -void payeeIdentifierContainerModel::setSource(const MyMoneyPayeeIdentifierContainer data) -{ - beginResetModel(); - m_data = QSharedPointer(new MyMoneyPayeeIdentifierContainer(data)); - endResetModel(); -} - -void payeeIdentifierContainerModel::closeSource() -{ - beginResetModel(); - m_data = QSharedPointer(); - endResetModel(); -} - -QList< ::payeeIdentifier > payeeIdentifierContainerModel::identifiers() const -{ - if (m_data.isNull()) - return QList< ::payeeIdentifier >(); - return m_data->payeeIdentifiers(); -} diff --git a/kmymoney/mymoney/payeeidentifier/payeeidentifiercontainermodel.h b/kmymoney/mymoney/payeeidentifier/payeeidentifiercontainermodel.h deleted file mode 100644 index 16545781c..000000000 --- a/kmymoney/mymoney/payeeidentifier/payeeidentifiercontainermodel.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2014-2015 Christian Dávid - * - * 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 PAYEEIDENTIFIERCONTAINERMODEL_H -#define PAYEEIDENTIFIERCONTAINERMODEL_H - -#include -#include "mymoney/mymoneypayeeidentifiercontainer.h" -#include "payeeidentifier/payeeidentifier.h" - -/** - * @brief Model for MyMoneyPayeeIdentifierContainer - * - * Changes the user does have initernal effect only. - */ -class payeeIdentifierContainerModel : public QAbstractListModel -{ - Q_OBJECT - -public: - enum roles { - payeeIdentifierType = Qt::UserRole, /**< type of payeeIdentifier */ - payeeIdentifier = Qt::UserRole + 1 /**< actual payeeIdentifier */ - }; - - explicit payeeIdentifierContainerModel(QObject* parent = 0); - - virtual QVariant data(const QModelIndex& index, int role) const; - - /** - * This model only supports to edit payeeIdentifier role with a QVariant of type - * payeeIdentifier. - */ - virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); - - virtual Qt::ItemFlags flags(const QModelIndex& index) const; - - virtual int rowCount(const QModelIndex& parent) const; - - virtual bool insertRows(int row, int count, const QModelIndex& parent); - virtual bool removeRows(int row, int count, const QModelIndex& parent); - - /** - * @brief Set source of data - * - * This makes the model editable. - */ - void setSource(MyMoneyPayeeIdentifierContainer data); - - /** @brief Get stored data */ - QList< ::payeeIdentifier > identifiers() const; - -public Q_SLOTS: - /** - * @brief Removes all data from the model - * - * The model is not editable afterwards. - */ - void closeSource(); - -private: - /** @internal - * The use of a shared pointer makes this future prof. Because using identifier() causes - * some unnecessary work. - */ - QSharedPointer m_data; -}; - -#endif // PAYEEIDENTIFIERMODEL_H diff --git a/kmymoney/mymoney/payeeidentifier/payeeidentifierloader.cpp b/kmymoney/mymoney/payeeidentifier/payeeidentifierloader.cpp deleted file mode 100644 index 0e843d913..000000000 --- a/kmymoney/mymoney/payeeidentifier/payeeidentifierloader.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2014-2016 Christian Dávid - * - * 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 "payeeidentifier/payeeidentifierloader.h" - -#include -#include - -#include - -payeeIdentifierLoader payeeIdentifierLoader::m_self; - -payeeIdentifierLoader::payeeIdentifierLoader() -{ -} - -payeeIdentifierLoader::~payeeIdentifierLoader() -{ -} - -/** - * @todo enable delegates again - */ -QAbstractItemDelegate* payeeIdentifierLoader::createItemDelegate(const QString& payeeIdentifierId, QObject* parent) -{ - /** @todo escape ' in payeeIdentifierId */ - KService::List offers = KServiceTypeTrader::self()->query(QLatin1String("KMyMoney/PayeeIdentifierDelegate"), QString("'%1' ~in [X-KMyMoney-payeeIdentifierIds]").arg(payeeIdentifierId)); - if (!offers.isEmpty()) { - QString error; - QAbstractItemDelegate* ptr = offers.at(0)->createInstance(parent, QVariantList(), &error); - if (ptr == nullptr) { - qWarning() << "could not load delegate" << error << payeeIdentifierId; - } - return ptr; - } - return nullptr; -} - -bool payeeIdentifierLoader::hasItemEditDelegate(const QString& payeeIdentifierId) -{ - QAbstractItemDelegate* delegate = createItemDelegate(payeeIdentifierId); - const bool ret = (delegate != nullptr); - delete delegate; - return ret; -} - -QStringList payeeIdentifierLoader::availableDelegates() -{ - QStringList list; - KService::List offers = KServiceTypeTrader::self()->query(QLatin1String("KMyMoney/PayeeIdentifierDelegate")); - foreach (KService::Ptr offer, offers) { - list.append(offer->property("X-KMyMoney-payeeIdentifierIds", QVariant::StringList).toStringList()); - } - return list; -} diff --git a/kmymoney/mymoney/payeeidentifier/payeeidentifierloader.h b/kmymoney/mymoney/payeeidentifier/payeeidentifierloader.h deleted file mode 100644 index 6d1870e99..000000000 --- a/kmymoney/mymoney/payeeidentifier/payeeidentifierloader.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2014-2016 Christian Dávid - * - * 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 PAYEEIDENTIFIERLOADER_H -#define PAYEEIDENTIFIERLOADER_H - -#include "kmm_payeeidentifier_loader_export.h" - -class QAbstractItemDelegate; -class QString; -class QObject; -class QStringList; - -/** - * - * @todo Load delegates dynamically - */ -class KMM_PAYEEIDENTIFIER_LOADER_EXPORT payeeIdentifierLoader -{ -public: - payeeIdentifierLoader(); - ~payeeIdentifierLoader(); - - /** - * @brief Create a delegate to show/edit - * - * The payeeIdentifier to edit is identified by payeeIdentifierId. parent is set as parent of the created - * Delegate. - * - * @return a pointer to a delegate or null_ptr. Caller takes ownership. - */ - QAbstractItemDelegate* createItemDelegate(const QString& payeeIdentifierId, QObject* parent = 0); - - /** - * @brief Test if a delegate for editing is available - */ - bool hasItemEditDelegate(const QString& payeeIdentifierId); - - /** - * @brief List availableDelegates delegates - * - * @return a list of payeeIdentifierIds for which a delegate exists. - * @see createItemDelegate() - */ - QStringList availableDelegates(); - - static payeeIdentifierLoader* instance() { - return &m_self; - } - -private: - static payeeIdentifierLoader m_self; -}; - -#endif // PAYEEIDENTIFIERLOADER_H diff --git a/kmymoney/mymoney/payeeidentifier/tests/CMakeLists.txt b/kmymoney/mymoney/payeeidentifier/tests/CMakeLists.txt index d38ee1d90..e55b16731 100644 --- a/kmymoney/mymoney/payeeidentifier/tests/CMakeLists.txt +++ b/kmymoney/mymoney/payeeidentifier/tests/CMakeLists.txt @@ -1,12 +1,11 @@ include(ECMAddTests) file(GLOB tests_sources "*-test.cpp") ecm_add_tests(${tests_sources} NAME_PREFIX payeeidentifier LINK_LIBRARIES Qt5::Test kmm_payeeidentifier - kmm_payeeidentifier_loader kmm_mymoney ) diff --git a/kmymoney/mymoney/payeeidentifier/tests/payeeidentifier-test.cpp b/kmymoney/mymoney/payeeidentifier/tests/payeeidentifier-test.cpp index af0cc910c..b5b79ce5c 100644 --- a/kmymoney/mymoney/payeeidentifier/tests/payeeidentifier-test.cpp +++ b/kmymoney/mymoney/payeeidentifier/tests/payeeidentifier-test.cpp @@ -1,87 +1,86 @@ /* * Copyright 2013-2016 Christian Dávid * * 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 "payeeidentifier-test.h" #include #include "mymoney/payeeidentifier/payeeidentifier.h" #include "mymoney/payeeidentifier/payeeidentifiertyped.h" -#include "mymoney/payeeidentifier/payeeidentifierloader.h" #include "payeeidentifier/ibanbic/ibanbic.h" QTEST_GUILESS_MAIN(payeeidentifier_test); void payeeidentifier_test::initTestCase() { // Called before the first testfunction is executed } void payeeidentifier_test::cleanupTestCase() { // Called after the last testfunction was executed } void payeeidentifier_test::init() { // Called before each testfunction is executed } void payeeidentifier_test::cleanup() { // Called after every testfunction } void payeeidentifier_test::createAndDeleteEmptyIdent() { payeeIdentifier ident{}; } void payeeidentifier_test::copyIdent() { try { const payeeIdentifier ident = payeeIdentifier(new payeeIdentifiers::ibanBic()); payeeIdentifier ident2 = ident; QVERIFY(!ident2.isNull()); QCOMPARE(ident2.iid(), payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()); } catch (...) { QFAIL("Unexpected exception"); } } void payeeidentifier_test::moveIdent() { try { payeeIdentifier ident = payeeIdentifier(new payeeIdentifiers::ibanBic()); payeeIdentifier ident2 = ident; } catch (...) { QFAIL("Unexpected exception"); } } void payeeidentifier_test::createTypedIdent() { try { payeeIdentifier ident = payeeIdentifier(new payeeIdentifiers::ibanBic()); payeeIdentifierTyped typedIdent{ident}; } catch (...) { QFAIL("Unexpected exception"); } } diff --git a/kmymoney/mymoney/tests/mymoneyfile-test.cpp b/kmymoney/mymoney/tests/mymoneyfile-test.cpp index 71e5a6322..373639825 100644 --- a/kmymoney/mymoney/tests/mymoneyfile-test.cpp +++ b/kmymoney/mymoney/tests/mymoneyfile-test.cpp @@ -1,2835 +1,2834 @@ /* * Copyright 2002-2018 Thomas Baumgart * Copyright 2004 Ace Jones * * 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 "mymoneyfile-test.h" #include #include #include #include #include #include #include "mymoneystoragemgr_p.h" #include "mymoneytestutils.h" #include "mymoneymoney.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneytransaction.h" #include "mymoneytransactionfilter.h" #include "mymoneysplit.h" #include "mymoneyprice.h" #include "mymoneypayee.h" #include "mymoneyenums.h" #include "onlinejob.h" #include "payeeidentifier/ibanbic/ibanbic.h" -#include "payeeidentifier/payeeidentifierloader.h" #include "payeeidentifiertyped.h" QTEST_GUILESS_MAIN(MyMoneyFileTest) MyMoneyFileTest::MyMoneyFileTest() : m(nullptr), storage(nullptr) { } void MyMoneyFileTest::objectAdded(eMyMoney::File::Object type, const QString& id) { Q_UNUSED(type); m_objectsAdded += id; } void MyMoneyFileTest::objectRemoved(eMyMoney::File::Object type, const QString& id) { Q_UNUSED(type); m_objectsRemoved += id; } void MyMoneyFileTest::objectModified(eMyMoney::File::Object type, const QString& id) { Q_UNUSED(type); m_objectsModified += id; } void MyMoneyFileTest::clearObjectLists() { m_objectsAdded.clear(); m_objectsModified.clear(); m_objectsRemoved.clear(); m_balanceChanged.clear(); m_valueChanged.clear(); } void MyMoneyFileTest::balanceChanged(const MyMoneyAccount& account) { m_balanceChanged += account.id(); } void MyMoneyFileTest::valueChanged(const MyMoneyAccount& account) { m_valueChanged += account.id(); } // this method will be called once at the beginning of the test void MyMoneyFileTest::initTestCase() { m = MyMoneyFile::instance(); connect(m, &MyMoneyFile::objectAdded, this, &MyMoneyFileTest::objectAdded); connect(m, &MyMoneyFile::objectRemoved, this, &MyMoneyFileTest::objectRemoved); connect(m, &MyMoneyFile::objectModified, this, &MyMoneyFileTest::objectModified); connect(m, &MyMoneyFile::balanceChanged, this, &MyMoneyFileTest::balanceChanged); connect(m, &MyMoneyFile::valueChanged, this, &MyMoneyFileTest::valueChanged); } // this method will be called before each testfunction void MyMoneyFileTest::init() { storage = new MyMoneyStorageMgr; m->attachStorage(storage); clearObjectLists(); } // this method will be called after each testfunction void MyMoneyFileTest::cleanup() { m->detachStorage(storage); delete storage; } void MyMoneyFileTest::testEmptyConstructor() { MyMoneyPayee user = m->user(); QCOMPARE(user.name(), QString()); QCOMPARE(user.address(), QString()); QCOMPARE(user.city(), QString()); QCOMPARE(user.state(), QString()); QCOMPARE(user.postcode(), QString()); QCOMPARE(user.telephone(), QString()); QCOMPARE(user.email(), QString()); QCOMPARE(m->institutionCount(), static_cast(0)); QCOMPARE(m->dirty(), false); QCOMPARE(m->accountCount(), static_cast(5)); } void MyMoneyFileTest::testAddOneInstitution() { MyMoneyInstitution institution; institution.setName("institution1"); institution.setTown("town"); institution.setStreet("street"); institution.setPostcode("postcode"); institution.setTelephone("telephone"); institution.setManager("manager"); institution.setSortcode("sortcode"); // MyMoneyInstitution institution_file("", institution); MyMoneyInstitution institution_id("I000002", institution); MyMoneyInstitution institution_noname(institution); institution_noname.setName(QString()); QString id; QCOMPARE(m->institutionCount(), static_cast(0)); storage->d_func()->m_dirty = false; clearObjectLists(); MyMoneyFileTransaction ft; try { m->addInstitution(institution); ft.commit(); QCOMPARE(institution.id(), QLatin1String("I000001")); QCOMPARE(m->institutionCount(), static_cast(1)); QCOMPARE(m->dirty(), true); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QCOMPARE(m_objectsAdded[0], QLatin1String("I000001")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } clearObjectLists(); ft.restart(); try { m->addInstitution(institution_id); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { ft.commit(); QCOMPARE(m->institutionCount(), static_cast(1)); } ft.restart(); try { m->addInstitution(institution_noname); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { ft.commit(); QCOMPARE(m->institutionCount(), static_cast(1)); } QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); } void MyMoneyFileTest::testAddTwoInstitutions() { testAddOneInstitution(); MyMoneyInstitution institution; institution.setName("institution2"); institution.setTown("town"); institution.setStreet("street"); institution.setPostcode("postcode"); institution.setTelephone("telephone"); institution.setManager("manager"); institution.setSortcode("sortcode"); QString id; storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; try { m->addInstitution(institution); ft.commit(); QCOMPARE(institution.id(), QLatin1String("I000002")); QCOMPARE(m->institutionCount(), static_cast(2)); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } storage->d_func()->m_dirty = false; try { institution = m->institution("I000001"); QCOMPARE(institution.id(), QLatin1String("I000001")); QCOMPARE(m->institutionCount(), static_cast(2)); QCOMPARE(m->dirty(), false); institution = m->institution("I000002"); QCOMPARE(institution.id(), QLatin1String("I000002")); QCOMPARE(m->institutionCount(), static_cast(2)); QCOMPARE(m->dirty(), false); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyFileTest::testRemoveInstitution() { testAddTwoInstitutions(); MyMoneyInstitution i; QCOMPARE(m->institutionCount(), static_cast(2)); i = m->institution("I000001"); QCOMPARE(i.id(), QLatin1String("I000001")); QCOMPARE(i.accountCount(), static_cast(0)); clearObjectLists(); storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; try { m->removeInstitution(i); QCOMPARE(m_objectsRemoved.count(), 0); ft.commit(); QCOMPARE(m->institutionCount(), static_cast(1)); QCOMPARE(m->dirty(), true); QCOMPARE(m_objectsRemoved.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QCOMPARE(m_objectsRemoved[0], QLatin1String("I000001")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } storage->d_func()->m_dirty = false; try { m->institution("I000001"); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { QCOMPARE(m->institutionCount(), static_cast(1)); QCOMPARE(m->dirty(), false); } clearObjectLists(); ft.restart(); try { m->removeInstitution(i); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { ft.commit(); QCOMPARE(m->institutionCount(), static_cast(1)); QCOMPARE(m->dirty(), false); QCOMPARE(m_objectsRemoved.count(), 0); } } void MyMoneyFileTest::testInstitutionRetrieval() { testAddOneInstitution(); storage->d_func()->m_dirty = false; MyMoneyInstitution institution; QCOMPARE(m->institutionCount(), static_cast(1)); try { institution = m->institution("I000001"); QCOMPARE(institution.id(), QLatin1String("I000001")); QCOMPARE(m->institutionCount(), static_cast(1)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } try { institution = m->institution("I000002"); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { QCOMPARE(m->institutionCount(), static_cast(1)); } QCOMPARE(m->dirty(), false); } void MyMoneyFileTest::testInstitutionListRetrieval() { QList list; storage->d_func()->m_dirty = false; list = m->institutionList(); QCOMPARE(m->dirty(), false); QCOMPARE(list.count(), 0); testAddTwoInstitutions(); storage->d_func()->m_dirty = false; list = m->institutionList(); QCOMPARE(m->dirty(), false); QCOMPARE(list.count(), 2); QList::ConstIterator it; it = list.constBegin(); QVERIFY((*it).name() == "institution1" || (*it).name() == "institution2"); ++it; QVERIFY((*it).name() == "institution2" || (*it).name() == "institution1"); ++it; QVERIFY(it == list.constEnd()); } void MyMoneyFileTest::testInstitutionModify() { testAddTwoInstitutions(); MyMoneyInstitution institution; institution = m->institution("I000001"); institution.setStreet("new street"); institution.setTown("new town"); institution.setPostcode("new postcode"); institution.setTelephone("new telephone"); institution.setManager("new manager"); institution.setName("new name"); institution.setSortcode("new sortcode"); storage->d_func()->m_dirty = false; clearObjectLists(); MyMoneyFileTransaction ft; try { m->modifyInstitution(institution); ft.commit(); QCOMPARE(institution.id(), QLatin1String("I000001")); QCOMPARE(m->institutionCount(), static_cast(2)); QCOMPARE(m->dirty(), true); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QCOMPARE(m_objectsModified[0], QLatin1String("I000001")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } MyMoneyInstitution newInstitution; newInstitution = m->institution("I000001"); QCOMPARE(newInstitution.id(), QLatin1String("I000001")); QCOMPARE(newInstitution.street(), QLatin1String("new street")); QCOMPARE(newInstitution.town(), QLatin1String("new town")); QCOMPARE(newInstitution.postcode(), QLatin1String("new postcode")); QCOMPARE(newInstitution.telephone(), QLatin1String("new telephone")); QCOMPARE(newInstitution.manager(), QLatin1String("new manager")); QCOMPARE(newInstitution.name(), QLatin1String("new name")); QCOMPARE(newInstitution.sortcode(), QLatin1String("new sortcode")); storage->d_func()->m_dirty = false; ft.restart(); MyMoneyInstitution failInstitution2("I000003", newInstitution); try { m->modifyInstitution(failInstitution2); QFAIL("Exception expected"); } catch (const MyMoneyException &) { ft.commit(); QCOMPARE(failInstitution2.id(), QLatin1String("I000003")); QCOMPARE(m->institutionCount(), static_cast(2)); QCOMPARE(m->dirty(), false); } } void MyMoneyFileTest::testSetFunctions() { MyMoneyPayee user = m->user(); QCOMPARE(user.name(), QString()); QCOMPARE(user.address(), QString()); QCOMPARE(user.city(), QString()); QCOMPARE(user.state(), QString()); QCOMPARE(user.postcode(), QString()); QCOMPARE(user.telephone(), QString()); QCOMPARE(user.email(), QString()); MyMoneyFileTransaction ft; storage->d_func()->m_dirty = false; user.setName("Name"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; user.setAddress("Street"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; user.setCity("Town"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; user.setState("County"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; user.setPostcode("Postcode"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; user.setTelephone("Telephone"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; user.setEmail("Email"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; ft.commit(); user = m->user(); QCOMPARE(user.name(), QLatin1String("Name")); QCOMPARE(user.address(), QLatin1String("Street")); QCOMPARE(user.city(), QLatin1String("Town")); QCOMPARE(user.state(), QLatin1String("County")); QCOMPARE(user.postcode(), QLatin1String("Postcode")); QCOMPARE(user.telephone(), QLatin1String("Telephone")); QCOMPARE(user.email(), QLatin1String("Email")); } void MyMoneyFileTest::testAddAccounts() { testAddTwoInstitutions(); MyMoneyAccount a, b, c; a.setAccountType(eMyMoney::Account::Type::Checkings); b.setAccountType(eMyMoney::Account::Type::Checkings); MyMoneyInstitution institution; storage->d_func()->m_dirty = false; QCOMPARE(m->accountCount(), static_cast(5)); institution = m->institution("I000001"); QCOMPARE(institution.id(), QLatin1String("I000001")); a.setName("Account1"); a.setInstitutionId(institution.id()); a.setCurrencyId("EUR"); clearObjectLists(); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->asset(); m->addAccount(a, parent); ft.commit(); QCOMPARE(m->accountCount(), static_cast(6)); QCOMPARE(a.parentAccountId(), QLatin1String("AStd::Asset")); QCOMPARE(a.id(), QLatin1String("A000001")); QCOMPARE(a.institutionId(), QLatin1String("I000001")); QCOMPARE(a.currencyId(), QLatin1String("EUR")); QCOMPARE(m->dirty(), true); QCOMPARE(m->asset().accountList().count(), 1); QCOMPARE(m->asset().accountList()[0], QLatin1String("A000001")); institution = m->institution("I000001"); QCOMPARE(institution.accountCount(), static_cast(1)); QCOMPARE(institution.accountList()[0], QLatin1String("A000001")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_objectsModified.count(), 2); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsAdded.contains(QLatin1String("A000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("I000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // try to add this account again, should not work ft.restart(); try { MyMoneyAccount parent = m->asset(); m->addAccount(a, parent); QFAIL("Expecting exception!"); } catch (const MyMoneyException &) { ft.commit(); } // check that we can modify the local object and // reload it from the file a.setName("AccountX"); a = m->account("A000001"); QCOMPARE(a.name(), QLatin1String("Account1")); storage->d_func()->m_dirty = false; // check if we can get the same info to a different object c = m->account("A000001"); QCOMPARE(c.accountType(), eMyMoney::Account::Type::Checkings); QCOMPARE(c.id(), QLatin1String("A000001")); QCOMPARE(c.name(), QLatin1String("Account1")); QCOMPARE(c.institutionId(), QLatin1String("I000001")); QCOMPARE(m->dirty(), false); // add a second account institution = m->institution("I000002"); b.setName("Account2"); b.setInstitutionId(institution.id()); b.setCurrencyId("EUR"); clearObjectLists(); ft.restart(); try { MyMoneyAccount parent = m->asset(); m->addAccount(b, parent); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(b.id(), QLatin1String("A000002")); QCOMPARE(b.currencyId(), QLatin1String("EUR")); QCOMPARE(b.parentAccountId(), QLatin1String("AStd::Asset")); QCOMPARE(m->accountCount(), static_cast(7)); institution = m->institution("I000001"); QCOMPARE(institution.accountCount(), static_cast(1)); QCOMPARE(institution.accountList()[0], QLatin1String("A000001")); institution = m->institution("I000002"); QCOMPARE(institution.accountCount(), static_cast(1)); QCOMPARE(institution.accountList()[0], QLatin1String("A000002")); QCOMPARE(m->asset().accountList().count(), 2); QCOMPARE(m->asset().accountList()[0], QLatin1String("A000001")); QCOMPARE(m->asset().accountList()[1], QLatin1String("A000002")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_objectsModified.count(), 2); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsAdded.contains(QLatin1String("A000002"))); QVERIFY(m_objectsModified.contains(QLatin1String("I000002"))); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } MyMoneyAccount p; p = m->account("A000002"); QCOMPARE(p.accountType(), eMyMoney::Account::Type::Checkings); QCOMPARE(p.id(), QLatin1String("A000002")); QCOMPARE(p.name(), QLatin1String("Account2")); QCOMPARE(p.institutionId(), QLatin1String("I000002")); QCOMPARE(p.currencyId(), QLatin1String("EUR")); } void MyMoneyFileTest::testAddCategories() { MyMoneyAccount a, b, c; a.setAccountType(eMyMoney::Account::Type::Income); a.setOpeningDate(QDate::currentDate()); b.setAccountType(eMyMoney::Account::Type::Expense); storage->d_func()->m_dirty = false; QCOMPARE(m->accountCount(), static_cast(5)); QCOMPARE(a.openingDate(), QDate::currentDate()); QVERIFY(!b.openingDate().isValid()); a.setName("Account1"); a.setCurrencyId("EUR"); clearObjectLists(); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->income(); m->addAccount(a, parent); ft.commit(); QCOMPARE(m->accountCount(), static_cast(6)); QCOMPARE(a.parentAccountId(), QLatin1String("AStd::Income")); QCOMPARE(a.id(), QLatin1String("A000001")); QCOMPARE(a.institutionId(), QString()); QCOMPARE(a.currencyId(), QLatin1String("EUR")); QCOMPARE(a.openingDate(), QDate(1900, 1, 1)); QCOMPARE(m->dirty(), true); QCOMPARE(m->income().accountList().count(), 1); QCOMPARE(m->income().accountList()[0], QLatin1String("A000001")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // add a second category, expense this time b.setName("Account2"); b.setCurrencyId("EUR"); clearObjectLists(); ft.restart(); try { MyMoneyAccount parent = m->expense(); m->addAccount(b, parent); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(b.id(), QLatin1String("A000002")); QCOMPARE(a.institutionId(), QString()); QCOMPARE(b.currencyId(), QLatin1String("EUR")); QCOMPARE(b.openingDate(), QDate(1900, 1, 1)); QCOMPARE(b.parentAccountId(), QLatin1String("AStd::Expense")); QCOMPARE(m->accountCount(), static_cast(7)); QCOMPARE(m->income().accountList().count(), 1); QCOMPARE(m->expense().accountList().count(), 1); QCOMPARE(m->income().accountList()[0], QLatin1String("A000001")); QCOMPARE(m->expense().accountList()[0], QLatin1String("A000002")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsAdded.contains(QLatin1String("A000002"))); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Expense"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testModifyAccount() { testAddAccounts(); storage->d_func()->m_dirty = false; MyMoneyAccount p = m->account("A000001"); MyMoneyInstitution institution; QCOMPARE(p.accountType(), eMyMoney::Account::Type::Checkings); QCOMPARE(p.name(), QLatin1String("Account1")); p.setName("New account name"); MyMoneyFileTransaction ft; clearObjectLists(); try { m->modifyAccount(p); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(m->accountCount(), static_cast(7)); QCOMPARE(p.accountType(), eMyMoney::Account::Type::Checkings); QCOMPARE(p.name(), QLatin1String("New account name")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("A000001"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } storage->d_func()->m_dirty = false; // try to move account to new institution p.setInstitutionId("I000002"); ft.restart(); clearObjectLists(); try { m->modifyAccount(p); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(m->accountCount(), static_cast(7)); QCOMPARE(p.accountType(), eMyMoney::Account::Type::Checkings); QCOMPARE(p.name(), QLatin1String("New account name")); QCOMPARE(p.institutionId(), QLatin1String("I000002")); institution = m->institution("I000001"); QCOMPARE(institution.accountCount(), static_cast(0)); institution = m->institution("I000002"); QCOMPARE(institution.accountCount(), static_cast(2)); QCOMPARE(institution.accountList()[0], QLatin1String("A000002")); QCOMPARE(institution.accountList()[1], QLatin1String("A000001")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 3); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("A000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("I000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("I000002"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } storage->d_func()->m_dirty = false; // try to change to an account type that is allowed p.setAccountType(eMyMoney::Account::Type::Savings); ft.restart(); try { m->modifyAccount(p); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(m->accountCount(), static_cast(7)); QCOMPARE(p.accountType(), eMyMoney::Account::Type::Savings); QCOMPARE(p.name(), QLatin1String("New account name")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } storage->d_func()->m_dirty = false; // try to change to an account type that is not allowed p.setAccountType(eMyMoney::Account::Type::CreditCard); ft.restart(); try { m->modifyAccount(p); QFAIL("Expecting exception!"); } catch (const MyMoneyException &) { ft.commit(); } storage->d_func()->m_dirty = false; // try to fool engine a bit p.setParentAccountId("A000001"); ft.restart(); try { m->modifyAccount(p); QFAIL("Expecting exception!"); } catch (const MyMoneyException &) { ft.commit(); } } void MyMoneyFileTest::testReparentAccount() { testAddAccounts(); storage->d_func()->m_dirty = false; MyMoneyAccount p = m->account("A000001"); MyMoneyAccount q = m->account("A000002"); MyMoneyAccount o = m->account(p.parentAccountId()); // make A000001 a child of A000002 clearObjectLists(); MyMoneyFileTransaction ft; try { QVERIFY(p.parentAccountId() != q.id()); QCOMPARE(o.accountCount(), 2); QCOMPARE(q.accountCount(), 0); m->reparentAccount(p, q); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(p.parentAccountId(), q.id()); QCOMPARE(q.accountCount(), 1); QCOMPARE(q.id(), QLatin1String("A000002")); QCOMPARE(p.id(), QLatin1String("A000001")); QCOMPARE(q.accountList()[0], p.id()); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 3); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("A000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("A000002"))); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); o = m->account(o.id()); QCOMPARE(o.accountCount(), 1); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testRemoveStdAccount(const MyMoneyAccount& acc) { QString txt("Exception expected while removing account "); txt += acc.id(); MyMoneyFileTransaction ft; try { m->removeAccount(acc); QFAIL(qPrintable(txt)); } catch (const MyMoneyException &) { ft.commit(); } } void MyMoneyFileTest::testRemoveAccount() { MyMoneyInstitution institution; testAddAccounts(); QCOMPARE(m->accountCount(), static_cast(7)); storage->d_func()->m_dirty = false; QString id; MyMoneyAccount p = m->account("A000001"); clearObjectLists(); MyMoneyFileTransaction ft; try { MyMoneyAccount q("Ainvalid", p); m->removeAccount(q); QFAIL("Exception expected!"); } catch (const MyMoneyException &) { ft.commit(); } ft.restart(); try { QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 0); m->removeAccount(p); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(m->accountCount(), static_cast(6)); institution = m->institution("I000001"); QCOMPARE(institution.accountCount(), static_cast(0)); QCOMPARE(m->asset().accountList().count(), 1); QCOMPARE(m_objectsRemoved.count(), 1); QCOMPARE(m_objectsModified.count(), 2); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsRemoved.contains(QLatin1String("A000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("I000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); institution = m->institution("I000002"); QCOMPARE(institution.accountCount(), static_cast(1)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // Check that the standard account-groups cannot be removed testRemoveStdAccount(m->liability()); testRemoveStdAccount(m->asset()); testRemoveStdAccount(m->expense()); testRemoveStdAccount(m->income()); } void MyMoneyFileTest::testRemoveAccountTree() { testReparentAccount(); MyMoneyAccount a = m->account("A000002"); clearObjectLists(); MyMoneyFileTransaction ft; // remove the account try { m->removeAccount(a); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 1); QCOMPARE(m_objectsModified.count(), 3); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsRemoved.contains(QLatin1String("A000002"))); QVERIFY(m_objectsModified.contains(QLatin1String("A000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("I000002"))); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } QCOMPARE(m->accountCount(), static_cast(6)); // make sure it's gone try { m->account("A000002"); QFAIL("Exception expected!"); } catch (const MyMoneyException &) { } // make sure that children are re-parented to parent account try { a = m->account("A000001"); QCOMPARE(a.parentAccountId(), m->asset().id()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testAccountListRetrieval() { QList list; storage->d_func()->m_dirty = false; m->accountList(list); QCOMPARE(m->dirty(), false); QCOMPARE(list.count(), 0); testAddAccounts(); storage->d_func()->m_dirty = false; list.clear(); m->accountList(list); QCOMPARE(m->dirty(), false); QCOMPARE(list.count(), 2); QCOMPARE(list[0].accountType(), eMyMoney::Account::Type::Checkings); QCOMPARE(list[1].accountType(), eMyMoney::Account::Type::Checkings); } void MyMoneyFileTest::testAddTransaction() { testAddAccounts(); MyMoneyTransaction t, p; MyMoneyAccount exp1; exp1.setAccountType(eMyMoney::Account::Type::Expense); exp1.setName("Expense1"); MyMoneyAccount exp2; exp2.setAccountType(eMyMoney::Account::Type::Expense); exp2.setName("Expense2"); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->expense(); m->addAccount(exp1, parent); m->addAccount(exp2, parent); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // fake the last modified flag to check that the // date is updated when we add the transaction MyMoneyAccount a = m->account("A000001"); a.setLastModified(QDate(1, 2, 3)); ft.restart(); try { m->modifyAccount(a); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } ft.restart(); QCOMPARE(m->accountCount(), static_cast(9)); a = m->account("A000001"); QCOMPARE(a.lastModified(), QDate(1, 2, 3)); // construct a transaction and add it to the pool t.setPostDate(QDate(2002, 2, 1)); t.setMemo("Memotext"); MyMoneySplit split1; MyMoneySplit split2; split1.setAccountId("A000001"); split1.setShares(MyMoneyMoney(-1000, 100)); split1.setValue(MyMoneyMoney(-1000, 100)); split2.setAccountId("A000003"); split2.setValue(MyMoneyMoney(1000, 100)); split2.setShares(MyMoneyMoney(1000, 100)); try { t.addSplit(split1); t.addSplit(split2); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } /* // FIXME: we don't have a payee and a number field right now // guess we should have a number field per split, don't know // about the payee t.setMethod(MyMoneyCheckingTransaction::Withdrawal); t.setPayee("Thomas Baumgart"); t.setNumber("1234"); t.setState(MyMoneyCheckingTransaction::Cleared); */ storage->d_func()->m_dirty = false; ft.restart(); clearObjectLists(); try { m->addTransaction(t); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_balanceChanged.count(), 2); QCOMPARE(m_balanceChanged.count("A000001"), 1); QCOMPARE(m_balanceChanged.count("A000003"), 1); QCOMPARE(m_valueChanged.count(), 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } ft.restart(); clearObjectLists(); QCOMPARE(t.id(), QLatin1String("T000000000000000001")); QCOMPARE(t.postDate(), QDate(2002, 2, 1)); QCOMPARE(t.entryDate(), QDate::currentDate()); QCOMPARE(m->dirty(), true); // check the balance of the accounts a = m->account("A000001"); QCOMPARE(a.lastModified(), QDate::currentDate()); QCOMPARE(a.balance(), MyMoneyMoney(-1000, 100)); MyMoneyAccount b = m->account("A000003"); QCOMPARE(b.lastModified(), QDate::currentDate()); QCOMPARE(b.balance(), MyMoneyMoney(1000, 100)); storage->d_func()->m_dirty = false; // locate transaction in MyMoneyFile via id try { p = m->transaction("T000000000000000001"); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QCOMPARE(p.splitCount(), static_cast(2)); QCOMPARE(p.memo(), QLatin1String("Memotext")); QCOMPARE(p.splits()[0].accountId(), QLatin1String("A000001")); QCOMPARE(p.splits()[1].accountId(), QLatin1String("A000003")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // check if it's in the account(s) as well try { p = m->transaction("A000001", 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QCOMPARE(p.id(), QLatin1String("T000000000000000001")); QCOMPARE(p.splitCount(), static_cast(2)); QCOMPARE(p.memo(), QLatin1String("Memotext")); QCOMPARE(p.splits()[0].accountId(), QLatin1String("A000001")); QCOMPARE(p.splits()[1].accountId(), QLatin1String("A000003")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } try { p = m->transaction("A000003", 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QCOMPARE(p.id(), QLatin1String("T000000000000000001")); QCOMPARE(p.splitCount(), static_cast(2)); QCOMPARE(p.memo(), QLatin1String("Memotext")); QCOMPARE(p.splits()[0].accountId(), QLatin1String("A000001")); QCOMPARE(p.splits()[1].accountId(), QLatin1String("A000003")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testIsStandardAccount() { QCOMPARE(m->isStandardAccount(m->liability().id()), true); QCOMPARE(m->isStandardAccount(m->asset().id()), true); QCOMPARE(m->isStandardAccount(m->expense().id()), true); QCOMPARE(m->isStandardAccount(m->income().id()), true); QCOMPARE(m->isStandardAccount("A00001"), false); } void MyMoneyFileTest::testHasActiveSplits() { testAddTransaction(); QCOMPARE(m->hasActiveSplits("A000001"), true); QCOMPARE(m->hasActiveSplits("A000002"), false); } void MyMoneyFileTest::testModifyTransactionSimple() { // this will test that we can modify the basic attributes // of a transaction testAddTransaction(); MyMoneyTransaction t = m->transaction("T000000000000000001"); t.setMemo("New Memotext"); storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; clearObjectLists(); try { m->modifyTransaction(t); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 2); QCOMPARE(m_balanceChanged.count(QLatin1String("A000001")), 1); QCOMPARE(m_balanceChanged.count(QLatin1String("A000003")), 1); QCOMPARE(m_valueChanged.count(), 0); t = m->transaction("T000000000000000001"); QCOMPARE(t.memo(), QLatin1String("New Memotext")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testModifyTransactionNewPostDate() { // this will test that we can modify the basic attributes // of a transaction testAddTransaction(); MyMoneyTransaction t = m->transaction("T000000000000000001"); t.setPostDate(QDate(2004, 2, 1)); storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; clearObjectLists(); try { m->modifyTransaction(t); ft.commit(); t = m->transaction("T000000000000000001"); QCOMPARE(t.postDate(), QDate(2004, 2, 1)); t = m->transaction("A000001", 0); QCOMPARE(t.id(), QLatin1String("T000000000000000001")); QCOMPARE(m->dirty(), true); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 2); QCOMPARE(m_balanceChanged.count(QLatin1String("A000001")), 1); QCOMPARE(m_balanceChanged.count(QLatin1String("A000003")), 1); QCOMPARE(m_valueChanged.count(), 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testModifyTransactionNewAccount() { // this will test that we can modify the basic attributes // of a transaction testAddTransaction(); MyMoneyTransaction t = m->transaction("T000000000000000001"); MyMoneySplit s; s = t.splits()[0]; s.setAccountId("A000002"); t.modifySplit(s); storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; clearObjectLists(); try { MyMoneyTransactionFilter f1("A000001"); MyMoneyTransactionFilter f2("A000002"); MyMoneyTransactionFilter f3("A000003"); QCOMPARE(m->transactionList(f1).count(), 1); QCOMPARE(m->transactionList(f2).count(), 0); QCOMPARE(m->transactionList(f3).count(), 1); m->modifyTransaction(t); ft.commit(); t = m->transaction("T000000000000000001"); QCOMPARE(t.postDate(), QDate(2002, 2, 1)); t = m->transaction("A000002", 0); QCOMPARE(m->dirty(), true); QCOMPARE(m->transactionList(f1).count(), 0); QCOMPARE(m->transactionList(f2).count(), 1); QCOMPARE(m->transactionList(f3).count(), 1); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 3); QCOMPARE(m_balanceChanged.count(QLatin1String("A000001")), 1); QCOMPARE(m_balanceChanged.count(QLatin1String("A000002")), 1); QCOMPARE(m_balanceChanged.count(QLatin1String("A000003")), 1); QCOMPARE(m_valueChanged.count(), 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testRemoveTransaction() { testModifyTransactionNewPostDate(); MyMoneyTransaction t; t = m->transaction("T000000000000000001"); storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; clearObjectLists(); try { m->removeTransaction(t); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(m->transactionCount(), static_cast(0)); MyMoneyTransactionFilter f1("A000001"); MyMoneyTransactionFilter f2("A000002"); MyMoneyTransactionFilter f3("A000003"); QCOMPARE(m->transactionList(f1).count(), 0); QCOMPARE(m->transactionList(f2).count(), 0); QCOMPARE(m->transactionList(f3).count(), 0); QCOMPARE(m_objectsRemoved.count(), 1); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 2); QCOMPARE(m_balanceChanged.count(QLatin1String("A000001")), 1); QCOMPARE(m_balanceChanged.count(QLatin1String("A000003")), 1); QCOMPARE(m_valueChanged.count(), 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } /* * This function is currently not implemented. It's kind of tricky * because it modifies a lot of objects in a single call. This might * be a problem for the undo/redo stuff. That's why I left it out in * the first run. We migh add it, if we need it. * / void testMoveSplits() { testModifyTransactionNewPostDate(); QCOMPARE(m->account("A000001").transactionCount(), 1); QCOMPARE(m->account("A000002").transactionCount(), 0); QCOMPARE(m->account("A000003").transactionCount(), 1); try { m->moveSplits("A000001", "A000002"); QCOMPARE(m->account("A000001").transactionCount(), 0); QCOMPARE(m->account("A000002").transactionCount(), 1); QCOMPARE(m->account("A000003").transactionCount(), 1); } catch (const MyMoneyException &e) { QFAIL("Unexpected exception!"); } } */ void MyMoneyFileTest::testBalanceTotal() { testAddTransaction(); MyMoneyTransaction t; // construct a transaction and add it to the pool t.setPostDate(QDate(2002, 2, 1)); t.setMemo("Memotext"); MyMoneySplit split1; MyMoneySplit split2; MyMoneyFileTransaction ft; try { split1.setAccountId("A000002"); split1.setShares(MyMoneyMoney(-1000, 100)); split1.setValue(MyMoneyMoney(-1000, 100)); split2.setAccountId("A000004"); split2.setValue(MyMoneyMoney(1000, 100)); split2.setShares(MyMoneyMoney(1000, 100)); t.addSplit(split1); t.addSplit(split2); m->addTransaction(t); ft.commit(); ft.restart(); QCOMPARE(t.id(), QLatin1String("T000000000000000002")); QCOMPARE(m->totalBalance("A000001"), MyMoneyMoney(-1000, 100)); QCOMPARE(m->totalBalance("A000002"), MyMoneyMoney(-1000, 100)); MyMoneyAccount p = m->account("A000001"); MyMoneyAccount q = m->account("A000002"); m->reparentAccount(p, q); ft.commit(); // check totalBalance() and balance() with combinations of parameters QCOMPARE(m->totalBalance("A000001"), MyMoneyMoney(-1000, 100)); QCOMPARE(m->totalBalance("A000002"), MyMoneyMoney(-2000, 100)); QVERIFY(m->totalBalance("A000002", QDate(2002, 1, 15)).isZero()); QCOMPARE(m->balance("A000001"), MyMoneyMoney(-1000, 100)); QCOMPARE(m->balance("A000002"), MyMoneyMoney(-1000, 100)); // Date of a transaction QCOMPARE(m->balance("A000001", QDate(2002, 2, 1)), MyMoneyMoney(-1000, 100)); QCOMPARE(m->balance("A000002", QDate(2002, 2, 1)), MyMoneyMoney(-1000, 100)); // Date after last transaction QCOMPARE(m->balance("A000001", QDate(2002, 2, 1)), MyMoneyMoney(-1000, 100)); QCOMPARE(m->balance("A000002", QDate(2002, 2, 1)), MyMoneyMoney(-1000, 100)); // Date before first transaction QVERIFY(m->balance("A000001", QDate(2002, 1, 15)).isZero()); QVERIFY(m->balance("A000002", QDate(2002, 1, 15)).isZero()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // Now check for exceptions try { // Account not found for balance() QVERIFY(m->balance("A000005").isZero()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { // Account not found for totalBalance() QVERIFY(m->totalBalance("A000005").isZero()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneyFileTest::testSetAccountName() { MyMoneyFileTransaction ft; clearObjectLists(); try { m->setAccountName(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Liability), "Verbindlichkeiten"); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Liability"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } ft.restart(); clearObjectLists(); try { m->setAccountName(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Asset), QString::fromUtf8("Vermögen")); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } ft.restart(); clearObjectLists(); try { m->setAccountName(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Expense), "Ausgaben"); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Expense"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } ft.restart(); clearObjectLists(); try { m->setAccountName(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Income), "Einnahmen"); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Income"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } ft.restart(); QCOMPARE(m->liability().name(), QLatin1String("Verbindlichkeiten")); QCOMPARE(m->asset().name(), QString::fromUtf8("Vermögen")); QCOMPARE(m->expense().name(), QLatin1String("Ausgaben")); QCOMPARE(m->income().name(), QLatin1String("Einnahmen")); try { m->setAccountName("A000001", "New account name"); ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneyFileTest::testAddPayee() { MyMoneyPayee p; p.setName("THB"); QCOMPARE(m->dirty(), false); MyMoneyFileTransaction ft; try { m->addPayee(p); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(p.id(), QLatin1String("P000001")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsAdded.contains(QLatin1String("P000001"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyFileTest::testModifyPayee() { MyMoneyPayee p; testAddPayee(); clearObjectLists(); p = m->payee("P000001"); p.setName("New name"); MyMoneyFileTransaction ft; try { m->modifyPayee(p); ft.commit(); p = m->payee("P000001"); QCOMPARE(p.name(), QLatin1String("New name")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("P000001"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyFileTest::testRemovePayee() { MyMoneyPayee p; testAddPayee(); clearObjectLists(); QCOMPARE(m->payeeList().count(), 1); p = m->payee("P000001"); MyMoneyFileTransaction ft; try { m->removePayee(p); ft.commit(); QCOMPARE(m->payeeList().count(), 0); QCOMPARE(m_objectsRemoved.count(), 1); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsRemoved.contains(QLatin1String("P000001"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyFileTest::testPayeeWithIdentifier() { MyMoneyPayee p; try { MyMoneyFileTransaction ft; m->addPayee(p); ft.commit(); p = m->payee(p.id()); payeeIdentifier ident = payeeIdentifier(new payeeIdentifiers::ibanBic()); payeeIdentifierTyped iban(ident); iban->setIban(QLatin1String("DE82 2007 0024 0066 6446 00")); ft.restart(); p.addPayeeIdentifier(iban); m->modifyPayee(p); ft.commit(); p = m->payee(p.id()); QCOMPARE(p.payeeIdentifiers().count(), 1); ident = p.payeeIdentifiers().first(); try { iban = payeeIdentifierTyped(ident); } catch (...) { QFAIL("Unexpected exception"); } QCOMPARE(iban->electronicIban(), QLatin1String("DE82200700240066644600")); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testAddTransactionStd() { testAddAccounts(); MyMoneyTransaction t, p; MyMoneyAccount a; a = m->account("A000001"); // construct a transaction and add it to the pool t.setPostDate(QDate(2002, 2, 1)); t.setMemo("Memotext"); MyMoneySplit split1; MyMoneySplit split2; split1.setAccountId("A000001"); split1.setShares(MyMoneyMoney(-1000, 100)); split1.setValue(MyMoneyMoney(-1000, 100)); split2.setAccountId(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Expense)); split2.setValue(MyMoneyMoney(1000, 100)); split2.setShares(MyMoneyMoney(1000, 100)); try { t.addSplit(split1); t.addSplit(split2); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } /* // FIXME: we don't have a payee and a number field right now // guess we should have a number field per split, don't know // about the payee t.setMethod(MyMoneyCheckingTransaction::Withdrawal); t.setPayee("Thomas Baumgart"); t.setNumber("1234"); t.setState(MyMoneyCheckingTransaction::Cleared); */ storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; try { m->addTransaction(t); ft.commit(); QFAIL("Missing expected exception!"); } catch (const MyMoneyException &) { } QCOMPARE(m->dirty(), false); } void MyMoneyFileTest::testAttachStorage() { MyMoneyStorageMgr *store = new MyMoneyStorageMgr; MyMoneyFile *file = new MyMoneyFile; QCOMPARE(file->storageAttached(), false); try { file->attachStorage(store); QCOMPARE(file->storageAttached(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } try { file->attachStorage(store); QFAIL("Exception expected!"); } catch (const MyMoneyException &) { } try { file->attachStorage(0); QFAIL("Exception expected!"); } catch (const MyMoneyException &) { } try { file->detachStorage(store); QCOMPARE(file->storageAttached(), false); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } delete store; delete file; } void MyMoneyFileTest::testAccount2Category() { testReparentAccount(); QCOMPARE(m->accountToCategory("A000001"), QLatin1String("Account2:Account1")); QCOMPARE(m->accountToCategory("A000002"), QLatin1String("Account2")); } void MyMoneyFileTest::testCategory2Account() { testAddTransaction(); MyMoneyAccount a = m->account("A000003"); MyMoneyAccount b = m->account("A000004"); MyMoneyFileTransaction ft; try { m->reparentAccount(b, a); ft.commit(); QCOMPARE(m->categoryToAccount("Expense1"), QLatin1String("A000003")); QCOMPARE(m->categoryToAccount("Expense1:Expense2"), QLatin1String("A000004")); QVERIFY(m->categoryToAccount("Acc2").isEmpty()); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testAttachedStorage() { QCOMPARE(m->storageAttached(), true); QVERIFY(m->storage() != 0); MyMoneyStorageMgr *p = m->storage(); m->detachStorage(p); QCOMPARE(m->storageAttached(), false); QCOMPARE(m->storage(), static_cast(0)); m->attachStorage(p); QCOMPARE(m->storageAttached(), true); QVERIFY(m->storage() != 0); } void MyMoneyFileTest::testHasAccount() { testAddAccounts(); MyMoneyAccount a, b; a.setAccountType(eMyMoney::Account::Type::Checkings); a.setName("Account3"); b = m->account("A000001"); MyMoneyFileTransaction ft; try { m->addAccount(a, b); ft.commit(); QCOMPARE(m->accountCount(), static_cast(8)); QCOMPARE(a.parentAccountId(), QLatin1String("A000001")); QCOMPARE(m->hasAccount("A000001", "Account3"), true); QCOMPARE(m->hasAccount("A000001", "Account2"), false); QCOMPARE(m->hasAccount("A000002", "Account3"), false); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testAddEquityAccount() { MyMoneyAccount i; i.setName("Investment"); i.setAccountType(eMyMoney::Account::Type::Investment); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->asset(); m->addAccount(i, parent); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } // keep a copy for later use m_inv = i; // make sure, that only equity accounts can be children to it MyMoneyAccount a; a.setName("Testaccount"); QList list; list << eMyMoney::Account::Type::Checkings; list << eMyMoney::Account::Type::Savings; list << eMyMoney::Account::Type::Cash; list << eMyMoney::Account::Type::CreditCard; list << eMyMoney::Account::Type::Loan; list << eMyMoney::Account::Type::CertificateDep; list << eMyMoney::Account::Type::Investment; list << eMyMoney::Account::Type::MoneyMarket; list << eMyMoney::Account::Type::Asset; list << eMyMoney::Account::Type::Liability; list << eMyMoney::Account::Type::Currency; list << eMyMoney::Account::Type::Income; list << eMyMoney::Account::Type::Expense; list << eMyMoney::Account::Type::AssetLoan; QList::Iterator it; for (it = list.begin(); it != list.end(); ++it) { a.setAccountType(*it); ft.restart(); try { char msg[100]; m->addAccount(a, i); sprintf(msg, "Can add non-equity type %d to investment", (int)*it); QFAIL(msg); } catch (const MyMoneyException &) { ft.commit(); } } ft.restart(); try { a.setName("Teststock"); a.setAccountType(eMyMoney::Account::Type::Stock); m->addAccount(a, i); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testReparentEquity() { testAddEquityAccount(); testAddEquityAccount(); MyMoneyAccount parent; // check the bad cases QList list; list << eMyMoney::Account::Type::Checkings; list << eMyMoney::Account::Type::Savings; list << eMyMoney::Account::Type::Cash; list << eMyMoney::Account::Type::CertificateDep; list << eMyMoney::Account::Type::MoneyMarket; list << eMyMoney::Account::Type::Asset; list << eMyMoney::Account::Type::AssetLoan; list << eMyMoney::Account::Type::Currency; parent = m->asset(); testReparentEquity(list, parent); list.clear(); list << eMyMoney::Account::Type::CreditCard; list << eMyMoney::Account::Type::Loan; list << eMyMoney::Account::Type::Liability; parent = m->liability(); testReparentEquity(list, parent); list.clear(); list << eMyMoney::Account::Type::Income; parent = m->income(); testReparentEquity(list, parent); list.clear(); list << eMyMoney::Account::Type::Expense; parent = m->expense(); testReparentEquity(list, parent); // now check the good case MyMoneyAccount stock = m->account("A000002"); MyMoneyAccount inv = m->account(m_inv.id()); MyMoneyFileTransaction ft; try { m->reparentAccount(stock, inv); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testReparentEquity(QList& list, MyMoneyAccount& parent) { MyMoneyAccount a; MyMoneyAccount stock = m->account("A000002"); QList::Iterator it; MyMoneyFileTransaction ft; for (it = list.begin(); it != list.end(); ++it) { a.setName(QString("Testaccount %1").arg((int)*it)); a.setAccountType(*it); try { m->addAccount(a, parent); char msg[100]; m->reparentAccount(stock, a); sprintf(msg, "Can reparent stock to non-investment type %d account", (int)*it); QFAIL(msg); } catch (const MyMoneyException &) { ft.commit(); } ft.restart(); } } void MyMoneyFileTest::testBaseCurrency() { MyMoneySecurity base("EUR", "Euro", QChar(0x20ac)); MyMoneySecurity ref; // make sure, no base currency is set try { ref = m->baseCurrency(); QVERIFY(ref.id().isEmpty()); } catch (const MyMoneyException &e) { unexpectedException(e); } // make sure, we cannot assign an unknown currency try { m->setBaseCurrency(base); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } MyMoneyFileTransaction ft; // add the currency and try again try { m->addCurrency(base); m->setBaseCurrency(base); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } ft.restart(); // make sure, the base currency is set try { ref = m->baseCurrency(); QCOMPARE(ref.id(), QLatin1String("EUR")); QCOMPARE(ref.name(), QLatin1String("Euro")); QVERIFY(ref.tradingSymbol() == QChar(0x20ac)); } catch (const MyMoneyException &e) { unexpectedException(e); } // check if it gets reset when attaching a new storage m->detachStorage(storage); MyMoneyStorageMgr* newStorage = new MyMoneyStorageMgr; m->attachStorage(newStorage); ref = m->baseCurrency(); QVERIFY(ref.id().isEmpty()); m->detachStorage(newStorage); delete newStorage; m->attachStorage(storage); ref = m->baseCurrency(); QCOMPARE(ref.id(), QLatin1String("EUR")); QCOMPARE(ref.name(), QLatin1String("Euro")); QVERIFY(ref.tradingSymbol() == QChar(0x20ac)); } void MyMoneyFileTest::testOpeningBalanceNoBase() { MyMoneyAccount openingAcc; MyMoneySecurity base; try { base = m->baseCurrency(); openingAcc = m->openingBalanceAccount(base); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } } void MyMoneyFileTest::testOpeningBalance() { MyMoneyAccount openingAcc; MyMoneySecurity second("USD", "US Dollar", "$"); testBaseCurrency(); try { openingAcc = m->openingBalanceAccount(m->baseCurrency()); QCOMPARE(openingAcc.parentAccountId(), m->equity().id()); QCOMPARE(openingAcc.name(), MyMoneyFile::openingBalancesPrefix()); QCOMPARE(openingAcc.openingDate(), QDate::currentDate()); } catch (const MyMoneyException &e) { unexpectedException(e); } // add a second currency MyMoneyFileTransaction ft; try { m->addCurrency(second); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } QString refName = QString("%1 (%2)").arg(MyMoneyFile::openingBalancesPrefix()).arg("USD"); try { openingAcc = m->openingBalanceAccount(second); QCOMPARE(openingAcc.parentAccountId(), m->equity().id()); QCOMPARE(openingAcc.name(), refName); QCOMPARE(openingAcc.openingDate(), QDate::currentDate()); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testModifyStdAccount() { QVERIFY(m->asset().currencyId().isEmpty()); QCOMPARE(m->asset().name(), QLatin1String("Asset")); testBaseCurrency(); QVERIFY(m->asset().currencyId().isEmpty()); QVERIFY(!m->baseCurrency().id().isEmpty()); MyMoneyFileTransaction ft; try { MyMoneyAccount acc = m->asset(); acc.setName("Anlagen"); acc.setCurrencyId(m->baseCurrency().id()); m->modifyAccount(acc); ft.commit(); QCOMPARE(m->asset().name(), QLatin1String("Anlagen")); QCOMPARE(m->asset().currencyId(), m->baseCurrency().id()); } catch (const MyMoneyException &e) { unexpectedException(e); } ft.restart(); try { MyMoneyAccount acc = m->asset(); acc.setNumber("Test"); m->modifyAccount(acc); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { ft.rollback(); } } void MyMoneyFileTest::testAddPrice() { testAddAccounts(); testBaseCurrency(); MyMoneyAccount p; MyMoneyFileTransaction ft; try { p = m->account("A000002"); p.setCurrencyId("RON"); m->modifyAccount(p); ft.commit(); QCOMPARE(m->account("A000002").currencyId(), QLatin1String("RON")); } catch (const MyMoneyException &e) { unexpectedException(e); } clearObjectLists(); ft.restart(); MyMoneyPrice price("EUR", "RON", QDate::currentDate(), MyMoneyMoney(4.1), "Test source"); m->addPrice(price); ft.commit(); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 1); QCOMPARE(m_valueChanged.count("A000002"), 1); clearObjectLists(); ft.restart(); MyMoneyPrice priceReciprocal("RON", "EUR", QDate::currentDate(), MyMoneyMoney(1 / 4.1), "Test source reciprocal price"); m->addPrice(priceReciprocal); ft.commit(); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 1); QCOMPARE(m_valueChanged.count("A000002"), 1); } void MyMoneyFileTest::testRemovePrice() { testAddPrice(); clearObjectLists(); MyMoneyFileTransaction ft; MyMoneyPrice price("EUR", "RON", QDate::currentDate(), MyMoneyMoney(4.1), "Test source"); m->removePrice(price); ft.commit(); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 1); QCOMPARE(m_valueChanged.count("A000002"), 1); } void MyMoneyFileTest::testGetPrice() { testAddPrice(); // the price for the current date is found QVERIFY(m->price("EUR", "RON", QDate::currentDate()).isValid()); // the price for the current date is returned when asking for the next day with exact date set to false { const MyMoneyPrice &price = m->price("EUR", "RON", QDate::currentDate().addDays(1), false); QVERIFY(price.isValid() && price.date() == QDate::currentDate()); } // no price is returned while asking for the next day with exact date set to true QVERIFY(!m->price("EUR", "RON", QDate::currentDate().addDays(1), true).isValid()); // no price is returned while asking for the previous day with exact date set to true/false because all prices are newer QVERIFY(!m->price("EUR", "RON", QDate::currentDate().addDays(-1), false).isValid()); QVERIFY(!m->price("EUR", "RON", QDate::currentDate().addDays(-1), true).isValid()); // add two more prices MyMoneyFileTransaction ft; m->addPrice(MyMoneyPrice("EUR", "RON", QDate::currentDate().addDays(3), MyMoneyMoney(4.1), "Test source")); m->addPrice(MyMoneyPrice("EUR", "RON", QDate::currentDate().addDays(5), MyMoneyMoney(4.1), "Test source")); ft.commit(); clearObjectLists(); // extra tests for the exactDate=false behavior { const MyMoneyPrice &price = m->price("EUR", "RON", QDate::currentDate().addDays(2), false); QVERIFY(price.isValid() && price.date() == QDate::currentDate()); } { const MyMoneyPrice &price = m->price("EUR", "RON", QDate::currentDate().addDays(3), false); QVERIFY(price.isValid() && price.date() == QDate::currentDate().addDays(3)); } { const MyMoneyPrice &price = m->price("EUR", "RON", QDate::currentDate().addDays(4), false); QVERIFY(price.isValid() && price.date() == QDate::currentDate().addDays(3)); } { const MyMoneyPrice &price = m->price("EUR", "RON", QDate::currentDate().addDays(5), false); QVERIFY(price.isValid() && price.date() == QDate::currentDate().addDays(5)); } { const MyMoneyPrice &price = m->price("EUR", "RON", QDate::currentDate().addDays(6), false); QVERIFY(price.isValid() && price.date() == QDate::currentDate().addDays(5)); } } void MyMoneyFileTest::testAddAccountMissingCurrency() { testAddTwoInstitutions(); MyMoneySecurity base("EUR", "Euro", QChar(0x20ac)); MyMoneyAccount a; a.setAccountType(eMyMoney::Account::Type::Checkings); MyMoneyInstitution institution; storage->d_func()->m_dirty = false; QCOMPARE(m->accountCount(), static_cast(5)); institution = m->institution("I000001"); QCOMPARE(institution.id(), QLatin1String("I000001")); a.setName("Account1"); a.setInstitutionId(institution.id()); clearObjectLists(); MyMoneyFileTransaction ft; try { m->addCurrency(base); m->setBaseCurrency(base); MyMoneyAccount parent = m->asset(); m->addAccount(a, parent); ft.commit(); QCOMPARE(m->account("A000001").currencyId(), QLatin1String("EUR")); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testAddTransactionToClosedAccount() { QSKIP("Test not implemented yet", SkipAll); } void MyMoneyFileTest::testRemoveTransactionFromClosedAccount() { QSKIP("Test not implemented yet", SkipAll); } void MyMoneyFileTest::testModifyTransactionInClosedAccount() { QSKIP("Test not implemented yet", SkipAll); } void MyMoneyFileTest::testStorageId() { QString id; // make sure id will be setup if it does not exist MyMoneyFileTransaction ft; try { m->setValue("kmm-id", ""); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } try { // check for a new id id = m->storageId(); QVERIFY(!id.isEmpty()); // check that it is the same if we ask again QCOMPARE(id, m->storageId()); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testHasMatchingOnlineBalance_emptyAccountWithoutImportedBalance() { AddOneAccount(); MyMoneyAccount a = m->account("A000001"); QCOMPARE(m->hasMatchingOnlineBalance(a), false); } void MyMoneyFileTest::testHasMatchingOnlineBalance_emptyAccountWithEqualImportedBalance() { AddOneAccount(); MyMoneyAccount a = m->account("A000001"); a.setValue("lastImportedTransactionDate", QDate(2011, 12, 1).toString(Qt::ISODate)); a.setValue("lastStatementBalance", MyMoneyMoney().toString()); MyMoneyFileTransaction ft; m->modifyAccount(a); ft.commit(); QCOMPARE(m->hasMatchingOnlineBalance(a), true); } void MyMoneyFileTest::testHasMatchingOnlineBalance_emptyAccountWithUnequalImportedBalance() { AddOneAccount(); MyMoneyAccount a = m->account("A000001"); a.setValue("lastImportedTransactionDate", QDate(2011, 12, 1).toString(Qt::ISODate)); a.setValue("lastStatementBalance", MyMoneyMoney::ONE.toString()); MyMoneyFileTransaction ft; m->modifyAccount(a); ft.commit(); QCOMPARE(m->hasMatchingOnlineBalance(a), false); } void MyMoneyFileTest::testHasNewerTransaction_withoutAnyTransaction_afterLastImportedTransaction() { AddOneAccount(); MyMoneyAccount a = m->account("A000001"); QDate dateOfLastTransactionImport(2011, 12, 1); // There are no transactions at all: QCOMPARE(m->hasNewerTransaction(a.id(), dateOfLastTransactionImport), false); } void MyMoneyFileTest::testHasNewerTransaction_withoutNewerTransaction_afterLastImportedTransaction() { AddOneAccount(); QString accId("A000001"); QDate dateOfLastTransactionImport(2011, 12, 1); MyMoneyFileTransaction ft; MyMoneyTransaction t; // construct a transaction at the day of the last transaction import and add it to the pool t.setPostDate(dateOfLastTransactionImport); MyMoneySplit split1; split1.setAccountId(accId); split1.setShares(MyMoneyMoney(-1000, 100)); split1.setValue(MyMoneyMoney(-1000, 100)); t.addSplit(split1); ft.restart(); m->addTransaction(t); ft.commit(); QCOMPARE(m->hasNewerTransaction(accId, dateOfLastTransactionImport), false); } void MyMoneyFileTest::testHasNewerTransaction_withNewerTransaction_afterLastImportedTransaction() { AddOneAccount(); QString accId("A000001"); QDate dateOfLastTransactionImport(2011, 12, 1); QDate dateOfDayAfterLastTransactionImport(dateOfLastTransactionImport.addDays(1)); MyMoneyFileTransaction ft; MyMoneyTransaction t; // construct a transaction a day after the last transaction import and add it to the pool t.setPostDate(dateOfDayAfterLastTransactionImport); MyMoneySplit split1; split1.setAccountId(accId); split1.setShares(MyMoneyMoney(-1000, 100)); split1.setValue(MyMoneyMoney(-1000, 100)); t.addSplit(split1); ft.restart(); m->addTransaction(t); ft.commit(); QCOMPARE(m->hasNewerTransaction(accId, dateOfLastTransactionImport), true); } void MyMoneyFileTest::AddOneAccount() { QString accountId = "A000001"; MyMoneyAccount a; a.setAccountType(eMyMoney::Account::Type::Checkings); storage->d_func()->m_dirty = false; QCOMPARE(m->accountCount(), static_cast(5)); a.setName("Account1"); a.setCurrencyId("EUR"); clearObjectLists(); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->asset(); m->addAccount(a, parent); ft.commit(); QCOMPARE(m->accountCount(), static_cast(6)); QCOMPARE(a.parentAccountId(), QLatin1String("AStd::Asset")); QCOMPARE(a.id(), accountId); QCOMPARE(a.currencyId(), QLatin1String("EUR")); QCOMPARE(m->dirty(), true); QCOMPARE(m->asset().accountList().count(), 1); QCOMPARE(m->asset().accountList()[0], accountId); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsAdded.contains(accountId.toLatin1())); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testCountTransactionsWithSpecificReconciliationState_noTransactions() { AddOneAccount(); QString accountId = "A000001"; QCOMPARE(m->countTransactionsWithSpecificReconciliationState(accountId, eMyMoney::TransactionFilter::State::NotReconciled), 0); } void MyMoneyFileTest::testCountTransactionsWithSpecificReconciliationState_transactionWithWantedReconcileState() { AddOneAccount(); QString accountId = "A000001"; // construct split & transaction MyMoneySplit split; split.setAccountId(accountId); split.setShares(MyMoneyMoney(-1000, 100)); split.setValue(MyMoneyMoney(-1000, 100)); MyMoneyTransaction transaction; transaction.setPostDate(QDate(2013, 1, 1)); transaction.addSplit(split); // add transaction MyMoneyFileTransaction ft; m->addTransaction(transaction); ft.commit(); QCOMPARE(m->countTransactionsWithSpecificReconciliationState(accountId, eMyMoney::TransactionFilter::State::NotReconciled), 1); } void MyMoneyFileTest::testCountTransactionsWithSpecificReconciliationState_transactionWithUnwantedReconcileState() { AddOneAccount(); QString accountId = "A000001"; // construct split & transaction MyMoneySplit split; split.setAccountId(accountId); split.setShares(MyMoneyMoney(-1000, 100)); split.setValue(MyMoneyMoney(-1000, 100)); split.setReconcileFlag(eMyMoney::Split::State::Reconciled); MyMoneyTransaction transaction; transaction.setPostDate(QDate(2013, 1, 1)); transaction.addSplit(split); // add transaction MyMoneyFileTransaction ft; m->addTransaction(transaction); ft.commit(); QCOMPARE(m->countTransactionsWithSpecificReconciliationState(accountId, eMyMoney::TransactionFilter::State::NotReconciled), 0); } void MyMoneyFileTest::testAddOnlineJob() { QSKIP("Need dummy task for this test", SkipAll); #if 0 // Add a onlineJob onlineJob job(new germanOnlineTransfer()); MyMoneyFileTransaction ft; m->addOnlineJob(job); QCOMPARE(job.id(), QString("O000001")); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); #endif } void MyMoneyFileTest::testGetOnlineJob() { QSKIP("Need dummy task for this test", SkipAll); #if 0 testAddOnlineJob(); const onlineJob requestedJob = m->getOnlineJob("O000001"); QVERIFY(!requestedJob.isNull()); QCOMPARE(requestedJob.id(), QString("O000001")); #endif } void MyMoneyFileTest::testRemoveOnlineJob() { QSKIP("Need dummy task for this test", SkipAll); #if 0 // Add a onlineJob onlineJob job(new germanOnlineTransfer()); onlineJob job2(new germanOnlineTransfer()); onlineJob job3(new germanOnlineTransfer()); MyMoneyFileTransaction ft; m->addOnlineJob(job); m->addOnlineJob(job2); m->addOnlineJob(job3); ft.commit(); clearObjectLists(); ft.restart(); m->removeOnlineJob(job); m->removeOnlineJob(job2); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 2); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); #endif } void MyMoneyFileTest::testOnlineJobRollback() { QSKIP("Need dummy task for this test", SkipAll); #if 0 // Add a onlineJob onlineJob job(new germanOnlineTransfer()); onlineJob job2(new germanOnlineTransfer()); onlineJob job3(new germanOnlineTransfer()); MyMoneyFileTransaction ft; m->addOnlineJob(job); m->addOnlineJob(job2); m->addOnlineJob(job3); ft.rollback(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); #endif } void MyMoneyFileTest::testRemoveLockedOnlineJob() { QSKIP("Need dummy task for this test", SkipAll); #if 0 // Add a onlineJob onlineJob job(new germanOnlineTransfer()); job.setLock(true); QVERIFY(job.isLocked()); MyMoneyFileTransaction ft; m->addOnlineJob(job); ft.commit(); clearObjectLists(); // Try removing locked transfer ft.restart(); m->removeOnlineJob(job); ft.commit(); QVERIFY2(m_objectsRemoved.count() == 0, "Online Job was locked, removing is not allowed"); QVERIFY(m_objectsAdded.count() == 0); QVERIFY(m_objectsModified.count() == 0); QVERIFY(m_balanceChanged.count() == 0); QVERIFY(m_valueChanged.count() == 0); #endif } /** @todo */ void MyMoneyFileTest::testModifyOnlineJob() { QSKIP("Need dummy task for this test", SkipAll); #if 0 // Add a onlineJob onlineJob job(new germanOnlineTransfer()); MyMoneyFileTransaction ft; m->addOnlineJob(job); ft.commit(); clearObjectLists(); // Modify online job job.setJobSend(); ft.restart(); m->modifyOnlineJob(job); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); //onlineJob modifiedJob = m->getOnlineJob( job.id() ); //QCOMPARE(modifiedJob.responsibleAccount(), QString("Std::Assert")); #endif } void MyMoneyFileTest::testClearedBalance() { testAddTransaction(); MyMoneyTransaction t1; MyMoneyTransaction t2; // construct a transaction and add it to the pool t1.setPostDate(QDate(2002, 2, 1)); t1.setMemo("Memotext"); t2.setPostDate(QDate(2002, 2, 4)); t2.setMemo("Memotext"); MyMoneySplit split1; MyMoneySplit split2; MyMoneySplit split3; MyMoneySplit split4; MyMoneyFileTransaction ft; try { split1.setAccountId("A000002"); split1.setShares(MyMoneyMoney(-1000, 100)); split1.setValue(MyMoneyMoney(-1000, 100)); split1.setReconcileFlag(eMyMoney::Split::State::Cleared); split2.setAccountId("A000004"); split2.setValue(MyMoneyMoney(1000, 100)); split2.setShares(MyMoneyMoney(1000, 100)); split2.setReconcileFlag(eMyMoney::Split::State::Cleared); t1.addSplit(split1); t1.addSplit(split2); m->addTransaction(t1); ft.commit(); ft.restart(); QCOMPARE(t1.id(), QLatin1String("T000000000000000002")); split3.setAccountId("A000002"); split3.setShares(MyMoneyMoney(-2000, 100)); split3.setValue(MyMoneyMoney(-2000, 100)); split3.setReconcileFlag(eMyMoney::Split::State::Cleared); split4.setAccountId("A000004"); split4.setValue(MyMoneyMoney(2000, 100)); split4.setShares(MyMoneyMoney(2000, 100)); split4.setReconcileFlag(eMyMoney::Split::State::Cleared); t2.addSplit(split3); t2.addSplit(split4); m->addTransaction(t2); ft.commit(); ft.restart(); QCOMPARE(m->balance("A000001", QDate(2002, 2, 4)), MyMoneyMoney(-1000, 100)); QCOMPARE(m->balance("A000002", QDate(2002, 2, 4)), MyMoneyMoney(-3000, 100)); // Date of last cleared transaction QCOMPARE(m->clearedBalance("A000002", QDate(2002, 2, 1)), MyMoneyMoney(-1000, 100)); // Date of last transaction QCOMPARE(m->balance("A000002", QDate(2002, 2, 4)), MyMoneyMoney(-3000, 100)); // Date before first transaction QVERIFY(m->clearedBalance("A000002", QDate(2002, 1, 15)).isZero()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testAdjustedValues() { // create a checking account, an expeense, an investment account and a stock AddOneAccount(); MyMoneyAccount exp1; exp1.setAccountType(eMyMoney::Account::Type::Expense); exp1.setName("Expense1"); exp1.setCurrencyId("EUR"); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->expense(); m->addAccount(exp1, parent); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } testAddEquityAccount(); testBaseCurrency(); MyMoneySecurity stockSecurity(QLatin1String("Blubber"), QLatin1String("TestsockSecurity"), QLatin1String("BLUB"), 1000, 1000, 1000); stockSecurity.setTradingCurrency(QLatin1String("BLUB")); // add the security ft.restart(); try { m->addSecurity(stockSecurity); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } MyMoneyAccount i = m->accountByName("Investment"); MyMoneyAccount stock; ft.restart(); try { stock.setName("Teststock"); stock.setCurrencyId(stockSecurity.id()); stock.setAccountType(eMyMoney::Account::Type::Stock); m->addAccount(stock, i); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } // values taken from real example on https://bugs.kde.org/show_bug.cgi?id=345655 MyMoneySplit s1, s2, s3; s1.setAccountId(QLatin1String("A000001")); s1.setShares(MyMoneyMoney(QLatin1String("-99901/1000"))); s1.setValue(MyMoneyMoney(QLatin1String("-999/10"))); s2.setAccountId(exp1.id()); s2.setShares(MyMoneyMoney(QLatin1String("-611/250"))); s2.setValue(MyMoneyMoney(QLatin1String("-61/25"))); s3.setAccountId(stock.id()); s3.setAction(eMyMoney::Split::InvestmentTransactionType::BuyShares); s3.setShares(MyMoneyMoney(QLatin1String("64901/100000"))); s3.setPrice(MyMoneyMoney(QLatin1String("157689/1000"))); s3.setValue(MyMoneyMoney(QLatin1String("102340161/1000000"))); MyMoneyTransaction t; t.setCommodity(QLatin1String("EUR")); t.setPostDate(QDate::currentDate()); t.addSplit(s1); t.addSplit(s2); t.addSplit(s3); // make sure the split sum is not zero QVERIFY(!t.splitSum().isZero()); ft.restart(); try { m->addTransaction(t); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } QCOMPARE(t.splitById(s1.id()).shares(), MyMoneyMoney(QLatin1String("-999/10"))); QCOMPARE(t.splitById(s1.id()).value(), MyMoneyMoney(QLatin1String("-999/10"))); QCOMPARE(t.splitById(s2.id()).shares(), MyMoneyMoney(QLatin1String("-61/25"))); QCOMPARE(t.splitById(s2.id()).value(), MyMoneyMoney(QLatin1String("-61/25"))); QCOMPARE(t.splitById(s3.id()).shares(), MyMoneyMoney(QLatin1String("649/1000"))); QCOMPARE(t.splitById(s3.id()).value(), MyMoneyMoney(QLatin1String("10234/100"))); QCOMPARE(t.splitById(s3.id()).price(), MyMoneyMoney(QLatin1String("157689/1000"))); QCOMPARE(t.splitSum(), MyMoneyMoney()); // now reset and check if modify also works s1.setShares(MyMoneyMoney(QLatin1String("-999/10"))); s1.setValue(MyMoneyMoney(QLatin1String("-999/10"))); s2.setShares(MyMoneyMoney(QLatin1String("-61/25"))); s2.setValue(MyMoneyMoney(QLatin1String("-61/25"))); s3.setShares(MyMoneyMoney(QLatin1String("649/1000"))); s3.setPrice(MyMoneyMoney(QLatin1String("157689/1000"))); s3.setValue(MyMoneyMoney(QLatin1String("102340161/1000000"))); t.modifySplit(s1); t.modifySplit(s2); t.modifySplit(s3); // make sure the split sum is not zero QVERIFY(!t.splitSum().isZero()); ft.restart(); try { m->modifyTransaction(t); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // we need to get the transaction from the engine, as modifyTransaction does // not return the modified values MyMoneyTransaction t2 = m->transaction(t.id()); QCOMPARE(t2.splitById(s3.id()).shares(), MyMoneyMoney(QLatin1String("649/1000"))); QCOMPARE(t2.splitById(s3.id()).value(), MyMoneyMoney(QLatin1String("10234/100"))); QCOMPARE(t2.splitById(s3.id()).price(), MyMoneyMoney(QLatin1String("157689/1000"))); QCOMPARE(t2.splitSum(), MyMoneyMoney()); } void MyMoneyFileTest::testVatAssignment() { MyMoneyAccount acc; MyMoneyAccount vat; MyMoneyAccount expense; testAddTransaction(); vat.setName("VAT"); vat.setCurrencyId("EUR"); vat.setAccountType(eMyMoney::Account::Type::Expense); // make it a VAT account vat.setValue(QLatin1String("VatRate"), QLatin1String("20/100")); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->expense(); m->addAccount(vat, parent); QVERIFY(!vat.id().isEmpty()); acc = m->account(QLatin1String("A000001")); expense = m->account(QLatin1String("A000003")); QCOMPARE(acc.name(), QLatin1String("Account1")); QCOMPARE(expense.name(), QLatin1String("Expense1")); expense.setValue(QLatin1String("VatAccount"), vat.id()); m->modifyAccount(expense); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // the categories are setup now for gross value entry MyMoneyTransaction tr; MyMoneySplit sp; MyMoneyMoney amount(1707, 100); // setup the transaction sp.setShares(amount); sp.setValue(amount); sp.setAccountId(acc.id()); tr.addSplit(sp); sp.clearId(); sp.setShares(-amount); sp.setValue(-amount); sp.setAccountId(expense.id()); tr.addSplit(sp); QCOMPARE(m->addVATSplit(tr, acc, expense, amount), true); QCOMPARE(tr.splits().count(), 3); QCOMPARE(tr.splitByAccount(acc.id()).shares().toString(), MyMoneyMoney(1707, 100).toString()); QCOMPARE(tr.splitByAccount(expense.id()).shares().toString(), MyMoneyMoney(-1422, 100).toString()); QCOMPARE(tr.splitByAccount(vat.id()).shares().toString(), MyMoneyMoney(-285, 100).toString()); QCOMPARE(tr.splitSum().toString(), MyMoneyMoney().toString()); tr.removeSplits(); ft.restart(); try { expense.setValue(QLatin1String("VatAmount"), QLatin1String("net")); m->modifyAccount(expense); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // the categories are setup now for net value entry amount = MyMoneyMoney(1422, 100); sp.clearId(); sp.setShares(amount); sp.setValue(amount); sp.setAccountId(acc.id()); tr.addSplit(sp); sp.clearId(); sp.setShares(-amount); sp.setValue(-amount); sp.setAccountId(expense.id()); tr.addSplit(sp); QCOMPARE(m->addVATSplit(tr, acc, expense, amount), true); QCOMPARE(tr.splits().count(), 3); QCOMPARE(tr.splitByAccount(acc.id()).shares().toString(), MyMoneyMoney(1706, 100).toString()); QCOMPARE(tr.splitByAccount(expense.id()).shares().toString(), MyMoneyMoney(-1422, 100).toString()); QCOMPARE(tr.splitByAccount(vat.id()).shares().toString(), MyMoneyMoney(-284, 100).toString()); QCOMPARE(tr.splitSum().toString(), MyMoneyMoney().toString()); } void MyMoneyFileTest::testEmptyFilter() { testAddTransaction(); try { QList > tList; MyMoneyTransactionFilter filter; MyMoneyFile::instance()->transactionList(tList, filter); QCOMPARE(tList.count(), 2); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testAddSecurity() { // create a checking account, an expeense, an investment account and a stock AddOneAccount(); MyMoneyAccount exp1; exp1.setAccountType(eMyMoney::Account::Type::Expense); exp1.setName("Expense1"); exp1.setCurrencyId("EUR"); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->expense(); m->addAccount(exp1, parent); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } testAddEquityAccount(); testBaseCurrency(); MyMoneySecurity stockSecurity(QLatin1String("Blubber"), QLatin1String("TestsockSecurity"), QLatin1String("BLUB"), 1000, 1000, 1000); stockSecurity.setTradingCurrency(QLatin1String("BLUB")); // add the security ft.restart(); try { m->addSecurity(stockSecurity); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } // check that we can get it via the security method try { MyMoneySecurity sec = m->security(stockSecurity.id()); } catch (const MyMoneyException &e) { unexpectedException(e); } // and also via the currency method try { MyMoneySecurity sec = m->currency(stockSecurity.id()); } catch (const MyMoneyException &e) { unexpectedException(e); } } diff --git a/kmymoney/payeeidentifier/CMakeLists.txt b/kmymoney/payeeidentifier/CMakeLists.txt deleted file mode 100644 index 9715c2273..000000000 --- a/kmymoney/payeeidentifier/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_subdirectory( ibanandbic ) -add_subdirectory( nationalaccount ) diff --git a/kmymoney/payeeidentifier/ibanandbic/CMakeLists.txt b/kmymoney/payeeidentifier/ibanandbic/CMakeLists.txt deleted file mode 100644 index 6e0b301ad..000000000 --- a/kmymoney/payeeidentifier/ibanandbic/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(widgets) diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/CMakeLists.txt b/kmymoney/payeeidentifier/ibanandbic/widgets/CMakeLists.txt deleted file mode 100644 index 8152c810c..000000000 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/CMakeLists.txt +++ /dev/null @@ -1,77 +0,0 @@ -## Shared library for widgets and modulue for delegate -# Note: all "real" code is in ..._widgets, ..._delegate is only used to load the shared library - -set ( IBAN_BIC_WIDGETS_SCRS - kibanlineedit.cpp - kbicedit.cpp - ibanvalidator.cpp - bicvalidator.cpp - ibanbicitemdelegate.cpp - ibanbicitemedit.cpp -) - -set( IBAN_BIC_WIDGETS_HEADERS - kibanlineedit.h - kbicedit.h - ibanvalidator.h - bicvalidator.h - ibanbicitemdelegate.h - ${CMAKE_CURRENT_BINARY_DIR}/payeeidentifier_iban_bic_widgets_export.h -) - -ki18n_wrap_ui(IBAN_BIC_WIDGETS_SCRS - ibanbicitemedit.ui -) - -add_library( payeeidentifier_iban_bic_widgets SHARED - ${IBAN_BIC_WIDGETS_SCRS} -) - -set_target_properties(payeeidentifier_iban_bic_widgets - PROPERTIES VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR} -) - -target_include_directories(payeeidentifier_iban_bic_widgets - PUBLIC ${CMAKE_CURRENT_BINARY_DIR} -) - -target_link_libraries( payeeidentifier_iban_bic_widgets PUBLIC - Qt5::Core - kmm_mymoney - kmm_widgets - kmm_plugin -) - -generate_export_header( payeeidentifier_iban_bic_widgets ) - -install(TARGETS payeeidentifier_iban_bic_widgets - ${INSTALL_TARGETS_DEFAULT_ARGS} -) - -## Delegate plugin -set (IBAN_BIC_UI_SCRS - pluginfactory.cpp -) - -add_library( payeeidentifier_iban_bic_delegates MODULE - ${IBAN_BIC_UI_SCRS} -) - -target_link_libraries( payeeidentifier_iban_bic_delegates - payeeidentifier_iban_bic_widgets - kmm_widgets - kmm_mymoney - Qt5::Core -) - -configure_file(kmymoney-ibanbic-delegate.desktop.in kmymoney-ibanbic-delegate.desktop) -kcoreaddons_desktop_to_json(payeeidentifier_iban_bic_widgets "${CMAKE_CURRENT_BINARY_DIR}/kmymoney-ibanbic-delegate.desktop") - -install(TARGETS payeeidentifier_iban_bic_delegates - DESTINATION "${KDE_INSTALL_PLUGINDIR}/kmymoney" -) - -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kmymoney-ibanbic-delegate.desktop - DESTINATION ${SERVICES_INSTALL_DIR} -) diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/kmymoney-ibanbic-delegate.desktop.in b/kmymoney/payeeidentifier/ibanandbic/widgets/kmymoney-ibanbic-delegate.desktop.in deleted file mode 100644 index ff68652a1..000000000 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/kmymoney-ibanbic-delegate.desktop.in +++ /dev/null @@ -1,19 +0,0 @@ -[Desktop Entry] -Type=Service -ServiceTypes=KMyMoney/PayeeIdentifierDelegate - -X-KMyMoney-payeeIdentifierIds=org.kmymoney.payeeIdentifier.ibanbic - -X-KDE-Library=kmymoney/payeeidentifier_iban_bic_delegates -X-KDE-PluginKeyword=ibanBicDelegate - -X-KDE-PluginInfo-Name=IBAN and BIC support -X-KDE-PluginInfo-Author=Christian David -X-KDE-PluginInfo-Email=christian-david@web.de -X-KDE-PluginInfo-Version=${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX} -X-KDE-PluginInfo-Website=https://kmymoney.org/plugins.html -X-KDE-PluginInfo-License=GPLv2+ -X-KDE-PluginInfo-EnabledByDefault=true - -Name=payeeidentifier_iban_bic_widgets -Comment=Plugin to store international bank account number and business identifier code diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/pluginfactory.cpp b/kmymoney/payeeidentifier/ibanandbic/widgets/pluginfactory.cpp deleted file mode 100644 index ef5297a14..000000000 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/pluginfactory.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian David - * Copyright (C) 2017 Łukasz Wojniłowicz - * - * 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 "ibanbicitemdelegate.h" - -K_PLUGIN_FACTORY(ibanAndBicPidDelegatesFactory, - registerPlugin("ibanBicDelegate"); - ) - -#include diff --git a/kmymoney/payeeidentifier/nationalaccount/CMakeLists.txt b/kmymoney/payeeidentifier/nationalaccount/CMakeLists.txt deleted file mode 100644 index 40fb9fed1..000000000 --- a/kmymoney/payeeidentifier/nationalaccount/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/kmymoney-nationalaccountnumberplugin.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/kmymoney-nationalaccountnumberplugin.desktop) - -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kmymoney-nationalaccountnumberplugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) - -add_subdirectory( ui ) diff --git a/kmymoney/payeeidentifier/nationalaccount/kmymoney-nationalaccountnumberplugin.desktop.in b/kmymoney/payeeidentifier/nationalaccount/kmymoney-nationalaccountnumberplugin.desktop.in deleted file mode 100644 index 245c17101..000000000 --- a/kmymoney/payeeidentifier/nationalaccount/kmymoney-nationalaccountnumberplugin.desktop.in +++ /dev/null @@ -1,10 +0,0 @@ -[Desktop Entry] -Type=ServiceType -X-KDE-ServiceType=KMyMoney/NationalAccountNumberPlugin -Name=A plugin which gives access to account number and bank code related information - -[PropertyDef::X-KMyMoney-PluginInfo-CountryCodes] -Type=QStringList - -[PropertyDef::X-KMyMoney-PluginInfo-BIC-CountryCodes] -Type=QStringList diff --git a/kmymoney/payeeidentifier/nationalaccount/ui/CMakeLists.txt b/kmymoney/payeeidentifier/nationalaccount/ui/CMakeLists.txt deleted file mode 100644 index fe207ef4f..000000000 --- a/kmymoney/payeeidentifier/nationalaccount/ui/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -configure_file(kmymoney-nationalaccount-delegate.desktop.in kmymoney-nationalaccount-delegate.desktop) - -set( PAYEEIDENTIFIER_NATIONALACCOUNT_UI_SCRS - nationalaccountedit.cpp - nationalaccountdelegate.cpp - pluginfactory.cpp -) - -ki18n_wrap_ui( - PAYEEIDENTIFIER_NATIONALACCOUNT_UI_SCRS - nationalaccountedit.ui -) - -add_library( payeeidentifier_nationalAccount_ui MODULE - ${PAYEEIDENTIFIER_NATIONALACCOUNT_UI_SCRS} -) - -target_link_libraries( payeeidentifier_nationalAccount_ui - Qt5::Core - kmm_widgets - kmm_mymoney -) - -install(TARGETS payeeidentifier_nationalAccount_ui - DESTINATION "${KDE_INSTALL_PLUGINDIR}/kmymoney") - -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kmymoney-nationalaccount-delegate.desktop - DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/kmymoney/payeeidentifier/nationalaccount/ui/kmymoney-nationalaccount-delegate.desktop.in b/kmymoney/payeeidentifier/nationalaccount/ui/kmymoney-nationalaccount-delegate.desktop.in deleted file mode 100644 index 57fd71017..000000000 --- a/kmymoney/payeeidentifier/nationalaccount/ui/kmymoney-nationalaccount-delegate.desktop.in +++ /dev/null @@ -1,19 +0,0 @@ -[Desktop Entry] -Type=Service -ServiceTypes=KMyMoney/PayeeIdentifierDelegate - -X-KMyMoney-payeeIdentifierIds=org.kmymoney.payeeIdentifier.national - -X-KDE-Library=kmymoney/payeeidentifier_nationalAccount_ui -X-KDE-PluginKeyword=delegate - -X-KDE-PluginInfo-Name=Support for national account numbers -X-KDE-PluginInfo-Author=Christian David -X-KDE-PluginInfo-Email=christian-david@web.de -X-KDE-PluginInfo-Version=${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX} -X-KDE-PluginInfo-Website=https://kmymoney.org/plugins.html -X-KDE-PluginInfo-License=GPLv2+ -X-KDE-PluginInfo-EnabledByDefault=true - -Name=payeeidentifier_nationalAccount_ui -Comment=Plugin to store national account numbers diff --git a/kmymoney/payeeidentifier/nationalaccount/ui/pluginfactory.cpp b/kmymoney/payeeidentifier/nationalaccount/ui/pluginfactory.cpp deleted file mode 100644 index 5b4be2575..000000000 --- a/kmymoney/payeeidentifier/nationalaccount/ui/pluginfactory.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian David - * Copyright (C) 2017 Łukasz Wojniłowicz - * - * 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 "nationalaccountdelegate.h" - -K_PLUGIN_FACTORY(ibanAndBicPidWidgetsFactory, - registerPlugin("delegate"); - ) - -#include diff --git a/kmymoney/plugins/onlinetasks/sepa/CMakeLists.txt b/kmymoney/plugins/onlinetasks/sepa/CMakeLists.txt index bba1bcccf..b98a8e83f 100644 --- a/kmymoney/plugins/onlinetasks/sepa/CMakeLists.txt +++ b/kmymoney/plugins/onlinetasks/sepa/CMakeLists.txt @@ -1,48 +1,47 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/kmymoney-sepaorders.json.in ${CMAKE_CURRENT_BINARY_DIR}/kmymoney-sepaorders.json @ONLY) set( sepaOnlineTasks_SRCS ui/sepacredittransferedit.cpp sepaonlinetasksloader.cpp ) ki18n_wrap_ui( sepaOnlineTasks_SRCS ui/sepacredittransferedit.ui ) add_library(konlinetasks_sepa_interface INTERFACE) kcoreaddons_add_plugin(konlinetasks_sepa SOURCES ${sepaOnlineTasks_SRCS} JSON "${CMAKE_CURRENT_BINARY_DIR}/kmymoney-sepaorders.json" INSTALL_NAMESPACE "kmymoney") #kcoreaddons_add_plugin sets LIBRARY_OUTPUT_DIRECTORY to ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${INSTALL_NAMESPACE} set_target_properties(konlinetasks_sepa PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") # TODO some dependencies can be moved to konlinetasks_sepa target_link_libraries( konlinetasks_sepa_interface INTERFACE kmm_mymoney kmm_widgets kmm_plugin onlinetask_interfaces Qt5::Xml Qt5::Core Qt5::Gui ) target_link_libraries(konlinetasks_sepa PUBLIC konlinetasks_sepa_interface PRIVATE KF5::I18n KF5::Service kmm_settings kmm_utils_validators - payeeidentifier_iban_bic_widgets KF5::ItemModels ) diff --git a/kmymoney/plugins/onlinetasks/sepa/ui/sepacredittransferedit.cpp b/kmymoney/plugins/onlinetasks/sepa/ui/sepacredittransferedit.cpp index 248189460..e113dcb4f 100644 --- a/kmymoney/plugins/onlinetasks/sepa/ui/sepacredittransferedit.cpp +++ b/kmymoney/plugins/onlinetasks/sepa/ui/sepacredittransferedit.cpp @@ -1,540 +1,540 @@ /* This file is part of KMyMoney, A Personal Finance Manager by KDE Copyright (C) 2013 Christian Dávid 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 "sepacredittransferedit.h" #include "ui_sepacredittransferedit.h" #include #include #include #include #include "kguiutils.h" #include "mymoney/payeeidentifiermodel.h" #include "onlinetasks/sepa/sepaonlinetransfer.h" -#include "payeeidentifier/ibanandbic/widgets/ibanvalidator.h" -#include "payeeidentifier/ibanandbic/widgets/bicvalidator.h" +#include "widgets/payeeidentifier/ibanbic/ibanvalidator.h" +#include "widgets/payeeidentifier/ibanbic/bicvalidator.h" #include "payeeidentifier/payeeidentifiertyped.h" #include "misc/charvalidator.h" #include "payeeidentifier/ibanbic/ibanbic.h" #include "styleditemdelegateforwarder.h" -#include "payeeidentifier/ibanandbic/widgets/ibanbicitemdelegate.h" +#include "widgets/payeeidentifier/ibanbic/ibanbicitemdelegate.h" #include "onlinejobtyped.h" #include "mymoneyaccount.h" #include "widgetenums.h" class ibanBicCompleterDelegate : public StyledItemDelegateForwarder { Q_OBJECT public: ibanBicCompleterDelegate(QObject *parent) : StyledItemDelegateForwarder(parent) {} protected: QAbstractItemDelegate* getItemDelegate(const QModelIndex &index) const final override { static QPointer defaultDelegate; static QPointer ibanBicDelegate; const bool ibanBicRequested = index.model()->data(index, payeeIdentifierModel::isPayeeIdentifier).toBool(); QAbstractItemDelegate* delegate = (ibanBicRequested) ? ibanBicDelegate : defaultDelegate; if (delegate == 0) { if (ibanBicRequested) { // Use this->parent() as parent because "this" is const ibanBicDelegate = new ibanBicItemDelegate(this->parent()); delegate = ibanBicDelegate; } else { // Use this->parent() as parent because "this" is const defaultDelegate = new QStyledItemDelegate(this->parent()); delegate = defaultDelegate; } connectSignals(delegate, Qt::UniqueConnection); } Q_CHECK_PTR(delegate); return delegate; } }; class payeeIdentifierCompleterPopup : public QTreeView { Q_OBJECT public: payeeIdentifierCompleterPopup(QWidget* parent = 0) : QTreeView(parent) { setRootIsDecorated(false); setAlternatingRowColors(true); setAnimated(true); setHeaderHidden(true); setUniformRowHeights(false); expandAll(); } }; class ibanBicFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: enum roles { payeeIban = payeeIdentifierModel::payeeIdentifierUserRole, /**< electornic IBAN of payee */ }; ibanBicFilterProxyModel(QObject* parent = 0) : QSortFilterProxyModel(parent) {} QVariant data(const QModelIndex &index, int role) const final override { if (role == payeeIban) { if (!index.isValid()) return QVariant(); try { payeeIdentifierTyped iban = payeeIdentifierTyped( index.model()->data(index, payeeIdentifierModel::payeeIdentifier).value() ); return iban->electronicIban(); } catch (const payeeIdentifier::empty &) { return QVariant(); } catch (const payeeIdentifier::badCast &) { return QVariant(); } } return QSortFilterProxyModel::data(index, role); } bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const final override { if (!source_parent.isValid()) return true; QModelIndex index = source_parent.model()->index(source_row, 0, source_parent); return (source_parent.model()->data(index, payeeIdentifierModel::payeeIdentifierType).toString() == payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()); } }; class ibanBicCompleter : public QCompleter { Q_OBJECT public: ibanBicCompleter(QObject* parent = 0); Q_SIGNALS: void activatedName(const QString& name) const; void highlightedName(const QString& name) const; void activatedBic(const QString& bic) const; void highlightedBic(const QString& bic) const; void activatedIban(const QString& iban) const; void highlightedIban(const QString& iban) const; private Q_SLOTS: void slotActivated(const QModelIndex& index) const; void slotHighlighted(const QModelIndex& index) const; }; ibanBicCompleter::ibanBicCompleter(QObject *parent) : QCompleter(parent) { connect(this, SIGNAL(activated(QModelIndex)), SLOT(slotActivated(QModelIndex))); connect(this, SIGNAL(highlighted(QModelIndex)), SLOT(slotHighlighted(QModelIndex))); } void ibanBicCompleter::slotActivated(const QModelIndex &index) const { if (!index.isValid()) return; emit activatedName(index.model()->data(index, payeeIdentifierModel::payeeName).toString()); try { payeeIdentifierTyped iban = payeeIdentifierTyped( index.model()->data(index, payeeIdentifierModel::payeeIdentifier).value() ); emit activatedIban(iban->electronicIban()); emit activatedBic(iban->storedBic()); } catch (const payeeIdentifier::empty &) { } catch (const payeeIdentifier::badCast &) { } } void ibanBicCompleter::slotHighlighted(const QModelIndex &index) const { if (!index.isValid()) return; emit highlightedName(index.model()->data(index, payeeIdentifierModel::payeeName).toString()); try { payeeIdentifierTyped iban = payeeIdentifierTyped( index.model()->data(index, payeeIdentifierModel::payeeIdentifier).value() ); emit highlightedIban(iban->electronicIban()); emit highlightedBic(iban->storedBic()); } catch (const payeeIdentifier::empty &) { } catch (const payeeIdentifier::badCast &) { } } sepaCreditTransferEdit::sepaCreditTransferEdit(QWidget *parent, QVariantList args) : IonlineJobEdit(parent, args), ui(new Ui::sepaCreditTransferEdit), m_onlineJob(onlineJobTyped()), m_requiredFields(new KMandatoryFieldGroup(this)), m_readOnly(false), m_showAllErrors(false) { ui->setupUi(this); m_requiredFields->add(ui->beneficiaryIban); m_requiredFields->add(ui->value); // Other required fields are set in updateSettings() connect(m_requiredFields, SIGNAL(stateChanged(bool)), this, SLOT(requiredFieldsCompleted(bool))); connect(ui->beneficiaryName, SIGNAL(textChanged(QString)), this, SLOT(beneficiaryNameChanged(QString))); connect(ui->beneficiaryIban, SIGNAL(textChanged(QString)), this, SLOT(beneficiaryIbanChanged(QString))); connect(ui->beneficiaryBankCode, SIGNAL(textChanged(QString)), this, SLOT(beneficiaryBicChanged(QString))); connect(ui->value, SIGNAL(valueChanged(QString)), this, SLOT(valueChanged())); connect(ui->sepaReference, SIGNAL(textChanged(QString)), this, SLOT(endToEndReferenceChanged(QString))); connect(ui->purpose, SIGNAL(textChanged()), this, SLOT(purposeChanged())); connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(updateEveryStatus())); connect(ui->beneficiaryName, SIGNAL(textChanged(QString)), this, SIGNAL(onlineJobChanged())); connect(ui->beneficiaryIban, SIGNAL(textChanged(QString)), this, SIGNAL(onlineJobChanged())); connect(ui->beneficiaryBankCode, SIGNAL(textChanged(QString)), this, SIGNAL(onlineJobChanged())); connect(ui->value, SIGNAL(valueChanged(QString)), this, SIGNAL(onlineJobChanged())); connect(ui->sepaReference, SIGNAL(textChanged(QString)), this, SIGNAL(onlineJobChanged())); connect(ui->purpose, SIGNAL(textChanged()), this, SIGNAL(onlineJobChanged())); // Connect signals for read only connect(this, SIGNAL(readOnlyChanged(bool)), ui->beneficiaryName, SLOT(setReadOnly(bool))); connect(this, SIGNAL(readOnlyChanged(bool)), ui->beneficiaryIban, SLOT(setReadOnly(bool))); connect(this, SIGNAL(readOnlyChanged(bool)), ui->beneficiaryBankCode, SLOT(setReadOnly(bool))); connect(this, SIGNAL(readOnlyChanged(bool)), ui->value, SLOT(setReadOnly(bool))); connect(this, SIGNAL(readOnlyChanged(bool)), ui->sepaReference, SLOT(setReadOnly(bool))); connect(this, SIGNAL(readOnlyChanged(bool)), ui->purpose, SLOT(setReadOnly(bool))); // Create models for completers payeeIdentifierModel* identModel = new payeeIdentifierModel(this); identModel->setTypeFilter(payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()); ibanBicFilterProxyModel* filterModel = new ibanBicFilterProxyModel(this); filterModel->setSourceModel(identModel); KDescendantsProxyModel* descendantsModel = new KDescendantsProxyModel(this); descendantsModel->setSourceModel(filterModel); // Set completers popup and bind them to the corresponding fields { // Beneficiary name field ibanBicCompleter* completer = new ibanBicCompleter(this); completer->setModel(descendantsModel); completer->setCompletionRole(payeeIdentifierModel::payeeName); completer->setCaseSensitivity(Qt::CaseInsensitive); connect(completer, SIGNAL(activatedIban(QString)), ui->beneficiaryIban, SLOT(setText(QString))); connect(completer, SIGNAL(activatedBic(QString)), ui->beneficiaryBankCode, SLOT(setText(QString))); ui->beneficiaryName->setCompleter(completer); QAbstractItemView *itemView = new payeeIdentifierCompleterPopup(); completer->setPopup(itemView); // setPopup() resets the delegate itemView->setItemDelegate(new ibanBicCompleterDelegate(this)); } { // IBAN field ibanBicCompleter* ibanCompleter = new ibanBicCompleter(this); ibanCompleter->setModel(descendantsModel); ibanCompleter->setCompletionRole(ibanBicFilterProxyModel::payeeIban); ibanCompleter->setCaseSensitivity(Qt::CaseInsensitive); connect(ibanCompleter, SIGNAL(activatedName(QString)), ui->beneficiaryName, SLOT(setText(QString))); connect(ibanCompleter, SIGNAL(activatedBic(QString)), ui->beneficiaryBankCode, SLOT(setText(QString))); ui->beneficiaryIban->setCompleter(ibanCompleter); QAbstractItemView *itemView = new payeeIdentifierCompleterPopup(); ibanCompleter->setPopup(itemView); // setPopup() resets the delegate itemView->setItemDelegate(new ibanBicCompleterDelegate(this)); } } sepaCreditTransferEdit::~sepaCreditTransferEdit() { delete ui; } void sepaCreditTransferEdit::showEvent(QShowEvent* event) { updateEveryStatus(); QWidget::showEvent(event); } void sepaCreditTransferEdit::showAllErrorMessages(const bool state) { if (m_showAllErrors != state) { m_showAllErrors = state; updateEveryStatus(); } } onlineJobTyped sepaCreditTransferEdit::getOnlineJobTyped() const { onlineJobTyped sepaJob(m_onlineJob); sepaJob.task()->setValue(ui->value->value()); sepaJob.task()->setPurpose(ui->purpose->toPlainText()); sepaJob.task()->setEndToEndReference(ui->sepaReference->text()); payeeIdentifiers::ibanBic accIdent; accIdent.setOwnerName(ui->beneficiaryName->text()); accIdent.setIban(ui->beneficiaryIban->text()); accIdent.setBic(ui->beneficiaryBankCode->text()); sepaJob.task()->setBeneficiary(accIdent); return sepaJob; } void sepaCreditTransferEdit::setOnlineJob(const onlineJobTyped& job) { m_onlineJob = job; updateSettings(); setReadOnly(!job.isEditable()); ui->purpose->setText(job.task()->purpose()); ui->sepaReference->setText(job.task()->endToEndReference()); ui->value->setValue(job.task()->value()); ui->beneficiaryName->setText(job.task()->beneficiaryTyped().ownerName()); ui->beneficiaryIban->setText(job.task()->beneficiaryTyped().paperformatIban()); ui->beneficiaryBankCode->setText(job.task()->beneficiaryTyped().storedBic()); } bool sepaCreditTransferEdit::setOnlineJob(const onlineJob& job) { if (!job.isNull() && job.task()->taskName() == sepaOnlineTransfer::name()) { setOnlineJob(onlineJobTyped(job)); return true; } return false; } void sepaCreditTransferEdit::setOriginAccount(const QString& accountId) { m_onlineJob.task()->setOriginAccount(accountId); updateSettings(); } void sepaCreditTransferEdit::updateEveryStatus() { beneficiaryNameChanged(ui->beneficiaryName->text()); beneficiaryIbanChanged(ui->beneficiaryIban->text()); beneficiaryBicChanged(ui->beneficiaryBankCode->text()); purposeChanged(); valueChanged(); endToEndReferenceChanged(ui->sepaReference->text()); } void sepaCreditTransferEdit::setReadOnly(const bool& readOnly) { // Only set writeable if it changes something and if it is possible if (readOnly != m_readOnly && (readOnly == true || getOnlineJobTyped().isEditable())) { m_readOnly = readOnly; emit readOnlyChanged(m_readOnly); } } void sepaCreditTransferEdit::updateSettings() { QSharedPointer settings = taskSettings(); // Reference ui->sepaReference->setMaxLength(settings->endToEndReferenceLength()); if (settings->endToEndReferenceLength() == 0) ui->sepaReference->setEnabled(false); else ui->sepaReference->setEnabled(true); // Purpose ui->purpose->setAllowedChars(settings->allowedChars()); ui->purpose->setMaxLineLength(settings->purposeLineLength()); ui->purpose->setMaxLines(settings->purposeMaxLines()); if (settings->purposeMinLength()) m_requiredFields->add(ui->purpose); else m_requiredFields->remove(ui->purpose); // Beneficiary Name ui->beneficiaryName->setValidator(new charValidator(ui->beneficiaryName, settings->allowedChars())); ui->beneficiaryName->setMaxLength(settings->recipientNameLineLength()); if (settings->recipientNameMinLength() != 0) m_requiredFields->add(ui->beneficiaryName); else m_requiredFields->remove(ui->beneficiaryName); updateEveryStatus(); } void sepaCreditTransferEdit::beneficiaryIbanChanged(const QString& iban) { // Check IBAN QPair answer = ibanValidator::validateWithMessage(iban); if (m_showAllErrors || iban.length() > 5 || (!ui->beneficiaryIban->hasFocus() && !iban.isEmpty())) ui->feedbackIban->setFeedback(answer.first, answer.second); else ui->feedbackIban->removeFeedback(); // Check if BIC is mandatory QSharedPointer settings = taskSettings(); QString payeeIban; try { payeeIdentifier ident = getOnlineJobTyped().task()->originAccountIdentifier(); payeeIban = ident.data()->electronicIban(); } catch (const payeeIdentifier::empty &) { } catch (const payeeIdentifier::badCast &) { } if (settings->isBicMandatory(payeeIban, iban)) { m_requiredFields->add(ui->beneficiaryBankCode); beneficiaryBicChanged(ui->beneficiaryBankCode->text()); } else { m_requiredFields->remove(ui->beneficiaryBankCode); beneficiaryBicChanged(ui->beneficiaryBankCode->text()); } } void sepaCreditTransferEdit::beneficiaryBicChanged(const QString& bic) { if (bic.isEmpty() && !ui->beneficiaryIban->text().isEmpty()) { QSharedPointer settings = taskSettings(); const payeeIdentifier payee = getOnlineJobTyped().task()->originAccountIdentifier(); QString iban; try { iban = payee.data()->electronicIban(); } catch (const payeeIdentifier::badCast &) { } if (settings->isBicMandatory(iban , ui->beneficiaryIban->text())) { ui->feedbackBic->setFeedback(eWidgets::ValidationFeedback::MessageType::Error, i18n("For this beneficiary's country the BIC is mandatory.")); return; } } QPair answer = bicValidator::validateWithMessage(bic); if (m_showAllErrors || bic.length() >= 8 || (!ui->beneficiaryBankCode->hasFocus() && !bic.isEmpty())) ui->feedbackBic->setFeedback(answer.first, answer.second); else ui->feedbackBic->removeFeedback(); } void sepaCreditTransferEdit::beneficiaryNameChanged(const QString& name) { QSharedPointer settings = taskSettings(); if (name.length() < settings->recipientNameMinLength() && (m_showAllErrors || (!ui->beneficiaryName->hasFocus() && !name.isEmpty()))) { ui->feedbackName->setFeedback(eWidgets::ValidationFeedback::MessageType::Error, i18np("A beneficiary name is needed.", "The beneficiary name must be at least %1 characters long", settings->recipientNameMinLength() )); } else { ui->feedbackName->removeFeedback(); } } void sepaCreditTransferEdit::valueChanged() { if ((!ui->value->isValid() && (m_showAllErrors || (!ui->value->hasFocus() && ui->value->value().toDouble() != 0))) || (!ui->value->value().isPositive() && ui->value->value().toDouble() != 0)) { ui->feedbackAmount->setFeedback(eWidgets::ValidationFeedback::MessageType::Error, i18n("A positive amount to transfer is needed.")); return; } if (!ui->value->isValid()) return; const MyMoneyAccount account = getOnlineJob().responsibleMyMoneyAccount(); const MyMoneyMoney expectedBalance = account.balance() - ui->value->value(); if (expectedBalance < MyMoneyMoney(account.value("maxCreditAbsolute"))) { ui->feedbackAmount->setFeedback(eWidgets::ValidationFeedback::MessageType::Warning, i18n("After this credit transfer the account's balance will be below your credit limit.")); } else if (expectedBalance < MyMoneyMoney(account.value("minBalanceAbsolute"))) { ui->feedbackAmount->setFeedback(eWidgets::ValidationFeedback::MessageType::Information, i18n("After this credit transfer the account's balance will be below the minimal balance.")); } else { ui->feedbackAmount->removeFeedback(); } } void sepaCreditTransferEdit::endToEndReferenceChanged(const QString& reference) { QSharedPointer settings = taskSettings(); if (settings->checkEndToEndReferenceLength(reference) == validators::tooLong) { ui->feedbackReference->setFeedback(eWidgets::ValidationFeedback::MessageType::Error, i18np("The end-to-end reference cannot contain more than one character.", "The end-to-end reference cannot contain more than %1 characters.", settings->endToEndReferenceLength() )); } else { ui->feedbackReference->removeFeedback(); } } void sepaCreditTransferEdit::purposeChanged() { const QString purpose = ui->purpose->toPlainText(); QSharedPointer settings = taskSettings(); QString message; if (!settings->checkPurposeLineLength(purpose)) message = i18np("The maximal line length of %1 character per line is exceeded.", "The maximal line length of %1 characters per line is exceeded.", settings->purposeLineLength()) .append('\n'); if (!settings->checkPurposeCharset(purpose)) message.append(i18n("The purpose can only contain the letters A-Z, spaces and ':?.,-()+ and /")).append('\n'); if (!settings->checkPurposeMaxLines(purpose)) { message.append(i18np("In the purpose only a single line is allowed.", "The purpose cannot contain more than %1 lines.", settings->purposeMaxLines())) .append('\n'); } else if (settings->checkPurposeLength(purpose) == validators::tooShort) { message.append(i18np("A purpose is needed.", "The purpose must be at least %1 characters long.", settings->purposeMinLength())) .append('\n'); } // Remove the last '\n' message.chop(1); if (!message.isEmpty()) { ui->feedbackPurpose->setFeedback(eWidgets::ValidationFeedback::MessageType::Error, message); } else { ui->feedbackPurpose->removeFeedback(); } } QSharedPointer< const sepaOnlineTransfer::settings > sepaCreditTransferEdit::taskSettings() { return getOnlineJobTyped().constTask()->getSettings(); } #include "sepacredittransferedit.moc" diff --git a/kmymoney/plugins/onlinetasks/sepa/ui/sepacredittransferedit.ui b/kmymoney/plugins/onlinetasks/sepa/ui/sepacredittransferedit.ui index 273a0340c..bea228c37 100644 --- a/kmymoney/plugins/onlinetasks/sepa/ui/sepacredittransferedit.ui +++ b/kmymoney/plugins/onlinetasks/sepa/ui/sepacredittransferedit.ui @@ -1,223 +1,223 @@ sepaCreditTransferEdit 0 0 340 438 Beneficiary &Name beneficiaryName IBAN BIC Amount Qt::Horizontal 40 20 100 0 false End-to-end reference sepaReference Purpose purpose 0 0 70 30 true QTextEdit::FixedColumnWidth 27 false label purpose label_4 label_2 beneficiaryName label_bic label_iban beneficiaryBankCode feedbackName feedbackBic feedbackAmount feedbackReference feedbackPurpose sepaReference feedbackIban beneficiaryIban KLineEdit QLineEdit
klineedit.h
KMyMoneyEdit QFrame
kmymoneyedit.h
1
KMyMoneyTextEdit QTextEdit
kmymoneytextedit.h
KBicEdit QLineEdit -
payeeidentifier/ibanandbic/widgets/kbicedit.h
+
widgets/payeeidentifier/ibanbic/kbicedit.h
validatorFeedback(KMyMoneyValidationFeedback::MessageType,QString)
KMyMoneyValidationFeedback QWidget
kmymoneyvalidationfeedback.h
1 setFeedback(KMyMoneyValidationFeedback::MessageType,QString)
KIbanLineEdit QLineEdit -
payeeidentifier/ibanandbic/widgets/kibanlineedit.h
+
widgets/payeeidentifier/ibanbic/kibanlineedit.h
validatorFeedback(KMyMoneyValidationFeedback::MessageType,QString)
beneficiaryName beneficiaryIban beneficiaryBankCode sepaReference purpose
diff --git a/kmymoney/plugins/sql/mymoneystoragesql_p.h b/kmymoney/plugins/sql/mymoneystoragesql_p.h index a250dc5ea..95efbbc5c 100644 --- a/kmymoney/plugins/sql/mymoneystoragesql_p.h +++ b/kmymoney/plugins/sql/mymoneystoragesql_p.h @@ -1,3124 +1,3123 @@ /*************************************************************************** mymoneystoragesql.cpp --------------------- begin : 11 November 2005 copyright : (C) 2005 by Tony Bloomfield email : tonybloom@users.sourceforge.net : Fernando Vilas : Christian Dávid (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef MYMONEYSTORAGESQL_P_H #define MYMONEYSTORAGESQL_P_H #include "mymoneystoragesql.h" // ---------------------------------------------------------------------------- // System Includes #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneystoragemgr.h" #include "kmymoneystorageplugin.h" #include "onlinejobadministration.h" -#include "payeeidentifier/payeeidentifierloader.h" #include "onlinetasks/interfaces/tasks/onlinetask.h" #include "mymoneycostcenter.h" #include "mymoneyexception.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneymoney.h" #include "mymoneyschedule.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneytransactionfilter.h" #include "mymoneybudget.h" #include "mymoneyreport.h" #include "mymoneyprice.h" #include "mymoneyutils.h" #include "mymoneydbdef.h" #include "mymoneydbdriver.h" #include "payeeidentifierdata.h" #include "payeeidentifier.h" #include "payeeidentifiertyped.h" #include "payeeidentifier/ibanbic/ibanbic.h" #include "payeeidentifier/nationalaccount/nationalaccount.h" #include "onlinetasks/sepa/sepaonlinetransferimpl.h" #include "xmlstoragehelper.h" #include "mymoneyenums.h" using namespace eMyMoney; class FilterFail { public: explicit FilterFail(const MyMoneyTransactionFilter& filter) : m_filter(filter) {} inline bool operator()(const QPair& transactionPair) { return (*this)(transactionPair.second); } inline bool operator()(const MyMoneyTransaction& transaction) { return !m_filter.match(transaction); } private: MyMoneyTransactionFilter m_filter; }; //***************************************************************************** // Create a class to handle db transactions using scope // // Don't let the database object get destroyed while this object exists, // that would result in undefined behavior. class MyMoneyDbTransaction { public: explicit MyMoneyDbTransaction(MyMoneyStorageSql& db, const QString& name) : m_db(db), m_name(name) { db.startCommitUnit(name); } ~MyMoneyDbTransaction() { if (std::uncaught_exception()) { m_db.cancelCommitUnit(m_name); } else { try{ m_db.endCommitUnit(m_name); } catch (const MyMoneyException &) { try { m_db.cancelCommitUnit(m_name); } catch (const MyMoneyException &e) { qDebug() << e.what(); } } } } private: MyMoneyStorageSql& m_db; QString m_name; }; /** * The MyMoneySqlQuery class is derived from QSqlQuery to provide * a way to adjust some queries based on database type and make * debugging easier by providing a place to put debug statements. */ class MyMoneySqlQuery : public QSqlQuery { public: explicit MyMoneySqlQuery(MyMoneyStorageSql* db = 0) : QSqlQuery(*db) { } virtual ~MyMoneySqlQuery() { } bool exec() { qDebug() << "start sql:" << lastQuery(); bool rc = QSqlQuery::exec(); qDebug() << "end sql:" << QSqlQuery::executedQuery(); qDebug() << "***Query returned:" << rc << ", row count:" << numRowsAffected(); return (rc); } bool exec(const QString & query) { qDebug() << "start sql:" << query; bool rc = QSqlQuery::exec(query); qDebug() << "end sql:" << QSqlQuery::executedQuery(); qDebug() << "***Query returned:" << rc << ", row count:" << numRowsAffected(); return rc; } bool prepare(const QString & query) { return (QSqlQuery::prepare(query)); } }; #define GETSTRING(a) query.value(a).toString() #define GETDATE(a) getDate(GETSTRING(a)) #define GETDATE_D(a) d->getDate(GETSTRING(a)) #define GETDATETIME(a) getDateTime(GETSTRING(a)) #define GETINT(a) query.value(a).toInt() #define GETULL(a) query.value(a).toULongLong() #define MYMONEYEXCEPTIONSQL(exceptionMessage) MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, exceptionMessage)) #define MYMONEYEXCEPTIONSQL_D(exceptionMessage) MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, exceptionMessage)) class MyMoneyStorageSqlPrivate { Q_DISABLE_COPY(MyMoneyStorageSqlPrivate) Q_DECLARE_PUBLIC(MyMoneyStorageSql) public: explicit MyMoneyStorageSqlPrivate(MyMoneyStorageSql* qq) : q_ptr(qq), m_dbVersion(0), m_storage(nullptr), m_loadAll(false), m_override(false), m_institutions(0), m_accounts(0), m_payees(0), m_tags(0), m_transactions(0), m_splits(0), m_securities(0), m_prices(0), m_currencies(0), m_schedules(0), m_reports(0), m_kvps(0), m_budgets(0), m_onlineJobs(0), m_payeeIdentifier(0), m_hiIdInstitutions(0), m_hiIdPayees(0), m_hiIdTags(0), m_hiIdAccounts(0), m_hiIdTransactions(0), m_hiIdSchedules(0), m_hiIdSecurities(0), m_hiIdReports(0), m_hiIdBudgets(0), m_hiIdOnlineJobs(0), m_hiIdPayeeIdentifier(0), m_hiIdCostCenter(0), m_displayStatus(false), m_readingPrices(false), m_newDatabase(false), m_progressCallback(nullptr) { m_preferred.setReportAllSplits(false); } ~MyMoneyStorageSqlPrivate() { } enum class SQLAction { Save, Modify, Remove }; /** * MyMoneyStorageSql get highest ID number from the database * * @return : highest ID number */ ulong highestNumberFromIdString(QString tableName, QString tableField, int prefixLength) { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QSqlQuery query(*q); if (!query.exec(m_driver->highestNumberFromIdString(tableName, tableField, prefixLength)) || !query.next()) throw MYMONEYEXCEPTIONSQL("retrieving highest ID number"); return query.value(0).toULongLong(); } /** * @name writeFromStorageMethods * @{ * These method write all data from m_storage to the database. Data which is * stored in the database is deleted. */ void writeUserInformation(); void writeInstitutions() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database // anything not in the list needs to be inserted // anything which is will be updated and removed from the list // anything left over at the end will need to be deleted // this is an expensive and inconvenient way to do things; find a better way // one way would be to build the lists when reading the db // unfortunately this object does not persist between read and write // it would also be nice if we could tell which objects had been updated since we read them in QList dbList; QSqlQuery query(*q); query.prepare("SELECT id FROM kmmInstitutions;"); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Institution list"); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); const QList list = m_storage->institutionList(); QList insertList; QList updateList; QSqlQuery query2(*q); query.prepare(m_db.m_tables["kmmInstitutions"].updateString()); query2.prepare(m_db.m_tables["kmmInstitutions"].insertString()); signalProgress(0, list.count(), "Writing Institutions..."); foreach (const MyMoneyInstitution& i, list) { if (dbList.contains(i.id())) { dbList.removeAll(i.id()); updateList << i; } else { insertList << i; } signalProgress(++m_institutions, 0); } if (!insertList.isEmpty()) writeInstitutionList(insertList, query2); if (!updateList.isEmpty()) writeInstitutionList(updateList, query); if (!dbList.isEmpty()) { QVariantList deleteList; // qCopy segfaults here, so do it with a hand-rolled loop foreach (const QString& it, dbList) { deleteList << it; } query.prepare("DELETE FROM kmmInstitutions WHERE id = :id"); query.bindValue(":id", deleteList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Institution"); deleteKeyValuePairs("OFXSETTINGS", deleteList); } } void writePayees() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QSqlQuery query(*q); query.prepare("SELECT id FROM kmmPayees;"); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Payee list"); // krazy:exclude=crashy QList dbList; dbList.reserve(query.numRowsAffected()); while (query.next()) dbList.append(query.value(0).toString()); QList list = m_storage->payeeList(); MyMoneyPayee user(QString("USER"), m_storage->user()); list.prepend(user); signalProgress(0, list.count(), "Writing Payees..."); Q_FOREACH(const MyMoneyPayee& it, list) { if (dbList.contains(it.id())) { dbList.removeAll(it.id()); q->modifyPayee(it); } else { q->addPayee(it); } signalProgress(++m_payees, 0); } if (!dbList.isEmpty()) { QMap payeesToDelete = q->fetchPayees(dbList, true); Q_FOREACH(const MyMoneyPayee& payee, payeesToDelete) { q->removePayee(payee); } } } void writeTags() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); query.prepare("SELECT id FROM kmmTags;"); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Tag list"); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); QList list = m_storage->tagList(); signalProgress(0, list.count(), "Writing Tags..."); QSqlQuery query2(*q); query.prepare(m_db.m_tables["kmmTags"].updateString()); query2.prepare(m_db.m_tables["kmmTags"].insertString()); foreach (const MyMoneyTag& it, list) { if (dbList.contains(it.id())) { dbList.removeAll(it.id()); writeTag(it, query); } else { writeTag(it, query2); } signalProgress(++m_tags, 0); } if (!dbList.isEmpty()) { QVariantList deleteList; // qCopy segfaults here, so do it with a hand-rolled loop foreach (const QString& it, dbList) { deleteList << it; } query.prepare(m_db.m_tables["kmmTags"].deleteString()); query.bindValue(":id", deleteList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Tag"); m_tags -= query.numRowsAffected(); } } void writeAccounts() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); query.prepare("SELECT id FROM kmmAccounts;"); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Account list"); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); QList list; m_storage->accountList(list); unsigned progress = 0; signalProgress(0, list.count(), "Writing Accounts..."); if (dbList.isEmpty()) { // new table, insert standard accounts query.prepare(m_db.m_tables["kmmAccounts"].insertString()); } else { query.prepare(m_db.m_tables["kmmAccounts"].updateString()); } // Attempt to write the standard accounts. For an empty db, this will fail. try { QList stdList; stdList << m_storage->asset(); stdList << m_storage->liability(); stdList << m_storage->expense(); stdList << m_storage->income(); stdList << m_storage->equity(); writeAccountList(stdList, query); m_accounts += stdList.size(); } catch (const MyMoneyException &) { // If the above failed, assume that the database is empty and create // the standard accounts by hand before writing them. MyMoneyAccount acc_l; acc_l.setAccountType(Account::Type::Liability); acc_l.setName("Liability"); MyMoneyAccount liability(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Liability), acc_l); MyMoneyAccount acc_a; acc_a.setAccountType(Account::Type::Asset); acc_a.setName("Asset"); MyMoneyAccount asset(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Asset), acc_a); MyMoneyAccount acc_e; acc_e.setAccountType(Account::Type::Expense); acc_e.setName("Expense"); MyMoneyAccount expense(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Expense), acc_e); MyMoneyAccount acc_i; acc_i.setAccountType(Account::Type::Income); acc_i.setName("Income"); MyMoneyAccount income(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Income), acc_i); MyMoneyAccount acc_q; acc_q.setAccountType(Account::Type::Equity); acc_q.setName("Equity"); MyMoneyAccount equity(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Equity), acc_q); QList stdList; stdList << asset; stdList << liability; stdList << expense; stdList << income; stdList << equity; writeAccountList(stdList, query); m_accounts += stdList.size(); } QSqlQuery query2(*q); query.prepare(m_db.m_tables["kmmAccounts"].updateString()); query2.prepare(m_db.m_tables["kmmAccounts"].insertString()); QList updateList; QList insertList; // Update the accounts that exist; insert the ones that do not. foreach (const MyMoneyAccount& it, list) { m_transactionCountMap[it.id()] = m_storage->transactionCount(it.id()); if (dbList.contains(it.id())) { dbList.removeAll(it.id()); updateList << it; } else { insertList << it; } signalProgress(++progress, 0); ++m_accounts; } writeAccountList(updateList, query); writeAccountList(insertList, query2); // Delete the accounts that are in the db but no longer in memory. if (!dbList.isEmpty()) { QVariantList kvpList; query.prepare("DELETE FROM kmmAccounts WHERE id = :id"); foreach (const QString& it, dbList) { if (!m_storage->isStandardAccount(it)) { kvpList << it; } } query.bindValue(":id", kvpList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Account"); deleteKeyValuePairs("ACCOUNT", kvpList); deleteKeyValuePairs("ONLINEBANKING", kvpList); } } void writeTransactions() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); query.prepare("SELECT id FROM kmmTransactions WHERE txType = 'N';"); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Transaction list"); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList list; m_storage->transactionList(list, filter); signalProgress(0, list.count(), "Writing Transactions..."); QSqlQuery q2(*q); query.prepare(m_db.m_tables["kmmTransactions"].updateString()); q2.prepare(m_db.m_tables["kmmTransactions"].insertString()); foreach (const MyMoneyTransaction& it, list) { if (dbList.contains(it.id())) { dbList.removeAll(it.id()); writeTransaction(it.id(), it, query, "N"); } else { writeTransaction(it.id(), it, q2, "N"); } signalProgress(++m_transactions, 0); } if (!dbList.isEmpty()) { foreach (const QString& it, dbList) { deleteTransaction(it); } } } void writeSchedules() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); query.prepare("SELECT id FROM kmmSchedules;"); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Schedule list"); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); const auto list = m_storage->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QSqlQuery query2(*q); //TODO: find a way to prepare the queries outside of the loop. writeSchedule() // modifies the query passed to it, so they have to be re-prepared every pass. signalProgress(0, list.count(), "Writing Schedules..."); foreach (const MyMoneySchedule& it, list) { query.prepare(m_db.m_tables["kmmSchedules"].updateString()); query2.prepare(m_db.m_tables["kmmSchedules"].insertString()); bool insert = true; if (dbList.contains(it.id())) { dbList.removeAll(it.id()); insert = false; writeSchedule(it, query, insert); } else { writeSchedule(it, query2, insert); } signalProgress(++m_schedules, 0); } if (!dbList.isEmpty()) { foreach (const QString& it, dbList) { deleteSchedule(it); } } } void writeSecurities() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); QSqlQuery query2(*q); query.prepare("SELECT id FROM kmmSecurities;"); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building security list"); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); const QList securityList = m_storage->securityList(); signalProgress(0, securityList.count(), "Writing Securities..."); query.prepare(m_db.m_tables["kmmSecurities"].updateString()); query2.prepare(m_db.m_tables["kmmSecurities"].insertString()); foreach (const MyMoneySecurity& it, securityList) { if (dbList.contains(it.id())) { dbList.removeAll(it.id()); writeSecurity(it, query); } else { writeSecurity(it, query2); } signalProgress(++m_securities, 0); } if (!dbList.isEmpty()) { QVariantList idList; // qCopy segfaults here, so do it with a hand-rolled loop foreach (const QString& it, dbList) { idList << it; } query.prepare("DELETE FROM kmmSecurities WHERE id = :id"); query2.prepare("DELETE FROM kmmPrices WHERE fromId = :id OR toId = :id"); query.bindValue(":id", idList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Security"); query2.bindValue(":fromId", idList); query2.bindValue(":toId", idList); if (!query2.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Security"); deleteKeyValuePairs("SECURITY", idList); } } void writePrices() { Q_Q(MyMoneyStorageSql); // due to difficulties in matching and determining deletes // easiest way is to delete all and re-insert QSqlQuery query(*q); query.prepare("DELETE FROM kmmPrices"); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("deleting Prices"); // krazy:exclude=crashy m_prices = 0; const MyMoneyPriceList list = m_storage->priceList(); signalProgress(0, list.count(), "Writing Prices..."); MyMoneyPriceList::ConstIterator it; for (it = list.constBegin(); it != list.constEnd(); ++it) { writePricePair(*it); } } void writeCurrencies() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); QSqlQuery query2(*q); query.prepare("SELECT ISOCode FROM kmmCurrencies;"); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Currency list"); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); const QList currencyList = m_storage->currencyList(); signalProgress(0, currencyList.count(), "Writing Currencies..."); query.prepare(m_db.m_tables["kmmCurrencies"].updateString()); query2.prepare(m_db.m_tables["kmmCurrencies"].insertString()); foreach (const MyMoneySecurity& it, currencyList) { if (dbList.contains(it.id())) { dbList.removeAll(it.id()); writeCurrency(it, query); } else { writeCurrency(it, query2); } signalProgress(++m_currencies, 0); } if (!dbList.isEmpty()) { QVariantList isoCodeList; query.prepare("DELETE FROM kmmCurrencies WHERE ISOCode = :ISOCode"); // qCopy segfaults here, so do it with a hand-rolled loop foreach (const QString& it, dbList) { isoCodeList << it; } query.bindValue(":ISOCode", isoCodeList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Currency"); } } void writeFileInfo() { Q_Q(MyMoneyStorageSql); // we have no real way of knowing when these change, so re-write them every time QVariantList kvpList; kvpList << ""; QList > pairs; pairs << m_storage->pairs(); deleteKeyValuePairs("STORAGE", kvpList); writeKeyValuePairs("STORAGE", kvpList, pairs); QSqlQuery query(*q); query.prepare("SELECT count(*) FROM kmmFileInfo;"); if (!query.exec() || !query.next()) throw MYMONEYEXCEPTIONSQL("checking fileinfo"); // krazy:exclude=crashy if (query.value(0).toInt() == 0) { // Cannot use "INSERT INTO kmmFileInfo DEFAULT VALUES;" because it is not supported by MySQL query.prepare(QLatin1String("INSERT INTO kmmFileInfo (version) VALUES (null);")); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("inserting fileinfo"); // krazy:exclude=crashy } query.prepare(QLatin1String( "UPDATE kmmFileInfo SET " "version = :version, " "fixLevel = :fixLevel, " "created = :created, " "lastModified = :lastModified, " "baseCurrency = :baseCurrency, " "dateRangeStart = :dateRangeStart, " "dateRangeEnd = :dateRangeEnd, " "hiInstitutionId = :hiInstitutionId, " "hiPayeeId = :hiPayeeId, " "hiTagId = :hiTagId, " "hiAccountId = :hiAccountId, " "hiTransactionId = :hiTransactionId, " "hiScheduleId = :hiScheduleId, " "hiSecurityId = :hiSecurityId, " "hiReportId = :hiReportId, " "hiBudgetId = :hiBudgetId, " "hiOnlineJobId = :hiOnlineJobId, " "hiPayeeIdentifierId = :hiPayeeIdentifierId, " "encryptData = :encryptData, " "updateInProgress = :updateInProgress, " "logonUser = :logonUser, " "logonAt = :logonAt, " //! @todo The following updates are for backwards compatibility only //! remove backwards compatibility in a later version "institutions = :institutions, " "accounts = :accounts, " "payees = :payees, " "tags = :tags, " "transactions = :transactions, " "splits = :splits, " "securities = :securities, " "prices = :prices, " "currencies = :currencies, " "schedules = :schedules, " "reports = :reports, " "kvps = :kvps, " "budgets = :budgets; " ) ); query.bindValue(":version", m_dbVersion); query.bindValue(":fixLevel", m_storage->fileFixVersion()); query.bindValue(":created", m_storage->creationDate().toString(Qt::ISODate)); //q.bindValue(":lastModified", m_storage->lastModificationDate().toString(Qt::ISODate)); query.bindValue(":lastModified", QDate::currentDate().toString(Qt::ISODate)); query.bindValue(":baseCurrency", m_storage->pairs()["kmm-baseCurrency"]); query.bindValue(":dateRangeStart", QDate()); query.bindValue(":dateRangeEnd", QDate()); //FIXME: This modifies all m_ used in this function. // Sometimes the memory has been updated. // Should most of these be tracked in a view? // Variables actually needed are: version, fileFixVersion, creationDate, // baseCurrency, encryption, update info, and logon info. //try { //readFileInfo(); //} catch (...) { //q->startCommitUnit(Q_FUNC_INFO); //} //! @todo The following bindings are for backwards compatibility only //! remove backwards compatibility in a later version query.bindValue(":hiInstitutionId", QVariant::fromValue(q->getNextInstitutionId())); query.bindValue(":hiPayeeId", QVariant::fromValue(q->getNextPayeeId())); query.bindValue(":hiTagId", QVariant::fromValue(q->getNextTagId())); query.bindValue(":hiAccountId", QVariant::fromValue(q->getNextAccountId())); query.bindValue(":hiTransactionId", QVariant::fromValue(q->getNextTransactionId())); query.bindValue(":hiScheduleId", QVariant::fromValue(q->getNextScheduleId())); query.bindValue(":hiSecurityId", QVariant::fromValue(q->getNextSecurityId())); query.bindValue(":hiReportId", QVariant::fromValue(q->getNextReportId())); query.bindValue(":hiBudgetId", QVariant::fromValue(q->getNextBudgetId())); query.bindValue(":hiOnlineJobId", QVariant::fromValue(q->getNextOnlineJobId())); query.bindValue(":hiPayeeIdentifierId", QVariant::fromValue(q->getNextPayeeIdentifierId())); query.bindValue(":encryptData", m_encryptData); query.bindValue(":updateInProgress", "N"); query.bindValue(":logonUser", m_logonUser); query.bindValue(":logonAt", m_logonAt.toString(Qt::ISODate)); //! @todo The following bindings are for backwards compatibility only //! remove backwards compatibility in a later version query.bindValue(":institutions", (unsigned long long) m_institutions); query.bindValue(":accounts", (unsigned long long) m_accounts); query.bindValue(":payees", (unsigned long long) m_payees); query.bindValue(":tags", (unsigned long long) m_tags); query.bindValue(":transactions", (unsigned long long) m_transactions); query.bindValue(":splits", (unsigned long long) m_splits); query.bindValue(":securities", (unsigned long long) m_securities); query.bindValue(":prices", (unsigned long long) m_prices); query.bindValue(":currencies", (unsigned long long) m_currencies); query.bindValue(":schedules", (unsigned long long) m_schedules); query.bindValue(":reports", (unsigned long long) m_reports); query.bindValue(":kvps", (unsigned long long) m_kvps); query.bindValue(":budgets", (unsigned long long) m_budgets); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing FileInfo"); // krazy:exclude=crashy } void writeReports() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); QSqlQuery query2(*q); query.prepare("SELECT id FROM kmmReportConfig;"); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Report list"); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); QList list = m_storage->reportList(); signalProgress(0, list.count(), "Writing Reports..."); query.prepare(m_db.m_tables["kmmReportConfig"].updateString()); query2.prepare(m_db.m_tables["kmmReportConfig"].insertString()); foreach (const MyMoneyReport& it, list) { if (dbList.contains(it.id())) { dbList.removeAll(it.id()); writeReport(it, query); } else { writeReport(it, query2); } signalProgress(++m_reports, 0); } if (!dbList.isEmpty()) { QVariantList idList; query.prepare("DELETE FROM kmmReportConfig WHERE id = :id"); // qCopy segfaults here, so do it with a hand-rolled loop foreach (const QString& it, dbList) { idList << it; } query.bindValue(":id", idList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Report"); } } void writeBudgets() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); QSqlQuery query2(*q); query.prepare("SELECT name FROM kmmBudgetConfig;"); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Budget list"); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); QList list = m_storage->budgetList(); signalProgress(0, list.count(), "Writing Budgets..."); query.prepare(m_db.m_tables["kmmBudgetConfig"].updateString()); query2.prepare(m_db.m_tables["kmmBudgetConfig"].insertString()); foreach (const MyMoneyBudget& it, list) { if (dbList.contains(it.name())) { dbList.removeAll(it.name()); writeBudget(it, query); } else { writeBudget(it, query2); } signalProgress(++m_budgets, 0); } if (!dbList.isEmpty()) { QVariantList idList; query.prepare("DELETE FROM kmmBudgetConfig WHERE id = :id"); // qCopy segfaults here, so do it with a hand-rolled loop foreach (const QString& it, dbList) { idList << it; } query.bindValue(":name", idList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Budget"); } } void writeOnlineJobs() { Q_Q(MyMoneyStorageSql); QSqlQuery query(*q); if (!query.exec("DELETE FROM kmmOnlineJobs;")) throw MYMONEYEXCEPTIONSQL("Clean kmmOnlineJobs table"); const QList jobs(m_storage->onlineJobList()); signalProgress(0, jobs.count(), i18n("Inserting online jobs.")); // Create list for onlineJobs which failed and the reason therefor QList > failedJobs; int jobCount = 0; foreach (const onlineJob& job, jobs) { try { q->addOnlineJob(job); } catch (const MyMoneyException &e) { // Do not save e as this may point to an inherited class failedJobs.append(QPair(job, e.what())); qDebug() << "Failed to save onlineJob" << job.id() << "Reson:" << e.what(); } signalProgress(++jobCount, 0); } if (!failedJobs.isEmpty()) { /** @todo Improve error message */ throw MYMONEYEXCEPTION_CSTRING("Could not save onlineJob."); } } /** @} */ /** * @name writeMethods * @{ * These methods bind the data fields of MyMoneyObjects to a given query and execute the query. * This is helpfull as the query has usually an update and a insert format. */ void writeInstitutionList(const QList& iList, QSqlQuery& query) { QVariantList idList; QVariantList nameList; QVariantList managerList; QVariantList routingCodeList; QVariantList addressStreetList; QVariantList addressCityList; QVariantList addressZipcodeList; QVariantList telephoneList; QList > kvpPairsList; foreach (const MyMoneyInstitution& i, iList) { idList << i.id(); nameList << i.name(); managerList << i.manager(); routingCodeList << i.sortcode(); addressStreetList << i.street(); addressCityList << i.city(); addressZipcodeList << i.postcode(); telephoneList << i.telephone(); kvpPairsList << i.pairs(); } query.bindValue(":id", idList); query.bindValue(":name", nameList); query.bindValue(":manager", managerList); query.bindValue(":routingCode", routingCodeList); query.bindValue(":addressStreet", addressStreetList); query.bindValue(":addressCity", addressCityList); query.bindValue(":addressZipcode", addressZipcodeList); query.bindValue(":telephone", telephoneList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("writing Institution"); writeKeyValuePairs("OFXSETTINGS", idList, kvpPairsList); // Set m_hiIdInstitutions to 0 to force recalculation the next time it is requested m_hiIdInstitutions = 0; } void writePayee(const MyMoneyPayee& p, QSqlQuery& query, bool isUserInfo = false) { if (isUserInfo) { query.bindValue(":id", "USER"); } else { query.bindValue(":id", p.id()); } query.bindValue(":name", p.name()); query.bindValue(":reference", p.reference()); query.bindValue(":email", p.email()); query.bindValue(":addressStreet", p.address()); query.bindValue(":addressCity", p.city()); query.bindValue(":addressZipcode", p.postcode()); query.bindValue(":addressState", p.state()); query.bindValue(":telephone", p.telephone()); query.bindValue(":notes", p.notes()); query.bindValue(":defaultAccountId", p.defaultAccountId()); bool ignoreCase; QString matchKeys; auto type = p.matchData(ignoreCase, matchKeys); query.bindValue(":matchData", static_cast(type)); if (ignoreCase) query.bindValue(":matchIgnoreCase", "Y"); else query.bindValue(":matchIgnoreCase", "N"); query.bindValue(":matchKeys", matchKeys); if (!query.exec()) // krazy:exclude=crashy throw MYMONEYEXCEPTIONSQL("writing Payee"); // krazy:exclude=crashy if (!isUserInfo) m_hiIdPayees = 0; } void writeTag(const MyMoneyTag& ta, QSqlQuery& query) { query.bindValue(":id", ta.id()); query.bindValue(":name", ta.name()); query.bindValue(":tagColor", ta.tagColor().name()); if (ta.isClosed()) query.bindValue(":closed", "Y"); else query.bindValue(":closed", "N"); query.bindValue(":notes", ta.notes()); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Tag"); // krazy:exclude=crashy m_hiIdTags = 0; } void writeAccountList(const QList& accList, QSqlQuery& query) { //MyMoneyMoney balance = m_storagePtr->balance(acc.id(), QDate()); QVariantList idList; QVariantList institutionIdList; QVariantList parentIdList; QVariantList lastReconciledList; QVariantList lastModifiedList; QVariantList openingDateList; QVariantList accountNumberList; QVariantList accountTypeList; QVariantList accountTypeStringList; QVariantList isStockAccountList; QVariantList accountNameList; QVariantList descriptionList; QVariantList currencyIdList; QVariantList balanceList; QVariantList balanceFormattedList; QVariantList transactionCountList; QList > pairs; QList > onlineBankingPairs; foreach (const MyMoneyAccount& a, accList) { idList << a.id(); institutionIdList << a.institutionId(); parentIdList << a.parentAccountId(); if (a.lastReconciliationDate() == QDate()) lastReconciledList << a.lastReconciliationDate(); else lastReconciledList << a.lastReconciliationDate().toString(Qt::ISODate); lastModifiedList << a.lastModified(); if (a.openingDate() == QDate()) openingDateList << a.openingDate(); else openingDateList << a.openingDate().toString(Qt::ISODate); accountNumberList << a.number(); accountTypeList << (int)a.accountType(); accountTypeStringList << MyMoneyAccount::accountTypeToString(a.accountType()); if (a.accountType() == Account::Type::Stock) isStockAccountList << "Y"; else isStockAccountList << "N"; accountNameList << a.name(); descriptionList << a.description(); currencyIdList << a.currencyId(); // This section attempts to get the balance from the database, if possible // That way, the balance fields are kept in sync. If that fails, then // It is assumed that the account actually knows its correct balance. //FIXME: Using exceptions for branching always feels like a kludge. // Look for a better way. try { MyMoneyMoney bal = m_storage->balance(a.id(), QDate()); balanceList << bal.toString(); balanceFormattedList << bal.formatMoney("", -1, false); } catch (const MyMoneyException &) { balanceList << a.balance().toString(); balanceFormattedList << a.balance().formatMoney("", -1, false); } transactionCountList << quint64(m_transactionCountMap[a.id()]); //MMAccount inherits from KVPContainer AND has a KVPContainer member //so handle both pairs << a.pairs(); onlineBankingPairs << a.onlineBankingSettings().pairs(); } query.bindValue(":id", idList); query.bindValue(":institutionId", institutionIdList); query.bindValue(":parentId", parentIdList); query.bindValue(":lastReconciled", lastReconciledList); query.bindValue(":lastModified", lastModifiedList); query.bindValue(":openingDate", openingDateList); query.bindValue(":accountNumber", accountNumberList); query.bindValue(":accountType", accountTypeList); query.bindValue(":accountTypeString", accountTypeStringList); query.bindValue(":isStockAccount", isStockAccountList); query.bindValue(":accountName", accountNameList); query.bindValue(":description", descriptionList); query.bindValue(":currencyId", currencyIdList); query.bindValue(":balance", balanceList); query.bindValue(":balanceFormatted", balanceFormattedList); query.bindValue(":transactionCount", transactionCountList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("writing Account"); //Add in Key-Value Pairs for accounts. writeKeyValuePairs("ACCOUNT", idList, pairs); writeKeyValuePairs("ONLINEBANKING", idList, onlineBankingPairs); m_hiIdAccounts = 0; } void writeTransaction(const QString& txId, const MyMoneyTransaction& tx, QSqlQuery& query, const QString& type) { query.bindValue(":id", txId); query.bindValue(":txType", type); query.bindValue(":postDate", tx.postDate().toString(Qt::ISODate)); query.bindValue(":memo", tx.memo()); query.bindValue(":entryDate", tx.entryDate().toString(Qt::ISODate)); query.bindValue(":currencyId", tx.commodity()); query.bindValue(":bankId", tx.bankID()); if (!query.exec()) // krazy:exclude=crashy throw MYMONEYEXCEPTIONSQL("writing Transaction"); // krazy:exclude=crashy m_txPostDate = tx.postDate(); // FIXME: TEMP till Tom puts date in split object QList splitList = tx.splits(); writeSplits(txId, type, splitList); //Add in Key-Value Pairs for transactions. QVariantList idList; idList << txId; deleteKeyValuePairs("TRANSACTION", idList); QList > pairs; pairs << tx.pairs(); writeKeyValuePairs("TRANSACTION", idList, pairs); m_hiIdTransactions = 0; } void writeSplits(const QString& txId, const QString& type, const QList& splitList) { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QList insertList; QList updateList; QList insertIdList; QList updateIdList; QSqlQuery query(*q); query.prepare("SELECT splitId FROM kmmSplits where transactionId = :id;"); query.bindValue(":id", txId); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Split list"); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toUInt()); QSqlQuery query2(*q); query.prepare(m_db.m_tables["kmmSplits"].updateString()); query2.prepare(m_db.m_tables["kmmSplits"].insertString()); auto i = 0; for (auto it = splitList.constBegin(); it != splitList.constEnd(); ++it) { if (dbList.contains(i)) { dbList.removeAll(i); updateList << *it; updateIdList << i; } else { ++m_splits; insertList << *it; insertIdList << i; } ++i; } if (!insertList.isEmpty()) { writeSplitList(txId, insertList, type, insertIdList, query2); writeTagSplitsList(txId, insertList, insertIdList); } if (!updateList.isEmpty()) { writeSplitList(txId, updateList, type, updateIdList, query); deleteTagSplitsList(txId, updateIdList); writeTagSplitsList(txId, updateList, updateIdList); } if (!dbList.isEmpty()) { QVector txIdList(dbList.count(), txId); QVariantList splitIdList; query.prepare("DELETE FROM kmmSplits WHERE transactionId = :txId AND splitId = :splitId"); // qCopy segfaults here, so do it with a hand-rolled loop foreach (int it, dbList) { splitIdList << it; } query.bindValue(":txId", txIdList.toList()); query.bindValue(":splitId", splitIdList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Splits"); } } void writeTagSplitsList (const QString& txId, const QList& splitList, const QList& splitIdList) { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QVariantList tagIdList; QVariantList txIdList; QVariantList splitIdList_TagSplits; QVariantList tagSplitsIdList; int i = 0, l = 0; foreach (const MyMoneySplit& s, splitList) { for (l = 0; l < s.tagIdList().size(); ++l) { tagIdList << s.tagIdList()[l]; splitIdList_TagSplits << splitIdList[i]; txIdList << txId; } i++; } QSqlQuery query(*q); query.prepare(m_db.m_tables["kmmTagSplits"].insertString()); query.bindValue(":tagId", tagIdList); query.bindValue(":splitId", splitIdList_TagSplits); query.bindValue(":transactionId", txIdList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("writing tagSplits"); } void writeSplitList (const QString& txId, const QList& splitList, const QString& type, const QList& splitIdList, QSqlQuery& query) { QVariantList txIdList; QVariantList typeList; QVariantList payeeIdList; QVariantList reconcileDateList; QVariantList actionList; QVariantList reconcileFlagList; QVariantList valueList; QVariantList valueFormattedList; QVariantList sharesList; QVariantList sharesFormattedList; QVariantList priceList; QVariantList priceFormattedList; QVariantList memoList; QVariantList accountIdList; QVariantList costCenterIdList; QVariantList checkNumberList; QVariantList postDateList; QVariantList bankIdList; QVariantList kvpIdList; QList > kvpPairsList; int i = 0; foreach (const MyMoneySplit& s, splitList) { txIdList << txId; typeList << type; payeeIdList << s.payeeId(); if (s.reconcileDate() == QDate()) reconcileDateList << s.reconcileDate(); else reconcileDateList << s.reconcileDate().toString(Qt::ISODate); actionList << s.action(); reconcileFlagList << (int)s.reconcileFlag(); valueList << s.value().toString(); valueFormattedList << s.value().formatMoney("", -1, false).replace(QChar(','), QChar('.')); sharesList << s.shares().toString(); MyMoneyAccount acc = m_storage->account(s.accountId()); MyMoneySecurity sec = m_storage->security(acc.currencyId()); sharesFormattedList << s.price(). formatMoney("", MyMoneyMoney::denomToPrec(sec.smallestAccountFraction()), false). replace(QChar(','), QChar('.')); MyMoneyMoney price = s.actualPrice(); if (!price.isZero()) { priceList << price.toString(); priceFormattedList << price.formatMoney ("", sec.pricePrecision(), false) .replace(QChar(','), QChar('.')); } else { priceList << QString(); priceFormattedList << QString(); } memoList << s.memo(); accountIdList << s.accountId(); costCenterIdList << s.costCenterId(); checkNumberList << s.number(); postDateList << m_txPostDate.toString(Qt::ISODate); // FIXME: when Tom puts date into split object bankIdList << s.bankID(); kvpIdList << QString(txId + QString::number(splitIdList[i])); kvpPairsList << s.pairs(); ++i; } query.bindValue(":transactionId", txIdList); query.bindValue(":txType", typeList); QVariantList iList; // qCopy segfaults here, so do it with a hand-rolled loop foreach (int it_s, splitIdList) { iList << it_s; } query.bindValue(":splitId", iList); query.bindValue(":payeeId", payeeIdList); query.bindValue(":reconcileDate", reconcileDateList); query.bindValue(":action", actionList); query.bindValue(":reconcileFlag", reconcileFlagList); query.bindValue(":value", valueList); query.bindValue(":valueFormatted", valueFormattedList); query.bindValue(":shares", sharesList); query.bindValue(":sharesFormatted", sharesFormattedList); query.bindValue(":price", priceList); query.bindValue(":priceFormatted", priceFormattedList); query.bindValue(":memo", memoList); query.bindValue(":accountId", accountIdList); query.bindValue(":costCenterId", costCenterIdList); query.bindValue(":checkNumber", checkNumberList); query.bindValue(":postDate", postDateList); query.bindValue(":bankId", bankIdList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("writing Split"); deleteKeyValuePairs("SPLIT", kvpIdList); writeKeyValuePairs("SPLIT", kvpIdList, kvpPairsList); } void writeSchedule(const MyMoneySchedule& sch, QSqlQuery& query, bool insert) { query.bindValue(":id", sch.id()); query.bindValue(":name", sch.name()); query.bindValue(":type", (int)sch.type()); query.bindValue(":typeString", MyMoneySchedule::scheduleTypeToString(sch.type())); query.bindValue(":occurence", (int)sch.occurrencePeriod()); // krazy:exclude=spelling query.bindValue(":occurenceMultiplier", sch.occurrenceMultiplier()); // krazy:exclude=spelling query.bindValue(":occurenceString", sch.occurrenceToString()); // krazy:exclude=spelling query.bindValue(":paymentType", (int)sch.paymentType()); query.bindValue(":paymentTypeString", MyMoneySchedule::paymentMethodToString(sch.paymentType())); query.bindValue(":startDate", sch.startDate().toString(Qt::ISODate)); query.bindValue(":endDate", sch.endDate().toString(Qt::ISODate)); if (sch.isFixed()) { query.bindValue(":fixed", "Y"); } else { query.bindValue(":fixed", "N"); } if (sch.lastDayInMonth()) { query.bindValue(":lastDayInMonth", "Y"); } else { query.bindValue(":lastDayInMonth", "N"); } if (sch.autoEnter()) { query.bindValue(":autoEnter", "Y"); } else { query.bindValue(":autoEnter", "N"); } query.bindValue(":lastPayment", sch.lastPayment()); query.bindValue(":nextPaymentDue", sch.nextDueDate().toString(Qt::ISODate)); query.bindValue(":weekendOption", (int)sch.weekendOption()); query.bindValue(":weekendOptionString", MyMoneySchedule::weekendOptionToString(sch.weekendOption())); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Schedules"); // krazy:exclude=crashy //store the payment history for this scheduled task. //easiest way is to delete all and re-insert; it's not a high use table query.prepare("DELETE FROM kmmSchedulePaymentHistory WHERE schedId = :id;"); query.bindValue(":id", sch.id()); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("deleting Schedule Payment History"); // krazy:exclude=crashy query.prepare(m_db.m_tables["kmmSchedulePaymentHistory"].insertString()); foreach (const QDate& it, sch.recordedPayments()) { query.bindValue(":schedId", sch.id()); query.bindValue(":payDate", it.toString(Qt::ISODate)); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Schedule Payment History"); // krazy:exclude=crashy } //store the transaction data for this task. if (!insert) { query.prepare(m_db.m_tables["kmmTransactions"].updateString()); } else { query.prepare(m_db.m_tables["kmmTransactions"].insertString()); } writeTransaction(sch.id(), sch.transaction(), query, "S"); //FIXME: enable when schedules have KVPs. //Add in Key-Value Pairs for transactions. //deleteKeyValuePairs("SCHEDULE", sch.id()); //writeKeyValuePairs("SCHEDULE", sch.id(), sch.pairs()); } void writeSecurity(const MyMoneySecurity& security, QSqlQuery& query) { query.bindValue(":id", security.id()); query.bindValue(":name", security.name()); query.bindValue(":symbol", security.tradingSymbol()); query.bindValue(":type", static_cast(security.securityType())); query.bindValue(":typeString", MyMoneySecurity::securityTypeToString(security.securityType())); query.bindValue(":roundingMethod", static_cast(security.roundingMethod())); query.bindValue(":smallestAccountFraction", security.smallestAccountFraction()); query.bindValue(":pricePrecision", security.pricePrecision()); query.bindValue(":tradingCurrency", security.tradingCurrency()); query.bindValue(":tradingMarket", security.tradingMarket()); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Securities"); // krazy:exclude=crashy //Add in Key-Value Pairs for security QVariantList idList; idList << security.id(); QList > pairs; pairs << security.pairs(); writeKeyValuePairs("SECURITY", idList, pairs); m_hiIdSecurities = 0; } void writePricePair(const MyMoneyPriceEntries& p) { MyMoneyPriceEntries::ConstIterator it; for (it = p.constBegin(); it != p.constEnd(); ++it) { writePrice(*it); signalProgress(++m_prices, 0); } } void writePrice(const MyMoneyPrice& p) { Q_Q(MyMoneyStorageSql); QSqlQuery query(*q); query.prepare(m_db.m_tables["kmmPrices"].insertString()); query.bindValue(":fromId", p.from()); query.bindValue(":toId", p.to()); query.bindValue(":priceDate", p.date().toString(Qt::ISODate)); query.bindValue(":price", p.rate(QString()).toString()); query.bindValue(":priceFormatted", p.rate(QString()).formatMoney("", 2)); query.bindValue(":priceSource", p.source()); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Prices"); // krazy:exclude=crashy } void writeCurrency(const MyMoneySecurity& currency, QSqlQuery& query) { query.bindValue(":ISOcode", currency.id()); query.bindValue(":name", currency.name()); query.bindValue(":type", static_cast(currency.securityType())); query.bindValue(":typeString", MyMoneySecurity::securityTypeToString(currency.securityType())); // writing the symbol as three short ints is a PITA, but the // problem is that database drivers have incompatible ways of declaring UTF8 QString symbol = currency.tradingSymbol() + " "; const ushort* symutf = symbol.utf16(); //int ix = 0; //while (x[ix] != '\0') qDebug() << "symbol" << symbol << "char" << ix << "=" << x[ix++]; //q.bindValue(":symbol1", symbol.mid(0,1).unicode()->unicode()); //q.bindValue(":symbol2", symbol.mid(1,1).unicode()->unicode()); //q.bindValue(":symbol3", symbol.mid(2,1).unicode()->unicode()); query.bindValue(":symbol1", symutf[0]); query.bindValue(":symbol2", symutf[1]); query.bindValue(":symbol3", symutf[2]); query.bindValue(":symbolString", symbol); query.bindValue(":smallestCashFraction", currency.smallestCashFraction()); query.bindValue(":smallestAccountFraction", currency.smallestAccountFraction()); query.bindValue(":pricePrecision", currency.pricePrecision()); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Currencies"); // krazy:exclude=crashy } void writeReport(const MyMoneyReport& rep, QSqlQuery& query) { QDomDocument d; // create a dummy XML document QDomElement e = d.createElement("REPORTS"); d.appendChild(e); MyMoneyXmlContentHandler2::writeReport(rep, d, e); // write the XML to document query.bindValue(":id", rep.id()); query.bindValue(":name", rep.name()); query.bindValue(":XML", d.toString()); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Reports"); // krazy:exclude=crashy } void writeBudget(const MyMoneyBudget& bud, QSqlQuery& query) { QDomDocument d; // create a dummy XML document QDomElement e = d.createElement("BUDGETS"); d.appendChild(e); MyMoneyXmlContentHandler2::writeBudget(bud, d, e); // write the XML to document query.bindValue(":id", bud.id()); query.bindValue(":name", bud.name()); query.bindValue(":start", bud.budgetStart()); query.bindValue(":XML", d.toString()); if (!query.exec()) // krazy:exclude=crashy throw MYMONEYEXCEPTIONSQL("writing Budgets"); // krazy:exclude=crashy } void writeKeyValuePairs(const QString& kvpType, const QVariantList& kvpId, const QList >& pairs) { Q_Q(MyMoneyStorageSql); if (pairs.empty()) return; QVariantList type; QVariantList id; QVariantList key; QVariantList value; int pairCount = 0; for (int i = 0; i < kvpId.size(); ++i) { QMap::ConstIterator it; for (it = pairs[i].constBegin(); it != pairs[i].constEnd(); ++it) { type << kvpType; id << kvpId[i]; key << it.key(); value << it.value(); } pairCount += pairs[i].size(); } QSqlQuery query(*q); query.prepare(m_db.m_tables["kmmKeyValuePairs"].insertString()); query.bindValue(":kvpType", type); query.bindValue(":kvpId", id); query.bindValue(":kvpKey", key); query.bindValue(":kvpData", value); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("writing KVP"); m_kvps += pairCount; } void writeOnlineJob(const onlineJob& job, QSqlQuery& query) { Q_ASSERT(job.id().startsWith('O')); query.bindValue(":id", job.id()); query.bindValue(":type", job.taskIid()); query.bindValue(":jobSend", job.sendDate()); query.bindValue(":bankAnswerDate", job.bankAnswerDate()); switch (job.bankAnswerState()) { case eMyMoney::OnlineJob::sendingState::acceptedByBank: query.bindValue(":state", QLatin1String("acceptedByBank")); break; case eMyMoney::OnlineJob::sendingState::rejectedByBank: query.bindValue(":state", QLatin1String("rejectedByBank")); break; case eMyMoney::OnlineJob::sendingState::abortedByUser: query.bindValue(":state", QLatin1String("abortedByUser")); break; case eMyMoney::OnlineJob::sendingState::sendingError: query.bindValue(":state", QLatin1String("sendingError")); break; case eMyMoney::OnlineJob::sendingState::noBankAnswer: default: query.bindValue(":state", QLatin1String("noBankAnswer")); } query.bindValue(":locked", QVariant::fromValue(job.isLocked() ? QLatin1String("Y") : QLatin1String("N"))); } void writePayeeIdentifier(const payeeIdentifier& pid, QSqlQuery& query) { query.bindValue(":id", pid.idString()); query.bindValue(":type", pid.iid()); if (!query.exec()) { // krazy:exclude=crashy qWarning() << buildError(query, Q_FUNC_INFO, QString("modifying payeeIdentifier")); throw MYMONEYEXCEPTIONSQL("modifying payeeIdentifier"); // krazy:exclude=crashy } } /** @} */ /** * @name readMethods * @{ */ void readFileInfo() { Q_Q(MyMoneyStorageSql); signalProgress(0, 1, QObject::tr("Loading file information...")); QSqlQuery query(*q); query.prepare( "SELECT " " created, lastModified, " " encryptData, logonUser, logonAt, " " (SELECT count(*) FROM kmmInstitutions) AS institutions, " " (SELECT count(*) from kmmAccounts) AS accounts, " " (SELECT count(*) FROM kmmCurrencies) AS currencies, " " (SELECT count(*) FROM kmmPayees) AS payees, " " (SELECT count(*) FROM kmmTags) AS tags, " " (SELECT count(*) FROM kmmTransactions) AS transactions, " " (SELECT count(*) FROM kmmSplits) AS splits, " " (SELECT count(*) FROM kmmSecurities) AS securities, " " (SELECT count(*) FROM kmmCurrencies) AS currencies, " " (SELECT count(*) FROM kmmSchedules) AS schedules, " " (SELECT count(*) FROM kmmPrices) AS prices, " " (SELECT count(*) FROM kmmKeyValuePairs) AS kvps, " " (SELECT count(*) FROM kmmReportConfig) AS reports, " " (SELECT count(*) FROM kmmBudgetConfig) AS budgets, " " (SELECT count(*) FROM kmmOnlineJobs) AS onlineJobs, " " (SELECT count(*) FROM kmmPayeeIdentifier) AS payeeIdentifier " "FROM kmmFileInfo;" ); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("reading FileInfo"); // krazy:exclude=crashy if (!query.next()) throw MYMONEYEXCEPTIONSQL("retrieving FileInfo"); QSqlRecord rec = query.record(); m_storage->setCreationDate(GETDATE(rec.indexOf("created"))); m_storage->setLastModificationDate(GETDATE(rec.indexOf("lastModified"))); m_institutions = (ulong) GETULL(rec.indexOf("institutions")); m_accounts = (ulong) GETULL(rec.indexOf("accounts")); m_payees = (ulong) GETULL(rec.indexOf("payees")); m_tags = (ulong) GETULL(rec.indexOf("tags")); m_transactions = (ulong) GETULL(rec.indexOf("transactions")); m_splits = (ulong) GETULL(rec.indexOf("splits")); m_securities = (ulong) GETULL(rec.indexOf("securities")); m_currencies = (ulong) GETULL(rec.indexOf("currencies")); m_schedules = (ulong) GETULL(rec.indexOf("schedules")); m_prices = (ulong) GETULL(rec.indexOf("prices")); m_kvps = (ulong) GETULL(rec.indexOf("kvps")); m_reports = (ulong) GETULL(rec.indexOf("reports")); m_budgets = (ulong) GETULL(rec.indexOf("budgets")); m_onlineJobs = (ulong) GETULL(rec.indexOf("onlineJobs")); m_payeeIdentifier = (ulong) GETULL(rec.indexOf("payeeIdentifier")); m_encryptData = GETSTRING(rec.indexOf("encryptData")); m_logonUser = GETSTRING(rec.indexOf("logonUser")); m_logonAt = GETDATETIME(rec.indexOf("logonAt")); signalProgress(1, 0); m_storage->setPairs(readKeyValuePairs("STORAGE", QString("")).pairs()); } void readLogonData(); void readUserInformation(); void readInstitutions() { Q_Q(MyMoneyStorageSql); try { QMap iList = q->fetchInstitutions(); m_storage->loadInstitutions(iList); readFileInfo(); } catch (const MyMoneyException &) { throw; } } void readAccounts() { Q_Q(MyMoneyStorageSql); m_storage->loadAccounts(q->fetchAccounts()); } void readTransactions(const QString& tidList, const QString& dateClause) { Q_Q(MyMoneyStorageSql); try { m_storage->loadTransactions(q->fetchTransactions(tidList, dateClause)); } catch (const MyMoneyException &) { throw; } } void readTransactions() { readTransactions(QString(), QString()); } void readSplit(MyMoneySplit& s, const QSqlQuery& query) const { Q_Q(const MyMoneyStorageSql); // Set these up as statics, since the field numbers should not change // during execution. static const MyMoneyDbTable& t = m_db.m_tables["kmmSplits"]; static const int splitIdCol = t.fieldNumber("splitId"); static const int transactionIdCol = t.fieldNumber("transactionId"); static const int payeeIdCol = t.fieldNumber("payeeId"); static const int reconcileDateCol = t.fieldNumber("reconcileDate"); static const int actionCol = t.fieldNumber("action"); static const int reconcileFlagCol = t.fieldNumber("reconcileFlag"); static const int valueCol = t.fieldNumber("value"); static const int sharesCol = t.fieldNumber("shares"); static const int priceCol = t.fieldNumber("price"); static const int memoCol = t.fieldNumber("memo"); static const int accountIdCol = t.fieldNumber("accountId"); static const int costCenterIdCol = t.fieldNumber("costCenterId"); static const int checkNumberCol = t.fieldNumber("checkNumber"); // static const int postDateCol = t.fieldNumber("postDate"); // FIXME - when Tom puts date into split object static const int bankIdCol = t.fieldNumber("bankId"); s.clearId(); QList tagIdList; QSqlQuery query1(*const_cast (q)); query1.prepare("SELECT tagId from kmmTagSplits where splitId = :id and transactionId = :transactionId"); query1.bindValue(":id", GETSTRING(splitIdCol)); query1.bindValue(":transactionId", GETSTRING(transactionIdCol)); if (!query1.exec()) throw MYMONEYEXCEPTIONSQL("reading tagId in Split"); // krazy:exclude=crashy while (query1.next()) tagIdList << query1.value(0).toString(); s.setTagIdList(tagIdList); s.setPayeeId(GETSTRING(payeeIdCol)); s.setReconcileDate(GETDATE(reconcileDateCol)); s.setAction(GETSTRING(actionCol)); s.setReconcileFlag(static_cast(GETINT(reconcileFlagCol))); s.setValue(MyMoneyMoney(MyMoneyUtils::QStringEmpty(GETSTRING(valueCol)))); s.setShares(MyMoneyMoney(MyMoneyUtils::QStringEmpty(GETSTRING(sharesCol)))); s.setPrice(MyMoneyMoney(MyMoneyUtils::QStringEmpty(GETSTRING(priceCol)))); s.setMemo(GETSTRING(memoCol)); s.setAccountId(GETSTRING(accountIdCol)); s.setCostCenterId(GETSTRING(costCenterIdCol)); s.setNumber(GETSTRING(checkNumberCol)); //s.setPostDate(GETDATETIME(postDateCol)); // FIXME - when Tom puts date into split object s.setBankID(GETSTRING(bankIdCol)); return; } const MyMoneyKeyValueContainer readKeyValuePairs(const QString& kvpType, const QString& kvpId) const { Q_Q(const MyMoneyStorageSql); MyMoneyKeyValueContainer list; QSqlQuery query(*const_cast (q)); query.prepare("SELECT kvpKey, kvpData from kmmKeyValuePairs where kvpType = :type and kvpId = :id;"); query.bindValue(":type", kvpType); query.bindValue(":id", kvpId); if (!query.exec()) throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("reading Kvp for %1 %2").arg(kvpType) // krazy:exclude=crashy .arg(kvpId)); while (query.next()) list.setValue(query.value(0).toString(), query.value(1).toString()); return (list); } const QHash readKeyValuePairs(const QString& kvpType, const QStringList& kvpIdList) const { Q_Q(const MyMoneyStorageSql); QHash retval; QSqlQuery query(*const_cast (q)); QString idList; if (!kvpIdList.empty()) { idList = QString(" and kvpId IN ('%1')").arg(kvpIdList.join("', '")); } QString sQuery = QString("SELECT kvpId, kvpKey, kvpData from kmmKeyValuePairs where kvpType = :type %1 order by kvpId;").arg(idList); query.prepare(sQuery); query.bindValue(":type", kvpType); if (!query.exec()) throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("reading Kvp List for %1").arg(kvpType)); // krazy:exclude=crashy // Reserve enough space for all values. retval.reserve(kvpIdList.size()); // The loop below is designed to limit the number of calls to // QHash::operator[] in order to speed up calls to this function. This // assumes that QString::operator== is faster. /* if (q.next()) { QString oldkey = q.value(0).toString(); MyMoneyKeyValueContainer& kvpc = retval[oldkey]; kvpc.setValue(q.value(1).toString(), q.value(2).toString()); while (q.next()) { if (q.value(0).toString() != oldkey) { oldkey = q.value(0).toString(); kvpc = retval[oldkey]; } kvpc.setValue(q.value(1).toString(), q.value(2).toString()); } } */ while (query.next()) { retval[query.value(0).toString()].setValue(query.value(1).toString(), query.value(2).toString()); } return (retval); } void readSchedules() { Q_Q(MyMoneyStorageSql); try { m_storage->loadSchedules(q->fetchSchedules()); } catch (const MyMoneyException &) { throw; } } void readSecurities() { Q_Q(MyMoneyStorageSql); try { m_storage->loadSecurities(q->fetchSecurities()); } catch (const MyMoneyException &) { throw; } } void readPrices() { // try { // m_storage->addPrice(MyMoneyPrice(from, to, date, rate, source)); // } catch (const MyMoneyException &) { // throw; // } } void readCurrencies() { Q_Q(MyMoneyStorageSql); try { m_storage->loadCurrencies(q->fetchCurrencies()); } catch (const MyMoneyException &) { throw; } } void readReports() { Q_Q(MyMoneyStorageSql); try { m_storage->loadReports(q->fetchReports()); } catch (const MyMoneyException &) { throw; } } void readBudgets() { Q_Q(MyMoneyStorageSql); m_storage->loadBudgets(q->fetchBudgets()); } void readOnlineJobs() { Q_Q(MyMoneyStorageSql); m_storage->loadOnlineJobs(q->fetchOnlineJobs()); } /** @} */ void deleteTransaction(const QString& id) { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QSqlQuery query(*q); QVariantList idList; idList << id; query.prepare("DELETE FROM kmmSplits WHERE transactionId = :transactionId;"); query.bindValue(":transactionId", idList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Splits"); query.prepare("DELETE FROM kmmKeyValuePairs WHERE kvpType = 'SPLIT' " "AND kvpId LIKE '?%'"); query.bindValue(1, idList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Splits KVP"); m_splits -= query.numRowsAffected(); deleteKeyValuePairs("TRANSACTION", idList); query.prepare(m_db.m_tables["kmmTransactions"].deleteString()); query.bindValue(":id", idList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Transaction"); } void deleteTagSplitsList(const QString& txId, const QList& splitIdList) { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QVariantList iList; QVariantList transactionIdList; // qCopy segfaults here, so do it with a hand-rolled loop foreach (int it_s, splitIdList) { iList << it_s; transactionIdList << txId; } QSqlQuery query(*q); query.prepare("DELETE FROM kmmTagSplits WHERE transactionId = :transactionId AND splitId = :splitId"); query.bindValue(":splitId", iList); query.bindValue(":transactionId", transactionIdList); if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting tagSplits"); } void deleteSchedule(const QString& id) { Q_Q(MyMoneyStorageSql); deleteTransaction(id); QSqlQuery query(*q); query.prepare("DELETE FROM kmmSchedulePaymentHistory WHERE schedId = :id"); query.bindValue(":id", id); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("deleting Schedule Payment History"); // krazy:exclude=crashy query.prepare(m_db.m_tables["kmmSchedules"].deleteString()); query.bindValue(":id", id); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("deleting Schedule"); // krazy:exclude=crashy //FIXME: enable when schedules have KVPs. //deleteKeyValuePairs("SCHEDULE", id); } void deleteKeyValuePairs(const QString& kvpType, const QVariantList& idList) { Q_Q(MyMoneyStorageSql); QSqlQuery query(*q); query.prepare("DELETE FROM kmmKeyValuePairs WHERE kvpType = :kvpType AND kvpId = :kvpId;"); QVariantList typeList; for (int i = 0; i < idList.size(); ++i) { typeList << kvpType; } query.bindValue(":kvpType", typeList); query.bindValue(":kvpId", idList); if (!query.execBatch()) { QString idString; for (int i = 0; i < idList.size(); ++i) { idString.append(idList[i].toString() + ' '); } throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("deleting kvp for %1 %2").arg(kvpType).arg(idString)); } m_kvps -= query.numRowsAffected(); } ulong calcHighId(ulong i, const QString& id) { QString nid = id; ulong high = (ulong) nid.remove(QRegExp("[A-Z]*")).toULongLong(); return std::max(high, i); } void setVersion(const QString& version); int splitState(const TransactionFilter::State& state) const { auto rc = (int)Split::State::NotReconciled; switch (state) { default: case TransactionFilter::State::NotReconciled: break; case TransactionFilter::State::Cleared: rc = (int)Split::State::Cleared; break; case TransactionFilter::State::Reconciled: rc = (int)Split::State::Reconciled; break; case TransactionFilter::State::Frozen: rc = (int)Split::State::Frozen; break; } return rc; } QDate getDate(const QString& date) const { return (date.isNull() ? QDate() : QDate::fromString(date, Qt::ISODate)); } QDateTime getDateTime(const QString& date) const { return (date.isNull() ? QDateTime() : QDateTime::fromString(date, Qt::ISODate)); } bool fileExists(const QString& dbName) { QFile f(dbName); if (!f.exists()) { m_error = i18n("SQLite file %1 does not exist", dbName); return (false); } return (true); } /** @brief a function to build a comprehensive error message for an SQL error */ QString& buildError(const QSqlQuery& query, const QString& function, const QString& messageb) const { Q_Q(const MyMoneyStorageSql); return (buildError(query, function, messageb, q)); } QString& buildError(const QSqlQuery& query, const QString& function, const QString& message, const QSqlDatabase* db) const { Q_Q(const MyMoneyStorageSql); QString s = QString("Error in function %1 : %2").arg(function).arg(message); s += QString("\nDriver = %1, Host = %2, User = %3, Database = %4") .arg(db->driverName()).arg(db->hostName()).arg(db->userName()).arg(db->databaseName()); QSqlError e = db->lastError(); s += QString("\nDriver Error: %1").arg(e.driverText()); s += QString("\nDatabase Error No %1: %2").arg(e.number()).arg(e.databaseText()); s += QString("\nText: %1").arg(e.text()); s += QString("\nError type %1").arg(e.type()); e = query.lastError(); s += QString("\nExecuted: %1").arg(query.executedQuery()); s += QString("\nQuery error No %1: %2").arg(e.number()).arg(e.text()); s += QString("\nError type %1").arg(e.type()); const_cast (q)->d_func()->m_error = s; qDebug("%s", qPrintable(s)); const_cast (q)->cancelCommitUnit(function); return (const_cast (q)->d_func()->m_error); } /** * MyMoneyStorageSql create database * * @param url pseudo-URL of database to be opened * * @return true - creation successful * @return false - could not create * */ bool createDatabase(const QUrl &url) { Q_Q(MyMoneyStorageSql); int rc = true; if (!m_driver->requiresCreation()) return(true); // not needed for sqlite QString dbName = url.path().right(url.path().length() - 1); // remove separator slash if (!m_driver->canAutocreate()) { m_error = i18n("Automatic database creation for type %1 is not currently implemented.\n" "Please create database %2 manually", q->driverName(), dbName); return (false); } // create the database (only works for mysql and postgre at present) { // for this code block, see QSqlDatabase API re removeDatabase QSqlDatabase maindb = QSqlDatabase::addDatabase(q->driverName(), "main"); maindb.setDatabaseName(m_driver->defaultDbName()); maindb.setHostName(url.host()); maindb.setUserName(url.userName()); maindb.setPassword(url.password()); if (!maindb.open()) { throw MYMONEYEXCEPTION(QString::fromLatin1("opening database %1 in function %2") .arg(maindb.databaseName()).arg(Q_FUNC_INFO)); } else { QSqlQuery qm(maindb); qm.exec(QString::fromLatin1("PRAGMA key = '%1'").arg(q->password())); QString qs = m_driver->createDbString(dbName) + ';'; if (!qm.exec(qs)) { // krazy:exclude=crashy buildError(qm, Q_FUNC_INFO, i18n("Error in create database %1; do you have create permissions?", dbName), &maindb); rc = false; } maindb.close(); } } QSqlDatabase::removeDatabase("main"); return (rc); } int upgradeDb() { Q_Q(MyMoneyStorageSql); //signalProgress(0, 1, QObject::tr("Upgrading database...")); QSqlQuery query(*q); query.prepare("SELECT version FROM kmmFileInfo;"); if (!query.exec() || !query.next()) { // krazy:exclude=crashy if (!m_newDatabase) { buildError(query, Q_FUNC_INFO, "Error retrieving file info (version)"); return(1); } else { m_dbVersion = m_db.currentVersion(); m_storage->setFileFixVersion(m_storage->currentFixVersion()); QSqlQuery query2(*q); query2.prepare("UPDATE kmmFileInfo SET version = :version, \ fixLevel = :fixLevel;"); query2.bindValue(":version", m_dbVersion); query2.bindValue(":fixLevel", m_storage->currentFixVersion()); if (!query2.exec()) { // krazy:exclude=crashy buildError(query2, Q_FUNC_INFO, "Error updating file info(version)"); return(1); } return (0); } } // prior to dbv6, 'version' format was 'dbversion.fixLevel+1' // as of dbv6, these are separate fields QString version = query.value(0).toString(); if (version.contains('.')) { m_dbVersion = query.value(0).toString().section('.', 0, 0).toUInt(); m_storage->setFileFixVersion(query.value(0).toString().section('.', 1, 1).toUInt() - 1); } else { m_dbVersion = version.toUInt(); query.prepare("SELECT fixLevel FROM kmmFileInfo;"); if (!query.exec() || !query.next()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error retrieving file info (fixLevel)"); return(1); } m_storage->setFileFixVersion(query.value(0).toUInt()); } if (m_dbVersion == m_db.currentVersion()) return 0; int rc = 0; // Drop VIEWs QStringList lowerTables = tables(QSql::AllTables); for (QStringList::iterator i = lowerTables.begin(); i != lowerTables.end(); ++i) { (*i) = (*i).toLower(); } for (QMap::ConstIterator tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) { if (lowerTables.contains(tt.key().toLower())) { if (!query.exec("DROP VIEW " + tt.value().name() + ';')) // krazy:exclude=crashy throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("dropping view %1").arg(tt.key())); } } while ((m_dbVersion < m_db.currentVersion()) && (rc == 0)) { switch (m_dbVersion) { case 0: if ((rc = upgradeToV1()) != 0) return (1); ++m_dbVersion; break; case 1: if ((rc = upgradeToV2()) != 0) return (1); ++m_dbVersion; break; case 2: if ((rc = upgradeToV3()) != 0) return (1); ++m_dbVersion; break; case 3: if ((rc = upgradeToV4()) != 0) return (1); ++m_dbVersion; break; case 4: if ((rc = upgradeToV5()) != 0) return (1); ++m_dbVersion; break; case 5: if ((rc = upgradeToV6()) != 0) return (1); ++m_dbVersion; break; case 6: if ((rc = upgradeToV7()) != 0) return (1); ++m_dbVersion; break; case 7: if ((rc = upgradeToV8()) != 0) return (1); ++m_dbVersion; break; case 8: if ((rc = upgradeToV9()) != 0) return (1); ++m_dbVersion; break; case 9: if ((rc = upgradeToV10()) != 0) return (1); ++m_dbVersion; break; case 10: if ((rc = upgradeToV11()) != 0) return (1); ++m_dbVersion; break; case 11: if ((rc = upgradeToV12()) != 0) return (1); ++m_dbVersion; break; default: qWarning("Unknown version number in database - %d", m_dbVersion); } } // restore VIEWs lowerTables = tables(QSql::AllTables); for (QStringList::iterator i = lowerTables.begin(); i != lowerTables.end(); ++i) { (*i) = (*i).toLower(); } for (QMap::ConstIterator tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) { if (!lowerTables.contains(tt.key().toLower())) { if (!query.exec(tt.value().createString())) // krazy:exclude=crashy throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("creating view %1").arg(tt.key())); } } // write updated version to DB //setVersion(QString("%1.%2").arg(m_dbVersion).arg(m_minorVersion)) query.prepare(QString("UPDATE kmmFileInfo SET version = :version;")); query.bindValue(":version", m_dbVersion); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error updating db version"); return (1); } //signalProgress(-1,-1); return (0); } int upgradeToV1() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QSqlQuery query(*q); // change kmmSplits pkey to (transactionId, splitId) if (!query.exec("ALTER TABLE kmmSplits ADD PRIMARY KEY (transactionId, splitId);")) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error updating kmmSplits pkey"); return (1); } // change kmmSplits alter checkNumber varchar(32) if (!query.exec(m_db.m_tables["kmmSplits"].modifyColumnString(m_driver, "checkNumber", // krazy:exclude=crashy MyMoneyDbColumn("checkNumber", "varchar(32)")))) { buildError(query, Q_FUNC_INFO, "Error expanding kmmSplits.checkNumber"); return (1); } // change kmmSplits add postDate datetime if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion)) return (1); // initialize it to same value as transaction (do it the long way round) query.prepare("SELECT id, postDate FROM kmmTransactions WHERE txType = 'N';"); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error priming kmmSplits.postDate"); return (1); } QMap tids; while (query.next()) tids[query.value(0).toString()] = query.value(1).toDateTime(); QMap::ConstIterator it; for (it = tids.constBegin(); it != tids.constEnd(); ++it) { query.prepare("UPDATE kmmSplits SET postDate=:postDate WHERE transactionId = :id;"); query.bindValue(":postDate", it.value().toString(Qt::ISODate)); query.bindValue(":id", it.key()); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "priming kmmSplits.postDate"); return(1); } } // add index to kmmKeyValuePairs to (kvpType,kvpId) QStringList list; list << "kvpType" << "kvpId"; if (!query.exec(MyMoneyDbIndex("kmmKeyValuePairs", "kmmKVPtype_id", list, false).generateDDL(m_driver) + ';')) { buildError(query, Q_FUNC_INFO, "Error adding kmmKeyValuePairs index"); return (1); } // add index to kmmSplits to (accountId, txType) list.clear(); list << "accountId" << "txType"; if (!query.exec(MyMoneyDbIndex("kmmSplits", "kmmSplitsaccount_type", list, false).generateDDL(m_driver) + ';')) { buildError(query, Q_FUNC_INFO, "Error adding kmmSplits index"); return (1); } // change kmmSchedulePaymentHistory pkey to (schedId, payDate) if (!query.exec("ALTER TABLE kmmSchedulePaymentHistory ADD PRIMARY KEY (schedId, payDate);")) { buildError(query, Q_FUNC_INFO, "Error updating kmmSchedulePaymentHistory pkey"); return (1); } // change kmmPrices pkey to (fromId, toId, priceDate) if (!query.exec("ALTER TABLE kmmPrices ADD PRIMARY KEY (fromId, toId, priceDate);")) { buildError(query, Q_FUNC_INFO, "Error updating kmmPrices pkey"); return (1); } // change kmmReportConfig pkey to (name) // There wasn't one previously, so no need to drop it. if (!query.exec("ALTER TABLE kmmReportConfig ADD PRIMARY KEY (name);")) { buildError(query, Q_FUNC_INFO, "Error updating kmmReportConfig pkey"); return (1); } // change kmmFileInfo add budgets, hiBudgetId unsigned bigint // change kmmFileInfo add logonUser // change kmmFileInfo add logonAt datetime if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion)) return (1); // change kmmAccounts add transactionCount unsigned bigint as last field if (!alterTable(m_db.m_tables["kmmAccounts"], m_dbVersion)) return (1); // calculate the transaction counts. the application logic defines an account's tx count // in such a way as to count multiple splits in a tx which reference the same account as one. // this is the only way I can think of to do this which will work in sqlite too. // inefficient, but it only gets done once... // get a list of all accounts so we'll get a zero value for those without txs query.prepare("SELECT id FROM kmmAccounts"); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error retrieving accounts for transaction counting"); return(1); } while (query.next()) { m_transactionCountMap[query.value(0).toString()] = 0; } query.prepare("SELECT accountId, transactionId FROM kmmSplits WHERE txType = 'N' ORDER BY 1, 2"); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error retrieving splits for transaction counting"); return(1); } QString lastAcc, lastTx; while (query.next()) { QString thisAcc = query.value(0).toString(); QString thisTx = query.value(1).toString(); if ((thisAcc != lastAcc) || (thisTx != lastTx)) ++m_transactionCountMap[thisAcc]; lastAcc = thisAcc; lastTx = thisTx; } QHash::ConstIterator itm; query.prepare("UPDATE kmmAccounts SET transactionCount = :txCount WHERE id = :id;"); for (itm = m_transactionCountMap.constBegin(); itm != m_transactionCountMap.constEnd(); ++itm) { query.bindValue(":txCount", QString::number(itm.value())); query.bindValue(":id", itm.key()); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error updating transaction count"); return (1); } } m_transactionCountMap.clear(); return (0); } int upgradeToV2() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QSqlQuery query(*q); // change kmmSplits add price, priceFormatted fields if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion)) return (1); return (0); } int upgradeToV3() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QSqlQuery query(*q); // kmmSchedules - add occurrenceMultiplier // The default value is given here to populate the column. if (!query.exec("ALTER TABLE kmmSchedules ADD COLUMN " + MyMoneyDbIntColumn("occurenceMultiplier", MyMoneyDbIntColumn::SMALL, false, false, true) .generateDDL(m_driver) + " DEFAULT 0;")) { buildError(query, Q_FUNC_INFO, "Error adding kmmSchedules.occurenceMultiplier"); return (1); } //The default is less than any useful value, so as each schedule is hit, it will update //itself to the appropriate value. return 0; } int upgradeToV4() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QSqlQuery query(*q); // kmmSplits - add index on transactionId + splitId QStringList list; list << "transactionId" << "splitId"; if (!query.exec(MyMoneyDbIndex("kmmSplits", "kmmTx_Split", list, false).generateDDL(m_driver) + ';')) { buildError(query, Q_FUNC_INFO, "Error adding kmmSplits index on (transactionId, splitId)"); return (1); } return 0; } int upgradeToV5() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); QSqlQuery query(*q); // kmmSplits - add bankId if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion)) return (1); //kmmPayees - add columns "notes" "defaultAccountId" "matchData" "matchIgnoreCase" "matchKeys"; if (!alterTable(m_db.m_tables["kmmPayees"], m_dbVersion)) return (1); // kmmReportConfig - drop primary key on name since duplicate names are allowed if (!alterTable(m_db.m_tables["kmmReportConfig"], m_dbVersion)) return (1); //} return 0; } int upgradeToV6() { Q_Q(MyMoneyStorageSql); q->startCommitUnit(Q_FUNC_INFO); QSqlQuery query(*q); // kmmFileInfo - add fixLevel if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion)) return (1); // upgrade Mysql to InnoDB transaction-safe engine // the following is not a good way to test for mysql - think of a better way if (!m_driver->tableOptionString().isEmpty()) { for (QMap::ConstIterator tt = m_db.tableBegin(); tt != m_db.tableEnd(); ++tt) { if (!query.exec(QString("ALTER TABLE %1 ENGINE = InnoDB;").arg(tt.value().name()))) { buildError(query, Q_FUNC_INFO, "Error updating to InnoDB"); return (1); } } } // the alterTable function really doesn't work too well // with adding a new column which is also to be primary key // so add the column first if (!query.exec("ALTER TABLE kmmReportConfig ADD COLUMN " + MyMoneyDbColumn("id", "varchar(32)").generateDDL(m_driver) + ';')) { buildError(query, Q_FUNC_INFO, "adding id to report table"); return(1); } QMap reportList = q->fetchReports(); // the V5 database allowed lots of duplicate reports with no // way to distinguish between them. The fetchReports call // will have effectively removed all duplicates // so we now delete from the db and re-write them if (!query.exec("DELETE FROM kmmReportConfig;")) { buildError(query, Q_FUNC_INFO, "Error deleting reports"); return (1); } // add unique id to reports table if (!alterTable(m_db.m_tables["kmmReportConfig"], m_dbVersion)) return(1); QMap::const_iterator it_r; for (it_r = reportList.constBegin(); it_r != reportList.constEnd(); ++it_r) { MyMoneyReport r = *it_r; query.prepare(m_db.m_tables["kmmReportConfig"].insertString()); writeReport(*it_r, query); } q->endCommitUnit(Q_FUNC_INFO); return 0; } int upgradeToV7() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); QSqlQuery query(*q); // add tags support // kmmFileInfo - add tags and hiTagId if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion)) return (1); m_tags = 0; return 0; } int upgradeToV8() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); // Added onlineJobs and payeeIdentifier if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion)) return (1); return 0; } int upgradeToV9() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); QSqlQuery query(*q); // kmmSplits - add bankId if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion)) return (1); return 0; } int upgradeToV10() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); QSqlQuery query(*q); if (!alterTable(m_db.m_tables["kmmPayeesPayeeIdentifier"], m_dbVersion)) return (1); if (!alterTable(m_db.m_tables["kmmAccountsPayeeIdentifier"], m_dbVersion)) return (1); return 0; } int upgradeToV11() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); QSqlQuery query(*q); // add column roundingMethodCol to kmmSecurities if (!alterTable(m_db.m_tables["kmmSecurities"], m_dbVersion)) return 1; // add column pricePrecision to kmmCurrencies if (!alterTable(m_db.m_tables["kmmCurrencies"], m_dbVersion)) return 1; return 0; } int upgradeToV12() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); switch(haveColumnInTable(QLatin1String("kmmSchedules"), QLatin1String("lastDayInMonth"))) { case -1: return 1; case 1: // column exists, nothing to do break; case 0: // need update of kmmSchedules // add column lastDayInMonth. Simply redo the update for 10 .. 11 if (!alterTable(m_db.m_tables["kmmSchedules"], m_dbVersion-1)) return 1; break; } switch(haveColumnInTable(QLatin1String("kmmSecurities"), QLatin1String("roundingMethod"))) { case -1: return 1; case 1: // column exists, nothing to do break; case 0: // need update of kmmSecurities and kmmCurrencies // add column roundingMethodCol to kmmSecurities. Simply redo the update for 10 .. 11 if (!alterTable(m_db.m_tables["kmmSecurities"], m_dbVersion-1)) return 1; // add column pricePrecision to kmmCurrencies. Simply redo the update for 10 .. 11 if (!alterTable(m_db.m_tables["kmmCurrencies"], m_dbVersion-1)) return 1; break; } return 0; } int createTables() { Q_Q(MyMoneyStorageSql); // check tables, create if required // convert everything to lower case, since SQL standard is case insensitive // table and column names (when not delimited), but some DBMSs disagree. QStringList lowerTables = tables(QSql::AllTables); for (QStringList::iterator i = lowerTables.begin(); i != lowerTables.end(); ++i) { (*i) = (*i).toLower(); } for (QMap::ConstIterator tt = m_db.tableBegin(); tt != m_db.tableEnd(); ++tt) { if (!lowerTables.contains(tt.key().toLower())) { createTable(tt.value()); } } QSqlQuery query(*q); for (QMap::ConstIterator tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) { if (!lowerTables.contains(tt.key().toLower())) { if (!query.exec(tt.value().createString())) throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("creating view %1").arg(tt.key())); } } // The columns to store version info changed with version 6. Prior versions are not supported here but an error is prevented and // an old behaviour is used: call upgradeDb(). m_dbVersion = m_db.currentVersion(); if (m_dbVersion >= 6) { query.prepare(QLatin1String("INSERT INTO kmmFileInfo (version, fixLevel) VALUES(?,?);")); query.bindValue(0, m_dbVersion); query.bindValue(1, m_storage->fileFixVersion()); if (!query.exec()) throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("Saving database version")); } return upgradeDb(); } void createTable(const MyMoneyDbTable& t, int version = std::numeric_limits::max()) { Q_Q(MyMoneyStorageSql); // create the tables QStringList ql = t.generateCreateSQL(m_driver, version).split('\n', QString::SkipEmptyParts); QSqlQuery query(*q); foreach (const QString& i, ql) { if (!query.exec(i)) throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("creating table/index %1").arg(t.name())); } } bool alterTable(const MyMoneyDbTable& t, int fromVersion) { Q_Q(MyMoneyStorageSql); const int toVersion = fromVersion + 1; QString tempTableName = t.name(); tempTableName.replace("kmm", "kmmtmp"); QSqlQuery query(*q); // drop primary key if it has one (and driver supports it) if (t.hasPrimaryKey(fromVersion)) { QString dropString = m_driver->dropPrimaryKeyString(t.name()); if (!dropString.isEmpty()) { if (!query.exec(dropString)) { buildError(query, Q_FUNC_INFO, QString("Error dropping old primary key from %1").arg(t.name())); return false; } } } for (MyMoneyDbTable::index_iterator i = t.indexBegin(); i != t.indexEnd(); ++i) { QString indexName = t.name() + '_' + i->name() + "_idx"; if (!query.exec(m_driver->dropIndexString(t.name(), indexName))) { buildError(query, Q_FUNC_INFO, QString("Error dropping index from %1").arg(t.name())); return false; } } if (!query.exec(QString("ALTER TABLE " + t.name() + " RENAME TO " + tempTableName + ';'))) { buildError(query, Q_FUNC_INFO, QString("Error renaming table %1").arg(t.name())); return false; } createTable(t, toVersion); if (q->getRecCount(tempTableName) > 0) { query.prepare(QString("INSERT INTO " + t.name() + " (" + t.columnList(fromVersion) + ") SELECT " + t.columnList(fromVersion) + " FROM " + tempTableName + ';')); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, QString("Error inserting into new table %1").arg(t.name())); return false; } } if (!query.exec(QString("DROP TABLE " + tempTableName + ';'))) { buildError(query, Q_FUNC_INFO, QString("Error dropping old table %1").arg(t.name())); return false; } return true; } void clean() { Q_Q(MyMoneyStorageSql); // delete all existing records QMap::ConstIterator it = m_db.tableBegin(); QSqlQuery query(*q); while (it != m_db.tableEnd()) { query.prepare(QString("DELETE from %1;").arg(it.key())); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("cleaning database"); // krazy:exclude=crashy ++it; } } int isEmpty() { Q_Q(MyMoneyStorageSql); // check all tables are empty QMap::ConstIterator tt = m_db.tableBegin(); int recordCount = 0; QSqlQuery query(*q); while ((tt != m_db.tableEnd()) && (recordCount == 0)) { query.prepare(QString("select count(*) from %1;").arg((*tt).name())); if (!query.exec()) throw MYMONEYEXCEPTIONSQL("getting record count"); // krazy:exclude=crashy if (!query.next()) throw MYMONEYEXCEPTIONSQL("retrieving record count"); recordCount += query.value(0).toInt(); ++tt; } // a fresh created database contains at least one record (see createTables()) in // the kmmFileInfo table providing file and fix version. So we report empty // even if there is a recordCount of 1 if (recordCount > 1) { return -1; // not empty } else { return 0; } } // for bug 252841 QStringList tables(QSql::TableType tt) { Q_Q(MyMoneyStorageSql); return (m_driver->tables(tt, static_cast(*q))); } //! Returns 1 in case the @a column exists in @a table, 0 if not. In case of error, -1 is returned. int haveColumnInTable(const QString& table, const QString& column) { Q_Q(MyMoneyStorageSql); QSqlQuery query(*q); QString cmd = QString("SELECT * FROM %1 LIMIT 1").arg(table); if(!query.exec(cmd)) { buildError(query, Q_FUNC_INFO, QString("Error detecting if %1 exists in %2").arg(column).arg(table)); return -1; } QSqlRecord rec = query.record(); return (rec.indexOf(column) != -1) ? 1 : 0; } /** * @brief Ensure the storagePlugin with iid was setup * * @throws MyMoneyException in case of an error which makes the use * of the plugin unavailable. */ bool setupStoragePlugin(QString iid) { Q_Q(MyMoneyStorageSql); // setupDatabase has to be called every time because this simple technique to check if was updated already // does not work if a user opens another file // also the setup is removed if the current database transaction is rolled back if (iid.isEmpty() /*|| m_loadedStoragePlugins.contains(iid)*/) return false; QString sqlIID; if (iid == payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()) sqlIID = QString::fromLatin1("org.kmymoney.payeeIdentifier.ibanbic.sqlStoragePlugin"); else if (iid == payeeIdentifiers::nationalAccount::staticPayeeIdentifierIid()) sqlIID = QLatin1String("org.kmymoney.payeeIdentifier.nationalAccount.sqlStoragePlugin"); else if (iid == sepaOnlineTransferImpl::name()) sqlIID = QLatin1String("org.kmymoney.creditTransfer.sepa.sqlStoragePlugin"); else return false; QString errorMsg; KMyMoneyPlugin::storagePlugin* plugin = KServiceTypeTrader::createInstanceFromQuery( QLatin1String("KMyMoney/sqlStoragePlugin"), QString("'%1' ~in [X-KMyMoney-PluginIid]").arg(sqlIID.replace(QLatin1Char('\''), QLatin1String("\\'"))), 0, QVariantList(), &errorMsg ); if (plugin == 0) throw MYMONEYEXCEPTION(QString::fromLatin1("Could not load sqlStoragePlugin '%1', (error: %2)").arg(sqlIID, errorMsg)); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); if (plugin->setupDatabase(*q)) { m_loadedStoragePlugins.insert(sqlIID); return true; } throw MYMONEYEXCEPTION(QString::fromLatin1("Could not install sqlStoragePlugin '%1' in database.").arg(sqlIID)); } bool actOnIBANBICObjectInSQL(SQLAction action, const payeeIdentifier &obj) { payeeIdentifierTyped payeeIdentifier = payeeIdentifierTyped(obj); Q_Q(MyMoneyStorageSql); QSqlQuery query(*q); auto writeQuery = [&]() { query.bindValue(":id", obj.idString()); query.bindValue(":iban", payeeIdentifier->electronicIban()); const auto bic = payeeIdentifier->fullStoredBic(); query.bindValue(":bic", (bic.isEmpty()) ? QVariant(QVariant::String) : bic); query.bindValue(":name", payeeIdentifier->ownerName()); if (!query.exec()) { // krazy:exclude=crashy qWarning("Error while saving ibanbic data for '%s': %s", qPrintable(obj.idString()), qPrintable(query.lastError().text())); return false; } return true; }; switch(action) { case SQLAction::Save: query.prepare("INSERT INTO kmmIbanBic " " ( id, iban, bic, name )" " VALUES( :id, :iban, :bic, :name ) " ); return writeQuery(); case SQLAction::Modify: query.prepare("UPDATE kmmIbanBic SET iban = :iban, bic = :bic, name = :name WHERE id = :id;"); return writeQuery(); case SQLAction::Remove: query.prepare("DELETE FROM kmmIbanBic WHERE id = ?;"); query.bindValue(0, obj.idString()); if (!query.exec()) { qWarning("Error while deleting ibanbic data '%s': %s", qPrintable(obj.idString()), qPrintable(query.lastError().text())); return false; } return true; } return false; } bool actOnNationalAccountObjectInSQL(SQLAction action, const payeeIdentifier &obj) { payeeIdentifierTyped payeeIdentifier = payeeIdentifierTyped(obj); Q_Q(MyMoneyStorageSql); QSqlQuery query(*q); auto writeQuery = [&]() { query.bindValue(":id", obj.idString()); query.bindValue(":countryCode", payeeIdentifier->country()); query.bindValue(":accountNumber", payeeIdentifier->accountNumber()); query.bindValue(":bankCode", (payeeIdentifier->bankCode().isEmpty()) ? QVariant(QVariant::String) : payeeIdentifier->bankCode()); query.bindValue(":name", payeeIdentifier->ownerName()); if (!query.exec()) { // krazy:exclude=crashy qWarning("Error while saving national account number for '%s': %s", qPrintable(obj.idString()), qPrintable(query.lastError().text())); return false; } return true; }; switch(action) { case SQLAction::Save: query.prepare("INSERT INTO kmmNationalAccountNumber " " ( id, countryCode, accountNumber, bankCode, name )" " VALUES( :id, :countryCode, :accountNumber, :bankCode, :name ) " ); return writeQuery(); case SQLAction::Modify: query.prepare("UPDATE kmmNationalAccountNumber SET countryCode = :countryCode, accountNumber = :accountNumber, bankCode = :bankCode, name = :name WHERE id = :id;"); return writeQuery(); case SQLAction::Remove: query.prepare("DELETE FROM kmmNationalAccountNumber WHERE id = ?;"); query.bindValue(0, obj.idString()); if (!query.exec()) { qWarning("Error while deleting national account number '%s': %s", qPrintable(obj.idString()), qPrintable(query.lastError().text())); return false; } return true; } return false; } bool actOnSepaOnlineTransferObjectInSQL(SQLAction action, const onlineTask &obj, const QString& id) { Q_Q(MyMoneyStorageSql); QSqlQuery query(*q); const auto& task = dynamic_cast(obj); auto bindValuesToQuery = [&]() { query.bindValue(":id", id); query.bindValue(":originAccount", task.responsibleAccount()); query.bindValue(":value", task.value().toString()); query.bindValue(":purpose", task.purpose()); query.bindValue(":endToEndReference", (task.endToEndReference().isEmpty()) ? QVariant() : QVariant::fromValue(task.endToEndReference())); query.bindValue(":beneficiaryName", task.beneficiaryTyped().ownerName()); query.bindValue(":beneficiaryIban", task.beneficiaryTyped().electronicIban()); query.bindValue(":beneficiaryBic", (task.beneficiaryTyped().storedBic().isEmpty()) ? QVariant() : QVariant::fromValue(task.beneficiaryTyped().storedBic())); query.bindValue(":textKey", task.textKey()); query.bindValue(":subTextKey", task.subTextKey()); }; switch(action) { case SQLAction::Save: query.prepare("INSERT INTO kmmSepaOrders (" " id, originAccount, value, purpose, endToEndReference, beneficiaryName, beneficiaryIban, " " beneficiaryBic, textKey, subTextKey) " " VALUES( :id, :originAccount, :value, :purpose, :endToEndReference, :beneficiaryName, :beneficiaryIban, " " :beneficiaryBic, :textKey, :subTextKey ) " ); bindValuesToQuery(); if (!query.exec()) { qWarning("Error while saving sepa order '%s': %s", qPrintable(id), qPrintable(query.lastError().text())); return false; } return true; case SQLAction::Modify: query.prepare( "UPDATE kmmSepaOrders SET" " originAccount = :originAccount," " value = :value," " purpose = :purpose," " endToEndReference = :endToEndReference," " beneficiaryName = :beneficiaryName," " beneficiaryIban = :beneficiaryIban," " beneficiaryBic = :beneficiaryBic," " textKey = :textKey," " subTextKey = :subTextKey " " WHERE id = :id"); bindValuesToQuery(); if (!query.exec()) { qWarning("Could not modify sepaOnlineTransfer '%s': %s", qPrintable(id), qPrintable(query.lastError().text())); return false; } return true; case SQLAction::Remove: query.prepare("DELETE FROM kmmSepaOrders WHERE id = ?"); query.bindValue(0, id); return query.exec(); } return false; } void actOnPayeeIdentifierObjectInSQL(SQLAction action, const payeeIdentifier& obj) { setupStoragePlugin(obj->payeeIdentifierId()); auto isSuccessfull = false; if (obj->payeeIdentifierId() == payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()) isSuccessfull = actOnIBANBICObjectInSQL(action, obj); else if (obj->payeeIdentifierId() == payeeIdentifiers::nationalAccount::staticPayeeIdentifierIid()) isSuccessfull = actOnNationalAccountObjectInSQL(action, obj); if (!isSuccessfull) { switch (action) { case SQLAction::Save: throw MYMONEYEXCEPTION(QString::fromLatin1("Could not save object with id '%1' in database (plugin failed).").arg(obj.idString())); case SQLAction::Modify: throw MYMONEYEXCEPTION(QString::fromLatin1("Could not modify object with id '%1' in database (plugin failed).").arg(obj.idString())); case SQLAction::Remove: throw MYMONEYEXCEPTION(QString::fromLatin1("Could not remove object with id '%1' from database (plugin failed).").arg(obj.idString())); } } } void actOnOnlineJobInSQL(SQLAction action, const onlineTask& obj, const QString& id) { setupStoragePlugin(obj.taskName()); auto isSuccessfull = false; if (obj.taskName() == sepaOnlineTransferImpl::name()) isSuccessfull = actOnSepaOnlineTransferObjectInSQL(action, obj, id); if (!isSuccessfull) { switch (action) { case SQLAction::Save: throw MYMONEYEXCEPTION(QString::fromLatin1("Could not save object with id '%1' in database (plugin failed).").arg(id)); case SQLAction::Modify: throw MYMONEYEXCEPTION(QString::fromLatin1("Could not modify object with id '%1' in database (plugin failed).").arg(id)); case SQLAction::Remove: throw MYMONEYEXCEPTION(QString::fromLatin1("Could not remove object with id '%1' from database (plugin failed).").arg(id)); } } } payeeIdentifierData* createIBANBICObject(QSqlDatabase db, const QString& identId) const { QSqlQuery query(db); query.prepare("SELECT iban, bic, name FROM kmmIbanBic WHERE id = ?;"); query.bindValue(0, identId); if (!query.exec() || !query.next()) { qWarning("Could load iban bic identifier from database"); return nullptr; } payeeIdentifiers::ibanBic *const ident = new payeeIdentifiers::ibanBic; ident->setIban(query.value(0).toString()); ident->setBic(query.value(1).toString()); ident->setOwnerName(query.value(2).toString()); return ident; } payeeIdentifierData* createNationalAccountObject(QSqlDatabase db, const QString& identId) const { QSqlQuery query(db); query.prepare("SELECT countryCode, accountNumber, bankCode, name FROM kmmNationalAccountNumber WHERE id = ?;"); query.bindValue(0, identId); if (!query.exec() || !query.next()) { qWarning("Could load national account number from database"); return nullptr; } payeeIdentifiers::nationalAccount *const ident = new payeeIdentifiers::nationalAccount; ident->setCountry(query.value(0).toString()); ident->setAccountNumber(query.value(1).toString()); ident->setBankCode(query.value(2).toString()); ident->setOwnerName(query.value(3).toString()); return ident; } payeeIdentifier createPayeeIdentifierObject(QSqlDatabase db, const QString& identifierType, const QString& identifierId) const { payeeIdentifierData* identData = nullptr; if (identifierType == payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()) identData = createIBANBICObject(db, identifierId); else if (identifierType == payeeIdentifiers::nationalAccount::staticPayeeIdentifierIid()) identData = createNationalAccountObject(db, identifierId); return payeeIdentifier(identifierId, identData); } onlineTask* createSepaOnlineTransferObject(QSqlDatabase connection, const QString& onlineJobId) const { Q_ASSERT(!onlineJobId.isEmpty()); Q_ASSERT(connection.isOpen()); QSqlQuery query = QSqlQuery( "SELECT originAccount, value, purpose, endToEndReference, beneficiaryName, beneficiaryIban, " " beneficiaryBic, textKey, subTextKey FROM kmmSepaOrders WHERE id = ?", connection ); query.bindValue(0, onlineJobId); if (query.exec() && query.next()) { sepaOnlineTransferImpl* task = new sepaOnlineTransferImpl(); task->setOriginAccount(query.value(0).toString()); task->setValue(MyMoneyMoney(query.value(1).toString())); task->setPurpose(query.value(2).toString()); task->setEndToEndReference(query.value(3).toString()); task->setTextKey(query.value(7).toUInt()); task->setSubTextKey(query.value(8).toUInt()); payeeIdentifiers::ibanBic beneficiary; beneficiary.setOwnerName(query.value(4).toString()); beneficiary.setIban(query.value(5).toString()); beneficiary.setBic(query.value(6).toString()); task->setBeneficiary(beneficiary); return task; } return nullptr; } onlineTask* createOnlineTaskObject(const QString& iid, const QString& onlineTaskId, QSqlDatabase connection) const { onlineTask* taskOnline = nullptr; if (iid == sepaOnlineTransferImpl::name()) { // @todo This is probably memory leak but for now it works alike to original code onlineJobAdministration::instance()->registerOnlineTask(new sepaOnlineTransferImpl); taskOnline = createSepaOnlineTransferObject(connection, onlineTaskId); } if (!taskOnline) qWarning("In the file is a onlineTask for which I could not find the plugin ('%s')", qPrintable(iid)); return taskOnline; } void alert(QString s) const // FIXME: remove... { qDebug() << s; } void signalProgress(qint64 current, qint64 total, const QString& msg) const { if (m_progressCallback != 0) (*m_progressCallback)(current, total, msg); } void signalProgress(qint64 current, qint64 total) const { signalProgress(current, total, QString()); } template ulong getNextId(const QString& table, const QString& id, const int prefixLength) const { Q_CHECK_PTR(cache); if (this->*cache == 0) { MyMoneyStorageSqlPrivate* nonConstThis = const_cast(this); nonConstThis->*cache = 1 + nonConstThis->highestNumberFromIdString(table, id, prefixLength); } Q_ASSERT(this->*cache > 0); // everything else is never a valid id return this->*cache; } //void startCommitUnit (const QString& callingFunction); //void endCommitUnit (const QString& callingFunction); //void cancelCommitUnit (const QString& callingFunction); MyMoneyStorageSql *q_ptr; // data QExplicitlySharedDataPointer m_driver; MyMoneyDbDef m_db; uint m_dbVersion; MyMoneyStorageMgr *m_storage; // input options bool m_loadAll; // preload all data bool m_override; // override open if already in use // error message QString m_error; // record counts ulong m_institutions; ulong m_accounts; ulong m_payees; ulong m_tags; ulong m_transactions; ulong m_splits; ulong m_securities; ulong m_prices; ulong m_currencies; ulong m_schedules; ulong m_reports; ulong m_kvps; ulong m_budgets; ulong m_onlineJobs; ulong m_payeeIdentifier; // Cache for next id to use // value 0 means data is not available and has to be loaded from the database ulong m_hiIdInstitutions; ulong m_hiIdPayees; ulong m_hiIdTags; ulong m_hiIdAccounts; ulong m_hiIdTransactions; ulong m_hiIdSchedules; ulong m_hiIdSecurities; ulong m_hiIdReports; ulong m_hiIdBudgets; ulong m_hiIdOnlineJobs; ulong m_hiIdPayeeIdentifier; ulong m_hiIdCostCenter; // encrypt option - usage TBD QString m_encryptData; /** * This variable is used to suppress status messages except during * initial data load and final write */ bool m_displayStatus; /** The following keeps track of commitment units (known as transactions in SQL * though it would be confusing to use that term within KMM). It is implemented * as a stack for debug purposes. Long term, probably a count would suffice */ QStack m_commitUnitStack; /** * This member variable is used to preload transactions for preferred accounts */ MyMoneyTransactionFilter m_preferred; /** * This member variable is used because reading prices from a file uses the 'add...' function rather than a * 'load...' function which other objects use. Having this variable allows us to avoid needing to check the * database to see if this really is a new or modified price */ bool m_readingPrices; /** * This member variable holds a map of transaction counts per account, indexed by * the account id. It is used * to avoid having to scan all transactions whenever a count is needed. It should * probably be moved into the MyMoneyAccount object; maybe we will do that once * the database code has been properly checked out */ QHash m_transactionCountMap; /** * These member variables hold the user name and date/time of logon */ QString m_logonUser; QDateTime m_logonAt; QDate m_txPostDate; // FIXME: remove when Tom puts date into split object bool m_newDatabase; /** * This member keeps the current precision to be used fro prices. * @sa setPrecision() */ static int m_precision; /** * This member keeps the current start date used for transaction retrieval. * @sa setStartDate() */ static QDate m_startDate; /** * */ QSet m_loadedStoragePlugins; void (*m_progressCallback)(int, int, const QString&); }; #endif diff --git a/kmymoney/views/kpayeeidentifierview.cpp b/kmymoney/views/kpayeeidentifierview.cpp index a022f46d3..e628f4aba 100644 --- a/kmymoney/views/kpayeeidentifierview.cpp +++ b/kmymoney/views/kpayeeidentifierview.cpp @@ -1,132 +1,138 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 "kpayeeidentifierview.h" #include "ui_kpayeeidentifierview.h" #include #include #include #include -#include "payeeidentifier/payeeidentifierloader.h" #include "payeeidentifiercontainermodel.h" #include "payeeidentifierselectiondelegate.h" +#include "widgets/payeeidentifier/ibanbic/ibanbicitemdelegate.h" +#include "widgets/payeeidentifier/nationalaccount/nationalaccountdelegate.h" payeeIdentifierDelegate::payeeIdentifierDelegate(QObject* parent) : StyledItemDelegateForwarder(parent) { } QAbstractItemDelegate* payeeIdentifierDelegate::getItemDelegate(const QModelIndex& index) const { static QPointer defaultDelegate; const QString type = (index.isValid()) ? index.model()->data(index, payeeIdentifierContainerModel::payeeIdentifierType).toString() : QString(); if (type.isEmpty()) { QAbstractItemDelegate* delegate = new payeeIdentifierSelectionDelegate(this->parent()); connectSignals(delegate); return delegate; } + QAbstractItemDelegate* delegate = nullptr; // Use this->parent() as parent because "this" is const - QAbstractItemDelegate* delegate = payeeIdentifierLoader::instance()->createItemDelegate(type, this->parent()); + if (type == payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()) { + delegate = new ibanBicItemDelegate(this->parent()); + } else if (type == payeeIdentifiers::nationalAccount::staticPayeeIdentifierIid()) { + delegate = new nationalAccountDelegate(this->parent()); + } if (delegate == 0) { if (defaultDelegate == 0) defaultDelegate = new QStyledItemDelegate(this->parent()); delegate = defaultDelegate; } connectSignals(delegate, Qt::UniqueConnection); Q_CHECK_PTR(delegate); return delegate; } KPayeeIdentifierView::KPayeeIdentifierView(QWidget* parent) : QWidget(parent), ui(new Ui::KPayeeIdentifierView) { ui->setupUi(this); ui->view->setItemDelegate(new payeeIdentifierDelegate(ui->view)); } KPayeeIdentifierView::~KPayeeIdentifierView() { delete ui; } void KPayeeIdentifierView::setSource(MyMoneyPayeeIdentifierContainer container) { if (ui->view->model() == 0) { // this model must be closed after each KMyMoneyApp::fileLoaded signal // to limit includes, it is connected outside of this class auto model = new payeeIdentifierContainerModel(ui->view); connect(model, &payeeIdentifierContainerModel::dataChanged, this, &KPayeeIdentifierView::dataChanged); connect(model, &payeeIdentifierContainerModel::rowsRemoved, this, &KPayeeIdentifierView::dataChanged); ui->view->setModel(model); } Q_CHECK_PTR(qobject_cast(ui->view->model())); // this should never fail but may help during debugging static_cast(ui->view->model())->setSource(container); // Open persistent editor for last row ui->view->openPersistentEditor(ui->view->model()->index(ui->view->model()->rowCount(QModelIndex()) - 1, 0)); } void KPayeeIdentifierView::closeSource() { auto model = ui->view->model(); if (model) static_cast(model)->closeSource(); } QList< payeeIdentifier > KPayeeIdentifierView::identifiers() const { const QAbstractItemModel* model = ui->view->model(); if (model != 0) return static_cast(model)->identifiers(); return QList< payeeIdentifier >(); } /** * @brief Helper to sort QModelIndexList in decreasing order. */ inline bool QModelIndexRowComparison(const QModelIndex& first, const QModelIndex& second) { return (first.row() > second.row()); } /** * @bug If the last row is removed the type selection editor (which is always behind that last row) closes. * Maybe that is a Qt bug?! */ void KPayeeIdentifierView::removeSelected() { QModelIndexList selectedRows = ui->view->selectionModel()->selectedRows(); // To keep the items valid during remove the data must be removed from highest row // to the lowes. Unfortunately QList has no reverse iterator. std::sort(selectedRows.begin(), selectedRows.end(), QModelIndexRowComparison); QAbstractItemModel* model = ui->view->model(); Q_CHECK_PTR(model); QModelIndexList::const_iterator end = selectedRows.constEnd(); for (QModelIndexList::const_iterator iter = selectedRows.constBegin(); iter != end; ++iter) model->removeRow(iter->row(), iter->parent()); } diff --git a/kmymoney/views/payeeidentifierselectiondelegate.cpp b/kmymoney/views/payeeidentifierselectiondelegate.cpp index ac0ad78d0..33004407f 100644 --- a/kmymoney/views/payeeidentifierselectiondelegate.cpp +++ b/kmymoney/views/payeeidentifierselectiondelegate.cpp @@ -1,95 +1,92 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 "payeeidentifierselectiondelegate.h" #include -#include "payeeidentifier/payeeidentifierloader.h" #include "models/payeeidentifiercontainermodel.h" #include "payeeidentifier/ibanbic/ibanbic.h" #include "payeeidentifier/nationalaccount/nationalaccount.h" payeeIdentifierTypeSelectionWidget::payeeIdentifierTypeSelectionWidget(QWidget* parent) : QComboBox(parent) { connect(this, SIGNAL(activated(int)), this, SLOT(itemSelected(int))); } void payeeIdentifierTypeSelectionWidget::itemSelected(int index) { if (index != 0) { emit commitData(this); setCurrentIndex(0); } } payeeIdentifierSelectionDelegate::payeeIdentifierSelectionDelegate(QObject* parent) : QStyledItemDelegate(parent) { } QWidget* payeeIdentifierSelectionDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(option); Q_UNUSED(index); payeeIdentifierTypeSelectionWidget* comboBox = new payeeIdentifierTypeSelectionWidget(parent); comboBox->setFrame(false); connect(comboBox, SIGNAL(commitData(QWidget*)), this, SIGNAL(commitData(QWidget*))); comboBox->addItem(i18n("Please select the account number type")); - payeeIdentifierLoader *const loader = payeeIdentifierLoader::instance(); - - for (const auto &pidid : loader->availableDelegates()) { - QString delegateName; - if (pidid == payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()) - delegateName = i18n("IBAN and BIC"); - else if (pidid == payeeIdentifiers::nationalAccount::staticPayeeIdentifierIid()) - delegateName = i18n("National Account Number"); - comboBox->addItem(delegateName, QVariant(pidid)); - } + + const QMap availableDelegates { + {payeeIdentifiers::ibanBic::staticPayeeIdentifierIid(), i18n("IBAN and BIC")}, + {payeeIdentifiers::nationalAccount::staticPayeeIdentifierIid(), i18n("National Account Number")} + }; + + for (auto delegate = availableDelegates.cbegin(); delegate != availableDelegates.cend(); ++delegate ) + comboBox->addItem(delegate.value(), delegate.key()); return comboBox; } void payeeIdentifierSelectionDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { QComboBox *const comboBox = qobject_cast(editor); const QString selectedPidType = comboBox->model()->data(comboBox->model()->index(comboBox->currentIndex(), 0), Qt::UserRole).toString(); payeeIdentifier orig = model->data(index, payeeIdentifierContainerModel::payeeIdentifier).value(); payeeIdentifier ident; if (selectedPidType == payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()) ident = payeeIdentifier(orig.id(), new payeeIdentifiers::ibanBic()); else if (selectedPidType == payeeIdentifiers::nationalAccount::staticPayeeIdentifierIid()) ident = payeeIdentifier(orig.id(), new payeeIdentifiers::nationalAccount()); model->setData(index, QVariant::fromValue(ident), payeeIdentifierContainerModel::payeeIdentifier); } void payeeIdentifierSelectionDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const { editor->setGeometry(option.rect); } QSize payeeIdentifierSelectionDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { return QStyledItemDelegate::sizeHint(option, index); } diff --git a/kmymoney/widgets/CMakeLists.txt b/kmymoney/widgets/CMakeLists.txt index fcb2cbefc..58aa17d7e 100644 --- a/kmymoney/widgets/CMakeLists.txt +++ b/kmymoney/widgets/CMakeLists.txt @@ -1,225 +1,261 @@ ########### create links ############### set(kmymoney_STAT_HEADERS kaccounttemplateselector.h kguiutils.h kmymoneyaccountcombo.h kmymoneyaccountcompletion.h kmymoneyaccountselector.h kmymoneycategory.h kmymoneycombo.h kmymoneymvccombo.h kmymoneycompletion.h kmymoneycurrencyselector.h kmymoneydateinput.h kmymoneyedit.h kmymoneylineedit.h kmymoneyselector.h kmymoneytitlelabel.h register.h registeritem.h groupmarker.h fancydategroupmarker.h scheduledtransaction.h selectedtransaction.h selectedtransactions.h stdtransactiondownloaded.h stdtransactionmatched.h transactioneditorcontainer.h transactionform.h transaction.h investtransaction.h stdtransaction.h transactionsortoption.h kmymoneyvalidationfeedback.h onlinejobmessagesview.h kmymoneydateedit.h amountedit.h amountvalidator.h creditdebithelper.h ) ########### Shared widget library ########### set(kmm_widgets_sources kmymoneydateinput.cpp kmymoneyvalidationfeedback.cpp styleditemdelegateforwarder.cpp kmymoneyedit.cpp kmymoneylineedit.cpp kmymoneytextedit.cpp kmymoneytextedithighlighter.cpp kmymoneymvccombo.cpp kmymoneygeneralcombo.cpp kmymoneyactivitycombo.cpp kmymoneycashflowcombo.cpp kmymoneyfrequencycombo.cpp kmymoneyoccurrencecombo.cpp kmymoneyoccurrenceperiodcombo.cpp kmymoneypayeecombo.cpp kmymoneyperiodcombo.cpp kmymoneyreconcilecombo.cpp kmymoneytagcombo.cpp kmymoneyaccountselector.cpp ktagcontainer.cpp ktaglabel.cpp kmymoneyselector.cpp kmymoneycalculator.cpp ktreewidgetfilterlinewidget.cpp kguiutils.cpp onlinejobmessagesview.cpp kmymoneydateedit.cpp kmymoneymoneyvalidator.cpp amountedit.cpp amountvalidator.cpp creditdebithelper.cpp daterangedlg.cpp ktransactionfilter.cpp kmymoneyaccounttreeview.cpp accountsviewproxymodel.cpp budgetviewproxymodel.cpp kmymoneyviewbase.cpp kmymoneyaccountsviewbase.cpp ) +set(nationalAccountWidget_SOURCES + ./payeeidentifier/nationalaccount/nationalaccountedit.cpp + ./payeeidentifier/nationalaccount/nationalaccountdelegate.cpp +) + +set(nationalAccountWidget_HEADERS + ./payeeidentifier/nationalaccount/nationalaccountdelegate.h + ./payeeidentifier/nationalaccount/nationalaccountedit.h +) + +set(IBANBICWidget_SOURCES + ./payeeidentifier/ibanbic/kibanlineedit.cpp + ./payeeidentifier/ibanbic/kbicedit.cpp + ./payeeidentifier/ibanbic/ibanvalidator.cpp + ./payeeidentifier/ibanbic/bicvalidator.cpp + ./payeeidentifier/ibanbic/ibanbicitemdelegate.cpp + ./payeeidentifier/ibanbic/ibanbicitemedit.cpp +) + +set(IBANBICWidget_HEADERS + ./payeeidentifier/ibanbic/kibanlineedit.h + ./payeeidentifier/ibanbic/kbicedit.h + ./payeeidentifier/ibanbic/ibanvalidator.h + ./payeeidentifier/ibanbic/bicvalidator.h + ./payeeidentifier/ibanbic/ibanbicitemdelegate.h +) + +list(APPEND kmm_widgets_sources ${nationalAccountWidget_SOURCES}) +list(APPEND kmymoney_STAT_HEADERS ${nationalAccountWidget_HEADERS}) + +list(APPEND kmm_widgets_sources ${IBANBICWidget_SOURCES}) +list(APPEND kmymoney_STAT_HEADERS ${IBANBICWidget_HEADERS}) + ki18n_wrap_ui(kmm_widgets_sources kmymoneyvalidationfeedback.ui onlinejobmessagesview.ui daterangedlg.ui ktransactionfilter.ui + ./payeeidentifier/nationalaccount/nationalaccountedit.ui + ./payeeidentifier/ibanbic/ibanbicitemedit.ui ) add_library(kmm_widgets SHARED ${kmm_widgets_sources}) target_link_libraries(kmm_widgets PUBLIC kmm_settings KF5::TextWidgets KF5::KIOWidgets KF5::Completion KF5::Notifications KF5::ItemViews KF5::I18n Qt5::Gui Qt5::Core Alkimia::alkimia kmm_mymoney kmm_models + kmm_plugin ) set_target_properties(kmm_widgets PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} COMPILE_FLAGS "-DKMM_BUILD_WIDGETS_LIB" ) generate_export_header(kmm_widgets) install(TARGETS kmm_widgets ${INSTALL_TARGETS_DEFAULT_ARGS} ) ########### Basic Widget Library (kmymoney_base) STATIC ################# # Common sources for libkmymoney.so and libwidgets.a that do not # contain the KMM_DESIGNER flag set(_uncritial_common_sources kmymoneyaccountcombo.cpp kmymoneycombo.cpp kmymoneycompletion.cpp kmymoneytitlelabel.cpp kmymoneydateedit.cpp kpricetreeitem.cpp registeritem.cpp registerfilter.cpp scheduledtransaction.cpp selectedtransaction.cpp selectedtransactions.cpp stdtransactiondownloaded.cpp stdtransactionmatched.cpp transactionform.cpp tabbar.cpp transactionformitemdelegate.cpp transactionsortoption.cpp ) # sources that contain the KMM_DESIGNER flag set (_critial_common_sources kaccounttemplateselector.cpp kmymoneycurrencyselector.cpp kmymoneyaccountcompletion.cpp kmymoneycategory.cpp groupmarker.cpp groupmarkers.cpp fancydategroupmarker.cpp fancydategroupmarkers.cpp register.cpp itemptrvector.cpp qwidgetcontainer.cpp registeritemdelegate.cpp transaction.cpp stdtransaction.cpp investtransaction.cpp transactioneditorcontainer.cpp ) set (kmymoney_base_UI transactionsortoption.ui kaccounttemplateselector.ui ) ki18n_wrap_ui(kmymoney_base_ui_srcs ${kmymoney_base_UI}) set(_uncritial_common_sources ${_uncritial_common_sources} ${kmymoney_base_ui_srcs}) # in order to use add_dependencies, we need to add this custom target # for all generated header files. # (see http://www.vtk.org/Wiki/CMake_FAQ#How_can_I_add_a_dependency_to_a_source_file_which_is_generated_in_a_subdirectory.3F ) add_custom_target(generate_base_ui_srcs DEPENDS ${kmymoney_base_ui_srcs}) # We can compile the uncritical sources without KMM_DESIGNER flags add_library(kmymoney_base STATIC ${_uncritial_common_sources}) # TODO: fix dependencies target_link_libraries(kmymoney_base KF5::XmlGui KF5::TextWidgets KF5::IconThemes KF5::I18n KF5::ConfigWidgets KF5::ConfigCore KF5::Completion KF5::Service Qt5::Gui Qt5::Widgets Qt5::Xml kmm_settings Alkimia::alkimia) add_dependencies(kmymoney_base kmm_settings) ########### QtDesigner Widget Library (kmymoneywidgets) ################# # we never link against this library, # but it is needed for uic and QtDesigner if( USE_QT_DESIGNER ) set(kmymoneywidgets_PART_SRCS ${CMAKE_CURRENT_BINARY_DIR}/kmymoneywidgets.cpp) kde4_add_widget_files(kmymoneywidgets_PART_SRCS kmymoney.widgets) set(kmymoneywidgets_PART_SRCS ${_critial_common_sources} ${kmymoneywidgets_PART_SRCS}) add_library(kmymoneywidgets MODULE ${kmymoneywidgets_PART_SRCS}) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) # The option -DKMM_DESIGNER will leave away any code that breaks uic. set_target_properties(kmymoneywidgets PROPERTIES COMPILE_FLAGS "-DKMM_DESIGNER") # The qt-designer widget library shouldn't need to link against the # dialogs and converter libraries. If a widget references something # from one of these libraries it is most likely due to code that needs # to be excluded with a KMM_DESIGNER ifndef. target_link_libraries(kmymoneywidgets kmymoney_base kmm_mymoney kmymoney_common kmm_settings models) install(TARGETS kmymoneywidgets DESTINATION ${QT_PLUGINS_DIR}/designer ) endif( USE_QT_DESIGNER ) ########### Widget Library (widgets) STATIC ################# set(libwidgets_a_SOURCES ${_critial_common_sources} kmymoneybriefschedule.cpp registersearchline.cpp transactioneditorcontainer.cpp ) set(libwidgets_a_UI kmymoneybriefschedule.ui ) # using uic on the above UI files DEPENDS on libkmymoney.so. If uic # does not find libkmymoney.so, gcc will fail compiling # kmymoneyreportconfigtab2decl.cpp and throw errors like "invalid use # of undefined type `struct KMyMoneyGeneralCombo'" ki18n_wrap_ui(widgets_ui_srcs ${libwidgets_a_UI}) add_custom_target(generate_widgets_ui_srcs DEPENDS ${widgets_ui_srcs}) add_library(widgets STATIC ${libwidgets_a_SOURCES} ${widgets_ui_srcs} ) target_link_libraries(widgets KF5::XmlGui kmymoney_base) add_dependencies(widgets kmm_settings) ########### install files ############### install(FILES ${kmymoney_STAT_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kmm_widgets_export.h DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel) diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/bicvalidator.cpp b/kmymoney/widgets/payeeidentifier/ibanbic/bicvalidator.cpp similarity index 86% rename from kmymoney/payeeidentifier/ibanandbic/widgets/bicvalidator.cpp rename to kmymoney/widgets/payeeidentifier/ibanbic/bicvalidator.cpp index f50c9e6db..d0641e388 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/bicvalidator.cpp +++ b/kmymoney/widgets/payeeidentifier/ibanbic/bicvalidator.cpp @@ -1,66 +1,65 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian David + * Copyright 2013-2015 Christian Dávid * * 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. + * 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 "bicvalidator.h" #include #include "payeeidentifier/ibanbic/ibanbic.h" #include "widgetenums.h" bicValidator::bicValidator(QObject* parent) : QValidator(parent) { } QValidator::State bicValidator::validate(QString &string, int&) const { for (int i = 0; i < qMin(string.length(), 6); ++i) { if (!string.at(i).isLetter()) return Invalid; if (string.at(i).isLower()) string[i] = string.at(i).toUpper(); } for (int i = 6; i < string.length(); ++i) { if (!string.at(i).isLetterOrNumber()) return Invalid; if (string.at(i).isLower()) string[i] = string.at(i).toUpper(); } if (string.length() > 11) return Invalid; else if (string.length() == 8 || string.length() == 11) { return Acceptable; } return Intermediate; } QPair< eWidgets::ValidationFeedback::MessageType, QString > bicValidator::validateWithMessage(const QString& string) { // Do not show an error message if no BIC is given. if (string.length() != 8 && string.length() != 11) return QPair< eWidgets::ValidationFeedback::MessageType, QString >(eWidgets::ValidationFeedback::MessageType::Error, i18n("A valid BIC is 8 or 11 characters long.")); if (payeeIdentifiers::ibanBic::isBicAllocated(string) == payeeIdentifiers::ibanBic::bicNotAllocated) return QPair< eWidgets::ValidationFeedback::MessageType, QString >(eWidgets::ValidationFeedback::MessageType::Error, i18n("The given BIC is not assigned to any credit institute.")); return QPair< eWidgets::ValidationFeedback::MessageType, QString >(eWidgets::ValidationFeedback::MessageType::None, QString()); } diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/bicvalidator.h b/kmymoney/widgets/payeeidentifier/ibanbic/bicvalidator.h similarity index 67% rename from kmymoney/payeeidentifier/ibanandbic/widgets/bicvalidator.h rename to kmymoney/widgets/payeeidentifier/ibanbic/bicvalidator.h index cdeebc297..dcb2c031f 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/bicvalidator.h +++ b/kmymoney/widgets/payeeidentifier/ibanbic/bicvalidator.h @@ -1,39 +1,38 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian David + * Copyright 2013-2015 Christian Dávid * * 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. + * 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 BICVALIDATOR_H #define BICVALIDATOR_H #include -#include "payeeidentifier_iban_bic_widgets_export.h" +#include "kmm_widgets_export.h" #include "kmymoneyvalidationfeedback.h" namespace eWidgets { namespace ValidationFeedback { enum class MessageType; } } -class PAYEEIDENTIFIER_IBAN_BIC_WIDGETS_EXPORT bicValidator : public QValidator +class KMM_WIDGETS_EXPORT bicValidator : public QValidator { Q_OBJECT public: explicit bicValidator(QObject* parent = 0); QValidator::State validate(QString& , int&) const final override; static QPair validateWithMessage(const QString&); }; #endif // BICVALIDATOR_H diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemdelegate.cpp b/kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemdelegate.cpp similarity index 95% rename from kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemdelegate.cpp rename to kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemdelegate.cpp index f796e6801..076596cf1 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemdelegate.cpp +++ b/kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemdelegate.cpp @@ -1,159 +1,158 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian Dávid + * Copyright 2014-2016 Christian Dávid * * 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. + * 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 "ibanbicitemdelegate.h" #include #include #include #include #include "models/payeeidentifiercontainermodel.h" #include "ibanbicitemedit.h" ibanBicItemDelegate::ibanBicItemDelegate(QObject* parent, const QVariantList&) : QStyledItemDelegate(parent) { } /** @todo elide texts */ void ibanBicItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); // Background QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; const QRect textArea = QRect(opt.rect.x() + margin, opt.rect.y() + margin, opt.rect.width() - 2 * margin, opt.rect.height() - 2 * margin); // Do not paint text if the edit widget is shown const QAbstractItemView *view = qobject_cast(opt.widget); if (view && view->indexWidget(index)) return; // Get data payeeIdentifierTyped ibanBic = ibanBicByIndex(index); // Paint Bic painter->save(); const QFont smallFont = painter->font(); const QFontMetrics metrics(opt.font); const QFontMetrics smallMetrics(smallFont); const QRect bicRect = style->alignedRect((opt.direction == Qt::RightToLeft) ? Qt::LeftToRight : Qt::RightToLeft, Qt::AlignTop, QSize(textArea.width(), smallMetrics.lineSpacing()), QRect(textArea.left(), metrics.lineSpacing() + textArea.top(), textArea.width(), smallMetrics.lineSpacing()) ); painter->setFont(smallFont); style->drawItemText(painter, bicRect, Qt::AlignBottom | Qt::AlignRight, QApplication::palette(), true, ibanBic->storedBic(), opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text); painter->restore(); // Paint Bank name painter->save(); const QRect nameRect = style->alignedRect(opt.direction, Qt::AlignTop, QSize(textArea.width(), smallMetrics.lineSpacing()), QRect(textArea.left(), metrics.lineSpacing() + textArea.top(), textArea.width(), smallMetrics.lineSpacing()) ); style->drawItemText(painter, nameRect, Qt::AlignBottom, QApplication::palette(), true, ibanBic->institutionName(), opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text); painter->restore(); // Paint IBAN painter->save(); QFont normal = painter->font(); normal.setBold(true); painter->setFont(normal); const QRect ibanRect = style->alignedRect(opt.direction, Qt::AlignTop, QSize(textArea.width(), metrics.lineSpacing()), textArea); const QString bic = index.model()->data(index, Qt::DisplayRole).toString(); style->drawItemText(painter, ibanRect, Qt::AlignTop, QApplication::palette(), true, ibanBic->paperformatIban(), opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text); painter->restore(); // Paint type painter->save(); QRect typeRect = style->alignedRect(opt.direction, Qt::AlignTop | Qt::AlignRight, QSize(textArea.width() / 5, metrics.lineSpacing()), textArea); style->drawItemText(painter, typeRect, Qt::AlignTop | Qt::AlignRight, QApplication::palette(), true, i18n("IBAN & BIC"), opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text); painter->restore(); } QSize ibanBicItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); // Test if current index is edited at the moment const QAbstractItemView *view = qobject_cast(opt.widget); if (view && view->indexWidget(index)) return view->indexWidget(index)->sizeHint(); QFontMetrics metrics(option.font); const QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; // A bic has maximal 11 characters, an IBAN 32 return QSize((32 + 11)*metrics.width(QLatin1Char('X')) + 3*margin, 3*metrics.lineSpacing() + metrics.leading() + 2*margin); } QWidget* ibanBicItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(option); ibanBicItemEdit* edit = new ibanBicItemEdit(parent); connect(edit, SIGNAL(commitData(QWidget*)), this, SIGNAL(commitData(QWidget*))); connect(edit, SIGNAL(closeEditor(QWidget*)), this, SIGNAL(closeEditor(QWidget*))); emit sizeHintChanged(index); return edit; } void ibanBicItemDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const { payeeIdentifierTyped ibanBic = ibanBicByIndex(index); ibanBicItemEdit* ibanEditor = qobject_cast< ibanBicItemEdit* >(editor); Q_CHECK_PTR(ibanEditor); ibanEditor->setIdentifier(ibanBic); } void ibanBicItemDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { Q_CHECK_PTR(editor); Q_CHECK_PTR(model); Q_ASSERT(index.isValid()); ibanBicItemEdit* ibanEditor = qobject_cast< ibanBicItemEdit* >(editor); Q_CHECK_PTR(ibanEditor); model->setData(index, QVariant::fromValue(ibanEditor->identifier()), payeeIdentifierContainerModel::payeeIdentifier); } void ibanBicItemDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index); editor->setGeometry(option.rect); } /** * Internal helper to direcly convert the QVariant into the correct pointer type. */ payeeIdentifierTyped ibanBicItemDelegate::ibanBicByIndex(const QModelIndex& index) const { payeeIdentifierTyped ibanBic{ index.model()->data(index, payeeIdentifierContainerModel::payeeIdentifier).value() }; Q_ASSERT(!ibanBic.isNull()); return ibanBic; } diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemdelegate.h b/kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemdelegate.h similarity index 73% rename from kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemdelegate.h rename to kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemdelegate.h index 3ccaf2557..c600bab2e 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemdelegate.h +++ b/kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemdelegate.h @@ -1,50 +1,49 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian Dávid + * Copyright 2014-2016 Christian Dávid * * 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. + * 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 IBANBICITEMDELEGATE_H #define IBANBICITEMDELEGATE_H -#include "payeeidentifier_iban_bic_widgets_export.h" +#include "kmm_widgets_export.h" #include + #include "payeeidentifier/payeeidentifiertyped.h" #include "payeeidentifier/ibanbic/ibanbic.h" -class PAYEEIDENTIFIER_IBAN_BIC_WIDGETS_EXPORT ibanBicItemDelegate : public QStyledItemDelegate +class KMM_WIDGETS_EXPORT ibanBicItemDelegate : public QStyledItemDelegate { Q_OBJECT - Q_PLUGIN_METADATA(IID "org.kmymoney.payeeIdentifier.ibanbic.delegate" FILE "kmymoney-ibanbic-delegate.json") public: explicit ibanBicItemDelegate(QObject* parent = nullptr, const QVariantList& args = QVariantList()); void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; void setEditorData(QWidget* editor, const QModelIndex& index) const override; void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override; Q_SIGNALS: void sizeHintChanged(const QModelIndex&) const; private: inline payeeIdentifierTyped ibanBicByIndex(const QModelIndex& index) const; }; #endif // IBANBICITEMDELEGATE_H diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.cpp b/kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemedit.cpp similarity index 90% rename from kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.cpp rename to kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemedit.cpp index 3c2f686fe..9f020314c 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.cpp +++ b/kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemedit.cpp @@ -1,116 +1,115 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian Dávid + * Copyright 2014-2015 Christian Dávid * * 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. + * 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 "ibanbicitemedit.h" #include "ui_ibanbicitemedit.h" #include "payeeidentifier/ibanbic/ibanbic.h" #include "payeeidentifier/payeeidentifiertyped.h" struct ibanBicItemEdit::Private { Ui::ibanBicItemEdit* ui; payeeIdentifier m_identifier; }; ibanBicItemEdit::ibanBicItemEdit(QWidget* parent) : QWidget(parent), d(new Private) { d->ui = new Ui::ibanBicItemEdit; d->ui->setupUi(this); setFocusProxy(d->ui->ibanEdit); connect(d->ui->ibanEdit, SIGNAL(textChanged(QString)), this, SLOT(updateIdentifier())); connect(d->ui->bicEdit, SIGNAL(textChanged(QString)), this, SLOT(updateIdentifier())); connect(d->ui->ibanEdit, SIGNAL(textChanged(QString)), this, SIGNAL(ibanChanged(QString))); connect(d->ui->bicEdit, SIGNAL(textChanged(QString)), this, SIGNAL(bicChanged(QString))); connect(d->ui->ibanEdit, SIGNAL(returnPressed()), this, SLOT(editFinished())); connect(d->ui->bicEdit, SIGNAL(returnPressed()), this, SLOT(editFinished())); } void ibanBicItemEdit::editFinished() { emit commitData(this); emit closeEditor(this); } payeeIdentifier ibanBicItemEdit::identifier() const { return d->m_identifier; } QString ibanBicItemEdit::bic() const { return d->ui->bicEdit->text(); } QString ibanBicItemEdit::iban() const { return d->ui->ibanEdit->text(); } void ibanBicItemEdit::setIdentifier(const payeeIdentifier& ident) { try { payeeIdentifierTyped identTyped(ident); d->ui->bicEdit->setText(identTyped->storedBic()); d->ui->ibanEdit->setText(identTyped->paperformatIban()); d->m_identifier = ident; } catch (const payeeIdentifier::empty &) { } catch (const payeeIdentifier::badCast &) { } } void ibanBicItemEdit::setBic(const QString& bic) { d->ui->bicEdit->setText(bic); } void ibanBicItemEdit::setIban(const QString& iban) { d->ui->ibanEdit->setText(payeeIdentifiers::ibanBic::ibanToPaperformat(iban)); } void ibanBicItemEdit::updateIdentifier() { if (d->m_identifier.isNull()) d->m_identifier = payeeIdentifier(d->m_identifier.id(), new payeeIdentifiers::ibanBic); const QString iban = payeeIdentifiers::ibanBic::ibanToElectronic(d->ui->ibanEdit->text()); const QString bic = d->ui->bicEdit->text(); bool changed = false; payeeIdentifierTyped ident(d->m_identifier); if (ident->storedBic() != bic) { ident->setBic(bic); changed = true; } if (ident->electronicIban() != iban) { ident->setElectronicIban(iban); changed = true; } d->m_identifier = ident; if (changed) { emit identifierChanged(d->m_identifier); emit commitData(this); } } diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.h b/kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemedit.h similarity index 83% rename from kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.h rename to kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemedit.h index cd78860b0..419116a1a 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.h +++ b/kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemedit.h @@ -1,67 +1,66 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian Dávid + * Copyright 2014-2015 Christian Dávid * * 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. + * 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 IBANBICITEMEDIT_H #define IBANBICITEMEDIT_H #include #include namespace Ui { class ibanBicItemEdit; } class ibanBicItemEdit : public QWidget { Q_OBJECT Q_PROPERTY(payeeIdentifier identifier READ identifier WRITE setIdentifier NOTIFY identifierChanged STORED true) Q_PROPERTY(QString iban READ iban WRITE setIban NOTIFY ibanChanged STORED false DESIGNABLE true) Q_PROPERTY(QString bic READ bic WRITE setBic NOTIFY bicChanged STORED false DESIGNABLE true) public: explicit ibanBicItemEdit(QWidget* parent = 0); payeeIdentifier identifier() const; QString iban() const; QString bic() const; public Q_SLOTS: void setIdentifier(const payeeIdentifier&); void setIban(const QString&); void setBic(const QString&); Q_SIGNALS: void commitData(QWidget*); void closeEditor(QWidget* editor); void identifierChanged(payeeIdentifier); void ibanChanged(QString); void bicChanged(QString); private Q_SLOTS: void updateIdentifier(); /** @brief emits commitData(this) and closeEditor(this) */ void editFinished(); private: struct Private; Private* d; }; #endif // IBANBICITEMEDIT_H diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.ui b/kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemedit.ui similarity index 90% rename from kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.ui rename to kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemedit.ui index 478504f10..eb5396344 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.ui +++ b/kmymoney/widgets/payeeidentifier/ibanbic/ibanbicitemedit.ui @@ -1,56 +1,56 @@ ibanBicItemEdit 0 0 86 84 IBAN BIC KIbanLineEdit QLineEdit -
kibanlineedit.h
+
widgets/payeeidentifier/ibanbic/kibanlineedit.h
KBicEdit QLineEdit -
kbicedit.h
+
widgets/payeeidentifier/ibanbic/kbicedit.h
KMyMoneyValidationFeedback QWidget
kmymoneyvalidationfeedback.h
1
diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanvalidator.cpp b/kmymoney/widgets/payeeidentifier/ibanbic/ibanvalidator.cpp similarity index 88% rename from kmymoney/payeeidentifier/ibanandbic/widgets/ibanvalidator.cpp rename to kmymoney/widgets/payeeidentifier/ibanbic/ibanvalidator.cpp index 6a56da967..449278800 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanvalidator.cpp +++ b/kmymoney/widgets/payeeidentifier/ibanbic/ibanvalidator.cpp @@ -1,84 +1,83 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian David + * Copyright 2013-2015 Christian Dávid * * 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. + * 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 "ibanvalidator.h" #include "payeeidentifier/ibanbic/ibanbic.h" #include #include "widgetenums.h" ibanValidator::ibanValidator(QObject* parent) : QValidator(parent) { } QValidator::State ibanValidator::validate(QString& string, int&) const { // Check country code and set it uppercase if (string.length() >= 1) { if (!string.at(0).isLetter()) return Invalid; if (string.at(0).isLower()) string[0] = string.at(0).toUpper(); } if (string.length() >= 2) { if (!string.at(1).isLetterOrNumber()) return Invalid; if (string.at(1).isLower()) string[1] = string.at(1).toUpper(); } // Check rest of the iban int characterCount = qMin(string.length(), 2); for (int i = 2; i < string.length(); ++i) { if (string.at(i).isLetterOrNumber()) { ++characterCount; } else if (!string.at(i).isSpace()) { return Invalid; } } if (characterCount > 32) return Invalid; if (characterCount > 5) { return Acceptable; } return Intermediate; } QPair< eWidgets::ValidationFeedback::MessageType, QString > ibanValidator::validateWithMessage(const QString& string) { // string.length() > 32 should not happen because all line edits should have this validator installed if (string.length() < 5) return QPair< eWidgets::ValidationFeedback::MessageType, QString >(eWidgets::ValidationFeedback::MessageType::Error, i18n("This IBAN is too short.")); if (!payeeIdentifiers::ibanBic::validateIbanChecksum(payeeIdentifiers::ibanBic::ibanToElectronic(string))) return QPair< eWidgets::ValidationFeedback::MessageType, QString >(eWidgets::ValidationFeedback::MessageType::Warning, i18n("This IBAN is invalid.")); return QPair< eWidgets::ValidationFeedback::MessageType, QString >(eWidgets::ValidationFeedback::MessageType::None, QString()); } void ibanValidator::fixup(QString& string) const { string = payeeIdentifiers::ibanBic::ibanToPaperformat(string); } diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanvalidator.h b/kmymoney/widgets/payeeidentifier/ibanbic/ibanvalidator.h similarity index 69% rename from kmymoney/payeeidentifier/ibanandbic/widgets/ibanvalidator.h rename to kmymoney/widgets/payeeidentifier/ibanbic/ibanvalidator.h index 48efcbed7..8beeb2952 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanvalidator.h +++ b/kmymoney/widgets/payeeidentifier/ibanbic/ibanvalidator.h @@ -1,43 +1,42 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian David + * Copyright 2013-2015 Christian Dávid * * 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. + * 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 IBANVALIDATOR_H #define IBANVALIDATOR_H -#include "payeeidentifier_iban_bic_widgets_export.h" +#include "kmm_widgets_export.h" #include #include "kmymoneyvalidationfeedback.h" namespace eWidgets { namespace ValidationFeedback { enum class MessageType; } } -class PAYEEIDENTIFIER_IBAN_BIC_WIDGETS_EXPORT ibanValidator : public QValidator +class KMM_WIDGETS_EXPORT ibanValidator : public QValidator { Q_OBJECT public: explicit ibanValidator(QObject* parent = 0); State validate(QString& , int&) const final override; State validate(const QString&) const; void fixup(QString&) const final override; static QPair validateWithMessage(const QString&); }; #endif // IBANVALIDATOR_H diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/kbicedit.cpp b/kmymoney/widgets/payeeidentifier/ibanbic/kbicedit.cpp similarity index 93% rename from kmymoney/payeeidentifier/ibanandbic/widgets/kbicedit.cpp rename to kmymoney/widgets/payeeidentifier/ibanbic/kbicedit.cpp index c22961b58..e1a1e3010 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/kbicedit.cpp +++ b/kmymoney/widgets/payeeidentifier/ibanbic/kbicedit.cpp @@ -1,122 +1,121 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian Dávid + * Copyright 2014-2015 Christian Dávid * * 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. + * 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 "kbicedit.h" #include #include #include #include #include #include #include "kmymoneyplugin.h" #include "bicvalidator.h" #include "kmymoneyvalidationfeedback.h" #include "plugins/ibanbicdata/ibanbicdataenums.h" class bicItemDelegate : public QStyledItemDelegate { public: explicit bicItemDelegate(QObject* parent = 0) : QStyledItemDelegate(parent) {} void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const final override; QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const final override; private: inline QFont getSmallFont(const QStyleOptionViewItem& option) const; }; KBicEdit::KBicEdit(QWidget* parent) : KLineEdit(parent) { QCompleter* completer = new QCompleter(this); if (auto plugin = pPlugins.data.value(QString::fromLatin1("ibanbicdata"), nullptr)) if (auto model = plugin->requestData(QString(), eIBANBIC::DataType::bicModel).value()) completer->setModel(model); m_popupDelegate = new bicItemDelegate(this); completer->popup()->setItemDelegate(m_popupDelegate); setCompleter(completer); bicValidator *const validator = new bicValidator(this); setValidator(validator); } KBicEdit::~KBicEdit() { delete m_popupDelegate; } QFont bicItemDelegate::getSmallFont(const QStyleOptionViewItem& option) const { QFont smallFont = option.font; smallFont.setPointSize(0.9*smallFont.pointSize()); return smallFont; } QSize bicItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); QFontMetrics metrics(option.font); QFontMetrics smallMetrics(getSmallFont(option)); const QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; // A bic has maximal 11 characters. So we guess, we want to display 11 characters. The name of the institution has to adapt to what is given return QSize(metrics.width(QLatin1Char('X')) + 2*margin, metrics.lineSpacing() + smallMetrics.lineSpacing() + smallMetrics.leading() + 2*margin); } /** * @todo enable eliding (use QFontMetrics::elidedText() ) */ void bicItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); // Background QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; const QRect textArea = QRect(opt.rect.x() + margin, opt.rect.y() + margin, opt.rect.width() - 2 * margin, opt.rect.height() - 2 * margin); // Paint name painter->save(); QFont smallFont = getSmallFont(opt); QFontMetrics metrics(opt.font); QFontMetrics smallMetrics(smallFont); QRect nameRect = style->alignedRect(opt.direction, Qt::AlignBottom, QSize(textArea.width(), smallMetrics.lineSpacing()), textArea); painter->setFont(smallFont); style->drawItemText(painter, nameRect, Qt::AlignBottom, QApplication::palette(), true, index.model()->data(index, eIBANBIC::DisplayRole::InstitutionNameRole).toString(), option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Mid); painter->restore(); // Paint BIC painter->save(); QFont normal = painter->font(); normal.setBold(true); painter->setFont(normal); QRect bicRect = style->alignedRect(opt.direction, Qt::AlignTop, QSize(textArea.width(), metrics.lineSpacing()), textArea); const QString bic = index.model()->data(index, Qt::DisplayRole).toString(); style->drawItemText(painter, bicRect, Qt::AlignTop, QApplication::palette(), true, bic, option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text); painter->restore(); } diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/kbicedit.h b/kmymoney/widgets/payeeidentifier/ibanbic/kbicedit.h similarity index 62% rename from kmymoney/payeeidentifier/ibanandbic/widgets/kbicedit.h rename to kmymoney/widgets/payeeidentifier/ibanbic/kbicedit.h index aa2cffb9d..8bf337594 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/kbicedit.h +++ b/kmymoney/widgets/payeeidentifier/ibanbic/kbicedit.h @@ -1,41 +1,40 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian Dávid + * Copyright 2014-2015 Christian Dávid * * 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. + * 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 KBICEDIT_H #define KBICEDIT_H #include #include -#include "payeeidentifier_iban_bic_widgets_export.h" +#include "kmm_widgets_export.h" class QAbstractItemDelegate; -class PAYEEIDENTIFIER_IBAN_BIC_WIDGETS_EXPORT KBicEdit : public KLineEdit +class KMM_WIDGETS_EXPORT KBicEdit : public KLineEdit { Q_OBJECT public: explicit KBicEdit(QWidget* parent = 0); virtual ~KBicEdit(); private: QAbstractItemDelegate* m_popupDelegate; }; #endif // KBICEDIT_H diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/kibanlineedit.cpp b/kmymoney/widgets/payeeidentifier/ibanbic/kibanlineedit.cpp similarity index 72% rename from kmymoney/payeeidentifier/ibanandbic/widgets/kibanlineedit.cpp rename to kmymoney/widgets/payeeidentifier/ibanbic/kibanlineedit.cpp index eb52af829..091f67081 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/kibanlineedit.cpp +++ b/kmymoney/widgets/payeeidentifier/ibanbic/kibanlineedit.cpp @@ -1,34 +1,33 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian Dávid + * Copyright 2013-2015 Christian Dávid * * 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. + * 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 "kibanlineedit.h" #include "ibanvalidator.h" #include "kmymoneyvalidationfeedback.h" KIbanLineEdit::KIbanLineEdit(QWidget* parent) : KLineEdit(parent) { ibanValidator *const validatorPtr = new ibanValidator; setValidator(validatorPtr); } const ibanValidator* KIbanLineEdit::validator() const { return qobject_cast(KLineEdit::validator()); } diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/kibanlineedit.h b/kmymoney/widgets/payeeidentifier/ibanbic/kibanlineedit.h similarity index 60% rename from kmymoney/payeeidentifier/ibanandbic/widgets/kibanlineedit.h rename to kmymoney/widgets/payeeidentifier/ibanbic/kibanlineedit.h index 73523439a..0d14b29a5 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/kibanlineedit.h +++ b/kmymoney/widgets/payeeidentifier/ibanbic/kibanlineedit.h @@ -1,37 +1,36 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian David + * Copyright 2013-2015 Christian Dávid * * 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. + * 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 KIBANLINEEDIT_H #define KIBANLINEEDIT_H -#include "payeeidentifier_iban_bic_widgets_export.h" +#include "kmm_widgets_export.h" #include class ibanValidator; -class PAYEEIDENTIFIER_IBAN_BIC_WIDGETS_EXPORT KIbanLineEdit : public KLineEdit +class KMM_WIDGETS_EXPORT KIbanLineEdit : public KLineEdit { Q_OBJECT public: explicit KIbanLineEdit(QWidget* parent); const ibanValidator* validator() const; }; #endif // KIBANLINEEDIT_H diff --git a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountdelegate.cpp b/kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountdelegate.cpp similarity index 95% rename from kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountdelegate.cpp rename to kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountdelegate.cpp index b51140261..ab5628f92 100644 --- a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountdelegate.cpp +++ b/kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountdelegate.cpp @@ -1,162 +1,161 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian Dávid + * Copyright 2014-2015 Christian Dávid * * 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. + * 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 "nationalaccountdelegate.h" #include #include #include #include #include "models/payeeidentifiercontainermodel.h" #include "nationalaccountedit.h" nationalAccountDelegate::nationalAccountDelegate(QObject* parent, const QVariantList&) : QStyledItemDelegate(parent) { } /** @todo elide texts */ void nationalAccountDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); // Background QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; const QRect textArea = QRect(opt.rect.x() + margin, opt.rect.y() + margin, opt.rect.width() - 2 * margin, opt.rect.height() - 2 * margin); // Do not paint text if the edit widget is shown const QAbstractItemView *view = qobject_cast(opt.widget); if (view && view->indexWidget(index)) return; // Get data payeeIdentifierTyped ident = identByIndex(index); // Paint bank code painter->save(); const QFont smallFont = painter->font(); const QFontMetrics metrics(opt.font); const QFontMetrics smallMetrics(smallFont); const QRect bicRect = style->alignedRect(opt.direction, Qt::AlignTop, QSize(textArea.width(), smallMetrics.lineSpacing()), QRect(textArea.left(), metrics.lineSpacing() + textArea.top(), textArea.width(), smallMetrics.lineSpacing()) ); painter->setFont(smallFont); style->drawItemText(painter, bicRect, Qt::AlignBottom, QApplication::palette(), true, ident->bankCode(), opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text); painter->restore(); // Paint bank name painter->save(); const QRect nameRect = style->alignedRect(opt.direction, Qt::AlignTop, QSize(textArea.width(), smallMetrics.lineSpacing()), QRect(textArea.left(), metrics.lineSpacing() + smallMetrics.lineSpacing() + textArea.top(), textArea.width(), smallMetrics.lineSpacing()) ); style->drawItemText(painter, nameRect, Qt::AlignBottom, QApplication::palette(), true, ident->bankName(), opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text); painter->restore(); // Paint account number painter->save(); QFont normal = painter->font(); normal.setBold(true); painter->setFont(normal); const QRect ibanRect = style->alignedRect(opt.direction, Qt::AlignTop, QSize(textArea.width(), metrics.lineSpacing()), textArea); const QString bic = index.model()->data(index, Qt::DisplayRole).toString(); style->drawItemText(painter, ibanRect, Qt::AlignTop, QApplication::palette(), true, ident->accountNumber(), opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text); painter->restore(); // Paint type painter->save(); QRect typeRect = style->alignedRect(opt.direction, Qt::AlignTop | Qt::AlignRight, QSize(textArea.width() / 5, metrics.lineSpacing()), textArea); style->drawItemText(painter, typeRect, Qt::AlignTop | Qt::AlignRight, QApplication::palette(), true, i18n("National Account"), opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text); painter->restore(); } QSize nationalAccountDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); // QStyle::State_Editing is never set (seems to be a bug in Qt)! This code is here only because it was written already const QAbstractItemView *view = qobject_cast(opt.widget); if (view && view->indexWidget(index)) return view->indexWidget(index)->sizeHint(); QFontMetrics metrics(option.font); const QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; // An iban has maximal 32 characters, so national accounts should be shorter than 28 return QSize((28)*metrics.width(QLatin1Char('X')) + 2*margin, 3*metrics.lineSpacing() + metrics.leading() + 2*margin); } QWidget* nationalAccountDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(option); nationalAccountEdit* edit = new nationalAccountEdit(parent); connect(edit, SIGNAL(commitData(QWidget*)), this, SIGNAL(commitData(QWidget*))); connect(edit, SIGNAL(closeEditor(QWidget*)), this, SIGNAL(closeEditor(QWidget*))); emit sizeHintChanged(index); return edit; } void nationalAccountDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const { nationalAccountEdit* nationalEditor = qobject_cast< nationalAccountEdit* >(editor); Q_CHECK_PTR(nationalEditor); nationalEditor->setIdentifier(identByIndex(index)); } void nationalAccountDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { Q_CHECK_PTR(editor); Q_CHECK_PTR(model); Q_ASSERT(index.isValid()); nationalAccountEdit* nationalEditor = qobject_cast< nationalAccountEdit* >(editor); Q_CHECK_PTR(nationalEditor); payeeIdentifierTyped ident = identByIndex(index); ident->setAccountNumber(nationalEditor->accountNumber()); ident->setBankCode(nationalEditor->institutionCode()); model->setData(index, QVariant::fromValue(ident), payeeIdentifierContainerModel::payeeIdentifier); } void nationalAccountDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index); editor->setGeometry(option.rect); } /** * Internal helper to direcly convert the QVariant into the correct pointer type. */ payeeIdentifierTyped nationalAccountDelegate::identByIndex(const QModelIndex& index) const { payeeIdentifierTyped ident = payeeIdentifierTyped( index.model()->data(index, payeeIdentifierContainerModel::payeeIdentifier).value() ); Q_ASSERT(!ident.isNull()); return ident; } diff --git a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountdelegate.h b/kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountdelegate.h similarity index 81% rename from kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountdelegate.h rename to kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountdelegate.h index 95140832c..8565ef428 100644 --- a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountdelegate.h +++ b/kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountdelegate.h @@ -1,48 +1,49 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian Dávid + * Copyright 2014-2015 Christian Dávid * * 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. + * 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 NATIONALACCOUNTDELEGATE_H #define NATIONALACCOUNTDELEGATE_H +#include "kmm_widgets_export.h" + #include #include "payeeidentifier/nationalaccount/nationalaccount.h" #include "payeeidentifier/payeeidentifiertyped.h" -class nationalAccountDelegate : public QStyledItemDelegate +class KMM_WIDGETS_EXPORT nationalAccountDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit nationalAccountDelegate(QObject* parent, const QVariantList& options = QVariantList()); void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const final override; QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const final override; QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const final override; void setEditorData(QWidget* editor, const QModelIndex& index) const final override; void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const final override; void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const final override; Q_SIGNALS: void sizeHintChanged(const QModelIndex&) const; private: inline payeeIdentifierTyped identByIndex(const QModelIndex& index) const; }; #endif // NATIONALACCOUNTDELEGATE_H diff --git a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountedit.cpp b/kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountedit.cpp similarity index 89% rename from kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountedit.cpp rename to kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountedit.cpp index cd06ec2a9..87136d4a7 100644 --- a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountedit.cpp +++ b/kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountedit.cpp @@ -1,95 +1,94 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian Dávid + * Copyright 2014-2015 Christian Dávid * * 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. + * 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 "nationalaccountedit.h" #include "payeeidentifier/payeeidentifiertyped.h" #include "payeeidentifier/nationalaccount/nationalaccount.h" #include "ui_nationalaccountedit.h" struct nationalAccountEdit::Private { Ui::nationalAccountEdit ui; payeeIdentifier m_identifier; }; nationalAccountEdit::nationalAccountEdit(QWidget* parent) : QWidget(parent), d(new Private) { d->ui.setupUi(this); setFocusProxy(d->ui.accountNumberEdit); connect(d->ui.accountNumberEdit, SIGNAL(textChanged(QString)), this, SIGNAL(accountNumberChannged(QString))); connect(d->ui.institutionCodeEdit, SIGNAL(textChanged(QString)), this, SIGNAL(institutionCodeChanged(QString))); connect(d->ui.accountNumberEdit, SIGNAL(returnPressed()), this, SLOT(editFinished())); connect(d->ui.institutionCodeEdit, SIGNAL(returnPressed()), this, SLOT(editFinished())); } payeeIdentifier nationalAccountEdit::identifier() const { if (!d->m_identifier.isNull()) { try { payeeIdentifierTyped ident(d->m_identifier); ident->setAccountNumber(d->ui.accountNumberEdit->text()); ident->setBankCode(d->ui.institutionCodeEdit->text()); } catch (const payeeIdentifier::empty &) { } catch (const payeeIdentifier::badCast &) { } } return d->m_identifier; } void nationalAccountEdit::editFinished() { emit commitData(this); emit closeEditor(this); } QString nationalAccountEdit::accountNumber() const { return d->ui.accountNumberEdit->text(); } QString nationalAccountEdit::institutionCode() const { return d->ui.institutionCodeEdit->text(); } void nationalAccountEdit::setIdentifier(const payeeIdentifier& ident) { try { payeeIdentifierTyped identTyped(ident); d->ui.accountNumberEdit->setText(identTyped->accountNumber()); d->ui.institutionCodeEdit->setText(identTyped->bankCode()); d->m_identifier = ident; } catch (const payeeIdentifier::empty &) { } catch (const payeeIdentifier::badCast &) { } } void nationalAccountEdit::setAccountNumber(const QString& accountNumber) { d->ui.accountNumberEdit->setText(accountNumber); } void nationalAccountEdit::setInstitutionCode(const QString& institutionCode) { d->ui.institutionCodeEdit->setText(institutionCode); } diff --git a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountedit.h b/kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountedit.h similarity index 83% rename from kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountedit.h rename to kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountedit.h index 1ff4bc29f..cf775f787 100644 --- a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountedit.h +++ b/kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountedit.h @@ -1,63 +1,62 @@ /* - * This file is part of KMyMoney, A Personal Finance Manager by KDE - * Copyright (C) 2014 Christian Dávid + * Copyright 2014-2015 Christian Dávid * * 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. + * 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 NATIONALACCOUNTEDIT_H #define NATIONALACCOUNTEDIT_H #include #include "payeeidentifier/payeeidentifier.h" namespace Ui { class nationalAccountEdit; } class nationalAccountEdit : public QWidget { Q_OBJECT Q_PROPERTY(payeeIdentifier identifier READ identifier WRITE setIdentifier STORED true) Q_PROPERTY(QString accountNumber READ accountNumber WRITE setAccountNumber NOTIFY accountNumberChannged STORED false DESIGNABLE true) Q_PROPERTY(QString institutionCode READ institutionCode WRITE setInstitutionCode NOTIFY institutionCodeChanged STORED false DESIGNABLE true) public: explicit nationalAccountEdit(QWidget* parent = 0); payeeIdentifier identifier() const; QString accountNumber() const; QString institutionCode() const; public Q_SLOTS: void setIdentifier(const payeeIdentifier&); void setAccountNumber(const QString&); void setInstitutionCode(const QString&); Q_SIGNALS: void institutionCodeChanged(QString); void accountNumberChannged(QString); void commitData(QWidget*); void closeEditor(QWidget*); private Q_SLOTS: void editFinished(); private: struct Private; Private* d; }; #endif // NATIONALACCOUNTEDIT_H diff --git a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountedit.ui b/kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountedit.ui similarity index 100% rename from kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountedit.ui rename to kmymoney/widgets/payeeidentifier/nationalaccount/nationalaccountedit.ui